Home | History | Annotate | Line # | Download | only in newsyslog
newsyslog.c revision 1.21
      1 /*	$NetBSD: newsyslog.c,v 1.21 1999/11/30 12:03:24 ad Exp $	*/
      2 
      3 /*
      4  * This file contains changes from the Open Software Foundation.
      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 
     30 #include <sys/cdefs.h>
     31 #ifndef lint
     32 __RCSID("$NetBSD: newsyslog.c,v 1.21 1999/11/30 12:03:24 ad 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 <sys/types.h>
     49 #include <sys/time.h>
     50 #include <sys/stat.h>
     51 #include <sys/param.h>
     52 #include <sys/wait.h>
     53 
     54 #include <ctype.h>
     55 #include <fcntl.h>
     56 #include <grp.h>
     57 #include <pwd.h>
     58 #include <signal.h>
     59 #include <stdio.h>
     60 #include <stdlib.h>
     61 #include <string.h>
     62 #include <time.h>
     63 #include <unistd.h>
     64 
     65 #define kbytes(size)  (((size) + 1023) >> 10)
     66 #ifdef _IBMR2
     67 /* Calculates (db * DEV_BSIZE) */
     68 #define dbtob(db)  ((unsigned)(db) << UBSHIFT)
     69 #endif
     70 
     71 #define CE_COMPACT 1            /* Compact the achived log files */
     72 #define CE_BINARY 2             /* Logfile is in binary, don't add */
     73                                 /* status messages */
     74 #define CE_NOSIGNAL 4           /* Don't send a signal when trimmed */
     75 #define NONE -1
     76 
     77 struct conf_entry {
     78         char    *log;           /* Name of the log */
     79         int     uid;            /* Owner of log */
     80         int     gid;            /* Group of log */
     81         int     numlogs;        /* Number of logs to keep */
     82         int     size;           /* Size cutoff to trigger trimming the log */
     83         int     hours;          /* Hours between log trimming */
     84         int     permissions;    /* File permissions on the log */
     85         int     flags;          /* Flags (CE_*) */
     86         char	*pidfile;	/* Name of file containing PID to signal */
     87         int	signum;		/* Signal to send */
     88         struct conf_entry       *next; /* Linked list pointer */
     89 };
     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 int	force;			/* Force the trim no matter what */
     96 char    *conf = CONF;           /* Configuration file to use */
     97 time_t  timenow;
     98 int     syslog_pid;             /* read in from /etc/syslog.pid */
     99 #define MIN_PID		3
    100 #define MAX_PID		65534
    101 char    hostname[MAXHOSTNAMELEN + 1];           /* hostname */
    102 char    *daytime;               /* timenow in human readable form */
    103 
    104 
    105 void	PRS __P((int, char **));
    106 int	age_old_log __P((char *));
    107 void	compress_log __P((char *));
    108 void	dotrim __P((char *, int, int, int, int, int, char *, int));
    109 void	do_entry __P((struct conf_entry *));
    110 int	isnumber __P((char *));
    111 int	log_trim __P((char *));
    112 int	main __P((int, char **));
    113 char   *missing_field __P((char *, char *));
    114 struct conf_entry *parse_file __P((void));
    115 int	sizefile __P((char *));
    116 char   *sob __P((char *));
    117 char   *son __P((char *));
    118 void	usage __P((void));
    119 int	getsig __P((char *));
    120 
    121 int
    122 main(argc,argv)
    123         int argc;
    124         char **argv;
    125 {
    126         struct conf_entry *p, *q;
    127 
    128         PRS(argc,argv);
    129         if (needroot && getuid() && geteuid()) {
    130                 fprintf(stderr,"%s: must have root privs\n",progname);
    131                 exit(1);
    132         }
    133         p = q = parse_file();
    134         while (p) {
    135                 do_entry(p);
    136                 p=p->next;
    137                 free((char *) q);
    138                 q=p;
    139         }
    140         exit(0);
    141 }
    142 
    143 void
    144 do_entry(ent)
    145         struct conf_entry       *ent;
    146 {
    147         int     size, modtime;
    148 
    149         if (verbose) {
    150                 if (ent->flags & CE_COMPACT)
    151                         printf("%s <%dZ>: ",ent->log,ent->numlogs);
    152                 else
    153                         printf("%s <%d>: ",ent->log,ent->numlogs);
    154         }
    155         size = sizefile(ent->log);
    156         modtime = age_old_log(ent->log);
    157         if (size < 0) {
    158                 if (verbose)
    159                         printf("does not exist.\n");
    160         } else {
    161                 if (verbose && (ent->size > 0))
    162                         printf("size (Kb): %d [%d] ", size, ent->size);
    163                 if (verbose && (ent->hours > 0))
    164                         printf(" age (hr): %d [%d] ", modtime, ent->hours);
    165                 if (force || ((ent->size > 0) && (size >= ent->size)) ||
    166                     ((ent->hours > 0) && ((modtime >= ent->hours)
    167                                         || (modtime < 0)))) {
    168                         if (verbose)
    169                                 printf("--> trimming log....\n");
    170                         if (noaction && !verbose) {
    171                                 if (ent->flags & CE_COMPACT)
    172                                         printf("%s <%dZ>: trimming",
    173                                                ent->log,ent->numlogs);
    174                                 else
    175                                         printf("%s <%d>: trimming",
    176                                                ent->log,ent->numlogs);
    177                         }
    178                         dotrim(ent->log, ent->numlogs, ent->flags,
    179                                ent->permissions, ent->uid, ent->gid,
    180                                ent->pidfile, ent->signum);
    181                 } else {
    182                         if (verbose)
    183                                 printf("--> skipping\n");
    184                 }
    185         }
    186 }
    187 
    188 void
    189 PRS(argc,argv)
    190         int argc;
    191         char **argv;
    192 {
    193         int     c;
    194         FILE    *f;
    195         char    line[BUFSIZ];
    196 	char	*p;
    197 
    198         progname = argv[0];
    199         timenow = time((time_t *) 0);
    200         daytime = ctime(&timenow) + 4;
    201         daytime[15] = '\0';
    202 
    203         /* Let's find the pid of syslogd */
    204         syslog_pid = 0;
    205         f = fopen(PIDFILE,"r");
    206         if (f && fgets(line,BUFSIZ,f))
    207                 syslog_pid = atoi(line);
    208 	if (f)
    209 		(void)fclose(f);
    210 
    211         /* Let's get our hostname */
    212         (void)gethostname(hostname, sizeof(hostname));
    213 	hostname[sizeof(hostname) - 1] = '\0';
    214 
    215 	/* Truncate domain */
    216 	if ((p = strchr(hostname, '.')) != NULL) {
    217 		*p = '\0';
    218 	}
    219 
    220         optind = 1;             /* Start options parsing */
    221         while ((c=getopt(argc,argv,"Fnrvf:")) != -1)
    222                 switch (c) {
    223                 case 'n':
    224                         noaction++; /* This implies needroot as off */
    225                         /* fall through */
    226                 case 'r':
    227                         needroot = 0;
    228                         break;
    229                 case 'v':
    230                         verbose++;
    231                         break;
    232                 case 'f':
    233                         conf = optarg;
    234                         break;
    235                 case 'F':
    236                         force++;
    237                         break;
    238                 default:
    239                         usage();
    240                 }
    241 }
    242 
    243 void
    244 usage()
    245 {
    246         fprintf(stderr,
    247                 "Usage: %s <-Fnrv> <-f config-file>\n", progname);
    248         exit(1);
    249 }
    250 
    251 /* Parse a configuration file and return a linked list of all the logs
    252  * to process
    253  */
    254 struct conf_entry *
    255 parse_file()
    256 {
    257         FILE    *f;
    258         char    line[BUFSIZ], *parse, *q;
    259         char    *errline, *group, prev;
    260         struct conf_entry *first = NULL;
    261         struct conf_entry *working;
    262         struct passwd *pass;
    263         struct group *grp;
    264 
    265 	working = NULL;
    266         if (strcmp(conf,"-"))
    267                 f = fopen(conf,"r");
    268         else
    269                 f = stdin;
    270         if (!f) {
    271                 (void) fprintf(stderr,"%s: ",progname);
    272                 perror(conf);
    273                 exit(1);
    274         }
    275         while (fgets(line,BUFSIZ,f)) {
    276                 if ((line[0]== '\n') || (line[0] == '#'))
    277                         continue;
    278                 errline = strdup(line);
    279                 if (!first) {
    280                         working = (struct conf_entry *) malloc(sizeof(struct conf_entry));
    281                         first = working;
    282                 } else {
    283                         working->next = (struct conf_entry *) malloc(sizeof(struct conf_entry));
    284                         working = working->next;
    285                 }
    286 
    287                 q = parse = missing_field(sob(line),errline);
    288                 *(parse = son(line)) = '\0';
    289                 working->log = strdup(q);
    290 
    291                 q = parse = missing_field(sob(++parse),errline);
    292                 *(parse = son(parse)) = '\0';
    293                 if ((group = strchr(q, ':')) != NULL || (group = strchr(q, '.')) != NULL) {
    294                     *group++ = '\0';
    295                     if (*q) {
    296                         if (!(isnumber(q))) {
    297                             if ((pass = getpwnam(q)) == NULL) {
    298                                 fprintf(stderr,
    299                                     "Error in config file; unknown user:\n");
    300                                 fputs(errline,stderr);
    301                                 exit(1);
    302                             }
    303                             working->uid = pass->pw_uid;
    304                         } else
    305                             working->uid = atoi(q);
    306                     } else
    307                         working->uid = NONE;
    308 
    309                     q = group;
    310                     if (*q) {
    311                         if (!(isnumber(q))) {
    312                             if ((grp = getgrnam(q)) == NULL) {
    313                                 fprintf(stderr,
    314                                     "Error in config file; unknown group:\n");
    315                                 fputs(errline,stderr);
    316                                 exit(1);
    317                             }
    318                             working->gid = grp->gr_gid;
    319                         } else
    320                             working->gid = atoi(q);
    321                     } else
    322                         working->gid = NONE;
    323 
    324                     q = parse = missing_field(sob(++parse),errline);
    325                     *(parse = son(parse)) = '\0';
    326                 }
    327                 else
    328                     working->uid = working->gid = NONE;
    329 
    330                 if (!sscanf(q,"%o",&working->permissions)) {
    331                         fprintf(stderr,
    332                                 "Error in config file; bad permissions:\n");
    333                         fputs(errline,stderr);
    334                         exit(1);
    335                 }
    336 
    337                 q = parse = missing_field(sob(++parse),errline);
    338                 *(parse = son(parse)) = '\0';
    339                 if (!sscanf(q,"%d",&working->numlogs)) {
    340                         fprintf(stderr,
    341                                 "Error in config file; bad number:\n");
    342                         fputs(errline,stderr);
    343                         exit(1);
    344                 }
    345 
    346                 q = parse = missing_field(sob(++parse),errline);
    347                 *(parse = son(parse)) = '\0';
    348                 if (isdigit((unsigned char)*q))
    349                         working->size = atoi(q);
    350                 else
    351                         working->size = -1;
    352 
    353                 q = parse = missing_field(sob(++parse),errline);
    354                 *(parse = son(parse)) = '\0';
    355                 if (isdigit((unsigned char)*q))
    356                         working->hours = atoi(q);
    357                 else
    358                         working->hours = -1;
    359 
    360                 q = parse = sob(++parse); /* Optional field */
    361                 prev = *(parse = son(parse));
    362                 *parse = '\0';
    363                 working->flags = 0;
    364                 while (q && *q && !isspace((unsigned char)*q)) {
    365                         if ((*q == 'Z') || (*q == 'z'))
    366                                 working->flags |= CE_COMPACT;
    367                         else if ((*q == 'B') || (*q == 'b'))
    368                                 working->flags |= CE_BINARY;
    369                         else if ((*q == 'N') || (*q == 'n'))
    370                                 working->flags |= CE_NOSIGNAL;
    371                         else if (*q != '-') {
    372                                 fprintf(stderr,
    373                                         "Illegal flag in config file -- %c\n",
    374                                         *q);
    375                                 exit(1);
    376                         }
    377                         q++;
    378                 }
    379 
    380                 if (prev != '\0' && (q = parse = sob(++parse)) != NULL &&
    381                     q[0] == '/') {
    382                 	prev = *(parse = son(parse));
    383                 	*parse++ = '\0';
    384                		working->pidfile = strdup(q);
    385                	} else {
    386                		working->pidfile = NULL;
    387 			prev = *parse;
    388 		}
    389 
    390                 if (prev != '\0' && (q = parse = sob(parse)) != NULL &&
    391                     q[0] != '\0') {
    392                 	*(parse = son(parse)) = '\0';
    393                		if ((working->signum = getsig(q)) < 0) {
    394                			fprintf(stderr,
    395                                     "Illegal signal in config file -- %s\n",
    396                                     q);
    397                                 exit(1);
    398                         }
    399                	} else
    400                		working->signum = SIGHUP;
    401 
    402                 free(errline);
    403         }
    404         if (working)
    405                 working->next = (struct conf_entry *) NULL;
    406         (void) fclose(f);
    407         return(first);
    408 }
    409 
    410 char *
    411 missing_field(p,errline)
    412         char    *p,*errline;
    413 {
    414         if (!p || !*p) {
    415                 fprintf(stderr,"Missing field in config file:\n");
    416                 fputs(errline,stderr);
    417                 exit(1);
    418         }
    419         return(p);
    420 }
    421 
    422 void
    423 dotrim(log,numdays,flags,perm,owner_uid,group_gid,pidfile,signum)
    424         char    *log;
    425         int     numdays;
    426         int     flags;
    427         int     perm;
    428         int     owner_uid;
    429         int     group_gid;
    430         char	*pidfile;
    431         int	signum;
    432 {
    433         char    file1[128], file2[128];
    434         char    zfile1[128], zfile2[128];
    435 	char    line[BUFSIZ];
    436         int     fd;
    437         struct  stat st;
    438         int     ngen = numdays;
    439         FILE    *f;
    440         pid_t   pid;
    441 
    442 #ifdef _IBMR2
    443 /* AIX 3.1 has a broken fchown- if the owner_uid is -1, it will actually */
    444 /* change it to be owned by uid -1, instead of leaving it as is, as it is */
    445 /* supposed to. */
    446                 if (owner_uid == -1)
    447                   owner_uid = geteuid();
    448 #endif
    449 
    450         if (pidfile != NULL && pidfile[0] != '\0') {
    451        		if ((f = fopen(pidfile,"r")) == NULL) {
    452        			(void) fprintf(stderr,"%s: ",progname);
    453                 	perror(conf);
    454                 	return;
    455                 }
    456 
    457        		if (fgets(line,BUFSIZ,f))
    458        	        	pid = atoi(line);
    459 		if (f)
    460 			(void)fclose(f);
    461 	} else
    462 		pid = syslog_pid;
    463 
    464         /* Remove oldest log */
    465         (void) sprintf(file1,"%s.%d",log,numdays);
    466         (void) strcpy(zfile1, file1);
    467         (void) strcat(zfile1, COMPRESS_POSTFIX);
    468 
    469         if (noaction) {
    470                 printf("rm -f %s\n", file1);
    471                 printf("rm -f %s\n", zfile1);
    472         } else {
    473                 (void) unlink(file1);
    474                 (void) unlink(zfile1);
    475         }
    476 
    477         /* Move down log files */
    478         while (numdays--) {
    479                 (void) strcpy(file2,file1);
    480                 (void) sprintf(file1,"%s.%d",log,numdays);
    481                 (void) strcpy(zfile1, file1);
    482                 (void) strcpy(zfile2, file2);
    483                 if (lstat(file1, &st)) {
    484                         (void) strcat(zfile1, COMPRESS_POSTFIX);
    485                         (void) strcat(zfile2, COMPRESS_POSTFIX);
    486                         if (lstat(zfile1, &st)) continue;
    487                 }
    488                 if (noaction) {
    489                         printf("mv %s %s\n",zfile1,zfile2);
    490                         printf("chmod %o %s\n", perm, zfile2);
    491                         printf("chown %d.%d %s\n",
    492                                owner_uid, group_gid, zfile2);
    493                 } else {
    494                         (void) rename(zfile1, zfile2);
    495                         (void) chmod(zfile2, perm);
    496                         (void) chown(zfile2, owner_uid, group_gid);
    497                 }
    498         }
    499         if (!noaction && !(flags & CE_BINARY))
    500                 (void) log_trim(log);  /* Report the trimming to the old log */
    501 
    502 	if (ngen == 0) {
    503 		if (noaction)
    504 			printf("rm %s\n",log);
    505 		else
    506 			(void) unlink(log);
    507 	} else
    508 		if (noaction)
    509 			printf("mv %s to %s\n",log,file1);
    510 		else
    511 			(void) rename(log,file1);
    512 
    513         if (noaction)
    514                 printf("Start new log...");
    515         else {
    516                 fd = creat(log,perm);
    517                 if (fd < 0) {
    518                         perror("can't start new log");
    519                         exit(1);
    520                 }
    521                 if (fchown(fd, owner_uid, group_gid)) {
    522                         perror("can't chmod new log file");
    523                         exit(1);
    524                 }
    525                 (void) close(fd);
    526                 if (!(flags & CE_BINARY))
    527                         if (log_trim(log)) {    /* Add status message */
    528                                 perror("can't add status message to log");
    529                                 exit(1);
    530                         }
    531         }
    532         if (noaction)
    533                 printf("chmod %o %s...",perm,log);
    534         else
    535                 (void) chmod(log,perm);
    536 
    537 	if ((flags & CE_NOSIGNAL) == 0) {
    538 		if (noaction)
    539 			printf("kill -HUP %d\n",pid);
    540 		else if (pid < MIN_PID || pid > MAX_PID) {
    541 			fprintf(stderr,"%s: preposterous process number: %d\n",
    542 			    progname, pid);
    543 		} else if (kill(pid, signum)) {
    544 			fprintf(stderr,"%s: ", progname);
    545 			perror("warning - could not restart daemon");
    546 		}
    547 	}
    548 
    549         if ((flags & CE_COMPACT) != 0) {
    550                 if (noaction)
    551                         printf("Compress %s.0\n",log);
    552                 else
    553                         compress_log(log);
    554         }
    555 }
    556 
    557 /* Log the fact that the logs were turned over */
    558 int
    559 log_trim(log)
    560         char    *log;
    561 {
    562         FILE    *f;
    563         if ((f = fopen(log,"a")) == NULL)
    564                 return(-1);
    565         fprintf(f,"%s %s newsyslog[%ld]: logfile turned over\n",
    566                 daytime, hostname, (u_long)getpid());
    567         if (fclose(f) == EOF) {
    568                 perror("log_trim: fclose");
    569                 exit(1);
    570         }
    571         return(0);
    572 }
    573 
    574 /* Fork off compress/gzip to compress the old log file */
    575 void
    576 compress_log(log)
    577         char    *log;
    578 {
    579         int     pid;
    580         char    tmp[128];
    581 
    582         pid = fork();
    583         (void) sprintf(tmp,"%s.0",log);
    584         if (pid < 0) {
    585                 fprintf(stderr,"%s: ",progname);
    586                 perror("fork");
    587                 exit(1);
    588         } else if (!pid) {
    589                 (void) execl(COMPRESS,"compress","-f",tmp,0);
    590                 fprintf(stderr,"%s: ",progname);
    591                 perror(COMPRESS);
    592                 exit(1);
    593         }
    594 }
    595 
    596 /* Return size in kilobytes of a file */
    597 int
    598 sizefile(file)
    599         char    *file;
    600 {
    601         struct stat sb;
    602 
    603         if (stat(file,&sb) < 0)
    604                 return(-1);
    605         return(kbytes(dbtob(sb.st_blocks)));
    606 }
    607 
    608 /* Return the age of old log file (file.0) */
    609 int
    610 age_old_log(file)
    611         char    *file;
    612 {
    613         struct stat sb;
    614         char tmp[MAXPATHLEN+3];
    615 
    616         (void) strcpy(tmp,file);
    617         if (stat(strcat(tmp,".0"),&sb) < 0)
    618             if (stat(strcat(tmp,COMPRESS_POSTFIX), &sb) < 0)
    619                 return(-1);
    620         return( (int) (timenow - sb.st_mtime + 1800) / 3600);
    621 }
    622 
    623 /* Skip Over Blanks */
    624 char *sob(p)
    625         char   *p;
    626 {
    627         while (p && *p && isspace((unsigned char)*p))
    628                 p++;
    629         return(p);
    630 }
    631 
    632 /* Skip Over Non-Blanks */
    633 char *
    634 son(p)
    635         char   *p;
    636 {
    637         while (p && *p && !isspace((unsigned char)*p))
    638                 p++;
    639         return(p);
    640 }
    641 
    642 
    643 /* Check if string is actually a number */
    644 
    645 int
    646 isnumber(string)
    647 	char *string;
    648 {
    649         while (*string != '\0') {
    650             if (*string < '0' || *string > '9') return(0);
    651             string++;
    652         }
    653         return(1);
    654 }
    655 
    656 int
    657 getsig(sig)
    658 	char *sig;
    659 {
    660 	int n;
    661 
    662 	if (isnumber(sig)) {
    663 		n = strtol(sig, &sig, 0);
    664 		if ((unsigned)n >= NSIG)
    665 			return (-1);
    666 		return (n);
    667 	}
    668 
    669 	if (!strncasecmp(sig, "sig", 3))
    670 		sig += 3;
    671 	for (n = 1; n < NSIG; n++) {
    672 		if (!strcasecmp(sys_signame[n], sig))
    673 			return (n);
    674 	}
    675 	return (-1);
    676 }
    677