newsyslog.c revision 35915
1139738Simp/*
2110211Smarcel * This file contains changes from the Open Software Foundation.
3110211Smarcel */
4110211Smarcel
5110211Smarcel/*
6110211Smarcel
7110211SmarcelCopyright 1988, 1989 by the Massachusetts Institute of Technology
8110211Smarcel
9110211SmarcelPermission to use, copy, modify, and distribute this software
10110211Smarceland its documentation for any purpose and without fee is
11110211Smarcelhereby granted, provided that the above copyright notice
12110211Smarcelappear in all copies and that both that copyright notice and
13110211Smarcelthis permission notice appear in supporting documentation,
14110211Smarceland that the names of M.I.T. and the M.I.T. S.I.P.B. not be
15110211Smarcelused in advertising or publicity pertaining to distribution
16110211Smarcelof the software without specific, written prior permission.
17110211SmarcelM.I.T. and the M.I.T. S.I.P.B. make no representations about
18110211Smarcelthe suitability of this software for any purpose.  It is
19110211Smarcelprovided "as is" without express or implied warranty.
20110211Smarcel
21110211Smarcel*/
22110211Smarcel
23110211Smarcel/*
24110211Smarcel *      newsyslog - roll over selected logs at the appropriate time,
25110211Smarcel *              keeping the a specified number of backup files around.
26110211Smarcel */
27119880Sobrien
28119880Sobrien#ifndef lint
29119880Sobrienstatic const char rcsid[] =
30193530Sjkim	"$Id: newsyslog.c,v 1.16 1998/03/14 22:28:25 pst Exp $";
31110211Smarcel#endif /* not lint */
32110211Smarcel
33110211Smarcel#ifndef CONF
34110211Smarcel#define CONF "/etc/athena/newsyslog.conf" /* Configuration file */
35110211Smarcel#endif
36110211Smarcel#ifndef PIDFILE
37110211Smarcel#define PIDFILE "/etc/syslog.pid"
38110211Smarcel#endif
39167814Sjkim#ifndef COMPRESS_PATH
40167814Sjkim#define COMPRESS_PATH "/usr/ucb/compress" /* File compression program */
41167814Sjkim#endif
42167814Sjkim#ifndef COMPRESS_PROG
43167814Sjkim#define COMPRESS_PROG "compress"
44167814Sjkim#endif
45167814Sjkim#ifndef COMPRESS_POSTFIX
46110211Smarcel#define COMPRESS_POSTFIX ".Z"
47110211Smarcel#endif
48110211Smarcel
49110211Smarcel#include <ctype.h>
50167814Sjkim#include <err.h>
51167814Sjkim#include <fcntl.h>
52167814Sjkim#include <grp.h>
53167814Sjkim#include <pwd.h>
54167814Sjkim#include <signal.h>
55110211Smarcel#include <stdio.h>
56110211Smarcel#include <stdlib.h>
57110211Smarcel#include <string.h>
58110211Smarcel#include <unistd.h>
59110211Smarcel#include <sys/types.h>
60110211Smarcel#include <sys/time.h>
61167814Sjkim#include <sys/stat.h>
62167814Sjkim#include <sys/param.h>
63167814Sjkim#include <sys/wait.h>
64167814Sjkim
65167814Sjkim#define kbytes(size)  (((size) + 1023) >> 10)
66167814Sjkim#ifdef _IBMR2
67110211Smarcel/* Calculates (db * DEV_BSIZE) */
68110211Smarcel#define dbtob(db)  ((unsigned)(db) << UBSHIFT)
69110211Smarcel#endif
70167814Sjkim
71123343Smarcel#define CE_COMPACT 1            /* Compact the achived log files */
72123343Smarcel#define CE_BINARY  2            /* Logfile is in binary, don't add */
73163927Smarcel                                /* status messages */
74123343Smarcel#define NONE -1
75123343Smarcel
76123343Smarcelstruct conf_entry {
77123343Smarcel        char    *log;           /* Name of the log */
78123343Smarcel	char	*pid_file;	/* PID file */
79110211Smarcel        int     uid;            /* Owner of log */
80110211Smarcel        int     gid;            /* Group of log */
81110211Smarcel        int     numlogs;        /* Number of logs to keep */
82110211Smarcel        int     size;           /* Size cutoff to trigger trimming the log */
83123343Smarcel        int     hours;          /* Hours between log trimming */
84123343Smarcel        int     permissions;    /* File permissions on the log */
85110211Smarcel        int     flags;          /* Flags (CE_COMPACT & CE_BINARY)  */
86110211Smarcel        struct conf_entry       *next; /* Linked list pointer */
87110211Smarcel};
88110211Smarcel
89110211Smarcelint     verbose = 0;            /* Print out what's going on */
90110211Smarcelint     needroot = 1;           /* Root privs are necessary */
91110211Smarcelint     noaction = 0;           /* Don't do anything, just show it */
92110211Smarcelint     force = 0;		/* Force the trim no matter what*/
93123343Smarcelchar    *conf = CONF;           /* Configuration file to use */
94123343Smarceltime_t  timenow;
95110211Smarcelpid_t   syslog_pid;             /* read in from /etc/syslog.pid */
96110211Smarcel#define MIN_PID         5
97110211Smarcel#define MAX_PID		30000   /* was 65534, see /usr/include/sys/proc.h */
98110211Smarcelchar    hostname[MAXHOSTNAMELEN+1]; /* hostname */
99110211Smarcelchar    *daytime;               /* timenow in human readable form */
100110211Smarcel
101110211Smarcel#ifndef OSF
102110211Smarcelchar *strdup(char *strp);
103123343Smarcel#endif
104123343Smarcel
105110211Smarcelstatic struct conf_entry *parse_file();
106110211Smarcelstatic char *sob(char *p);
107110211Smarcelstatic char *son(char *p);
108110211Smarcelstatic char *missing_field(char *p,char *errline);
109110211Smarcelstatic void do_entry(struct conf_entry *ent);
110110211Smarcelstatic void PRS(int argc,char **argv);
111110211Smarcelstatic void usage();
112110211Smarcelstatic void dotrim(char *log,char *pid_file,int numdays,int falgs,int perm, int owner_uid,int group_gid);
113123343Smarcelstatic int log_trim(char *log);
114123343Smarcelstatic void compress_log(char *log);
115110211Smarcelstatic int sizefile(char *file);
116110211Smarcelstatic int age_old_log(char *file);
117110211Smarcelstatic pid_t get_pid(char *pid_file);
118110211Smarcel
119110211Smarcelint main(argc,argv)
120110211Smarcel        int argc;
121110211Smarcel        char **argv;
122110211Smarcel{
123123343Smarcel        struct conf_entry *p, *q;
124123343Smarcel
125110211Smarcel        PRS(argc,argv);
126110211Smarcel        if (needroot && getuid() && geteuid())
127110211Smarcel                errx(1, "must have root privs");
128110211Smarcel        p = q = parse_file();
129110211Smarcel
130110211Smarcel	syslog_pid = needroot ? get_pid(PIDFILE) : 0;
131110211Smarcel
132110211Smarcel        while (p) {
133110211Smarcel                do_entry(p);
134110211Smarcel                p=p->next;
135110211Smarcel                free((char *) q);
136110211Smarcel                q=p;
137167814Sjkim        }
138110211Smarcel        return(0);
139110211Smarcel}
140110211Smarcel
141110211Smarcelstatic void do_entry(ent)
142110211Smarcel        struct conf_entry       *ent;
143110211Smarcel
144110211Smarcel{
145110211Smarcel        int     size, modtime;
146110211Smarcel
147135700Smarcel        if (verbose) {
148110211Smarcel                if (ent->flags & CE_COMPACT)
149110211Smarcel                        printf("%s <%dZ>: ",ent->log,ent->numlogs);
150167814Sjkim                else
151167814Sjkim                        printf("%s <%d>: ",ent->log,ent->numlogs);
152110211Smarcel        }
153110211Smarcel        size = sizefile(ent->log);
154110211Smarcel        modtime = age_old_log(ent->log);
155135700Smarcel        if (size < 0) {
156110211Smarcel                if (verbose)
157135700Smarcel                        printf("does not exist.\n");
158110211Smarcel        } else {
159110211Smarcel                if (verbose && (ent->size > 0))
160110211Smarcel                        printf("size (Kb): %d [%d] ", size, ent->size);
161110211Smarcel                if (verbose && (ent->hours > 0))
162110211Smarcel                        printf(" age (hr): %d [%d] ", modtime, ent->hours);
163110211Smarcel                if (force || ((ent->size > 0) && (size >= ent->size)) ||
164110211Smarcel                    ((ent->hours > 0) && ((modtime >= ent->hours)
165110211Smarcel                                        || (modtime < 0)))) {
166110211Smarcel                        if (verbose)
167110211Smarcel                                printf("--> trimming log....\n");
168110211Smarcel                        if (noaction && !verbose) {
169110211Smarcel                                if (ent->flags & CE_COMPACT)
170110211Smarcel                                        printf("%s <%dZ>: trimming",
171110211Smarcel                                               ent->log,ent->numlogs);
172110211Smarcel                                else
173110211Smarcel                                        printf("%s <%d>: trimming",
174110211Smarcel                                               ent->log,ent->numlogs);
175110211Smarcel                        }
176110211Smarcel                        dotrim(ent->log, ent->pid_file, ent->numlogs,
177110211Smarcel			    ent->flags, ent->permissions, ent->uid, ent->gid);
178110211Smarcel                } else {
179110211Smarcel                        if (verbose)
180167814Sjkim                                printf("--> skipping\n");
181110211Smarcel                }
182110211Smarcel        }
183110211Smarcel}
184
185static void PRS(argc,argv)
186        int argc;
187        char **argv;
188{
189        int     c;
190	char	*p;
191
192        timenow = time((time_t *) 0);
193        daytime = ctime(&timenow) + 4;
194        daytime[15] = '\0';
195
196        /* Let's get our hostname */
197        (void) gethostname(hostname, sizeof(hostname));
198
199	/* Truncate domain */
200	if ((p = strchr(hostname, '.'))) {
201		*p = '\0';
202	}
203
204        optind = 1;             /* Start options parsing */
205        while ((c=getopt(argc,argv,"nrvFf:t:")) != -1)
206                switch (c) {
207                case 'n':
208                        noaction++; /* This implies needroot as off */
209                        /* fall through */
210                case 'r':
211                        needroot = 0;
212                        break;
213                case 'v':
214                        verbose++;
215                        break;
216                case 'f':
217                        conf = optarg;
218                        break;
219		case 'F':
220			force++;
221			break;
222                default:
223                        usage();
224                }
225        }
226
227static void usage()
228{
229        fprintf(stderr, "usage: newsyslog [-nrvF] [-f config-file]\n");
230        exit(1);
231}
232
233/* Parse a configuration file and return a linked list of all the logs
234 * to process
235 */
236static struct conf_entry *parse_file()
237{
238        FILE    *f;
239        char    line[BUFSIZ], *parse, *q;
240        char    *errline, *group;
241        struct conf_entry *first = NULL;
242        struct conf_entry *working = NULL;
243        struct passwd *pass;
244        struct group *grp;
245	int eol;
246
247        if (strcmp(conf,"-"))
248                f = fopen(conf,"r");
249        else
250                f = stdin;
251        if (!f)
252                err(1, "%s", conf);
253        while (fgets(line,BUFSIZ,f)) {
254                if ((line[0]== '\n') || (line[0] == '#'))
255                        continue;
256                errline = strdup(line);
257                if (!first) {
258                        working = (struct conf_entry *) malloc(sizeof(struct conf_entry));
259                        first = working;
260                } else {
261                        working->next = (struct conf_entry *) malloc(sizeof(struct conf_entry));
262                        working = working->next;
263                }
264
265                q = parse = missing_field(sob(line),errline);
266                parse = son(line);
267		if (!*parse)
268                  errx(1, "malformed line (missing fields):\n%s", errline);
269                *parse = '\0';
270                working->log = strdup(q);
271
272                q = parse = missing_field(sob(++parse),errline);
273                parse = son(parse);
274		if (!*parse)
275                  errx(1, "malformed line (missing fields):\n%s", errline);
276                *parse = '\0';
277                if ((group = strchr(q, '.')) != NULL) {
278                    *group++ = '\0';
279                    if (*q) {
280                        if (!(isnumber(*q))) {
281                            if ((pass = getpwnam(q)) == NULL)
282                                errx(1,
283                                  "error in config file; unknown user:\n%s",
284                                  errline);
285                            working->uid = pass->pw_uid;
286                        } else
287                            working->uid = atoi(q);
288                    } else
289                        working->uid = NONE;
290
291                    q = group;
292                    if (*q) {
293                        if (!(isnumber(*q))) {
294                            if ((grp = getgrnam(q)) == NULL)
295                                errx(1,
296                                  "error in config file; unknown group:\n%s",
297                                  errline);
298                            working->gid = grp->gr_gid;
299                        } else
300                            working->gid = atoi(q);
301                    } else
302                        working->gid = NONE;
303
304                    q = parse = missing_field(sob(++parse),errline);
305                    parse = son(parse);
306		    if (!*parse)
307                      errx(1, "malformed line (missing fields):\n%s", errline);
308                    *parse = '\0';
309                }
310                else
311                    working->uid = working->gid = NONE;
312
313                if (!sscanf(q,"%o",&working->permissions))
314                        errx(1, "error in config file; bad permissions:\n%s",
315                          errline);
316
317                q = parse = missing_field(sob(++parse),errline);
318                parse = son(parse);
319		if (!*parse)
320                  errx(1, "malformed line (missing fields):\n%s", errline);
321                *parse = '\0';
322                if (!sscanf(q,"%d",&working->numlogs))
323                        errx(1, "error in config file; bad number:\n%s",
324                          errline);
325
326                q = parse = missing_field(sob(++parse),errline);
327                parse = son(parse);
328		if (!*parse)
329                  errx(1, "malformed line (missing fields):\n%s", errline);
330                *parse = '\0';
331                if (isdigit(*q))
332                        working->size = atoi(q);
333                else
334                        working->size = -1;
335
336                q = parse = missing_field(sob(++parse),errline);
337                parse = son(parse);
338		eol = !*parse;
339                *parse = '\0';
340                if (isdigit(*q))
341                        working->hours = atoi(q);
342                else
343                        working->hours = -1;
344
345		if (eol)
346		  q = NULL;
347		else {
348                  q = parse = sob(++parse); /* Optional field */
349                  parse = son(parse);
350		  if (!*parse)
351                    eol = 1;
352                  *parse = '\0';
353		}
354
355                working->flags = 0;
356                while (q && *q && !isspace(*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 if (*q != '-')
362                           errx(1, "illegal flag in config file -- %c", *q);
363                        q++;
364                }
365
366		if (eol)
367		  q = NULL;
368		else {
369		  q = parse = sob(++parse); /* Optional field */
370		  *(parse = son(parse)) = '\0';
371		}
372
373		working->pid_file = NULL;
374		if (q && *q) {
375			if (*q == '/')
376				working->pid_file = strdup(q);
377                        else
378			   errx(1, "illegal pid file in config file:\n%s", q);
379		}
380
381                free(errline);
382        }
383        if (working)
384                working->next = (struct conf_entry *) NULL;
385        (void) fclose(f);
386        return(first);
387}
388
389static char *missing_field(p,errline)
390        char    *p,*errline;
391{
392        if (!p || !*p)
393            errx(1, "missing field in config file:\n%s", errline);
394        return(p);
395}
396
397static void dotrim(log,pid_file,numdays,flags,perm,owner_uid,group_gid)
398        char    *log;
399	char    *pid_file;
400        int     numdays;
401        int     flags;
402        int     perm;
403        int     owner_uid;
404        int     group_gid;
405{
406        char    file1 [MAXPATHLEN+1], file2 [MAXPATHLEN+1];
407        char    zfile1[MAXPATHLEN+1], zfile2[MAXPATHLEN+1];
408	int     notified, need_notification, fd, _numdays;
409        struct  stat st;
410	pid_t   pid;
411
412#ifdef _IBMR2
413/* AIX 3.1 has a broken fchown- if the owner_uid is -1, it will actually */
414/* change it to be owned by uid -1, instead of leaving it as is, as it is */
415/* supposed to. */
416                if (owner_uid == -1)
417                  owner_uid = geteuid();
418#endif
419
420        /* Remove oldest log */
421        (void) sprintf(file1,"%s.%d",log,numdays);
422        (void) strcpy(zfile1, file1);
423        (void) strcat(zfile1, COMPRESS_POSTFIX);
424
425        if (noaction) {
426                printf("rm -f %s\n", file1);
427                printf("rm -f %s\n", zfile1);
428        } else {
429                (void) unlink(file1);
430                (void) unlink(zfile1);
431        }
432
433        /* Move down log files */
434	_numdays = numdays;	/* preserve */
435        while (numdays--) {
436                (void) strcpy(file2,file1);
437                (void) sprintf(file1,"%s.%d",log,numdays);
438                (void) strcpy(zfile1, file1);
439                (void) strcpy(zfile2, file2);
440                if (lstat(file1, &st)) {
441                        (void) strcat(zfile1, COMPRESS_POSTFIX);
442                        (void) strcat(zfile2, COMPRESS_POSTFIX);
443			if (lstat(zfile1, &st)) continue;
444                }
445                if (noaction) {
446                        printf("mv %s %s\n",zfile1,zfile2);
447                        printf("chmod %o %s\n", perm, zfile2);
448                        printf("chown %d.%d %s\n",
449                               owner_uid, group_gid, zfile2);
450                } else {
451                        (void) rename(zfile1, zfile2);
452                        (void) chmod(zfile2, perm);
453                        (void) chown(zfile2, owner_uid, group_gid);
454                }
455        }
456        if (!noaction && !(flags & CE_BINARY))
457                (void) log_trim(log);  /* Report the trimming to the old log */
458
459	if (!_numdays) {
460		if (noaction)
461			printf("rm %s\n",log);
462		else
463			(void)unlink(log);
464	}
465	else {
466		if (noaction)
467			printf("mv %s to %s\n",log,file1);
468		else
469			(void)rename(log, file1);
470	}
471
472        if (noaction)
473                printf("Start new log...");
474        else {
475                fd = creat(log,perm);
476                if (fd < 0)
477                        err(1, "can't start new log");
478                if (fchown(fd, owner_uid, group_gid))
479                        err(1, "can't chmod new log file");
480                (void) close(fd);
481                if (!(flags & CE_BINARY))
482                        if (log_trim(log))    /* Add status message */
483                             err(1, "can't add status message to log");
484        }
485        if (noaction)
486                printf("chmod %o %s...",perm,log);
487        else
488                (void) chmod(log,perm);
489
490	pid = 0;
491	need_notification = notified = 0;
492	if (pid_file != NULL) {
493		need_notification = 1;
494		pid = get_pid(pid_file);
495	} else if (needroot && !(flags & CE_BINARY)) {
496		need_notification = 1;
497		pid = syslog_pid;
498	}
499
500	if (pid) {
501		if (noaction) {
502			notified = 1;
503			printf("kill -HUP %d\n", (int)pid);
504		} else if (kill(pid,SIGHUP))
505			warn("can't notify daemon, pid %d", (int)pid);
506		else {
507			notified = 1;
508			if (verbose)
509				printf("daemon pid %d notified\n", (int)pid);
510		}
511	}
512
513	if ((flags & CE_COMPACT)) {
514		if (need_notification && !notified)
515			warnx("log not compressed because daemon not notified");
516		else if (noaction)
517                        printf("Compress %s.0\n",log);
518		else {
519			if (notified) {
520				if (verbose)
521					printf("small pause to allow daemon to close log\n");
522				sleep(10);
523			}
524                        compress_log(log);
525		}
526        }
527}
528
529/* Log the fact that the logs were turned over */
530static int log_trim(log)
531        char    *log;
532{
533        FILE    *f;
534        if ((f = fopen(log,"a")) == NULL)
535                return(-1);
536        fprintf(f,"%s %s newsyslog[%d]: logfile turned over\n",
537                daytime, hostname, (int)getpid());
538        if (fclose(f) == EOF)
539                err(1, "log_trim: fclose:");
540        return(0);
541}
542
543/* Fork of /usr/ucb/compress to compress the old log file */
544static void compress_log(log)
545        char    *log;
546{
547	pid_t   pid;
548	char    tmp[MAXPATHLEN+1];
549
550        (void) sprintf(tmp,"%s.0",log);
551	pid = fork();
552        if (pid < 0)
553                err(1, "fork");
554        else if (!pid) {
555		(void) execl(COMPRESS_PATH,COMPRESS_PROG,"-f",tmp,0);
556		err(1, COMPRESS_PATH);
557        }
558}
559
560/* Return size in kilobytes of a file */
561static int sizefile(file)
562        char    *file;
563{
564        struct stat sb;
565
566        if (stat(file,&sb) < 0)
567                return(-1);
568        return(kbytes(dbtob(sb.st_blocks)));
569}
570
571/* Return the age of old log file (file.0) */
572static int age_old_log(file)
573        char    *file;
574{
575        struct stat sb;
576        char tmp[MAXPATHLEN+sizeof(".0")+sizeof(COMPRESS_POSTFIX)+1];
577
578        (void) strcpy(tmp,file);
579        if (stat(strcat(tmp,".0"),&sb) < 0)
580            if (stat(strcat(tmp,COMPRESS_POSTFIX), &sb) < 0)
581                return(-1);
582        return( (int) (timenow - sb.st_mtime + 1800) / 3600);
583}
584
585static pid_t get_pid(pid_file)
586	char *pid_file;
587{
588	FILE *f;
589	char  line[BUFSIZ];
590	pid_t pid = 0;
591
592	if ((f = fopen(pid_file,"r")) == NULL)
593		warn("can't open %s pid file to restart a daemon",
594			pid_file);
595	else {
596		if (fgets(line,BUFSIZ,f)) {
597			pid = atol(line);
598			if (pid < MIN_PID || pid > MAX_PID) {
599				warnx("preposterous process number: %d", (int)pid);
600				pid = 0;
601			}
602		} else
603			warn("can't read %s pid file to restart a daemon",
604				pid_file);
605		(void)fclose(f);
606	}
607	return pid;
608}
609
610#ifndef OSF
611/* Duplicate a string using malloc */
612
613char *strdup(strp)
614register char   *strp;
615{
616        register char *cp;
617
618        if ((cp = malloc((unsigned) strlen(strp) + 1)) == NULL)
619                abort();
620        return(strcpy (cp, strp));
621}
622#endif
623
624/* Skip Over Blanks */
625char *sob(p)
626	register char   *p;
627{
628        while (p && *p && isspace(*p))
629                p++;
630        return(p);
631}
632
633/* Skip Over Non-Blanks */
634char *son(p)
635	register char   *p;
636{
637        while (p && *p && !isspace(*p))
638                p++;
639        return(p);
640}
641