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