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