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