newsyslog.c revision 1.14 1 /* $NetBSD: newsyslog.c,v 1.14 1998/04/02 10:35:26 kleink 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.14 1998/04/02 10:35:26 kleink 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[64]; /* 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
208 /* Truncate domain */
209 if ((p = strchr(hostname, '.')) != NULL) {
210 *p = '\0';
211 }
212
213 optind = 1; /* Start options parsing */
214 while ((c=getopt(argc,argv,"nrvf:t:")) != -1)
215 switch (c) {
216 case 'n':
217 noaction++; /* This implies needroot as off */
218 /* fall through */
219 case 'r':
220 needroot = 0;
221 break;
222 case 'v':
223 verbose++;
224 break;
225 case 'f':
226 conf = optarg;
227 break;
228 default:
229 usage();
230 }
231 }
232
233 void
234 usage()
235 {
236 fprintf(stderr,
237 "Usage: %s <-nrv> <-f config-file>\n", progname);
238 exit(1);
239 }
240
241 /* Parse a configuration file and return a linked list of all the logs
242 * to process
243 */
244 struct conf_entry *
245 parse_file()
246 {
247 FILE *f;
248 char line[BUFSIZ], *parse, *q;
249 char *errline, *group;
250 struct conf_entry *first = NULL;
251 struct conf_entry *working;
252 struct passwd *pass;
253 struct group *grp;
254
255 working = NULL;
256 if (strcmp(conf,"-"))
257 f = fopen(conf,"r");
258 else
259 f = stdin;
260 if (!f) {
261 (void) fprintf(stderr,"%s: ",progname);
262 perror(conf);
263 exit(1);
264 }
265 while (fgets(line,BUFSIZ,f)) {
266 if ((line[0]== '\n') || (line[0] == '#'))
267 continue;
268 errline = strdup(line);
269 if (!first) {
270 working = (struct conf_entry *) malloc(sizeof(struct conf_entry));
271 first = working;
272 } else {
273 working->next = (struct conf_entry *) malloc(sizeof(struct conf_entry));
274 working = working->next;
275 }
276
277 q = parse = missing_field(sob(line),errline);
278 *(parse = son(line)) = '\0';
279 working->log = strdup(q);
280
281 q = parse = missing_field(sob(++parse),errline);
282 *(parse = son(parse)) = '\0';
283 if ((group = strchr(q, '.')) != NULL) {
284 *group++ = '\0';
285 if (*q) {
286 if (!(isnumber(q))) {
287 if ((pass = getpwnam(q)) == NULL) {
288 fprintf(stderr,
289 "Error in config file; unknown user:\n");
290 fputs(errline,stderr);
291 exit(1);
292 }
293 working->uid = pass->pw_uid;
294 } else
295 working->uid = atoi(q);
296 } else
297 working->uid = NONE;
298
299 q = group;
300 if (*q) {
301 if (!(isnumber(q))) {
302 if ((grp = getgrnam(q)) == NULL) {
303 fprintf(stderr,
304 "Error in config file; unknown group:\n");
305 fputs(errline,stderr);
306 exit(1);
307 }
308 working->gid = grp->gr_gid;
309 } else
310 working->gid = atoi(q);
311 } else
312 working->gid = NONE;
313
314 q = parse = missing_field(sob(++parse),errline);
315 *(parse = son(parse)) = '\0';
316 }
317 else
318 working->uid = working->gid = NONE;
319
320 if (!sscanf(q,"%o",&working->permissions)) {
321 fprintf(stderr,
322 "Error in config file; bad permissions:\n");
323 fputs(errline,stderr);
324 exit(1);
325 }
326
327 q = parse = missing_field(sob(++parse),errline);
328 *(parse = son(parse)) = '\0';
329 if (!sscanf(q,"%d",&working->numlogs)) {
330 fprintf(stderr,
331 "Error in config file; bad number:\n");
332 fputs(errline,stderr);
333 exit(1);
334 }
335
336 q = parse = missing_field(sob(++parse),errline);
337 *(parse = son(parse)) = '\0';
338 if (isdigit(*q))
339 working->size = atoi(q);
340 else
341 working->size = -1;
342
343 q = parse = missing_field(sob(++parse),errline);
344 *(parse = son(parse)) = '\0';
345 if (isdigit(*q))
346 working->hours = atoi(q);
347 else
348 working->hours = -1;
349
350 q = parse = sob(++parse); /* Optional field */
351 *(parse = son(parse)) = '\0';
352 working->flags = 0;
353 while (q && *q && !isspace(*q)) {
354 if ((*q == 'Z') || (*q == 'z'))
355 working->flags |= CE_COMPACT;
356 else if ((*q == 'B') || (*q == 'b'))
357 working->flags |= CE_BINARY;
358 else {
359 fprintf(stderr,
360 "Illegal flag in config file -- %c\n",
361 *q);
362 exit(1);
363 }
364 q++;
365 }
366
367 free(errline);
368 }
369 if (working)
370 working->next = (struct conf_entry *) NULL;
371 (void) fclose(f);
372 return(first);
373 }
374
375 char *
376 missing_field(p,errline)
377 char *p,*errline;
378 {
379 if (!p || !*p) {
380 fprintf(stderr,"Missing field in config file:\n");
381 fputs(errline,stderr);
382 exit(1);
383 }
384 return(p);
385 }
386
387 void
388 dotrim(log,numdays,flags,perm,owner_uid,group_gid)
389 char *log;
390 int numdays;
391 int flags;
392 int perm;
393 int owner_uid;
394 int group_gid;
395 {
396 char file1[128], file2[128];
397 char zfile1[128], zfile2[128];
398 int fd;
399 struct stat st;
400 int ngen = numdays;
401
402 #ifdef _IBMR2
403 /* AIX 3.1 has a broken fchown- if the owner_uid is -1, it will actually */
404 /* change it to be owned by uid -1, instead of leaving it as is, as it is */
405 /* supposed to. */
406 if (owner_uid == -1)
407 owner_uid = geteuid();
408 #endif
409
410 /* Remove oldest log */
411 (void) sprintf(file1,"%s.%d",log,numdays);
412 (void) strcpy(zfile1, file1);
413 (void) strcat(zfile1, COMPRESS_POSTFIX);
414
415 if (noaction) {
416 printf("rm -f %s\n", file1);
417 printf("rm -f %s\n", zfile1);
418 } else {
419 (void) unlink(file1);
420 (void) unlink(zfile1);
421 }
422
423 /* Move down log files */
424 while (numdays--) {
425 (void) strcpy(file2,file1);
426 (void) sprintf(file1,"%s.%d",log,numdays);
427 (void) strcpy(zfile1, file1);
428 (void) strcpy(zfile2, file2);
429 if (lstat(file1, &st)) {
430 (void) strcat(zfile1, COMPRESS_POSTFIX);
431 (void) strcat(zfile2, COMPRESS_POSTFIX);
432 if (lstat(zfile1, &st)) continue;
433 }
434 if (noaction) {
435 printf("mv %s %s\n",zfile1,zfile2);
436 printf("chmod %o %s\n", perm, zfile2);
437 printf("chown %d.%d %s\n",
438 owner_uid, group_gid, zfile2);
439 } else {
440 (void) rename(zfile1, zfile2);
441 (void) chmod(zfile2, perm);
442 (void) chown(zfile2, owner_uid, group_gid);
443 }
444 }
445 if (!noaction && !(flags & CE_BINARY))
446 (void) log_trim(log); /* Report the trimming to the old log */
447
448 if (ngen == 0)
449 if (noaction)
450 printf("rm %s\n",log);
451 else
452 (void) unlink(log);
453 else
454 if (noaction)
455 printf("mv %s to %s\n",log,file1);
456 else
457 (void) rename(log,file1);
458
459 if (noaction)
460 printf("Start new log...");
461 else {
462 fd = creat(log,perm);
463 if (fd < 0) {
464 perror("can't start new log");
465 exit(1);
466 }
467 if (fchown(fd, owner_uid, group_gid)) {
468 perror("can't chmod new log file");
469 exit(1);
470 }
471 (void) close(fd);
472 if (!(flags & CE_BINARY))
473 if (log_trim(log)) { /* Add status message */
474 perror("can't add status message to log");
475 exit(1);
476 }
477 }
478 if (noaction)
479 printf("chmod %o %s...",perm,log);
480 else
481 (void) chmod(log,perm);
482 if (noaction)
483 printf("kill -HUP %d (syslogd)\n",syslog_pid);
484 else
485 if (syslog_pid < MIN_PID || syslog_pid > MAX_PID) {
486 fprintf(stderr,"%s: preposterous process number: %d\n",
487 progname, syslog_pid);
488 } else if (kill(syslog_pid,SIGHUP)) {
489 fprintf(stderr,"%s: ",progname);
490 perror("warning - could not restart syslogd");
491 }
492 if (flags & CE_COMPACT) {
493 if (noaction)
494 printf("Compress %s.0\n",log);
495 else
496 compress_log(log);
497 }
498 }
499
500 /* Log the fact that the logs were turned over */
501 int
502 log_trim(log)
503 char *log;
504 {
505 FILE *f;
506 if ((f = fopen(log,"a")) == NULL)
507 return(-1);
508 fprintf(f,"%s %s newsyslog[%d]: logfile turned over\n",
509 daytime, hostname, getpid());
510 if (fclose(f) == EOF) {
511 perror("log_trim: fclose:");
512 exit(1);
513 }
514 return(0);
515 }
516
517 /* Fork of /usr/ucb/compress to compress the old log file */
518 void
519 compress_log(log)
520 char *log;
521 {
522 int pid;
523 char tmp[128];
524
525 pid = fork();
526 (void) sprintf(tmp,"%s.0",log);
527 if (pid < 0) {
528 fprintf(stderr,"%s: ",progname);
529 perror("fork");
530 exit(1);
531 } else if (!pid) {
532 (void) execl(COMPRESS,"compress","-f",tmp,0);
533 fprintf(stderr,"%s: ",progname);
534 perror(COMPRESS);
535 exit(1);
536 }
537 }
538
539 /* Return size in kilobytes of a file */
540 int
541 sizefile(file)
542 char *file;
543 {
544 struct stat sb;
545
546 if (stat(file,&sb) < 0)
547 return(-1);
548 return(kbytes(dbtob(sb.st_blocks)));
549 }
550
551 /* Return the age of old log file (file.0) */
552 int
553 age_old_log(file)
554 char *file;
555 {
556 struct stat sb;
557 char tmp[MAXPATHLEN+3];
558
559 (void) strcpy(tmp,file);
560 if (stat(strcat(tmp,".0"),&sb) < 0)
561 if (stat(strcat(tmp,COMPRESS_POSTFIX), &sb) < 0)
562 return(-1);
563 return( (int) (timenow - sb.st_mtime + 1800) / 3600);
564 }
565
566
567 #ifndef OSF
568 /* Duplicate a string using malloc */
569
570 char *
571 strdup(strp)
572 char *strp;
573 {
574 char *cp;
575
576 if ((cp = malloc((unsigned) strlen(strp) + 1)) == NULL)
577 abort();
578 return(strcpy (cp, strp));
579 }
580 #endif
581
582 /* Skip Over Blanks */
583 char *sob(p)
584 char *p;
585 {
586 while (p && *p && isspace(*p))
587 p++;
588 return(p);
589 }
590
591 /* Skip Over Non-Blanks */
592 char *
593 son(p)
594 char *p;
595 {
596 while (p && *p && !isspace(*p))
597 p++;
598 return(p);
599 }
600
601
602 /* Check if string is actually a number */
603
604 int
605 isnumber(string)
606 char *string;
607 {
608 while (*string != '\0') {
609 if (*string < '0' || *string > '9') return(0);
610 string++;
611 }
612 return(1);
613 }
614