Home | History | Annotate | Line # | Download | only in newsyslog
newsyslog.c revision 1.14
      1 /*	$NetBSD: newsyslog.c,v 1.14 1998/04/02 10:35:26 kleink 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.14 1998/04/02 10:35:26 kleink 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[64];           /* 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 
    208 	/* Truncate domain */
    209 	if ((p = strchr(hostname, '.')) != NULL) {
    210 		*p = '\0';
    211 	}
    212 
    213         optind = 1;             /* Start options parsing */
    214         while ((c=getopt(argc,argv,"nrvf:t:")) != -1)
    215                 switch (c) {
    216                 case 'n':
    217                         noaction++; /* This implies needroot as off */
    218                         /* fall through */
    219                 case 'r':
    220                         needroot = 0;
    221                         break;
    222                 case 'v':
    223                         verbose++;
    224                         break;
    225                 case 'f':
    226                         conf = optarg;
    227                         break;
    228                 default:
    229                         usage();
    230                 }
    231 }
    232 
    233 void
    234 usage()
    235 {
    236         fprintf(stderr,
    237                 "Usage: %s <-nrv> <-f config-file>\n", progname);
    238         exit(1);
    239 }
    240 
    241 /* Parse a configuration file and return a linked list of all the logs
    242  * to process
    243  */
    244 struct conf_entry *
    245 parse_file()
    246 {
    247         FILE    *f;
    248         char    line[BUFSIZ], *parse, *q;
    249         char    *errline, *group;
    250         struct conf_entry *first = NULL;
    251         struct conf_entry *working;
    252         struct passwd *pass;
    253         struct group *grp;
    254 
    255 	working = NULL;
    256         if (strcmp(conf,"-"))
    257                 f = fopen(conf,"r");
    258         else
    259                 f = stdin;
    260         if (!f) {
    261                 (void) fprintf(stderr,"%s: ",progname);
    262                 perror(conf);
    263                 exit(1);
    264         }
    265         while (fgets(line,BUFSIZ,f)) {
    266                 if ((line[0]== '\n') || (line[0] == '#'))
    267                         continue;
    268                 errline = strdup(line);
    269                 if (!first) {
    270                         working = (struct conf_entry *) malloc(sizeof(struct conf_entry));
    271                         first = working;
    272                 } else {
    273                         working->next = (struct conf_entry *) malloc(sizeof(struct conf_entry));
    274                         working = working->next;
    275                 }
    276 
    277                 q = parse = missing_field(sob(line),errline);
    278                 *(parse = son(line)) = '\0';
    279                 working->log = strdup(q);
    280 
    281                 q = parse = missing_field(sob(++parse),errline);
    282                 *(parse = son(parse)) = '\0';
    283                 if ((group = strchr(q, '.')) != NULL) {
    284                     *group++ = '\0';
    285                     if (*q) {
    286                         if (!(isnumber(q))) {
    287                             if ((pass = getpwnam(q)) == NULL) {
    288                                 fprintf(stderr,
    289                                     "Error in config file; unknown user:\n");
    290                                 fputs(errline,stderr);
    291                                 exit(1);
    292                             }
    293                             working->uid = pass->pw_uid;
    294                         } else
    295                             working->uid = atoi(q);
    296                     } else
    297                         working->uid = NONE;
    298 
    299                     q = group;
    300                     if (*q) {
    301                         if (!(isnumber(q))) {
    302                             if ((grp = getgrnam(q)) == NULL) {
    303                                 fprintf(stderr,
    304                                     "Error in config file; unknown group:\n");
    305                                 fputs(errline,stderr);
    306                                 exit(1);
    307                             }
    308                             working->gid = grp->gr_gid;
    309                         } else
    310                             working->gid = atoi(q);
    311                     } else
    312                         working->gid = NONE;
    313 
    314                     q = parse = missing_field(sob(++parse),errline);
    315                     *(parse = son(parse)) = '\0';
    316                 }
    317                 else
    318                     working->uid = working->gid = NONE;
    319 
    320                 if (!sscanf(q,"%o",&working->permissions)) {
    321                         fprintf(stderr,
    322                                 "Error in config file; bad permissions:\n");
    323                         fputs(errline,stderr);
    324                         exit(1);
    325                 }
    326 
    327                 q = parse = missing_field(sob(++parse),errline);
    328                 *(parse = son(parse)) = '\0';
    329                 if (!sscanf(q,"%d",&working->numlogs)) {
    330                         fprintf(stderr,
    331                                 "Error in config file; bad number:\n");
    332                         fputs(errline,stderr);
    333                         exit(1);
    334                 }
    335 
    336                 q = parse = missing_field(sob(++parse),errline);
    337                 *(parse = son(parse)) = '\0';
    338                 if (isdigit(*q))
    339                         working->size = atoi(q);
    340                 else
    341                         working->size = -1;
    342 
    343                 q = parse = missing_field(sob(++parse),errline);
    344                 *(parse = son(parse)) = '\0';
    345                 if (isdigit(*q))
    346                         working->hours = atoi(q);
    347                 else
    348                         working->hours = -1;
    349 
    350                 q = parse = sob(++parse); /* Optional field */
    351                 *(parse = son(parse)) = '\0';
    352                 working->flags = 0;
    353                 while (q && *q && !isspace(*q)) {
    354                         if ((*q == 'Z') || (*q == 'z'))
    355                                 working->flags |= CE_COMPACT;
    356                         else if ((*q == 'B') || (*q == 'b'))
    357                                 working->flags |= CE_BINARY;
    358                         else {
    359                                 fprintf(stderr,
    360                                         "Illegal flag in config file -- %c\n",
    361                                         *q);
    362                                 exit(1);
    363                         }
    364                         q++;
    365                 }
    366 
    367                 free(errline);
    368         }
    369         if (working)
    370                 working->next = (struct conf_entry *) NULL;
    371         (void) fclose(f);
    372         return(first);
    373 }
    374 
    375 char *
    376 missing_field(p,errline)
    377         char    *p,*errline;
    378 {
    379         if (!p || !*p) {
    380                 fprintf(stderr,"Missing field in config file:\n");
    381                 fputs(errline,stderr);
    382                 exit(1);
    383         }
    384         return(p);
    385 }
    386 
    387 void
    388 dotrim(log,numdays,flags,perm,owner_uid,group_gid)
    389         char    *log;
    390         int     numdays;
    391         int     flags;
    392         int     perm;
    393         int     owner_uid;
    394         int     group_gid;
    395 {
    396         char    file1[128], file2[128];
    397         char    zfile1[128], zfile2[128];
    398         int     fd;
    399         struct  stat st;
    400         int     ngen = numdays;
    401 
    402 #ifdef _IBMR2
    403 /* AIX 3.1 has a broken fchown- if the owner_uid is -1, it will actually */
    404 /* change it to be owned by uid -1, instead of leaving it as is, as it is */
    405 /* supposed to. */
    406                 if (owner_uid == -1)
    407                   owner_uid = geteuid();
    408 #endif
    409 
    410         /* Remove oldest log */
    411         (void) sprintf(file1,"%s.%d",log,numdays);
    412         (void) strcpy(zfile1, file1);
    413         (void) strcat(zfile1, COMPRESS_POSTFIX);
    414 
    415         if (noaction) {
    416                 printf("rm -f %s\n", file1);
    417                 printf("rm -f %s\n", zfile1);
    418         } else {
    419                 (void) unlink(file1);
    420                 (void) unlink(zfile1);
    421         }
    422 
    423         /* Move down log files */
    424         while (numdays--) {
    425                 (void) strcpy(file2,file1);
    426                 (void) sprintf(file1,"%s.%d",log,numdays);
    427                 (void) strcpy(zfile1, file1);
    428                 (void) strcpy(zfile2, file2);
    429                 if (lstat(file1, &st)) {
    430                         (void) strcat(zfile1, COMPRESS_POSTFIX);
    431                         (void) strcat(zfile2, COMPRESS_POSTFIX);
    432                         if (lstat(zfile1, &st)) continue;
    433                 }
    434                 if (noaction) {
    435                         printf("mv %s %s\n",zfile1,zfile2);
    436                         printf("chmod %o %s\n", perm, zfile2);
    437                         printf("chown %d.%d %s\n",
    438                                owner_uid, group_gid, zfile2);
    439                 } else {
    440                         (void) rename(zfile1, zfile2);
    441                         (void) chmod(zfile2, perm);
    442                         (void) chown(zfile2, owner_uid, group_gid);
    443                 }
    444         }
    445         if (!noaction && !(flags & CE_BINARY))
    446                 (void) log_trim(log);  /* Report the trimming to the old log */
    447 
    448 	if (ngen == 0)
    449 		if (noaction)
    450 			printf("rm %s\n",log);
    451 		else
    452 			(void) unlink(log);
    453 	else
    454 		if (noaction)
    455 			printf("mv %s to %s\n",log,file1);
    456 		else
    457 			(void) rename(log,file1);
    458 
    459         if (noaction)
    460                 printf("Start new log...");
    461         else {
    462                 fd = creat(log,perm);
    463                 if (fd < 0) {
    464                         perror("can't start new log");
    465                         exit(1);
    466                 }
    467                 if (fchown(fd, owner_uid, group_gid)) {
    468                         perror("can't chmod new log file");
    469                         exit(1);
    470                 }
    471                 (void) close(fd);
    472                 if (!(flags & CE_BINARY))
    473                         if (log_trim(log)) {    /* Add status message */
    474                                 perror("can't add status message to log");
    475                                 exit(1);
    476                         }
    477         }
    478         if (noaction)
    479                 printf("chmod %o %s...",perm,log);
    480         else
    481                 (void) chmod(log,perm);
    482         if (noaction)
    483                 printf("kill -HUP %d (syslogd)\n",syslog_pid);
    484         else
    485 	if (syslog_pid < MIN_PID || syslog_pid > MAX_PID) {
    486 		fprintf(stderr,"%s: preposterous process number: %d\n",
    487 				progname, syslog_pid);
    488         } else if (kill(syslog_pid,SIGHUP)) {
    489                         fprintf(stderr,"%s: ",progname);
    490                         perror("warning - could not restart syslogd");
    491                 }
    492         if (flags & CE_COMPACT) {
    493                 if (noaction)
    494                         printf("Compress %s.0\n",log);
    495                 else
    496                         compress_log(log);
    497         }
    498 }
    499 
    500 /* Log the fact that the logs were turned over */
    501 int
    502 log_trim(log)
    503         char    *log;
    504 {
    505         FILE    *f;
    506         if ((f = fopen(log,"a")) == NULL)
    507                 return(-1);
    508         fprintf(f,"%s %s newsyslog[%d]: logfile turned over\n",
    509                 daytime, hostname, getpid());
    510         if (fclose(f) == EOF) {
    511                 perror("log_trim: fclose:");
    512                 exit(1);
    513         }
    514         return(0);
    515 }
    516 
    517 /* Fork of /usr/ucb/compress to compress the old log file */
    518 void
    519 compress_log(log)
    520         char    *log;
    521 {
    522         int     pid;
    523         char    tmp[128];
    524 
    525         pid = fork();
    526         (void) sprintf(tmp,"%s.0",log);
    527         if (pid < 0) {
    528                 fprintf(stderr,"%s: ",progname);
    529                 perror("fork");
    530                 exit(1);
    531         } else if (!pid) {
    532                 (void) execl(COMPRESS,"compress","-f",tmp,0);
    533                 fprintf(stderr,"%s: ",progname);
    534                 perror(COMPRESS);
    535                 exit(1);
    536         }
    537 }
    538 
    539 /* Return size in kilobytes of a file */
    540 int
    541 sizefile(file)
    542         char    *file;
    543 {
    544         struct stat sb;
    545 
    546         if (stat(file,&sb) < 0)
    547                 return(-1);
    548         return(kbytes(dbtob(sb.st_blocks)));
    549 }
    550 
    551 /* Return the age of old log file (file.0) */
    552 int
    553 age_old_log(file)
    554         char    *file;
    555 {
    556         struct stat sb;
    557         char tmp[MAXPATHLEN+3];
    558 
    559         (void) strcpy(tmp,file);
    560         if (stat(strcat(tmp,".0"),&sb) < 0)
    561             if (stat(strcat(tmp,COMPRESS_POSTFIX), &sb) < 0)
    562                 return(-1);
    563         return( (int) (timenow - sb.st_mtime + 1800) / 3600);
    564 }
    565 
    566 
    567 #ifndef OSF
    568 /* Duplicate a string using malloc */
    569 
    570 char *
    571 strdup(strp)
    572 	char   *strp;
    573 {
    574         char *cp;
    575 
    576         if ((cp = malloc((unsigned) strlen(strp) + 1)) == NULL)
    577                 abort();
    578         return(strcpy (cp, strp));
    579 }
    580 #endif
    581 
    582 /* Skip Over Blanks */
    583 char *sob(p)
    584         char   *p;
    585 {
    586         while (p && *p && isspace(*p))
    587                 p++;
    588         return(p);
    589 }
    590 
    591 /* Skip Over Non-Blanks */
    592 char *
    593 son(p)
    594         char   *p;
    595 {
    596         while (p && *p && !isspace(*p))
    597                 p++;
    598         return(p);
    599 }
    600 
    601 
    602 /* Check if string is actually a number */
    603 
    604 int
    605 isnumber(string)
    606 	char *string;
    607 {
    608         while (*string != '\0') {
    609             if (*string < '0' || *string > '9') return(0);
    610             string++;
    611         }
    612         return(1);
    613 }
    614