aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDuncaen <mail@duncano.de>2017-03-06 01:09:32 +0100
committerDuncaen <mail@duncano.de>2017-03-06 01:09:32 +0100
commitc7d6aaddbfe478bd8db6ee01941584d529c2feeb (patch)
treedb9984973a90fef9f466b0284c6f885f30527035
parentca9b7c80fe6e5194f98f5faa28a742724d990e08 (diff)
downloadlobase-c7d6aaddbfe478bd8db6ee01941584d529c2feeb.tar.gz
usr.bin/newsyslog: import
-rw-r--r--usr.bin/newsyslog/Makefile10
-rw-r--r--usr.bin/newsyslog/newsyslog.8441
-rw-r--r--usr.bin/newsyslog/newsyslog.c1360
3 files changed, 1811 insertions, 0 deletions
diff --git a/usr.bin/newsyslog/Makefile b/usr.bin/newsyslog/Makefile
new file mode 100644
index 0000000..5793a90
--- /dev/null
+++ b/usr.bin/newsyslog/Makefile
@@ -0,0 +1,10 @@
+# $OpenBSD: Makefile,v 1.7 2016/06/01 16:57:48 tedu Exp $
+
+PROG= newsyslog
+CPPFLAGS+=-D_GNU_SOURCE
+
+BINOWN= root
+
+MAN= newsyslog.8
+
+include bsd.prog.mk
diff --git a/usr.bin/newsyslog/newsyslog.8 b/usr.bin/newsyslog/newsyslog.8
new file mode 100644
index 0000000..2b6c4a3
--- /dev/null
+++ b/usr.bin/newsyslog/newsyslog.8
@@ -0,0 +1,441 @@
+.\" $OpenBSD: newsyslog.8,v 1.53 2015/11/14 01:22:04 jmc Exp $
+.\"
+.\" Copyright (c) 1997, Jason Downs. 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(S) ``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(S) 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: @(#)newsyslog.8
+.\"
+.\" 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.
+.\"
+.Dd $Mdocdate: November 14 2015 $
+.Dt NEWSYSLOG 8
+.Os
+.Sh NAME
+.Nm newsyslog ,
+.Nm newsyslog.conf
+.Nd rotate log files
+.Sh SYNOPSIS
+.Nm newsyslog
+.Op Fl Fmnrv
+.Op Fl a Ar directory
+.Op Fl f Ar config_file
+.Op Ar log ...
+.Sh DESCRIPTION
+The
+.Nm
+utility rotates log files when they exceed a configurable size or age.
+The
+.Ar log
+file is renamed to
+.Pa log.0
+and an empty file is created in its place.
+An archive of older logs may be kept:
+in order of increasing age, these files are named
+.Pa log.1 ,
+.Pa log.2 ,
+and so on.
+When their number exceeds a given limit, the oldest is removed.
+The archived logs may also be compressed.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl a Ar directory
+Specify a
+.Ar directory
+into which archived log files will be written.
+If
+.Ar directory
+is a relative path, it is appended to the parent directory
+of each log and the archived log is stored in the result.
+If an absolute path is given, all archived logs are stored in the given
+.Ar directory .
+If
+.Ar directory
+does not exist for a specified log, it is ignored for that entry and
+the log is rotated as if the
+.Fl a
+option was not specified.
+.It Fl F
+Force
+.Nm
+to trim logs regardless of the size and/or age requirements specified in
+.Pa /etc/newsyslog.conf .
+This option may be combined with the
+.Fl n
+or
+.Fl v
+flags to aid in debugging problems with
+.Pa /etc/newsyslog.conf .
+.It Fl f Ar config_file
+Use
+.Ar config_file
+instead of
+.Pa /etc/newsyslog.conf
+for the configuration file.
+.It Fl m
+Monitoring mode; only entries marked with an
+.Sq M
+in flags are processed.
+For each log file being monitored, any log output since the last time
+.Nm
+was run with the
+.Fl m
+flag is mailed to the user listed in the monitor notification section.
+.It Fl n
+Do not trim the logs, but instead print out what would be done if this option
+were not specified.
+.It Fl r
+Removes the restriction that
+.Nm
+must be running as root.
+Note that in this mode
+.Nm
+will not be able to send a
+.Dv SIGHUP
+signal to
+.Xr syslogd 8 .
+.It Fl v
+Place
+.Nm newsyslog
+in verbose mode.
+In this mode it will print out each log and its
+reasons for either trimming that log or skipping it.
+.El
+.Pp
+In the default system configuration,
+.Nm
+is run by
+.Xr cron 8 ,
+but it may also be run manually.
+If one or more
+.Ar log
+files are specified on the command line, only the specified files are
+rotated.
+Note that each
+.Ar log
+specified must have an entry in
+.Pa /etc/newsyslog.conf .
+.Pp
+A log can be archived because of two reasons:
+The log file can have
+grown bigger than a preset size in kilobytes, or a preset number of
+hours may have elapsed since the last log archive.
+The granularity of
+.Nm
+is dependent on how often it is scheduled to run in
+.Xr cron 8 .
+Since the program is quite fast, it may be scheduled to run every hour
+without any ill effects.
+.Pp
+When starting up,
+.Nm
+reads in a configuration file to determine which logs should be looked
+at.
+By default, this configuration file is
+.Pa /etc/newsyslog.conf .
+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 up to three optional fields, with
+whitespace separating each field.
+Blank lines or lines beginning with a hash mark
+.Pq Ql #
+are ignored.
+The fields of the configuration file are as
+follows:
+.Bl -tag -width XXXXXXXXXXXXXXXX
+.It Ar logfile_name
+The full pathname of the system log file to be archived.
+.It Ar owner:group
+This optional field specifies the owner and group for the archive file.
+The
+.Ql \&:
+is essential, even if the
+.Ar owner
+or
+.Ar group
+field is left blank.
+The fields may be numeric, or a name which is looked up
+in the system password and group databases.
+For backwards compatibility, a
+.Ql \&.
+may be used instead of a
+.Ql \&: .
+If either
+.Ar owner
+or
+.Ar group
+is not specified, the owner and/or group of the existing log file is used.
+.It Ar mode
+File mode (in octal) to use for created log files and archives.
+.It Ar count
+The number of archives to be kept besides the log file itself.
+.It Ar size
+When the size of the log file (in kilobytes) reaches this point, the log
+file is trimmed as described above.
+If this field is replaced by an
+.Ql * ,
+or set to
+.Ql 0 ,
+then the size of
+the log file is not taken into account when determining when to trim the
+log file.
+By default, files smaller than 256 bytes are not rotated unless the
+.Sq B
+(binary) flag is set or the
+.Fl F
+option is specified.
+This prevents
+.Nm
+from rotating files consisting solely of a message indicating
+that the log file has been turned over.
+.It Ar when
+The
+.Ar when
+field can consist of an interval, a specific time, or both.
+If the
+.Ar when
+field consists of 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, possibly followed
+by an
+.So Li \&@ Sc Ns -sign
+and a time in a restricted
+.St -iso8601
+format or by a
+.So Li \&$ Sc Ns -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 the specification of a time zone.
+There is little point in specifying an explicit minutes or seconds
+component in the current implementation, since the only comparison is
+.Sq within the hour .
+.Pp
+.Sy ISO 8601 restricted time format:
+The lead-in character for a restricted
+.St -iso8601
+time is an
+.So Li \&@ Sc Ns -sign .
+The particular format of the time in restricted
+.St -iso8601
+is:
+.Sm off
+.Oo Oo Oo Oo Oo
+.Va \&cc Oc
+.Va \&yy Oc
+.Va \&mm Oc
+.Va \&dd Oc
+.Oo Va \&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.
+For example, if today is January 22, 1999, the following date specifications
+are all equivalent:
+.Pp
+.Bl -item -compact -offset indent
+.It
+.Ql 19990122T000000
+.It
+.Ql 990122T000000
+.It
+.Ql 0122T000000
+.It
+.Ql 22T000000
+.It
+.Ql T000000
+.It
+.Ql T0000
+.It
+.Ql T00
+.It
+.Ql 22T
+.It
+.Ql \&T
+.It
+.Ql \&
+.El
+.Pp
+.Sy Day, week and month time format:
+The lead-in character for day, week and month specification is a
+dollar sign
+.Pq $ .
+The particular format of day, week and month specification is:
+.Op Li D Ns Ar HH ,
+.Op Li W Ns Ar w Ns Op Li D Ns Ar HH ,
+and
+.Op Li M Ns Ar dd Ns Op Li D Ns Ar HH ,
+respectively.
+Optional time fields default to midnight.
+The ranges for day and hour specifications 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
+.Sy Some examples:
+.Bl -tag -width Ds -compact -offset indent
+.It Li $D0
+rotate every night at midnight
+(same as
+.Li @T00 )
+.It Li $D23
+rotate every day at 23:00 hr
+(same as
+.Li @T23 )
+.It Li $W0D23
+rotate every week on Sunday at 23:00 hr
+.It Li $W5D16
+rotate every week on Friday at 16:00 hr
+.It Li $M1D0
+rotate on the first day of every month at midnight
+(i.e., the start of the day; same as
+.Li @01T00 )
+.It Li $M5D6
+rotate on every 5th day of the month at 6:00 hr
+(same as
+.Li @05T06 )
+.El
+.It Ar flags
+The optional
+.Ar flags
+field specifies if the archives should have any special processing
+done to the archived log files.
+The
+.Sq Z
+flag will make the archive
+files compressed to save space using
+.Xr gzip 1
+or
+.Xr compress 1 ,
+depending on compilation options.
+The
+.Sq B
+flag means that the file is a
+binary file, and so the ASCII message which
+.Nm
+inserts to indicate the fact that the logs have been turned over
+should not be included.
+The
+.Sq M
+flag marks this entry as a monitored
+log file.
+The
+.Sq F
+flag specifies that symbolic links should be followed.
+.It Ar monitor
+Specify the username (or email address) that should receive notification
+messages if this is a monitored log file.
+Notification messages are sent as email; the operator
+deserves what they get if they mark the mail
+log file as monitored.
+This field is only valid when the
+.Sq M
+flag is set.
+.It Ar pid_file
+This optional field specifies a file containing the PID of a process to send a
+signal (usually
+.Dv SIGHUP )
+to instead of
+.Pa /var/run/syslog.pid .
+.It Ar signal
+Specify the signal to send to the process instead of
+.Dv SIGHUP .
+Signal names
+must start with
+.Dq SIG
+and be the signal name, not the number, e.g.,
+.Dv SIGUSR1 .
+.It Ar command
+This optional field specifies a command to run instead of sending a signal
+to the process.
+The command must be enclosed in double quotes
+.Pq Ql \&" .
+The empty string,
+.Ql \&"\&" ,
+can be used to prevent
+.Nm
+from sending a signal or running a command.
+You cannot specify both a command and a PID file.
+.Em NOTE:
+If you specify a command to be run,
+.Nm
+will not send a
+.Dv SIGHUP to
+.Xr syslogd 8 .
+.El
+.Sh FILES
+.Bl -tag -width /etc/newsyslog.conf
+.It Pa /etc/newsyslog.conf
+default configuration file
+.El
+.Sh SEE ALSO
+.Xr compress 1 ,
+.Xr gzip 1 ,
+.Xr syslog 3 ,
+.Xr syslogd 8
+.Sh AUTHORS
+.An Theodore Ts'o ,
+MIT Project Athena
+.br
+Copyright 1987, Massachusetts Institute of Technology
diff --git a/usr.bin/newsyslog/newsyslog.c b/usr.bin/newsyslog/newsyslog.c
new file mode 100644
index 0000000..0120e56
--- /dev/null
+++ b/usr.bin/newsyslog/newsyslog.c
@@ -0,0 +1,1360 @@
+/* $OpenBSD: newsyslog.c,v 1.101 2016/06/01 16:57:48 tedu Exp $ */
+
+/*
+ * Copyright (c) 1999, 2002, 2003 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Sponsored in part by the Defense Advanced Research Projects
+ * Agency (DARPA) and Air Force Research Laboratory, Air Force
+ * Materiel Command, USAF, under agreement number F39502-99-1-0512.
+ */
+
+/*
+ * Copyright (c) 1997, Jason Downs. 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(S) ``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(S) 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.
+ */
+
+/*
+ * newsyslog - roll over selected logs at the appropriate time,
+ * keeping the specified number of backup files around.
+ *
+ */
+
+#define CONF "/etc/newsyslog.conf"
+#define PIDFILE "/var/run/syslog.pid"
+#define COMPRESS "/usr/bin/gzip"
+#define COMPRESS_POSTFIX ".gz"
+#define STATS_DIR "/var/run"
+#define SENDMAIL "/usr/sbin/sendmail"
+
+#include <sys/param.h> /* DEV_BSIZE */
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <grp.h>
+#include <limits.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#define CE_ROTATED 0x01 /* Log file has been rotated */
+#define CE_COMPACT 0x02 /* Compact the archived log files */
+#define CE_BINARY 0x04 /* Logfile is in binary, don't add */
+ /* status messages */
+#define CE_MONITOR 0x08 /* Monitor for changes */
+#define CE_FOLLOW 0x10 /* Follow symbolic links */
+#define CE_TRIMAT 0x20 /* Trim at a specific time */
+
+#define MIN_PID 2 /* Don't touch pids lower than this */
+#define MIN_SIZE 256 /* Don't rotate if smaller (in bytes) */
+
+#define DPRINTF(x) do { if (verbose) printf x ; } while (0)
+
+struct conf_entry {
+ char *log; /* Name of the log */
+ char *logbase; /* Basename of the log */
+ char *backdir; /* Directory in which to store backups */
+ uid_t uid; /* Owner of log */
+ gid_t gid; /* Group of log */
+ int numlogs; /* Number of logs to keep */
+ off_t size; /* Size cutoff to trigger trimming the log */
+ int hours; /* Hours between log trimming */
+ time_t trim_at; /* Specific time at which to do trimming */
+ mode_t permissions; /* File permissions on the log */
+ int signal; /* Signal to send (defaults to SIGHUP) */
+ int flags; /* Flags (CE_COMPACT & CE_BINARY) */
+ char *whom; /* Whom to notify if logfile changes */
+ char *pidfile; /* Path to file containing pid to signal */
+ char *runcmd; /* Command to run instead of sending a signal */
+ struct conf_entry *next; /* Linked list pointer */
+};
+
+struct pidinfo {
+ char *file;
+ int signal;
+};
+
+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 monitormode = 0; /* Don't do monitoring by default */
+int force = 0; /* Force the logs to be rotated */
+char *conf = CONF; /* Configuration file to use */
+time_t timenow;
+char hostname[HOST_NAME_MAX+1]; /* Hostname */
+char *daytime; /* timenow in human readable form */
+char *arcdir; /* Dir to put archives in (if it exists) */
+
+FILE *openmail(void);
+char *lstat_log(char *, size_t, int);
+char *missing_field(char *, char *, int);
+char *sob(char *);
+char *son(char *);
+int age_old_log(struct conf_entry *);
+int domonitor(struct conf_entry *);
+int isnumberstr(char *);
+int log_trim(char *);
+int movefile(char *, char *, uid_t, gid_t, mode_t);
+int stat_suffix(char *, size_t, char *, struct stat *,
+ int (*)(const char *, struct stat *));
+off_t sizefile(struct stat *);
+struct conf_entry *
+ parse_file(int *);
+time_t parse8601(char *);
+time_t parseDWM(char *);
+void child_killer(int);
+void compress_log(struct conf_entry *);
+void do_entry(struct conf_entry *);
+void dotrim(struct conf_entry *);
+void rotate(struct conf_entry *, const char *);
+void parse_args(int, char **);
+void run_command(char *);
+void send_signal(char *, int);
+void usage(void);
+
+int
+main(int argc, char **argv)
+{
+ struct conf_entry *p, *q, *x, *y;
+ struct pidinfo *pidlist, *pl;
+ int status, listlen;
+ char **av;
+
+ parse_args(argc, argv);
+ argc -= optind;
+ argv += optind;
+
+ if (needroot && getuid() && geteuid())
+ errx(1, "You must be root.");
+
+ p = parse_file(&listlen);
+ if (argc > 0) {
+ /* Only rotate specified files. */
+ x = y = NULL;
+ listlen = 0;
+ for (av = argv; *av; av++) {
+ for (q = p; q; q = q->next)
+ if (strcmp(*av, q->log) == 0) {
+ if (x == NULL)
+ x = y = q;
+ else {
+ y->next = q;
+ y = q;
+ }
+ listlen++;
+ break;
+ }
+ if (q == NULL)
+ warnx("%s: %s not found", conf, *av);
+ }
+ if (x == NULL)
+ errx(1, "%s: no specified log files", conf);
+ y->next = NULL;
+ p = x;
+ }
+
+ pidlist = calloc(listlen + 1, sizeof(struct pidinfo));
+ if (pidlist == NULL)
+ err(1, "calloc");
+
+ signal(SIGCHLD, child_killer);
+
+ /* Step 1, rotate all log files */
+ for (q = p; q; q = q->next)
+ do_entry(q);
+
+ /* Step 2, make a list of unique pid files */
+ for (q = p, pl = pidlist; q; ) {
+ if (q->flags & CE_ROTATED) {
+ struct pidinfo *pltmp;
+
+ for (pltmp = pidlist; pltmp < pl; pltmp++) {
+ if ((q->pidfile && pltmp->file &&
+ strcmp(pltmp->file, q->pidfile) == 0 &&
+ pltmp->signal == q->signal) ||
+ (q->runcmd && pltmp->file &&
+ strcmp(q->runcmd, pltmp->file) == 0))
+ break;
+ }
+ if (pltmp == pl) { /* unique entry */
+ if (q->runcmd) {
+ pl->file = q->runcmd;
+ pl->signal = -1;
+ } else {
+ pl->file = q->pidfile;
+ pl->signal = q->signal;
+ }
+ pl++;
+ }
+ }
+ q = q->next;
+ }
+
+ /* Step 3, send a signal or run a command */
+ for (pl--; pl >= pidlist; pl--) {
+ if (pl->file != NULL) {
+ if (pl->signal == -1)
+ run_command(pl->file);
+ else
+ send_signal(pl->file, pl->signal);
+ }
+ }
+ if (!noaction)
+ sleep(5);
+
+ /* Step 4, compress the log.0 file if configured to do so and free */
+ while (p) {
+ if ((p->flags & CE_COMPACT) && (p->flags & CE_ROTATED) &&
+ p->numlogs > 0)
+ compress_log(p);
+ q = p;
+ p = p->next;
+ free(q);
+ }
+
+ /* Wait for children to finish, then exit */
+ while (waitpid(-1, &status, 0) != -1)
+ ;
+ exit(0);
+}
+
+void
+do_entry(struct conf_entry *ent)
+{
+ struct stat sb;
+ int modhours;
+ off_t size;
+
+ if (lstat(ent->log, &sb) != 0)
+ return;
+ if (!S_ISREG(sb.st_mode) &&
+ (!S_ISLNK(sb.st_mode) || !(ent->flags & CE_FOLLOW))) {
+ DPRINTF(("--> not a regular file, skipping\n"));
+ return;
+ }
+ if (S_ISLNK(sb.st_mode) && stat(ent->log, &sb) != 0) {
+ DPRINTF(("--> link target does not exist, skipping\n"));
+ return;
+ }
+ if (ent->uid == (uid_t)-1)
+ ent->uid = sb.st_uid;
+ if (ent->gid == (gid_t)-1)
+ ent->gid = sb.st_gid;
+
+ DPRINTF(("%s <%d%s%s%s%s>: ", ent->log, ent->numlogs,
+ (ent->flags & CE_COMPACT) ? "Z" : "",
+ (ent->flags & CE_BINARY) ? "B" : "",
+ (ent->flags & CE_FOLLOW) ? "F" : "",
+ (ent->flags & CE_MONITOR) && monitormode ? "M" : ""));
+ size = sizefile(&sb);
+ modhours = age_old_log(ent);
+ if (ent->flags & CE_TRIMAT && !force) {
+ if (timenow < ent->trim_at ||
+ difftime(timenow, ent->trim_at) >= 60 * 60) {
+ DPRINTF(("--> will trim at %s",
+ ctime(&ent->trim_at)));
+ return;
+ } else if (ent->hours <= 0) {
+ DPRINTF(("--> time is up\n"));
+ }
+ }
+ if (ent->size > 0)
+ DPRINTF(("size (KB): %.2f [%d] ", size / 1024.0,
+ (int)(ent->size / 1024)));
+ if (ent->hours > 0)
+ DPRINTF(("age (hr): %d [%d] ", modhours, ent->hours));
+ if (monitormode && (ent->flags & CE_MONITOR) && domonitor(ent))
+ DPRINTF(("--> monitored\n"));
+ else if (!monitormode &&
+ (force || (ent->size > 0 && size >= ent->size) ||
+ (ent->hours <= 0 && (ent->flags & CE_TRIMAT)) ||
+ (ent->hours > 0 && (modhours >= ent->hours || modhours < 0)
+ && ((ent->flags & CE_BINARY) || size >= MIN_SIZE)))) {
+ DPRINTF(("--> trimming log....\n"));
+ if (noaction && !verbose)
+ printf("%s <%d%s%s%s>\n", ent->log,
+ ent->numlogs,
+ (ent->flags & CE_COMPACT) ? "Z" : "",
+ (ent->flags & CE_BINARY) ? "B" : "",
+ (ent->flags & CE_FOLLOW) ? "F" : "");
+ dotrim(ent);
+ ent->flags |= CE_ROTATED;
+ } else
+ DPRINTF(("--> skipping\n"));
+}
+
+/* Run the specified command */
+void
+run_command(char *cmd)
+{
+ if (noaction)
+ (void)printf("run %s\n", cmd);
+ else
+ system(cmd);
+}
+
+/* Send a signal to the pid specified by pidfile */
+void
+send_signal(char *pidfile, int signal)
+{
+ char line[BUFSIZ], *ep, *err;
+ pid_t pid;
+ long lval;
+ FILE *f;
+
+ if ((f = fopen(pidfile, "r")) == NULL) {
+ warn("can't open %s", pidfile);
+ return;
+ }
+
+ pid = 0;
+ errno = 0;
+ err = NULL;
+ if (fgets(line, sizeof(line), f)) {
+ lval = strtol(line, &ep, 10);
+ if (line[0] == '\0' || (*ep != '\0' && *ep != '\n'))
+ err = "invalid number in";
+ else if (lval < 0 || (errno == ERANGE && lval == LONG_MAX))
+ err = "out of range number in";
+ else if (lval == 0)
+ err = "no number in";
+ else if (lval < MIN_PID)
+ err = "preposterous process number in";
+ else
+ pid = (pid_t)lval;
+ } else {
+ if (errno == 0)
+ err = "empty";
+ else
+ err = "error reading";
+ }
+ (void)fclose(f);
+
+ if (err)
+ warnx("%s pid file: %s", err, pidfile);
+ else if (noaction)
+ (void)printf("kill -%s %ld\n", sys_signame[signal], (long)pid);
+ else if (kill(pid, signal))
+ warnx("warning - could not send SIG%s to PID from pid file %s",
+ sys_signame[signal], pidfile);
+}
+
+void
+parse_args(int argc, char **argv)
+{
+ char *p;
+ int ch;
+
+ timenow = time(NULL);
+ daytime = ctime(&timenow) + 4;
+ daytime[15] = '\0';
+
+ /* Let's get our hostname */
+ (void)gethostname(hostname, sizeof(hostname));
+
+ /* Truncate domain */
+ if ((p = strchr(hostname, '.')) != NULL)
+ *p = '\0';
+
+ while ((ch = getopt(argc, argv, "Fmnrva:f:")) != -1) {
+ switch (ch) {
+ case 'a':
+ arcdir = optarg;
+ break;
+ case 'n':
+ noaction = 1; /* This implies needroot as off */
+ /* fall through */
+ case 'r':
+ needroot = 0;
+ break;
+ case 'v':
+ verbose = 1;
+ break;
+ case 'f':
+ conf = optarg;
+ break;
+ case 'm':
+ monitormode = 1;
+ break;
+ case 'F':
+ force = 1;
+ break;
+ default:
+ usage();
+ }
+ }
+ if (monitormode && force)
+ errx(1, "cannot specify both -m and -F flags");
+}
+
+void
+usage(void)
+{
+ extern const char *__progname;
+
+ (void)fprintf(stderr, "usage: %s [-Fmnrv] [-a directory] "
+ "[-f config_file] [log ...]\n", __progname);
+ exit(1);
+}
+
+/*
+ * Parse a configuration file and return a linked list of all the logs
+ * to process
+ */
+struct conf_entry *
+parse_file(int *nentries)
+{
+ char line[BUFSIZ], *parse, *q, *errline, *group, *tmp, *ep;
+ struct conf_entry *working = NULL, *first = NULL;
+ struct passwd *pwd;
+ struct group *grp;
+ struct stat sb;
+ int lineno;
+ FILE *f;
+ long l;
+
+ if (strcmp(conf, "-") == 0)
+ f = stdin;
+ else if ((f = fopen(conf, "r")) == NULL)
+ err(1, "can't open %s", conf);
+
+ *nentries = 0;
+ for (lineno = 1; fgets(line, sizeof(line), f); lineno++) {
+ tmp = sob(line);
+ if (*tmp == '\0' || *tmp == '#')
+ continue;
+ errline = strdup(tmp);
+ if (errline == NULL)
+ err(1, "strdup");
+ (*nentries)++;
+ if (!first) {
+ working = malloc(sizeof(struct conf_entry));
+ if (working == NULL)
+ err(1, "malloc");
+ first = working;
+ } else {
+ working->next = malloc(sizeof(struct conf_entry));
+ if (working->next == NULL)
+ err(1, "malloc");
+ working = working->next;
+ }
+
+ q = parse = missing_field(sob(line), errline, lineno);
+ *(parse = son(line)) = '\0';
+ working->log = strdup(q);
+ if (working->log == NULL)
+ err(1, "strdup");
+
+ if ((working->logbase = strrchr(working->log, '/')) != NULL)
+ working->logbase++;
+
+ q = parse = missing_field(sob(++parse), errline, lineno);
+ *(parse = son(parse)) = '\0';
+ if ((group = strchr(q, ':')) != NULL ||
+ (group = strrchr(q, '.')) != NULL) {
+ *group++ = '\0';
+ if (*q) {
+ if (!(isnumberstr(q))) {
+ if ((pwd = getpwnam(q)) == NULL)
+ errx(1, "%s:%d: unknown user: %s",
+ conf, lineno, q);
+ working->uid = pwd->pw_uid;
+ } else
+ working->uid = atoi(q);
+ } else
+ working->uid = (uid_t)-1;
+
+ q = group;
+ if (*q) {
+ if (!(isnumberstr(q))) {
+ if ((grp = getgrnam(q)) == NULL)
+ errx(1, "%s:%d: unknown group: %s",
+ conf, lineno, q);
+ working->gid = grp->gr_gid;
+ } else
+ working->gid = atoi(q);
+ } else
+ working->gid = (gid_t)-1;
+
+ q = parse = missing_field(sob(++parse), errline, lineno);
+ *(parse = son(parse)) = '\0';
+ } else {
+ working->uid = (uid_t)-1;
+ working->gid = (gid_t)-1;
+ }
+
+ l = strtol(q, &ep, 8);
+ if (*ep != '\0' || l < 0 || l > ALLPERMS)
+ errx(1, "%s:%d: bad permissions: %s", conf, lineno, q);
+ working->permissions = (mode_t)l;
+
+ q = parse = missing_field(sob(++parse), errline, lineno);
+ *(parse = son(parse)) = '\0';
+ l = strtol(q, &ep, 10);
+ if (*ep != '\0' || l < 0 || l >= INT_MAX)
+ errx(1, "%s:%d: bad number: %s", conf, lineno, q);
+ working->numlogs = (int)l;
+
+ q = parse = missing_field(sob(++parse), errline, lineno);
+ *(parse = son(parse)) = '\0';
+ if (isdigit((unsigned char)*q))
+ working->size = atoi(q) * 1024;
+ else
+ working->size = -1;
+
+ working->flags = 0;
+ q = parse = missing_field(sob(++parse), errline, lineno);
+ *(parse = son(parse)) = '\0';
+ l = strtol(q, &ep, 10);
+ if (l < 0 || l >= INT_MAX)
+ errx(1, "%s:%d: interval out of range: %s", conf,
+ lineno, q);
+ working->hours = (int)l;
+ switch (*ep) {
+ case '\0':
+ break;
+ case '@':
+ working->trim_at = parse8601(ep + 1);
+ if (working->trim_at == (time_t) - 1)
+ errx(1, "%s:%d: bad time: %s", conf, lineno, q);
+ working->flags |= CE_TRIMAT;
+ break;
+ case '$':
+ working->trim_at = parseDWM(ep + 1);
+ if (working->trim_at == (time_t) - 1)
+ errx(1, "%s:%d: bad time: %s", conf, lineno, q);
+ working->flags |= CE_TRIMAT;
+ break;
+ case '*':
+ if (q == ep)
+ break;
+ /* FALLTHROUGH */
+ default:
+ errx(1, "%s:%d: bad interval/at: %s", conf, lineno, q);
+ break;
+ }
+
+ q = sob(++parse); /* Optional field */
+ if (*q == 'Z' || *q == 'z' || *q == 'B' || *q == 'b' ||
+ *q == 'M' || *q == 'm') {
+ *(parse = son(q)) = '\0';
+ while (*q) {
+ switch (*q) {
+ case 'Z':
+ case 'z':
+ working->flags |= CE_COMPACT;
+ break;
+ case 'B':
+ case 'b':
+ working->flags |= CE_BINARY;
+ break;
+ case 'M':
+ case 'm':
+ working->flags |= CE_MONITOR;
+ break;
+ case 'F':
+ case 'f':
+ working->flags |= CE_FOLLOW;
+ break;
+ default:
+ errx(1, "%s:%d: illegal flag: `%c'",
+ conf, lineno, *q);
+ break;
+ }
+ q++;
+ }
+ } else
+ parse--; /* no flags so undo */
+
+ working->pidfile = PIDFILE;
+ working->signal = SIGHUP;
+ working->runcmd = NULL;
+ working->whom = NULL;
+ for (;;) {
+ q = parse = sob(++parse); /* Optional field */
+ if (q == NULL || *q == '\0')
+ break;
+ if (*q == '/') {
+ *(parse = son(parse)) = '\0';
+ if (strlen(q) >= PATH_MAX)
+ errx(1, "%s:%d: pathname too long: %s",
+ conf, lineno, q);
+ working->pidfile = strdup(q);
+ if (working->pidfile == NULL)
+ err(1, "strdup");
+ } else if (*q == '"' && (tmp = strchr(q + 1, '"'))) {
+ *(parse = tmp) = '\0';
+ if (*++q != '\0') {
+ working->runcmd = strdup(q);
+ if (working->runcmd == NULL)
+ err(1, "strdup");
+ }
+ working->pidfile = NULL;
+ working->signal = -1;
+ } else if (strncmp(q, "SIG", 3) == 0) {
+ int i;
+
+ *(parse = son(parse)) = '\0';
+ for (i = 1; i < NSIG; i++) {
+ if (!strcmp(sys_signame[i], q + 3)) {
+ working->signal = i;
+ break;
+ }
+ }
+ if (i == NSIG)
+ errx(1, "%s:%d: unknown signal: %s",
+ conf, lineno, q);
+ } else if (working->flags & CE_MONITOR) {
+ *(parse = son(parse)) = '\0';
+ working->whom = strdup(q);
+ if (working->whom == NULL)
+ err(1, "strdup");
+ } else
+ errx(1, "%s:%d: unrecognized field: %s",
+ conf, lineno, q);
+ }
+ free(errline);
+
+ if ((working->flags & CE_MONITOR) && working->whom == NULL)
+ errx(1, "%s:%d: missing monitor notification field",
+ conf, lineno);
+
+ /* If there is an arcdir, set working->backdir. */
+ if (arcdir != NULL && working->logbase != NULL) {
+ if (*arcdir == '/') {
+ /* Fully qualified arcdir */
+ working->backdir = arcdir;
+ } else {
+ /* arcdir is relative to log's parent dir */
+ *(working->logbase - 1) = '\0';
+ if ((asprintf(&working->backdir, "%s/%s",
+ working->log, arcdir)) == -1)
+ err(1, "malloc");
+ *(working->logbase - 1) = '/';
+ }
+ /* Ignore arcdir if it doesn't exist. */
+ if (stat(working->backdir, &sb) != 0 ||
+ !S_ISDIR(sb.st_mode)) {
+ if (working->backdir != arcdir)
+ free(working->backdir);
+ working->backdir = NULL;
+ }
+ } else
+ working->backdir = NULL;
+
+ /* Make sure we can't oflow PATH_MAX */
+ if (working->backdir != NULL) {
+ if (snprintf(line, sizeof(line), "%s/%s.%d%s",
+ working->backdir, working->logbase,
+ working->numlogs, COMPRESS_POSTFIX) >= PATH_MAX)
+ errx(1, "%s:%d: pathname too long: %s",
+ conf, lineno, q);
+ } else {
+ if (snprintf(line, sizeof(line), "%s.%d%s",
+ working->log, working->numlogs, COMPRESS_POSTFIX)
+ >= PATH_MAX)
+ errx(1, "%s:%d: pathname too long: %s",
+ conf, lineno, working->log);
+ }
+ }
+ if (working)
+ working->next = NULL;
+ (void)fclose(f);
+ return (first);
+}
+
+char *
+missing_field(char *p, char *errline, int lineno)
+{
+ if (p == NULL || *p == '\0') {
+ warnx("%s:%d: missing field", conf, lineno);
+ fputs(errline, stderr);
+ exit(1);
+ }
+ return (p);
+}
+
+void
+rotate(struct conf_entry *ent, const char *oldlog)
+{
+ char file1[PATH_MAX], file2[PATH_MAX], *suffix;
+ int numdays = ent->numlogs - 1;
+ int done = 0;
+
+ /* Remove old logs */
+ do {
+ (void)snprintf(file1, sizeof(file1), "%s.%d", oldlog, numdays);
+ (void)snprintf(file2, sizeof(file2), "%s.%d%s", oldlog,
+ numdays, COMPRESS_POSTFIX);
+ if (noaction) {
+ printf("\trm -f %s %s\n", file1, file2);
+ done = access(file1, 0) && access(file2, 0);
+ } else {
+ done = unlink(file1) && unlink(file2);
+ }
+ numdays++;
+ } while (done == 0);
+
+ /* Move down log files */
+ for (numdays = ent->numlogs - 2; numdays >= 0; numdays--) {
+ /*
+ * If both the compressed archive and the non-compressed archive
+ * exist, we decide which to rotate based on the CE_COMPACT flag
+ */
+ (void)snprintf(file1, sizeof(file1), "%s.%d", oldlog, numdays);
+ suffix = lstat_log(file1, sizeof(file1), ent->flags);
+ if (suffix == NULL)
+ continue;
+ (void)snprintf(file2, sizeof(file2), "%s.%d%s", oldlog,
+ numdays + 1, suffix);
+
+ if (noaction) {
+ printf("\tmv %s %s\n", file1, file2);
+ printf("\tchmod %o %s\n", ent->permissions, file2);
+ printf("\tchown %u:%u %s\n", ent->uid, ent->gid, file2);
+ } else {
+ if (rename(file1, file2))
+ warn("can't mv %s to %s", file1, file2);
+ if (chmod(file2, ent->permissions))
+ warn("can't chmod %s", file2);
+ if (chown(file2, ent->uid, ent->gid))
+ warn("can't chown %s", file2);
+ }
+ }
+}
+
+void
+dotrim(struct conf_entry *ent)
+{
+ char file1[PATH_MAX], file2[PATH_MAX], oldlog[PATH_MAX];
+ int fd;
+
+ /* Is there a separate backup dir? */
+ if (ent->backdir != NULL)
+ snprintf(oldlog, sizeof(oldlog), "%s/%s", ent->backdir,
+ ent->logbase);
+ else
+ strlcpy(oldlog, ent->log, sizeof(oldlog));
+
+ if (ent->numlogs > 0)
+ rotate(ent, oldlog);
+ if (!noaction && !(ent->flags & CE_BINARY))
+ (void)log_trim(ent->log);
+
+ (void)snprintf(file2, sizeof(file2), "%s.XXXXXXXXXX", ent->log);
+ if (noaction) {
+ printf("\tmktemp %s\n", file2);
+ } else {
+ if ((fd = mkstemp(file2)) < 0)
+ err(1, "can't start '%s' log", file2);
+ if (fchmod(fd, ent->permissions))
+ err(1, "can't chmod '%s' log file", file2);
+ if (fchown(fd, ent->uid, ent->gid))
+ err(1, "can't chown '%s' log file", file2);
+ (void)close(fd);
+ /* Add status message */
+ if (!(ent->flags & CE_BINARY) && log_trim(file2))
+ err(1, "can't add status message to log '%s'", file2);
+ }
+
+ if (ent->numlogs == 0) {
+ if (noaction)
+ printf("\trm %s\n", ent->log);
+ else if (unlink(ent->log))
+ warn("can't rm %s", ent->log);
+ } else {
+ (void)snprintf(file1, sizeof(file1), "%s.0", oldlog);
+ if (noaction) {
+ printf("\tmv %s to %s\n", ent->log, file1);
+ printf("\tchmod %o %s\n", ent->permissions, file1);
+ printf("\tchown %u:%u %s\n", ent->uid, ent->gid, file1);
+ } else if (movefile(ent->log, file1, ent->uid, ent->gid,
+ ent->permissions))
+ warn("can't mv %s to %s", ent->log, file1);
+ }
+
+ /* Now move the new log file into place */
+ if (noaction)
+ printf("\tmv %s to %s\n", file2, ent->log);
+ else if (rename(file2, ent->log))
+ warn("can't mv %s to %s", file2, ent->log);
+}
+
+/* Log the fact that the logs were turned over */
+int
+log_trim(char *log)
+{
+ FILE *f;
+
+ if ((f = fopen(log, "a")) == NULL)
+ return (-1);
+ (void)fprintf(f, "%s %s newsyslog[%ld]: logfile turned over\n",
+ daytime, hostname, (long)getpid());
+ if (fclose(f) == EOF)
+ err(1, "log_trim: fclose");
+ return (0);
+}
+
+/* Fork off compress or gzip to compress the old log file */
+void
+compress_log(struct conf_entry *ent)
+{
+ char *base, tmp[PATH_MAX];
+ pid_t pid;
+
+ if (ent->backdir != NULL)
+ snprintf(tmp, sizeof(tmp), "%s/%s.0", ent->backdir,
+ ent->logbase);
+ else
+ snprintf(tmp, sizeof(tmp), "%s.0", ent->log);
+
+ if ((base = strrchr(COMPRESS, '/')) == NULL)
+ base = COMPRESS;
+ else
+ base++;
+ if (noaction) {
+ printf("%s %s\n", base, tmp);
+ return;
+ }
+ pid = fork();
+ if (pid < 0) {
+ err(1, "fork");
+ } else if (pid == 0) {
+ (void)execl(COMPRESS, base, "-f", tmp, (char *)NULL);
+ warn(COMPRESS);
+ _exit(1);
+ }
+}
+
+/* Return size in bytes of a file */
+off_t
+sizefile(struct stat *sb)
+{
+ /* For sparse files, return the size based on number of blocks used. */
+ if (sb->st_size / DEV_BSIZE > sb->st_blocks)
+ return (sb->st_blocks * DEV_BSIZE);
+ else
+ return (sb->st_size);
+}
+
+/* Return the age (in hours) of old log file (file.0), or -1 if none */
+int
+age_old_log(struct conf_entry *ent)
+{
+ char file[PATH_MAX];
+ struct stat sb;
+
+ if (ent->backdir != NULL)
+ (void)snprintf(file, sizeof(file), "%s/%s.0", ent->backdir,
+ ent->logbase);
+ else
+ (void)snprintf(file, sizeof(file), "%s.0", ent->log);
+ if (ent->flags & CE_COMPACT) {
+ if (stat_suffix(file, sizeof(file), COMPRESS_POSTFIX, &sb,
+ stat) < 0 && stat(file, &sb) < 0)
+ return (-1);
+ } else {
+ if (stat(file, &sb) < 0 && stat_suffix(file, sizeof(file),
+ COMPRESS_POSTFIX, &sb, stat) < 0)
+ return (-1);
+ }
+ return ((int)(timenow - sb.st_mtime + 1800) / 3600);
+}
+
+/* Skip Over Blanks */
+char *
+sob(char *p)
+{
+ if (p == NULL)
+ return(p);
+ while (isspace((unsigned char)*p))
+ p++;
+ return (p);
+}
+
+/* Skip Over Non-Blanks */
+char *
+son(char *p)
+{
+ while (p && *p && !isspace((unsigned char)*p))
+ p++;
+ return (p);
+}
+
+/* Check if string is actually a number */
+int
+isnumberstr(char *string)
+{
+ while (*string) {
+ if (!isdigit((unsigned char)*string++))
+ return (0);
+ }
+ return (1);
+}
+
+int
+domonitor(struct conf_entry *ent)
+{
+ char fname[PATH_MAX], *flog, *p, *rb = NULL;
+ struct stat sb, tsb;
+ off_t osize;
+ FILE *fp;
+ int rd;
+
+ if (stat(ent->log, &sb) < 0)
+ return (0);
+
+ if (noaction) {
+ if (!verbose)
+ printf("%s: monitored\n", ent->log);
+ return (1);
+ }
+
+ flog = strdup(ent->log);
+ if (flog == NULL)
+ err(1, "strdup");
+
+ for (p = flog; *p != '\0'; p++) {
+ if (*p == '/')
+ *p = '_';
+ }
+ snprintf(fname, sizeof(fname), "%s/newsyslog.%s.size",
+ STATS_DIR, flog);
+
+ /* ..if it doesn't exist, simply record the current size. */
+ if ((sb.st_size == 0) || stat(fname, &tsb) < 0)
+ goto update;
+
+ fp = fopen(fname, "r");
+ if (fp == NULL) {
+ warn("%s", fname);
+ goto cleanup;
+ }
+ if (fscanf(fp, "%lld\n", &osize) != 1) {
+ fclose(fp);
+ goto update;
+ }
+
+ fclose(fp);
+
+ /* If the file is smaller, mark the entire thing as changed. */
+ if (sb.st_size < osize)
+ osize = 0;
+
+ /* Now see if current size is larger. */
+ if (sb.st_size > osize) {
+ rb = malloc(sb.st_size - osize);
+ if (rb == NULL)
+ err(1, "malloc");
+
+ /* Open logfile, seek. */
+ fp = fopen(ent->log, "r");
+ if (fp == NULL) {
+ warn("%s", ent->log);
+ goto cleanup;
+ }
+ fseek(fp, osize, SEEK_SET);
+ rd = fread(rb, 1, sb.st_size - osize, fp);
+ if (rd < 1) {
+ warn("fread");
+ fclose(fp);
+ goto cleanup;
+ }
+
+ /* Send message. */
+ fclose(fp);
+
+ fp = openmail();
+ if (fp == NULL) {
+ warn("openmail");
+ goto cleanup;
+ }
+ fprintf(fp, "Auto-Submitted: auto-generated\n");
+ fprintf(fp, "To: %s\nSubject: LOGFILE NOTIFICATION: %s\n\n\n",
+ ent->whom, ent->log);
+ fwrite(rb, 1, rd, fp);
+ fputs("\n\n", fp);
+
+ pclose(fp);
+ }
+update:
+ /* Reopen for writing and update file. */
+ fp = fopen(fname, "w");
+ if (fp == NULL) {
+ warn("%s", fname);
+ goto cleanup;
+ }
+ fprintf(fp, "%lld\n", (long long)sb.st_size);
+ fclose(fp);
+
+cleanup:
+ free(flog);
+ free(rb);
+ return (1);
+}
+
+FILE *
+openmail(void)
+{
+ char *cmdbuf = NULL;
+ FILE *ret;
+
+ if (asprintf(&cmdbuf, "%s -t", SENDMAIL) != -1) {
+ ret = popen(cmdbuf, "w");
+ free(cmdbuf);
+ return (ret);
+ }
+ return (NULL);
+}
+
+/* ARGSUSED */
+void
+child_killer(int signo)
+{
+ int save_errno = errno;
+ int status;
+
+ while (waitpid(-1, &status, WNOHANG) > 0)
+ ;
+ errno = save_errno;
+}
+
+int
+stat_suffix(char *file, size_t size, char *suffix, struct stat *sp,
+ int (*func)(const char *, struct stat *))
+{
+ size_t n;
+
+ n = strlcat(file, suffix, size);
+ if (n < size && func(file, sp) == 0)
+ return (0);
+ file[n - strlen(suffix)] = '\0';
+ return (-1);
+}
+
+/*
+ * lstat() a log, possibly appending a suffix; order is based on flags.
+ * Returns the suffix appended (may be empty string) or NULL if no file.
+ */
+char *
+lstat_log(char *file, size_t size, int flags)
+{
+ struct stat sb;
+
+ if (flags & CE_COMPACT) {
+ if (stat_suffix(file, size, COMPRESS_POSTFIX, &sb, lstat) == 0)
+ return (COMPRESS_POSTFIX);
+ if (lstat(file, &sb) == 0)
+ return ("");
+ } else {
+ if (lstat(file, &sb) == 0)
+ return ("");
+ if (stat_suffix(file, size, COMPRESS_POSTFIX, &sb, lstat) == 0)
+ return (COMPRESS_POSTFIX);
+
+ }
+ return (NULL);
+}
+
+/*
+ * 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
+parse8601(char *s)
+{
+ struct tm tm, *tmp;
+ char *t;
+ long l;
+
+ tmp = localtime(&timenow);
+ tm = *tmp;
+
+ tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
+
+ l = strtol(s, &t, 10);
+ if (l < 0 || l >= INT_MAX || (*t != '\0' && *t != 'T'))
+ return (-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 = ((l / 1000000) - 19) * 100;
+ l = l % 1000000;
+ case 6:
+ tm.tm_year -= tm.tm_year % 100;
+ tm.tm_year += l / 10000;
+ l = l % 10000;
+ case 4:
+ tm.tm_mon = (l / 100) - 1;
+ l = l % 100;
+ case 2:
+ tm.tm_mday = l;
+ case 0:
+ break;
+ default:
+ return (-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 (-1);
+
+ if (*t != '\0') {
+ s = ++t;
+ l = strtol(s, &t, 10);
+ if (l < 0 || l >= INT_MAX ||
+ (*t != '\0' && !isspace((unsigned char)*t)))
+ return (-1);
+
+ switch (t - s) {
+ case 6:
+ tm.tm_sec = l % 100;
+ l /= 100;
+ case 4:
+ tm.tm_min = l % 100;
+ l /= 100;
+ case 2:
+ tm.tm_hour = l;
+ case 0:
+ break;
+ default:
+ return (-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 (-1);
+ }
+ return (mktime(&tm));
+}
+
+/*-
+ * Parse a cyclic time specification, the format is as follows:
+ *
+ * [Dhh] or [Wd[Dhh]] or [Mdd[Dhh]]
+ *
+ * to rotate a logfile 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
+parseDWM(char *s)
+{
+ static int mtab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
+ int WMseen = 0, Dseen = 0, nd;
+ struct tm tm, *tmp;
+ char *t;
+ long l;
+
+ tmp = localtime(&timenow);
+ tm = *tmp;
+
+ /* set no. of days per month */
+
+ nd = mtab[tm.tm_mon];
+
+ if (tm.tm_mon == 1) {
+ if (((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 (-1);
+ Dseen++;
+ s++;
+ l = strtol(s, &t, 10);
+ if (l < 0 || l > 23)
+ return (-1);
+ tm.tm_hour = l;
+ break;
+
+ case 'W':
+ if (WMseen)
+ return (-1);
+ WMseen++;
+ s++;
+ l = strtol(s, &t, 10);
+ if (l < 0 || l > 6)
+ return (-1);
+ if (l != tm.tm_wday) {
+ int save;
+
+ if (l < tm.tm_wday) {
+ save = 6 - tm.tm_wday;
+ save += (l + 1);
+ } else {
+ save = l - 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((unsigned char)*s) == 'l') {
+ tm.tm_mday = nd;
+ s++;
+ t = s;
+ } else {
+ l = strtol(s, &t, 10);
+ if (l < 1 || l > 31)
+ return (-1);
+
+ if (l > nd)
+ return (-1);
+ if (l < tm.tm_mday)
+ tm.tm_mon++;
+ tm.tm_mday = l;
+ }
+ break;
+
+ default:
+ return (-1);
+ break;
+ }
+
+ if (*t == '\0' || isspace((unsigned char)*t))
+ break;
+ else
+ s = t;
+ }
+ return (mktime(&tm));
+}
+
+/*
+ * Move a file using rename(2) if possible and copying if not.
+ */
+int
+movefile(char *from, char *to, uid_t owner_uid, gid_t group_gid, mode_t perm)
+{
+ FILE *src, *dst;
+ int i;
+
+ /* try rename(2) first */
+ if (rename(from, to) == 0) {
+ if (chmod(to, perm))
+ warn("can't chmod %s", to);
+ if (chown(to, owner_uid, group_gid))
+ warn("can't chown %s", to);
+ return (0);
+ } else if (errno != EXDEV)
+ return (-1);
+
+ /* different filesystem, have to copy the file */
+ if ((src = fopen(from, "r")) == NULL)
+ err(1, "can't fopen %s for reading", from);
+ if ((dst = fopen(to, "w")) == NULL)
+ err(1, "can't fopen %s for writing", to);
+ if (fchmod(fileno(dst), perm))
+ err(1, "can't fchmod %s", to);
+ if (fchown(fileno(dst), owner_uid, group_gid))
+ err(1, "can't fchown %s", to);
+
+ while ((i = getc(src)) != EOF) {
+ if ((putc(i, dst)) == EOF)
+ err(1, "error writing to %s", to);
+ }
+
+ if (ferror(src))
+ err(1, "error reading from %s", from);
+ if ((fclose(src)) != 0)
+ err(1, "can't fclose %s", from);
+ if ((fclose(dst)) != 0)
+ err(1, "can't fclose %s", to);
+ if ((unlink(from)) != 0)
+ err(1, "can't unlink %s", from);
+
+ return (0);
+}