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