Index | Thread | Search

From:
Alvar Penning <post@0x21.biz>
Subject:
newsyslog: support glob(3) through new G flag
To:
tech@openbsd.org
Date:
Tue, 23 Apr 2024 23:28:47 +0200

Download raw body.

Thread
Hi all,

A patch against newsyslog(8) follows, implementing optional globing.
   
newsyslog.conf lines with the newly introduced G flag having their
logfile_name now being parsed as a glob(3).
    
This required only little changes to the parse_file function, mostly
extracting those parts which would be repeated for each globed file
entry. Even while the diff might look longer, lots of lines just got
moved from one place to another and have now another indentation level.

While reflecting this change in newsyslog.8, the text for the flags
field was reformatted to list each possible flag in a list, easing the
readability a bit.

Other man page changes were noting that the default compression is gzip,
as this was formulated quite open, and correcting that up to four
optional fields might be present, e.g., owner:group, flags, pid_file,
and signal.

Please take a look at the diff and tell me what you think about it.

Best,
Alvar


diff --git usr.bin/newsyslog/newsyslog.8 usr.bin/newsyslog/newsyslog.8
index 9e3f84a0b26..a8326e1dc91 100644
--- usr.bin/newsyslog/newsyslog.8
+++ usr.bin/newsyslog/newsyslog.8
@@ -170,7 +170,7 @@ By default, this configuration file is
 Each line of the file contains information about a particular log file
 that should be handled by
 .Nm newsyslog .
-Each line has five mandatory fields and up to three optional fields, with
+Each line has five mandatory fields and up to four optional fields, with
 whitespace separating each field.
 Blank lines or lines beginning with a hash mark
 .Pq Ql #
@@ -180,6 +180,9 @@ follows:
 .Bl -tag -width XXXXXXXXXXXXXXXX
 .It Ar logfile_name
 The full pathname of the system log file to be archived.
+Globbing is supported by setting the
+.Sq G
+flag.
 .It Ar owner:group
 This optional field specifies the owner and group for the archive file.
 The
@@ -359,8 +362,13 @@ rotate on every 5th day of the month at 6:00 hr
 .It Ar flags
 The optional
 .Ar flags
-field specifies if the archives should have any special processing
-done to the archived log files.
+field specifies any special processing for each entry.
+Multiple
+.Ar flags
+can be concatenated together.
+.Pp
+.Bl -tag -width Ds -compact -offset indent
+.It Li Z
 The
 .Sq Z
 flag will make the archive
@@ -368,7 +376,9 @@ files compressed to save space using
 .Xr gzip 1
 or
 .Xr compress 1 ,
-depending on compilation options.
+depending on compilation options, defaulting to
+.Xr gzip 1 .
+.It Li B
 The
 .Sq B
 flag means that the file is a
@@ -376,13 +386,24 @@ 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.
+.It Li M
 The
 .Sq M
 flag marks this entry as a monitored
 log file.
+.It Li F
 The
 .Sq F
 flag specifies that symbolic links should be followed.
+.It Li G
+The
+.Sq G
+flag results in the
+.Ar logfile_name
+being handled as a
+.Xr glob 3
+pattern.
+.El
 .It Ar monitor
 Specify the username (or email address) that should receive notification
 messages if this is a monitored log file.
diff --git usr.bin/newsyslog/newsyslog.c usr.bin/newsyslog/newsyslog.c
index 852f1dfac87..0e6fcc2bca1 100644
--- usr.bin/newsyslog/newsyslog.c
+++ usr.bin/newsyslog/newsyslog.c
@@ -88,6 +88,7 @@
 #include <err.h>
 #include <errno.h>
 #include <fcntl.h>
+#include <glob.h>
 #include <grp.h>
 #include <limits.h>
 #include <pwd.h>
@@ -105,6 +106,7 @@
 #define CE_MONITOR	0x08		/* Monitor for changes */
 #define CE_FOLLOW	0x10		/* Follow symbolic links */
 #define CE_TRIMAT	0x20		/* Trim at a specific time */
+#define CE_GLOB	0x40		/* Treat logfile_name as a glob(3) */
 
 #define	MIN_PID		2		/* Don't touch pids lower than this */
 #define	MIN_SIZE	256		/* Don't rotate if smaller (in bytes) */
@@ -160,6 +162,7 @@ int	stat_suffix(char *, size_t, char *, struct stat *,
 	    int (*)(const char *, struct stat *));
 off_t	sizefile(struct stat *);
 int	parse_file(struct entrylist *, int *);
+int parse_entry(struct entrylist *, int *, int, struct conf_entry *);
 time_t	parse8601(char *);
 time_t	parseDWM(char *);
 void	child_killer(int);
@@ -301,10 +304,11 @@ do_entry(struct conf_entry *ent)
 	if (ent->gid == (gid_t)-1)
 		ent->gid = sb.st_gid;
 
