source: trunk/athena/etc/newsyslog/newsyslog.c @ 13769

Revision 13769, 35.4 KB checked in by danw, 25 years ago (diff)
compile with warnings
Line 
1/*
2 *      newsyslog - roll over selected logs at the appropriate time,
3 *              keeping the a specified number of backup files around.
4 *
5 *      $Id: newsyslog.c,v 1.11 1999-10-19 20:22:28 danw Exp $
6 */
7
8static const char rcsid[] = "$Id: newsyslog.c,v 1.11 1999-10-19 20:22:28 danw Exp $";
9
10#include "config.h"
11
12#include <sys/types.h>
13#include <stdio.h>
14#include <stdlib.h>
15#include <unistd.h>
16#ifdef HAVE_STRING_H
17#include <string.h>
18#else
19#include <strings.h>  /* only VAXen don't have string.h --bert 15mar96 */
20#endif
21#include <ctype.h>
22#include <signal.h>
23#include <pwd.h>
24#include <grp.h>
25#include <sys/stat.h>
26#include <sys/param.h>
27#include <sys/wait.h>
28#include <fcntl.h>
29#include <sys/time.h>
30#include <time.h>
31#include <dirent.h>
32
33#include "signames.h"
34
35#ifdef sgi
36/* Note: Irix has the "killall" command, which is like kill except
37   that it takes a process name instead of an id. This command is
38   not to be confused with the Solaris killall command, which kills
39   just about everything. It would probably be nice just to go
40   ahead and read the process table instead of execing an external
41   program, but... */
42#define ALLOW_PROCNAME
43#define USE_PROCNAME
44#endif
45
46/*** defines ***/
47
48#define CONF "/etc/athena/newsyslog.conf"       /* Configuration file */
49#define SYSLOG_PID "/etc/syslog.pid"            /* Pidfile for syslogd */
50#define SYSLOG_PNAME "syslogd"                  /* Process name for syslogd */
51
52/* COMPRESS is now defined in config.h */
53
54#ifndef SLEEP_DELAY
55#define SLEEP_DELAY 1    /* Default delay used after restarting each daemon, */
56#endif                           /* to give it time to clean up (in seconds) */
57
58#define kbytes(size)  (((size) + 1023) >> 10)
59#ifdef _IBMR2
60/* Calculates (db * DEV_BSIZE) */
61#define dbtob(db)  ((unsigned)(db) << UBSHIFT)
62#elif defined(__linux__)
63#define dbtob(db)  (db * DEV_BSIZE)
64#endif
65
66/* special (i.e. non-configurable) flags for logfile entries */
67#define CE_ACTIVE 1     /* Logfile needs to be turned over */
68#define CE_BINARY 2     /* Logfile is in binary, don't add status messages */
69#define CE_DATED  4     /* Mark the logfile with date, not number */
70
71#define NONE -1
72
73/* Definitions of the keywords for the logfile.
74 * Should not be changed once we come up with good ones and document them. =)
75 */
76/* This is used to add another post-processing command, e.g. 'gzip -9' */
77#define KEYWORD_EXEC "filter"
78/* This describes a process to restart, other than syslogd */
79#define KEYWORD_PID  "signal"
80/* This works as "signal," only takes a process name rather than a pid file. */
81#define KEYWORD_PNAME "signame"
82
83/* Definitions of the predefined flag letters. */
84#define FL_BINARY 'B'   /* reserved */
85#define FL_DATED  'D'   /* reserved */
86#define FL_COMPRESS 'Z' /* can be redefined */
87
88/* Define the date format used for generating dated filenames. */
89/***If you change the format, you must change the parsing in valid_date_ext***/
90#define DATE_FORMAT "%Y%m%d"    /* YYYYMMDD, as used by strftime */
91#define DATE_LENGTH 8
92
93struct log_entry {
94    char *log;                  /* Name of the log */
95    int  uid;                   /* Owner of log */
96    int  gid;                   /* Group of log */
97    int  numlogs;               /* Number of logs to keep */
98    int  size;                  /* Size cutoff to trigger trimming the log */
99    int  hours;                 /* Hours between log trimming */
100    int  permissions;           /* File permissions on the log */
101    int  flags;                 /* Flags (CE_ACTIVE , CE_BINARY, CE_DATED) */
102    char *exec_flags;           /* Flag letters for things to run */
103    char *pid_flags;            /* Flag letters for things to restart */
104    struct log_entry *next;     /* Linked list pointer */
105};
106
107/* The same structure is used for different kinds of flags. */
108/* This wastes a little run-time space, but makes my life easier. */
109struct flag_entry {
110    char  option;               /* Option character used for this program */
111    enum { EXEC, PID, PNAME } type;     /* Type of flag ('run' vs. 'signal') */
112
113    /* EXEC flag options */
114    char  *extension;           /* Extension added by this program */
115    char  **args;               /* Program name and command-line arguments */
116    int   nargs;                /* Number of arguments not including args[0] */
117
118    /* PID/PNAME flag options */
119    char  *pidident;            /* Path to the PID file, or the process name */
120    int signal;                 /* Signal to restart the process */
121    int ref_count;              /* Number of times used (if 0, no restart) */
122
123    struct flag_entry *next;    /* Linked list pointer */
124};
125
126/* extern uid_t getuid(),geteuid(); */
127/* extern time_t time(); */
128
129/* extern's needed for getopt() processing */
130extern int      optind;
131extern char     *optarg;
132
133/*** globals ***/
134
135char    *progname;              /* name we were run as (ie, argv[0]) */
136char    hostname[64];           /* hostname of this machine */
137
138/* variables which correspond to the command-line flags */
139int     verbose = 0;            /* Print out what's going on */
140int     needroot = 1;           /* Root privs are necessary */
141int     noaction = 0;           /* Don't do anything, just show it */
142char    *conf = CONF;           /* Configuration file to use */
143int     sleeptm = SLEEP_DELAY;  /* Time to wait after restarting a daemon */
144
145time_t  timenow;                /* Time of the start of newsyslog */
146char    *daytime;               /* timenow in human readable form */
147char    datestamp[DATE_LENGTH+1]; /* timenow in the YYYYMMDD format */
148
149struct flag_entry *flags;       /* List of all config-file flags */
150
151/*** support functions ***/
152
153/* Skip Over Blanks */
154static char *sob (register char *p)
155{
156    while (p && *p && isspace((unsigned char)*p))
157        p++;
158    return(p);
159}
160
161/* Skip Over Non-Blanks */
162static char *son (register char *p)
163{
164    while (p && *p && !isspace((unsigned char)*p))
165        p++;
166    return(p);
167}
168
169/* Check if string is actually a number */
170static int isnumber(char *string)
171{
172    while (*string != '\0') {
173        if (! isdigit((unsigned char)*string)) return(0);
174        string++;
175    }
176    return(1);
177}
178
179#ifndef HAVE_STRDUP
180/* Duplicate a string using malloc */
181static char *strdup (register char *strp)
182{
183    register char *cp;
184
185    if ((cp = malloc(strlen(strp) + 1)) == NULL)
186        abort();
187    return(strcpy (cp, strp));
188}
189#endif
190
191/* Tack a character to the end of a string. (Inefficient...) */
192static char *grow_option_string (char **strp, char new)
193{
194    size_t len;
195
196    if (! strp)
197        return NULL;
198    else if (! *strp) {
199        len = 0;
200        (*strp) = malloc(2);
201    } else {
202        len = strlen(*strp);
203        (*strp) = realloc(*strp, len + 2);
204    }
205
206    if (! *strp) abort();
207
208    (*strp)[len] = new;
209    (*strp)[len+1] = '\0';
210
211    return *strp;
212}
213
214/*** finding the appropriate logfile, whatever its extension ***/
215
216/* Check if the filename extension is composed of valid components */
217static int valid_extension(char* ext)
218{
219    struct flag_entry *cfl = flags;
220    int len;
221
222    if (ext && (*ext))
223        while (cfl) {
224            if ((cfl->type == EXEC) && (cfl->extension)) {
225                len = strlen(cfl->extension);
226
227                /* if the current extension matches... */
228                if (!strncmp(ext, cfl->extension, len)) {
229                    /* exact match */
230                    if (ext[len] == '\0')
231                        return 1;
232                    /* match, but we have more to check */
233                    else if (ext[len] == '.') {
234                        if (valid_extension(ext + len + 1))
235                            return 1;
236                        /* if valid_ext returns false, try other possibilities;
237                           that way, one proc can add >1 ext (.tar.gz) */
238                    }
239                }
240            }
241            cfl = cfl->next;
242        }
243    return 0;
244}
245
246/* Check if the string is empty, or a dot followed by a valid extension */
247static int valid_dot_ext(char* dot)
248{
249    if (!dot)
250        return 0;
251    else if (*dot == '\0')
252        return 1;
253    else if (*dot == '.')
254        return valid_extension(dot + 1);
255
256    return 0;
257}
258
259/* Check if the string is a valid date (YYYYMMDD) plus extension */
260static int valid_date_ext(char* date)
261{
262    /* if we're still compiling C code in 2099, something is horribly wrong */
263    return (((date[0]=='1' && date[1]=='9')
264             || (date[0]=='2' && date[1]=='0')) &&
265            isdigit((unsigned char)date[2]) &&
266            isdigit((unsigned char)date[3]) &&
267            ((date[4]=='0' && (date[5]>='1' && date[5]<='9'))
268             || (date[4]=='1' && (date[5]>='0' && date[5]<='2'))) &&
269            ((date[6]=='0' && (date[7]>='1' && date[7]<='9'))
270             || ((date[6]=='1' || date[6]=='2') &&
271                 isdigit((unsigned char)date[7]))
272             || (date[6]=='3' && (date[7]>='0' && date[7]<='1'))) &&
273            valid_dot_ext(date + 8)) ;
274}
275
276/* Find the file whose name matches one given, with a valid extension,
277 * subject to the following rules:
278 * (1) If a filename of the form "${name}${root}valid_ext" exists,
279 *     then it is returned.  If there is more than one such file, then
280 *     the file returned is the one whose entry occurs first in the directory.
281 * (2) If not, and if cmp is non-NULL, then filenames of the form
282 *     "${name}YYYYMMDD.valid_ext" are considered.  If there are less
283 *     than ${limit} files, NULL is returned; otherwise the 'cmp-most'
284 *     match is returned; i.e. the match for which cmp returned true
285 *     for all the files it was compared to.
286 * Look below for more directly useful functions that call this one.
287 */
288static char *matching_file_compared (char* name, char* root,
289                                     int cmp(char*,char*,size_t), int limit)
290{
291    DIR *parent;
292    struct dirent *dent;
293    char *dirname, *namefrag, *fp, *filename;
294    char *cmpmost = NULL;
295    int fraglen, rootlen, have_path, mismatch, matches;
296
297    if (root)
298        rootlen = strlen(root);
299    else
300        rootlen = 0;
301
302    /* extract the directory path and the filename fragment. */
303    fp = dirname = strdup(name);
304    namefrag = strrchr(dirname, '/');
305    if (( have_path = (namefrag != NULL) )) {
306        *(namefrag++) = '\0';
307    } else {
308        namefrag = dirname;
309        dirname = ".";
310    }
311    fraglen = strlen(namefrag);
312
313    if (!(parent = opendir(dirname))) {
314        free(fp);
315        return (char*)NULL;
316    }
317
318    /* WARNING: on Solaris, -lucb will link with readdir() which
319       returns a *different* struct dirent.  libucb: just say no. */
320
321    matches = 0;
322    /* find matching directory entry */
323    while ((dent=readdir(parent))
324           && ((mismatch = strncmp(dent->d_name, namefrag, fraglen))
325               || (root && strncmp(dent->d_name + fraglen, root, rootlen))
326               || !valid_dot_ext(dent->d_name + fraglen + rootlen))) {
327        if (!mismatch && cmp && valid_date_ext(dent->d_name + fraglen)) {
328            matches++;
329            if ((cmpmost == NULL)
330                || cmp(dent->d_name+fraglen, cmpmost+fraglen, DATE_LENGTH))
331            {
332                /* current entry is cmp'er than previous value of cmpmost */
333                if (cmpmost)  free(cmpmost);
334                cmpmost = strdup(dent->d_name);
335            }
336        }
337    }
338
339    /* If we found an entry matching root, use that; if not, use a dated one */
340    if (dent && dent->d_name)
341        filename = dent->d_name;
342    else if (matches >= limit)
343        filename = cmpmost; /* possibly NULL */
344    else
345        filename = NULL;
346
347    if (filename) {
348        char *glued = (char*)malloc( strlen(dirname)+strlen(filename) + 3);
349        if (!glued)
350            abort();
351
352        /* reconstruct pathname+filename */
353        if (have_path) {
354            sprintf(glued, "%s/%s", dirname, filename);
355        } else
356            strcpy(glued, filename);
357
358        closedir(parent);
359        if (cmpmost)  free(cmpmost);
360        free(fp);
361        return glued;
362    } else {
363        closedir(parent);
364        if (cmpmost)  free(cmpmost);
365        free(fp);
366        return (char*)NULL;
367    }
368}
369
370/* Comparison functions: true if file1's date is earlier than file2's */
371static int cmp_earliest_date (char* file1, char* file2, size_t n)
372{
373    return ( strncmp(file1, file2, n) < 0 );
374}
375
376/* Comparison functions: true if file1's date is later than file2's */
377static int cmp_latest_date (char* file1, char* file2, size_t n)
378{
379    return ( strncmp(file1, file2, n) > 0 );
380}
381
382/* Find the file whose name matches one given, with a valid extension */
383static char *matching_file(char* name)
384{
385    return matching_file_compared (name, NULL, NULL, -1);
386}
387
388/* Find the newest logfile: ".0" if it exists, or the latest-dated one */
389static char *newest_log(char* file)
390{
391    char tmp[MAXPATHLEN];
392
393    sprintf(tmp, "%s.", file);
394    return matching_file_compared(tmp, "0", cmp_latest_date, -1);
395}
396
397/* Find the oldest logfile: ".${numlogs}" if it exists, or else the
398 * first-dated file if there are more than ${numlogs} dated files.
399 */
400static char *oldest_log(char* file, int numlogs)
401{
402    char tmp[MAXPATHLEN];
403    char num[MAXPATHLEN];
404
405    sprintf(tmp, "%s.", file);
406    sprintf(num, "%d",  numlogs);
407    return matching_file_compared(tmp, num, cmp_earliest_date, numlogs);
408}
409
410
411/*** examining linked lists of flags ***/
412
413/* return the flag_entry for option letter (if it's currently used), or NULL */
414static struct flag_entry *get_flag_entry(char option, struct flag_entry *flags)
415{
416#ifdef DBG_FLAG_ENTRY
417    { int debuggers_suck_when_breakpoint_is_a_while_loop = 1; }
418#endif
419
420    /* go down the list until we find a match. */
421    while (flags) {
422        if (option == flags->option)
423            return flags;
424        flags = flags->next;
425    }
426
427    return (struct flag_entry*)NULL;  /* not found */
428}
429
430/* check if an option letter is already used for a flag */
431static int already_used (char option, struct flag_entry *flags)
432{
433    /* these letters cannot be redefined! */
434    if ((option == FL_BINARY) || (option == FL_DATED))
435        return 1;
436
437    /* otherwise check the list */
438    if ( get_flag_entry(option,flags) )
439        return 1;
440
441    return 0;   /* not found */
442}
443
444/*** command-line parsing ***/
445
446static void usage(void)
447{
448    fprintf(stderr,
449            "Usage: %s [-nrv] [-f config-file] [-t restart-time]\n", progname);
450    exit(1);
451}
452
453/* Parse the command-line arguments */
454static void PRS(int argc, char **argv)
455{
456    int c;
457    char    *end;
458
459    progname = argv[0];
460    timenow = time((time_t *) 0);
461    daytime = ctime(&timenow);
462    daytime[strlen(daytime)-1] = '\0';
463    if (!strftime(datestamp, DATE_LENGTH+1,DATE_FORMAT, localtime(&timenow))) {
464        for (c=0; c<DATE_LENGTH; c++)
465            datestamp[c] = '0';
466        datestamp[DATE_LENGTH] = '\0';
467    }
468
469    /* Let's get our hostname */
470    /* WARNING: on Solaris, -lnsl has gethostname().
471       Using -lucb may be dangerous to your foot. --bert 7nov1995 */
472    if (gethostname(hostname, 64)) {
473        perror("gethostname");
474        (void) strcpy(hostname,"Mystery Host");
475    }
476
477    optind = 1;         /* Start options parsing */
478    while ((c=getopt(argc,argv,"nrvf:t:")) != EOF)
479        switch (c) {
480         case 'n':
481            noaction++; /* This implies needroot as off */
482            /* fall through */
483         case 'r':
484            needroot = 0;
485            break;
486         case 'v':
487            verbose++;
488            break;
489         case 'f':
490            conf = optarg;
491            break;
492         case 't':
493            sleeptm = strtol(optarg, &end, 0);
494            if (end && *end) /* arg contains non-numeric chars */
495                usage();
496            break;
497         default:
498            usage();
499        }
500}
501
502/*** config file parsing ***/
503
504/* Complain if the first argument is NULL, return it otherwise. */
505static char *missing_field(char *p, char *errline)
506{
507    if (!p || !*p) {
508        fprintf(stderr,"Missing field in config file:\n");
509        fputs(errline,stderr);
510        exit(1);
511    }
512    return(p);
513}
514
515/* Parse a logfile description from the configuration file and update
516 * the linked list of logfiles
517 */
518static void parse_logfile_line(char *line, struct log_entry **first,
519                               struct flag_entry *flags_list)
520{
521    char   *parse, *q;
522    char          *errline, *group = NULL;
523    struct passwd *pass;
524    struct group *grp;
525    struct log_entry *working = (*first);
526    struct flag_entry *opt;
527
528    errline = strdup(line);
529
530    if (!working) {
531        (*first) = working =
532            (struct log_entry *) malloc(sizeof(struct log_entry));
533        if (!working) abort();
534    } else {
535        while (working->next)
536            working = working->next;
537
538        working->next = (struct log_entry *)malloc(sizeof(struct log_entry));
539        working = working->next;
540        if (!working) abort();
541    }
542
543    working->next = (struct log_entry *)NULL;
544
545    q = parse = missing_field(sob(line),errline);
546    *(parse = son(line)) = '\0';
547    working->log = strdup(q);
548
549    q = parse = missing_field(sob(++parse),errline);
550    *(parse = son(parse)) = '\0';
551    if ((group = strchr(q, '.')) != NULL) {
552        *group++ = '\0';
553        if (*q) {
554            if (!(isnumber(q))) {
555                if ((pass = getpwnam(q)) == NULL) {
556                    fprintf(stderr,
557                            "Error in config file; unknown user:\n");
558                    fputs(errline,stderr);
559                    exit(1);
560                }
561                working->uid = pass->pw_uid;
562            } else
563                working->uid = atoi(q);
564        } else
565            working->uid = NONE;
566
567        q = group;
568        if (*q) {
569            if (!(isnumber(q))) {
570                if ((grp = getgrnam(q)) == NULL) {
571                    fprintf(stderr,
572                            "Error in config file; unknown group:\n");
573                    fputs(errline,stderr);
574                    exit(1);
575                }
576                working->gid = grp->gr_gid;
577            } else
578                working->gid = atoi(q);
579        } else
580            working->gid = NONE;
581
582        q = parse = missing_field(sob(++parse),errline);
583        *(parse = son(parse)) = '\0';
584    }
585    else  /* next string does not contain a '.' */
586        working->uid = working->gid = NONE;
587
588    if (!sscanf(q,"%o",&working->permissions)) {
589        fprintf(stderr,
590                "Error in config file; bad permissions:\n");
591        fputs(errline,stderr);
592        exit(1);
593    }
594
595    q = parse = missing_field(sob(++parse),errline);
596    *(parse = son(parse)) = '\0';
597    if (!sscanf(q,"%d",&working->numlogs)) {
598        fprintf(stderr,
599                "Error in config file; bad number:\n");
600        fputs(errline,stderr);
601        exit(1);
602    }
603
604    q = parse = missing_field(sob(++parse),errline);
605    *(parse = son(parse)) = '\0';
606    if (isdigit((unsigned char)*q))
607        working->size = atoi(q);
608    else
609        working->size = -1;
610
611    q = parse = missing_field(sob(++parse),errline);
612    *(parse = son(parse)) = '\0';
613    if (isdigit((unsigned char)*q))
614        working->hours = atoi(q);
615    else
616        working->hours = -1;
617
618    q = parse = sob(++parse); /* Optional field */
619    *(parse = son(parse)) = '\0';
620    working->flags = 0;
621    working->exec_flags = NULL;
622    working->pid_flags = NULL;
623
624    while (q && *q && !isspace((unsigned char)*q)) {
625        char qq = toupper(*q);
626        if (qq == FL_BINARY)
627            working->flags |= CE_BINARY;
628        else if (qq == FL_DATED)
629            working->flags |= CE_DATED;
630        else if (( opt = get_flag_entry(qq, flags_list) )) {
631            if ( opt->type == EXEC )
632                grow_option_string( &(working->exec_flags), qq );
633            else if ( opt->type == PID || opt->type == PNAME )
634                grow_option_string( &(working->pid_flags), qq );
635            else abort();
636        } else {
637            fprintf(stderr,
638                    "Illegal flag in config file -- %c\n",
639                    *q);
640            exit(1);
641        }
642        q++;
643    }
644
645    free(errline);
646}
647
648/* Parse a program-to-run description from the configuration file and update
649 * the linked list of flag entries
650 */
651static void parse_exec_line(char *line, struct flag_entry **first)
652{
653    char *parse, *q;
654    char *errline;
655    struct flag_entry *working = (*first);
656    int num_args, i;
657
658    errline = strdup(line);
659
660    if (!working) {
661        (*first) = working =
662            (struct flag_entry *) malloc(sizeof(struct flag_entry));
663        if (!working) abort();
664    } else {
665        while (working->next)
666            working = working->next;
667
668        working->next = (struct flag_entry*) malloc(sizeof(struct flag_entry));
669        working = working->next;
670        if (!working) abort();
671    }
672    working->next = (struct flag_entry *)NULL;
673    working->type = EXEC;
674
675    parse = missing_field(sob(line),errline);
676    parse = missing_field(son(parse),errline);   /* skip over the keyword */
677
678    parse = missing_field(sob(parse),errline);
679    working->option = 0;   /* This is here so already_used doesn't break. */
680    if (already_used(toupper(*parse), *first)) {
681        fprintf(stderr,
682                "Error in config file; option letter already in use:\n");
683        fputs(errline,stderr);
684        exit(1);
685    }
686    working->option = toupper(*parse);
687    if (!isspace((unsigned char)*(++parse))) {
688        fprintf(stderr,
689                "Error in config file; more than one option letter:\n");
690        fputs(errline,stderr);
691        exit(1);
692    }
693
694    q = parse = missing_field(sob(parse),errline);
695    if (*q == '.') {  /* extension */
696        *(parse = son(parse)) = '\0';
697        working->extension = strdup(++q);
698       
699        q = parse = missing_field(sob(++parse),errline);
700    }
701    else  /* no extension */
702        working->extension = NULL;
703
704    num_args = 0;
705    while (q && (*q)) {
706        num_args++;
707        q = son(q);
708        if (q && (*q))
709            q = sob(q);
710    }
711    working->args = (char**) malloc((num_args+2)*sizeof(char*));
712    if (! working->args) abort();
713    for (i=0; i<num_args; i++) {
714        q = missing_field(parse,errline);
715        *(parse = son(parse)) = '\0';
716      working->args[i] = strdup(q);
717        parse = sob(++parse);
718    }
719    working->args[num_args] = NULL;    /* placeholder for log file name */
720    working->args[num_args+1] = NULL;  /* end of array */
721    working->nargs = num_args;
722   
723    free(errline);
724}
725
726/* Parse a process-to-restart description from the configuration file and
727 * update the linked list of flag entries
728 */
729static void parse_pid_line(char *line, struct flag_entry **first)
730{
731    char *parse, *q, *end;
732    char        *errline;
733    struct flag_entry *working = (*first);
734    char oops;
735
736    errline = strdup(line);
737
738    if (!working) {
739        (*first) = working =
740            (struct flag_entry *) malloc(sizeof(struct flag_entry));
741        if (!working) abort();
742    } else {
743        while (working->next)
744            working = working->next;
745
746        working->next = (struct flag_entry *)malloc(sizeof(struct flag_entry));
747        working = working->next;
748        if (!working) abort();
749    }
750
751    working->next = (struct flag_entry *)NULL;
752
753    parse = missing_field(sob(line),errline);
754
755    if (!strncasecmp(parse, KEYWORD_PID, sizeof(KEYWORD_PID)-1)
756        && isspace((unsigned char)parse[sizeof(KEYWORD_PID)-1]))
757      working->type = PID;
758    else
759      working->type = PNAME;
760
761    parse = missing_field(son(parse),errline);   /* skip over the keyword */
762
763    parse = missing_field(sob(parse),errline);
764    working->option = 0;   /* This is here so already_used doesn't break. */
765    if (already_used(toupper(*parse), *first)) {
766        fprintf(stderr,
767                "Error in config file; option letter already in use:\n");
768        fputs(errline,stderr);
769        exit(1);
770    }
771    working->option = toupper(*parse);
772    if (!isspace((unsigned char)*(++parse))) {
773        fprintf(stderr,
774                "Error in config file; more than one option letter:\n");
775        fputs(errline,stderr);
776        exit(1);
777    }
778
779    q = parse = missing_field(sob(++parse),errline);
780    oops = *(parse = son(parse));
781    (*parse) = '\0';
782    working->pidident = strdup(q);
783    (*parse) = oops;  /* parse may have been pointing at the end of
784                       * the line already, so we can't just ++ it.
785                       * (Falling off the edge of the world would be bad.) */
786
787    /* the signal field is optional */
788    q = parse = sob(parse);
789    if (q && (*q)) {
790        *(parse = son(parse)) = '\0';
791        if (! (working->signal = signal_number(q))) {
792            working->signal = strtol(q, &end, 0);
793            if ((! working->signal) || (end != parse)) {
794                fprintf(stderr,
795                        "Error in config file; bad signal number:\n");
796                fputs(errline,stderr);
797                exit(1);
798            }
799        }
800    } else {
801        /* default signal value */
802        working->signal = SIGHUP;
803    }
804
805    working->ref_count = 0;
806
807    free(errline);
808}
809
810/* Add support for default flags to the linked list of flag entries.
811 */
812static void add_default_flags(struct flag_entry **first)
813{
814    struct flag_entry *working = (*first);
815
816    /* we find out if Z flag is used *before* we change the linked list */
817    int have_Z = already_used(FL_COMPRESS, *first);
818
819    /* we'll be adding at least one new entry. */
820    if (!working) {
821        (*first) = working =
822            (struct flag_entry *) malloc(sizeof(struct flag_entry));
823        if (!working) abort();
824    } else {
825        while (working->next)
826            working = working->next;
827        working->next = (struct flag_entry*) malloc(sizeof(struct flag_entry));
828        working = working->next;
829        if (!working) abort();
830    }
831
832    /* add 'Z' flag for running "compress" on the logfile. */
833    if (! have_Z) {
834        working->option = FL_COMPRESS;
835        working->type = EXEC;
836        working->extension = "Z";
837
838        working->args = (char**) malloc(4*sizeof(char*));
839        if (! working->args) abort();
840        working->args[0] = strdup(COMPRESS);
841        working->args[1] = strdup("-f");
842        working->args[2] = NULL;          /* placeholder for log file name */
843        working->args[3] = NULL;          /* end of array */
844        working->nargs = 2;
845
846        /* we used up an entry, so we allocate a new one. */
847        working->next = (struct flag_entry*) malloc(sizeof(struct flag_entry));
848        working = working->next;
849        if (!working) abort();
850    } else {
851        fprintf(stderr, "warning: '%c' flag has been redefined.\n",
852                FL_COMPRESS);
853    }
854
855    /* add unnamed flag for restarting syslogd. */
856#ifdef USE_PROCNAME
857    working->type = PNAME;
858    working->pidident = SYSLOG_PNAME;
859#else
860    working->type = PID;
861    working->pidident = SYSLOG_PID;
862#endif
863    working->option = 0;
864    working->signal = SIGHUP;
865    working->ref_count = 0;
866
867    working->next = (struct flag_entry *)NULL;
868}
869
870/* Parse a configuration file and return all relevant data in several
871 * linked lists
872 */
873static void parse_file(struct log_entry **logfiles, struct flag_entry **flags)
874{
875    FILE        *f;
876    char        line[BUFSIZ];
877    int add_flags = 1;
878
879    (*logfiles) = (struct log_entry *) NULL;
880    (*flags) = (struct flag_entry *) NULL;
881
882    if (strcmp(conf,"-"))
883        f = fopen(conf,"r");
884    else
885        f = stdin;
886    if (!f) {
887        (void) fprintf(stderr,"%s: ",progname);
888        perror(conf);
889        exit(1);
890    }
891    while (fgets(line,BUFSIZ,f)) {
892        if ((line[0]== '\n') || (line[0] == '#'))
893            continue;
894        if (!strncasecmp(line, KEYWORD_EXEC, sizeof(KEYWORD_EXEC)-1)
895            && isspace((unsigned char)line[sizeof(KEYWORD_EXEC)-1])) {
896            parse_exec_line(line, flags);
897            continue;
898        }
899        if ((!strncasecmp(line, KEYWORD_PID, sizeof(KEYWORD_PID)-1)
900             && isspace((unsigned char)line[sizeof(KEYWORD_PID)-1]))
901#ifdef ALLOW_PROCNAME
902            || (!strncasecmp(line, KEYWORD_PNAME, sizeof(KEYWORD_PNAME)-1)
903                && isspace(line[sizeof(KEYWORD_PNAME)-1]))) {
904#else
905            ) {
906#endif
907            parse_pid_line(line, flags);
908            continue;
909        }
910
911        /* Add records for some of the default flags.  Do it before
912         * the first file-to-turn-over line, so 'Z' can be redefined.
913         * Also, make sure to do it only once. */
914        if (add_flags) {
915            add_default_flags(flags);
916            add_flags = 0;
917        }
918
919        parse_logfile_line (line, logfiles, *flags);
920    }
921    (void) fclose(f);
922}
923
924/*** support functions for turning logfiles over ***/
925
926/* Return size in kilobytes of a file */
927static int sizefile (char *file)
928{
929    struct stat sb;
930
931    if (stat(file,&sb) < 0)
932        return(-1);
933    return(kbytes(dbtob(sb.st_blocks)));
934}
935
936/* Return the age of old log file (file.0) */
937static int age_old_log (char *file)
938{
939    struct stat sb;
940    char* last = newest_log(file);
941
942    if (!last || (stat(last, &sb) < 0))
943        return(-1);
944    free(last);
945
946#ifdef MINUTES    /* this is for debugging: times in minutes instead of hrs */
947    return( (int) (timenow - sb.st_mtime + 30) / 60);
948#else
949    return( (int) (timenow - sb.st_mtime + 1800) / 3600);
950#endif
951}
952
953/* Log the fact that the logs were turned over */
954static int log_trim(char *log)
955{
956    FILE        *f;
957    if ((f = fopen(log,"a")) == NULL)
958        return(-1);
959    fprintf(f,"%s %s newsyslog[%d]: logfile turned over\n",
960            daytime, hostname, (int)getpid());
961    if (fclose(f) == EOF) {
962        perror("log_trim: fclose:");
963        exit(1);
964    }
965    return(0);
966}
967
968/* Fork a program to compress or otherwise process the old log file */
969static void compress_log(char *log, struct flag_entry *flag)
970{
971    int pid;
972    int     i;
973       
974    if (noaction) {
975        printf("flag '%c':\n ", flag->option);
976        for (i=0; i < flag->nargs; i++)   printf(" %s", flag->args[i]);
977        printf(" %s\n", log);
978    } else {
979        pid = fork();
980        if (pid < 0) {
981            fprintf(stderr,"%s: ",progname);
982            perror("fork");
983            exit(1);
984        } else if (!pid) {
985            flag->args[flag->nargs] = log;
986#ifdef USE_EXECVP
987            /* execvp() looks for the program in PATH (unless path is given) */
988            (void) execvp(flag->args[0], flag->args);
989#else
990            (void) execv(flag->args[0], flag->args);
991#endif
992            fprintf(stderr,"%s: ",progname);
993            perror(flag->args[0]);
994            exit(1);
995        }
996    }
997}
998
999/* Restart the process whose PID is given in the specified file */
1000static void restart_proc(char *pidfile, int signum)
1001{
1002    FILE        *f;
1003    char        line[BUFSIZ];
1004    int  pid = 0;
1005    char *signame;
1006
1007    /* Let's find the pid of syslogd */
1008    f = fopen(pidfile, "r");
1009    if (f && fgets(line,BUFSIZ,f))
1010        pid = atoi(line);
1011    fclose(f);
1012
1013    if (pid) {
1014        if (noaction) {
1015            if (( signame = signal_name(signum) ))
1016                printf("  kill -%s %d (%s)\n", signame, pid, pidfile);
1017            else
1018                printf("  kill -%d %d (%s)\n", signum, pid, pidfile);
1019            printf("  sleep %d\n", sleeptm);
1020        } else {
1021            if (kill(pid, signum)) {
1022                fprintf(stderr,"%s: %s: ", progname, pidfile);
1023                perror("warning - could not restart process");
1024            }
1025            if (sleeptm > 0)
1026                sleep(sleeptm);
1027        }
1028    } else {
1029        fprintf(stderr,"%s: %s: ", progname, pidfile);
1030        perror("warning - could not read pidfile");
1031    }
1032}
1033
1034#ifdef ALLOW_PROCNAME
1035static int run(char *what)
1036{
1037  char cmd[1024], *args[20], *ptr;
1038  int i = 0, status;
1039  sigset_t mask, omask;
1040  pid_t pid;
1041
1042  if (what == NULL)
1043    return -1;
1044
1045  strcpy(cmd, what);
1046  ptr = cmd;
1047  while (*ptr != '\0')
1048    {
1049      args[i++] = ptr;
1050      while (*ptr != '\0' && *ptr != ' ') ptr++;
1051      if (*ptr == ' ')
1052        *ptr++ = '\0';
1053    }
1054  args[i] = NULL;
1055
1056  /* Guard against kidnapping. */
1057  sigemptyset(&mask);
1058  sigaddset(&mask, SIGCHLD);
1059  sigprocmask(SIG_BLOCK, &mask, &omask);
1060
1061  if ((pid = fork()) == 0)
1062    {
1063      sigprocmask(SIG_SETMASK, &omask, NULL);
1064      execvp(args[0], args);
1065      exit(-1);
1066    }
1067
1068  if (pid == -1)
1069    {
1070      sigprocmask(SIG_SETMASK, &omask, NULL);
1071      return -1;
1072    }
1073
1074  while (pid != waitpid(pid, &status, 0));
1075  sigprocmask(SIG_SETMASK, &omask, NULL);
1076
1077  if (WIFEXITED(status))
1078    return WEXITSTATUS(status);
1079
1080  return -2;
1081}
1082
1083static int killproc(char *procname, int signum)
1084{
1085    char line[BUFSIZ];
1086
1087    sprintf(line, "killall -%d %s", signum, procname);
1088    return run(line);
1089}
1090
1091/* Restart the process whose name has been specified */
1092static void restart_procname(char *procname, int signum)
1093{
1094    char *signame;
1095
1096    if (noaction) {
1097       if (( signame = signal_name(signum) ))
1098          printf("  killproc -%s %s\n", signame, procname);
1099       else
1100          printf("  killproc -%d %s\n", signum, procname);
1101       printf("  sleep %d\n", sleeptm);
1102    } else {
1103       if (killproc(procname, signum)) {
1104          fprintf(stderr,"%s: %s: ", progname, procname);
1105          perror("warning - could not restart process");
1106       }
1107       if (sleeptm > 0)
1108         sleep(sleeptm);
1109    }
1110}
1111#endif /* ALLOW_PROCNAME */
1112
1113/*** various fun+games with logfile entries ***/
1114
1115/* Examine an entry and figure out whether the logfile needs to be
1116 * turned over; if it does, mark it with CE_ACTIVE flag.
1117 */
1118static void do_entry(struct log_entry *ent)
1119{
1120    int size, modtime;
1121
1122    if (verbose) {
1123        printf("%s <%d>: ",ent->log,ent->numlogs);
1124    }
1125    size = sizefile(ent->log);
1126    modtime = age_old_log(ent->log);
1127    if (size < 0) {
1128        if (verbose)
1129            printf("does not exist.\n");
1130    } else {
1131        if (verbose && (ent->size > 0))
1132            printf("size (Kb): %d [%d] ", size, ent->size);
1133        if (verbose && (ent->hours > 0))
1134            printf(" age (hr): %d [%d] ", modtime, ent->hours);
1135        if (((ent->size > 0) && (size >= ent->size)) ||
1136            ((ent->hours > 0) && ((modtime >= ent->hours)
1137                                  || (modtime < 0)))) {
1138            if (verbose)
1139                printf("--> trimming log....\n");
1140            if (noaction && !verbose) {
1141                printf("%s <%d>: trimming\n",
1142                       ent->log,ent->numlogs);
1143            }
1144            ent->flags |= CE_ACTIVE;
1145        } else {
1146            if (verbose)
1147                printf("--> skipping\n");
1148        }
1149    }
1150}
1151
1152/* Turn over the logfiles and create a new one, in preparation for a
1153 * restart of the logging process(es)
1154 */
1155static void do_trim(struct log_entry *ent)
1156{
1157    char    file1[MAXPATHLEN], file2[MAXPATHLEN];
1158    char    *zfile1, zfile2[MAXPATHLEN];
1159    char    *freep = NULL;
1160    int fd;
1161    struct stat st;
1162    int numdays = ent->numlogs;
1163    int owner_uid = ent->uid;
1164    struct flag_entry *flg;
1165
1166#ifdef _IBMR2
1167/* AIX 3.1 has a broken fchown- if the owner_uid is -1, it will actually */
1168/* change it to be owned by uid -1, instead of leaving it as is, as it is */
1169/* supposed to. */
1170        if (owner_uid == -1)
1171          owner_uid = geteuid();
1172#endif
1173
1174    /* Remove oldest log */
1175    zfile1 = freep = oldest_log(ent->log, numdays);
1176    while (zfile1) {
1177        if (noaction) {
1178            printf("  rm -f %s ...\n", zfile1);
1179            zfile1 = NULL;
1180        } else {
1181            (void) unlink(zfile1);
1182            if (freep) { free(freep);  freep = NULL; }
1183            zfile1 = freep = oldest_log(ent->log, numdays);
1184        }
1185    }
1186
1187    /* Note: if all of the logs are dated, the rolling-over code does nothing.
1188     * However, it does no harm, and it is vaguely useful in the transition.
1189     */
1190
1191    /* Move down log files */
1192    (void) sprintf(file1,"%s.%d", ent->log, numdays);
1193    while (numdays--) {
1194        (void) strcpy(file2,file1);
1195        (void) sprintf(file1,"%s.%d",ent->log,numdays);
1196
1197        if (!stat(file1, &st))
1198            zfile1 = file1;
1199        else {
1200            if (freep) { free(freep);  freep = NULL; }
1201            zfile1 = freep = matching_file(file1);
1202            if (!zfile1) continue;
1203        }
1204
1205        /* the extension starts at (zfile1 + strlen(file1)) */
1206        (void) strcpy(zfile2, file2);
1207        (void) strcat(zfile2, (zfile1 + strlen(file1)));
1208
1209        if (noaction) {
1210            printf("  mv %s %s\n",zfile1,zfile2);
1211            printf("  chmod %o %s\n", ent->permissions, zfile2);
1212            printf("  chown %d.%d %s\n",
1213                   owner_uid, ent->gid, zfile2);
1214        } else {
1215            (void) rename(zfile1, zfile2);
1216            (void) chmod(zfile2, ent->permissions);
1217            (void) chown(zfile2, owner_uid, ent->gid);
1218        }
1219    }
1220    if (freep) { free(freep);  freep = NULL; }
1221
1222    if (ent->flags & CE_DATED) {    /* if logs are dated, generate new name  */
1223        (void) sprintf(file1, "%s.%s", ent->log, datestamp);
1224    }
1225
1226    if (noaction)
1227        printf("  mv %s %s\n",ent->log, file1);
1228    else
1229        (void) rename(ent->log, file1);
1230    if (noaction)
1231        printf("Start new log...\n");
1232    else {
1233        fd = creat(ent->log, ent->permissions);
1234        if (fd < 0) {
1235            perror("can't start new log");
1236            exit(1);
1237        }               
1238        if (fchown(fd, owner_uid, ent->gid)) {
1239            perror("can't chmod new log file");
1240            exit(1);
1241        }
1242        (void) close(fd);
1243        if (!(ent->flags & CE_BINARY))
1244            if (log_trim(ent->log)) {      /* Add status message */
1245                perror("can't add status message to log");
1246                exit(1);
1247            }
1248    }
1249    if (noaction)
1250        printf("  chmod %o %s\n",ent->permissions,ent->log);
1251    else
1252        (void) chmod(ent->log,ent->permissions);
1253
1254    /* Everything on the pid_flags list should get tagged to be restarted. */
1255    if (ent->pid_flags) {
1256        char* pid;
1257        for (pid = ent->pid_flags; *pid; pid++) {
1258            flg = get_flag_entry(*pid, flags);
1259            if (!flg || ((flg->type != PID) && (flg->type != PNAME))) abort();
1260
1261            flg->ref_count++;
1262        }
1263    } else {
1264        /* do the default restart. ('\0' is special.) */
1265        flg = get_flag_entry('\0', flags);
1266        if (!flg || ((flg->type != PID) && flg->type != PNAME)) abort();
1267
1268        flg->ref_count++;
1269    }
1270}
1271
1272/* Take the old logfile, mark it with current time, then run the
1273 * apropriate program(s) on it.
1274 */
1275static void do_compress(struct log_entry *ent)
1276{
1277    struct flag_entry *flg;
1278    char* exec;
1279    char  old[MAXPATHLEN];
1280
1281   
1282    if (ent->flags & CE_DATED) {
1283        sprintf(old, "%s.%s", ent->log, datestamp);
1284    } else {
1285        sprintf(old, "%s.0", ent->log);
1286    }
1287
1288    if (!noaction && !(ent->flags & CE_BINARY)) {
1289        (void) log_trim(old);   /* add a date stamp to old log */
1290    }
1291
1292    if ((ent->flags & CE_ACTIVE) && ent->exec_flags) {
1293        for (exec = ent->exec_flags; *exec; exec++) {
1294            flg = get_flag_entry(*exec, flags);
1295            if (!flg || (flg->type != EXEC)) abort();
1296
1297            compress_log(old, flg);
1298
1299            if (flg->extension) {
1300                strcat(old, ".");
1301                strcat(old, flg->extension);
1302            }
1303        }
1304    }
1305}
1306
1307/*** main ***/
1308
1309int main(int argc, char **argv)
1310{
1311    struct log_entry *p, *q;
1312    struct flag_entry *flg;
1313
1314    PRS(argc,argv);
1315    if (needroot && getuid() && geteuid()) {
1316        fprintf(stderr,"%s: must have root privs\n",progname);
1317        exit(1);
1318    }
1319    parse_file(&q, &flags);  /* NB: 'flags' is a global variable */
1320   
1321    for (p = q; p; p = p->next)
1322        do_entry(p);
1323
1324    for (p = q; p; p = p->next)
1325        if (p->flags & CE_ACTIVE) {
1326            if (verbose>1) printf("Trimming %s\n", p->log);
1327            do_trim(p);
1328        }
1329
1330    for (flg = flags; flg; flg = flg->next)
1331        if ((flg->type == PID) && flg->ref_count) {
1332            if (verbose>1) printf("Restarting %s\n", flg->pidident);
1333            restart_proc(flg->pidident, flg->signal);
1334        }
1335
1336#ifdef ALLOW_PROCNAME
1337    for (flg = flags; flg; flg = flg->next)
1338        if ((flg->type == PNAME) && flg->ref_count) {
1339            if (verbose>1) printf("Restarting %s\n", flg->pidident);
1340            restart_procname(flg->pidident, flg->signal);
1341        }
1342#endif
1343
1344    for (p = q; p; p = p->next) {
1345        if (p->flags & CE_ACTIVE) {
1346            if (verbose>1) printf("Processing %s\n", p->log);
1347            do_compress(p);
1348        }
1349    }
1350
1351    return 0;
1352}
1353
1354/*
1355 * Local Variables:
1356 * mode: c
1357 * c-indent-level: 4
1358 * c-continued-statement-offset: 4
1359 * c-label-offset: -3
1360 * End:
1361 */
Note: See TracBrowser for help on using the repository browser.