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

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