newsyslog.c revision 36817
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.19 1998/05/10 21:13:29 hoek 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	int     sig;            /* Signal to send */
87        struct conf_entry       *next; /* Linked list pointer */
88};
89
90int     verbose = 0;            /* Print out what's going on */
91int     needroot = 1;           /* Root privs are necessary */
92int     noaction = 0;           /* Don't do anything, just show it */
93int     force = 0;		/* Force the trim no matter what*/
94char    *conf = CONF;           /* Configuration file to use */
95time_t  timenow;
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,int sig);
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        while (p) {
131                do_entry(p);
132                p=p->next;
133                free((char *) q);
134                q=p;
135        }
136        return(0);
137}
138
139static void do_entry(ent)
140        struct conf_entry       *ent;
141
142{
143        int     size, modtime;
144	char 	*pid_file;
145
146        if (verbose) {
147                if (ent->flags & CE_COMPACT)
148                        printf("%s <%dZ>: ",ent->log,ent->numlogs);
149                else
150                        printf("%s <%d>: ",ent->log,ent->numlogs);
151        }
152        size = sizefile(ent->log);
153        modtime = age_old_log(ent->log);
154        if (size < 0) {
155                if (verbose)
156                        printf("does not exist.\n");
157        } else {
158                if (verbose && (ent->size > 0))
159                        printf("size (Kb): %d [%d] ", size, ent->size);
160                if (verbose && (ent->hours > 0))
161                        printf(" age (hr): %d [%d] ", modtime, ent->hours);
162                if (force || ((ent->size > 0) && (size >= ent->size)) ||
163                    ((ent->hours > 0) && ((modtime >= ent->hours)
164                                        || (modtime < 0)))) {
165                        if (verbose)
166                                printf("--> trimming log....\n");
167                        if (noaction && !verbose) {
168                                if (ent->flags & CE_COMPACT)
169                                        printf("%s <%dZ>: trimming\n",
170                                               ent->log,ent->numlogs);
171                                else
172                                        printf("%s <%d>: trimming\n",
173                                               ent->log,ent->numlogs);
174                        }
175			if (ent->pid_file) {
176				pid_file = ent->pid_file;
177			} else {
178				/* Only try to notify syslog if we are root */
179				if (needroot)
180					pid_file = PIDFILE;
181				else
182					pid_file = NULL;
183			}
184                        dotrim(ent->log, pid_file, ent->numlogs,
185			    ent->flags, ent->permissions, ent->uid, ent->gid, ent->sig);
186                } else {
187                        if (verbose)
188                                printf("--> skipping\n");
189                }
190        }
191}
192
193static void PRS(argc,argv)
194        int argc;
195        char **argv;
196{
197        int     c;
198	char	*p;
199
200        timenow = time((time_t *) 0);
201        daytime = ctime(&timenow) + 4;
202        daytime[15] = '\0';
203
204        /* Let's get our hostname */
205        (void) gethostname(hostname, sizeof(hostname));
206
207	/* Truncate domain */
208	if ((p = strchr(hostname, '.'))) {
209		*p = '\0';
210	}
211
212        optind = 1;             /* Start options parsing */
213        while ((c=getopt(argc,argv,"nrvFf:t:")) != -1)
214                switch (c) {
215                case 'n':
216                        noaction++;
217			break;
218                case 'r':
219                        needroot = 0;
220                        break;
221                case 'v':
222                        verbose++;
223                        break;
224                case 'f':
225                        conf = optarg;
226                        break;
227		case 'F':
228			force++;
229			break;
230                default:
231                        usage();
232                }
233        }
234
235static void usage()
236{
237        fprintf(stderr, "usage: newsyslog [-Fnrv] [-f config-file]\n");
238        exit(1);
239}
240
241/* Parse a configuration file and return a linked list of all the logs
242 * to process
243 */
244static struct conf_entry *parse_file()
245{
246        FILE    *f;
247        char    line[BUFSIZ], *parse, *q;
248        char    *errline, *group;
249        struct conf_entry *first = NULL;
250        struct conf_entry *working = NULL;
251        struct passwd *pass;
252        struct group *grp;
253	int eol;
254
255        if (strcmp(conf,"-"))
256                f = fopen(conf,"r");
257        else
258                f = stdin;
259        if (!f)
260                err(1, "%s", conf);
261        while (fgets(line,BUFSIZ,f)) {
262                if ((line[0]== '\n') || (line[0] == '#'))
263                        continue;
264                errline = strdup(line);
265                if (!first) {
266                        working = (struct conf_entry *) malloc(sizeof(struct conf_entry));
267                        first = working;
268                } else {
269                        working->next = (struct conf_entry *) malloc(sizeof(struct conf_entry));
270                        working = working->next;
271                }
272
273                q = parse = missing_field(sob(line),errline);
274                parse = son(line);
275		if (!*parse)
276                  errx(1, "malformed line (missing fields):\n%s", errline);
277                *parse = '\0';
278                working->log = strdup(q);
279
280                q = parse = missing_field(sob(++parse),errline);
281                parse = son(parse);
282		if (!*parse)
283                  errx(1, "malformed line (missing fields):\n%s", errline);
284                *parse = '\0';
285                if ((group = strchr(q, '.')) != NULL) {
286                    *group++ = '\0';
287                    if (*q) {
288                        if (!(isnumber(*q))) {
289                            if ((pass = getpwnam(q)) == NULL)
290                                errx(1,
291                                  "error in config file; unknown user:\n%s",
292                                  errline);
293                            working->uid = pass->pw_uid;
294                        } else
295                            working->uid = atoi(q);
296                    } else
297                        working->uid = NONE;
298
299                    q = group;
300                    if (*q) {
301                        if (!(isnumber(*q))) {
302                            if ((grp = getgrnam(q)) == NULL)
303                                errx(1,
304                                  "error in config file; unknown group:\n%s",
305                                  errline);
306                            working->gid = grp->gr_gid;
307                        } else
308                            working->gid = atoi(q);
309                    } else
310                        working->gid = NONE;
311
312                    q = parse = missing_field(sob(++parse),errline);
313                    parse = son(parse);
314		    if (!*parse)
315                      errx(1, "malformed line (missing fields):\n%s", errline);
316                    *parse = '\0';
317                }
318                else
319                    working->uid = working->gid = NONE;
320
321                if (!sscanf(q,"%o",&working->permissions))
322                        errx(1, "error in config file; bad permissions:\n%s",
323                          errline);
324
325                q = parse = missing_field(sob(++parse),errline);
326                parse = son(parse);
327		if (!*parse)
328                  errx(1, "malformed line (missing fields):\n%s", errline);
329                *parse = '\0';
330                if (!sscanf(q,"%d",&working->numlogs))
331                        errx(1, "error in config file; bad number:\n%s",
332                          errline);
333
334                q = parse = missing_field(sob(++parse),errline);
335                parse = son(parse);
336		if (!*parse)
337                  errx(1, "malformed line (missing fields):\n%s", errline);
338                *parse = '\0';
339                if (isdigit(*q))
340                        working->size = atoi(q);
341                else
342                        working->size = -1;
343
344                q = parse = missing_field(sob(++parse),errline);
345                parse = son(parse);
346		eol = !*parse;
347                *parse = '\0';
348                if (isdigit(*q))
349                        working->hours = atoi(q);
350                else
351                        working->hours = -1;
352
353		if (eol)
354		  q = NULL;
355		else {
356                  q = parse = sob(++parse); /* Optional field */
357                  parse = son(parse);
358		  if (!*parse)
359                    eol = 1;
360                  *parse = '\0';
361		}
362
363                working->flags = 0;
364                while (q && *q && !isspace(*q)) {
365                        if ((*q == 'Z') || (*q == 'z'))
366                                working->flags |= CE_COMPACT;
367                        else if ((*q == 'B') || (*q == 'b'))
368                                working->flags |= CE_BINARY;
369                        else if (*q != '-')
370                           errx(1, "illegal flag in config file -- %c", *q);
371                        q++;
372                }
373
374		if (eol)
375		  q = NULL;
376		else {
377		  q = parse = sob(++parse); /* Optional field */
378                  parse = son(parse);
379		  if (!*parse)
380                    eol = 1;
381                  *parse = '\0';
382		}
383
384		working->pid_file = NULL;
385		if (q && *q) {
386			if (*q == '/')
387				working->pid_file = strdup(q);
388			else if (isdigit(*q))
389				goto got_sig;
390                        else
391			   errx(1, "illegal pid file or signal number in config file:\n%s", errline);
392		}
393
394		if (eol)
395		  q = NULL;
396		else {
397		  q = parse = sob(++parse); /* Optional field */
398		  *(parse = son(parse)) = '\0';
399		}
400
401		working->sig = SIGHUP;
402		if (q && *q) {
403			if (isdigit(*q)) {
404			got_sig:
405				working->sig = atoi(q);
406			} else {
407			err_sig:
408			   errx(1, "illegal signal number in config file:\n%s", errline);
409			}
410			if (working->sig < 1 || working->sig >= NSIG)
411				goto err_sig;
412		}
413
414                free(errline);
415        }
416        if (working)
417                working->next = (struct conf_entry *) NULL;
418        (void) fclose(f);
419        return(first);
420}
421
422static char *missing_field(p,errline)
423        char    *p,*errline;
424{
425        if (!p || !*p)
426            errx(1, "missing field in config file:\n%s", errline);
427        return(p);
428}
429
430static void dotrim(log,pid_file,numdays,flags,perm,owner_uid,group_gid,sig)
431        char    *log;
432	char    *pid_file;
433        int     numdays;
434        int     flags;
435        int     perm;
436        int     owner_uid;
437        int     group_gid;
438	int     sig;
439{
440        char    file1 [MAXPATHLEN+1], file2 [MAXPATHLEN+1];
441        char    zfile1[MAXPATHLEN+1], zfile2[MAXPATHLEN+1];
442	int     notified, need_notification, fd, _numdays;
443        struct  stat st;
444	pid_t   pid;
445
446#ifdef _IBMR2
447/* AIX 3.1 has a broken fchown- if the owner_uid is -1, it will actually */
448/* change it to be owned by uid -1, instead of leaving it as is, as it is */
449/* supposed to. */
450                if (owner_uid == -1)
451                  owner_uid = geteuid();
452#endif
453
454        /* Remove oldest log */
455        (void) sprintf(file1,"%s.%d",log,numdays);
456        (void) strcpy(zfile1, file1);
457        (void) strcat(zfile1, COMPRESS_POSTFIX);
458
459        if (noaction) {
460                printf("rm -f %s\n", file1);
461                printf("rm -f %s\n", zfile1);
462        } else {
463                (void) unlink(file1);
464                (void) unlink(zfile1);
465        }
466
467        /* Move down log files */
468	_numdays = numdays;	/* preserve */
469        while (numdays--) {
470                (void) strcpy(file2,file1);
471                (void) sprintf(file1,"%s.%d",log,numdays);
472                (void) strcpy(zfile1, file1);
473                (void) strcpy(zfile2, file2);
474                if (lstat(file1, &st)) {
475                        (void) strcat(zfile1, COMPRESS_POSTFIX);
476                        (void) strcat(zfile2, COMPRESS_POSTFIX);
477			if (lstat(zfile1, &st)) continue;
478                }
479                if (noaction) {
480                        printf("mv %s %s\n",zfile1,zfile2);
481                        printf("chmod %o %s\n", perm, zfile2);
482                        printf("chown %d.%d %s\n",
483                               owner_uid, group_gid, zfile2);
484                } else {
485                        (void) rename(zfile1, zfile2);
486                        (void) chmod(zfile2, perm);
487                        (void) chown(zfile2, owner_uid, group_gid);
488                }
489        }
490        if (!noaction && !(flags & CE_BINARY))
491                (void) log_trim(log);  /* Report the trimming to the old log */
492
493	if (!_numdays) {
494		if (noaction)
495			printf("rm %s\n",log);
496		else
497			(void)unlink(log);
498	}
499	else {
500		if (noaction)
501			printf("mv %s to %s\n",log,file1);
502		else
503			(void)rename(log, file1);
504	}
505
506        if (noaction)
507                printf("Start new log...");
508        else {
509                fd = creat(log,perm);
510                if (fd < 0)
511                        err(1, "can't start new log");
512                if (fchown(fd, owner_uid, group_gid))
513                        err(1, "can't chmod new log file");
514                (void) close(fd);
515                if (!(flags & CE_BINARY))
516                        if (log_trim(log))    /* Add status message */
517                             err(1, "can't add status message to log");
518        }
519        if (noaction)
520                printf("chmod %o %s...",perm,log);
521        else
522                (void) chmod(log,perm);
523
524	pid = 0;
525	need_notification = notified = 0;
526	if (pid_file != NULL) {
527		need_notification = 1;
528		pid = get_pid(pid_file);
529	}
530
531	if (pid) {
532		if (noaction) {
533			notified = 1;
534			printf("kill -%d %d\n", sig, (int)pid);
535		} else if (kill(pid,sig))
536			warn("can't notify daemon, pid %d", (int)pid);
537		else {
538			notified = 1;
539			if (verbose)
540				printf("daemon pid %d notified\n", (int)pid);
541		}
542	}
543
544	if ((flags & CE_COMPACT)) {
545		if (need_notification && !notified)
546			warnx("log not compressed because daemon not notified");
547		else if (noaction)
548                        printf("Compress %s.0\n",log);
549		else {
550			if (notified) {
551				if (verbose)
552					printf("small pause to allow daemon to close log\n");
553				sleep(10);
554			}
555                        compress_log(log);
556		}
557        }
558}
559
560/* Log the fact that the logs were turned over */
561static int log_trim(log)
562        char    *log;
563{
564        FILE    *f;
565        if ((f = fopen(log,"a")) == NULL)
566                return(-1);
567        fprintf(f,"%s %s newsyslog[%d]: logfile turned over\n",
568                daytime, hostname, (int)getpid());
569        if (fclose(f) == EOF)
570                err(1, "log_trim: fclose:");
571        return(0);
572}
573
574/* Fork of /usr/ucb/compress to compress the old log file */
575static void compress_log(log)
576        char    *log;
577{
578	pid_t   pid;
579	char    tmp[MAXPATHLEN+1];
580
581        (void) sprintf(tmp,"%s.0",log);
582	pid = fork();
583        if (pid < 0)
584                err(1, "fork");
585        else if (!pid) {
586		(void) execl(COMPRESS_PATH,COMPRESS_PROG,"-f",tmp,0);
587		err(1, COMPRESS_PATH);
588        }
589}
590
591/* Return size in kilobytes of a file */
592static int sizefile(file)
593        char    *file;
594{
595        struct stat sb;
596
597        if (stat(file,&sb) < 0)
598                return(-1);
599        return(kbytes(dbtob(sb.st_blocks)));
600}
601
602/* Return the age of old log file (file.0) */
603static int age_old_log(file)
604        char    *file;
605{
606        struct stat sb;
607        char tmp[MAXPATHLEN+sizeof(".0")+sizeof(COMPRESS_POSTFIX)+1];
608
609        (void) strcpy(tmp,file);
610        if (stat(strcat(tmp,".0"),&sb) < 0)
611            if (stat(strcat(tmp,COMPRESS_POSTFIX), &sb) < 0)
612                return(-1);
613        return( (int) (timenow - sb.st_mtime + 1800) / 3600);
614}
615
616static pid_t get_pid(pid_file)
617	char *pid_file;
618{
619	FILE *f;
620	char  line[BUFSIZ];
621	pid_t pid = 0;
622
623	if ((f = fopen(pid_file,"r")) == NULL)
624		warn("can't open %s pid file to restart a daemon",
625			pid_file);
626	else {
627		if (fgets(line,BUFSIZ,f)) {
628			pid = atol(line);
629			if (pid < MIN_PID || pid > MAX_PID) {
630				warnx("preposterous process number: %d", (int)pid);
631				pid = 0;
632			}
633		} else
634			warn("can't read %s pid file to restart a daemon",
635				pid_file);
636		(void)fclose(f);
637	}
638	return pid;
639}
640
641#ifndef OSF
642/* Duplicate a string using malloc */
643
644char *strdup(strp)
645register char   *strp;
646{
647        register char *cp;
648
649        if ((cp = malloc((unsigned) strlen(strp) + 1)) == NULL)
650                abort();
651        return(strcpy (cp, strp));
652}
653#endif
654
655/* Skip Over Blanks */
656char *sob(p)
657	register char   *p;
658{
659        while (p && *p && isspace(*p))
660                p++;
661        return(p);
662}
663
664/* Skip Over Non-Blanks */
665char *son(p)
666	register char   *p;
667{
668        while (p && *p && !isspace(*p))
669                p++;
670        return(p);
671}
672