newsyslog.c revision 34584
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.15 1997/11/30 18:58:18 ache Exp $";
31#endif /* not lint */
32
33#ifndef CONF
34#define CONF "/etc/athena/newsyslog.conf" /* Configuration file */
35#endif
36#ifndef PIDFILE
37#define PIDFILE "/etc/syslog.pid"
38#endif
39#ifndef COMPRESS_PATH
40#define COMPRESS_PATH "/usr/ucb/compress" /* File compression program */
41#endif
42#ifndef COMPRESS_PROG
43#define COMPRESS_PROG "compress"
44#endif
45#ifndef COMPRESS_POSTFIX
46#define COMPRESS_POSTFIX ".Z"
47#endif
48
49#include <ctype.h>
50#include <err.h>
51#include <fcntl.h>
52#include <grp.h>
53#include <pwd.h>
54#include <signal.h>
55#include <stdio.h>
56#include <stdlib.h>
57#include <string.h>
58#include <unistd.h>
59#include <sys/types.h>
60#include <sys/time.h>
61#include <sys/stat.h>
62#include <sys/param.h>
63#include <sys/wait.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
76struct conf_entry {
77        char    *log;           /* Name of the log */
78	char	*pid_file;	/* PID file */
79        int     uid;            /* Owner of log */
80        int     gid;            /* Group of log */
81        int     numlogs;        /* Number of logs to keep */
82        int     size;           /* Size cutoff to trigger trimming the log */
83        int     hours;          /* Hours between log trimming */
84        int     permissions;    /* File permissions on the log */
85        int     flags;          /* Flags (CE_COMPACT & CE_BINARY)  */
86        struct conf_entry       *next; /* Linked list pointer */
87};
88
89int     verbose = 0;            /* Print out what's going on */
90int     needroot = 1;           /* Root privs are necessary */
91int     noaction = 0;           /* Don't do anything, just show it */
92int     force = 0;		/* Force the tim no matter what*/
93char    *conf = CONF;           /* Configuration file to use */
94time_t  timenow;
95pid_t   syslog_pid;             /* read in from /etc/syslog.pid */
96#define MIN_PID         5
97#define MAX_PID		30000   /* was 65534, see /usr/include/sys/proc.h */
98char    hostname[MAXHOSTNAMELEN+1]; /* hostname */
99char    *daytime;               /* timenow in human readable form */
100
101#ifndef OSF
102char *strdup(char *strp);
103#endif
104
105static struct conf_entry *parse_file();
106static char *sob(char *p);
107static char *son(char *p);
108static char *missing_field(char *p,char *errline);
109static void do_entry(struct conf_entry *ent);
110static void PRS(int argc,char **argv);
111static void usage();
112static void dotrim(char *log,char *pid_file,int numdays,int falgs,int perm, int owner_uid,int group_gid);
113static int log_trim(char *log);
114static void compress_log(char *log);
115static int sizefile(char *file);
116static int age_old_log(char *file);
117static pid_t get_pid(char *pid_file);
118
119int main(argc,argv)
120        int argc;
121        char **argv;
122{
123        struct conf_entry *p, *q;
124
125        PRS(argc,argv);
126        if (needroot && getuid() && geteuid())
127                errx(1, "must have root privs");
128        p = q = parse_file();
129
130	syslog_pid = needroot ? get_pid(PIDFILE) : 0;
131
132        while (p) {
133                do_entry(p);
134                p=p->next;
135                free((char *) q);
136                q=p;
137        }
138        return(0);
139}
140
141static void do_entry(ent)
142        struct conf_entry       *ent;
143
144{
145        int     size, modtime;
146
147        if (verbose) {
148                if (ent->flags & CE_COMPACT)
149                        printf("%s <%dZ>: ",ent->log,ent->numlogs);
150                else
151                        printf("%s <%d>: ",ent->log,ent->numlogs);
152        }
153        size = sizefile(ent->log);
154        modtime = age_old_log(ent->log);
155        if (size < 0) {
156                if (verbose)
157                        printf("does not exist.\n");
158        } else {
159                if (verbose && (ent->size > 0))
160                        printf("size (Kb): %d [%d] ", size, ent->size);
161                if (verbose && (ent->hours > 0))
162                        printf(" age (hr): %d [%d] ", modtime, ent->hours);
163                if (force || ((ent->size > 0) && (size >= ent->size)) ||
164                    ((ent->hours > 0) && ((modtime >= ent->hours)
165                                        || (modtime < 0)))) {
166                        if (verbose)
167                                printf("--> trimming log....\n");
168                        if (noaction && !verbose) {
169                                if (ent->flags & CE_COMPACT)
170                                        printf("%s <%dZ>: trimming",
171                                               ent->log,ent->numlogs);
172                                else
173                                        printf("%s <%d>: trimming",
174                                               ent->log,ent->numlogs);
175                        }
176                        dotrim(ent->log, ent->pid_file, ent->numlogs,
177			    ent->flags, ent->permissions, ent->uid, ent->gid);
178                } else {
179                        if (verbose)
180                                printf("--> skipping\n");
181                }
182        }
183}
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 [-nrv] [-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