aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--usr.sbin/newsyslog/newsyslog.831
-rw-r--r--usr.sbin/newsyslog/newsyslog.c252
2 files changed, 267 insertions, 16 deletions
diff --git a/usr.sbin/newsyslog/newsyslog.8 b/usr.sbin/newsyslog/newsyslog.8
index 19d01d7f6022..58881a2d5dae 100644
--- a/usr.sbin/newsyslog/newsyslog.8
+++ b/usr.sbin/newsyslog/newsyslog.8
@@ -30,6 +30,7 @@
.Op Fl a Ar directory
.Op Fl d Ar directory
.Op Fl f Ar config_file
+.Op Fl t Ar timefmt
.Op Ar
.Sh DESCRIPTION
The
@@ -50,6 +51,11 @@ the last period's logs in it,
has the next to last
period's logs in it, and so on, up to a user-specified number of
archived logs.
+It is also possible to let archived log filenames be created using the
+time the log file was archived instead of the sequential number using
+the
+.Fl t
+option.
Optionally the archived logs can be compressed to save
space.
.Pp
@@ -141,6 +147,31 @@ However, this option is most likely to be useful when specified
with the
.Fl R
option, and in that case the compression will be done.
+.It Fl t Ar timefmt
+If specified
+.Nm
+will create the
+.Dq rotated
+logfiles using the specified time format instead of the default
+sequential filenames.
+The time format is described in the
+.Xr strftime 3
+manual page.
+If the
+.Ar timefmt
+argument is set to an empty string or the string
+.Dq DEFAULT ,
+the default built in time format
+is used.
+If the
+.Ar timefmt
+string is changed the old files created using the previous time format
+will not be be automatically removed (unless the new format is very
+similar to the old format).
+This is also the case when changing from sequential filenames to time
+based file names, and the other way around.
+The time format should contain at least year, month, day, and hour to
+make sure rotating of old logfiles can select the correct logfiles.
.It Fl C
If specified once, then
.Nm
diff --git a/usr.sbin/newsyslog/newsyslog.c b/usr.sbin/newsyslog/newsyslog.c
index 594375a746b4..f962482e0a67 100644
--- a/usr.sbin/newsyslog/newsyslog.c
+++ b/usr.sbin/newsyslog/newsyslog.c
@@ -69,9 +69,11 @@ __FBSDID("$FreeBSD$");
#include <sys/stat.h>
#include <sys/wait.h>
+#include <assert.h>
#include <ctype.h>
#include <err.h>
#include <errno.h>
+#include <dirent.h>
#include <fcntl.h>
#include <fnmatch.h>
#include <glob.h>
@@ -80,6 +82,7 @@ __FBSDID("$FreeBSD$");
#include <pwd.h>
#include <signal.h>
#include <stdio.h>
+#include <libgen.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
@@ -112,6 +115,9 @@ __FBSDID("$FreeBSD$");
#define DEFAULT_MARKER "<default>"
#define DEBUG_MARKER "<debug>"
#define INCLUDE_MARKER "<include>"
+#define DEFAULT_TIMEFNAME_FMT "%Y%m%dT%H%M%S"
+
+#define MAX_OLDLOGS 65536 /* Default maximum number of old logfiles */
struct conf_entry {
STAILQ_ENTRY(conf_entry) cf_nextp;
@@ -155,6 +161,11 @@ struct include_entry {
const char *file; /* Name of file to process */
};
+struct oldlog_entry {
+ char *fname; /* Filename of the log file */
+ time_t t; /* Parses timestamp of the logfile */
+};
+
typedef enum {
FREE_ENT, KEEP_ENT
} fk_entry;
@@ -182,6 +193,7 @@ int rotatereq = 0; /* -R = Always rotate the file(s) as given */
/* that a list of files *are* given on */
/* the run command). */
char *requestor; /* The name given on a -R request */
+char *timefnamefmt = NULL; /* Use time based filenames instead of .0 etc */
char *archdirname; /* Directory path to old logfiles archive */
char *destdir = NULL; /* Directory to treat at root for logs */
const char *conf; /* Configuration file to use */
@@ -585,7 +597,7 @@ parse_args(int argc, char **argv)
*p = '\0';
/* Parse command line options. */
- while ((ch = getopt(argc, argv, "a:d:f:nrsvCD:FNPR:")) != -1)
+ while ((ch = getopt(argc, argv, "a:d:f:nrst:vCD:FNPR:")) != -1)
switch (ch) {
case 'a':
archtodir++;
@@ -606,6 +618,13 @@ parse_args(int argc, char **argv)
case 's':
nosignal = 1;
break;
+ case 't':
+ if (optarg[0] == '\0' ||
+ strcmp(optarg, "DEFAULT") == 0)
+ timefnamefmt = strdup(DEFAULT_TIMEFNAME_FMT);
+ else
+ timefnamefmt = strdup(optarg);
+ break;
case 'v':
verbose++;
break;
@@ -728,7 +747,7 @@ usage(void)
fprintf(stderr,
"usage: newsyslog [-CFNnrsv] [-a directory] [-d directory] [-f config-file]\n"
- " [ [-R requestor] filename ... ]\n");
+ " [-t timefmt ] [ [-R requestor] filename ... ]\n");
exit(1);
}
@@ -1365,6 +1384,177 @@ missing_field(char *p, char *errline)
}
/*
+ * In our sort we return it in the reverse of what qsort normally
+ * would do, as we want the newest files first. If we have two
+ * entries with the same time we don't really care about order.
+ *
+ * Support function for qsort() in delete_oldest_timelog().
+ */
+static int
+oldlog_entry_compare(const void *a, const void *b)
+{
+ const struct oldlog_entry *ola = a, *olb = b;
+
+ if (ola->t > olb->t)
+ return (-1);
+ else if (ola->t < olb->t)
+ return (1);
+ else
+ return (0);
+}
+
+/*
+ * Delete the oldest logfiles, when using time based filenames.
+ */
+static void
+delete_oldest_timelog(const struct conf_entry *ent, const char *archive_dir)
+{
+ char *logfname, *s, *dir, errbuf[80];
+ int logcnt, max_logcnt, dirfd, i;
+ struct oldlog_entry *oldlogs;
+ size_t logfname_len;
+ struct dirent *dp;
+ const char *cdir;
+ struct tm tm;
+ DIR *dirp;
+
+ oldlogs = malloc(MAX_OLDLOGS * sizeof(struct oldlog_entry));
+ max_logcnt = MAX_OLDLOGS;
+ logcnt = 0;
+
+ if (archive_dir != NULL && archive_dir[0] != '\0')
+ cdir = archive_dir;
+ else
+ if ((cdir = dirname(ent->log)) == NULL)
+ err(1, "dirname()");
+ if ((dir = strdup(cdir)) == NULL)
+ err(1, "strdup()");
+
+ if ((s = basename(ent->log)) == NULL)
+ err(1, "basename()");
+ if ((logfname = strdup(s)) == NULL)
+ err(1, "strdup()");
+ logfname_len = strlen(logfname);
+ if (strcmp(logfname, "/") == 0)
+ errx(1, "Invalid log filename - became '/'");
+
+ if (verbose > 2)
+ printf("Searching for old logs in %s\n", dir);
+
+ /* First we create a 'list' of all archived logfiles */
+ if ((dirp = opendir(dir)) == NULL)
+ err(1, "Cannot open log directory '%s'", dir);
+ dirfd = dirfd(dirp);
+ while ((dp = readdir(dirp)) != NULL) {
+ if (dp->d_type != DT_REG)
+ continue;
+
+ /* Ignore everything but files with our logfile prefix */
+ if (strncmp(dp->d_name, logfname, logfname_len) != 0)
+ continue;
+ /* Ignore the actual non-rotated logfile */
+ if (dp->d_namlen == logfname_len)
+ continue;
+ /*
+ * Make sure we created have found a logfile, so the
+ * postfix is valid, IE format is: '.<time>(.[bg]z)?'.
+ */
+ if (dp->d_name[logfname_len] != '.') {
+ if (verbose)
+ printf("Ignoring %s which has unexpected "
+ "extension '%s'\n", dp->d_name,
+ &dp->d_name[logfname_len]);
+ continue;
+ }
+ if ((s = strptime(&dp->d_name[logfname_len + 1],
+ timefnamefmt, &tm)) == NULL) {
+ /*
+ * We could special case "old" sequentially
+ * named logfiles here, but we do not as that
+ * would require special handling to decide
+ * which one was the oldest compared to "new"
+ * time based logfiles.
+ */
+ if (verbose)
+ printf("Ignoring %s which does not "
+ "match time format\n", dp->d_name);
+ continue;
+ }
+ if (*s != '\0' && !(strcmp(s, BZCOMPRESS_POSTFIX) == 0 ||
+ strcmp(s, COMPRESS_POSTFIX) == 0)) {
+ if (verbose)
+ printf("Ignoring %s which has unexpected "
+ "extension '%s'\n", dp->d_name, s);
+ continue;
+ }
+
+ /*
+ * We should now have old an old rotated logfile, so
+ * add it to the 'list'.
+ */
+ if ((oldlogs[logcnt].t = timegm(&tm)) == -1)
+ err(1, "Could not convert time string to time value");
+ if ((oldlogs[logcnt].fname = strdup(dp->d_name)) == NULL)
+ err(1, "strdup()");
+ logcnt++;
+
+ /*
+ * It is very unlikely we ever run out of space in the
+ * logfile array from the default size, but lets
+ * handle it anyway...
+ */
+ if (logcnt >= max_logcnt) {
+ max_logcnt *= 4;
+ /* Detect integer overflow */
+ if (max_logcnt < logcnt)
+ errx(1, "Too many old logfiles found");
+ oldlogs = realloc(oldlogs,
+ max_logcnt * sizeof(struct oldlog_entry));
+ if (oldlogs == NULL)
+ err(1, "realloc()");
+ }
+ }
+
+ /* Second, if needed we delete oldest archived logfiles */
+ if (logcnt > 0 && logcnt >= ent->numlogs && ent->numlogs > 1) {
+ oldlogs = realloc(oldlogs, logcnt *
+ sizeof(struct oldlog_entry));
+ if (oldlogs == NULL)
+ err(1, "realloc()");
+
+ /*
+ * We now sort the logs in the order of newest to
+ * oldest. That way we can simply skip over the
+ * number of records we want to keep.
+ */
+ qsort(oldlogs, logcnt, sizeof(struct oldlog_entry),
+ oldlog_entry_compare);
+ for (i = ent->numlogs - 1; i < logcnt; i++) {
+ if (noaction)
+ printf("\trm -f %s/%s\n", dir,
+ oldlogs[i].fname);
+ else if (unlinkat(dirfd, oldlogs[i].fname, 0) != 0) {
+ snprintf(errbuf, sizeof(errbuf),
+ "Could not delet old logfile '%s'",
+ oldlogs[i].fname);
+ perror(errbuf);
+ }
+ }
+ } else if (verbose > 1)
+ printf("No old logs to delete for logfile %s\n", ent->log);
+
+ /* Third, cleanup */
+ closedir(dirp);
+ for (i = 0; i < logcnt; i++) {
+ assert(oldlogs[i].fname != NULL);
+ free(oldlogs[i].fname);
+ }
+ free(oldlogs);
+ free(logfname);
+ free(dir);
+}
+
+/*
* Only add to the queue if the file hasn't already been added. This is
* done to prevent circular include loops.
*/
@@ -1398,10 +1588,13 @@ do_rotate(const struct conf_entry *ent)
char file1[MAXPATHLEN], file2[MAXPATHLEN];
char zfile1[MAXPATHLEN], zfile2[MAXPATHLEN];
char jfile1[MAXPATHLEN];
+ char datetimestr[30];
int flags, numlogs_c;
fk_entry free_or_keep;
struct sigwork_entry *swork;
struct stat st;
+ struct tm tm;
+ time_t now;
flags = ent->flags;
free_or_keep = FREE_ENT;
@@ -1435,32 +1628,59 @@ do_rotate(const struct conf_entry *ent)
/* name of oldest log */
(void) snprintf(file1, sizeof(file1), "%s/%s.%d", dirpart,
namepart, ent->numlogs);
- (void) snprintf(zfile1, sizeof(zfile1), "%s%s", file1,
- COMPRESS_POSTFIX);
- snprintf(jfile1, sizeof(jfile1), "%s%s", file1,
- BZCOMPRESS_POSTFIX);
} else {
+ /*
+ * Tell delete_oldest_timelog() we are not using an
+ * archive dir.
+ */
+ dirpart[0] = '\0';
+
/* name of oldest log */
(void) snprintf(file1, sizeof(file1), "%s.%d", ent->log,
ent->numlogs);
+ }
+
+ /* Delete old logs */
+ if (timefnamefmt != NULL)
+ delete_oldest_timelog(ent, dirpart);
+ else {
+ /* name of oldest log */
(void) snprintf(zfile1, sizeof(zfile1), "%s%s", file1,
COMPRESS_POSTFIX);
snprintf(jfile1, sizeof(jfile1), "%s%s", file1,
BZCOMPRESS_POSTFIX);
- }
- if (noaction) {
- printf("\trm -f %s\n", file1);
- printf("\trm -f %s\n", zfile1);
- printf("\trm -f %s\n", jfile1);
- } else {
- (void) unlink(file1);
- (void) unlink(zfile1);
- (void) unlink(jfile1);
+ if (noaction) {
+ printf("\trm -f %s\n", file1);
+ printf("\trm -f %s\n", zfile1);
+ printf("\trm -f %s\n", jfile1);
+ } else {
+ (void) unlink(file1);
+ (void) unlink(zfile1);
+ (void) unlink(jfile1);
+ }
}
+ if (timefnamefmt != NULL) {
+ /* If time functions fails we can't really do any sensible */
+ if (time(&now) == (time_t)-1 ||
+ localtime_r(&now, &tm) == NULL)
+ bzero(&tm, sizeof(tm));
+
+ strftime(datetimestr, sizeof(datetimestr), timefnamefmt, &tm);
+ if (archtodir)
+ (void) snprintf(file1, sizeof(file1), "%s/%s.%s",
+ dirpart, namepart, datetimestr);
+ else
+ (void) snprintf(file1, sizeof(file1), "%s.%s",
+ ent->log, datetimestr);
+
+ /* Don't run the code to move down logs */
+ numlogs_c = 0;
+ } else
+ numlogs_c = ent->numlogs; /* copy for countdown */
+
/* Move down log files */
- numlogs_c = ent->numlogs; /* copy for countdown */
while (numlogs_c--) {
(void) strlcpy(file2, file1, sizeof(file2));