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