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