Index: usr.sbin/syslogd/syslog.conf.5 =================================================================== --- usr.sbin/syslogd/syslog.conf.5 +++ usr.sbin/syslogd/syslog.conf.5 @@ -28,7 +28,7 @@ .\" @(#)syslog.conf.5 8.1 (Berkeley) 6/9/93 .\" $FreeBSD$ .\" -.Dd November 1, 2016 +.Dd June 25, 2019 .Dt SYSLOG.CONF 5 .Os .Sh NAME @@ -342,6 +342,21 @@ .Dq - option may improve performance, especially if the kernel is logging many messages. +.Pp +Options may be specified after the pathname. +The options, delimited by whitespace, take the form of a letter, +potentially followed by an associated value. +The available options are: +.Bl -tag -indent +.It Ar R size +Limit the growth of the file to the given size. +If the file exceeds that size, enter +.Dq recycle +mode, in which the last 32 KBytes of the file become a circular buffer +which is continually overwritten, preserving the bulk of the original file. +The size is specified in bytes, with the usual power-of-two suffixes +recognized. +.El .It A hostname (preceded by an at .Pq Dq @ Index: usr.sbin/syslogd/syslogd.c =================================================================== --- usr.sbin/syslogd/syslogd.c +++ usr.sbin/syslogd/syslogd.c @@ -199,6 +199,24 @@ static STAILQ_HEAD(, socklist) shead = STAILQ_HEAD_INITIALIZER(shead); /* + * If a file has a size limit specified, and that limit is exceeded, we treat + * the last few kbytes of the file as a circular buffer and repeatedly overwrite + * it. When that happens we write a msg to the file saying that we're doing so. + * This effectively turns the end of the file into a small circular buffer but + * preserves the bulk of the file (and thus hopefully preserves some clue as to + * what began spewing to the log to create this condition). + * + */ +#define LIMIT_CIRCBUF_SIZE (32 * 1024) +#define LIMIT_CIRCBUF_MESSAGE \ +"\n" \ +"\n*******************************************************************************" \ +"\nsyslogd: Configured file size limit has been exceeded; data has been lost." \ +"\nsyslogd: From here down the file is being recycled in circular-buffer mode." \ +"\n*******************************************************************************" \ +"\n" + +/* * Flags to logmsg(). */ @@ -242,8 +260,11 @@ struct addrinfo *f_addr; } f_forw; /* forwarding address */ - char f_fname[MAXPATHLEN]; struct { + char f_fname[MAXPATHLEN]; + off_t f_fsizelimit; + } f_logfile; + struct { char f_pname[MAXPATHLEN]; pid_t f_pid; } f_pipe; @@ -251,7 +272,8 @@ #define fu_uname f_un.f_uname #define fu_forw_hname f_un.f_forw.f_hname #define fu_forw_addr f_un.f_forw.f_addr -#define fu_fname f_un.f_fname +#define fu_fname f_un.f_logfile.f_fname +#define fu_fsizelimit f_un.f_logfile.f_fsizelimit #define fu_pipe_pname f_un.f_pipe.f_pname #define fu_pipe_pid f_un.f_pipe.f_pid char f_prevline[MAXSVLINE]; /* last message logged */ @@ -1727,6 +1749,18 @@ case F_FILE: dprintf(" %s\n", f->fu_fname); iovlist_append(il, "\n"); + /* + * If this file has a size limit, and we're about to write to a + * location beyond that limit, seek to limit-CIRCBUF_SIZE before + * writing and drop a message into the file to say we lost data. + */ + if (f->fu_fsizelimit != 0 && f->fu_fsizelimit < + lseek(f->f_file, 0, SEEK_CUR)) { + lseek(f->f_file, f->fu_fsizelimit - LIMIT_CIRCBUF_SIZE, + SEEK_SET); + write(f->f_file, LIMIT_CIRCBUF_MESSAGE, + sizeof(LIMIT_CIRCBUF_MESSAGE) - 1); + } if (writev(f->f_file, il->iov, il->iovcnt) < 0) { /* * If writev(2) fails for potentially transient errors @@ -2529,6 +2563,42 @@ } /* + * Parse a filename config line to extract the filename and any options that may + * follow it on the line, storing the values into f->f_un.logfile. + */ +static void +parse_fline(const char *p, struct filed *f) +{ + char *q, *r, *s; + uint64_t sizelimit; + + (void)strlcpy(f->fu_fname, p, sizeof(f->fu_fname)); + + /* If there are no spaces on the line, no options follow the name. */ + if ((q = strpbrk(f->fu_fname, "\t ")) == NULL) + return; + + /* The only option we support right now is 'R' for recycle. */ + r = q + strspn(q, "\t "); + if (*r++ != 'R') + return; + + /* The thing following the R must be numeric. */ + s = r + strspn(r, "\t "); + if (expand_number(s, &sizelimit) == -1) + return; + + /* + * Looks like a valid R option is present. Terminate fu_name at + * the first space and store the limit associated with the file. + */ + *q = 0; + f->fu_fsizelimit = sizelimit; + if (f->fu_fsizelimit < LIMIT_CIRCBUF_SIZE) + f->fu_fsizelimit = LIMIT_CIRCBUF_SIZE; +} + +/* * Crack a configuration file line */ static struct filed * @@ -2746,9 +2816,14 @@ break; case '/': - if ((f->f_file = open(p, logflags, 0600)) < 0) { + /* Parse the filename and any options into f->f_un.f_logfile. */ + parse_fline(p, f); + if (f->fu_fsizelimit != 0) { + logflags &= ~O_APPEND; + } + if ((f->f_file = open(f->fu_fname, logflags, 0600)) < 0) { f->f_type = F_UNUSED; - logerror(p); + logerror(f->fu_fname); break; } if (syncfile) @@ -2761,7 +2836,12 @@ (void)strlcpy(f->fu_fname, p + sizeof(_PATH_DEV) - 1, sizeof(f->fu_fname)); } else { - (void)strlcpy(f->fu_fname, p, sizeof(f->fu_fname)); + /* + * O_APPEND isn't used when limiting file size; limiting + * requires seeking. Start by seeking to end of file. + */ + if (f->fu_fsizelimit != 0) + lseek(f->f_file, 0, SEEK_END); f->f_type = F_FILE; } break;