newsyslog.c revision 1.20 1 /* $NetBSD: newsyslog.c,v 1.20 1999/10/06 13:26:28 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.20 1999/10/06 13:26:28 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 NONE -1
75
76 struct conf_entry {
77 char *log; /* Name of the log */
78 int uid; /* Owner of log */
79 int gid; /* Group of log */
80 int numlogs; /* Number of logs to keep */
81 int size; /* Size cutoff to trigger trimming the log */
82 int hours; /* Hours between log trimming */
83 int permissions; /* File permissions on the log */
84 int flags; /* Flags (CE_COMPACT & CE_BINARY) */
85 char *pidfile; /* Name of file containing PID to signal */
86 struct conf_entry *next; /* Linked list pointer */
87 };
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[MAXHOSTNAMELEN + 1]; /* hostname */
99 char *daytime; /* timenow in human readable form */
100
101
102 void PRS __P((int, char **));
103 int age_old_log __P((char *));
104 void compress_log __P((char *));
105 void dotrim __P((char *, int, int, int, int, int, char *));
106 void do_entry __P((struct conf_entry *));
107 int isnumber __P((char *));
108 int log_trim __P((char *));
109 int main __P((int, char **));
110 char *missing_field __P((char *, char *));
111 struct conf_entry *parse_file __P((void));
112 int sizefile __P((char *));
113 char *sob __P((char *));
114 char *son __P((char *));
115 void usage __P((void));
116
117 int
118 main(argc,argv)
119 int argc;
120 char **argv;
121 {
122 struct conf_entry *p, *q;
123
124 PRS(argc,argv);
125 if (needroot && getuid() && geteuid()) {
126 fprintf(stderr,"%s: must have root privs\n",progname);
127 exit(1);
128 }
129 p = q = parse_file();
130 while (p) {
131 do_entry(p);
132 p=p->next;
133 free((char *) q);
134 q=p;
135 }
136 exit(0);
137 }
138
139 void
140 do_entry(ent)
141 struct conf_entry *ent;
142 {
143 int size, modtime;
144
145 if (verbose) {
146 if (ent->flags & CE_COMPACT)
147 printf("%s <%dZ>: ",ent->log,ent->numlogs);
148 else
149 printf("%s <%d>: ",ent->log,ent->numlogs);
150 }
151 size = sizefile(ent->log);
152 modtime = age_old_log(ent->log);
153 if (size < 0) {
154 if (verbose)
155 printf("does not exist.\n");
156 } else {
157 if (verbose && (ent->size > 0))
158 printf("size (Kb): %d [%d] ", size, ent->size);
159 if (verbose && (ent->hours > 0))
160 printf(" age (hr): %d [%d] ", modtime, ent->hours);
161 if (((ent->size > 0) && (size >= ent->size)) ||
162 ((ent->hours > 0) && ((modtime >= ent->hours)
163 || (modtime < 0)))) {
164 if (verbose)
165 printf("--> trimming log....\n");
166 if (noaction && !verbose) {
167 if (ent->flags & CE_COMPACT)
168 printf("%s <%dZ>: trimming",
169 ent->log,ent->numlogs);
170 else
171 printf("%s <%d>: trimming",
172 ent->log,ent->numlogs);
173 }
174 dotrim(ent->log, ent->numlogs, ent->flags,
175 ent->permissions, ent->uid, ent->gid,
176 ent->pidfile);
177 } else {
178 if (verbose)
179 printf("--> skipping\n");
180 }
181 }
182 }
183
184 void
185 PRS(argc,argv)
186 int argc;
187 char **argv;
188 {
189 int c;
190 FILE *f;
191 char line[BUFSIZ];
192 char *p;
193
194 progname = argv[0];
195 timenow = time((time_t *) 0);
196 daytime = ctime(&timenow) + 4;
197 daytime[15] = '\0';
198
199 /* Let's find the pid of syslogd */
200 syslog_pid = 0;
201 f = fopen(PIDFILE,"r");
202 if (f && fgets(line,BUFSIZ,f))
203 syslog_pid = atoi(line);
204 if (f)
205 (void)fclose(f);
206
207 /* Let's get our hostname */
208 (void)gethostname(hostname, sizeof(hostname));
209 hostname[sizeof(hostname) - 1] = '\0';
210
211 /* Truncate domain */
212 if ((p = strchr(hostname, '.')) != NULL) {
213 *p = '\0';
214 }
215
216 optind = 1; /* Start options parsing */
217 while ((c=getopt(argc,argv,"nrvf:t:")) != -1)
218 switch (c) {
219 case 'n':
220 noaction++; /* This implies needroot as off */
221 /* fall through */
222 case 'r':
223 needroot = 0;
224 break;
225 case 'v':
226 verbose++;
227 break;
228 case 'f':
229 conf = optarg;
230 break;
231 default:
232 usage();
233 }
234 }
235
236 void
237 usage()
238 {
239 fprintf(stderr,
240 "Usage: %s <-nrv> <-f config-file>\n", progname);
241 exit(1);
242 }
243
244 /* Parse a configuration file and return a linked list of all the logs
245 * to process
246 */
247 struct conf_entry *
248 parse_file()
249 {
250 FILE *f;
251 char line[BUFSIZ], *parse, *q;
252 char *errline, *group;
253 struct conf_entry *first = NULL;
254 struct conf_entry *working;
255 struct passwd *pass;
256 struct group *grp;
257
258 working = NULL;
259 if (strcmp(conf,"-"))
260 f = fopen(conf,"r");
261 else
262 f = stdin;
263 if (!f) {
264 (void) fprintf(stderr,"%s: ",progname);
265 perror(conf);
266 exit(1);
267 }
268 while (fgets(line,BUFSIZ,f)) {
269 if ((line[0]== '\n') || (line[0] == '#'))
270 continue;
271 errline = strdup(line);
272 if (!first) {
273 working = (struct conf_entry *) malloc(sizeof(struct conf_entry));
274 first = working;
275 } else {
276 working->next = (struct conf_entry *) malloc(sizeof(struct conf_entry));
277 working = working->next;
278 }
279
280 q = parse = missing_field(sob(line),errline);
281 *(parse = son(line)) = '\0';
282 working->log = strdup(q);
283
284 q = parse = missing_field(sob(++parse),errline);
285 *(parse = son(parse)) = '\0';
286 if ((group = strchr(q, ':')) != NULL || (group = strchr(q, '.')) != NULL) {
287 *group++ = '\0';
288 if (*q) {
289 if (!(isnumber(q))) {
290 if ((pass = getpwnam(q)) == NULL) {
291 fprintf(stderr,
292 "Error in config file; unknown user:\n");
293 fputs(errline,stderr);
294 exit(1);
295 }
296 working->uid = pass->pw_uid;
297 } else
298 working->uid = atoi(q);
299 } else
300 working->uid = NONE;
301
302 q = group;
303 if (*q) {
304 if (!(isnumber(q))) {
305 if ((grp = getgrnam(q)) == NULL) {
306 fprintf(stderr,
307 "Error in config file; unknown group:\n");
308 fputs(errline,stderr);
309 exit(1);
310 }
311 working->gid = grp->gr_gid;
312 } else
313 working->gid = atoi(q);
314 } else
315 working->gid = NONE;
316
317 q = parse = missing_field(sob(++parse),errline);
318 *(parse = son(parse)) = '\0';
319 }
320 else
321 working->uid = working->gid = NONE;
322
323 if (!sscanf(q,"%o",&working->permissions)) {
324 fprintf(stderr,
325 "Error in config file; bad permissions:\n");
326 fputs(errline,stderr);
327 exit(1);
328 }
329
330 q = parse = missing_field(sob(++parse),errline);
331 *(parse = son(parse)) = '\0';
332 if (!sscanf(q,"%d",&working->numlogs)) {
333 fprintf(stderr,
334 "Error in config file; bad number:\n");
335 fputs(errline,stderr);
336 exit(1);
337 }
338
339 q = parse = missing_field(sob(++parse),errline);
340 *(parse = son(parse)) = '\0';
341 if (isdigit((unsigned char)*q))
342 working->size = atoi(q);
343 else
344 working->size = -1;
345
346 q = parse = missing_field(sob(++parse),errline);
347 *(parse = son(parse)) = '\0';
348 if (isdigit((unsigned char)*q))
349 working->hours = atoi(q);
350 else
351 working->hours = -1;
352
353 q = parse = sob(++parse); /* Optional field */
354 *(parse = son(parse)) = '\0';
355 working->flags = 0;
356 while (q && *q && !isspace((unsigned char)*q)) {
357 if ((*q == 'Z') || (*q == 'z'))
358 working->flags |= CE_COMPACT;
359 else if ((*q == 'B') || (*q == 'b'))
360 working->flags |= CE_BINARY;
361 else {
362 fprintf(stderr,
363 "Illegal flag in config file -- %c\n",
364 *q);
365 exit(1);
366 }
367 q++;
368 }
369
370 if ((q = parse = sob(++parse)) != NULL && q[0] != '\0') {
371 *(parse = son(parse)) = '\0';
372 working->pidfile = strdup(q);
373 } else
374 working->pidfile = NULL;
375
376 free(errline);
377 }
378 if (working)
379 working->next = (struct conf_entry *) NULL;
380 (void) fclose(f);
381 return(first);
382 }
383
384 char *
385 missing_field(p,errline)
386 char *p,*errline;
387 {
388 if (!p || !*p) {
389 fprintf(stderr,"Missing field in config file:\n");
390 fputs(errline,stderr);
391 exit(1);
392 }
393 return(p);
394 }
395
396 void
397 dotrim(log,numdays,flags,perm,owner_uid,group_gid,pidfile)
398 char *log;
399 int numdays;
400 int flags;
401 int perm;
402 int owner_uid;
403 int group_gid;
404 char *pidfile;
405 {
406 char file1[128], file2[128];
407 char zfile1[128], zfile2[128];
408 char line[BUFSIZ];
409 int fd;
410 struct stat st;
411 int ngen = numdays;
412 FILE *f;
413 pid_t pid;
414
415 #ifdef _IBMR2
416 /* AIX 3.1 has a broken fchown- if the owner_uid is -1, it will actually */
417 /* change it to be owned by uid -1, instead of leaving it as is, as it is */
418 /* supposed to. */
419 if (owner_uid == -1)
420 owner_uid = geteuid();
421 #endif
422
423 if (pidfile != NULL && pidfile[0] != '\0') {
424 if ((f = fopen(pidfile,"r")) == NULL) {
425 (void) fprintf(stderr,"%s: ",progname);
426 perror(conf);
427 return;
428 }
429
430 if (fgets(line,BUFSIZ,f))
431 pid = atoi(line);
432 if (f)
433 (void)fclose(f);
434 } else
435 pid = syslog_pid;
436
437 /* Remove oldest log */
438 (void) sprintf(file1,"%s.%d",log,numdays);
439 (void) strcpy(zfile1, file1);
440 (void) strcat(zfile1, COMPRESS_POSTFIX);
441
442 if (noaction) {
443 printf("rm -f %s\n", file1);
444 printf("rm -f %s\n", zfile1);
445 } else {
446 (void) unlink(file1);
447 (void) unlink(zfile1);
448 }
449
450 /* Move down log files */
451 while (numdays--) {
452 (void) strcpy(file2,file1);
453 (void) sprintf(file1,"%s.%d",log,numdays);
454 (void) strcpy(zfile1, file1);
455 (void) strcpy(zfile2, file2);
456 if (lstat(file1, &st)) {
457 (void) strcat(zfile1, COMPRESS_POSTFIX);
458 (void) strcat(zfile2, COMPRESS_POSTFIX);
459 if (lstat(zfile1, &st)) continue;
460 }
461 if (noaction) {
462 printf("mv %s %s\n",zfile1,zfile2);
463 printf("chmod %o %s\n", perm, zfile2);
464 printf("chown %d.%d %s\n",
465 owner_uid, group_gid, zfile2);
466 } else {
467 (void) rename(zfile1, zfile2);
468 (void) chmod(zfile2, perm);
469 (void) chown(zfile2, owner_uid, group_gid);
470 }
471 }
472 if (!noaction && !(flags & CE_BINARY))
473 (void) log_trim(log); /* Report the trimming to the old log */
474
475 if (ngen == 0) {
476 if (noaction)
477 printf("rm %s\n",log);
478 else
479 (void) unlink(log);
480 } else
481 if (noaction)
482 printf("mv %s to %s\n",log,file1);
483 else
484 (void) rename(log,file1);
485
486 if (noaction)
487 printf("Start new log...");
488 else {
489 fd = creat(log,perm);
490 if (fd < 0) {
491 perror("can't start new log");
492 exit(1);
493 }
494 if (fchown(fd, owner_uid, group_gid)) {
495 perror("can't chmod new log file");
496 exit(1);
497 }
498 (void) close(fd);
499 if (!(flags & CE_BINARY))
500 if (log_trim(log)) { /* Add status message */
501 perror("can't add status message to log");
502 exit(1);
503 }
504 }
505 if (noaction)
506 printf("chmod %o %s...",perm,log);
507 else
508 (void) chmod(log,perm);
509 if (noaction)
510 printf("kill -HUP %d\n",pid);
511 else
512 if (pid < MIN_PID || pid > MAX_PID) {
513 fprintf(stderr,"%s: preposterous process number: %d\n",
514 progname, pid);
515 } else if (kill(pid,SIGHUP)) {
516 fprintf(stderr,"%s: ",progname);
517 perror("warning - could not restart daemon");
518 }
519 if (flags & CE_COMPACT) {
520 if (noaction)
521 printf("Compress %s.0\n",log);
522 else
523 compress_log(log);
524 }
525 }
526
527 /* Log the fact that the logs were turned over */
528 int
529 log_trim(log)
530 char *log;
531 {
532 FILE *f;
533 if ((f = fopen(log,"a")) == NULL)
534 return(-1);
535 fprintf(f,"%s %s newsyslog[%ld]: logfile turned over\n",
536 daytime, hostname, (u_long)getpid());
537 if (fclose(f) == EOF) {
538 perror("log_trim: fclose");
539 exit(1);
540 }
541 return(0);
542 }
543
544 /* Fork of /usr/ucb/compress to compress the old log file */
545 void
546 compress_log(log)
547 char *log;
548 {
549 int pid;
550 char tmp[128];
551
552 pid = fork();
553 (void) sprintf(tmp,"%s.0",log);
554 if (pid < 0) {
555 fprintf(stderr,"%s: ",progname);
556 perror("fork");
557 exit(1);
558 } else if (!pid) {
559 (void) execl(COMPRESS,"compress","-f",tmp,0);
560 fprintf(stderr,"%s: ",progname);
561 perror(COMPRESS);
562 exit(1);
563 }
564 }
565
566 /* Return size in kilobytes of a file */
567 int
568 sizefile(file)
569 char *file;
570 {
571 struct stat sb;
572
573 if (stat(file,&sb) < 0)
574 return(-1);
575 return(kbytes(dbtob(sb.st_blocks)));
576 }
577
578 /* Return the age of old log file (file.0) */
579 int
580 age_old_log(file)
581 char *file;
582 {
583 struct stat sb;
584 char tmp[MAXPATHLEN+3];
585
586 (void) strcpy(tmp,file);
587 if (stat(strcat(tmp,".0"),&sb) < 0)
588 if (stat(strcat(tmp,COMPRESS_POSTFIX), &sb) < 0)
589 return(-1);
590 return( (int) (timenow - sb.st_mtime + 1800) / 3600);
591 }
592
593 /* Skip Over Blanks */
594 char *sob(p)
595 char *p;
596 {
597 while (p && *p && isspace((unsigned char)*p))
598 p++;
599 return(p);
600 }
601
602 /* Skip Over Non-Blanks */
603 char *
604 son(p)
605 char *p;
606 {
607 while (p && *p && !isspace((unsigned char)*p))
608 p++;
609 return(p);
610 }
611
612
613 /* Check if string is actually a number */
614
615 int
616 isnumber(string)
617 char *string;
618 {
619 while (*string != '\0') {
620 if (*string < '0' || *string > '9') return(0);
621 string++;
622 }
623 return(1);
624 }
625