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