Index: newsyslog.8 =================================================================== RCS file: /home/ncvs/src/usr.sbin/newsyslog/newsyslog.8,v retrieving revision 1.32 diff -u -r1.32 newsyslog.8 --- newsyslog.8 2001/07/30 15:17:17 1.32 +++ newsyslog.8 2001/10/11 16:40:16 @@ -76,7 +76,7 @@ Each line of the file contains information about a particular log file that should be handled by .Nm . -Each line has five mandatory fields and four optional fields, with +Each line has five mandatory fields and five optional fields, with whitespace separating each field. Blank lines or lines beginning with ``#'' are ignored. The fields of the configuration file are as follows: @@ -294,12 +294,28 @@ .Ar signal_number is sent the process id contained in this file. This field must start with "/" in order to be recognized -properly. +properly. Same as +.Ar flags +field, you can use hyphen (-) for null field. .It Ar signal_number This optional field specifies -the signal number will be sent to the daemon process. +the signal number or signal name will be sent to the daemon process. By default -a SIGHUP will be sent. +a SIGHUP will be sent. Same as +.Ar flags +field, you can use hyphen (-) for null field. +.It Ar command +This optional field specifies +the command to be postprocessing of log file. A command must begin +with "/" character (absolute pathname), but can enclose the entire +with a double quotation mark. Double quotation mark and backslash in +a command must escape with backslash (\\), and "%l" and "%o" is +replaced with new/old log file name, and command is executed by +.Pa /bin/sh . +A command must be execute permission by specified +.Ar owner +and +.Ar group . .El .Sh OPTIONS The following options can be used with Index: newsyslog.c =================================================================== RCS file: /home/ncvs/src/usr.sbin/newsyslog/newsyslog.c,v retrieving revision 1.37 diff -u -r1.37 newsyslog.c --- newsyslog.c 2001/07/31 16:25:55 1.37 +++ newsyslog.c 2001/10/13 09:10:04 @@ -38,6 +38,7 @@ #include #include +#include #include #include #include @@ -73,6 +74,7 @@ struct conf_entry { char *log; /* Name of the log */ char *pid_file; /* PID file */ + char *prog; /* Command for postprocessing */ int uid; /* Owner of log */ int gid; /* Group of log */ int numlogs; /* Number of logs to keep */ @@ -99,18 +101,24 @@ char hostname[MAXHOSTNAMELEN]; /* hostname */ char *daytime; /* timenow in human readable form */ +/* Flag of get_field() function */ +#define F_REQUIRED (1 << 1) /* A required field */ +#define F_DQUOTE (1 << 2) /* Field is enclosed with double quote */ + static struct conf_entry *parse_file(char **files); static char *sob(char *p); static char *son(char *p); -static char *missing_field(char *p, char *errline); +static char *get_field(char *str, int flag, char **last, char *errline); static void do_entry(struct conf_entry * ent); static void PRS(int argc, char **argv); static void usage(void); -static void dotrim(char *log, const char *pid_file, int numdays, int falgs, - int perm, int owner_uid, int group_gid, int sig); +static void dotrim(char *log, const char *pid_file, const char *prog, + int numdays, int falgs, int perm, int owner_uid, + int group_gid, int sig); static int log_trim(char *log); static void compress_log(char *log); static void bzcompress_log(char *log); +static int post_prog(const char *prog, char *log, int owner_uid, int group_gid); static int sizefile(char *file); static int age_old_log(char *file); static pid_t get_pid(const char *pid_file); @@ -119,6 +127,8 @@ int group_gid); static void createdir(char *dirpart); static time_t parseDWM(char *s); +static int signame_to_signum(char *sig); +static void child_handler(int signum); int main(int argc, char **argv) @@ -128,6 +138,7 @@ PRS(argc, argv); if (needroot && getuid() && geteuid()) errx(1, "must have root privs"); + signal(SIGCHLD, child_handler); p = q = parse_file(argv + optind); while (p) { @@ -200,7 +211,7 @@ else pid_file = NULL; } - dotrim(ent->log, pid_file, ent->numlogs, + dotrim(ent->log, pid_file, ent->prog, ent->numlogs, ent->flags, ent->permissions, ent->uid, ent->gid, ent->sig); } else { @@ -276,7 +287,6 @@ struct conf_entry *first, *working; struct passwd *pass; struct group *grp; - int eol; first = working = NULL; @@ -291,12 +301,8 @@ continue; errline = strdup(line); - q = parse = missing_field(sob(line), errline); - parse = son(line); - if (!*parse) - errx(1, "malformed line (missing fields):\n%s", - errline); - *parse = '\0'; + /* Required field: pathname of log file */ + q = get_field(line, F_REQUIRED, &parse, errline); if (*files) { for (p = files; *p; ++p) @@ -320,12 +326,13 @@ if ((working->log = strdup(q)) == NULL) err(1, "strdup"); - q = parse = missing_field(sob(++parse), errline); - parse = son(parse); - if (!*parse) - errx(1, "malformed line (missing fields):\n%s", - errline); - *parse = '\0'; + /* Field of owner and group id or file permissions */ + q = get_field(parse, F_REQUIRED, &parse, errline); + + /* + * Find delimiter of owner and group. If delimiter is + * not found, it will be file permissions. + */ if ((group = strchr(q, ':')) != NULL || (group = strrchr(q, '.')) != NULL) { *group++ = '\0'; @@ -354,12 +361,8 @@ } else working->gid = NONE; - q = parse = missing_field(sob(++parse), errline); - parse = son(parse); - if (!*parse) - errx(1, "malformed line (missing fields):\n%s", - errline); - *parse = '\0'; + /* Field of file permissions */ + q = get_field(parse, F_REQUIRED, &parse, errline); } else working->uid = working->gid = NONE; @@ -367,32 +370,22 @@ errx(1, "error in config file; bad permissions:\n%s", errline); - q = parse = missing_field(sob(++parse), errline); - parse = son(parse); - if (!*parse) - errx(1, "malformed line (missing fields):\n%s", - errline); - *parse = '\0'; + /* Required field: number to preserve log */ + q = get_field(parse, F_REQUIRED, &parse, errline); if (!sscanf(q, "%d", &working->numlogs)) errx(1, "error in config file; bad number:\n%s", errline); - q = parse = missing_field(sob(++parse), errline); - parse = son(parse); - if (!*parse) - errx(1, "malformed line (missing fields):\n%s", - errline); - *parse = '\0'; + /* Required field: size specification of log */ + q = get_field(parse, F_REQUIRED, &parse, errline); if (isdigit(*q)) working->size = atoi(q); else working->size = -1; + /* Required field: time specification to do trimming of log */ + q = get_field(parse, F_REQUIRED, &parse, errline); working->flags = 0; - q = parse = missing_field(sob(++parse), errline); - parse = son(parse); - eol = !*parse; - *parse = '\0'; { char *ep; u_long ul; @@ -422,17 +415,9 @@ working->flags |= CE_TRIMAT; } } - - if (eol) - q = NULL; - else { - q = parse = sob(++parse); /* Optional field */ - parse = son(parse); - if (!*parse) - eol = 1; - *parse = '\0'; - } + /* Optional field: flags for special processing */ + q = get_field(parse, 0, &parse, errline); while (q && *q && !isspace(*q)) { if ((*q == 'Z') || (*q == 'z')) working->flags |= CE_COMPACT; @@ -446,48 +431,45 @@ q++; } - if (eol) - q = NULL; - else { - q = parse = sob(++parse); /* Optional field */ - parse = son(parse); - if (!*parse) - eol = 1; - *parse = '\0'; - } - + /* Optional field: pathname of process id file */ + q = get_field(parse, 0, &parse, errline); working->pid_file = NULL; if (q && *q) { if (*q == '/') working->pid_file = strdup(q); - else if (isdigit(*q)) + else if (isalnum(*q)) goto got_sig; - else - errx(1, - "illegal pid file or signal number in config file:\n%s", + else if (*q != '-') + errx(1, "illegal pid file in config file:\n%s", errline); } - if (eol) - q = NULL; - else { - q = parse = sob(++parse); /* Optional field */ - *(parse = son(parse)) = '\0'; - } + /* Optional field: signal number or name */ + q = get_field(parse, 0, &parse, errline); + got_sig: working->sig = SIGHUP; if (q && *q) { - if (isdigit(*q)) { - got_sig: + if (isdigit(*q)) working->sig = atoi(q); - } else { - err_sig: + else if (*q != '-') + working->sig = signame_to_signum(q); + if (working->sig < 1 || working->sig >= NSIG) errx(1, "illegal signal number in config file:\n%s", errline); - } - if (working->sig < 1 || working->sig >= NSIG) - goto err_sig; } + + /* Optional field: command for postprocessing */ + q = get_field(parse, F_DQUOTE, NULL, errline); + working->prog = NULL; + if (q && *q) { + if (*q == '/') + working->prog = strdup(q); + else if (*q != '-') + errx(1, + "illegal command in config file:\n%s", + errline); + } free(errline); } if (working) @@ -496,24 +478,82 @@ return (first); } +/* This function takes out the next field from line buffer */ static char * -missing_field(char *p, char *errline) +get_field(char *str, int flag, char **last, char *errline) { + char * field; + + if (str == NULL) + return NULL; + + field = str = sob(str); - if (!p || !*p) + /* If a field is required and null string, it is failure */ + if ((flag & F_REQUIRED) && (*str == '\0')) errx(1, "missing field in config file:\n%s", errline); - return (p); + + /* The field that was enclosed with a double quotation mark */ + if ((flag & F_DQUOTE) && (*str == '"')) { + char * p = str++; + + /* + * Escape processing of special character to the last + * double quotation mark. + */ + while (*str != '"') { + switch (*str) { + case '\\': /* escape of special character */ + switch (*++str) { + case '\0': + errx(1, "unterminated field:\n%s", + errline); + break; + case '"': /* double quotation mark */ + case '\\': /* backslash */ + break; + default: + --str; /* back to escape mark */ + break; + } + break; + case '\0': + errx(1, "unterminated field:\n%s", errline); + break; + } + *p++ = *str++; + } + *p = '\0'; + + /* + * If the next character of a double quotation mark is + * not space character, it is syntax error. + */ + if (*++str && !isspace(*str)) + errx(1, "malformed line:\n%s", errline); + } else { + str = son(str); + if (*str) /* not EOL */ + *str++ = '\0'; + else /* end of line */ + str = NULL; + } + + /* Preserve the last pointer of character string */ + if (last) + *last = str; + return field; } static void -dotrim(char *log, const char *pid_file, int numdays, int flags, int perm, - int owner_uid, int group_gid, int sig) +dotrim(char *log, const char *pid_file, const char *prog, int numdays, + int flags, int perm, int owner_uid, int group_gid, int sig) { char dirpart[MAXPATHLEN], namepart[MAXPATHLEN]; char file1[MAXPATHLEN], file2[MAXPATHLEN]; char zfile1[MAXPATHLEN], zfile2[MAXPATHLEN]; char jfile1[MAXPATHLEN]; - int notified, need_notification, fd, _numdays; + int notified, need_notification, pstat, fd, _numdays; struct stat st; pid_t pid; @@ -660,7 +700,7 @@ (void) chmod(log, perm); pid = 0; - need_notification = notified = 0; + need_notification = notified = pstat = 0; if (pid_file != NULL) { need_notification = 1; pid = get_pid(pid_file); @@ -677,11 +717,23 @@ printf("daemon pid %d notified\n", (int) pid); } } + if (prog != NULL) { + if (need_notification && !notified) + warnx("don't run %s because signal is not notified", + prog); + else if (noaction) + printf("Exec %s\n", prog); + else + pstat = post_prog(prog, log, owner_uid, group_gid); + } if ((flags & CE_COMPACT) || (flags & CE_BZCOMPACT)) { if (need_notification && !notified) warnx( "log %s not compressed because daemon not notified", log); + else if (pstat != 0) + warnx("log %s is not compressed because %s failed", + log, prog); else if (noaction) printf("Compress %s.0\n", log); else { @@ -756,6 +808,88 @@ } } +/* Command run for postprocessing of log file */ +static int +post_prog(const char *prog, char *log, int owner_uid, int group_gid) +{ + sigset_t omask, nmask; + pid_t pid; + int pstat; + + /* Block SIGCHLD */ + sigemptyset(&nmask); + sigaddset(&nmask, SIGCHLD); + sigprocmask(SIG_BLOCK, &nmask, &omask); + + pid = vfork(); + if (pid < 0) /* Failure */ + err(1, "can't vfork"); + else if (pid == 0) { /* Child process */ + char command[ARG_MAX]; /* command buffer to execl() */ + const char * p = prog; /* source */ + char * q = command; /* destination */ + size_t n = sizeof(command) - 1; /* remainder */ + size_t len = strlen(log); + + /* Substitute processing by "%" character in a command */ + while (*p && n > 0) { + if (*p == '%') { + switch (*++p) { + case 'l': /* "%l": new log file */ + (void)strncpy(q, log, n); + n -= len; + q += len; + p++; + continue; + case 'o': /* "%o": old log file */ + (void)snprintf(q, n, "%s.0", log); + n -= len + 2; + q += len + 2; + p++; + continue; + } + } + *q++ = *p++; + n--; + } + *q = '\0'; + + /* XXXXX -- Change a gid/uid of process */ + if (group_gid != NONE) + setgid(group_gid); + if (owner_uid != NONE) + setuid(owner_uid); + setsid(); + + /* Reset to default handler */ + signal(SIGCHLD, SIG_DFL); + sigprocmask(SIG_SETMASK, &omask, NULL); + + /* Command running by /bin/sh */ + (void)execl(_PATH_BSHELL, "sh", "-c", command, NULL); + warn("can't exec %s", command); + _exit(127); + /* NOTREACHED */ + } + + /* Parent process */ + while (waitpid(pid, &pstat, 0) == -1 && errno == EINTR) + ; /* nothing */ + if (WIFEXITED(pstat)) { + if (WEXITSTATUS(pstat)) + warnx("%s (pid %d) returned exit status %d", + prog, pid, WEXITSTATUS(pstat)); + else if (verbose) + printf("%s (pid %d) was completed\n", prog, pid); + } else if (WIFSIGNALED(pstat)) { + warnx("%s (pid %d) caught signal %d%s", + prog, pid, WTERMSIG(pstat), + WCOREDUMP(pstat) ? ", core dumped" : ""); + } + sigprocmask(SIG_SETMASK, &omask, NULL); + return pstat; +} + /* Return size in kilobytes of a file */ static int sizefile(char *file) @@ -1099,4 +1233,43 @@ s = t; } return mktime(&tm); +} + +/* Convert a signal name into signal number */ +static int +signame_to_signum(char *sig) +{ + int n; + + if (!strncasecmp(sig, "sig", (size_t)3)) + sig += 3; + for (n = 1; n < NSIG; n++) { + if (!strcasecmp(sys_signame[n], sig)) + return (n); + } + return (-1); +} + +/* SIGCHLD signal handler for debug */ +static void +child_handler(int signum) +{ + pid_t pid; + int pstat; + + if (verbose) + printf("sig%s handler was called\n", sys_signame[signum]); + do { + pid = waitpid(-1, &pstat, WNOHANG); + } while (pid == -1 && errno == EINTR); + + if (pid > 0 && verbose) { + if (WIFEXITED(pstat)) + printf("pid %d returned exit status %d\n", + pid, WEXITSTATUS(pstat)); + else if (WIFSIGNALED(pstat)) + printf("pid %d caught signal %d%s\n", + pid, WTERMSIG(pstat), + WCOREDUMP(pstat) ? ", core dumped" : ""); + } }