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