newsyslog.c revision 25518
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: ache $
29 */
30
31#ifndef lint
32static char rcsid[] = "$Id: newsyslog.c,v 1.12 1997/05/05 15:00:15 ache 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 = needroot ? get_pid(PIDFILE) : 0;
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	int eol;
249
250        if (strcmp(conf,"-"))
251                f = fopen(conf,"r");
252        else
253                f = stdin;
254        if (!f)
255                err(1, "%s", conf);
256        while (fgets(line,BUFSIZ,f)) {
257                if ((line[0]== '\n') || (line[0] == '#'))
258                        continue;
259                errline = strdup(line);
260                if (!first) {
261                        working = (struct conf_entry *) malloc(sizeof(struct conf_entry));
262                        first = working;
263                } else {
264                        working->next = (struct conf_entry *) malloc(sizeof(struct conf_entry));
265                        working = working->next;
266                }
267
268                q = parse = missing_field(sob(line),errline);
269                parse = son(line);
270		if (!*parse)
271                  errx(1, "Malformed line (missing fields):\n%s", errline);
272                *parse = '\0';
273                working->log = strdup(q);
274
275                q = parse = missing_field(sob(++parse),errline);
276                parse = son(parse);
277		if (!*parse)
278                  errx(1, "Malformed line (missing fields):\n%s", errline);
279                *parse = '\0';
280                if ((group = strchr(q, '.')) != NULL) {
281                    *group++ = '\0';
282                    if (*q) {
283                        if (!(isnumber(*q))) {
284                            if ((pass = getpwnam(q)) == NULL)
285                                errx(1,
286                                  "Error in config file; unknown user:\n%s",
287                                  errline);
288                            working->uid = pass->pw_uid;
289                        } else
290                            working->uid = atoi(q);
291                    } else
292                        working->uid = NONE;
293
294                    q = group;
295                    if (*q) {
296                        if (!(isnumber(*q))) {
297                            if ((grp = getgrnam(q)) == NULL)
298                                errx(1,
299                                  "Error in config file; unknown group:\n%s",
300                                  errline);
301                            working->gid = grp->gr_gid;
302                        } else
303                            working->gid = atoi(q);
304                    } else
305                        working->gid = NONE;
306
307                    q = parse = missing_field(sob(++parse),errline);
308                    parse = son(parse);
309		    if (!*parse)
310                      errx(1, "Malformed line (missing fields):\n%s", errline);
311                    *parse = '\0';
312                }
313                else
314                    working->uid = working->gid = NONE;
315
316                if (!sscanf(q,"%o",&working->permissions))
317                        errx(1, "Error in config file; bad permissions:\n%s",
318                          errline);
319
320                q = parse = missing_field(sob(++parse),errline);
321                parse = son(parse);
322		if (!*parse)
323                  errx(1, "Malformed line (missing fields):\n%s", errline);
324                *parse = '\0';
325                if (!sscanf(q,"%d",&working->numlogs))
326                        errx(1, "Error in config file; bad number:\n%s",
327                          errline);
328
329                q = parse = missing_field(sob(++parse),errline);
330                parse = son(parse);
331		if (!*parse)
332                  errx(1, "Malformed line (missing fields):\n%s", errline);
333                *parse = '\0';
334                if (isdigit(*q))
335                        working->size = atoi(q);
336                else
337                        working->size = -1;
338
339                q = parse = missing_field(sob(++parse),errline);
340                parse = son(parse);
341		eol = !*parse;
342                *parse = '\0';
343                if (isdigit(*q))
344                        working->hours = atoi(q);
345                else
346                        working->hours = -1;
347
348		if (eol)
349		  q = NULL;
350		else {
351                  q = parse = sob(++parse); /* Optional field */
352                  parse = son(parse);
353		  if (!*parse)
354                    eol = 1;
355                  *parse = '\0';
356		}
357
358                working->flags = 0;
359                while (q && *q && !isspace(*q)) {
360                        if ((*q == 'Z') || (*q == 'z'))
361                                working->flags |= CE_COMPACT;
362                        else if ((*q == 'B') || (*q == 'b'))
363                                working->flags |= CE_BINARY;
364                        else if (*q != '-')
365                           errx(1, "Illegal flag in config file -- %c", *q);
366                        q++;
367                }
368
369		if (eol)
370		  q = NULL;
371		else {
372		  q = parse = sob(++parse); /* Optional field */
373		  *(parse = son(parse)) = '\0';
374		}
375
376		working->pid_file = NULL;
377		if (q && *q) {
378			if (*q == '/')
379				working->pid_file = strdup(q);
380                        else
381			   errx(1, "Illegal pid file in config file:\n%s", q);
382		}
383
384                free(errline);
385        }
386        if (working)
387                working->next = (struct conf_entry *) NULL;
388        (void) fclose(f);
389        return(first);
390}
391
392static char *missing_field(p,errline)
393        char    *p,*errline;
394{
395        if (!p || !*p)
396            errx(1, "Missing field in config file:\n%s", errline);
397        return(p);
398}
399
400static void dotrim(log,pid_file,numdays,flags,perm,owner_uid,group_gid)
401        char    *log;
402	char    *pid_file;
403        int     numdays;
404        int     flags;
405        int     perm;
406        int     owner_uid;
407        int     group_gid;
408{
409        char    file1 [MAXPATHLEN+1], file2 [MAXPATHLEN+1];
410        char    zfile1[MAXPATHLEN+1], zfile2[MAXPATHLEN+1];
411	int     notified, need_notification, fd, _numdays;
412        struct  stat st;
413	pid_t   pid;
414
415#ifdef _IBMR2
416/* AIX 3.1 has a broken fchown- if the owner_uid is -1, it will actually */
417/* change it to be owned by uid -1, instead of leaving it as is, as it is */
418/* supposed to. */
419                if (owner_uid == -1)
420                  owner_uid = geteuid();
421#endif
422
423        /* Remove oldest log */
424        (void) sprintf(file1,"%s.%d",log,numdays);
425        (void) strcpy(zfile1, file1);
426        (void) strcat(zfile1, COMPRESS_POSTFIX);
427
428        if (noaction) {
429                printf("rm -f %s\n", file1);
430                printf("rm -f %s\n", zfile1);
431        } else {
432                (void) unlink(file1);
433                (void) unlink(zfile1);
434        }
435
436        /* Move down log files */
437	_numdays = numdays;	/* preserve */
438        while (numdays--) {
439                (void) strcpy(file2,file1);
440                (void) sprintf(file1,"%s.%d",log,numdays);
441                (void) strcpy(zfile1, file1);
442                (void) strcpy(zfile2, file2);
443                if (lstat(file1, &st)) {
444                        (void) strcat(zfile1, COMPRESS_POSTFIX);
445                        (void) strcat(zfile2, COMPRESS_POSTFIX);
446			if (lstat(zfile1, &st)) continue;
447                }
448                if (noaction) {
449                        printf("mv %s %s\n",zfile1,zfile2);
450                        printf("chmod %o %s\n", perm, zfile2);
451                        printf("chown %d.%d %s\n",
452                               owner_uid, group_gid, zfile2);
453                } else {
454                        (void) rename(zfile1, zfile2);
455                        (void) chmod(zfile2, perm);
456                        (void) chown(zfile2, owner_uid, group_gid);
457                }
458        }
459        if (!noaction && !(flags & CE_BINARY))
460                (void) log_trim(log);  /* Report the trimming to the old log */
461
462	if (!_numdays) {
463		if (noaction)
464			printf("rm %s\n",log);
465		else
466			(void)unlink(log);
467	}
468	else {
469		if (noaction)
470			printf("mv %s to %s\n",log,file1);
471		else
472			(void)rename(log, file1);
473	}
474
475        if (noaction)
476                printf("Start new log...");
477        else {
478                fd = creat(log,perm);
479                if (fd < 0)
480                        err(1, "can't start new log");
481                if (fchown(fd, owner_uid, group_gid))
482                        err(1, "can't chmod new log file");
483                (void) close(fd);
484                if (!(flags & CE_BINARY))
485                        if (log_trim(log))    /* Add status message */
486                             err(1, "can't add status message to log");
487        }
488        if (noaction)
489                printf("chmod %o %s...",perm,log);
490        else
491                (void) chmod(log,perm);
492
493	pid = 0;
494	need_notification = notified = 0;
495	if (pid_file != NULL) {
496		need_notification = 1;
497		pid = get_pid(pid_file);
498	} else if (needroot && !(flags & CE_BINARY)) {
499		need_notification = 1;
500		pid = syslog_pid;
501	}
502
503	if (pid) {
504		if (noaction) {
505			notified = 1;
506			printf("kill -HUP %d\n", (int)pid);
507		} else if (kill(pid,SIGHUP))
508			warn("can't notify daemon, pid %d", (int)pid);
509		else {
510			notified = 1;
511			if (verbose)
512				printf("daemon pid %d notified\n", (int)pid);
513		}
514	}
515
516	if ((flags & CE_COMPACT)) {
517		if (need_notification && !notified)
518			warnx("log not compressed because daemon not notified");
519		else if (noaction)
520                        printf("Compress %s.0\n",log);
521		else {
522			if (notified) {
523				if (verbose)
524					printf("small pause to allow daemon to close log\n");
525				sleep(3);
526			}
527                        compress_log(log);
528		}
529        }
530}
531
532/* Log the fact that the logs were turned over */
533static int log_trim(log)
534        char    *log;
535{
536        FILE    *f;
537        if ((f = fopen(log,"a")) == NULL)
538                return(-1);
539        fprintf(f,"%s %s newsyslog[%d]: logfile turned over\n",
540                daytime, hostname, (int)getpid());
541        if (fclose(f) == EOF)
542                err(1, "log_trim: fclose:");
543        return(0);
544}
545
546/* Fork of /usr/ucb/compress to compress the old log file */
547static void compress_log(log)
548        char    *log;
549{
550	pid_t   pid;
551	char    tmp[MAXPATHLEN+1];
552
553        (void) sprintf(tmp,"%s.0",log);
554	pid = fork();
555        if (pid < 0)
556                err(1, "fork");
557        else if (!pid) {
558		(void) execl(COMPRESS_PATH,COMPRESS_PROG,"-f",tmp,0);
559		err(1, COMPRESS_PATH);
560        }
561}
562
563/* Return size in kilobytes of a file */
564static int sizefile(file)
565        char    *file;
566{
567        struct stat sb;
568
569        if (stat(file,&sb) < 0)
570                return(-1);
571        return(kbytes(dbtob(sb.st_blocks)));
572}
573
574/* Return the age of old log file (file.0) */
575static int age_old_log(file)
576        char    *file;
577{
578        struct stat sb;
579        char tmp[MAXPATHLEN+sizeof(".0")+sizeof(COMPRESS_POSTFIX)+1];
580
581        (void) strcpy(tmp,file);
582        if (stat(strcat(tmp,".0"),&sb) < 0)
583            if (stat(strcat(tmp,COMPRESS_POSTFIX), &sb) < 0)
584                return(-1);
585        return( (int) (timenow - sb.st_mtime + 1800) / 3600);
586}
587
588static pid_t get_pid(pid_file)
589	char *pid_file;
590{
591	FILE *f;
592	char  line[BUFSIZ];
593	pid_t pid = 0;
594
595	if ((f = fopen(pid_file,"r")) == NULL)
596		warn("can't open %s pid file to restart a daemon",
597			pid_file);
598	else {
599		if (fgets(line,BUFSIZ,f)) {
600			pid = atol(line);
601			if (pid < MIN_PID || pid > MAX_PID) {
602				warnx("preposterous process number: %d", (int)pid);
603				pid = 0;
604			}
605		} else
606			warn("can't read %s pid file to restart a daemon",
607				pid_file);
608		(void)fclose(f);
609	}
610	return pid;
611}
612
613#ifndef OSF
614/* Duplicate a string using malloc */
615
616char *strdup(strp)
617register char   *strp;
618{
619        register char *cp;
620
621        if ((cp = malloc((unsigned) strlen(strp) + 1)) == NULL)
622                abort();
623        return(strcpy (cp, strp));
624}
625#endif
626
627/* Skip Over Blanks */
628char *sob(p)
629	register char   *p;
630{
631        while (p && *p && isspace(*p))
632                p++;
633        return(p);
634}
635
636/* Skip Over Non-Blanks */
637char *son(p)
638	register char   *p;
639{
640        while (p && *p && !isspace(*p))
641                p++;
642        return(p);
643}
644