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