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..6827432 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,12 @@ 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); +static void notifier_free_all(void); void reload(int); static int validate(struct sockaddr *sa, const char *hname); static int waitdaemon(int); @@ -1604,6 +1612,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); } @@ -2455,6 +2466,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 +2550,7 @@ static void init(void) } } - if (cfparse(fp, &newf)) { + if (cfparse(fp, &newf, &newn)) { fclose(fp); return; } @@ -2548,11 +2560,27 @@ static void init(void) * Close all open log files. */ close_open_log_files(); + fhead = newf; + /* + * Free all notifiers + */ + notifier_free_all(); + + 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 +2951,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 +3016,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 +3370,70 @@ 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; + + np = calloc(1, sizeof(*np)); + if (!np) { + ERR("Cannot allocate memory for a notify program"); + return; + } + np->n_program = strdup(program); + if (!np->n_program) { + free (np); + ERR("Cannot allocate memory for a notify program"); + return; + } + SIMPLEQ_INSERT_TAIL(newn, np, n_link); + } else + logit("notify: non-existing, or not executable program\n"); +} + +static void notifier_invoke(const char *logfile) +{ + char *argv[3]; + int childpid; + struct notifier *np; + + logit("notify: rotated %s, invoking hooks\n", logfile); + + SIMPLEQ_FOREACH(np, ¬head, n_link) { + childpid = fork(); + + switch (childpid) { + case -1: + ERR("Cannot start notifier %s", np->n_program); + break; + case 0: + argv[0] = np->n_program; + argv[1] = (char*)logfile; + argv[2] = NULL; + execv(argv[0], argv); + _exit(1); + default: + logit("notify: forked child pid %d for %s\n", + childpid, np->n_program); + break; + } + } +} + +static void notifier_free_all(void) +{ + struct notifier *np, *npnext; + + SIMPLEQ_FOREACH_SAFE(np, ¬head, n_link, npnext) { + free(np->n_program); + free(np); + } +} + /* * 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_ */ diff --git a/test/Makefile.am b/test/Makefile.am index f966b2b..5282297 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -1,5 +1,5 @@ EXTRA_DIST = lib.sh opts.sh -EXTRA_DIST += api.sh local.sh unicode.sh remote.sh fwd.sh mark.sh +EXTRA_DIST += api.sh local.sh unicode.sh remote.sh fwd.sh mark.sh notify.sh CLEANFILES = *~ *.trs *.log TEST_EXTENSIONS = .sh TESTS_ENVIRONMENT= unshare -mrun @@ -17,5 +17,6 @@ TESTS += remote.sh TESTS += api.sh TESTS += fwd.sh TESTS += mark.sh +TESTS += notify.sh programs: $(check_PROGRAMS) diff --git a/test/notify.sh b/test/notify.sh new file mode 100755 index 0000000..5c6b554 --- /dev/null +++ b/test/notify.sh @@ -0,0 +1,41 @@ +#!/bin/sh +set -x + +if [ x"${srcdir}" = x ]; then + srcdir=. +fi +. ${srcdir}/lib.sh + +[ -x ../src/logger ] || SKIP 'logger missing' + +NOT1=${DIR}/${NM}-1.sh +NOT1STAMP=${DIR}/${NM}-1.stamp +NOT2=${DIR}/${NM}-2.sh +NOT2STAMP=${DIR}/${NM}-2.stamp + +printf '#!/bin/sh -\necho script 1: $* > '${NOT1STAMP}'\n' > ${NOT1} +printf '#!/bin/sh -\necho script 2: $* > '${NOT2STAMP}'\n' > ${NOT2} +chmod 0755 ${NOT1} ${NOT2} + +cat < ${CONFD}/notifier.conf +notify ${NOT1} +# Match all log messages, store in RC5424 format and rotate every 1 KiB +*.* -${LOG} ;rotate=1k:2,RFC5424 +notify ${NOT2} +EOF + +setup + +MSG=01234567890123456789012345678901234567890123456789 +MSG=$MSG$MSG$MSG$MSG$MSG$MSG$MSG$MSG$MSG$MSG +../src/logger -u ${SOCK} ${MSG} +../src/logger -u ${SOCK} 1${MSG} +../src/logger -u ${SOCK} 2${MSG} + +if [ -f ${LOG}.0 ] && + grep 'script 1' ${NOT1STAMP} && + grep 'script 2' ${NOT2STAMP}; then + OK +else + FAIL 'Notifier did not run.' +fi