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

Revision 8707, 35.1 KB checked in by cfields, 28 years ago (diff)
Add "signame" option. Like "signal," but takes name of process rather than name of pid file. For SGI, compile this code, and use it for killing syslogd.
Line 
1/*
2 *      newsyslog - roll over selected logs at the appropriate time,
3 *              keeping the a specified number of backup files around.
4 *
5 *      $Source: /afs/dev.mit.edu/source/repository/athena/etc/newsyslog/newsyslog.c,v $
6 *      $Author: cfields $    $Revision: 1.7 $
7 */
8
9#ifndef lint
10static char *rcsid = "$Header: /afs/dev.mit.edu/source/repository/athena/etc/newsyslog/newsyslog.c,v 1.7 1996-08-04 03:18:34 cfields Exp $";
11#endif  /* lint */
12
13#include "config.h"
14
15#include <sys/types.h>
16#include <stdio.h>
17#include <stdlib.h>
18#include <unistd.h>
19#ifdef HAVE_STRING_H
20#include <string.h>
21#else
22#include <strings.h>  /* only VAXen don't have string.h --bert 15mar96 */
23#endif
24#include <ctype.h>
25#include <signal.h>
26#include <pwd.h>
27#include <grp.h>
28#include <sys/stat.h>
29#include <sys/param.h>
30#include <sys/wait.h>
31#include <fcntl.h>
32#include <sys/time.h>
33#include <time.h>
34#include <dirent.h>
35
36#include "signames.h"
37
38#ifdef sgi
39/* Note: Irix has the "killall" command, which is like kill except
40   that it takes a process name instead of an id. This command is
41   not to be confused with the Solaris killall command, which kills
42   just about everything. It would probably be nice just to go
43   ahead and read the process table instead of execing an external
44   program, but... */
45#define ALLOW_PROCNAME
46#define USE_PROCNAME
47#endif
48
49/*** defines ***/
50
51#define CONF "/etc/athena/newsyslog.conf"       /* Configuration file */
52#define SYSLOG_PID "/etc/syslog.pid"            /* Pidfile for syslogd */
53#define SYSLOG_PNAME "syslogd"                  /* Process name for syslogd */
54
55/* COMPRESS is now defined in config.h */
56
57#ifndef SLEEP_DELAY
58#define SLEEP_DELAY 1    /* Default delay used after restarting each daemon, */
59#endif                           /* to give it time to clean up (in seconds) */
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/* special (i.e. non-configurable) flags for logfile entries */
68#define CE_ACTIVE 1     /* Logfile needs to be turned over */
69#define CE_BINARY 2     /* Logfile is in binary, don't add status messages */
70#define CE_DATED  4     /* Mark the logfile with date, not number */
71
72#define NONE -1
73
74/* Definitions of the keywords for the logfile.
75 * Should not be changed once we come up with good ones and document them. =)
76 */
77/* This is used to add another post-processing command, e.g. 'gzip -9' */
78#define KEYWORD_EXEC "filter"
79/* This describes a process to restart, other than syslogd */
80#define KEYWORD_PID  "signal"
81/* This works as "signal," only takes a process name rather than a pid file. */
82#define KEYWORD_PNAME "signame"
83
84/* Definitions of the predefined flag letters. */
85#define FL_BINARY 'B'   /* reserved */
86#define FL_DATED  'D'   /* reserved */
87#define FL_COMPRESS 'Z' /* can be redefined */
88
89/* Define the date format used for generating dated filenames. */
90/***If you change the format, you must change the parsing in valid_date_ext***/
91#define DATE_FORMAT "%Y%m%d"    /* YYYYMMDD, as used by strftime */
92#define DATE_LENGTH 8
93
94struct log_entry {
95    char *log;                  /* Name of the log */
96    int  uid;                   /* Owner of log */
97    int  gid;                   /* Group of log */
98    int  numlogs;               /* Number of logs to keep */
99    int  size;                  /* Size cutoff to trigger trimming the log */
100    int  hours;                 /* Hours between log trimming */
101    int  permissions;           /* File permissions on the log */
102    int  flags;                 /* Flags (CE_ACTIVE , CE_BINARY, CE_DATED) */
103    char *exec_flags;           /* Flag letters for things to run */
104    char *pid_flags;            /* Flag letters for things to restart */
105    struct log_entry *next;     /* Linked list pointer */
106};
107
108/* The same structure is used for different kinds of flags. */
109/* This wastes a little run-time space, but makes my life easier. */
110struct flag_entry {
111    char  option;               /* Option character used for this program */
112    enum { EXEC, PID, PNAME } type;     /* Type of flag ('run' vs. 'signal') */
113
114    /* EXEC flag options */
115    char  *extension;           /* Extension added by this program */
116    char  **args;               /* Program name and command-line arguments */
117    int   nargs;                /* Number of arguments not including args[0] */
118
119    /* PID/PNAME flag options */
120    char  *pidident;            /* Path to the PID file, or the process name */
121    int signal;                 /* Signal to restart the process */
122    int ref_count;              /* Number of times used (if 0, no restart) */
123
124    struct flag_entry *next;    /* Linked list pointer */
125};
126
127/* extern uid_t getuid(),geteuid(); */
128/* extern time_t time(); */
129
130/* extern's needed for getopt() processing */
131extern int      optind;
132extern char     *optarg;
133
134/*** globals ***/
135
136char    *progname;              /* name we were run as (ie, argv[0]) */
137char    hostname[64];           /* hostname of this machine */
138
139/* variables which correspond to the command-line flags */
140int     verbose = 0;            /* Print out what's going on */
141int     needroot = 1;           /* Root privs are necessary */
142int     noaction = 0;           /* Don't do anything, just show it */
143char    *conf = CONF;           /* Configuration file to use */
144int     sleeptm = SLEEP_DELAY;  /* Time to wait after restarting a daemon */
145
146time_t  timenow;                /* Time of the start of newsyslog */
147char    *daytime;               /* timenow in human readable form */
148char    datestamp[DATE_LENGTH+1]; /* timenow in the YYYTMMDD format */
149
150struct flag_entry *flags;       /* List of all config-file flags */
151
152/*** support functions ***/
153
154/* Skip Over Blanks */
155char *sob (register char *p)
156{
157    while (p && *p && isspace(*p))
158        p++;
159    return(p);
160}
161
162/* Skip Over Non-Blanks */
163char *son (register char *p)
164{
165    while (p && *p && !isspace(*p))
166        p++;
167    return(p);
168}
169
170/* Check if string is actually a number */
171int isnumber(char *string)
172{
173    while (*string != '\0') {
174        if (! isdigit(*string)) return(0);
175        string++;
176    }
177    return(1);
178}
179
180#ifndef HAVE_STRDUP
181/* Duplicate a string using malloc */
182char *strdup (register char *strp)
183{
184    register char *cp;
185
186    if ((cp = malloc((unsigned) strlen(strp) + 1)) == NULL)
187        abort();
188    return(strcpy (cp, strp));
189}
190#endif
191
192/* Tack a character to the end of a string. (Inefficient...) */
193char *grow_option_string (char **strp, char new)
194{
195    size_t len;
196
197    if (! strp)
198        return NULL;
199    else if (! *strp) {
200        len = 0;
201        (*strp) = (char*) malloc(2);
202    } else {
203        len = strlen(*strp);
204        (*strp) = (char*) realloc(*strp, len + 2);
205    }
206
207    if (! *strp) abort();
208
209    (*strp)[len] = new;
210    (*strp)[len+1] = '\0';
211
212    return *strp;
213}
214
215/*** finding the appropriate logfile, whatever its extension ***/
216
217/* Check if the filename extension is composed of valid components */
218int valid_extension(char* ext)
219{
220    struct flag_entry *cfl = flags;
221    int len;
222
223    if (ext && (*ext))
224        while (cfl) {
225            if ((cfl->type == EXEC) && (cfl->extension)) {
226                len = strlen(cfl->extension);
227
228                /* if the current extension matches... */
229                if (!strncmp(ext, cfl->extension, len)) {
230                    /* exact match */
231                    if (ext[len] == '\0')
232                        return 1;
233                    /* match, but we have more to check */
234                    else if (ext[len] == '.') {
235                        if (valid_extension(ext + len + 1))
236                            return 1;
237                        /* if valid_ext returns false, try other possibilities;
238                           that way, one proc can add >1 ext (.tar.gz) */
239                    }
240                }
241            }
242            cfl = cfl->next;
243        }
244    return 0;
245}
246
247/* Check if the string is empty, or a dot followed by a valid extension */
248int valid_dot_ext(char* dot)
249{
250    if (!dot)
251        return 0;
252    else if (*dot == '\0')
253        return 1;
254    else if (*dot == '.')
255        return valid_extension(dot + 1);
256
257    return 0;
258}
259
260/* Check if the string is a valid date (YYYYMMDD) plus extension */
261int valid_date_ext(char* date)
262{
263    /* if we're still compiling C code in 2099, something is horribly wrong */
264    return (((date[0]=='1' && date[1]=='9')
265             || (date[0]=='2' && date[1]=='0')) &&
266            isdigit(date[2]) && isdigit(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') && isdigit(date[7]))
271             || (date[6]=='3' && (date[7]>='0' && date[7]<='1'))) &&
272            valid_dot_ext(date + 8)) ;
273}
274
275/* Find the file whose name matches one given, with a valid extension,
276 * subject to the following rules:
277 * (1) If a filename of the form "${name}${root}valid_ext" exists,
278 *     then it is returned.  If there is more than one such file, then
279 *     the file returned is the one whose entry occurs first in the directory.
280 * (2) If not, and if cmp is non-NULL, then filenames of the form
281 *     "${name}YYYYMMDD.valid_ext" are considered.  If there are less
282 *     than ${limit} files, NULL is returned; otherwise the 'cmp-most'
283 *     match is returned; i.e. the match for which cmp returned true
284 *     for all the files it was compared to.
285 * Look below for more directly useful functions that call this one.
286 */
287char *matching_file_compared (char* name, char* root,
288                              int cmp(char*,char*,size_t), int limit)
289{
290    DIR *parent;
291    struct dirent *dent;
292    char *dirname, *namefrag, *fp, *filename;
293    char *cmpmost = NULL;
294    int fraglen, rootlen, have_path, mismatch, matches;
295
296    if (root)
297        rootlen = strlen(root);
298    else
299        rootlen = 0;
300
301    /* extract the directory path and the filename fragment. */
302    fp = dirname = strdup(name);
303    namefrag = strrchr(dirname, '/');
304    if (( have_path = (namefrag != NULL) )) {
305        *(namefrag++) = '\0';
306    } else {
307        namefrag = dirname;
308        dirname = ".";
309    }
310    fraglen = strlen(namefrag);
311
312    if (!(parent = opendir(dirname))) {
313        free(fp);
314        return (char*)NULL;
315    }
316
317    /* WARNING: on Solaris, -lucb will link with readdir() which
318       returns a *different* struct dirent.  libucb: just say no. */
319
320    matches = 0;
321    /* find matching directory entry */
322    while ((dent=readdir(parent))
323           && ((mismatch = strncmp(dent->d_name, namefrag, fraglen))
324               || (root && strncmp(dent->d_name + fraglen, root, rootlen))
325               || !valid_dot_ext(dent->d_name + fraglen + rootlen))) {
326        if (!mismatch && cmp && valid_date_ext(dent->d_name + fraglen)) {
327            matches++;
328            if ((cmpmost == NULL)
329                || cmp(dent->d_name+fraglen, cmpmost+fraglen, DATE_LENGTH))
330            {
331                /* current entry is cmp'er than previous value of cmpmost */
332                if (cmpmost)  free(cmpmost);
333                cmpmost = strdup(dent->d_name);
334            }
335        }
336    }
337
338    /* If we found an entry matching root, use that; if not, use a dated one */
339    if (dent && dent->d_name)
340        filename = dent->d_name;
341    else if (matches >= limit)
342        filename = cmpmost; /* possibly NULL */
343    else
344        filename = NULL;
345
346    if (filename) {
347        char *glued = (char*)malloc( strlen(dirname)+strlen(filename) + 3);
348        if (!glued)
349            abort();
350
351        /* reconstruct pathname+filename */
352        if (have_path) {
353            sprintf(glued, "%s/%s", dirname, filename);
354        } else
355            strcpy(glued, filename);
356
357        closedir(parent);
358        if (cmpmost)  free(cmpmost);
359        free(fp);
360        return glued;
361    } else {
362        closedir(parent);
363        if (cmpmost)  free(cmpmost);
364        free(fp);
365        return (char*)NULL;
366    }
367}
368
369/* Comparison functions: true if file1's date is earlier than file2's */
370int cmp_earliest_date (char* file1, char* file2, size_t n)
371{
372    return ( strncmp(file1, file2, n) < 0 );
373}
374
375/* Comparison functions: true if file1's date is later than file2's */
376int cmp_latest_date (char* file1, char* file2, size_t n)
377{
378    return ( strncmp(file1, file2, n) > 0 );
379}
380
381/* Find the file whose name matches one given, with a valid extension */
382char *matching_file(char* name)
383{
384    return matching_file_compared (name, NULL, NULL, -1);
385}
386
387/* Find the newest logfile: ".0" if it exists, or the latest-dated one */
388char *newest_log(char* file)
389{
390    char tmp[MAXPATHLEN];
391
392    sprintf(tmp, "%s.", file);
393    return matching_file_compared(tmp, "0", cmp_latest_date, -1);
394}
395
396/* Find the oldest logfile: ".${numlogs}" if it exists, or else the
397 * first-dated file if there are more than ${numlogs} dated files.
398 */
399char *oldest_log(char* file, int numlogs)
400{
401    char tmp[MAXPATHLEN];
402    char num[MAXPATHLEN];
403
404    sprintf(tmp, "%s.", file);
405    sprintf(num, "%d",  numlogs);
406    return matching_file_compared(tmp, num, cmp_earliest_date, numlogs);
407}
408
409
410/*** examining linked lists of flags ***/
411
412/* return the flag_entry for option letter (if it's currently used), or NULL */
413struct flag_entry *get_flag_entry (char option, struct flag_entry *flags)
414{
415#ifdef DBG_FLAG_ENTRY
416    { int debuggers_suck_when_breakpoint_is_a_while_loop = 1; }
417#endif
418
419    /* go down the list until we find a match. */
420    while (flags) {
421        if (option == flags->option)
422            return flags;
423        flags = flags->next;
424    }
425
426    return (struct flag_entry*)NULL;  /* not found */
427}
428
429/* check if an option letter is already used for a flag */
430int already_used (char option, struct flag_entry *flags)
431{
432    /* these letters cannot be redefined! */
433    if ((option == FL_BINARY) || (option == FL_DATED))
434        return 1;
435
436    /* otherwise check the list */
437    if ( get_flag_entry(option,flags) )
438        return 1;
439
440    return 0;   /* not found */
441}
442
443/*** command-line parsing ***/
444
445void usage(void)
446{
447    fprintf(stderr,
448            "Usage: %s [-nrv] [-f config-file] [-t restart-time]\n", progname);
449    exit(1);
450}
451
452/* Parse the command-line arguments */
453void PRS(int argc, char **argv)
454{
455    int c;
456    char    *end;
457
458    progname = argv[0];
459    timenow = time((time_t *) 0);
460    daytime = ctime(&timenow);
461    daytime[strlen(daytime)-1] = '\0';
462    if (!strftime(datestamp, DATE_LENGTH+1,DATE_FORMAT, localtime(&timenow))) {
463        for (c=0; c<DATE_LENGTH; c++)
464            datestamp[c] = '0';
465        datestamp[DATE_LENGTH] = '\0';
466    }
467
468    /* Let's get our hostname */
469    /* WARNING: on Solaris, -lnsl has gethostname().
470       Using -lucb may be dangerous to your foot. --bert 7nov1995 */
471    if (gethostname(hostname, 64)) {
472        perror("gethostname");
473        (void) strcpy(hostname,"Mystery Host");
474    }
475
476    optind = 1;         /* Start options parsing */
477    while ((c=getopt(argc,argv,"nrvf:t:")) != EOF)
478        switch (c) {
479         case 'n':
480            noaction++; /* This implies needroot as off */
481            /* fall through */
482         case 'r':
483            needroot = 0;
484            break;
485         case 'v':
486            verbose++;
487            break;
488         case 'f':
489            conf = optarg;
490            break;
491         case 't':
492            sleeptm = strtol(optarg, &end, 0);
493            if (end && *end) /* arg contains non-numeric chars */
494                usage();
495            break;
496         default:
497            usage();
498        }
499}
500
501/*** config file parsing ***/
502
503/* Complain if the first argument is NULL, return it otherwise. */
504char *missing_field(char *p, char *errline)
505{
506    if (!p || !*p) {
507        fprintf(stderr,"Missing field in config file:\n");
508        fputs(errline,stderr);
509        exit(1);
510    }
511    return(p);
512}
513
514/* Parse a logfile description from the configuration file and update
515 * the linked list of logfiles
516 */
517void parse_logfile_line(char *line, struct log_entry **first,
518                        struct flag_entry *flags_list)
519{
520    char   *parse, *q;
521    char          *errline, *group = NULL;
522    struct passwd *pass;
523    struct group *grp;
524    struct log_entry *working = (*first);
525    struct flag_entry *opt;
526
527    errline = strdup(line);
528
529    if (!working) {
530        (*first) = working =
531            (struct log_entry *) malloc(sizeof(struct log_entry));
532        if (!working) abort();
533    } else {
534        while (working->next)
535            working = working->next;
536
537        working->next = (struct log_entry *)malloc(sizeof(struct log_entry));
538        working = working->next;
539        if (!working) abort();
540    }
541
542    working->next = (struct log_entry *)NULL;
543
544    q = parse = missing_field(sob(line),errline);
545    *(parse = son(line)) = '\0';
546    working->log = strdup(q);
547
548    q = parse = missing_field(sob(++parse),errline);
549    *(parse = son(parse)) = '\0';
550    if ((group = strchr(q, '.')) != NULL) {
551        *group++ = '\0';
552        if (*q) {
553            if (!(isnumber(q))) {
554                if ((pass = getpwnam(q)) == NULL) {
555                    fprintf(stderr,
556                            "Error in config file; unknown user:\n");
557                    fputs(errline,stderr);
558                    exit(1);
559                }
560                working->uid = pass->pw_uid;
561            } else
562                working->uid = atoi(q);
563        } else
564            working->uid = NONE;
565
566        q = group;
567        if (*q) {
568            if (!(isnumber(q))) {
569                if ((grp = getgrnam(q)) == NULL) {
570                    fprintf(stderr,
571                            "Error in config file; unknown group:\n");
572                    fputs(errline,stderr);
573                    exit(1);
574                }
575                working->gid = grp->gr_gid;
576            } else
577                working->gid = atoi(q);
578        } else
579            working->gid = NONE;
580
581        q = parse = missing_field(sob(++parse),errline);
582        *(parse = son(parse)) = '\0';
583    }
584    else  /* next string does not contain a '.' */
585        working->uid = working->gid = NONE;
586
587    if (!sscanf(q,"%o",&working->permissions)) {
588        fprintf(stderr,
589                "Error in config file; bad permissions:\n");
590        fputs(errline,stderr);
591        exit(1);
592    }
593
594    q = parse = missing_field(sob(++parse),errline);
595    *(parse = son(parse)) = '\0';
596    if (!sscanf(q,"%d",&working->numlogs)) {
597        fprintf(stderr,
598                "Error in config file; bad number:\n");
599        fputs(errline,stderr);
600        exit(1);
601    }
602
603    q = parse = missing_field(sob(++parse),errline);
604    *(parse = son(parse)) = '\0';
605    if (isdigit(*q))
606        working->size = atoi(q);
607    else
608        working->size = -1;
609
610    q = parse = missing_field(sob(++parse),errline);
611    *(parse = son(parse)) = '\0';
612    if (isdigit(*q))
613        working->hours = atoi(q);
614    else
615        working->hours = -1;
616
617    q = parse = sob(++parse); /* Optional field */
618    *(parse = son(parse)) = '\0';
619    working->flags = 0;
620    working->exec_flags = NULL;
621    working->pid_flags = NULL;
622
623    while (q && *q && !isspace(*q)) {
624        char qq = toupper(*q);
625        if (qq == FL_BINARY)
626            working->flags |= CE_BINARY;
627        else if (qq == FL_DATED)
628            working->flags |= CE_DATED;
629        else if (( opt = get_flag_entry(qq, flags_list) )) {
630            if ( opt->type == EXEC )
631                grow_option_string( &(working->exec_flags), qq );
632            else if ( opt->type == PID || opt->type == PNAME )
633                grow_option_string( &(working->pid_flags), qq );
634            else abort();
635        } else {
636            fprintf(stderr,
637                    "Illegal flag in config file -- %c\n",
638                    *q);
639            exit(1);
640        }
641        q++;
642    }
643
644    free(errline);
645}
646
647/* Parse a program-to-run description from the configuration file and update
648 * the linked list of flag entries
649 */
650void parse_exec_line(char *line, struct flag_entry **first)
651{
652    char *parse, *q;
653    char *errline;
654    char **arg;
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(*(++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    arg = 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 */
729void 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(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(*(++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 */
812void 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 */
873void 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(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(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 */
927int 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) */
937int 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 */
954int 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 */
969void 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 */
1000void 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
1083int 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 */
1092void 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 */
1118void 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 */
1155void 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 */
1275void 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    exit(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.