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