newsyslog.c revision 1.9 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: jtc $
29 */
30
31 #ifndef lint
32 static char rcsid[] = "$Id: newsyslog.c,v 1.9 1995/01/21 21:53:46 jtc 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[16] = '\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
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 (noaction)
431 printf("mv %s to %s\n",log,file1);
432 else
433 (void) rename(log,file1);
434 if (noaction)
435 printf("Start new log...");
436 else {
437 fd = creat(log,perm);
438 if (fd < 0) {
439 perror("can't start new log");
440 exit(1);
441 }
442 if (fchown(fd, owner_uid, group_gid)) {
443 perror("can't chmod new log file");
444 exit(1);
445 }
446 (void) close(fd);
447 if (!(flags & CE_BINARY))
448 if (log_trim(log)) { /* Add status message */
449 perror("can't add status message to log");
450 exit(1);
451 }
452 }
453 if (noaction)
454 printf("chmod %o %s...",perm,log);
455 else
456 (void) chmod(log,perm);
457 if (noaction)
458 printf("kill -HUP %d (syslogd)\n",syslog_pid);
459 else
460 if (syslog_pid < MIN_PID || syslog_pid > MAX_PID) {
461 fprintf(stderr,"%s: preposterous process number: %d\n",
462 progname, syslog_pid);
463 } else if (kill(syslog_pid,SIGHUP)) {
464 fprintf(stderr,"%s: ",progname);
465 perror("warning - could not restart syslogd");
466 }
467 if (flags & CE_COMPACT) {
468 if (noaction)
469 printf("Compress %s.0\n",log);
470 else
471 compress_log(log);
472 }
473 }
474
475 /* Log the fact that the logs were turned over */
476 log_trim(log)
477 char *log;
478 {
479 FILE *f;
480 if ((f = fopen(log,"a")) == NULL)
481 return(-1);
482 fprintf(f,"%s %s newsyslog[%d]: logfile turned over\n",
483 daytime, hostname, getpid());
484 if (fclose(f) == EOF) {
485 perror("log_trim: fclose:");
486 exit(1);
487 }
488 return(0);
489 }
490
491 /* Fork of /usr/ucb/compress to compress the old log file */
492 compress_log(log)
493 char *log;
494 {
495 int pid;
496 char tmp[128];
497
498 pid = fork();
499 (void) sprintf(tmp,"%s.0",log);
500 if (pid < 0) {
501 fprintf(stderr,"%s: ",progname);
502 perror("fork");
503 exit(1);
504 } else if (!pid) {
505 (void) execl(COMPRESS,"compress","-f",tmp,0);
506 fprintf(stderr,"%s: ",progname);
507 perror(COMPRESS);
508 exit(1);
509 }
510 }
511
512 /* Return size in kilobytes of a file */
513 int sizefile(file)
514 char *file;
515 {
516 struct stat sb;
517
518 if (stat(file,&sb) < 0)
519 return(-1);
520 return(kbytes(dbtob(sb.st_blocks)));
521 }
522
523 /* Return the age of old log file (file.0) */
524 int age_old_log(file)
525 char *file;
526 {
527 struct stat sb;
528 char tmp[MAXPATHLEN+3];
529
530 (void) strcpy(tmp,file);
531 if (stat(strcat(tmp,".0"),&sb) < 0)
532 if (stat(strcat(tmp,COMPRESS_POSTFIX), &sb) < 0)
533 return(-1);
534 return( (int) (timenow - sb.st_mtime + 1800) / 3600);
535 }
536
537
538 #ifndef OSF
539 /* Duplicate a string using malloc */
540
541 char *strdup(strp)
542 register char *strp;
543 {
544 register char *cp;
545
546 if ((cp = malloc((unsigned) strlen(strp) + 1)) == NULL)
547 abort();
548 return(strcpy (cp, strp));
549 }
550 #endif
551
552 /* Skip Over Blanks */
553 char *sob(p)
554 register char *p;
555 {
556 while (p && *p && isspace(*p))
557 p++;
558 return(p);
559 }
560
561 /* Skip Over Non-Blanks */
562 char *son(p)
563 register char *p;
564 {
565 while (p && *p && !isspace(*p))
566 p++;
567 return(p);
568 }
569
570
571 /* Check if string is actually a number */
572
573 isnumber(string)
574 char *string;
575 {
576 while (*string != '\0') {
577 if (*string < '0' || *string > '9') return(0);
578 string++;
579 }
580 return(1);
581 }
582