Home | History | Annotate | Line # | Download | only in newsyslog
newsyslog.c revision 1.24
      1 /*	$NetBSD: newsyslog.c,v 1.24 2000/07/07 14:09:41 ad Exp $	*/
      2 
      3 /*
      4  * Copyright (c) 1999, 2000 Andrew Doran <ad (at) NetBSD.org>
      5  * All rights reserved.
      6  *
      7  * Redistribution and use in source and binary forms, with or without
      8  * modification, are permitted provided that the following conditions
      9  * are met:
     10  * 1. Redistributions of source code must retain the above copyright
     11  *    notice, this list of conditions and the following disclaimer.
     12  * 2. Redistributions in binary form must reproduce the above copyright
     13  *    notice, this list of conditions and the following disclaimer in the
     14  *    documentation and/or other materials provided with the distribution.
     15  *
     16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
     17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     19  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
     20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     26  * SUCH DAMAGE.
     27  *
     28  */
     29 
     30 /*
     31  * This file contains changes from the Open Software Foundation.
     32  */
     33 
     34 /*
     35  * Copyright 1988, 1989 by the Massachusetts Institute of Technology
     36  *
     37  * Permission to use, copy, modify, and distribute this software
     38  * and its documentation for any purpose and without fee is
     39  * hereby granted, provided that the above copyright notice
     40  * appear in all copies and that both that copyright notice and
     41  * this permission notice appear in supporting documentation,
     42  * and that the names of M.I.T. and the M.I.T. S.I.P.B. not be
     43  * used in advertising or publicity pertaining to distribution
     44  * of the software without specific, written prior permission.
     45  * M.I.T. and the M.I.T. S.I.P.B. make no representations about
     46  * the suitability of this software for any purpose.  It is
     47  * provided "as is" without express or implied warranty.
     48  *
     49  */
     50 
     51 /*
     52  * newsyslog(1) - a program to roll over log files provided that specified
     53  * critera are met, optionally preserving a number of historical log files.
     54  *
     55  * XXX too much size_t fsckage.
     56  */
     57 
     58 #include <sys/cdefs.h>
     59 #ifndef lint
     60 __RCSID("$NetBSD: newsyslog.c,v 1.24 2000/07/07 14:09:41 ad Exp $");
     61 #endif /* not lint */
     62 
     63 #include <sys/types.h>
     64 #include <sys/time.h>
     65 #include <sys/stat.h>
     66 #include <sys/param.h>
     67 
     68 #include <ctype.h>
     69 #include <fcntl.h>
     70 #include <grp.h>
     71 #include <pwd.h>
     72 #include <signal.h>
     73 #include <stdio.h>
     74 #include <stdlib.h>
     75 #include <stdarg.h>
     76 #include <string.h>
     77 #include <time.h>
     78 #include <unistd.h>
     79 #include <errno.h>
     80 #include <err.h>
     81 #include <util.h>
     82 #include <paths.h>
     83 
     84 #include "pathnames.h"
     85 
     86 #define	PRINFO(x)	((void)(verbose ? printf x : 0))
     87 
     88 #define	CE_COMPACT	1	/* Compact the achived log files */
     89 #define	CE_BINARY	2	/* Logfile is a binary file/non-syslog */
     90 #define	CE_NOSIGNAL	4	/* Don't send a signal when trimmed */
     91 #define CE_CREATE	8	/* Create log file if none exists */
     92 
     93 struct conf_entry {
     94 	uid_t	uid;			/* Owner of log */
     95 	gid_t	gid;			/* Group of log */
     96 	mode_t	mode;			/* File permissions */
     97 	int	numhist;		/* Number of historical logs to keep */
     98 	size_t	maxsize;		/* Maximum log size */
     99 	int	maxage;			/* Hours between log trimming */
    100 	int	flags;			/* Flags (CE_*) */
    101 	int	signum;			/* Signal to send */
    102 	char	pidfile[MAXPATHLEN];	/* File containing PID to signal */
    103 	char	logfile[MAXPATHLEN];	/* Path to log file */
    104 };
    105 
    106 int	verbose = 0;			/* Be verbose */
    107 int	noaction = 0;			/* Take no real action */
    108 char	hostname[MAXHOSTNAMELEN + 1];	/* Hostname, stripped of domain */
    109 
    110 int	main(int, char **);
    111 int	parse(struct conf_entry *, FILE *, size_t *);
    112 
    113 void	log_create(struct conf_entry *);
    114 void	log_examine(struct conf_entry *, int);
    115 void	log_trim(struct conf_entry *);
    116 void	log_trimmed(struct conf_entry *);
    117 
    118 int	getsig(const char *);
    119 int	isnumber(const char *);
    120 int	parseuserspec(const char *, struct passwd **, struct group **);
    121 pid_t	readpidfile(const char *);
    122 void	usage(void);
    123 
    124 /*
    125  * Program entry point.
    126  */
    127 int
    128 main(int argc, char **argv)
    129 {
    130 	struct conf_entry ent;
    131 	FILE *fd;
    132 	char *p, *cfile;
    133 	int c, force, needroot, mutex;
    134 	size_t lineno;
    135 	uid_t euid;
    136 	pid_t oldpid;
    137 
    138 	force = 0;
    139 	needroot = 1;
    140 	mutex = 1;
    141 	cfile = _PATH_NEWSYSLOGCONF;
    142 
    143 	gethostname(hostname, sizeof(hostname));
    144 	hostname[sizeof(hostname) - 1] = '\0';
    145 
    146 	/* Truncate domain */
    147 	if ((p = strchr(hostname, '.')) != NULL)
    148 		*p = '\0';
    149 
    150 	/* Parse command line options */
    151 	while ((c = getopt(argc, argv, "f:Fmnrv")) != -1) {
    152 		switch (c) {
    153 		case 'm':
    154 			mutex = 0;
    155 			break;
    156 		case 'n':
    157 			noaction = 1;
    158 			break;
    159 		case 'r':
    160 			needroot = 0;
    161 			break;
    162 		case 'v':
    163 			verbose = 1;
    164 			break;
    165 		case 'f':
    166 			cfile = optarg;
    167 			break;
    168 		case 'F':
    169 			force = 1;
    170 			break;
    171 		default:
    172 			usage();
    173 		}
    174 	}
    175 
    176 	euid = geteuid();
    177 	if (needroot && euid != 0)
    178 		errx(EXIT_FAILURE, "must be run as root");
    179 
    180 	/* Prevent multiple instances if running as root */
    181 	if (euid == 0) {
    182 		if (mutex) {
    183 			oldpid = readpidfile("newsyslog.pid");
    184 			if (oldpid != (pid_t)-1)
    185 				errx(EXIT_FAILURE, "already running (pid %ld)",
    186 				    (long)oldpid);
    187 		}
    188 		pidfile("newsyslog");
    189 	}
    190 
    191 	if (strcmp(cfile, "-") == 0)
    192 		fd = stdin;
    193 	else if ((fd = fopen(cfile, "rt")) == NULL)
    194 		err(EXIT_FAILURE, "%s", cfile);
    195 
    196 	for (lineno = 0; !parse(&ent, fd, &lineno);)
    197 		log_examine(&ent, force);
    198 
    199 	if (fd != stdin)
    200 		fclose(fd);
    201 
    202 	exit(EXIT_SUCCESS);
    203 	/* NOTREACHED */
    204 }
    205 
    206 /*
    207  * Parse a single line from the configuration file.
    208  */
    209 int
    210 parse(struct conf_entry *log, FILE *fd, size_t *_lineno)
    211 {
    212 	char *line, *q, **ap, *argv[10];
    213 	struct passwd *pw;
    214 	struct group *gr;
    215 	int nf, lineno, i;
    216 
    217 	if ((line = fparseln(fd, NULL, _lineno, NULL, 0)) == NULL)
    218 		return (-1);
    219 	lineno = (int)*_lineno;
    220 
    221 	for (ap = argv, nf = 0; (*ap = strsep(&line, " \t")) != NULL;)
    222 		if (**ap != '\0') {
    223 			if (++nf == sizeof(argv) / sizeof(argv[0])) {
    224 				warnx("config line %d: too many fields",
    225 				    lineno);
    226 				return (-1);
    227 			}
    228 			ap++;
    229 		}
    230 
    231 	if (nf == 0)
    232 		return (0);
    233 
    234 	if (nf < 6)
    235 		errx(EXIT_FAILURE, "config line %d: too few fields", lineno);
    236 
    237 	ap = argv;
    238 	strlcpy(log->logfile, *ap++, sizeof(log->logfile));
    239 
    240 	if (strchr(*ap, ':') != NULL) {
    241 		if (parseuserspec(*ap++, &pw, &gr)) {
    242 			warnx("config line %d: unknown user/group", lineno);
    243 			return (-1);
    244 		}
    245 		log->uid = pw->pw_uid;
    246 		log->gid = gr->gr_gid;
    247 		if (nf < 7)
    248 			errx(EXIT_FAILURE, "config line %d: too few fields",
    249 			    lineno);
    250 	}
    251 
    252 	if (sscanf(*ap++, "%o", &i) != 1) {
    253 		warnx("config line %d: bad permissions", lineno);
    254 		return (-1);
    255 	}
    256 	log->mode = (mode_t)i;
    257 
    258 	if (sscanf(*ap++, "%d", &log->numhist) != 0) {
    259 		warnx("config line %d: bad log count", lineno);
    260 		return (-1);
    261 	}
    262 
    263 	if (isdigit(**ap))
    264 		log->maxsize = atoi(*ap);
    265 	else if (**ap == '*')
    266 		log->maxsize = (size_t)-1;
    267 	else {
    268 		warnx("config line %d: bad log size", lineno);
    269 		return (-1);
    270 	}
    271 	ap++;
    272 
    273 	if (isdigit(**ap))
    274 		log->maxage = atoi(*ap);
    275 	else if (**ap == '*')
    276 		log->maxage = -1;
    277 	else {
    278 		warnx("config line %d: bad log age", lineno);
    279 		return (-1);
    280 	}
    281 	ap++;
    282 
    283 	log->flags = 0;
    284 	for (q = *ap++; q != NULL && *q != '\0' && !isspace(*q); q++) {
    285 		switch (tolower(*q)) {
    286 		case 'b':
    287 			log->flags |= CE_BINARY;
    288 			break;
    289 		case 'c':
    290 			log->flags |= CE_CREATE;
    291 			break;
    292 		case 'n':
    293 			log->flags |= CE_NOSIGNAL;
    294 			break;
    295 		case 'z':
    296 			log->flags |= CE_COMPACT;
    297 			break;
    298 		case '-':
    299 			break;
    300 		default:
    301 			warnx("config line %d: bad flags", lineno);
    302 			return (-1);
    303 		}
    304 	}
    305 
    306 	if (*ap != NULL && **ap == '/')
    307 		strlcpy(log->pidfile, *ap++, sizeof(log->pidfile));
    308 	else
    309 		log->pidfile[0] = '\0';
    310 
    311 	if (*ap != NULL && (log->signum = getsig(*ap++)) < 0) {
    312 		warnx("config line %d: bad signal type", lineno);
    313 		return (-1);
    314 	} else
    315 		log->signum = SIGHUP;
    316 
    317 	return (0);
    318 }
    319 
    320 /*
    321  * Examine a log file.  If the trim conditions are met, call log_trim() to
    322  * trim the log file.
    323  */
    324 void
    325 log_examine(struct conf_entry *ent, int force)
    326 {
    327 	struct stat sb;
    328 	size_t size;
    329 	int modtime;
    330 	char tmp[MAXPATHLEN];
    331 	time_t now;
    332 
    333 	if (ent->logfile[0] == '\0')
    334 		return;
    335 	if (stat(ent->logfile, &sb) < 0) {
    336 		if (errno == ENOENT && (ent->flags & CE_CREATE) != 0) {
    337 			PRINFO(("%s: no file, creating\n", ent->logfile));
    338 			log_create(ent);
    339 			errno = 0;
    340 			stat(ent->logfile, &sb);
    341 		}
    342 
    343 		if (errno != 0) {
    344 			PRINFO(("%s: %s\n", ent->logfile, strerror(errno)));
    345 			return;
    346 		}
    347 	}
    348 
    349 	if (verbose) {
    350 		if ((ent->flags & CE_COMPACT) != 0)
    351 			PRINFO(("%s <%dZ>: ", ent->logfile, ent->numhist));
    352 		else
    353 			PRINFO(("%s <%d>: ", ent->logfile, ent->numhist));
    354 	}
    355 
    356 	size = ((size_t)sb.st_blocks * S_BLKSIZE) >> 10;
    357 
    358 	now = time(NULL);
    359 	strlcpy(tmp, ent->logfile, sizeof(tmp));
    360 	strlcat(tmp, ".0", sizeof(tmp));
    361 	if (stat(tmp, &sb) < 0) {
    362 		strlcat(tmp, ".gz", sizeof(tmp));
    363 		if (stat(tmp, &sb) < 0)
    364 			modtime = -1;
    365 		else
    366 			modtime = (int)(now - sb.st_mtime + 1800) / 3600;
    367 	} else
    368 		modtime = (int)(now - sb.st_mtime + 1800) / 3600;
    369 
    370 	if (verbose) {
    371 		if (ent->maxsize != (size_t)-1)
    372 			PRINFO(("size (Kb): %d [%d] ", size, ent->maxsize));
    373 		if (ent->maxage > 0)
    374 			PRINFO((" age (hr): %d [%d] ", modtime, ent->maxage));
    375 	}
    376 
    377 	/*
    378 	 * Note: if maxage is used as a trim condition, we need at least one
    379 	 * historical log file to determine the `age' of the active log file.
    380 	 */
    381 	if ((ent->maxage > 0 && (modtime >= ent->maxage || modtime < 0)) ||
    382 	    size >= ent->maxsize || force) {
    383 		PRINFO(("--> trimming log....\n"));
    384 		log_trim(ent);
    385 	} else
    386 		PRINFO(("--> skipping\n"));
    387 }
    388 
    389 /*
    390  * Trim the specified log file.
    391  */
    392 void
    393 log_trim(struct conf_entry *log)
    394 {
    395 	char file1[MAXPATHLEN], file2[MAXPATHLEN];
    396 	char zfile1[MAXPATHLEN], zfile2[MAXPATHLEN];
    397 	int i;
    398 	struct stat st;
    399 	pid_t pid;
    400 
    401 	/* Remove oldest log */
    402 	snprintf(file1, sizeof(file1), "%s.%d", log->logfile,
    403 	    log->numhist - 1);
    404 	strcpy(zfile1, file1);
    405 	strlcat(zfile1, ".gz", sizeof(zfile1));
    406 
    407 	PRINFO(("rm -f %s\n", file1));
    408 	unlink(file1);
    409 	PRINFO(("rm -f %s\n", zfile1));
    410 	unlink(zfile1);
    411 
    412 	/* Move down log files. */
    413 	for (i = log->numhist - 1; i != 0; i--) {
    414 		strcpy(file2, file1);
    415 		snprintf(file1, sizeof(file1), "%s.%d", log->logfile, i - 1);
    416 		strcpy(zfile1, file1);
    417 		strcpy(zfile2, file2);
    418 
    419 		if (lstat(file1, &st) != 0) {
    420 			strlcat(zfile1, ".gz", sizeof(zfile1));
    421 			strlcat(zfile2, ".gz", sizeof(zfile2));
    422 			if (lstat(zfile1, &st) != 0)
    423 				continue;
    424 		}
    425 
    426 		PRINFO(("mv %s %s\n", zfile1, zfile2));
    427 		rename(zfile1, zfile2);
    428 		PRINFO(("chmod %o %s\n", log->mode, zfile2));
    429 		chmod(zfile2, log->mode);
    430 		PRINFO(("chown %d.%d %s\n", log->uid, log->gid, zfile2));
    431 		chown(zfile2, log->uid, log->gid);
    432 	}
    433 
    434 	if ((log->flags & CE_BINARY) == 0)
    435 		log_trimmed(log);
    436 
    437 	if (log->numhist == 0) {
    438 		PRINFO(("rm %s\n", log->logfile));
    439 		unlink(log->logfile);
    440 	} else {
    441 		PRINFO(("mv %s to %s\n", log->logfile, file1));
    442 		rename(log->logfile, file1);
    443 	}
    444 
    445 	PRINFO(("Start new log...\n"));
    446 	log_create(log);
    447 
    448 	if ((log->flags & CE_BINARY) == 0)
    449 		log_trimmed(log);
    450 
    451 	PRINFO(("chmod %o %s\n", log->mode, log->logfile));
    452 	chmod(log->logfile, log->mode);
    453 
    454 	if ((log->flags & CE_NOSIGNAL) == 0) {
    455 		if (log->pidfile[0] != '\0')
    456 			pid = readpidfile(log->pidfile);
    457 		else
    458 			pid = readpidfile(_PATH_SYSLOGDPID);
    459 
    460 		if (pid != (pid_t)-1) {
    461 			PRINFO(("kill -%s %d\n", sys_signame[log->signum],
    462 			    pid));
    463 			if (kill(pid, log->signum))
    464 				warn("warning - could not signal daemon");
    465 		}
    466 	}
    467 
    468 	if ((log->flags & CE_COMPACT) != 0) {
    469 		PRINFO(("gzip %s.0\n", log->logfile));
    470 
    471 		if ((pid = fork()) < 0)
    472 			err(EXIT_FAILURE, "fork");
    473 		else if (pid == 0) {
    474 			snprintf(file1, sizeof(file1), "%s.0", log->logfile);
    475 			execl(_PATH_GZIP, "gzip", "-f", file1, NULL);
    476 			err(EXIT_FAILURE, _PATH_GZIP);
    477 		}
    478 	}
    479 }
    480 
    481 /*
    482  * Write an entry to the log file recording the fact that it was trimmed.
    483  */
    484 void
    485 log_trimmed(struct conf_entry *log)
    486 {
    487 	FILE *fd;
    488 	time_t now;
    489 	char *daytime;
    490 
    491 	if ((fd = fopen(log->logfile, "at")) == NULL)
    492 		err(EXIT_FAILURE, "%s", log->logfile);
    493 
    494 	now = time(NULL);
    495 	daytime = ctime(&now) + 4;
    496 	daytime[15] = '\0';
    497 
    498 	fprintf(fd, "%s %s newsyslog[%ld]: log file turned over\n", daytime,
    499 	    hostname, (u_long)getpid());
    500 	fclose(fd);
    501 }
    502 
    503 /*
    504  * Create a new log file.
    505  */
    506 void
    507 log_create(struct conf_entry *ent)
    508 {
    509 	int fd;
    510 
    511 	if ((fd = creat(ent->logfile, ent->mode)) < 0)
    512 		err(EXIT_FAILURE, "%s", ent->logfile);
    513 	if (fchown(fd, ent->uid, ent->gid) < 0)
    514 		err(EXIT_FAILURE, "%s", ent->logfile);
    515 	if (close(fd) < 0)
    516 		err(EXIT_FAILURE, "%s", ent->logfile);
    517 }
    518 
    519 /*
    520  * Display program usage information.
    521  */
    522 void
    523 usage(void)
    524 {
    525 
    526 	fprintf(stderr, "usage: newsyslog [-Frv] [-f config-file]\n");
    527 	exit(EXIT_FAILURE);
    528 }
    529 
    530 /*
    531  * Return non-zero if a string represents a decimal value.
    532  */
    533 int
    534 isnumber(const char *string)
    535 {
    536 
    537 	while (isdigit(*string))
    538 		string++;
    539 
    540 	return (*string == '\0');
    541 }
    542 
    543 /*
    544  * Given a signal name, attempt to find the corresponding signal number.
    545  */
    546 int
    547 getsig(const char *sig)
    548 {
    549 	char *p;
    550 	int n;
    551 
    552 	if (isnumber(sig)) {
    553 		n = (int)strtol(sig, &p, 0);
    554 		if (p != '\0' || (unsigned)n >= NSIG)
    555 			return (-1);
    556 		return (n);
    557 	}
    558 
    559 	if (strncasecmp(sig, "sig", 3) == 0)
    560 		sig += 3;
    561 	for (n = 1; n < NSIG; n++)
    562 		if (strcasecmp(sys_signame[n], sig) == 0)
    563 			return (n);
    564 	return (-1);
    565 }
    566 
    567 /*
    568  * Given a path to a PID file, return the PID contained within.
    569  */
    570 pid_t
    571 readpidfile(const char *file)
    572 {
    573 	FILE *fd;
    574 	char line[BUFSIZ];
    575 	char tmp[MAXPATHLEN];
    576 	pid_t pid;
    577 
    578 	if (file[0] != '/') {
    579 		strcpy(tmp, _PATH_VARRUN);
    580 		strlcat(tmp, file, sizeof(tmp));
    581 	} else
    582 		strlcpy(tmp, file, sizeof(tmp));
    583 
    584 	if ((fd = fopen(tmp, "rt")) == NULL) {
    585 		warn("%s", tmp);
    586 		return (-1);
    587 	}
    588 
    589 	if (fgets(line, sizeof(line) - 1, fd) != NULL) {
    590 		line[sizeof(line) - 1] = '\0';
    591 		pid = (pid_t)strtol(line, NULL, 0);
    592 	}
    593 
    594 	fclose(fd);
    595 	return (pid);
    596 }
    597 
    598 /*
    599  * Parse a user:group specification.
    600  *
    601  * XXX This is over the top for newsyslog(1).  It should be moved to libutil.
    602  */
    603 int
    604 parseuserspec(const char *name, struct passwd **pw, struct group **gr)
    605 {
    606 	char buf[MAXLOGNAME * 2 + 2], *group;
    607 
    608 	strlcpy(buf, name, sizeof(buf));
    609 	*gr = NULL;
    610 
    611 	/*
    612 	 * Before attempting to use '.' as a separator, see if the whole
    613 	 * string resolves as a user name or UID.
    614 	 */
    615 	if ((*pw = getpwnam(buf)) != NULL) {
    616 		*gr = getgrgid((*pw)->pw_gid);
    617 		return (0);
    618 	}
    619 
    620 	/* Split the user and group name. */
    621 	if ((group = strchr(buf, ':')) != NULL ||
    622 	    (group = strchr(buf, '.')) != NULL)
    623 		*group++ = '\0';
    624 
    625 	if (isnumber(buf))
    626 		*pw = getpwuid((uid_t)atoi(buf));
    627 	else
    628 		*pw = getpwnam(buf);
    629 
    630 	/*
    631 	 * Find the group.  If a group wasn't specified, use the user's
    632 	 * `natural' group.  We get to this point even if no user was found.
    633 	 * This is to allow the caller to get a better idea of what went
    634 	 * wrong, if anything.
    635 	 */
    636 	if (group == NULL || *group == '\0') {
    637 		if (*pw == NULL)
    638 			return (-1);
    639 		*gr = getgrgid((*pw)->pw_gid);
    640 	} else if (isnumber(group))
    641 		*gr = getgrgid((gid_t)atoi(group));
    642 	else
    643 		*gr = getgrnam(group);
    644 
    645 	return (*gr != NULL ? 0 : -1);
    646 }
    647