-	DPRINTF(("%s <%d%s%s%s%s>: ", ent->log, ent->numlogs,
+	DPRINTF(("%s <%d%s%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_GLOB) ? "G" : "",
 	    (ent->flags & CE_MONITOR) && monitormode ? "M" : ""));
 	size = sizefile(&sb);
 	modhours = age_old_log(ent);
@@ -332,11 +336,12 @@ do_entry(struct conf_entry *ent)
 	    && ((ent->flags & CE_BINARY) || size >= MIN_SIZE)))) {
 		DPRINTF(("--> trimming log....\n"));
 		if (noaction && !verbose)
-			printf("%s <%d%s%s%s>\n", ent->log,
+			printf("%s <%d%s%s%s%s>\n", ent->log,
 			    ent->numlogs,
 			    (ent->flags & CE_COMPACT) ? "Z" : "",
 			    (ent->flags & CE_BINARY) ? "B" : "",
-			    (ent->flags & CE_FOLLOW) ? "F" : "");
+			    (ent->flags & CE_FOLLOW) ? "F" : "",
+			    (ent->flags & CE_GLOB) ? "G" : "");
 		dotrim(ent);
 		ent->flags |= CE_ROTATED;
 	} else
@@ -471,12 +476,13 @@ int
 parse_file(struct entrylist *list, int *nentries)
 {
 	char line[BUFSIZ], *parse, *q, *errline, *group, *tmp, *ep;
-	struct conf_entry *working;
-	struct stat sb;
+	struct conf_entry *working, *working_glob;
+	size_t i;
 	int lineno = 0;
 	int ret = 0;
 	FILE *f;
 	long l;
+	glob_t g;
 
 	if (strcmp(conf, "-") == 0)
 		f = stdin;
@@ -504,9 +510,6 @@ nextline:
 		if (working->log == NULL)
 			err(1, NULL);
 
-		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 ||
@@ -616,7 +619,8 @@ nextline:
 
 		q = sob(++parse);	/* Optional field */
 		if (*q == 'Z' || *q == 'z' || *q == 'B' || *q == 'b' ||
-		    *q == 'M' || *q == 'm' || *q == 'F' || *q == 'f') {
+		    *q == 'M' || *q == 'm' || *q == 'F' || *q == 'f' ||
+		    *q == 'G' || *q == 'g') {
 			*(parse = son(q)) = '\0';
 			while (*q) {
 				switch (*q) {
@@ -636,6 +640,10 @@ nextline:
 				case 'f':
 					working->flags |= CE_FOLLOW;
 					break;
+				case 'G':
+				case 'g':
+					working->flags |= CE_GLOB;
+					break;
 				default:
 					warnx("%s:%d: illegal flag: `%c'"
 					    " --> skipping",
@@ -717,57 +725,106 @@ nextline:
 			goto nextline;
 		}
 
-		/* 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, NULL);
-				*(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) {
-				warnx("%s:%d: pathname too long: %s"
-				    " --> skipping", conf, lineno, q);
+		if (working->flags & CE_GLOB) {
+			if (glob(working->log, GLOB_NOSORT, NULL, &g)) {
+				warnx("%s:%d: cannot glob(3), %s --> skipping",
+					conf, lineno, strerror(errno));
 				ret = 1;
 				goto nextline;
 			}
-		} else {
-			if (snprintf(line, sizeof(line), "%s.%d%s",
-			    working->log, working->numlogs, COMPRESS_POSTFIX)
-			    >= PATH_MAX) {
-				warnx("%s:%d: pathname too long: %s"
-				    " --> skipping", conf, lineno,
-				    working->log);
+			DPRINTF(("%s:%d: expanded glob %s to %zu files\n",
+			    conf, lineno, working->log, g.gl_pathc));
+
+			for (i = 0; i < g.gl_pathc; i++) {
+				working_glob = malloc(sizeof(*working));
+				if (working_glob == NULL)
+					err(1, NULL);
+
+				memcpy(working_glob, working, sizeof(*working));
+
+				if ((working_glob->log = strdup(g.gl_pathv[i])) == NULL)
+					err(1, NULL);
+
+				if (parse_entry(list, nentries, lineno, working_glob)) {
+					ret = 1;
+					goto nextline;
+				}
+			}
+
+			/*
+			 * It is not necessary to duplicate and free the other three char
+			 * pointers in conf_entry as those values are identical for each
+			 * globed file. Even more important, later on they will only be
+			 * referenced in a reading fashion.
+			 */
+			free(working->log);
+			free(working);
+
+			globfree(&g);
+		} else
+			if (parse_entry(list, nentries, lineno, working)) {
 				ret = 1;
 				goto nextline;
 			}
-		}
-		TAILQ_INSERT_TAIL(list, working, next);
-		(*nentries)++;
 	}
 	(void)fclose(f);
 	return (ret);
 }
 
+int
+parse_entry(
+	struct entrylist *list, int *nentries,
+	int lineno, struct conf_entry *working
+)
+{
+	char line[BUFSIZ];
+	int linelen;
+	struct stat sb;
+
+	if ((working->logbase = strrchr(working->log, '/')) != NULL)
+		working->logbase++;
+
+	/* 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, NULL);
+			*(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)
+		linelen = snprintf(line, sizeof(line), "%s/%s.%d%s",
+		    working->backdir, working->logbase,
+		    working->numlogs, COMPRESS_POSTFIX);
+	else
+		linelen = snprintf(line, sizeof(line), "%s.%d%s",
+		    working->log, working->numlogs, COMPRESS_POSTFIX);
+	if (linelen >= PATH_MAX) {
+		warnx("%s:%d: pathname too long: %s --> skipping", conf, lineno, line);
+		return 1;
+	}
+
+	TAILQ_INSERT_TAIL(list, working, next);
+	(*nentries)++;
+	return 0;
+}
+
 char *
 missing_field(char *p, char *errline, int lineno)
 {