From e55de4b4cfa3d1e4f460f1785929a1d9071855fd Mon Sep 17 00:00:00 2001 From: ad Date: Fri, 7 Jul 2000 10:52:41 +0000 Subject: [PATCH] Overhaul newsyslog(8). Changes: - Code cleanup. - Better flowing English in manpage (in places). - `-n' option has been removed (was mainly a debugging convenience, I think). - Add `C' flag: create log file if one doesn't exist. --- usr.bin/newsyslog/Makefile | 10 +- usr.bin/newsyslog/newsyslog.8 | 58 +- usr.bin/newsyslog/newsyslog.c | 1118 ++++++++++++++++----------------- usr.bin/newsyslog/pathnames.h | 32 + 4 files changed, 605 insertions(+), 613 deletions(-) create mode 100644 usr.bin/newsyslog/pathnames.h diff --git a/usr.bin/newsyslog/Makefile b/usr.bin/newsyslog/Makefile index a917ab705642..c11e39e3123b 100644 --- a/usr.bin/newsyslog/Makefile +++ b/usr.bin/newsyslog/Makefile @@ -1,11 +1,9 @@ -# $NetBSD: Makefile,v 1.12 1999/12/07 11:28:13 ad Exp $ +# $NetBSD: Makefile,v 1.13 2000/07/07 10:52:41 ad Exp $ PROG= newsyslog - -CPPFLAGS+= -DCONF=\"/etc/newsyslog.conf\" -CPPFLAGS+= -DPIDFILE=\"/var/run/syslogd.pid\" -CPPFLAGS+= -DCOMPRESS=\"/usr/bin/gzip\" -CPPFLAGS+= -DCOMPRESS_POSTFIX=\".gz\" +SRCS= newsyslog.c +LDADD+= -lutil +DPADD+= ${LIBUTIL} MAN= newsyslog.8 MLINKS+=newsyslog.8 newsyslog.conf.5 diff --git a/usr.bin/newsyslog/newsyslog.8 b/usr.bin/newsyslog/newsyslog.8 index 38755982486c..76b5bbd85f4c 100644 --- a/usr.bin/newsyslog/newsyslog.8 +++ b/usr.bin/newsyslog/newsyslog.8 @@ -1,9 +1,29 @@ -.\" $NetBSD: newsyslog.8,v 1.12 1999/12/29 06:54:01 cgd Exp $ +.\" $NetBSD: newsyslog.8,v 1.13 2000/07/07 10:52:41 ad Exp $ +.\" +.\" Copyright (c) 1999, 2000 Andrew Doran +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. The name of the author may not be used to endorse or promote products +.\" derived from this software without specific prior written permission +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +.\" IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +.\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" .\" This file contains changes from the Open Software Foundation. .\" -.\" from FreeBSD: newsyslog.8,v 1.14.2.1 1999/02/25 18:38:33 wollman Exp -.\" .\" Copyright 1988, 1989 by the Massachusetts Institute of Technology .\" .\" Permission to use, copy, modify, and distribute this software @@ -18,6 +38,8 @@ .\" the suitability of this software for any purpose. It is .\" provided "as is" without express or implied warranty. .\" +.\" from FreeBSD: newsyslog.8,v 1.14.2.1 1999/02/25 18:38:33 wollman Exp +.\" .Dd November 20, 1999 .Dt NEWSYSLOG 8 .Os @@ -26,7 +48,7 @@ .Nd maintain system log files to manageable sizes .Sh SYNOPSIS .Nm newsyslog -.Op Fl Fnrv +.Op Fl Frv .Op Fl f Ar config_file .Sh DESCRIPTION .Nm Newsyslog @@ -73,7 +95,7 @@ By default, this configuration file is Each line of the file contains information about a particular log file that should be handled by .Nm newsyslog . -Each line has five mandatory fields and four optional fields, with a +Each line has six mandatory fields and three optional fields, with whitespace separating each field. Blank lines or lines beginning with ``#'' are ignored. The fields of the configuration file are as follows: @@ -111,14 +133,13 @@ when determining when to trim the log file. The .Ar interval field specifies the time separation (in hours) between trimming of the -logfile. If this field +log file. If this field is replaced by an asterisk .Pq Ql \&* , then the interval is not taken into account when determining when to trim the log file. .It Ar flags -This optional field specifies if any special processing is required. -The +This field specifies any special processing that is required. The .Ar Z flag means that archived log files should be compressed with .Xr gzip 1 @@ -129,14 +150,16 @@ flag means that the file is a binary file and so the .Tn ASCII message which .Nm -inserts to indicate the fact that the logs have been -trimmed should not be included. The +inserts to indicate that the logs have been trimmed should not be included. +The .Ar N flag means that no signal should be sent when the log is trimmed. The +.Ar C +flag instructs +.Nm +to create an empty log file if none currently exists. The .Ar - -flag means nothing, but can be used as a placeholder when the -.Ar path_to_pid_file -field is specified. +flag means nothing - it is used as a spacer when no flags are set. .It Ar path_to_pid_file This optional field specifies the file name to read to find the daemon process id. If this @@ -164,11 +187,6 @@ Place .Nm in verbose mode. In this mode it will print out each log and its reasons for either trimming that log or skipping it. -.It Fl n -Cause -.Nm -not to trim the logs, but to print out what it would do if this option -were not specified. .It Fl r Remove the restriction that .Nm @@ -194,8 +212,12 @@ configuration file. .Sh AUTHORS .An Theodore Ts'o , MIT Project Athena +.An Andrew Doran , +The NetBSD Project .Pp Copyright 1987, Massachusetts Institute of Technology +.Pp +Copyright 1999, 2000 Andrew Doran .Sh SEE ALSO .Xr gzip 1 , .Xr syslog 3 , diff --git a/usr.bin/newsyslog/newsyslog.c b/usr.bin/newsyslog/newsyslog.c index 32ca9da45be2..62eb79612150 100644 --- a/usr.bin/newsyslog/newsyslog.c +++ b/usr.bin/newsyslog/newsyslog.c @@ -1,55 +1,69 @@ -/* $NetBSD: newsyslog.c,v 1.21 1999/11/30 12:03:24 ad Exp $ */ +/* $NetBSD: newsyslog.c,v 1.22 2000/07/07 10:52:41 ad Exp $ */ + +/* + * Copyright (c) 1999, 2000 Andrew Doran + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ /* * This file contains changes from the Open Software Foundation. */ /* - -Copyright 1988, 1989 by the Massachusetts Institute of Technology - -Permission to use, copy, modify, and distribute this software -and its documentation for any purpose and without fee is -hereby granted, provided that the above copyright notice -appear in all copies and that both that copyright notice and -this permission notice appear in supporting documentation, -and that the names of M.I.T. and the M.I.T. S.I.P.B. not be -used in advertising or publicity pertaining to distribution -of the software without specific, written prior permission. -M.I.T. and the M.I.T. S.I.P.B. make no representations about -the suitability of this software for any purpose. It is -provided "as is" without express or implied warranty. - -*/ + * Copyright 1988, 1989 by the Massachusetts Institute of Technology + * + * Permission to use, copy, modify, and distribute this software + * and its documentation for any purpose and without fee is + * hereby granted, provided that the above copyright notice + * appear in all copies and that both that copyright notice and + * this permission notice appear in supporting documentation, + * and that the names of M.I.T. and the M.I.T. S.I.P.B. not be + * used in advertising or publicity pertaining to distribution + * of the software without specific, written prior permission. + * M.I.T. and the M.I.T. S.I.P.B. make no representations about + * the suitability of this software for any purpose. It is + * provided "as is" without express or implied warranty. + * + */ /* - * newsyslog - roll over selected logs at the appropriate time, - * keeping the a specified number of backup files around. + * newsyslog(1) - a program to roll over log files provided that specified + * critera are met, optionally preserving a number of historical log files. + * + * XXX too much size_t fsckage. */ #include #ifndef lint -__RCSID("$NetBSD: newsyslog.c,v 1.21 1999/11/30 12:03:24 ad Exp $"); +__RCSID("$NetBSD: newsyslog.c,v 1.22 2000/07/07 10:52:41 ad Exp $"); #endif /* not lint */ -#ifndef CONF -#define CONF "/etc/athena/newsyslog.conf" /* Configuration file */ -#endif -#ifndef PIDFILE -#define PIDFILE "/etc/syslog.pid" -#endif -#ifndef COMPRESS -#define COMPRESS "/usr/ucb/compress" /* File compression program */ -#endif -#ifndef COMPRESS_POSTFIX -#define COMPRESS_POSTFIX ".Z" -#endif - #include #include #include #include -#include #include #include @@ -58,619 +72,545 @@ __RCSID("$NetBSD: newsyslog.c,v 1.21 1999/11/30 12:03:24 ad Exp $"); #include #include #include +#include #include #include #include +#include +#include +#include -#define kbytes(size) (((size) + 1023) >> 10) -#ifdef _IBMR2 -/* Calculates (db * DEV_BSIZE) */ -#define dbtob(db) ((unsigned)(db) << UBSHIFT) -#endif +#include "pathnames.h" + +#define PRINFO(x) ((void)(verbose ? printf x : 0)) + +#define CE_COMPACT 1 /* Compact the achived log files */ +#define CE_BINARY 2 /* Logfile is a binary file/non-syslog */ +#define CE_NOSIGNAL 4 /* Don't send a signal when trimmed */ +#define CE_CREATE 8 /* Create log file if none exists */ -#define CE_COMPACT 1 /* Compact the achived log files */ -#define CE_BINARY 2 /* Logfile is in binary, don't add */ - /* status messages */ -#define CE_NOSIGNAL 4 /* Don't send a signal when trimmed */ -#define NONE -1 - struct conf_entry { - char *log; /* Name of the log */ - int uid; /* Owner of log */ - int gid; /* Group of log */ - int numlogs; /* Number of logs to keep */ - int size; /* Size cutoff to trigger trimming the log */ - int hours; /* Hours between log trimming */ - int permissions; /* File permissions on the log */ - int flags; /* Flags (CE_*) */ - char *pidfile; /* Name of file containing PID to signal */ - int signum; /* Signal to send */ - struct conf_entry *next; /* Linked list pointer */ + uid_t uid; /* Owner of log */ + gid_t gid; /* Group of log */ + mode_t mode; /* File permissions */ + int numhist; /* Number of historical logs to keep */ + size_t maxsize; /* Maximum log size */ + int maxage; /* Hours between log trimming */ + int flags; /* Flags (CE_*) */ + int signum; /* Signal to send */ + char pidfile[MAXPATHLEN]; /* File containing PID to signal */ + char logfile[MAXPATHLEN]; /* Path to log file */ }; -char *progname; /* contains argv[0] */ -int verbose = 0; /* Print out what's going on */ -int needroot = 1; /* Root privs are necessary */ -int noaction = 0; /* Don't do anything, just show it */ -int force; /* Force the trim no matter what */ -char *conf = CONF; /* Configuration file to use */ -time_t timenow; -int syslog_pid; /* read in from /etc/syslog.pid */ -#define MIN_PID 3 -#define MAX_PID 65534 -char hostname[MAXHOSTNAMELEN + 1]; /* hostname */ -char *daytime; /* timenow in human readable form */ +int verbose = 0; /* Be verbose */ +char hostname[MAXHOSTNAMELEN + 1]; /* Hostname, stripped of domain */ +int main(int, char **); +int parse(struct conf_entry *, FILE *, size_t *); -void PRS __P((int, char **)); -int age_old_log __P((char *)); -void compress_log __P((char *)); -void dotrim __P((char *, int, int, int, int, int, char *, int)); -void do_entry __P((struct conf_entry *)); -int isnumber __P((char *)); -int log_trim __P((char *)); -int main __P((int, char **)); -char *missing_field __P((char *, char *)); -struct conf_entry *parse_file __P((void)); -int sizefile __P((char *)); -char *sob __P((char *)); -char *son __P((char *)); -void usage __P((void)); -int getsig __P((char *)); +void log_create(struct conf_entry *); +void log_examine(struct conf_entry *, int); +void log_trim(struct conf_entry *); +void log_trimmed(struct conf_entry *); +int getsig(const char *); +int isnumber(const char *); +int parseuserspec(const char *, struct passwd **, struct group **); +pid_t readpidfile(const char *); +void usage(void); + +/* + * Program entry point. + */ int -main(argc,argv) - int argc; - char **argv; +main(int argc, char **argv) { - struct conf_entry *p, *q; - - PRS(argc,argv); - if (needroot && getuid() && geteuid()) { - fprintf(stderr,"%s: must have root privs\n",progname); - exit(1); - } - p = q = parse_file(); - while (p) { - do_entry(p); - p=p->next; - free((char *) q); - q=p; - } - exit(0); -} + struct conf_entry ent; + FILE *fd; + char *p, *cfile; + int c, force, needroot; + size_t lineno; -void -do_entry(ent) - struct conf_entry *ent; -{ - int size, modtime; - - if (verbose) { - if (ent->flags & CE_COMPACT) - printf("%s <%dZ>: ",ent->log,ent->numlogs); - else - printf("%s <%d>: ",ent->log,ent->numlogs); - } - size = sizefile(ent->log); - modtime = age_old_log(ent->log); - if (size < 0) { - if (verbose) - printf("does not exist.\n"); - } else { - if (verbose && (ent->size > 0)) - printf("size (Kb): %d [%d] ", size, ent->size); - if (verbose && (ent->hours > 0)) - printf(" age (hr): %d [%d] ", modtime, ent->hours); - if (force || ((ent->size > 0) && (size >= ent->size)) || - ((ent->hours > 0) && ((modtime >= ent->hours) - || (modtime < 0)))) { - if (verbose) - printf("--> trimming log....\n"); - if (noaction && !verbose) { - if (ent->flags & CE_COMPACT) - printf("%s <%dZ>: trimming", - ent->log,ent->numlogs); - else - printf("%s <%d>: trimming", - ent->log,ent->numlogs); - } - dotrim(ent->log, ent->numlogs, ent->flags, - ent->permissions, ent->uid, ent->gid, - ent->pidfile, ent->signum); - } else { - if (verbose) - printf("--> skipping\n"); - } - } -} + force = 0; + needroot = 1; + cfile = _PATH_NEWSYSLOGCONF; -void -PRS(argc,argv) - int argc; - char **argv; -{ - int c; - FILE *f; - char line[BUFSIZ]; - char *p; - - progname = argv[0]; - timenow = time((time_t *) 0); - daytime = ctime(&timenow) + 4; - daytime[15] = '\0'; - - /* Let's find the pid of syslogd */ - syslog_pid = 0; - f = fopen(PIDFILE,"r"); - if (f && fgets(line,BUFSIZ,f)) - syslog_pid = atoi(line); - if (f) - (void)fclose(f); - - /* Let's get our hostname */ - (void)gethostname(hostname, sizeof(hostname)); + gethostname(hostname, sizeof(hostname)); hostname[sizeof(hostname) - 1] = '\0'; /* Truncate domain */ - if ((p = strchr(hostname, '.')) != NULL) { + if ((p = strchr(hostname, '.')) != NULL) *p = '\0'; + + /* Parse command line options */ + while ((c = getopt(argc, argv, "Frvf:")) != -1) { + switch (c) { + case 'r': + needroot = 0; + break; + case 'v': + verbose = 1; + break; + case 'f': + cfile = optarg; + break; + case 'F': + force = 1; + break; + default: + usage(); + } } - optind = 1; /* Start options parsing */ - while ((c=getopt(argc,argv,"Fnrvf:")) != -1) - switch (c) { - case 'n': - noaction++; /* This implies needroot as off */ - /* fall through */ - case 'r': - needroot = 0; - break; - case 'v': - verbose++; - break; - case 'f': - conf = optarg; - break; - case 'F': - force++; - break; - default: - usage(); - } + if (needroot && getuid() != 0 && geteuid() != 0) + errx(EXIT_FAILURE, "must be run as root"); + + if (strcmp(cfile, "-") == 0) + fd = stdin; + else if ((fd = fopen(cfile, "rt")) == NULL) + err(EXIT_FAILURE, "%s", cfile); + + for (lineno = 0; !parse(&ent, fd, &lineno);) + log_examine(&ent, force); + + if (fd != stdin) + fclose(fd); + + exit(EXIT_SUCCESS); + /* NOTREACHED */ } -void -usage() -{ - fprintf(stderr, - "Usage: %s <-Fnrv> <-f config-file>\n", progname); - exit(1); -} - -/* Parse a configuration file and return a linked list of all the logs - * to process +/* + * Parse a single line from the configuration file. */ -struct conf_entry * -parse_file() +int +parse(struct conf_entry *log, FILE *fd, size_t *_lineno) { - FILE *f; - char line[BUFSIZ], *parse, *q; - char *errline, *group, prev; - struct conf_entry *first = NULL; - struct conf_entry *working; - struct passwd *pass; - struct group *grp; + char *line, *q, **ap, *argv[10]; + struct passwd *pw; + struct group *gr; + int nf, lineno, i; + + if ((line = fparseln(fd, NULL, _lineno, NULL, 0)) == NULL) + return (-1); + lineno = (int)*_lineno; - working = NULL; - if (strcmp(conf,"-")) - f = fopen(conf,"r"); - else - f = stdin; - if (!f) { - (void) fprintf(stderr,"%s: ",progname); - perror(conf); - exit(1); - } - while (fgets(line,BUFSIZ,f)) { - if ((line[0]== '\n') || (line[0] == '#')) - continue; - errline = strdup(line); - if (!first) { - working = (struct conf_entry *) malloc(sizeof(struct conf_entry)); - first = working; - } else { - working->next = (struct conf_entry *) malloc(sizeof(struct conf_entry)); - working = working->next; - } - - q = parse = missing_field(sob(line),errline); - *(parse = son(line)) = '\0'; - working->log = strdup(q); - - q = parse = missing_field(sob(++parse),errline); - *(parse = son(parse)) = '\0'; - if ((group = strchr(q, ':')) != NULL || (group = strchr(q, '.')) != NULL) { - *group++ = '\0'; - if (*q) { - if (!(isnumber(q))) { - if ((pass = getpwnam(q)) == NULL) { - fprintf(stderr, - "Error in config file; unknown user:\n"); - fputs(errline,stderr); - exit(1); - } - working->uid = pass->pw_uid; - } else - working->uid = atoi(q); - } else - working->uid = NONE; - - q = group; - if (*q) { - if (!(isnumber(q))) { - if ((grp = getgrnam(q)) == NULL) { - fprintf(stderr, - "Error in config file; unknown group:\n"); - fputs(errline,stderr); - exit(1); - } - working->gid = grp->gr_gid; - } else - working->gid = atoi(q); - } else - working->gid = NONE; - - q = parse = missing_field(sob(++parse),errline); - *(parse = son(parse)) = '\0'; - } - else - working->uid = working->gid = NONE; - - if (!sscanf(q,"%o",&working->permissions)) { - fprintf(stderr, - "Error in config file; bad permissions:\n"); - fputs(errline,stderr); - exit(1); - } - - q = parse = missing_field(sob(++parse),errline); - *(parse = son(parse)) = '\0'; - if (!sscanf(q,"%d",&working->numlogs)) { - fprintf(stderr, - "Error in config file; bad number:\n"); - fputs(errline,stderr); - exit(1); - } - - q = parse = missing_field(sob(++parse),errline); - *(parse = son(parse)) = '\0'; - if (isdigit((unsigned char)*q)) - working->size = atoi(q); - else - working->size = -1; - - q = parse = missing_field(sob(++parse),errline); - *(parse = son(parse)) = '\0'; - if (isdigit((unsigned char)*q)) - working->hours = atoi(q); - else - working->hours = -1; - - q = parse = sob(++parse); /* Optional field */ - prev = *(parse = son(parse)); - *parse = '\0'; - working->flags = 0; - while (q && *q && !isspace((unsigned char)*q)) { - if ((*q == 'Z') || (*q == 'z')) - working->flags |= CE_COMPACT; - else if ((*q == 'B') || (*q == 'b')) - working->flags |= CE_BINARY; - else if ((*q == 'N') || (*q == 'n')) - working->flags |= CE_NOSIGNAL; - else if (*q != '-') { - fprintf(stderr, - "Illegal flag in config file -- %c\n", - *q); - exit(1); - } - q++; - } - - if (prev != '\0' && (q = parse = sob(++parse)) != NULL && - q[0] == '/') { - prev = *(parse = son(parse)); - *parse++ = '\0'; - working->pidfile = strdup(q); - } else { - working->pidfile = NULL; - prev = *parse; + for (ap = argv, nf = 0; (*ap = strsep(&line, " \t")) != NULL;) + if (**ap != '\0') { + if (++nf == sizeof(argv) / sizeof(argv[0])) { + warnx("config line %d: too many fields", + lineno); + return (-1); + } + ap++; } + + if (nf == 0) + return (0); - if (prev != '\0' && (q = parse = sob(parse)) != NULL && - q[0] != '\0') { - *(parse = son(parse)) = '\0'; - if ((working->signum = getsig(q)) < 0) { - fprintf(stderr, - "Illegal signal in config file -- %s\n", - q); - exit(1); - } - } else - working->signum = SIGHUP; - - free(errline); - } - if (working) - working->next = (struct conf_entry *) NULL; - (void) fclose(f); - return(first); -} - -char * -missing_field(p,errline) - char *p,*errline; -{ - if (!p || !*p) { - fprintf(stderr,"Missing field in config file:\n"); - fputs(errline,stderr); - exit(1); - } - return(p); + if (nf < 6) + errx(EXIT_FAILURE, "config line %d: too few fields", lineno); + + ap = argv; + strlcpy(log->logfile, *ap++, sizeof(log->logfile)); + + if (strchr(*ap, ':') != NULL) { + if (parseuserspec(*ap++, &pw, &gr)) { + warnx("config line %d: unknown user/group", lineno); + return (-1); + } + log->uid = pw->pw_uid; + log->gid = gr->gr_gid; + if (nf < 7) + errx(EXIT_FAILURE, "config line %d: too few fields", + lineno); + } + + if (sscanf(*ap++, "%o", &i) != 1) { + warnx("config line %d: bad permissions", lineno); + return (-1); + } + log->mode = (mode_t)i; + + if (sscanf(*ap++, "%d", &log->numhist) != 0) { + warnx("config line %d: bad log count", lineno); + return (-1); + } + + if (isdigit(**ap)) + log->maxsize = atoi(*ap); + else if (**ap == '*') + log->maxsize = (size_t)-1; + else { + warnx("config line %d: bad log size", lineno); + return (-1); + } + ap++; + + if (isdigit(**ap)) + log->maxage = atoi(*ap); + else if (**ap == '*') + log->maxage = -1; + else { + warnx("config line %d: bad log age", lineno); + return (-1); + } + ap++; + + log->flags = 0; + for (q = *ap++; q != NULL && *q != '\0' && !isspace(*q); q++) { + switch (tolower(*q)) { + case 'b': + log->flags |= CE_BINARY; + break; + case 'c': + log->flags |= CE_CREATE; + break; + case 'n': + log->flags |= CE_NOSIGNAL; + break; + case 'z': + log->flags |= CE_COMPACT; + break; + case '-': + break; + default: + warnx("config line %d: bad flags", lineno); + return (-1); + } + } + + if (*ap != NULL && **ap == '/') + strlcpy(log->pidfile, *ap++, sizeof(log->pidfile)); + else + log->pidfile[0] = '\0'; + + if (*ap != NULL && (log->signum = getsig(*ap++)) < 0) { + warnx("config line %d: bad signal type", lineno); + return (-1); + } else + log->signum = SIGHUP; + + return (0); } +/* + * Examine a log file. If the trim conditions are met, call log_trim() to + * trim the log file. + */ void -dotrim(log,numdays,flags,perm,owner_uid,group_gid,pidfile,signum) - char *log; - int numdays; - int flags; - int perm; - int owner_uid; - int group_gid; - char *pidfile; - int signum; +log_examine(struct conf_entry *ent, int force) { - char file1[128], file2[128]; - char zfile1[128], zfile2[128]; - char line[BUFSIZ]; - int fd; - struct stat st; - int ngen = numdays; - FILE *f; - pid_t pid; + struct stat sb; + size_t size; + int modtime; + char tmp[MAXPATHLEN]; + time_t now; -#ifdef _IBMR2 -/* AIX 3.1 has a broken fchown- if the owner_uid is -1, it will actually */ -/* change it to be owned by uid -1, instead of leaving it as is, as it is */ -/* supposed to. */ - if (owner_uid == -1) - owner_uid = geteuid(); -#endif + if (ent->logfile[0] == '\0') + return; + if (stat(ent->logfile, &sb) < 0) { + if (errno == ENOENT && (ent->flags & CE_CREATE) != 0) { + PRINFO(("%s: no file, creating\n", ent->logfile)); + log_create(ent); + errno = 0; + stat(ent->logfile, &sb); + } - if (pidfile != NULL && pidfile[0] != '\0') { - if ((f = fopen(pidfile,"r")) == NULL) { - (void) fprintf(stderr,"%s: ",progname); - perror(conf); - return; - } - - if (fgets(line,BUFSIZ,f)) - pid = atoi(line); - if (f) - (void)fclose(f); - } else - pid = syslog_pid; - - /* Remove oldest log */ - (void) sprintf(file1,"%s.%d",log,numdays); - (void) strcpy(zfile1, file1); - (void) strcat(zfile1, COMPRESS_POSTFIX); - - if (noaction) { - printf("rm -f %s\n", file1); - printf("rm -f %s\n", zfile1); - } else { - (void) unlink(file1); - (void) unlink(zfile1); - } - - /* Move down log files */ - while (numdays--) { - (void) strcpy(file2,file1); - (void) sprintf(file1,"%s.%d",log,numdays); - (void) strcpy(zfile1, file1); - (void) strcpy(zfile2, file2); - if (lstat(file1, &st)) { - (void) strcat(zfile1, COMPRESS_POSTFIX); - (void) strcat(zfile2, COMPRESS_POSTFIX); - if (lstat(zfile1, &st)) continue; - } - if (noaction) { - printf("mv %s %s\n",zfile1,zfile2); - printf("chmod %o %s\n", perm, zfile2); - printf("chown %d.%d %s\n", - owner_uid, group_gid, zfile2); - } else { - (void) rename(zfile1, zfile2); - (void) chmod(zfile2, perm); - (void) chown(zfile2, owner_uid, group_gid); - } - } - if (!noaction && !(flags & CE_BINARY)) - (void) log_trim(log); /* Report the trimming to the old log */ - - if (ngen == 0) { - if (noaction) - printf("rm %s\n",log); - else - (void) unlink(log); - } else - if (noaction) - printf("mv %s to %s\n",log,file1); - else - (void) rename(log,file1); - - if (noaction) - printf("Start new log..."); - else { - fd = creat(log,perm); - if (fd < 0) { - perror("can't start new log"); - exit(1); - } - if (fchown(fd, owner_uid, group_gid)) { - perror("can't chmod new log file"); - exit(1); - } - (void) close(fd); - if (!(flags & CE_BINARY)) - if (log_trim(log)) { /* Add status message */ - perror("can't add status message to log"); - exit(1); - } - } - if (noaction) - printf("chmod %o %s...",perm,log); - else - (void) chmod(log,perm); - - if ((flags & CE_NOSIGNAL) == 0) { - if (noaction) - printf("kill -HUP %d\n",pid); - else if (pid < MIN_PID || pid > MAX_PID) { - fprintf(stderr,"%s: preposterous process number: %d\n", - progname, pid); - } else if (kill(pid, signum)) { - fprintf(stderr,"%s: ", progname); - perror("warning - could not restart daemon"); + if (errno != 0) { + PRINFO(("%s: %s\n", ent->logfile, strerror(errno))); + return; } } - if ((flags & CE_COMPACT) != 0) { - if (noaction) - printf("Compress %s.0\n",log); - else - compress_log(log); - } + if (verbose) { + if ((ent->flags & CE_COMPACT) != 0) + PRINFO(("%s <%dZ>: ", ent->logfile, ent->numhist)); + else + PRINFO(("%s <%d>: ", ent->logfile, ent->numhist)); + } + + size = ((size_t)sb.st_blocks * S_BLKSIZE) >> 10; + + now = time(NULL); + strlcpy(tmp, ent->logfile, sizeof(tmp)); + strlcat(tmp, ".0", sizeof(tmp)); + if (stat(tmp, &sb) < 0) { + strlcat(tmp, ".gz", sizeof(tmp)); + if (stat(tmp, &sb) < 0) + modtime = -1; + else + modtime = (int)(now - sb.st_mtime + 1800) / 3600; + } else + modtime = (int)(now - sb.st_mtime + 1800) / 3600; + + if (verbose) { + if (ent->maxsize != (size_t)-1) + PRINFO(("size (Kb): %d [%d] ", size, ent->maxsize)); + if (ent->maxage > 0) + PRINFO((" age (hr): %d [%d] ", modtime, ent->maxage)); + } + + /* + * Note: if maxage is used as a trim condition, we need at least one + * historical log file to determine the `age' of the active log file. + */ + if ((ent->maxage > 0 && (modtime >= ent->maxage || modtime < 0)) || + size >= ent->maxsize || force) { + PRINFO(("--> trimming log....\n")); + log_trim(ent); + } else + PRINFO(("--> skipping\n")); } -/* Log the fact that the logs were turned over */ -int -log_trim(log) - char *log; -{ - FILE *f; - if ((f = fopen(log,"a")) == NULL) - return(-1); - fprintf(f,"%s %s newsyslog[%ld]: logfile turned over\n", - daytime, hostname, (u_long)getpid()); - if (fclose(f) == EOF) { - perror("log_trim: fclose"); - exit(1); - } - return(0); -} - -/* Fork off compress/gzip to compress the old log file */ +/* + * Trim the specified log file. + */ void -compress_log(log) - char *log; +log_trim(struct conf_entry *log) { - int pid; - char tmp[128]; - - pid = fork(); - (void) sprintf(tmp,"%s.0",log); - if (pid < 0) { - fprintf(stderr,"%s: ",progname); - perror("fork"); - exit(1); - } else if (!pid) { - (void) execl(COMPRESS,"compress","-f",tmp,0); - fprintf(stderr,"%s: ",progname); - perror(COMPRESS); - exit(1); - } + char file1[MAXPATHLEN], file2[MAXPATHLEN]; + char zfile1[MAXPATHLEN], zfile2[MAXPATHLEN]; + int i; + struct stat st; + pid_t pid; + + /* Remove oldest log */ + snprintf(file1, sizeof(file1), "%s.%d", log->logfile, + log->numhist - 1); + strcpy(zfile1, file1); + strlcat(zfile1, ".gz", sizeof(zfile1)); + + PRINFO(("rm -f %s\n", file1)); + unlink(file1); + PRINFO(("rm -f %s\n", zfile1)); + unlink(zfile1); + + /* Move down log files. */ + for (i = log->numhist - 1; i != 0; i--) { + strcpy(file2, file1); + snprintf(file1, sizeof(file1), "%s.%d", log->logfile, i - 1); + strcpy(zfile1, file1); + strcpy(zfile2, file2); + + if (lstat(file1, &st) != 0) { + strlcat(zfile1, ".gz", sizeof(zfile1)); + strlcat(zfile2, ".gz", sizeof(zfile2)); + if (lstat(zfile1, &st) != 0) + continue; + } + + PRINFO(("mv %s %s\n", zfile1, zfile2)); + rename(zfile1, zfile2); + PRINFO(("chmod %o %s\n", log->mode, zfile2)); + chmod(zfile2, log->mode); + PRINFO(("chown %d.%d %s\n", log->uid, log->gid, zfile2)); + chown(zfile2, log->uid, log->gid); + } + + if ((log->flags & CE_BINARY) == 0) + log_trimmed(log); + + if (log->numhist == 0) { + PRINFO(("rm %s\n", log->logfile)); + unlink(log->logfile); + } else { + PRINFO(("mv %s to %s\n", log->logfile, file1)); + rename(log->logfile, file1); + } + + PRINFO(("Start new log...\n")); + log_create(log); + + if ((log->flags & CE_BINARY) == 0) + log_trimmed(log); + + PRINFO(("chmod %o %s\n", log->mode, log->logfile)); + chmod(log->logfile, log->mode); + + if ((log->flags & CE_NOSIGNAL) == 0) { + if (log->pidfile[0] != '\0') + pid = readpidfile(log->pidfile); + else + pid = readpidfile(_PATH_SYSLOGDPID); + + if (pid != (pid_t)-1) { + PRINFO(("kill -%s %d\n", sys_signame[log->signum], + pid)); + if (kill(pid, log->signum)) + warn("warning - could not signal daemon"); + } + } + + if ((log->flags & CE_COMPACT) != 0) { + PRINFO(("gzip %s.0\n", log->logfile)); + + if ((pid = fork()) < 0) + err(EXIT_FAILURE, "fork"); + else if (pid == 0) { + snprintf(file1, sizeof(file1), "%s.0", log->logfile); + execl(_PATH_GZIP, "gzip", "-f", file1, NULL); + err(EXIT_FAILURE, _PATH_GZIP); + } + } } -/* Return size in kilobytes of a file */ +/* + * Write an entry to the log file recording the fact that it was trimmed. + */ +void +log_trimmed(struct conf_entry *log) +{ + FILE *fd; + time_t now; + char *daytime; + + if ((fd = fopen(log->logfile, "at")) == NULL) + err(EXIT_FAILURE, "%s", log->logfile); + + now = time(NULL); + daytime = ctime(&now) + 4; + daytime[15] = '\0'; + + fprintf(fd, "%s %s newsyslog[%ld]: log file turned over\n", daytime, + hostname, (u_long)getpid()); + fclose(fd); +} + +/* + * Create a new log file. + */ +void +log_create(struct conf_entry *ent) +{ + int fd; + + if ((fd = creat(ent->logfile, ent->mode)) < 0) + err(EXIT_FAILURE, "%s", ent->logfile); + if (fchown(fd, ent->uid, ent->gid) < 0) + err(EXIT_FAILURE, "%s", ent->logfile); + if (close(fd) < 0) + err(EXIT_FAILURE, "%s", ent->logfile); +} + +/* + * Display program usage information. + */ +void +usage(void) +{ + + fprintf(stderr, "usage: newsyslog [-Frv] [-f config-file]\n"); + exit(EXIT_FAILURE); +} + +/* + * Return non-zero if a string represents a decimal value. + */ int -sizefile(file) - char *file; +isnumber(const char *string) { - struct stat sb; - if (stat(file,&sb) < 0) - return(-1); - return(kbytes(dbtob(sb.st_blocks))); + while (isdigit(*string)) + string++; + + return (*string == '\0'); } -/* Return the age of old log file (file.0) */ +/* + * Given a signal name, attempt to find the corresponding signal number. + */ int -age_old_log(file) - char *file; -{ - struct stat sb; - char tmp[MAXPATHLEN+3]; - - (void) strcpy(tmp,file); - if (stat(strcat(tmp,".0"),&sb) < 0) - if (stat(strcat(tmp,COMPRESS_POSTFIX), &sb) < 0) - return(-1); - return( (int) (timenow - sb.st_mtime + 1800) / 3600); -} - -/* Skip Over Blanks */ -char *sob(p) - char *p; -{ - while (p && *p && isspace((unsigned char)*p)) - p++; - return(p); -} - -/* Skip Over Non-Blanks */ -char * -son(p) - char *p; -{ - while (p && *p && !isspace((unsigned char)*p)) - p++; - return(p); -} - - -/* Check if string is actually a number */ - -int -isnumber(string) - char *string; -{ - while (*string != '\0') { - if (*string < '0' || *string > '9') return(0); - string++; - } - return(1); -} - -int -getsig(sig) - char *sig; +getsig(const char *sig) { + char *p; int n; if (isnumber(sig)) { - n = strtol(sig, &sig, 0); - if ((unsigned)n >= NSIG) + n = (int)strtol(sig, &p, 0); + if (p != '\0' || (unsigned)n >= NSIG) return (-1); return (n); } - if (!strncasecmp(sig, "sig", 3)) + if (strncasecmp(sig, "sig", 3) == 0) sig += 3; - for (n = 1; n < NSIG; n++) { - if (!strcasecmp(sys_signame[n], sig)) + for (n = 1; n < NSIG; n++) + if (strcasecmp(sys_signame[n], sig) == 0) return (n); - } return (-1); } + +/* + * Given a path to a PID file, return the PID contained within. + */ +pid_t +readpidfile(const char *file) +{ + FILE *fd; + char line[BUFSIZ]; + pid_t pid; + + if ((fd = fopen(file, "rt")) == NULL) { + warn("%s", file); + return (-1); + } + + if (fgets(line, sizeof(line) - 1, fd) != NULL) { + line[sizeof(line) - 1] = '\0'; + pid = (pid_t)strtol(line, NULL, 0); + } + + fclose(fd); + return (pid); +} + +/* + * Parse a user:group specification. + * + * XXX This is over the top for newsyslog(1). It should be moved to libutil. + */ +int +parseuserspec(const char *name, struct passwd **pw, struct group **gr) +{ + char buf[MAXLOGNAME * 2 + 2], *group; + + strlcpy(buf, name, sizeof(buf)); + *gr = NULL; + + /* + * Before attempting to use '.' as a separator, see if the whole + * string resolves as a user name or UID. + */ + if ((*pw = getpwnam(buf)) != NULL) { + *gr = getgrgid((*pw)->pw_gid); + return (0); + } + + /* Split the user and group name. */ + if ((group = strchr(buf, ':')) != NULL || + (group = strchr(buf, '.')) != NULL) + *group++ = '\0'; + + if (isnumber(buf)) + *pw = getpwuid((uid_t)atoi(buf)); + else + *pw = getpwnam(buf); + + /* + * Find the group. If a group wasn't specified, use the user's + * `natural' group. We get to this point even if no user was found. + * This is to allow the caller to get a better idea of what went + * wrong, if anything. + */ + if (group == NULL || *group == '\0') { + if (*pw == NULL) + return (-1); + *gr = getgrgid((*pw)->pw_gid); + } else if (isnumber(group)) + *gr = getgrgid((gid_t)atoi(group)); + else + *gr = getgrnam(group); + + return (*gr != NULL ? 0 : -1); +} diff --git a/usr.bin/newsyslog/pathnames.h b/usr.bin/newsyslog/pathnames.h new file mode 100644 index 000000000000..a63ea5f23f63 --- /dev/null +++ b/usr.bin/newsyslog/pathnames.h @@ -0,0 +1,32 @@ +/* $NetBSD: pathnames.h,v 1.1 2000/07/07 10:52:41 ad Exp $ */ + +/* + * Copyright (c) 1999, 2000 Andrew Doran + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#define _PATH_NEWSYSLOGCONF "/etc/newsyslog.conf" +#define _PATH_SYSLOGDPID "/var/run/syslogd.pid" +#define _PATH_GZIP "/usr/bin/gzip"