More work on newsyslog(8):

- Add some comments. It might be getting a little unclear what's going on.
- Make it useful as non-root.

Also, some new features taken from FreeBSD. The last two are not well
tested yet:

- Specifing log files on the command line limits operation to those files.
- Add cyclic time specification for periodic trimming.
- Add restricted ISO-8601 time specification for periodic trimming.
This commit is contained in:
ad 2000-07-18 15:59:24 +00:00
parent 3ac8ce9b63
commit 677a105692
2 changed files with 577 additions and 114 deletions

View File

@ -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 <ad@NetBSD.org>
.\" 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

View File

@ -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 <ad@NetBSD.org>
@ -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 <sys/cdefs.h>
#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 <sys/types.h>
@ -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));
}