newsyslog.c revision 43071
1/*
2 * This file contains changes from the Open Software Foundation.
3 */
4
5/*
6
7Copyright 1988, 1989 by the Massachusetts Institute of Technology
8
9Permission to use, copy, modify, and distribute this software
10and its documentation for any purpose and without fee is
11hereby granted, provided that the above copyright notice
12appear in all copies and that both that copyright notice and
13this permission notice appear in supporting documentation,
14and that the names of M.I.T. and the M.I.T. S.I.P.B. not be
15used in advertising or publicity pertaining to distribution
16of the software without specific, written prior permission.
17M.I.T. and the M.I.T. S.I.P.B. make no representations about
18the suitability of this software for any purpose.  It is
19provided "as is" without express or implied warranty.
20
21*/
22
23/*
24 *      newsyslog - roll over selected logs at the appropriate time,
25 *              keeping the a specified number of backup files around.
26 */
27
28#ifndef lint
29static const char rcsid[] =
30	"$Id: newsyslog.c,v 1.21 1998/12/23 12:03:33 peter Exp $";
31#endif /* not lint */
32
33#define OSF
34#ifndef COMPRESS_POSTFIX
35#define COMPRESS_POSTFIX ".gz"
36#endif
37
38#include <ctype.h>
39#include <err.h>
40#include <fcntl.h>
41#include <grp.h>
42#include <paths.h>
43#include <pwd.h>
44#include <signal.h>
45#include <stdio.h>
46#include <stdlib.h>
47#include <string.h>
48#include <time.h>
49#include <unistd.h>
50#include <sys/types.h>
51#include <sys/stat.h>
52#include <sys/param.h>
53#include <sys/wait.h>
54
55#include "pathnames.h"
56
57#define kbytes(size)  (((size) + 1023) >> 10)
58#ifdef _IBMR2
59/* Calculates (db * DEV_BSIZE) */
60#define dbtob(db)  ((unsigned)(db) << UBSHIFT)
61#endif
62
63#define CE_COMPACT 1            /* Compact the achived log files */
64#define CE_BINARY  2            /* Logfile is in binary, don't add */
65                                /* status messages */
66#define	CE_TRIMAT  4		/* trim at a specific time */
67
68#define NONE -1
69
70struct conf_entry {
71        char    *log;           /* Name of the log */
72	char	*pid_file;	/* PID file */
73        int     uid;            /* Owner of log */
74        int     gid;            /* Group of log */
75        int     numlogs;        /* Number of logs to keep */
76        int     size;           /* Size cutoff to trigger trimming the log */
77        int     hours;          /* Hours between log trimming */
78	time_t	trim_at;	/* Specific time to do trimming */
79        int     permissions;    /* File permissions on the log */
80        int     flags;          /* Flags (CE_COMPACT & CE_BINARY)  */
81	int     sig;            /* Signal to send */
82        struct conf_entry       *next; /* Linked list pointer */
83};
84
85int     verbose = 0;            /* Print out what's going on */
86int     needroot = 1;           /* Root privs are necessary */
87int     noaction = 0;           /* Don't do anything, just show it */
88int     force = 0;		/* Force the trim no matter what*/
89char    *conf = _PATH_CONF;	/* Configuration file to use */
90time_t  timenow;
91#define MIN_PID         5
92#define MAX_PID		99999   /* was lower, see /usr/include/sys/proc.h */
93char    hostname[MAXHOSTNAMELEN+1]; /* hostname */
94char    *daytime;               /* timenow in human readable form */
95
96static struct conf_entry *parse_file();
97static char *sob(char *p);
98static char *son(char *p);
99static char *missing_field(char *p,char *errline);
100static void do_entry(struct conf_entry *ent);
101static void PRS(int argc,char **argv);
102static void usage();
103static void dotrim(char *log,char *pid_file,int numdays,int falgs,int perm,int owner_uid,int group_gid,int sig);
104static int log_trim(char *log);
105static void compress_log(char *log);
106static int sizefile(char *file);
107static int age_old_log(char *file);
108static pid_t get_pid(char *pid_file);
109static	time_t parse8601(const char *s);
110
111int main(argc,argv)
112        int argc;
113        char **argv;
114{
115        struct conf_entry *p, *q;
116
117        PRS(argc,argv);
118        if (needroot && getuid() && geteuid())
119                errx(1, "must have root privs");
120        p = q = parse_file();
121
122        while (p) {
123                do_entry(p);
124                p=p->next;
125                free((char *) q);
126                q=p;
127        }
128        return(0);
129}
130
131static void do_entry(ent)
132        struct conf_entry       *ent;
133
134{
135        int     size, modtime;
136	char 	*pid_file;
137
138        if (verbose) {
139                if (ent->flags & CE_COMPACT)
140                        printf("%s <%dZ>: ",ent->log,ent->numlogs);
141                else
142                        printf("%s <%d>: ",ent->log,ent->numlogs);
143        }
144        size = sizefile(ent->log);
145        modtime = age_old_log(ent->log);
146        if (size < 0) {
147                if (verbose)
148                        printf("does not exist.\n");
149        } else {
150		if (ent->flags & CE_TRIMAT) {
151			if (timenow < ent->trim_at
152			    || difftime(timenow, ent->trim_at) >= 60*60) {
153				if (verbose)
154					printf("--> will trim at %s",
155					       ctime(&ent->trim_at));
156				return;
157			} else if (verbose && ent->hours <= 0) {
158				printf("--> time is up\n");
159			}
160		}
161                if (verbose && (ent->size > 0))
162                        printf("size (Kb): %d [%d] ", size, ent->size);
163                if (verbose && (ent->hours > 0))
164                        printf(" age (hr): %d [%d] ", modtime, ent->hours);
165                if (force || ((ent->size > 0) && (size >= ent->size)) ||
166		    (ent->hours <= 0 && (ent->flags & CE_TRIMAT)) ||
167                    ((ent->hours > 0) && ((modtime >= ent->hours)
168                                        || (modtime < 0)))) {
169                        if (verbose)
170                                printf("--> trimming log....\n");
171                        if (noaction && !verbose) {
172                                if (ent->flags & CE_COMPACT)
173                                        printf("%s <%dZ>: trimming\n",
174                                               ent->log,ent->numlogs);
175                                else
176                                        printf("%s <%d>: trimming\n",
177                                               ent->log,ent->numlogs);
178                        }
179			if (ent->pid_file) {
180				pid_file = ent->pid_file;
181			} else {
182				/* Only try to notify syslog if we are root */
183				if (needroot)
184					pid_file = _PATH_SYSLOGPID;
185				else
186					pid_file = NULL;
187			}
188                        dotrim(ent->log, pid_file, ent->numlogs,
189			    ent->flags, ent->permissions, ent->uid, ent->gid, ent->sig);
190                } else {
191                        if (verbose)
192                                printf("--> skipping\n");
193                }
194        }
195}
196
197static void PRS(argc,argv)
198        int argc;
199        char **argv;
200{
201        int     c;
202	char	*p;
203
204        timenow = time((time_t *) 0);
205        daytime = ctime(&timenow) + 4;
206        daytime[15] = '\0';
207
208        /* Let's get our hostname */
209        (void) gethostname(hostname, sizeof(hostname));
210
211	/* Truncate domain */
212	if ((p = strchr(hostname, '.'))) {
213		*p = '\0';
214	}
215
216        optind = 1;             /* Start options parsing */
217        while ((c=getopt(argc,argv,"nrvFf:t:")) != -1)
218                switch (c) {
219                case 'n':
220                        noaction++;
221			break;
222                case 'r':
223                        needroot = 0;
224                        break;
225                case 'v':
226                        verbose++;
227                        break;
228                case 'f':
229                        conf = optarg;
230                        break;
231		case 'F':
232			force++;
233			break;
234                default:
235                        usage();
236                }
237}
238
239static void usage()
240{
241        fprintf(stderr, "usage: newsyslog [-Fnrv] [-f config-file]\n");
242        exit(1);
243}
244
245/* Parse a configuration file and return a linked list of all the logs
246 * to process
247 */
248static struct conf_entry *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 = NULL;
255        struct passwd *pass;
256        struct group *grp;
257	int eol;
258
259        if (strcmp(conf,"-"))
260                f = fopen(conf,"r");
261        else
262                f = stdin;
263        if (!f)
264                err(1, "%s", conf);
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);
279		if (!*parse)
280                  errx(1, "malformed line (missing fields):\n%s", errline);
281                *parse = '\0';
282                working->log = strdup(q);
283
284                q = parse = missing_field(sob(++parse),errline);
285                parse = son(parse);
286		if (!*parse)
287                  errx(1, "malformed line (missing fields):\n%s", errline);
288                *parse = '\0';
289                if ((group = strchr(q, '.')) != NULL) {
290                    *group++ = '\0';
291                    if (*q) {
292                        if (!(isnumber(*q))) {
293                            if ((pass = getpwnam(q)) == NULL)
294                                errx(1,
295                                  "error in config file; unknown user:\n%s",
296                                  errline);
297                            working->uid = pass->pw_uid;
298                        } else
299                            working->uid = atoi(q);
300                    } else
301                        working->uid = NONE;
302
303                    q = group;
304                    if (*q) {
305                        if (!(isnumber(*q))) {
306                            if ((grp = getgrnam(q)) == NULL)
307                                errx(1,
308                                  "error in config file; unknown group:\n%s",
309                                  errline);
310                            working->gid = grp->gr_gid;
311                        } else
312                            working->gid = atoi(q);
313                    } else
314                        working->gid = NONE;
315
316                    q = parse = missing_field(sob(++parse),errline);
317                    parse = son(parse);
318		    if (!*parse)
319                      errx(1, "malformed line (missing fields):\n%s", errline);
320                    *parse = '\0';
321                }
322                else
323                    working->uid = working->gid = NONE;
324
325                if (!sscanf(q,"%o",&working->permissions))
326                        errx(1, "error in config file; bad permissions:\n%s",
327                          errline);
328
329                q = parse = missing_field(sob(++parse),errline);
330                parse = son(parse);
331		if (!*parse)
332                  errx(1, "malformed line (missing fields):\n%s", errline);
333                *parse = '\0';
334                if (!sscanf(q,"%d",&working->numlogs))
335                        errx(1, "error in config file; bad number:\n%s",
336                          errline);
337
338                q = parse = missing_field(sob(++parse),errline);
339                parse = son(parse);
340		if (!*parse)
341                  errx(1, "malformed line (missing fields):\n%s", errline);
342                *parse = '\0';
343                if (isdigit(*q))
344                        working->size = atoi(q);
345                else
346                        working->size = -1;
347
348                working->flags = 0;
349                q = parse = missing_field(sob(++parse),errline);
350                parse = son(parse);
351		eol = !*parse;
352                *parse = '\0';
353		{
354			char	*ep;
355			u_long	ul;
356
357			ul = strtoul(q, &ep, 10);
358			if (ep == q)
359				working->hours = 0;
360			else if (*ep == '*')
361				working->hours = -1;
362			else if (ul > INT_MAX)
363				errx(1, "interval is too large:\n%s", errline);
364			else
365				working->hours = ul;
366
367			if (*ep != '\0' && *ep != '@' && *ep != '*')
368				errx(1, "malformed interval/at:\n%s", errline);
369			if (*ep == '@') {
370				if ((working->trim_at = parse8601(ep + 1))
371				    == (time_t)-1)
372					errx(1, "malformed at:\n%s", errline);
373				working->flags |= CE_TRIMAT;
374			}
375		}
376
377		if (eol)
378		  q = NULL;
379		else {
380                  q = parse = sob(++parse); /* Optional field */
381                  parse = son(parse);
382		  if (!*parse)
383                    eol = 1;
384                  *parse = '\0';
385		}
386
387                while (q && *q && !isspace(*q)) {
388                        if ((*q == 'Z') || (*q == 'z'))
389                                working->flags |= CE_COMPACT;
390                        else if ((*q == 'B') || (*q == 'b'))
391                                working->flags |= CE_BINARY;
392                        else if (*q != '-')
393                           errx(1, "illegal flag in config file -- %c", *q);
394                        q++;
395                }
396
397		if (eol)
398		  q = NULL;
399		else {
400		  q = parse = sob(++parse); /* Optional field */
401                  parse = son(parse);
402		  if (!*parse)
403                    eol = 1;
404                  *parse = '\0';
405		}
406
407		working->pid_file = NULL;
408		if (q && *q) {
409			if (*q == '/')
410				working->pid_file = strdup(q);
411			else if (isdigit(*q))
412				goto got_sig;
413                        else
414			   errx(1, "illegal pid file or signal number in config file:\n%s", errline);
415		}
416
417		if (eol)
418		  q = NULL;
419		else {
420		  q = parse = sob(++parse); /* Optional field */
421		  *(parse = son(parse)) = '\0';
422		}
423
424		working->sig = SIGHUP;
425		if (q && *q) {
426			if (isdigit(*q)) {
427			got_sig:
428				working->sig = atoi(q);
429			} else {
430			err_sig:
431			   errx(1, "illegal signal number in config file:\n%s", errline);
432			}
433			if (working->sig < 1 || working->sig >= NSIG)
434				goto err_sig;
435		}
436
437                free(errline);
438        }
439        if (working)
440                working->next = (struct conf_entry *) NULL;
441        (void) fclose(f);
442        return(first);
443}
444
445static char *missing_field(p,errline)
446        char    *p,*errline;
447{
448        if (!p || !*p)
449            errx(1, "missing field in config file:\n%s", errline);
450        return(p);
451}
452
453static void dotrim(log,pid_file,numdays,flags,perm,owner_uid,group_gid,sig)
454        char    *log;
455	char    *pid_file;
456        int     numdays;
457        int     flags;
458        int     perm;
459        int     owner_uid;
460        int     group_gid;
461	int     sig;
462{
463        char    file1 [MAXPATHLEN+1], file2 [MAXPATHLEN+1];
464        char    zfile1[MAXPATHLEN+1], zfile2[MAXPATHLEN+1];
465	int     notified, need_notification, fd, _numdays;
466        struct  stat st;
467	pid_t   pid;
468
469#ifdef _IBMR2
470/* AIX 3.1 has a broken fchown- if the owner_uid is -1, it will actually */
471/* change it to be owned by uid -1, instead of leaving it as is, as it is */
472/* supposed to. */
473                if (owner_uid == -1)
474                  owner_uid = geteuid();
475#endif
476
477        /* Remove oldest log */
478        (void) sprintf(file1,"%s.%d",log,numdays);
479        (void) strcpy(zfile1, file1);
480        (void) strcat(zfile1, COMPRESS_POSTFIX);
481
482        if (noaction) {
483                printf("rm -f %s\n", file1);
484                printf("rm -f %s\n", zfile1);
485        } else {
486                (void) unlink(file1);
487                (void) unlink(zfile1);
488        }
489
490        /* Move down log files */
491	_numdays = numdays;	/* preserve */
492        while (numdays--) {
493                (void) strcpy(file2,file1);
494                (void) sprintf(file1,"%s.%d",log,numdays);
495                (void) strcpy(zfile1, file1);
496                (void) strcpy(zfile2, file2);
497                if (lstat(file1, &st)) {
498                        (void) strcat(zfile1, COMPRESS_POSTFIX);
499                        (void) strcat(zfile2, COMPRESS_POSTFIX);
500			if (lstat(zfile1, &st)) continue;
501                }
502                if (noaction) {
503                        printf("mv %s %s\n",zfile1,zfile2);
504                        printf("chmod %o %s\n", perm, zfile2);
505                        printf("chown %d.%d %s\n",
506                               owner_uid, group_gid, zfile2);
507                } else {
508                        (void) rename(zfile1, zfile2);
509                        (void) chmod(zfile2, perm);
510                        (void) chown(zfile2, owner_uid, group_gid);
511                }
512        }
513        if (!noaction && !(flags & CE_BINARY))
514                (void) log_trim(log);  /* Report the trimming to the old log */
515
516	if (!_numdays) {
517		if (noaction)
518			printf("rm %s\n",log);
519		else
520			(void)unlink(log);
521	}
522	else {
523		if (noaction)
524			printf("mv %s to %s\n",log,file1);
525		else
526			(void)rename(log, file1);
527	}
528
529        if (noaction)
530                printf("Start new log...");
531        else {
532                fd = creat(log,perm);
533                if (fd < 0)
534                        err(1, "can't start new log");
535                if (fchown(fd, owner_uid, group_gid))
536                        err(1, "can't chmod new log file");
537                (void) close(fd);
538                if (!(flags & CE_BINARY))
539                        if (log_trim(log))    /* Add status message */
540                             err(1, "can't add status message to log");
541        }
542        if (noaction)
543                printf("chmod %o %s...\n", perm, log);
544        else
545                (void) chmod(log,perm);
546
547	pid = 0;
548	need_notification = notified = 0;
549	if (pid_file != NULL) {
550		need_notification = 1;
551		pid = get_pid(pid_file);
552	}
553
554	if (pid) {
555		if (noaction) {
556			notified = 1;
557			printf("kill -%d %d\n", sig, (int)pid);
558		} else if (kill(pid,sig))
559			warn("can't notify daemon, pid %d", (int)pid);
560		else {
561			notified = 1;
562			if (verbose)
563				printf("daemon pid %d notified\n", (int)pid);
564		}
565	}
566
567	if ((flags & CE_COMPACT)) {
568		if (need_notification && !notified)
569			warnx("log not compressed because daemon not notified");
570		else if (noaction)
571                        printf("Compress %s.0\n",log);
572		else {
573			if (notified) {
574				if (verbose)
575					printf("small pause to allow daemon to close log\n");
576				sleep(10);
577			}
578                        compress_log(log);
579		}
580        }
581}
582
583/* Log the fact that the logs were turned over */
584static int log_trim(log)
585        char    *log;
586{
587        FILE    *f;
588        if ((f = fopen(log,"a")) == NULL)
589                return(-1);
590        fprintf(f,"%s %s newsyslog[%d]: logfile turned over\n",
591                daytime, hostname, (int)getpid());
592        if (fclose(f) == EOF)
593                err(1, "log_trim: fclose:");
594        return(0);
595}
596
597/* Fork of /usr/ucb/compress to compress the old log file */
598static void compress_log(log)
599        char    *log;
600{
601	pid_t   pid;
602	char    tmp[MAXPATHLEN+1];
603
604        (void) sprintf(tmp,"%s.0",log);
605	pid = fork();
606        if (pid < 0)
607                err(1, "fork");
608        else if (!pid) {
609		(void) execl(_PATH_GZIP, _PATH_GZIP, "-f", tmp, 0);
610		err(1, _PATH_GZIP);
611        }
612}
613
614/* Return size in kilobytes of a file */
615static int sizefile(file)
616        char    *file;
617{
618        struct stat sb;
619
620        if (stat(file,&sb) < 0)
621                return(-1);
622        return(kbytes(dbtob(sb.st_blocks)));
623}
624
625/* Return the age of old log file (file.0) */
626static int age_old_log(file)
627        char    *file;
628{
629        struct stat sb;
630        char tmp[MAXPATHLEN+sizeof(".0")+sizeof(COMPRESS_POSTFIX)+1];
631
632        (void) strcpy(tmp,file);
633        if (stat(strcat(tmp,".0"),&sb) < 0)
634            if (stat(strcat(tmp,COMPRESS_POSTFIX), &sb) < 0)
635                return(-1);
636        return( (int) (timenow - sb.st_mtime + 1800) / 3600);
637}
638
639static pid_t get_pid(pid_file)
640	char *pid_file;
641{
642	FILE *f;
643	char  line[BUFSIZ];
644	pid_t pid = 0;
645
646	if ((f = fopen(pid_file,"r")) == NULL)
647		warn("can't open %s pid file to restart a daemon",
648			pid_file);
649	else {
650		if (fgets(line,BUFSIZ,f)) {
651			pid = atol(line);
652			if (pid < MIN_PID || pid > MAX_PID) {
653				warnx("preposterous process number: %d", (int)pid);
654				pid = 0;
655			}
656		} else
657			warn("can't read %s pid file to restart a daemon",
658				pid_file);
659		(void)fclose(f);
660	}
661	return pid;
662}
663
664/* Skip Over Blanks */
665char *sob(p)
666	register char   *p;
667{
668        while (p && *p && isspace(*p))
669                p++;
670        return(p);
671}
672
673/* Skip Over Non-Blanks */
674char *son(p)
675	register char   *p;
676{
677        while (p && *p && !isspace(*p))
678                p++;
679        return(p);
680}
681
682/*
683 * Parse a limited subset of ISO 8601.
684 * The specific format is as follows:
685 *
686 *	[CC[YY[MM[DD]]]][THH[MM[SS]]]	(where `T' is the literal letter)
687 *
688 * We don't accept a timezone specification; missing fields (including
689 * timezone) are defaulted to the current date but time zero.
690 */
691static time_t
692parse8601(const char *s)
693{
694	char		*t;
695	struct tm	tm, *tmp;
696	u_long		ul;
697
698	tmp = localtime(&timenow);
699	tm = *tmp;
700
701	tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
702
703	ul = strtoul(s, &t, 10);
704	if (*t != '\0' && *t != 'T')
705		return -1;
706
707	/*
708	 * Now t points either to the end of the string (if no time
709	 * was provided) or to the letter `T' which separates date
710	 * and time in ISO 8601.  The pointer arithmetic is the same for
711	 * either case.
712	 */
713	switch (t - s) {
714	case 8:
715		tm.tm_year = ((ul / 1000000) - 19) * 100;
716		ul = ul % 1000000;
717	case 6:
718		tm.tm_year = tm.tm_year - (tm.tm_year % 100);
719		tm.tm_year += ul / 10000;
720		ul = ul % 10000;
721	case 4:
722		tm.tm_mon = (ul / 100) - 1;
723		ul = ul % 100;
724	case 2:
725		tm.tm_mday = ul;
726	case 0:
727		break;
728	default:
729		return -1;
730	}
731
732	/* sanity check */
733	if (tm.tm_year < 70 || tm.tm_mon < 0 || tm.tm_mon > 12
734	    || tm.tm_mday < 1 || tm.tm_mday > 31)
735		return -1;
736
737	if (*t != '\0') {
738		s = ++t;
739		ul = strtoul(s, &t, 10);
740		if (*t != '\0' && !isspace(*t))
741			return -1;
742
743		switch (t - s) {
744		case 6:
745			tm.tm_sec = ul % 100;
746			ul /= 100;
747		case 4:
748			tm.tm_min = ul % 100;
749			ul /= 100;
750		case 2:
751			tm.tm_hour = ul;
752		case 0:
753			break;
754		default:
755			return -1;
756		}
757
758		/* sanity check */
759		if (tm.tm_sec < 0 || tm.tm_sec > 60 || tm.tm_min < 0
760		    || tm.tm_min > 59 || tm.tm_hour < 0 || tm.tm_hour > 23)
761			return -1;
762	}
763
764	return mktime(&tm);
765}
766
767
768