diff --git a/usr.bin/newsyslog/newsyslog.8 b/usr.bin/newsyslog/newsyslog.8 index 402f1646c72c..2b7b78cbde49 100644 --- a/usr.bin/newsyslog/newsyslog.8 +++ b/usr.bin/newsyslog/newsyslog.8 @@ -1,4 +1,4 @@ -.\" $NetBSD: newsyslog.8,v 1.15 2000/07/11 11:39:46 ad Exp $ +.\" $NetBSD: newsyslog.8,v 1.16 2000/07/18 15:59:24 ad Exp $ .\" .\" Copyright (c) 1999, 2000 Andrew Doran .\" All rights reserved. @@ -50,6 +50,7 @@ .Nm newsyslog .Op Fl nrvF .Op Fl f Ar config_file +.Op Pa file ... .Sh DESCRIPTION .Nm Newsyslog is a program that should be scheduled to run periodically by @@ -69,13 +70,16 @@ period's logs in it and so on, up to a user-specified number of archived logs. Optionally the archived logs can be compressed to save space. .Pp -A log can be archived for two reasons: +A log can be archived for three reasons: .Bl -enum -offset indent .It It is larger than the configured size (in kilobytes). .It A configured number of hours have elapsed since the log was last archived. +.It +The configured time for rotation of the log occurred within the last 60 +minutes. .El .Pp The granularity of @@ -129,15 +133,165 @@ is replaced by an asterisk .Pq Ql \&* , then the size of the log file is not taken into account when determining when to trim the log file. -.It Ar interval +.It Ar when The -.Ar interval -field specifies the time separation (in hours) between trimming of the -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. +.Ar when +field can consist of an interval, a specific time, or both. If +the +.Ar when +field is an asterisk +.Pq Ql \&* +log rotation will depend only on the contents of the +.Ar size +field. +Otherwise, the +.Ar when +field consists of an optional interval in hours, optionally followed +by an +.So Li \&@ Sc Ns No -sign +and a time in a restricted +.Tn ISO 8601 +format or by an +.So Li \&$ Sc Ns No -sign +and a time specification for logfile rotation at a fixed time once +per day, per week or per month. +.Pp +If a time is specified, the log file will only be trimmed if +.Nm +is run within one hour of the specified time. If an +interval is specified, the log file will be trimmed if that many hours have +passed since the last rotation. When both a time and an interval are +specified, both conditions must be satisfied for the rotation to take +place. +.Pp +There is no provision for specification of a timezone. There is +little point in specifying an explicit minutes or seconds component in +the current implementation, since the only comparison is `within the +hour'. +.Pp +.Em ISO 8601 restricted time format +.Pp +The lead-in character for a restricted +.Tn ISO 8601 +time is +an +.So Li \&@ Sc Ns No -sign . +The particular format of the time in restricted +.Tn ISO 8601 +is: +.Sm off +.Oo +.Oo +.Oo +.Oo +.Oo +.Va \&cc +.Oc +.Va \&yy +.Oc +.Va \&mm +.Oc +.Va \&dd +.Oc +.Oo +.Li \&T +.Oo +.Va \&hh +.Oo +.Va \&mm +.Oo +.Va \&ss +.Oc +.Oc +.Oc +.Oc +.Oc . +.Sm on +Optional date fields default to the appropriate component of the +current date; optional time fields default to midnight; hence if today +is January 22, 1999, the following date specifications are all +equivalent: +.Pp +.Bl -item -compact -offset indent +.It +.Sq Li 19990122T000000 +.It +.Sq Li 990122T000000 +.It +.Sq Li 0122T000000 +.It +.Sq Li 22T000000 +.It +.Sq Li T000000 +.It +.Sq Li T0000 +.It +.Sq Li T00 +.It +.Sq Li 22T +.It +.Sq Li \&T +.It +.Sq Li \& +.El +.Pp +.Em Day, week and month time format +.Pp +The lead-in character for day, week and month specification is a +.So Li \&$ Sc Ns No -sign . +The particular format of day, week and month specification is: +.Sm off +.Oo +.Va D\&hh +.Oc , +.Oo +.Va W\&w +.Oo +.Va D\&hh +.Oc +.Oc +and +.Oo +.Va M\&dd +.Oo +.Va D\&hh +.Oc +.Oc +.Sm on +respectively. +Optional time fields default to midnight. +The ranges for day and hour secifications are: +.Pp +.Bl -tag -width Ds -compact -offset indent +.It Ar hh +hours, range 0 ... 23 +.It Ar w +day of week, range 0 ... 6, 0 = Sunday +.It Ar dd +day of month, range 1 ... 31, or the letter +.Em L +or +.Em l +to specify the last day of the month. +.El +.Pp +Some examples: +.Pp +.Bl -tag -width Ds -compact -offset indent +.It Ar $D0 +rotate every night at midnight +.It Ar $D23 +rotate every day at 23:00 hr +.It Ar $W0D23 +rotate every week on Sunday at 23:00 hr +.It Ar $W5D16 +rotate every week on Friday at 16:00 hr +.It Ar $MLD0 +rotate at the last day of every month at midnight +.It Ar $M5D6 +rotate on every 5th day of month at 6:00 hr +.El +.Pp .It Ar flags This field specifies any special processing that is required. Individual flags and their meanings: @@ -182,16 +336,14 @@ a SIGHUP (hang-up) will be sent. The following options can be used with newsyslog: .Bl -tag -width indent .It Fl f Ar config_file -Instruct newsyslog to use +Use .Ar config_file instead of .Pa /etc/newsyslog.conf -as its configuration file. +as the configuration file. .It Fl n -Cause -.Nm -not to trim the logs, but to print out what it would do if this option -were not specified: +Do not trim the logs, but print out would be done if this option were not +specified: .Fl n implies .Fl v . @@ -205,17 +357,17 @@ will not be able to send a HUP signal to so this option should be used only when debugging or trimming user generated logs. .It Fl v -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. +Run in verbose mode. In this mode each action that is taken will be printed. .It Fl F -Force -.Nm -to trim the logs, even if the trim conditions have not been met. This -option is useful for diagnosing system problems by providing you with +Force trimming of the logs, even if the trim conditions have not been met. +This option is useful for diagnosing system problems by providing you with fresh logs. .El +.Pp +If additional command line arguments are given, +.Nm +will only examine log files that match those arguments; otherwise, it +will examine all files listed in the configuration file. .Sh FILES .Bl -tag -width /etc/newsyslog.confxxxx -compact .It Pa /etc/newsyslog.conf diff --git a/usr.bin/newsyslog/newsyslog.c b/usr.bin/newsyslog/newsyslog.c index 66aed3043f0d..af716a2c3170 100644 --- a/usr.bin/newsyslog/newsyslog.c +++ b/usr.bin/newsyslog/newsyslog.c @@ -1,4 +1,4 @@ -/* $NetBSD: newsyslog.c,v 1.31 2000/07/13 11:28:50 ad Exp $ */ +/* $NetBSD: newsyslog.c,v 1.32 2000/07/18 15:59:25 ad Exp $ */ /* * Copyright (c) 1999, 2000 Andrew Doran @@ -49,13 +49,13 @@ */ /* - * newsyslog(1) - a program to roll over log files provided that specified + * newsyslog(8) - a program to roll over log files provided that specified * critera are met, optionally preserving a number of historical log files. */ #include #ifndef lint -__RCSID("$NetBSD: newsyslog.c,v 1.31 2000/07/13 11:28:50 ad Exp $"); +__RCSID("$NetBSD: newsyslog.c,v 1.32 2000/07/18 15:59:25 ad Exp $"); #endif /* not lint */ #include @@ -87,8 +87,8 @@ __RCSID("$NetBSD: newsyslog.c,v 1.31 2000/07/13 11:28:50 ad Exp $"); #define CE_COMPRESS 0x01 /* Compress the achived log files */ #define CE_BINARY 0x02 /* Logfile is a binary file/non-syslog */ #define CE_NOSIGNAL 0x04 /* Don't send a signal when trimmed */ -#define CE_CREATE 0x08 /* Create log file if none exists */ -#define CE_PLAIN0 0x10 /* Do not compress zero'th history file */ +#define CE_CREATE 0x08 /* Create log file if none exists */ +#define CE_PLAIN0 0x10 /* Do not compress zero'th history file */ struct conf_entry { uid_t uid; /* Owner of log */ @@ -97,6 +97,7 @@ struct conf_entry { int numhist; /* Number of historical logs to keep */ size_t maxsize; /* Maximum log size */ int maxage; /* Hours between log trimming */ + time_t trimat; /* Specific trim time */ int flags; /* Flags (CE_*) */ int signum; /* Signal to send */ char pidfile[MAXPATHLEN]; /* File containing PID to signal */ @@ -106,9 +107,17 @@ struct conf_entry { int verbose = 0; /* Be verbose */ int noaction = 0; /* Take no action */ char hostname[MAXHOSTNAMELEN + 1]; /* Hostname, stripped of domain */ +uid_t myeuid; /* EUID we are running with */ +int getsig(const char *); +int isnumber(const char *); int main(int, char **); -int parse(struct conf_entry *, FILE *, size_t *); +int parse_cfgline(struct conf_entry *, FILE *, size_t *); +time_t parse_iso8601(char *); +time_t parse_dwm(char *); +int parse_userspec(const char *, struct passwd **, struct group **); +pid_t readpidfile(const char *); +void usage(void); void log_compress(struct conf_entry *, const char *); void log_create(struct conf_entry *); @@ -116,12 +125,6 @@ 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. */ @@ -131,21 +134,21 @@ main(int argc, char **argv) struct conf_entry log; FILE *fd; char *p, *cfile; - int c, force, needroot; + int c, needroot, i, force; size_t lineno; - + force = 0; needroot = 1; cfile = _PATH_NEWSYSLOGCONF; - gethostname(hostname, sizeof(hostname)); - hostname[sizeof(hostname) - 1] = '\0'; + gethostname(hostname, sizeof (hostname)); + hostname[sizeof (hostname) - 1] = '\0'; - /* Truncate domain */ + /* Truncate domain. */ if ((p = strchr(hostname, '.')) != NULL) *p = '\0'; - /* Parse command line options */ + /* Parse command line options. */ while ((c = getopt(argc, argv, "f:nrvF")) != -1) { switch (c) { case 'f': @@ -170,16 +173,32 @@ main(int argc, char **argv) } } - if (needroot && geteuid() != 0) + myeuid = geteuid(); + if (needroot && myeuid != 0) errx(EXIT_FAILURE, "must be run as root"); + argc -= optind; + argv += optind; + if (strcmp(cfile, "-") == 0) fd = stdin; else if ((fd = fopen(cfile, "rt")) == NULL) err(EXIT_FAILURE, "%s", cfile); - - for (lineno = 0; !parse(&log, fd, &lineno);) + + for (lineno = 0; !parse_cfgline(&log, fd, &lineno);) { + /* + * If specific log files were specified, touch only + * those. + */ + if (argc != 0) { + for (i = 0; i < argc; i++) + if (strcmp(log.logfile, argv[i]) == 0) + break; + if (i == argc) + continue; + } log_examine(&log, force); + } if (fd != stdin) fclose(fd); @@ -192,22 +211,23 @@ main(int argc, char **argv) * Parse a single line from the configuration file. */ int -parse(struct conf_entry *log, FILE *fd, size_t *_lineno) +parse_cfgline(struct conf_entry *log, FILE *fd, size_t *_lineno) { char *line, *q, **ap, *argv[10]; struct passwd *pw; struct group *gr; int nf, lineno, i; - memset(log, 0, sizeof(*log)); + memset(log, 0, sizeof (*log)); + /* Place the white-space separated fields into an array. */ if ((line = fparseln(fd, NULL, _lineno, NULL, 0)) == NULL) return (-1); lineno = (int)*_lineno; for (ap = argv, nf = 0; (*ap = strsep(&line, " \t")) != NULL;) if (**ap != '\0') { - if (++nf == sizeof(argv) / sizeof(argv[0])) { + if (++nf == sizeof (argv) / sizeof (argv[0])) { warnx("config line %d: too many fields", lineno); return (-1); @@ -221,52 +241,99 @@ parse(struct conf_entry *log, FILE *fd, size_t *_lineno) if (nf < 6) errx(EXIT_FAILURE, "config line %d: too few fields", lineno); + /* logfile_name */ ap = argv; - strlcpy(log->logfile, *ap++, sizeof(log->logfile)); + strlcpy(log->logfile, *ap++, sizeof (log->logfile)); + if (log->logfile[0] != '/') + errx(EXIT_FAILURE, + "config line %d: logfile must have a full path", lineno); + /* owner:group */ if (strchr(*ap, ':') != NULL || strchr(*ap, '.') != NULL) { - if (parseuserspec(*ap++, &pw, &gr)) { + if (parse_userspec(*ap++, &pw, &gr)) { warnx("config line %d: unknown user/group", lineno); return (-1); } - log->uid = pw->pw_uid; + + /* + * We may only change the file's group as non-root. + */ + if (myeuid != 0) { + if (pw->pw_uid != myeuid) + errx(EXIT_FAILURE, "config line %d: user:group " + "as non-root must match current user", + lineno); + log->uid = (uid_t)-1; + } else + log->uid = pw->pw_uid; log->gid = gr->gr_gid; if (nf < 7) errx(EXIT_FAILURE, "config line %d: too few fields", lineno); + } else if (myeuid != 0) { + log->uid = (uid_t)-1; + log->gid = getegid(); } + /* mode */ if (sscanf(*ap++, "%o", &i) != 1) { warnx("config line %d: bad permissions", lineno); return (-1); } log->mode = (mode_t)i; + /* count */ if (sscanf(*ap++, "%d", &log->numhist) != 1) { warnx("config line %d: bad log count", lineno); return (-1); } - if (isdigit(**ap)) - log->maxsize = atoi(*ap); - else if (**ap == '*') + /* size */ + if (isdigit(**ap)) { + log->maxsize = (int)strtol(*ap, &q, 0); + if (*q != '\0') { + warnx("config line %d: bad log size", lineno); + return (-1); + } + } 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++; + /* when */ + log->maxage = -1; + log->trimat = (time_t)-1; + q = *ap++; + + if (strcmp(q, "*") != 0) { + if (isdigit(*q)) + log->maxage = (int)strtol(q, &q, 10); + + /* + * One class of periodic interval specification can follow a + * maximum age specification. Handle it. + */ + if (*q == '@') { + log->trimat = parse_iso8601(q + 1); + if (log->trimat == (time_t)-1) { + warnx("config line %d: bad trim time", lineno); + return (-1); + } + } else if (*q == '$') { + if ((log->trimat = parse_dwm(q + 1)) == (time_t)-1) { + warnx("config line %d: bad trim time", lineno); + return (-1); + } + } else if (log->maxage == -1) { + warnx("config line %d: bad log age", lineno); + return (-1); + } + } + + /* flags */ log->flags = 0; for (q = *ap++; q != NULL && *q != '\0'; q++) { switch (tolower(*q)) { @@ -293,11 +360,13 @@ parse(struct conf_entry *log, FILE *fd, size_t *_lineno) } } + /* path_to_pidfile */ if (*ap != NULL && **ap == '/') - strlcpy(log->pidfile, *ap++, sizeof(log->pidfile)); + strlcpy(log->pidfile, *ap++, sizeof (log->pidfile)); else log->pidfile[0] = '\0'; + /* sigtype */ if (*ap != NULL && (log->signum = getsig(*ap++)) < 0) { warnx("config line %d: bad signal type", lineno); return (-1); @@ -316,16 +385,27 @@ log_examine(struct conf_entry *log, int force) { struct stat sb; size_t size; - int age; - char tmp[MAXPATHLEN]; + int age, trim; + char tmp[MAXPATHLEN], *reason; time_t now; + /* + * XXX We get here for comment/blank lines. I'm assuming this is + * intentional behaviour on the part of fparseln(). [ad] + */ if (log->logfile[0] == '\0') return; + now = time(NULL); + PRHDRINFO(("\n%s <%d%s>: ", log->logfile, log->numhist, (log->flags & CE_COMPRESS) != 0 ? "Z" : "")); + /* + * stat() the logfile. If it doesn't exist and the `c' flag has + * been specified, create it. If it doesn't exist and the `c' flag + * hasn't been specified, give up. + */ if (stat(log->logfile, &sb) < 0) { if (errno == ENOENT && (log->flags & CE_CREATE) != 0) { PRHDRINFO(("creating; ")); @@ -343,15 +423,18 @@ log_examine(struct conf_entry *log, int force) } else if (errno != 0) err(EXIT_FAILURE, "%s", log->logfile); } - + /* Size of the log file in kB. */ size = ((size_t)sb.st_blocks * S_BLKSIZE) >> 10; - now = time(NULL); - strlcpy(tmp, log->logfile, sizeof(tmp)); - strlcat(tmp, ".0", sizeof(tmp)); + /* + * Get the age (expressed in hours) of the current log file with + * respect to the newest historical log file. + */ + strlcpy(tmp, log->logfile, sizeof (tmp)); + strlcat(tmp, ".0", sizeof (tmp)); if (stat(tmp, &sb) < 0) { - strlcat(tmp, ".gz", sizeof(tmp)); + strlcat(tmp, ".gz", sizeof (tmp)); if (stat(tmp, &sb) < 0) age = -1; else @@ -359,25 +442,37 @@ log_examine(struct conf_entry *log, int force) } else age = (int)(now - sb.st_mtime + 1800) / 3600; - if (verbose) { - if (log->maxsize != (size_t)-1) - PRHDRINFO(("size (Kb): %lu [%lu] ", - (u_long)size, - (u_long)log->maxsize)); - if (log->maxage > 0) - PRHDRINFO(("age (hr): %d [%d] ", age, log->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. + * Examine the set of given trim conditions and if any one is met, + * trim the log. + * + * Note: if `maxage' or `trimat' is used as a trim condition, we + * need at least one historical log file to determine the `age' of + * the active log file. WRT `trimat', we will trim up to one hour + * after the specific trim time has passed - we need to know if + * we've trimmed to meet that condition with a previous invocation + * of newsyslog(8). */ - if ((log->maxage > 0 && (age >= log->maxage || age < 0)) || - size >= log->maxsize || force) { - PRHDRINFO(("--> trim log\n")); + if (log->maxage >= 0 && (age >= log->maxage || age < 0)) { + trim = 1; + reason = "log age > interval"; + } else if (size >= log->maxsize) { + trim = 1; + reason = "log size > size"; + } else if (log->trimat != (time_t)-1 && now >= log->trimat && + age > 1 && difftime(now, log->trimat) < 60 * 60) { + trim = 1; + reason = "specific trim time"; + } else { + trim = force; + reason = "trim forced"; + } + + if (trim) { + PRHDRINFO(("--> trim log (%s)\n", reason)); log_trim(log); } else - PRHDRINFO(("--> skip log\n")); + PRHDRINFO(("--> skip log (trim conditions not met)\n")); } /* @@ -391,25 +486,25 @@ log_trim(struct conf_entry *log) struct stat st; pid_t pid; - /* Remove oldest historical log */ - snprintf(file1, sizeof(file1), "%s.%d", log->logfile, log->numhist - 1); + /* Remove oldest historical log. */ + snprintf(file1, sizeof (file1), "%s.%d", log->logfile, log->numhist - 1); PRINFO(("rm -f %s\n", file1)); if (!noaction) unlink(file1); - strlcat(file1, ".gz", sizeof(file1)); + strlcat(file1, ".gz", sizeof (file1)); PRINFO(("rm -f %s\n", file1)); if (!noaction) unlink(file1); - /* Move down log files */ + /* Move down log files. */ for (i = log->numhist - 1; i != 0; i--) { - snprintf(file1, sizeof(file1), "%s.%d", log->logfile, i - 1); - snprintf(file2, sizeof(file2), "%s.%d", log->logfile, i); + snprintf(file1, sizeof (file1), "%s.%d", log->logfile, i - 1); + snprintf(file2, sizeof (file2), "%s.%d", log->logfile, i); if (lstat(file1, &st) != 0) { - strlcat(file1, ".gz", sizeof(file1)); - strlcat(file2, ".gz", sizeof(file2)); + strlcat(file1, ".gz", sizeof (file1)); + strlcat(file2, ".gz", sizeof (file2)); if (lstat(file1, &st) != 0) continue; } @@ -422,11 +517,11 @@ log_trim(struct conf_entry *log) if (!noaction) if (chmod(file2, log->mode)) err(EXIT_FAILURE, "%s", file2); - PRINFO(("chown %d:%d %s\n", log->uid, log->gid, file2)); + PRINFO(("chown %d:%d %s\n", log->uid, log->gid, + file2)); if (!noaction) if (chown(file2, log->uid, log->gid)) - err(EXIT_FAILURE, "%s", file2); - + err(EXIT_FAILURE, "%s", file2); } /* @@ -436,10 +531,10 @@ log_trim(struct conf_entry *log) * preserves file ownership and file mode. */ for (i = (log->flags & CE_PLAIN0) != 0; i < log->numhist; i++) { - snprintf(file1, sizeof(file1), "%s.%d", log->logfile, i); + snprintf(file1, sizeof (file1), "%s.%d", log->logfile, i); if (lstat(file1, &st) != 0) continue; - snprintf(file2, sizeof(file2), "%s.gz", file1); + snprintf(file2, sizeof (file2), "%s.gz", file1); if (lstat(file2, &st) == 0) continue; log_compress(log, file1); @@ -447,13 +542,14 @@ log_trim(struct conf_entry *log) log_trimmed(log); + /* Create the historical log file if we're maintaining history. */ if (log->numhist == 0) { PRINFO(("rm -f %s\n", log->logfile)); if (!noaction) if (unlink(log->logfile)) err(EXIT_FAILURE, "%s", log->logfile); } else { - snprintf(file1, sizeof(file1), "%s.0", log->logfile); + snprintf(file1, sizeof (file1), "%s.0", log->logfile); PRINFO(("mv %s %s\n", log->logfile, file1)); if (!noaction) if (rename(log->logfile, file1)) @@ -464,11 +560,13 @@ log_trim(struct conf_entry *log) log_create(log); log_trimmed(log); + /* Set the correct permissions on the log. */ PRINFO(("chmod %o %s\n", log->mode, log->logfile)); if (!noaction) if (chmod(log->logfile, log->mode)) err(EXIT_FAILURE, "%s", log->logfile); + /* Do we need to signal a daemon? */ if ((log->flags & CE_NOSIGNAL) == 0) { if (log->pidfile[0] != '\0') pid = readpidfile(log->pidfile); @@ -476,16 +574,17 @@ log_trim(struct conf_entry *log) pid = readpidfile(_PATH_SYSLOGDPID); if (pid != (pid_t)-1) { - PRINFO(("kill -%s %lu\n", sys_signame[log->signum], - (u_long)pid)); + PRINFO(("kill -%s %lu\n", + sys_signame[log->signum], (u_long)pid)); if (!noaction) if (kill(pid, log->signum)) warn("kill"); } } + /* If the newest historical log is to be compressed, do it here. */ if ((log->flags & (CE_PLAIN0 | CE_COMPRESS)) == CE_COMPRESS) { - snprintf(file1, sizeof(file1), "%s.0", log->logfile); + snprintf(file1, sizeof (file1), "%s.0", log->logfile); log_compress(log, file1); } } @@ -537,12 +636,14 @@ log_create(struct conf_entry *log) } /* - * Compress a log file. This routine takes an additional string argument: - * it is also used to compress historical log files. + * Fork off gzip(1) to compress a log file. This routine takes an + * additional string argument (the name of the file to compress): it is also + * used to compress historical log files other than the newest. */ void log_compress(struct conf_entry *log, const char *fn) { + char tmp[MAXPATHLEN]; pid_t pid; PRINFO(("gzip %s\n", fn)); @@ -554,6 +655,12 @@ log_compress(struct conf_entry *log, const char *fn) err(EXIT_FAILURE, _PATH_GZIP); } } + + snprintf(tmp, sizeof (tmp), "%s.gz", fn); + PRINFO(("chown %d:%d %s\n", log->uid, log->gid, tmp)); + if (!noaction) + if (!chown(tmp, log->uid, log->gid)) + err(EXIT_FAILURE, "%s", tmp); } /* @@ -563,7 +670,8 @@ void usage(void) { - fprintf(stderr, "usage: newsyslog [-Frv] [-f config-file]\n"); + fprintf(stderr, + "usage: newsyslog [-nrvF] [-f config-file] [file ...]\n"); exit(EXIT_FAILURE); } @@ -616,9 +724,9 @@ readpidfile(const char *file) #ifdef notyet if (file[0] != '/') - snprintf(tmp, sizeof(tmp), "%s%s", _PATH_VARRUN, file); + snprintf(tmp, sizeof (tmp), "%s%s", _PATH_VARRUN, file); else - strlcpy(tmp, file, sizeof(tmp)); + strlcpy(tmp, file, sizeof (tmp)); #endif if ((fd = fopen(file, "rt")) == NULL) { @@ -626,8 +734,8 @@ readpidfile(const char *file) return (-1); } - if (fgets(line, sizeof(line) - 1, fd) != NULL) { - line[sizeof(line) - 1] = '\0'; + if (fgets(line, sizeof (line) - 1, fd) != NULL) { + line[sizeof (line) - 1] = '\0'; pid = (pid_t)strtol(line, NULL, 0); } else { warnx("unable to read %s", file); @@ -641,14 +749,14 @@ readpidfile(const char *file) /* * Parse a user:group specification. * - * XXX This is over the top for newsyslog(1). It should be moved to libutil. + * XXX This is over the top for newsyslog(8). It should be moved to libutil. */ int -parseuserspec(const char *name, struct passwd **pw, struct group **gr) +parse_userspec(const char *name, struct passwd **pw, struct group **gr) { char buf[MAXLOGNAME * 2 + 2], *group; - strlcpy(buf, name, sizeof(buf)); + strlcpy(buf, name, sizeof (buf)); *gr = NULL; /* @@ -687,3 +795,206 @@ parseuserspec(const char *name, struct passwd **pw, struct group **gr) return (*pw != NULL && *gr != NULL ? 0 : -1); } + +/* + * Parse a cyclic time specification, the format is as follows: + * + * [Dhh] or [Wd[Dhh]] or [Mdd[Dhh]] + * + * to rotate a log file cyclic at + * + * - every day (D) within a specific hour (hh) (hh = 0...23) + * - once a week (W) at a specific day (d) OR (d = 0..6, 0 = Sunday) + * - once a month (M) at a specific day (d) (d = 1..31,l|L) + * + * We don't accept a timezone specification; missing fields are defaulted to + * the current date but time zero. + */ +time_t +parse_dwm(char *s) +{ + char *t; + struct tm tm, *tmp; + u_long ul; + time_t now; + static int mtab[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + int wmseen, dseen, nd, save; + + wmseen = 0; + dseen = 0; + + now = time(NULL); + tmp = localtime(&now); + tm = *tmp; + + /* Set no. of days per month */ + nd = mtab[tm.tm_mon]; + + if (tm.tm_mon == 1 && + ((tm.tm_year + 1900) % 4 == 0) && + ((tm.tm_year + 1900) % 100 != 0) && + ((tm.tm_year + 1900) % 400 == 0)) + nd++; /* leap year, 29 days in february */ + tm.tm_hour = tm.tm_min = tm.tm_sec = 0; + + for (;;) { + switch (*s) { + case 'D': + if (dseen) + return ((time_t)-1); + dseen++; + s++; + ul = strtoul(s, &t, 10); + if (ul < 0 || ul > 23) + return ((time_t)-1); + tm.tm_hour = ul; + break; + + case 'W': + if (wmseen) + return (-1); + wmseen++; + s++; + ul = strtoul(s, &t, 10); + if (ul < 0 || ul > 6) + return (-1); + if (ul != tm.tm_wday) { + if (ul < tm.tm_wday) { + save = 6 - tm.tm_wday; + save += (ul + 1); + } else + save = ul - tm.tm_wday; + tm.tm_mday += save; + + if (tm.tm_mday > nd) { + tm.tm_mon++; + tm.tm_mday = tm.tm_mday - nd; + } + } + break; + + case 'M': + if (wmseen) + return (-1); + wmseen++; + s++; + if (tolower(*s) == 'l') { + tm.tm_mday = nd; + s++; + t = s; + } else { + ul = strtoul(s, &t, 10); + if (ul < 1 || ul > 31) + return (-1); + + if (ul > nd) + return (-1); + tm.tm_mday = ul; + } + break; + + default: + return (-1); + break; + } + + if (*t == '\0' || isspace(*t)) + break; + else + s = t; + } + + return (mktime(&tm)); +} + +/* + * Parse a limited subset of ISO 8601. The specific format is as follows: + * + * [CC[YY[MM[DD]]]][THH[MM[SS]]] (where `T' is the literal letter) + * + * We don't accept a timezone specification; missing fields (including + * timezone) are defaulted to the current date but time zero. + */ +time_t +parse_iso8601(char *s) +{ + char *t; + struct tm tm, *tmp; + u_long ul; + time_t now; + + now = time(NULL); + tmp = localtime(&now); + tm = *tmp; + + tm.tm_hour = tm.tm_min = tm.tm_sec = 0; + + ul = strtoul(s, &t, 10); + if (*t != '\0' && *t != 'T') + return ((time_t)-1); + + /* + * Now t points either to the end of the string (if no time was + * provided) or to the letter `T' which separates date and time in + * ISO 8601. The pointer arithmetic is the same for either case. + */ + switch (t - s) { + case 8: + tm.tm_year = ((ul / 1000000) - 19) * 100; + ul = ul % 1000000; + /* FALLTHROUGH */ + case 6: + tm.tm_year = tm.tm_year - (tm.tm_year % 100); + tm.tm_year += ul / 10000; + ul = ul % 10000; + /* FALLTHROUGH */ + case 4: + tm.tm_mon = (ul / 100) - 1; + ul = ul % 100; + /* FALLTHROUGH */ + case 2: + tm.tm_mday = ul; + /* FALLTHROUGH */ + case 0: + break; + default: + return ((time_t)-1); + } + + /* Sanity check */ + if (tm.tm_year < 70 || tm.tm_mon < 0 || tm.tm_mon > 12 || + tm.tm_mday < 1 || tm.tm_mday > 31) + return ((time_t)-1); + + if (*t != '\0') { + s = ++t; + ul = strtoul(s, &t, 10); + if (*t != '\0' && !isspace(*t)) + return ((time_t)-1); + + switch (t - s) { + case 6: + tm.tm_sec = ul % 100; + ul /= 100; + /* FALLTHROUGH */ + case 4: + tm.tm_min = ul % 100; + ul /= 100; + /* FALLTHROUGH */ + case 2: + tm.tm_hour = ul; + /* FALLTHROUGH */ + case 0: + break; + default: + return ((time_t)-1); + } + + /* Sanity check */ + if (tm.tm_sec < 0 || tm.tm_sec > 60 || tm.tm_min < 0 || + tm.tm_min > 59 || tm.tm_hour < 0 || tm.tm_hour > 23) + return ((time_t)-1); + } + + return (mktime(&tm)); +}