newsyslog.c revision 25443
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 *      $Source: /home/ncvs/src/usr.sbin/newsyslog/newsyslog.c,v $
28 *      $Author: imp $
29 */
30
31#ifndef lint
32static char rcsid[] = "$Id: newsyslog.c,v 1.10 1997/03/31 05:10:25 imp Exp $";
33#endif /* not lint */
34
35#ifndef CONF
36#define CONF "/etc/athena/newsyslog.conf" /* Configuration file */
37#endif
38#ifndef PIDFILE
39#define PIDFILE "/etc/syslog.pid"
40#endif
41#ifndef COMPRESS_PATH
42#define COMPRESS_PATH "/usr/ucb/compress" /* File compression program */
43#endif
44#ifndef COMPRESS_PROG
45#define COMPRESS_PROG "compress"
46#endif
47#ifndef COMPRESS_POSTFIX
48#define COMPRESS_POSTFIX ".Z"
49#endif
50
51#include <stdio.h>
52#include <stdlib.h>
53#include <string.h>
54#include <ctype.h>
55#include <signal.h>
56#include <pwd.h>
57#include <grp.h>
58#include <fcntl.h>
59#include <unistd.h>
60#include <err.h>
61#include <sys/types.h>
62#include <sys/time.h>
63#include <sys/stat.h>
64#include <sys/param.h>
65#include <sys/wait.h>
66
67#define kbytes(size)  (((size) + 1023) >> 10)
68#ifdef _IBMR2
69/* Calculates (db * DEV_BSIZE) */
70#define dbtob(db)  ((unsigned)(db) << UBSHIFT)
71#endif
72
73#define CE_COMPACT 1            /* Compact the achived log files */
74#define CE_BINARY  2            /* Logfile is in binary, don't add */
75                                /* status messages */
76#define NONE -1
77
78struct conf_entry {
79        char    *log;           /* Name of the log */
80	char	*pid_file;	/* PID file */
81        int     uid;            /* Owner of log */
82        int     gid;            /* Group of log */
83        int     numlogs;        /* Number of logs to keep */
84        int     size;           /* Size cutoff to trigger trimming the log */
85        int     hours;          /* Hours between log trimming */
86        int     permissions;    /* File permissions on the log */
87        int     flags;          /* Flags (CE_COMPACT & CE_BINARY)  */
88        struct conf_entry       *next; /* Linked list pointer */
89};
90
91char    *progname;              /* contains argv[0] */
92int     verbose = 0;            /* Print out what's going on */
93int     needroot = 1;           /* Root privs are necessary */
94int     noaction = 0;           /* Don't do anything, just show it */
95char    *conf = CONF;           /* Configuration file to use */
96time_t  timenow;
97pid_t   syslog_pid;             /* read in from /etc/syslog.pid */
98#define MIN_PID         5
99#define MAX_PID		30000   /* was 65534, see /usr/include/sys/proc.h */
100char    hostname[MAXHOSTNAMELEN+1]; /* hostname */
101char    *daytime;               /* timenow in human readable form */
102
103#ifndef OSF
104char *strdup(char *strp);
105#endif
106
107static struct conf_entry *parse_file();
108static char *sob(char *p);
109static char *son(char *p);
110static char *missing_field(char *p,char *errline);
111static void do_entry(struct conf_entry *ent);
112static void PRS(int argc,char **argv);
113static void usage();
114static void dotrim(char *log,char *pid_file,int numdays,int falgs,int perm, int owner_uid,int group_gid);
115static int log_trim(char *log);
116static void compress_log(char *log);
117static int sizefile(char *file);
118static int age_old_log(char *file);
119static pid_t get_pid(char *pid_file);
120
121int main(argc,argv)
122        int argc;
123        char **argv;
124{
125        struct conf_entry *p, *q;
126
127        PRS(argc,argv);
128        if (needroot && getuid() && geteuid()) {
129                fprintf(stderr,"%s: must have root privs\n",progname);
130                return(1);
131        }
132        p = q = parse_file();
133
134	syslog_pid = get_pid(PIDFILE);
135
136        while (p) {
137                do_entry(p);
138                p=p->next;
139                free((char *) q);
140                q=p;
141        }
142        return(0);
143}
144
145static void do_entry(ent)
146        struct conf_entry       *ent;
147
148{
149        int     size, modtime;
150
151        if (verbose) {
152                if (ent->flags & CE_COMPACT)
153                        printf("%s <%dZ>: ",ent->log,ent->numlogs);
154                else
155                        printf("%s <%d>: ",ent->log,ent->numlogs);
156        }
157        size = sizefile(ent->log);
158        modtime = age_old_log(ent->log);
159        if (size < 0) {
160                if (verbose)
161                        printf("does not exist.\n");
162        } else {
163                if (verbose && (ent->size > 0))
164                        printf("size (Kb): %d [%d] ", size, ent->size);
165                if (verbose && (ent->hours > 0))
166                        printf(" age (hr): %d [%d] ", modtime, ent->hours);
167                if (((ent->size > 0) && (size >= ent->size)) ||
168                    ((ent->hours > 0) && ((modtime >= ent->hours)
169                                        || (modtime < 0)))) {
170                        if (verbose)
171                                printf("--> trimming log....\n");
172                        if (noaction && !verbose) {
173                                if (ent->flags & CE_COMPACT)
174                                        printf("%s <%dZ>: trimming",
175                                               ent->log,ent->numlogs);
176                                else
177                                        printf("%s <%d>: trimming",
178                                               ent->log,ent->numlogs);
179                        }
180                        dotrim(ent->log, ent->pid_file, ent->numlogs,
181			    ent->flags, ent->permissions, ent->uid, ent->gid);
182                } else {
183                        if (verbose)
184                                printf("--> skipping\n");
185                }
186        }
187}
188
189static void PRS(argc,argv)
190        int argc;
191        char **argv;
192{
193        int     c;
194	char	*p;
195
196        progname = argv[0];
197        timenow = time((time_t *) 0);
198        daytime = ctime(&timenow) + 4;
199        daytime[15] = '\0';
200
201        /* Let's get our hostname */
202        (void) gethostname(hostname, sizeof(hostname));
203
204	/* Truncate domain */
205	if ((p = strchr(hostname, '.'))) {
206		*p = '\0';
207	}
208
209        optind = 1;             /* Start options parsing */
210        while ((c=getopt(argc,argv,"nrvf:t:")) != -1)
211                switch (c) {
212                case 'n':
213                        noaction++; /* This implies needroot as off */
214                        /* fall through */
215                case 'r':
216                        needroot = 0;
217                        break;
218                case 'v':
219                        verbose++;
220                        break;
221                case 'f':
222                        conf = optarg;
223                        break;
224                default:
225                        usage();
226                }
227        }
228
229static void usage()
230{
231        fprintf(stderr,
232                "Usage: %s <-nrv> <-f config-file>\n", progname);
233        exit(1);
234}
235
236/* Parse a configuration file and return a linked list of all the logs
237 * to process
238 */
239static struct conf_entry *parse_file()
240{
241        FILE    *f;
242        char    line[BUFSIZ], *parse, *q;
243        char    *errline, *group;
244        struct conf_entry *first = NULL;
245        struct conf_entry *working = NULL;
246        struct passwd *pass;
247        struct group *grp;
248
249        if (strcmp(conf,"-"))
250                f = fopen(conf,"r");
251        else
252                f = stdin;
253        if (!f)
254                err(1, "%s", conf);
255        while (fgets(line,BUFSIZ,f)) {
256                if ((line[0]== '\n') || (line[0] == '#'))
257                        continue;
258                errline = strdup(line);
259                if (!first) {
260                        working = (struct conf_entry *) malloc(sizeof(struct conf_entry));
261                        first = working;
262                } else {
263                        working->next = (struct conf_entry *) malloc(sizeof(struct conf_entry));
264                        working = working->next;
265                }
266
267                q = parse = missing_field(sob(line),errline);
268                *(parse = son(line)) = '\0';
269                working->log = strdup(q);
270
271                q = parse = missing_field(sob(++parse),errline);
272                *(parse = son(parse)) = '\0';
273                if ((group = strchr(q, '.')) != NULL) {
274                    *group++ = '\0';
275                    if (*q) {
276                        if (!(isnumber(*q))) {
277                            if ((pass = getpwnam(q)) == NULL)
278                                errx(1,
279                                  "Error in config file; unknown user:\n%s",
280                                  errline);
281                            working->uid = pass->pw_uid;
282                        } else
283                            working->uid = atoi(q);
284                    } else
285                        working->uid = NONE;
286
287                    q = group;
288                    if (*q) {
289                        if (!(isnumber(*q))) {
290                            if ((grp = getgrnam(q)) == NULL)
291                                errx(1,
292                                  "Error in config file; unknown group:\n%s",
293                                  errline);
294                            working->gid = grp->gr_gid;
295                        } else
296                            working->gid = atoi(q);
297                    } else
298                        working->gid = NONE;
299
300                    q = parse = missing_field(sob(++parse),errline);
301                    *(parse = son(parse)) = '\0';
302                }
303                else
304                    working->uid = working->gid = NONE;
305
306                if (!sscanf(q,"%o",&working->permissions))
307                        errx(1, "Error in config file; bad permissions:\n%s",
308                          errline);
309
310                q = parse = missing_field(sob(++parse),errline);
311                *(parse = son(parse)) = '\0';
312                if (!sscanf(q,"%d",&working->numlogs))
313                        errx(1, "Error in config file; bad number:\n%s",
314                          errline);
315
316                q = parse = missing_field(sob(++parse),errline);
317                *(parse = son(parse)) = '\0';
318                if (isdigit(*q))
319                        working->size = atoi(q);
320                else
321                        working->size = -1;
322
323                q = parse = missing_field(sob(++parse),errline);
324                *(parse = son(parse)) = '\0';
325                if (isdigit(*q))
326                        working->hours = atoi(q);
327                else
328                        working->hours = -1;
329
330                q = parse = sob(++parse); /* Optional field */
331                *(parse = son(parse)) = '\0';
332
333                working->flags = 0;
334                while (q && *q && !isspace(*q)) {
335                        if ((*q == 'Z') || (*q == 'z'))
336                                working->flags |= CE_COMPACT;
337                        else if ((*q == 'B') || (*q == 'b'))
338                                working->flags |= CE_BINARY;
339                        else
340                           errx(1, "Illegal flag in config file -- %c", *q);
341                        q++;
342                }
343
344		q = parse = sob(++parse); /* Optional field */
345		*(parse = son(parse)) = '\0';
346
347		working->pid_file = NULL;
348		if (q && *q) {
349			if (*q == '/')
350				working->pid_file = strdup(q);
351                        else
352			   errx(1, "Illegal pid file in config file:\n%s", q);
353		}
354
355                free(errline);
356        }
357        if (working)
358                working->next = (struct conf_entry *) NULL;
359        (void) fclose(f);
360        return(first);
361}
362
363static char *missing_field(p,errline)
364        char    *p,*errline;
365{
366        if (!p || !*p)
367            errx(1, "Missing field in config file:\n%s", errline);
368        return(p);
369}
370
371static void dotrim(log,pid_file,numdays,flags,perm,owner_uid,group_gid)
372        char    *log;
373	char    *pid_file;
374        int     numdays;
375        int     flags;
376        int     perm;
377        int     owner_uid;
378        int     group_gid;
379{
380        char    file1 [MAXPATHLEN+1], file2 [MAXPATHLEN+1];
381        char    zfile1[MAXPATHLEN+1], zfile2[MAXPATHLEN+1];
382	int     notified, need_notification, fd, _numdays;
383        struct  stat st;
384	pid_t   pid;
385
386#ifdef _IBMR2
387/* AIX 3.1 has a broken fchown- if the owner_uid is -1, it will actually */
388/* change it to be owned by uid -1, instead of leaving it as is, as it is */
389/* supposed to. */
390                if (owner_uid == -1)
391                  owner_uid = geteuid();
392#endif
393
394        /* Remove oldest log */
395        (void) sprintf(file1,"%s.%d",log,numdays);
396        (void) strcpy(zfile1, file1);
397        (void) strcat(zfile1, COMPRESS_POSTFIX);
398
399        if (noaction) {
400                printf("rm -f %s\n", file1);
401                printf("rm -f %s\n", zfile1);
402        } else {
403                (void) unlink(file1);
404                (void) unlink(zfile1);
405        }
406
407        /* Move down log files */
408	_numdays = numdays;	/* preserve */
409        while (numdays--) {
410                (void) strcpy(file2,file1);
411                (void) sprintf(file1,"%s.%d",log,numdays);
412                (void) strcpy(zfile1, file1);
413                (void) strcpy(zfile2, file2);
414                if (lstat(file1, &st)) {
415                        (void) strcat(zfile1, COMPRESS_POSTFIX);
416                        (void) strcat(zfile2, COMPRESS_POSTFIX);
417			if (lstat(zfile1, &st)) continue;
418                }
419                if (noaction) {
420                        printf("mv %s %s\n",zfile1,zfile2);
421                        printf("chmod %o %s\n", perm, zfile2);
422                        printf("chown %d.%d %s\n",
423                               owner_uid, group_gid, zfile2);
424                } else {
425                        (void) rename(zfile1, zfile2);
426                        (void) chmod(zfile2, perm);
427                        (void) chown(zfile2, owner_uid, group_gid);
428                }
429        }
430        if (!noaction && !(flags & CE_BINARY))
431                (void) log_trim(log);  /* Report the trimming to the old log */
432
433	if (!_numdays) {
434		if (noaction)
435			printf("rm %s\n",log);
436		else
437			(void)unlink(log);
438	}
439	else {
440		if (noaction)
441			printf("mv %s to %s\n",log,file1);
442		else
443			(void)rename(log, file1);
444	}
445
446        if (noaction)
447                printf("Start new log...");
448        else {
449                fd = creat(log,perm);
450                if (fd < 0)
451                        err(1, "can't start new log");
452                if (fchown(fd, owner_uid, group_gid))
453                        err(1, "can't chmod new log file");
454                (void) close(fd);
455                if (!(flags & CE_BINARY))
456                        if (log_trim(log))    /* Add status message */
457                             err(1, "can't add status message to log");
458        }
459        if (noaction)
460                printf("chmod %o %s...",perm,log);
461        else
462                (void) chmod(log,perm);
463
464	pid = 0;
465	need_notification = notified = 0;
466	if (pid_file != NULL) {
467		need_notification = 1;
468		pid = get_pid(pid_file);
469	} else if (!(flags & CE_BINARY)) {
470		need_notification = 1;
471		pid = syslog_pid;
472	}
473
474	if (pid) {
475		if (noaction) {
476			notified = 1;
477			printf("kill -HUP %d\n", (int)pid);
478		} else if (kill(pid,SIGHUP))
479			warn("can't notify daemon, pid %d", (int)pid);
480		else {
481			notified = 1;
482			if (verbose)
483				printf("daemon pid %d notified\n", (int)pid);
484		}
485	}
486
487	if ((flags & CE_COMPACT)) {
488		if (need_notification && !notified)
489			warnx("log not compressed because daemon not notified");
490		else if (noaction)
491                        printf("Compress %s.0\n",log);
492		else {
493			if (notified) {
494				if (verbose)
495					printf("small pause to allow daemon to close log\n");
496				sleep(3);
497			}
498                        compress_log(log);
499		}
500        }
501}
502
503/* Log the fact that the logs were turned over */
504static int log_trim(log)
505        char    *log;
506{
507        FILE    *f;
508        if ((f = fopen(log,"a")) == NULL)
509                return(-1);
510        fprintf(f,"%s %s newsyslog[%d]: logfile turned over\n",
511                daytime, hostname, (int)getpid());
512        if (fclose(f) == EOF)
513                err(1, "log_trim: fclose:");
514        return(0);
515}
516
517/* Fork of /usr/ucb/compress to compress the old log file */
518static void compress_log(log)
519        char    *log;
520{
521	pid_t   pid;
522	char    tmp[MAXPATHLEN+1];
523
524        (void) sprintf(tmp,"%s.0",log);
525	pid = fork();
526        if (pid < 0)
527                err(1, "fork");
528        else if (!pid) {
529		(void) execl(COMPRESS_PATH,COMPRESS_PROG,"-f",tmp,0);
530		err(1, COMPRESS_PATH);
531        }
532}
533
534/* Return size in kilobytes of a file */
535static int sizefile(file)
536        char    *file;
537{
538        struct stat sb;
539
540        if (stat(file,&sb) < 0)
541                return(-1);
542        return(kbytes(dbtob(sb.st_blocks)));
543}
544
545/* Return the age of old log file (file.0) */
546static int age_old_log(file)
547        char    *file;
548{
549        struct stat sb;
550        char tmp[MAXPATHLEN+sizeof(".0")+sizeof(COMPRESS_POSTFIX)+1];
551
552        (void) strcpy(tmp,file);
553        if (stat(strcat(tmp,".0"),&sb) < 0)
554            if (stat(strcat(tmp,COMPRESS_POSTFIX), &sb) < 0)
555                return(-1);
556        return( (int) (timenow - sb.st_mtime + 1800) / 3600);
557}
558
559static pid_t get_pid(pid_file)
560	char *pid_file;
561{
562	FILE *f;
563	char  line[BUFSIZ];
564	pid_t pid = 0;
565
566	if ((f = fopen(pid_file,"r")) == NULL)
567		warn("can't open %s pid file to restart a daemon",
568			pid_file);
569	else {
570		if (fgets(line,BUFSIZ,f)) {
571			pid = atol(line);
572			if (pid < MIN_PID || pid > MAX_PID) {
573				warnx("preposterous process number: %d", (int)pid);
574				pid = 0;
575			}
576		} else
577			warn("can't read %s pid file to restart a daemon",
578				pid_file);
579		(void)fclose(f);
580	}
581	return pid;
582}
583
584#ifndef OSF
585/* Duplicate a string using malloc */
586
587char *strdup(strp)
588register char   *strp;
589{
590        register char *cp;
591
592        if ((cp = malloc((unsigned) strlen(strp) + 1)) == NULL)
593                abort();
594        return(strcpy (cp, strp));
595}
596#endif
597
598/* Skip Over Blanks */
599char *sob(p)
600	register char   *p;
601{
602        while (p && *p && isspace(*p))
603                p++;
604        return(p);
605}
606
607/* Skip Over Non-Blanks */
608char *son(p)
609	register char   *p;
610{
611        while (p && *p && !isspace(*p))
612                p++;
613        return(p);
614}
615