newsyslog.c revision 1.7 1 /*
2 * This file contains changes from the Open Software Foundation.
3 */
4
5 /*
6
7 Copyright 1988, 1989 by the Massachusetts Institute of Technology
8
9 Permission to use, copy, modify, and distribute this software
10 and its documentation for any purpose and without fee is
11 hereby granted, provided that the above copyright notice
12 appear in all copies and that both that copyright notice and
13 this permission notice appear in supporting documentation,
14 and that the names of M.I.T. and the M.I.T. S.I.P.B. not be
15 used in advertising or publicity pertaining to distribution
16 of the software without specific, written prior permission.
17 M.I.T. and the M.I.T. S.I.P.B. make no representations about
18 the suitability of this software for any purpose. It is
19 provided "as is" without express or implied warranty.
20
21 */
22
23 /*
24 * newsyslog - roll over selected logs at the appropriate time,
25 * keeping the a specified number of backup files around.
26 *
27 * $Source: /tank/opengrok/rsync2/NetBSD/src/usr.bin/newsyslog/newsyslog.c,v $
28 * $Author: pk $
29 */
30
31 #ifndef lint
32 static char rcsid[] = "$Id: newsyslog.c,v 1.7 1994/02/20 09:54:45 pk Exp $";
33 #endif /* not lint */
34
35 #ifndef CONF
36 #define CONF "/etc/athena/newsyslog.conf" /* Configuration file */
37 #endif
38 #ifndef PIDFILE
39 #define PIDFILE "/etc/syslog.pid"
40 #endif
41 #ifndef COMPRESS
42 #define COMPRESS "/usr/ucb/compress" /* File compression program */
43 #endif
44 #ifndef COMPRESS_POSTFIX
45 #define COMPRESS_POSTFIX ".Z"
46 #endif
47
48 #include <stdio.h>
49 #include <string.h>
50 #include <ctype.h>
51 #include <signal.h>
52 #include <pwd.h>
53 #include <grp.h>
54 #include <sys/types.h>
55 #include <sys/time.h>
56 #include <sys/stat.h>
57 #include <sys/param.h>
58 #include <sys/wait.h>
59
60 #define kbytes(size) (((size) + 1023) >> 10)
61 #ifdef _IBMR2
62 /* Calculates (db * DEV_BSIZE) */
63 #define dbtob(db) ((unsigned)(db) << UBSHIFT)
64 #endif
65
66 #define CE_COMPACT 1 /* Compact the achived log files */
67 #define CE_BINARY 2 /* Logfile is in binary, don't add */
68 /* status messages */
69 #define NONE -1
70
71 struct conf_entry {
72 char *log; /* Name of the log */
73 int uid; /* Owner of log */
74 int gid; /* Group of log */
75 int numlogs; /* Number of logs to keep */
76 int size; /* Size cutoff to trigger trimming the log */
77 int hours; /* Hours between log trimming */
78 int permissions; /* File permissions on the log */
79 int flags; /* Flags (CE_COMPACT & CE_BINARY) */
80 struct conf_entry *next; /* Linked list pointer */
81 };
82
83 extern int optind;
84 extern char *optarg;
85 extern char *malloc();
86 extern uid_t getuid(),geteuid();
87 extern time_t time();
88
89 char *progname; /* contains argv[0] */
90 int verbose = 0; /* Print out what's going on */
91 int needroot = 1; /* Root privs are necessary */
92 int noaction = 0; /* Don't do anything, just show it */
93 char *conf = CONF; /* Configuration file to use */
94 time_t timenow;
95 int syslog_pid; /* read in from /etc/syslog.pid */
96 #define MIN_PID 3
97 #define MAX_PID 65534
98 char hostname[64]; /* hostname */
99 char *daytime; /* timenow in human readable form */
100
101
102 struct conf_entry *parse_file();
103 char *sob(), *son(), *strdup(), *missing_field();
104
105 main(argc,argv)
106 int argc;
107 char **argv;
108 {
109 struct conf_entry *p, *q;
110
111 PRS(argc,argv);
112 if (needroot && getuid() && geteuid()) {
113 fprintf(stderr,"%s: must have root privs\n",progname);
114 exit(1);
115 }
116 p = q = parse_file();
117 while (p) {
118 do_entry(p);
119 p=p->next;
120 free((char *) q);
121 q=p;
122 }
123 exit(0);
124 }
125
126 do_entry(ent)
127 struct conf_entry *ent;
128
129 {
130 int size, modtime;
131
132 if (verbose) {
133 if (ent->flags & CE_COMPACT)
134 printf("%s <%dZ>: ",ent->log,ent->numlogs);
135 else
136 printf("%s <%d>: ",ent->log,ent->numlogs);
137 }
138 size = sizefile(ent->log);
139 modtime = age_old_log(ent->log);
140 if (size < 0) {
141 if (verbose)
142 printf("does not exist.\n");
143 } else {
144 if (verbose && (ent->size > 0))
145 printf("size (Kb): %d [%d] ", size, ent->size);
146 if (verbose && (ent->hours > 0))
147 printf(" age (hr): %d [%d] ", modtime, ent->hours);
148 if (((ent->size > 0) && (size >= ent->size)) ||
149 ((ent->hours > 0) && ((modtime >= ent->hours)
150 || (modtime < 0)))) {
151 if (verbose)
152 printf("--> trimming log....\n");
153 if (noaction && !verbose) {
154 if (ent->flags & CE_COMPACT)
155 printf("%s <%dZ>: trimming",
156 ent->log,ent->numlogs);
157 else
158 printf("%s <%d>: trimming",
159 ent->log,ent->numlogs);
160 }
161 dotrim(ent->log, ent->numlogs, ent->flags,
162 ent->permissions, ent->uid, ent->gid);
163 } else {
164 if (verbose)
165 printf("--> skipping\n");
166 }
167 }
168 }
169
170 PRS(argc,argv)
171 int argc;
172 char **argv;
173 {
174 int c;
175 FILE *f;
176 char line[BUFSIZ];
177
178 progname = argv[0];
179 timenow = time((time_t *) 0);
180 daytime = ctime(&timenow);
181 daytime[strlen(daytime)-1] = '\0';
182
183 /* Let's find the pid of syslogd */
184 syslog_pid = 0;
185 f = fopen(PIDFILE,"r");
186 if (f && fgets(line,BUFSIZ,f))
187 syslog_pid = atoi(line);
188 if (f)
189 (void)fclose(f);
190
191 /* Let's get our hostname */
192 if (gethostname(hostname, 64)) {
193 perror("gethostname");
194 (void) strcpy(hostname,"Mystery Host");
195 }
196
197 optind = 1; /* Start options parsing */
198 while ((c=getopt(argc,argv,"nrvf:t:")) != EOF)
199 switch (c) {
200 case 'n':
201 noaction++; /* This implies needroot as off */
202 /* fall through */
203 case 'r':
204 needroot = 0;
205 break;
206 case 'v':
207 verbose++;
208 break;
209 case 'f':
210 conf = optarg;
211 break;
212 default:
213 usage();
214 }
215 }
216
217 usage()
218 {
219 fprintf(stderr,
220 "Usage: %s <-nrv> <-f config-file>\n");
221 exit(1);
222 }
223
224 /* Parse a configuration file and return a linked list of all the logs
225 * to process
226 */
227 struct conf_entry *parse_file()
228 {
229 FILE *f;
230 char line[BUFSIZ], *parse, *q;
231 char *errline, *group;
232 struct conf_entry *first = NULL;
233 struct conf_entry *working;
234 struct passwd *pass;
235 struct group *grp;
236
237 if (strcmp(conf,"-"))
238 f = fopen(conf,"r");
239 else
240 f = stdin;
241 if (!f) {
242 (void) fprintf(stderr,"%s: ",progname);
243 perror(conf);
244 exit(1);
245 }
246 while (fgets(line,BUFSIZ,f)) {
247 if ((line[0]== '\n') || (line[0] == '#'))
248 continue;
249 errline = strdup(line);
250 if (!first) {
251 working = (struct conf_entry *) malloc(sizeof(struct conf_entry));
252 first = working;
253 } else {
254 working->next = (struct conf_entry *) malloc(sizeof(struct conf_entry));
255 working = working->next;
256 }
257
258 q = parse = missing_field(sob(line),errline);
259 *(parse = son(line)) = '\0';
260 working->log = strdup(q);
261
262 q = parse = missing_field(sob(++parse),errline);
263 *(parse = son(parse)) = '\0';
264 if ((group = strchr(q, '.')) != NULL) {
265 *group++ = '\0';
266 if (*q) {
267 if (!(isnumber(q))) {
268 if ((pass = getpwnam(q)) == NULL) {
269 fprintf(stderr,
270 "Error in config file; unknown user:\n");
271 fputs(errline,stderr);
272 exit(1);
273 }
274 working->uid = pass->pw_uid;
275 } else
276 working->uid = atoi(q);
277 } else
278 working->uid = NONE;
279
280 q = group;
281 if (*q) {
282 if (!(isnumber(q))) {
283 if ((grp = getgrnam(q)) == NULL) {
284 fprintf(stderr,
285 "Error in config file; unknown group:\n");
286 fputs(errline,stderr);
287 exit(1);
288 }
289 working->gid = grp->gr_gid;
290 } else
291 working->gid = atoi(q);
292 } else
293 working->gid = NONE;
294
295 q = parse = missing_field(sob(++parse),errline);
296 *(parse = son(parse)) = '\0';
297 }
298 else
299 working->uid = working->gid = NONE;
300
301 if (!sscanf(q,"%o",&working->permissions)) {
302 fprintf(stderr,
303 "Error in config file; bad permissions:\n");
304 fputs(errline,stderr);
305 exit(1);
306 }
307
308 q = parse = missing_field(sob(++parse),errline);
309 *(parse = son(parse)) = '\0';
310 if (!sscanf(q,"%d",&working->numlogs)) {
311 fprintf(stderr,
312 "Error in config file; bad number:\n");
313 fputs(errline,stderr);
314 exit(1);
315 }
316
317 q = parse = missing_field(sob(++parse),errline);
318 *(parse = son(parse)) = '\0';
319 if (isdigit(*q))
320 working->size = atoi(q);
321 else
322 working->size = -1;
323
324 q = parse = missing_field(sob(++parse),errline);
325 *(parse = son(parse)) = '\0';
326 if (isdigit(*q))
327 working->hours = atoi(q);
328 else
329 working->hours = -1;
330
331 q = parse = sob(++parse); /* Optional field */
332 *(parse = son(parse)) = '\0';
333 working->flags = 0;
334 while (q && *q && !isspace(*q)) {
335 if ((*q == 'Z') || (*q == 'z'))
336 working->flags |= CE_COMPACT;
337 else if ((*q == 'B') || (*q == 'b'))
338 working->flags |= CE_BINARY;
339 else {
340 fprintf(stderr,
341 "Illegal flag in config file -- %c\n",
342 *q);
343 exit(1);
344 }
345 q++;
346 }
347
348 free(errline);
349 }
350 if (working)
351 working->next = (struct conf_entry *) NULL;
352 (void) fclose(f);
353 return(first);
354 }
355
356 char *missing_field(p,errline)
357 char *p,*errline;
358 {
359 if (!p || !*p) {
360 fprintf(stderr,"Missing field in config file:\n");
361 fputs(errline,stderr);
362 exit(1);
363 }
364 return(p);
365 }
366
367 dotrim(log,numdays,flags,perm,owner_uid,group_gid)
368 char *log;
369 int numdays;
370 int flags;
371 int perm;
372 int owner_uid;
373 int group_gid;
374 {
375 char file1[128], file2[128];
376 char zfile1[128], zfile2[128];
377 int fd;
378 struct stat st;
379
380 #ifdef _IBMR2
381 /* AIX 3.1 has a broken fchown- if the owner_uid is -1, it will actually */
382 /* change it to be owned by uid -1, instead of leaving it as is, as it is */
383 /* supposed to. */
384 if (owner_uid == -1)
385 owner_uid = geteuid();
386 #endif
387
388 /* Remove oldest log */
389 (void) sprintf(file1,"%s.%d",log,numdays);
390 (void) strcpy(zfile1, file1);
391 (void) strcat(zfile1, COMPRESS_POSTFIX);
392
393 if (noaction) {
394 printf("rm -f %s\n", file1);
395 printf("rm -f %s\n", zfile1);
396 } else {
397 (void) unlink(file1);
398 (void) unlink(zfile1);
399 }
400
401 /* Move down log files */
402 while (numdays--) {
403 (void) strcpy(file2,file1);
404 (void) sprintf(file1,"%s.%d",log,numdays);
405 (void) strcpy(zfile1, file1);
406 (void) strcpy(zfile2, file2);
407 if (lstat(file1, &st)) {
408 (void) strcat(zfile1, COMPRESS_POSTFIX);
409 (void) strcat(zfile2, COMPRESS_POSTFIX);
410 if (lstat(zfile1, &st)) continue;
411 }
412 if (noaction) {
413 printf("mv %s %s\n",zfile1,zfile2);
414 printf("chmod %o %s\n", perm, zfile2);
415 printf("chown %d.%d %s\n",
416 owner_uid, group_gid, zfile2);
417 } else {
418 (void) rename(zfile1, zfile2);
419 (void) chmod(zfile2, perm);
420 (void) chown(zfile2, owner_uid, group_gid);
421 }
422 }
423 if (!noaction && !(flags & CE_BINARY))
424 (void) log_trim(log); /* Report the trimming to the old log */
425
426 if (noaction)
427 printf("mv %s to %s\n",log,file1);
428 else
429 (void) rename(log,file1);
430 if (noaction)
431 printf("Start new log...");
432 else {
433 fd = creat(log,perm);
434 if (fd < 0) {
435 perror("can't start new log");
436 exit(1);
437 }
438 if (fchown(fd, owner_uid, group_gid)) {
439 perror("can't chmod new log file");
440 exit(1);
441 }
442 (void) close(fd);
443 if (!(flags & CE_BINARY))
444 if (log_trim(log)) { /* Add status message */
445 perror("can't add status message to log");
446 exit(1);
447 }
448 }
449 if (noaction)
450 printf("chmod %o %s...",perm,log);
451 else
452 (void) chmod(log,perm);
453 if (noaction)
454 printf("kill -HUP %d (syslogd)\n",syslog_pid);
455 else
456 if (syslog_pid < MIN_PID || syslog_pid > MAX_PID) {
457 fprintf(stderr,"%s: preposterous process number: %d\n",
458 progname, syslog_pid);
459 } else if (kill(syslog_pid,SIGHUP)) {
460 fprintf(stderr,"%s: ",progname);
461 perror("warning - could not restart syslogd");
462 }
463 if (flags & CE_COMPACT) {
464 if (noaction)
465 printf("Compress %s.0\n",log);
466 else
467 compress_log(log);
468 }
469 }
470
471 /* Log the fact that the logs were turned over */
472 log_trim(log)
473 char *log;
474 {
475 FILE *f;
476 if ((f = fopen(log,"a")) == NULL)
477 return(-1);
478 fprintf(f,"%s %s newsyslog[%d]: logfile turned over\n",
479 daytime, hostname, getpid());
480 if (fclose(f) == EOF) {
481 perror("log_trim: fclose:");
482 exit(1);
483 }
484 return(0);
485 }
486
487 /* Fork of /usr/ucb/compress to compress the old log file */
488 compress_log(log)
489 char *log;
490 {
491 int pid;
492 char tmp[128];
493
494 pid = fork();
495 (void) sprintf(tmp,"%s.0",log);
496 if (pid < 0) {
497 fprintf(stderr,"%s: ",progname);
498 perror("fork");
499 exit(1);
500 } else if (!pid) {
501 (void) execl(COMPRESS,"compress","-f",tmp,0);
502 fprintf(stderr,"%s: ",progname);
503 perror(COMPRESS);
504 exit(1);
505 }
506 }
507
508 /* Return size in kilobytes of a file */
509 int sizefile(file)
510 char *file;
511 {
512 struct stat sb;
513
514 if (stat(file,&sb) < 0)
515 return(-1);
516 return(kbytes(dbtob(sb.st_blocks)));
517 }
518
519 /* Return the age of old log file (file.0) */
520 int age_old_log(file)
521 char *file;
522 {
523 struct stat sb;
524 char tmp[MAXPATHLEN+3];
525
526 (void) strcpy(tmp,file);
527 if (stat(strcat(tmp,".0"),&sb) < 0)
528 if (stat(strcat(tmp,COMPRESS_POSTFIX), &sb) < 0)
529 return(-1);
530 return( (int) (timenow - sb.st_mtime + 1800) / 3600);
531 }
532
533
534 #ifndef OSF
535 /* Duplicate a string using malloc */
536
537 char *strdup(strp)
538 register char *strp;
539 {
540 register char *cp;
541
542 if ((cp = malloc((unsigned) strlen(strp) + 1)) == NULL)
543 abort();
544 return(strcpy (cp, strp));
545 }
546 #endif
547
548 /* Skip Over Blanks */
549 char *sob(p)
550 register char *p;
551 {
552 while (p && *p && isspace(*p))
553 p++;
554 return(p);
555 }
556
557 /* Skip Over Non-Blanks */
558 char *son(p)
559 register char *p;
560 {
561 while (p && *p && !isspace(*p))
562 p++;
563 return(p);
564 }
565
566
567 /* Check if string is actually a number */
568
569 isnumber(string)
570 char *string;
571 {
572 while (*string != '\0') {
573 if (*string < '0' || *string > '9') return(0);
574 string++;
575 }
576 return(1);
577 }
578