From: Alvar Penning Subject: newsyslog: support glob(3) through new G flag To: tech@openbsd.org Date: Tue, 23 Apr 2024 23:28:47 +0200 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 #include #include +#include #include #include #include @@ -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) {