diff --git a/man/syslog.conf.5 b/man/syslog.conf.5 index 41fb86e..b232fb5 100644 --- a/man/syslog.conf.5 +++ b/man/syslog.conf.5 @@ -105,6 +105,17 @@ cron or a separate log rotate daemon. Comments, lines starting with a hash mark ('#'), and empty lines are ignored. If an error occurs during parsing the whole line is ignored. .Pp +The special keyword +.Em notify +specifies the path to an executable program which will get called +whenever a log file has been rotated, with the name of the file, less +its rotation suffix +.Ql .0 , +as an argument. +For example: +.Ql notify /sbin/on-log-rotate.sh . +Any number of notifiers may be installed. +.Pp A special .Em include keyword can be used to include all files with names ending in '.conf' diff --git a/src/syslogd.c b/src/syslogd.c index cb6ad16..9318a29 100644 --- a/src/syslogd.c +++ b/src/syslogd.c @@ -147,6 +147,11 @@ static int KeepKernTime; /* Keep kernel timestamp, evern after initial read static off_t RotateSz = 0; /* Max file size (bytes) before rotating, disabled by default */ static int RotateCnt = 5; /* Max number (count) of log files to keep, set with -c */ +/* + * List of notifiers + */ +static SIMPLEQ_HEAD(notifiers, notifier) nothead = SIMPLEQ_HEAD_INITIALIZER(nothead); + /* * List of peers and sockets for binding. */ @@ -180,9 +185,11 @@ static void signal_init(void); static void boot_time_init(void); static void init(void); static int strtobytes(char *arg); -static int cfparse(FILE *fp, struct files *newf); +static int cfparse(FILE *fp, struct files *newf, struct notifiers *newn); int decode(char *name, struct _code *codetab); static void logit(char *, ...); +static void notifier_add(struct notifiers *newn, const char *program); +static void notifier_invoke(const char *logfile); void reload(int); static int validate(struct sockaddr *sa, const char *hname); static int waitdaemon(int); @@ -1604,6 +1611,9 @@ void logrotate(struct filed *f) ERR("Failed re-opening log file %s after rotation", f->f_un.f_fname); return; } + + if (!SIMPLEQ_EMPTY(¬head)) + notifier_invoke(f->f_un.f_fname); } ftruncate(f->f_file, 0); } @@ -2250,6 +2260,7 @@ void debug_switch(int signo) */ static void close_open_log_files(void) { + struct notifier *np = NULL, *npnext = NULL; struct filed *f = NULL, *next = NULL; SIMPLEQ_FOREACH_SAFE(f, &fhead, f_link, next) { @@ -2276,6 +2287,9 @@ static void close_open_log_files(void) free(f); } + + SIMPLEQ_FOREACH_SAFE(np, ¬head, n_link, npnext) + free(np); } void die(int signo) @@ -2455,6 +2469,7 @@ static void boot_time_init(void) static void init(void) { static int once = 1; + struct notifiers newn = SIMPLEQ_HEAD_INITIALIZER(newn); struct filed *f; struct files newf = SIMPLEQ_HEAD_INITIALIZER(newf); FILE *fp; @@ -2538,7 +2553,7 @@ static void init(void) } } - if (cfparse(fp, &newf)) { + if (cfparse(fp, &newf, &newn)) { fclose(fp); return; } @@ -2548,11 +2563,21 @@ static void init(void) * Close all open log files. */ close_open_log_files(); + fhead = newf; + nothead = newn; Initialized = 1; if (Debug) { + if (!SIMPLEQ_EMPTY(¬head)) { + struct notifier *np; + + SIMPLEQ_FOREACH(np, ¬head, n_link) + printf("notify %s\n", np->n_program); + printf("\n"); + } + SIMPLEQ_FOREACH(f, &fhead, f_link) { if (f->f_type == F_UNUSED) continue; @@ -2923,7 +2948,7 @@ static struct filed *cfline(char *line) /* * Parse .conf file and append to list */ -static int cfparse(FILE *fp, struct files *newf) +static int cfparse(FILE *fp, struct files *newf, struct notifiers *newn) { struct filed *f; char cbuf[BUFSIZ]; @@ -2988,13 +3013,18 @@ static int cfparse(FILE *fp, struct files *newf) } logit("Parsing %s ...", gl.gl_pathv[i]); - cfparse(fpi, newf); + cfparse(fpi, newf, newn); fclose(fpi); } globfree(&gl); continue; } + if (!strncmp(cbuf, "notify", 6)) { + notifier_add(newn, &cbuf[6]); + continue; + } + f = cfline(cbuf); if (!f) continue; @@ -3337,6 +3367,58 @@ static void logit(char *fmt, ...) fflush(stdout); } +static void notifier_add(struct notifiers *newn, const char *program) +{ + while (*program && isspace(*program)) + program++; + + /* Check whether it is accessible, regardless of TOCTOU */ + if (!access(program, X_OK)) { + struct notifier *np; + size_t len; + + len = strlen(program); + + np = calloc(1, sizeof(*np) + len +1); + if (np) { + /* xxx Actually wastes space -- vararray? */ + np->n_program = (char*)&np[1]; + memcpy(np->n_program, program, len); + SIMPLEQ_INSERT_TAIL(newn, np, n_link); + } else + ERR("Cannot allocate memory for a notify program"); + } else + logit("notify: non-existing, or not executable program\n"); +} + +static void notifier_invoke(const char *logfile) +{ + char *argv[3]; + struct notifier *np; + + logit("notify: rotated %s, invoking hooks\n", logfile); + + SIMPLEQ_FOREACH(np, ¬head, n_link) { + switch (fork()) { + case -1: + ERR("Cannot start notifier %s", np->n_program); + break; + case 0: + /* No specific I/O setup, just use what we had */ + argv[0] = np->n_program; + argv[1] = (char*)logfile; /* logical unconst */ + argv[2] = NULL; + execv(argv[0], argv); + _exit(1); + default: + /* Do not care beside that, no special process group + * etc.; it will eventually be reaped via reapchild() */ + logit("notify: forked for %s\n", np->n_program); + break; + } + } +} + /* * The following function is resposible for handling a SIGHUP signal. Since * we are now doing mallocs/free as part of init we had better not being diff --git a/src/syslogd.h b/src/syslogd.h index e9247d8..934afd1 100644 --- a/src/syslogd.h +++ b/src/syslogd.h @@ -305,6 +305,14 @@ struct filed { int f_rotatesz; }; +/* + * Log rotation notifiers + */ +struct notifier { + SIMPLEQ_ENTRY(notifier) n_link; + char *n_program; +}; + void flog(int pri, char *fmt, ...); #endif /* SYSKLOGD_SYSLOGD_H_ */