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