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

Revision 11221, 35.1 KB checked in by ghudson, 27 years ago (diff)
From mhpower: fix typo in datestamp comment.
RevLine 
[8045]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 $
[11221]6 *      $Author: ghudson $    $Revision: 1.8 $
[8045]7 */
8
9#ifndef lint
[11221]10static char *rcsid = "$Header: /afs/dev.mit.edu/source/repository/athena/etc/newsyslog/newsyslog.c,v 1.8 1998-03-09 04:52:29 ghudson Exp $";
[8050]11#endif  /* lint */
[8045]12
[8319]13#include "config.h"
14
[8080]15#include <sys/types.h>
[8045]16#include <stdio.h>
[8050]17#include <stdlib.h>
18#include <unistd.h>
[8319]19#ifdef HAVE_STRING_H
[8079]20#include <string.h>
[8050]21#else
[8319]22#include <strings.h>  /* only VAXen don't have string.h --bert 15mar96 */
[8050]23#endif
[8045]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>
[8050]31#include <fcntl.h>
[8080]32#include <sys/time.h>
[8050]33#include <time.h>
34#include <dirent.h>
[8045]35
[8079]36#include "signames.h"
37
[8707]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
[8080]49/*** defines ***/
50
51#define CONF "/etc/athena/newsyslog.conf"       /* Configuration file */
52#define SYSLOG_PID "/etc/syslog.pid"            /* Pidfile for syslogd */
[8707]53#define SYSLOG_PNAME "syslogd"                  /* Process name for syslogd */
[8080]54
[8319]55/* COMPRESS is now defined in config.h */
[8080]56
57#ifndef SLEEP_DELAY
[8319]58#define SLEEP_DELAY 1    /* Default delay used after restarting each daemon, */
59#endif                           /* to give it time to clean up (in seconds) */
[8080]60
[8045]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
[8080]67/* special (i.e. non-configurable) flags for logfile entries */
[8206]68#define CE_ACTIVE 1     /* Logfile needs to be turned over */
[8080]69#define CE_BINARY 2     /* Logfile is in binary, don't add status messages */
[8206]70#define CE_DATED  4     /* Mark the logfile with date, not number */
[8050]71
[8079]72#define NONE -1
[8050]73
[8079]74/* Definitions of the keywords for the logfile.
75 * Should not be changed once we come up with good ones and document them. =)
76 */
[8206]77/* This is used to add another post-processing command, e.g. 'gzip -9' */
78#define KEYWORD_EXEC "filter"
[8079]79/* This describes a process to restart, other than syslogd */
[8206]80#define KEYWORD_PID  "signal"
[8707]81/* This works as "signal," only takes a process name rather than a pid file. */
82#define KEYWORD_PNAME "signame"
[8079]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
[8206]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
[8079]94struct log_entry {
[8080]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 */
[8045]106};
107
[8079]108/* The same structure is used for different kinds of flags. */
[8080]109/* This wastes a little run-time space, but makes my life easier. */
[8079]110struct flag_entry {
[8080]111    char  option;               /* Option character used for this program */
[8707]112    enum { EXEC, PID, PNAME } type;     /* Type of flag ('run' vs. 'signal') */
[8079]113
[8080]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] */
[8079]118
[8707]119    /* PID/PNAME flag options */
120    char  *pidident;            /* Path to the PID file, or the process name */
[8080]121    int signal;                 /* Signal to restart the process */
122    int ref_count;              /* Number of times used (if 0, no restart) */
[8079]123
[8080]124    struct flag_entry *next;    /* Linked list pointer */
[8050]125};
126
[8080]127/* extern uid_t getuid(),geteuid(); */
128/* extern time_t time(); */
129
130/* extern's needed for getopt() processing */
[8045]131extern int      optind;
132extern char     *optarg;
133
[8080]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 */
[8045]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 */
[8080]144int     sleeptm = SLEEP_DELAY;  /* Time to wait after restarting a daemon */
145
146time_t  timenow;                /* Time of the start of newsyslog */
[8045]147char    *daytime;               /* timenow in human readable form */
[11221]148char    datestamp[DATE_LENGTH+1]; /* timenow in the YYYYMMDD format */
[8045]149
[8080]150struct flag_entry *flags;       /* List of all config-file flags */
[8045]151
[8050]152/*** support functions ***/
[8045]153
[8050]154/* Skip Over Blanks */
155char *sob (register char *p)
[8045]156{
[8080]157    while (p && *p && isspace(*p))
158        p++;
159    return(p);
[8050]160}
161
162/* Skip Over Non-Blanks */
163char *son (register char *p)
164{
[8080]165    while (p && *p && !isspace(*p))
166        p++;
167    return(p);
[8050]168}
169
170/* Check if string is actually a number */
171int isnumber(char *string)
172{
[8080]173    while (*string != '\0') {
[8206]174        if (! isdigit(*string)) return(0);
[8080]175        string++;
176    }
177    return(1);
[8050]178}
179
[8319]180#ifndef HAVE_STRDUP
[8050]181/* Duplicate a string using malloc */
182char *strdup (register char *strp)
183{
[8080]184    register char *cp;
[8050]185
[8080]186    if ((cp = malloc((unsigned) strlen(strp) + 1)) == NULL)
187        abort();
188    return(strcpy (cp, strp));
[8050]189}
190#endif
191
[8080]192/* Tack a character to the end of a string. (Inefficient...) */
[8079]193char *grow_option_string (char **strp, char new)
194{
[8080]195    size_t len;
[8079]196
[8080]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    }
[8079]206
[8080]207    if (! *strp) abort();
[8079]208
[8080]209    (*strp)[len] = new;
210    (*strp)[len+1] = '\0';
[8079]211
[8080]212    return *strp;
[8079]213}
214
[8080]215/*** finding the appropriate logfile, whatever its extension ***/
216
[8079]217/* Check if the filename extension is composed of valid components */
218int valid_extension(char* ext)
219{
[8080]220    struct flag_entry *cfl = flags;
221    int len;
[8079]222
[8080]223    if (ext && (*ext))
224        while (cfl) {
225            if ((cfl->type == EXEC) && (cfl->extension)) {
226                len = strlen(cfl->extension);
[8079]227
[8080]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                }
[8079]241            }
[8080]242            cfl = cfl->next;
243        }
244    return 0;
[8079]245}
246
[8206]247/* Check if the string is empty, or a dot followed by a valid extension */
248int valid_dot_ext(char* dot)
[8050]249{
[8206]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{
[8080]290    DIR *parent;
291    struct dirent *dent;
[8206]292    char *dirname, *namefrag, *fp, *filename;
293    char *cmpmost = NULL;
294    int fraglen, rootlen, have_path, mismatch, matches;
[8050]295
[8206]296    if (root)
297        rootlen = strlen(root);
298    else
299        rootlen = 0;
300
[8080]301    /* extract the directory path and the filename fragment. */
302    fp = dirname = strdup(name);
303    namefrag = strrchr(dirname, '/');
[8206]304    if (( have_path = (namefrag != NULL) )) {
[8080]305        *(namefrag++) = '\0';
306    } else {
307        namefrag = dirname;
308        dirname = ".";
309    }
310    fraglen = strlen(namefrag);
[8050]311
[8080]312    if (!(parent = opendir(dirname))) {
313        free(fp);
314        return (char*)NULL;
315    }
[8050]316
[8080]317    /* WARNING: on Solaris, -lucb will link with readdir() which
318       returns a *different* struct dirent.  libucb: just say no. */
[8050]319
[8206]320    matches = 0;
[8080]321    /* find matching directory entry */
322    while ((dent=readdir(parent))
[8206]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    }
[8050]337
[8206]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);
[8080]348        if (!glued)
349            abort();
[8050]350
[8080]351        /* reconstruct pathname+filename */
352        if (have_path) {
[8206]353            sprintf(glued, "%s/%s", dirname, filename);
[8080]354        } else
[8206]355            strcpy(glued, filename);
[8050]356
[8080]357        closedir(parent);
[8206]358        if (cmpmost)  free(cmpmost);
[8080]359        free(fp);
360        return glued;
361    } else {
362        closedir(parent);
[8206]363        if (cmpmost)  free(cmpmost);
[8080]364        free(fp);
365        return (char*)NULL;
366    }
[8050]367}
368
[8206]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
[8080]410/*** examining linked lists of flags ***/
[8050]411
[8079]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{
[8080]415#ifdef DBG_FLAG_ENTRY
416    { int debuggers_suck_when_breakpoint_is_a_while_loop = 1; }
[8079]417#endif
418
[8080]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    }
[8079]425
[8080]426    return (struct flag_entry*)NULL;  /* not found */
[8079]427}
428
429/* check if an option letter is already used for a flag */
430int already_used (char option, struct flag_entry *flags)
431{
[8080]432    /* these letters cannot be redefined! */
433    if ((option == FL_BINARY) || (option == FL_DATED))
434        return 1;
[8079]435
[8080]436    /* otherwise check the list */
437    if ( get_flag_entry(option,flags) )
438        return 1;
[8079]439
[8080]440    return 0;   /* not found */
[8079]441}
442
[8080]443/*** command-line parsing ***/
444
445void usage(void)
[8050]446{
[8080]447    fprintf(stderr,
[8206]448            "Usage: %s [-nrv] [-f config-file] [-t restart-time]\n", progname);
[8080]449    exit(1);
[8050]450}
451
[8080]452/* Parse the command-line arguments */
453void PRS(int argc, char **argv)
[8050]454{
[8080]455    int c;
456    char    *end;
[8050]457
[8080]458    progname = argv[0];
459    timenow = time((time_t *) 0);
460    daytime = ctime(&timenow);
461    daytime[strlen(daytime)-1] = '\0';
[8206]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    }
[8050]467
[8080]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);
[8206]493            if (end && *end) /* arg contains non-numeric chars */
[8080]494                usage();
495            break;
496         default:
497            usage();
[8050]498        }
499}
500
[8080]501/*** config file parsing ***/
[8050]502
[8080]503/* Complain if the first argument is NULL, return it otherwise. */
504char *missing_field(char *p, char *errline)
[8050]505{
[8080]506    if (!p || !*p) {
507        fprintf(stderr,"Missing field in config file:\n");
508        fputs(errline,stderr);
509        exit(1);
510    }
511    return(p);
[8050]512}
513
[8080]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)
[8050]519{
[8080]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;
[8079]526
[8080]527    errline = strdup(line);
[8079]528
[8080]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;
[8079]536
[8080]537        working->next = (struct log_entry *)malloc(sizeof(struct log_entry));
538        working = working->next;
539        if (!working) abort();
540    }
[8050]541
[8080]542    working->next = (struct log_entry *)NULL;
[8050]543
[8080]544    q = parse = missing_field(sob(line),errline);
545    *(parse = son(line)) = '\0';
546    working->log = strdup(q);
[8050]547
[8080]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;
[8050]565
[8080]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);
[8050]574                }
[8080]575                working->gid = grp->gr_gid;
576            } else
577                working->gid = atoi(q);
578        } else
579            working->gid = NONE;
[8050]580
[8080]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;
[8050]586
[8080]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    }
[8050]593
[8080]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    }
[8050]602
[8080]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;
[8079]609
[8080]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;
[8079]616
[8080]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);
[8206]625        if (qq == FL_BINARY)
[8080]626            working->flags |= CE_BINARY;
[8206]627        else if (qq == FL_DATED)
628            working->flags |= CE_DATED;
629        else if (( opt = get_flag_entry(qq, flags_list) )) {
[8080]630            if ( opt->type == EXEC )
631                grow_option_string( &(working->exec_flags), qq );
[8707]632            else if ( opt->type == PID || opt->type == PNAME )
[8080]633                grow_option_string( &(working->pid_flags), qq );
634            else abort();
[8079]635        } else {
[8080]636            fprintf(stderr,
637                    "Illegal flag in config file -- %c\n",
638                    *q);
639            exit(1);
640        }
641        q++;
642    }
[8079]643
[8080]644    free(errline);
[8045]645}
646
[8080]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)
[8079]651{
[8080]652    char *parse, *q;
653    char *errline;
654    char **arg;
655    struct flag_entry *working = (*first);
656    int num_args, i;
[8079]657
[8080]658    errline = strdup(line);
[8079]659
[8080]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;
[8079]667
[8080]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;
[8079]674
[8080]675    parse = missing_field(sob(line),errline);
676    parse = missing_field(son(parse),errline);   /* skip over the keyword */
[8079]677
[8080]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);
[8045]698       
[8080]699        q = parse = missing_field(sob(++parse),errline);
700    }
701    else  /* no extension */
702        working->extension = NULL;
[8045]703
[8080]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);
[8050]724}
725
[8080]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)
[8045]730{
[8080]731    char *parse, *q, *end;
732    char        *errline;
733    struct flag_entry *working = (*first);
734    char oops;
[8045]735
[8080]736    errline = strdup(line);
[8045]737
[8080]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;
[8045]745
[8080]746        working->next = (struct flag_entry *)malloc(sizeof(struct flag_entry));
747        working = working->next;
748        if (!working) abort();
749    }
[8045]750
[8080]751    working->next = (struct flag_entry *)NULL;
752
753    parse = missing_field(sob(line),errline);
[8707]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
[8080]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';
[8707]782    working->pidident = strdup(q);
[8080]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");
[8050]796                fputs(errline,stderr);
797                exit(1);
[8080]798            }
[8050]799        }
[8080]800    } else {
801        /* default signal value */
802        working->signal = SIGHUP;
803    }
804
805    working->ref_count = 0;
806
807    free(errline);
[8045]808}
809
[8080]810/* Add support for default flags to the linked list of flag entries.
[8045]811 */
[8080]812void add_default_flags(struct flag_entry **first)
[8045]813{
[8080]814    struct flag_entry *working = (*first);
[8079]815
[8080]816    /* we find out if Z flag is used *before* we change the linked list */
817    int have_Z = already_used(FL_COMPRESS, *first);
[8079]818
[8080]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    }
[8079]831
[8080]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";
[8079]837
[8080]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;
[8079]845
[8080]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    }
[8079]854
[8080]855    /* add unnamed flag for restarting syslogd. */
[8707]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
[8080]863    working->option = 0;
864    working->signal = SIGHUP;
865    working->ref_count = 0;
[8079]866
[8080]867    working->next = (struct flag_entry *)NULL;
[8079]868}
869
[8080]870/* Parse a configuration file and return all relevant data in several
871 * linked lists
[8079]872 */
[8080]873void parse_file(struct log_entry **logfiles, struct flag_entry **flags)
[8079]874{
[8080]875    FILE        *f;
876    char        line[BUFSIZ];
877    int add_flags = 1;
[8079]878
[8080]879    (*logfiles) = (struct log_entry *) NULL;
880    (*flags) = (struct flag_entry *) NULL;
[8079]881
[8080]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        }
[8707]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
[8080]907            parse_pid_line(line, flags);
908            continue;
909        }
[8079]910
[8080]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        }
[8079]918
[8080]919        parse_logfile_line (line, logfiles, *flags);
920    }
921    (void) fclose(f);
922}
[8079]923
[8080]924/*** support functions for turning logfiles over ***/
[8079]925
[8080]926/* Return size in kilobytes of a file */
927int sizefile (char *file)
928{
929    struct stat sb;
[8079]930
[8080]931    if (stat(file,&sb) < 0)
932        return(-1);
933    return(kbytes(dbtob(sb.st_blocks)));
934}
[8079]935
[8080]936/* Return the age of old log file (file.0) */
937int age_old_log (char *file)
938{
939    struct stat sb;
[8206]940    char* last = newest_log(file);
[8079]941
[8206]942    if (!last || (stat(last, &sb) < 0))
943        return(-1);
944    free(last);
945
[8319]946#ifdef MINUTES    /* this is for debugging: times in minutes instead of hrs */
[8080]947    return( (int) (timenow - sb.st_mtime + 30) / 60);
948#else
949    return( (int) (timenow - sb.st_mtime + 1800) / 3600);
950#endif
[8079]951}
952
[8080]953/* Log the fact that the logs were turned over */
954int log_trim(char *log)
[8079]955{
[8080]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}
[8079]967
[8080]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]);
[8206]977        printf(" %s\n", log);
[8080]978    } else {
979        pid = fork();
980        if (pid < 0) {
981            fprintf(stderr,"%s: ",progname);
982            perror("fork");
983            exit(1);
984        } else if (!pid) {
[8206]985            flag->args[flag->nargs] = log;
[8319]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
[8080]990            (void) execv(flag->args[0], flag->args);
[8319]991#endif
[8080]992            fprintf(stderr,"%s: ",progname);
993            perror(flag->args[0]);
994            exit(1);
995        }
996    }
997}
[8079]998
[8080]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;
[8079]1006
[8080]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);
[8079]1012
[8080]1013    if (pid) {
1014        if (noaction) {
[8206]1015            if (( signame = signal_name(signum) ))
[8080]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}
[8079]1033
[8707]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
[8080]1113/*** various fun+games with logfile entries ***/
[8079]1114
[8080]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;
[8079]1121
[8080]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    }
[8079]1150}
1151
[8080]1152/* Turn over the logfiles and create a new one, in preparation for a
1153 * restart of the logging process(es)
[8079]1154 */
[8080]1155void do_trim(struct log_entry *ent)
[8079]1156{
[8080]1157    char    file1[MAXPATHLEN], file2[MAXPATHLEN];
1158    char    *zfile1, zfile2[MAXPATHLEN];
[8206]1159    char    *freep = NULL;
[8080]1160    int fd;
1161    struct stat st;
1162    int numdays = ent->numlogs;
1163    int owner_uid = ent->uid;
1164    struct flag_entry *flg;
[8079]1165
[8080]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
[8079]1173
[8080]1174    /* Remove oldest log */
[8206]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 {
[8080]1181            (void) unlink(zfile1);
[8206]1182            if (freep) { free(freep);  freep = NULL; }
1183            zfile1 = freep = oldest_log(ent->log, numdays);
1184        }
[8080]1185    }
[8079]1186
[8206]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
[8080]1191    /* Move down log files */
[8206]1192    (void) sprintf(file1,"%s.%d", ent->log, numdays);
[8080]1193    while (numdays--) {
1194        (void) strcpy(file2,file1);
1195        (void) sprintf(file1,"%s.%d",ent->log,numdays);
[8079]1196
[8080]1197        if (!stat(file1, &st))
1198            zfile1 = file1;
1199        else {
1200            if (freep) { free(freep);  freep = NULL; }
[8206]1201            zfile1 = freep = matching_file(file1);
[8080]1202            if (!zfile1) continue;
1203        }
[8079]1204
[8080]1205        /* the extension starts at (zfile1 + strlen(file1)) */
1206        (void) strcpy(zfile2, file2);
1207        (void) strcat(zfile2, (zfile1 + strlen(file1)));
[8079]1208
[8080]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; }
[8079]1221
[8206]1222    if (ent->flags & CE_DATED) {    /* if logs are dated, generate new name  */
1223        (void) sprintf(file1, "%s.%s", ent->log, datestamp);
1224    }
1225
[8080]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);
[8045]1253
[8080]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);
[8707]1259            if (!flg || ((flg->type != PID) && (flg->type != PNAME))) abort();
[8079]1260
[8080]1261            flg->ref_count++;
[8045]1262        }
[8080]1263    } else {
1264        /* do the default restart. ('\0' is special.) */
1265        flg = get_flag_entry('\0', flags);
[8707]1266        if (!flg || ((flg->type != PID) && flg->type != PNAME)) abort();
[8045]1267
[8080]1268        flg->ref_count++;
1269    }
1270}
[8045]1271
[8080]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];
[8045]1280
[8206]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
[8080]1288    if (!noaction && !(ent->flags & CE_BINARY)) {
1289        (void) log_trim(old);   /* add a date stamp to old log */
1290    }
[8045]1291
[8080]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
[8206]1297            compress_log(old, flg);
1298
1299            if (flg->extension) {
1300                strcat(old, ".");
1301                strcat(old, flg->extension);
1302            }
[8045]1303        }
[8080]1304    }
[8045]1305}
1306
[8080]1307/*** main ***/
1308
[8050]1309int main(int argc, char **argv)
[8045]1310{
[8080]1311    struct log_entry *p, *q;
1312    struct flag_entry *flg;
[8079]1313
[8080]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);
[8045]1328        }
[8079]1329
[8080]1330    for (flg = flags; flg; flg = flg->next)
1331        if ((flg->type == PID) && flg->ref_count) {
[8707]1332            if (verbose>1) printf("Restarting %s\n", flg->pidident);
1333            restart_proc(flg->pidident, flg->signal);
[8080]1334        }
[8079]1335
[8707]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
[8080]1344    for (p = q; p; p = p->next) {
[8319]1345        if (p->flags & CE_ACTIVE) {
1346            if (verbose>1) printf("Processing %s\n", p->log);
1347            do_compress(p);
1348        }
[8080]1349    }
[8079]1350
[8080]1351    exit(0);
1352}
[8079]1353
[8080]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.