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