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