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