newsyslog.c revision 119904
1/*
2 * This file contains changes from the Open Software Foundation.
3 */
4
5/*
6 * Copyright 1988, 1989 by the Massachusetts Institute of Technology
7 *
8 * Permission to use, copy, modify, and distribute this software and its
9 * documentation for any purpose and without fee is hereby granted, provided
10 * that the above copyright notice appear in all copies and that both that
11 * copyright notice and this permission notice appear in supporting
12 * documentation, and that the names of M.I.T. and the M.I.T. S.I.P.B. not be
13 * used in advertising or publicity pertaining to distribution of the
14 * software without specific, written prior permission. M.I.T. and the M.I.T.
15 * S.I.P.B. make no representations about the suitability of this software
16 * for any purpose.  It is provided "as is" without express or implied
17 * warranty.
18 *
19 */
20
21/*
22 * newsyslog - roll over selected logs at the appropriate time, keeping the a
23 * specified number of backup files around.
24 */
25
26#include <sys/cdefs.h>
27__FBSDID("$FreeBSD: head/usr.sbin/newsyslog/newsyslog.c 119904 2003-09-09 05:23:06Z gad $");
28
29#define OSF
30#ifndef COMPRESS_POSTFIX
31#define COMPRESS_POSTFIX ".gz"
32#endif
33#ifndef	BZCOMPRESS_POSTFIX
34#define	BZCOMPRESS_POSTFIX ".bz2"
35#endif
36
37#include <sys/param.h>
38#include <sys/stat.h>
39#include <sys/wait.h>
40
41#include <ctype.h>
42#include <err.h>
43#include <errno.h>
44#include <fcntl.h>
45#include <fnmatch.h>
46#include <glob.h>
47#include <grp.h>
48#include <paths.h>
49#include <pwd.h>
50#include <signal.h>
51#include <stdio.h>
52#include <stdlib.h>
53#include <string.h>
54#include <time.h>
55#include <unistd.h>
56
57#include "pathnames.h"
58
59/*
60 * Bit-values for the 'flags' parsed from a config-file entry.
61 */
62#define CE_COMPACT	0x0001	/* Compact the achived log files with gzip. */
63#define CE_BZCOMPACT	0x0002	/* Compact the achived log files with bzip2. */
64#define CE_COMPACTWAIT	0x0004	/* wait until compressing one file finishes */
65				/*    before starting the next step. */
66#define CE_BINARY	0x0008	/* Logfile is in binary, do not add status */
67				/*    messages to logfile(s) when rotating. */
68#define CE_NOSIGNAL	0x0010	/* There is no process to signal when */
69				/*    trimming this file. */
70#define CE_TRIMAT	0x0020	/* trim file at a specific time. */
71#define CE_GLOB		0x0040	/* name of the log is file name pattern. */
72#define CE_SIGNALGROUP	0x0080	/* Signal a process-group instead of a single */
73				/*    process when trimming this file. */
74#define CE_CREATE	0x0100	/* Create the log file if it does not exist. */
75
76#define MIN_PID         5	/* Don't touch pids lower than this */
77#define MAX_PID		99999	/* was lower, see /usr/include/sys/proc.h */
78
79#define kbytes(size)  (((size) + 1023) >> 10)
80
81struct conf_entry {
82	char *log;		/* Name of the log */
83	char *pid_file;		/* PID file */
84	char *r_reason;		/* The reason this file is being rotated */
85	int firstcreate;	/* Creating log for the first time (-C). */
86	int rotate;		/* Non-zero if this file should be rotated */
87	uid_t uid;		/* Owner of log */
88	gid_t gid;		/* Group of log */
89	int numlogs;		/* Number of logs to keep */
90	int size;		/* Size cutoff to trigger trimming the log */
91	int hours;		/* Hours between log trimming */
92	time_t trim_at;		/* Specific time to do trimming */
93	int permissions;	/* File permissions on the log */
94	int flags;		/* CE_COMPACT, CE_BZCOMPACT, CE_BINARY */
95	int sig;		/* Signal to send */
96	int def_cfg;		/* Using the <default> rule for this file */
97	struct conf_entry *next;/* Linked list pointer */
98};
99
100#define DEFAULT_MARKER "<default>"
101
102int archtodir = 0;		/* Archive old logfiles to other directory */
103int createlogs;			/* Create (non-GLOB) logfiles which do not */
104				/*    already exist.  1=='for entries with */
105				/*    C flag', 2=='for all entries'. */
106int verbose = 0;		/* Print out what's going on */
107int needroot = 1;		/* Root privs are necessary */
108int noaction = 0;		/* Don't do anything, just show it */
109int nosignal;			/* Do not send any signals */
110int force = 0;			/* Force the trim no matter what */
111int rotatereq = 0;		/* -R = Always rotate the file(s) as given */
112				/*    on the command (this also requires   */
113				/*    that a list of files *are* given on  */
114				/*    the run command). */
115char *requestor;		/* The name given on a -R request */
116char *archdirname;		/* Directory path to old logfiles archive */
117const char *conf;		/* Configuration file to use */
118time_t dbg_timenow;		/* A "timenow" value set via -D option */
119time_t timenow;
120
121char hostname[MAXHOSTNAMELEN];	/* hostname */
122char daytime[16];		/* timenow in human readable form */
123
124static struct conf_entry *get_worklist(char **files);
125static void parse_file(FILE *cf, const char *cfname, struct conf_entry **work_p,
126		struct conf_entry **glob_p, struct conf_entry **defconf_p);
127static char *sob(char *p);
128static char *son(char *p);
129static int isnumberstr(const char *);
130static char *missing_field(char *p, char *errline);
131static void do_entry(struct conf_entry * ent);
132static void expand_globs(struct conf_entry **work_p,
133		struct conf_entry **glob_p);
134static void free_clist(struct conf_entry **firstent);
135static void free_entry(struct conf_entry *ent);
136static struct conf_entry *init_entry(const char *fname,
137		struct conf_entry *src_entry);
138static void parse_args(int argc, char **argv);
139static int parse_doption(const char *doption);
140static void usage(void);
141static void dotrim(const struct conf_entry *ent, char *log,
142		int numdays, int flags);
143static int log_trim(const char *log, const struct conf_entry *log_ent);
144static void compress_log(char *log, int dowait);
145static void bzcompress_log(char *log, int dowait);
146static int sizefile(char *file);
147static int age_old_log(char *file);
148static int send_signal(const struct conf_entry *ent);
149static void movefile(char *from, char *to, int perm, uid_t owner_uid,
150		gid_t group_gid);
151static void createdir(const struct conf_entry *ent, char *dirpart);
152static void createlog(const struct conf_entry *ent);
153static time_t parse8601(const char *s);
154static time_t parseDWM(char *s);
155
156/*
157 * All the following are defined to work on an 'int', in the
158 * range 0 to 255, plus EOF.  Define wrappers which can take
159 * values of type 'char', either signed or unsigned.
160 */
161#define isdigitch(Anychar)    isdigit(((int) Anychar) & 255)
162#define isprintch(Anychar)    isprint(((int) Anychar) & 255)
163#define isspacech(Anychar)    isspace(((int) Anychar) & 255)
164#define tolowerch(Anychar)    tolower(((int) Anychar) & 255)
165
166int
167main(int argc, char **argv)
168{
169	struct conf_entry *p, *q;
170
171	parse_args(argc, argv);
172	argc -= optind;
173	argv += optind;
174
175	if (needroot && getuid() && geteuid())
176		errx(1, "must have root privs");
177	p = q = get_worklist(argv);
178
179	while (p) {
180		do_entry(p);
181		p = p->next;
182		free_entry(q);
183		q = p;
184	}
185	while (wait(NULL) > 0 || errno == EINTR)
186		;
187	return (0);
188}
189
190static struct conf_entry *
191init_entry(const char *fname, struct conf_entry *src_entry)
192{
193	struct conf_entry *tempwork;
194
195	if (verbose > 4)
196		printf("\t--> [creating entry for %s]\n", fname);
197
198	tempwork = malloc(sizeof(struct conf_entry));
199	if (tempwork == NULL)
200		err(1, "malloc of conf_entry for %s", fname);
201
202	tempwork->log = strdup(fname);
203	if (tempwork->log == NULL)
204		err(1, "strdup for %s", fname);
205
206	if (src_entry != NULL) {
207		tempwork->pid_file = NULL;
208		if (src_entry->pid_file)
209			tempwork->pid_file = strdup(src_entry->pid_file);
210		tempwork->r_reason = NULL;
211		tempwork->firstcreate = 0;
212		tempwork->rotate = 0;
213		tempwork->uid = src_entry->uid;
214		tempwork->gid = src_entry->gid;
215		tempwork->numlogs = src_entry->numlogs;
216		tempwork->size = src_entry->size;
217		tempwork->hours = src_entry->hours;
218		tempwork->trim_at = src_entry->trim_at;
219		tempwork->permissions = src_entry->permissions;
220		tempwork->flags = src_entry->flags;
221		tempwork->sig = src_entry->sig;
222		tempwork->def_cfg = src_entry->def_cfg;
223	} else {
224		/* Initialize as a "do-nothing" entry */
225		tempwork->pid_file = NULL;
226		tempwork->r_reason = NULL;
227		tempwork->firstcreate = 0;
228		tempwork->rotate = 0;
229		tempwork->uid = (uid_t)-1;
230		tempwork->gid = (gid_t)-1;
231		tempwork->numlogs = 1;
232		tempwork->size = -1;
233		tempwork->hours = -1;
234		tempwork->trim_at = (time_t)0;
235		tempwork->permissions = 0;
236		tempwork->flags = 0;
237		tempwork->sig = SIGHUP;
238		tempwork->def_cfg = 0;
239	}
240	tempwork->next = NULL;
241
242	return (tempwork);
243}
244
245static void
246free_entry(struct conf_entry *ent)
247{
248
249	if (ent == NULL)
250		return;
251
252	if (ent->log != NULL) {
253		if (verbose > 4)
254			printf("\t--> [freeing entry for %s]\n", ent->log);
255		free(ent->log);
256		ent->log = NULL;
257	}
258
259	if (ent->pid_file != NULL) {
260		free(ent->pid_file);
261		ent->pid_file = NULL;
262	}
263
264	if (ent->r_reason != NULL) {
265		free(ent->r_reason);
266		ent->r_reason = NULL;
267	}
268
269	free(ent);
270}
271
272static void
273free_clist(struct conf_entry **firstent)
274{
275	struct conf_entry *ent, *nextent;
276
277	if (firstent == NULL)
278		return;			/* There is nothing to do. */
279
280	ent = *firstent;
281	firstent = NULL;
282
283	while (ent) {
284		nextent = ent->next;
285		free_entry(ent);
286		ent = nextent;
287	}
288}
289
290static void
291do_entry(struct conf_entry * ent)
292{
293#define REASON_MAX	80
294	int size, modtime;
295	char temp_reason[REASON_MAX];
296
297	if (verbose) {
298		if (ent->flags & CE_COMPACT)
299			printf("%s <%dZ>: ", ent->log, ent->numlogs);
300		else if (ent->flags & CE_BZCOMPACT)
301			printf("%s <%dJ>: ", ent->log, ent->numlogs);
302		else
303			printf("%s <%d>: ", ent->log, ent->numlogs);
304	}
305	size = sizefile(ent->log);
306	modtime = age_old_log(ent->log);
307	ent->rotate = 0;
308	ent->firstcreate = 0;
309	if (size < 0) {
310		/*
311		 * If either the C flag or the -C option was specified,
312		 * and if we won't be creating the file, then have the
313		 * verbose message include a hint as to why the file
314		 * will not be created.
315		 */
316		temp_reason[0] = '\0';
317		if (createlogs > 1)
318			ent->firstcreate = 1;
319		else if ((ent->flags & CE_CREATE) && createlogs)
320			ent->firstcreate = 1;
321		else if (ent->flags & CE_CREATE)
322			strncpy(temp_reason, " (no -C option)", REASON_MAX);
323		else if (createlogs)
324			strncpy(temp_reason, " (no C flag)", REASON_MAX);
325
326		if (ent->firstcreate) {
327			if (verbose)
328				printf("does not exist -> will create.\n");
329			createlog(ent);
330		} else if (verbose) {
331			printf("does not exist, skipped%s.\n", temp_reason);
332		}
333	} else {
334		if (ent->flags & CE_TRIMAT && !force && !rotatereq) {
335			if (timenow < ent->trim_at
336			    || difftime(timenow, ent->trim_at) >= 60 * 60) {
337				if (verbose)
338					printf("--> will trim at %s",
339					    ctime(&ent->trim_at));
340				return;
341			} else if (verbose && ent->hours <= 0) {
342				printf("--> time is up\n");
343			}
344		}
345		if (verbose && (ent->size > 0))
346			printf("size (Kb): %d [%d] ", size, ent->size);
347		if (verbose && (ent->hours > 0))
348			printf(" age (hr): %d [%d] ", modtime, ent->hours);
349
350		/*
351		 * Figure out if this logfile needs to be rotated.
352		 */
353		temp_reason[0] = '\0';
354		if (rotatereq) {
355			ent->rotate = 1;
356			snprintf(temp_reason, REASON_MAX, " due to -R from %s",
357			    requestor);
358		} else if (force) {
359			ent->rotate = 1;
360			snprintf(temp_reason, REASON_MAX, " due to -F request");
361		} else if ((ent->size > 0) && (size >= ent->size)) {
362			ent->rotate = 1;
363			snprintf(temp_reason, REASON_MAX, " due to size>%dK",
364			    ent->size);
365		} else if (ent->hours <= 0 && (ent->flags & CE_TRIMAT)) {
366			ent->rotate = 1;
367		} else if ((ent->hours > 0) && ((modtime >= ent->hours) ||
368		    (modtime < 0))) {
369			ent->rotate = 1;
370		}
371
372		/*
373		 * If the file needs to be rotated, then rotate it.
374		 */
375		if (ent->rotate) {
376			if (temp_reason[0] != '\0')
377				ent->r_reason = strdup(temp_reason);
378			if (verbose)
379				printf("--> trimming log....\n");
380			if (noaction && !verbose) {
381				if (ent->flags & CE_COMPACT)
382					printf("%s <%dZ>: trimming\n",
383					    ent->log, ent->numlogs);
384				else if (ent->flags & CE_BZCOMPACT)
385					printf("%s <%dJ>: trimming\n",
386					    ent->log, ent->numlogs);
387				else
388					printf("%s <%d>: trimming\n",
389					    ent->log, ent->numlogs);
390			}
391			dotrim(ent, ent->log, ent->numlogs, ent->flags);
392		} else {
393			if (verbose)
394				printf("--> skipping\n");
395		}
396	}
397#undef REASON_MAX
398}
399
400/* Send a signal to the pid specified by pidfile */
401static int
402send_signal(const struct conf_entry *ent)
403{
404	pid_t target_pid;
405	int did_notify;
406	FILE *f;
407	long minok, maxok, rval;
408	const char *target_name;
409	char *endp, *linep, line[BUFSIZ];
410
411	did_notify = 0;
412	f = fopen(ent->pid_file, "r");
413	if (f == NULL) {
414		warn("can't open pid file: %s", ent->pid_file);
415		return (did_notify);
416		/* NOTREACHED */
417	}
418
419	if (fgets(line, BUFSIZ, f) == NULL) {
420		/*
421		 * XXX - If the pid file is empty, is that really a
422		 *	problem?  Wouldn't that mean that the process
423		 *	has shut down?  In that case there would be no
424		 *	problem with compressing the rotated log file.
425		 */
426		if (feof(f))
427			warnx("pid file is empty: %s",  ent->pid_file);
428		else
429			warn("can't read from pid file: %s", ent->pid_file);
430		(void) fclose(f);
431		return (did_notify);
432		/* NOTREACHED */
433	}
434	(void) fclose(f);
435
436	target_name = "daemon";
437	minok = MIN_PID;
438	maxok = MAX_PID;
439	if (ent->flags & CE_SIGNALGROUP) {
440		/*
441		 * If we are expected to signal a process-group when
442		 * rotating this logfile, then the value read in should
443		 * be the negative of a valid process ID.
444		 */
445		target_name = "process-group";
446		minok = -MAX_PID;
447		maxok = -MIN_PID;
448	}
449
450	errno = 0;
451	linep = line;
452	while (*linep == ' ')
453		linep++;
454	rval = strtol(linep, &endp, 10);
455	if (*endp != '\0' && !isspacech(*endp)) {
456		warnx("pid file does not start with a valid number: %s",
457		    ent->pid_file);
458		rval = 0;
459	} else if (rval < minok || rval > maxok) {
460		warnx("bad value '%ld' for process number in %s",
461		    rval, ent->pid_file);
462		if (verbose)
463			warnx("\t(expecting value between %ld and %ld)",
464			    minok, maxok);
465		rval = 0;
466	}
467	if (rval == 0) {
468		return (did_notify);
469		/* NOTREACHED */
470	}
471
472	target_pid = rval;
473
474	if (noaction) {
475		did_notify = 1;
476		printf("\tkill -%d %d\n", ent->sig, (int) target_pid);
477	} else if (kill(target_pid, ent->sig)) {
478		/*
479		 * XXX - Iff the error was "no such process", should that
480		 *	really be an error for us?  Perhaps the process
481		 *	is already gone, in which case there would be no
482		 *	problem with compressing the rotated log file.
483		 */
484		warn("can't notify %s, pid %d", target_name,
485		    (int) target_pid);
486	} else {
487		did_notify = 1;
488		if (verbose)
489			printf("%s pid %d notified\n", target_name,
490			    (int) target_pid);
491	}
492
493	return (did_notify);
494}
495
496static void
497parse_args(int argc, char **argv)
498{
499	int ch;
500	char *p;
501	char debugtime[32];
502
503	timenow = time(NULL);
504	(void)strncpy(daytime, ctime(&timenow) + 4, 15);
505	daytime[15] = '\0';
506
507	/* Let's get our hostname */
508	(void)gethostname(hostname, sizeof(hostname));
509
510	/* Truncate domain */
511	if ((p = strchr(hostname, '.')) != NULL)
512		*p = '\0';
513
514	/* Parse command line options. */
515	while ((ch = getopt(argc, argv, "a:f:nrsvCD:FR:")) != -1)
516		switch (ch) {
517		case 'a':
518			archtodir++;
519			archdirname = optarg;
520			break;
521		case 'f':
522			conf = optarg;
523			break;
524		case 'n':
525			noaction++;
526			break;
527		case 'r':
528			needroot = 0;
529			break;
530		case 's':
531			nosignal = 1;
532			break;
533		case 'v':
534			verbose++;
535			break;
536		case 'C':
537			/* Useful for things like rc.diskless... */
538			createlogs++;
539			break;
540		case 'D':
541			/*
542			 * Set some debugging option.  The specific option
543			 * depends on the value of optarg.  These options
544			 * may come and go without notice or documentation.
545			 */
546			if (parse_doption(optarg))
547				break;
548			usage();
549			/* NOTREACHED */
550		case 'F':
551			force++;
552			break;
553		case 'R':
554			rotatereq++;
555			requestor = strdup(optarg);
556			break;
557		case 'm':	/* Used by OpenBSD for "monitor mode" */
558		default:
559			usage();
560			/* NOTREACHED */
561		}
562
563	if (rotatereq) {
564		if (optind == argc) {
565			warnx("At least one filename must be given when -R is specified.");
566			usage();
567			/* NOTREACHED */
568		}
569		/* Make sure "requestor" value is safe for a syslog message. */
570		for (p = requestor; *p != '\0'; p++) {
571			if (!isprintch(*p) && (*p != '\t'))
572				*p = '.';
573		}
574	}
575
576	if (dbg_timenow) {
577		/*
578		 * Note that the 'daytime' variable is not changed.
579		 * That is only used in messages that track when a
580		 * logfile is rotated, and if a file *is* rotated,
581		 * then it will still rotated at the "real now" time.
582		 */
583		timenow = dbg_timenow;
584		strlcpy(debugtime, ctime(&timenow), sizeof(debugtime));
585		fprintf(stderr, "Debug: Running as if TimeNow is %s",
586		    debugtime);
587	}
588
589}
590
591static int
592parse_doption(const char *doption)
593{
594	const char TN[] = "TN=";
595
596	if (strncmp(doption, TN, sizeof(TN) - 1) == 0) {
597		/*
598		 * The "TimeNow" debugging option.  This probably will
599		 * be off by an hour when crossing a timezone change.
600		 */
601		dbg_timenow = parse8601(doption + sizeof(TN) - 1);
602		if (dbg_timenow == (time_t)-1) {
603			warnx("Malformed time given on -D %s", doption);
604			return (0);			/* failure */
605		}
606		if (dbg_timenow == (time_t)-2) {
607			warnx("Non-existent time specified on -D %s", doption);
608			return (0);			/* failure */
609		}
610		return (1);			/* successfully parsed */
611
612	}
613
614	warnx("Unknown -D (debug) option: %s", doption);
615	return (0);				/* failure */
616}
617
618static void
619usage(void)
620{
621
622	fprintf(stderr,
623	    "usage: newsyslog [-CFnrsv] [-a directory] [-f config-file]\n"
624	    "                 [ [-R requestor] filename ... ]\n");
625	exit(1);
626}
627
628/*
629 * Parse a configuration file and return a linked list of all the logs
630 * which should be processed.
631 */
632static struct conf_entry *
633get_worklist(char **files)
634{
635	FILE *f;
636	const char *fname;
637	char **given;
638	struct conf_entry *defconf, *dupent, *ent, *firstnew;
639	struct conf_entry *globlist, *lastnew, *worklist;
640	int gmatch, fnres;
641
642	defconf = globlist = worklist = NULL;
643
644	fname = conf;
645	if (fname == NULL)
646		fname = _PATH_CONF;
647
648	if (strcmp(fname, "-") != 0)
649		f = fopen(fname, "r");
650	else {
651		f = stdin;
652		fname = "<stdin>";
653	}
654	if (!f)
655		err(1, "%s", conf);
656
657	parse_file(f, fname, &worklist, &globlist, &defconf);
658	(void) fclose(f);
659
660	/*
661	 * All config-file information has been read in and turned into
662	 * a worklist and a globlist.  If there were no specific files
663	 * given on the run command, then the only thing left to do is to
664	 * call a routine which finds all files matched by the globlist
665	 * and adds them to the worklist.  Then return the worklist.
666	 */
667	if (*files == NULL) {
668		expand_globs(&worklist, &globlist);
669		free_clist(&globlist);
670		if (defconf != NULL)
671			free_entry(defconf);
672		return (worklist);
673		/* NOTREACHED */
674	}
675
676	/*
677	 * If newsyslog was given a specific list of files to process,
678	 * it may be that some of those files were not listed in any
679	 * config file.  Those unlisted files should get the default
680	 * rotation action.  First, create the default-rotation action
681	 * if none was found in a system config file.
682	 */
683	if (defconf == NULL) {
684		defconf = init_entry(DEFAULT_MARKER, NULL);
685		defconf->numlogs = 3;
686		defconf->size = 50;
687		defconf->permissions = S_IRUSR|S_IWUSR;
688	}
689
690	/*
691	 * If newsyslog was run with a list of specific filenames,
692	 * then create a new worklist which has only those files in
693	 * it, picking up the rotation-rules for those files from
694	 * the original worklist.
695	 *
696	 * XXX - Note that this will copy multiple rules for a single
697	 *	logfile, if multiple entries are an exact match for
698	 *	that file.  That matches the historic behavior, but do
699	 *	we want to continue to allow it?  If so, it should
700	 *	probably be handled more intelligently.
701	 */
702	firstnew = lastnew = NULL;
703	for (given = files; *given; ++given) {
704		/*
705		 * First try to find exact-matches for this given file.
706		 */
707		gmatch = 0;
708		for (ent = worklist; ent; ent = ent->next) {
709			if (strcmp(ent->log, *given) == 0) {
710				gmatch++;
711				dupent = init_entry(*given, ent);
712				if (!firstnew)
713					firstnew = dupent;
714				else
715					lastnew->next = dupent;
716				lastnew = dupent;
717			}
718		}
719		if (gmatch) {
720			if (verbose > 2)
721				printf("\t+ Matched entry %s\n", *given);
722			continue;
723		}
724
725		/*
726		 * There was no exact-match for this given file, so look
727		 * for a "glob" entry which does match.
728		 */
729		gmatch = 0;
730		if (verbose > 2 && globlist != NULL)
731			printf("\t+ Checking globs for %s\n", *given);
732		for (ent = globlist; ent; ent = ent->next) {
733			fnres = fnmatch(ent->log, *given, FNM_PATHNAME);
734			if (verbose > 2)
735				printf("\t+    = %d for pattern %s\n", fnres,
736				    ent->log);
737			if (fnres == 0) {
738				gmatch++;
739				dupent = init_entry(*given, ent);
740				if (!firstnew)
741					firstnew = dupent;
742				else
743					lastnew->next = dupent;
744				lastnew = dupent;
745				/* This new entry is not a glob! */
746				dupent->flags &= ~CE_GLOB;
747				/* Only allow a match to one glob-entry */
748				break;
749			}
750		}
751		if (gmatch) {
752			if (verbose > 2)
753				printf("\t+ Matched %s via %s\n", *given,
754				    ent->log);
755			continue;
756		}
757
758		/*
759		 * This given file was not found in any config file, so
760		 * add a worklist item based on the default entry.
761		 */
762		if (verbose > 2)
763			printf("\t+ No entry matched %s  (will use %s)\n",
764			    *given, DEFAULT_MARKER);
765		dupent = init_entry(*given, defconf);
766		if (!firstnew)
767			firstnew = dupent;
768		else
769			lastnew->next = dupent;
770		/* Mark that it was *not* found in a config file */
771		dupent->def_cfg = 1;
772		lastnew = dupent;
773	}
774
775	/*
776	 * Free all the entries in the original work list, the list of
777	 * glob entries, and the default entry.
778	 */
779	free_clist(&worklist);
780	free_clist(&globlist);
781	free_entry(defconf);
782
783	/* And finally, return a worklist which matches the given files. */
784	return (firstnew);
785}
786
787/*
788 * Expand the list of entries with filename patterns, and add all files
789 * which match those glob-entries onto the worklist.
790 */
791static void
792expand_globs(struct conf_entry **work_p, struct conf_entry **glob_p)
793{
794	int gmatch, gres, i;
795	char *mfname;
796	struct conf_entry *dupent, *ent, *firstmatch, *globent;
797	struct conf_entry *lastmatch;
798	glob_t pglob;
799	struct stat st_fm;
800
801	if ((glob_p == NULL) || (*glob_p == NULL))
802		return;			/* There is nothing to do. */
803
804	/*
805	 * The worklist contains all fully-specified (non-GLOB) names.
806	 *
807	 * Now expand the list of filename-pattern (GLOB) entries into
808	 * a second list, which (by definition) will only match files
809	 * that already exist.  Do not add a glob-related entry for any
810	 * file which already exists in the fully-specified list.
811	 */
812	firstmatch = lastmatch = NULL;
813	for (globent = *glob_p; globent; globent = globent->next) {
814
815		gres = glob(globent->log, GLOB_NOCHECK, NULL, &pglob);
816		if (gres != 0) {
817			warn("cannot expand pattern (%d): %s", gres,
818			    globent->log);
819			continue;
820		}
821
822		if (verbose > 2)
823			printf("\t+ Expanding pattern %s\n", globent->log);
824		for (i = 0; i < pglob.gl_matchc; i++) {
825			mfname = pglob.gl_pathv[i];
826
827			/* See if this file already has a specific entry. */
828			gmatch = 0;
829			for (ent = *work_p; ent; ent = ent->next) {
830				if (strcmp(mfname, ent->log) == 0) {
831					gmatch++;
832					break;
833				}
834			}
835			if (gmatch)
836				continue;
837
838			/* Make sure the named matched is a file. */
839			gres = lstat(mfname, &st_fm);
840			if (gres != 0) {
841				/* Error on a file that glob() matched?!? */
842				warn("Skipping %s - lstat() error", mfname);
843				continue;
844			}
845			if (!S_ISREG(st_fm.st_mode)) {
846				/* We only rotate files! */
847				if (verbose > 2)
848					printf("\t+  . skipping %s (!file)\n",
849					    mfname);
850				continue;
851			}
852
853			if (verbose > 2)
854				printf("\t+  . add file %s\n", mfname);
855			dupent = init_entry(mfname, globent);
856			if (!firstmatch)
857				firstmatch = dupent;
858			else
859				lastmatch->next = dupent;
860			lastmatch = dupent;
861			/* This new entry is not a glob! */
862			dupent->flags &= ~CE_GLOB;
863		}
864		globfree(&pglob);
865		if (verbose > 2)
866			printf("\t+ Done with pattern %s\n", globent->log);
867	}
868
869	/* Add the list of matched files to the end of the worklist. */
870	if (!*work_p)
871		*work_p = firstmatch;
872	else {
873		ent = *work_p;
874		while (ent->next)
875			ent = ent->next;
876		ent->next = firstmatch;
877	}
878
879}
880
881/*
882 * Parse a configuration file and update a linked list of all the logs to
883 * process.
884 */
885static void
886parse_file(FILE *cf, const char *cfname, struct conf_entry **work_p,
887    struct conf_entry **glob_p, struct conf_entry **defconf_p)
888{
889	char line[BUFSIZ], *parse, *q;
890	char *cp, *errline, *group;
891	struct conf_entry *lastglob, *lastwork, *working;
892	struct passwd *pwd;
893	struct group *grp;
894	int eol, special;
895
896	/*
897	 * XXX - for now, assume that only one config file will be read,
898	 *	ie, this routine is only called one time.
899	 */
900	lastglob = lastwork = NULL;
901
902	while (fgets(line, BUFSIZ, cf)) {
903		if ((line[0] == '\n') || (line[0] == '#') ||
904		    (strlen(line) == 0))
905			continue;
906		errline = strdup(line);
907		for (cp = line + 1; *cp != '\0'; cp++) {
908			if (*cp != '#')
909				continue;
910			if (*(cp - 1) == '\\') {
911				strcpy(cp - 1, cp);
912				cp--;
913				continue;
914			}
915			*cp = '\0';
916			break;
917		}
918
919		q = parse = missing_field(sob(line), errline);
920		parse = son(line);
921		if (!*parse)
922			errx(1, "malformed line (missing fields):\n%s",
923			    errline);
924		*parse = '\0';
925
926		special = 0;
927		working = init_entry(q, NULL);
928		if (strcasecmp(DEFAULT_MARKER, q) == 0) {
929			special = 1;
930			if (defconf_p == NULL) {
931				warnx("Ignoring entry for %s in %s!", q,
932				    cfname);
933				free_entry(working);
934				continue;
935			} else if (*defconf_p != NULL) {
936				warnx("Ignoring duplicate entry for %s!", q);
937				free_entry(working);
938				continue;
939			}
940			*defconf_p = working;
941		}
942
943		q = parse = missing_field(sob(++parse), errline);
944		parse = son(parse);
945		if (!*parse)
946			errx(1, "malformed line (missing fields):\n%s",
947			    errline);
948		*parse = '\0';
949		if ((group = strchr(q, ':')) != NULL ||
950		    (group = strrchr(q, '.')) != NULL) {
951			*group++ = '\0';
952			if (*q) {
953				if (!(isnumberstr(q))) {
954					if ((pwd = getpwnam(q)) == NULL)
955						errx(1,
956				     "error in config file; unknown user:\n%s",
957						    errline);
958					working->uid = pwd->pw_uid;
959				} else
960					working->uid = atoi(q);
961			} else
962				working->uid = (uid_t)-1;
963
964			q = group;
965			if (*q) {
966				if (!(isnumberstr(q))) {
967					if ((grp = getgrnam(q)) == NULL)
968						errx(1,
969				    "error in config file; unknown group:\n%s",
970						    errline);
971					working->gid = grp->gr_gid;
972				} else
973					working->gid = atoi(q);
974			} else
975				working->gid = (gid_t)-1;
976
977			q = parse = missing_field(sob(++parse), errline);
978			parse = son(parse);
979			if (!*parse)
980				errx(1, "malformed line (missing fields):\n%s",
981				    errline);
982			*parse = '\0';
983		} else {
984			working->uid = (uid_t)-1;
985			working->gid = (gid_t)-1;
986		}
987
988		if (!sscanf(q, "%o", &working->permissions))
989			errx(1, "error in config file; bad permissions:\n%s",
990			    errline);
991
992		q = parse = missing_field(sob(++parse), errline);
993		parse = son(parse);
994		if (!*parse)
995			errx(1, "malformed line (missing fields):\n%s",
996			    errline);
997		*parse = '\0';
998		if (!sscanf(q, "%d", &working->numlogs) || working->numlogs < 0)
999			errx(1, "error in config file; bad value for count of logs to save:\n%s",
1000			    errline);
1001
1002		q = parse = missing_field(sob(++parse), errline);
1003		parse = son(parse);
1004		if (!*parse)
1005			errx(1, "malformed line (missing fields):\n%s",
1006			    errline);
1007		*parse = '\0';
1008		if (isdigitch(*q))
1009			working->size = atoi(q);
1010		else if (strcmp(q,"*") == 0)
1011			working->size = -1;
1012		else {
1013			warnx("Invalid value of '%s' for 'size' in line:\n%s",
1014			    q, errline);
1015			working->size = -1;
1016		}
1017
1018		working->flags = 0;
1019		q = parse = missing_field(sob(++parse), errline);
1020		parse = son(parse);
1021		eol = !*parse;
1022		*parse = '\0';
1023		{
1024			char *ep;
1025			u_long ul;
1026
1027			ul = strtoul(q, &ep, 10);
1028			if (ep == q)
1029				working->hours = 0;
1030			else if (*ep == '*')
1031				working->hours = -1;
1032			else if (ul > INT_MAX)
1033				errx(1, "interval is too large:\n%s", errline);
1034			else
1035				working->hours = ul;
1036
1037			if (*ep != '\0' && *ep != '@' && *ep != '*' &&
1038			    *ep != '$')
1039				errx(1, "malformed interval/at:\n%s", errline);
1040			if (*ep == '@') {
1041				working->trim_at = parse8601(ep + 1);
1042				working->flags |= CE_TRIMAT;
1043			} else if (*ep == '$') {
1044				working->trim_at = parseDWM(ep + 1);
1045				working->flags |= CE_TRIMAT;
1046			}
1047			if (working->flags & CE_TRIMAT) {
1048				if (working->trim_at == (time_t)-1)
1049					errx(1, "malformed at:\n%s", errline);
1050				if (working->trim_at == (time_t)-2)
1051					errx(1, "nonexistent time:\n%s",
1052					    errline);
1053			}
1054		}
1055
1056		if (eol)
1057			q = NULL;
1058		else {
1059			q = parse = sob(++parse);	/* Optional field */
1060			parse = son(parse);
1061			if (!*parse)
1062				eol = 1;
1063			*parse = '\0';
1064		}
1065
1066		for (; q && *q && !isspacech(*q); q++) {
1067			switch (tolowerch(*q)) {
1068			case 'b':
1069				working->flags |= CE_BINARY;
1070				break;
1071			case 'c':
1072				/*
1073				 * XXX - 	Ick! Ugly! Remove ASAP!
1074				 * We want `c' and `C' for "create".  But we
1075				 * will temporarily treat `c' as `g', because
1076				 * FreeBSD releases <= 4.8 have a typo of
1077				 * checking  ('G' || 'c')  for CE_GLOB.
1078				 */
1079				if (*q == 'c') {
1080					warnx("Assuming 'g' for 'c' in flags for line:\n%s",
1081					    errline);
1082					warnx("The 'c' flag will eventually mean 'CREATE'");
1083					working->flags |= CE_GLOB;
1084					break;
1085				}
1086				working->flags |= CE_CREATE;
1087				break;
1088			case 'g':
1089				working->flags |= CE_GLOB;
1090				break;
1091			case 'j':
1092				working->flags |= CE_BZCOMPACT;
1093				break;
1094			case 'n':
1095				working->flags |= CE_NOSIGNAL;
1096				break;
1097			case 'u':
1098				working->flags |= CE_SIGNALGROUP;
1099				break;
1100			case 'w':
1101				working->flags |= CE_COMPACTWAIT;
1102				break;
1103			case 'z':
1104				working->flags |= CE_COMPACT;
1105				break;
1106			case '-':
1107				break;
1108			case 'f':	/* Used by OpenBSD for "CE_FOLLOW" */
1109			case 'm':	/* Used by OpenBSD for "CE_MONITOR" */
1110			case 'p':	/* Used by NetBSD  for "CE_PLAIN0" */
1111			default:
1112				errx(1, "illegal flag in config file -- %c",
1113				    *q);
1114			}
1115		}
1116
1117		if (eol)
1118			q = NULL;
1119		else {
1120			q = parse = sob(++parse);	/* Optional field */
1121			parse = son(parse);
1122			if (!*parse)
1123				eol = 1;
1124			*parse = '\0';
1125		}
1126
1127		working->pid_file = NULL;
1128		if (q && *q) {
1129			if (*q == '/')
1130				working->pid_file = strdup(q);
1131			else if (isdigit(*q))
1132				goto got_sig;
1133			else
1134				errx(1,
1135			"illegal pid file or signal number in config file:\n%s",
1136				    errline);
1137		}
1138		if (eol)
1139			q = NULL;
1140		else {
1141			q = parse = sob(++parse);	/* Optional field */
1142			*(parse = son(parse)) = '\0';
1143		}
1144
1145		working->sig = SIGHUP;
1146		if (q && *q) {
1147			if (isdigit(*q)) {
1148		got_sig:
1149				working->sig = atoi(q);
1150			} else {
1151		err_sig:
1152				errx(1,
1153				    "illegal signal number in config file:\n%s",
1154				    errline);
1155			}
1156			if (working->sig < 1 || working->sig >= NSIG)
1157				goto err_sig;
1158		}
1159
1160		/*
1161		 * Finish figuring out what pid-file to use (if any) in
1162		 * later processing if this logfile needs to be rotated.
1163		 */
1164		if ((working->flags & CE_NOSIGNAL) == CE_NOSIGNAL) {
1165			/*
1166			 * This config-entry specified 'n' for nosignal,
1167			 * see if it also specified an explicit pid_file.
1168			 * This would be a pretty pointless combination.
1169			 */
1170			if (working->pid_file != NULL) {
1171				warnx("Ignoring '%s' because flag 'n' was specified in line:\n%s",
1172				    working->pid_file, errline);
1173				free(working->pid_file);
1174				working->pid_file = NULL;
1175			}
1176		} else if (working->pid_file == NULL) {
1177			/*
1178			 * This entry did not specify the 'n' flag, which
1179			 * means it should signal syslogd unless it had
1180			 * specified some other pid-file (and obviously the
1181			 * syslog pid-file will not be for a process-group).
1182			 * Also, we should only try to notify syslog if we
1183			 * are root.
1184			 */
1185			if (working->flags & CE_SIGNALGROUP) {
1186				warnx("Ignoring flag 'U' in line:\n%s",
1187				    errline);
1188				working->flags &= ~CE_SIGNALGROUP;
1189			}
1190			if (needroot)
1191				working->pid_file = strdup(_PATH_SYSLOGPID);
1192		}
1193
1194		/*
1195		 * Add this entry to the appropriate list of entries, unless
1196		 * it was some kind of special entry (eg: <default>).
1197		 */
1198		if (special) {
1199			;			/* Do not add to any list */
1200		} else if (working->flags & CE_GLOB) {
1201			if (!*glob_p)
1202				*glob_p = working;
1203			else
1204				lastglob->next = working;
1205			lastglob = working;
1206		} else {
1207			if (!*work_p)
1208				*work_p = working;
1209			else
1210				lastwork->next = working;
1211			lastwork = working;
1212		}
1213
1214		free(errline);
1215		errline = NULL;
1216	}
1217}
1218
1219static char *
1220missing_field(char *p, char *errline)
1221{
1222
1223	if (!p || !*p)
1224		errx(1, "missing field in config file:\n%s", errline);
1225	return (p);
1226}
1227
1228static void
1229dotrim(const struct conf_entry *ent, char *log, int numdays, int flags)
1230{
1231	char dirpart[MAXPATHLEN], namepart[MAXPATHLEN];
1232	char file1[MAXPATHLEN], file2[MAXPATHLEN];
1233	char zfile1[MAXPATHLEN], zfile2[MAXPATHLEN];
1234	char jfile1[MAXPATHLEN];
1235	char tfile[MAXPATHLEN];
1236	int notified, need_notification, fd, _numdays;
1237	struct stat st;
1238
1239	if (archtodir) {
1240		char *p;
1241
1242		/* build complete name of archive directory into dirpart */
1243		if (*archdirname == '/') {	/* absolute */
1244			strlcpy(dirpart, archdirname, sizeof(dirpart));
1245		} else {	/* relative */
1246			/* get directory part of logfile */
1247			strlcpy(dirpart, log, sizeof(dirpart));
1248			if ((p = rindex(dirpart, '/')) == NULL)
1249				dirpart[0] = '\0';
1250			else
1251				*(p + 1) = '\0';
1252			strlcat(dirpart, archdirname, sizeof(dirpart));
1253		}
1254
1255		/* check if archive directory exists, if not, create it */
1256		if (lstat(dirpart, &st))
1257			createdir(ent, dirpart);
1258
1259		/* get filename part of logfile */
1260		if ((p = rindex(log, '/')) == NULL)
1261			strlcpy(namepart, log, sizeof(namepart));
1262		else
1263			strlcpy(namepart, p + 1, sizeof(namepart));
1264
1265		/* name of oldest log */
1266		(void) snprintf(file1, sizeof(file1), "%s/%s.%d", dirpart,
1267		    namepart, numdays);
1268		(void) snprintf(zfile1, sizeof(zfile1), "%s%s", file1,
1269		    COMPRESS_POSTFIX);
1270		snprintf(jfile1, sizeof(jfile1), "%s%s", file1,
1271		    BZCOMPRESS_POSTFIX);
1272	} else {
1273		/* name of oldest log */
1274		(void) snprintf(file1, sizeof(file1), "%s.%d", log, numdays);
1275		(void) snprintf(zfile1, sizeof(zfile1), "%s%s", file1,
1276		    COMPRESS_POSTFIX);
1277		snprintf(jfile1, sizeof(jfile1), "%s%s", file1,
1278		    BZCOMPRESS_POSTFIX);
1279	}
1280
1281	if (noaction) {
1282		printf("\trm -f %s\n", file1);
1283		printf("\trm -f %s\n", zfile1);
1284		printf("\trm -f %s\n", jfile1);
1285	} else {
1286		(void) unlink(file1);
1287		(void) unlink(zfile1);
1288		(void) unlink(jfile1);
1289	}
1290
1291	/* Move down log files */
1292	_numdays = numdays;	/* preserve */
1293	while (numdays--) {
1294
1295		(void) strlcpy(file2, file1, sizeof(file2));
1296
1297		if (archtodir)
1298			(void) snprintf(file1, sizeof(file1), "%s/%s.%d",
1299			    dirpart, namepart, numdays);
1300		else
1301			(void) snprintf(file1, sizeof(file1), "%s.%d", log,
1302			    numdays);
1303
1304		(void) strlcpy(zfile1, file1, sizeof(zfile1));
1305		(void) strlcpy(zfile2, file2, sizeof(zfile2));
1306		if (lstat(file1, &st)) {
1307			(void) strlcat(zfile1, COMPRESS_POSTFIX,
1308			    sizeof(zfile1));
1309			(void) strlcat(zfile2, COMPRESS_POSTFIX,
1310			    sizeof(zfile2));
1311			if (lstat(zfile1, &st)) {
1312				strlcpy(zfile1, file1, sizeof(zfile1));
1313				strlcpy(zfile2, file2, sizeof(zfile2));
1314				strlcat(zfile1, BZCOMPRESS_POSTFIX,
1315				    sizeof(zfile1));
1316				strlcat(zfile2, BZCOMPRESS_POSTFIX,
1317				    sizeof(zfile2));
1318				if (lstat(zfile1, &st))
1319					continue;
1320			}
1321		}
1322		if (noaction) {
1323			printf("\tmv %s %s\n", zfile1, zfile2);
1324			printf("\tchmod %o %s\n", ent->permissions, zfile2);
1325			if (ent->uid != (uid_t)-1 || ent->gid != (gid_t)-1)
1326				printf("\tchown %u:%u %s\n",
1327				    ent->uid, ent->gid, zfile2);
1328		} else {
1329			(void) rename(zfile1, zfile2);
1330			if (chmod(zfile2, ent->permissions))
1331				warn("can't chmod %s", file2);
1332			if (ent->uid != (uid_t)-1 || ent->gid != (gid_t)-1)
1333				if (chown(zfile2, ent->uid, ent->gid))
1334					warn("can't chown %s", zfile2);
1335		}
1336	}
1337	if (!noaction && !(flags & CE_BINARY)) {
1338		/* Report the trimming to the old log */
1339		(void) log_trim(log, ent);
1340	}
1341
1342	if (!_numdays) {
1343		if (noaction)
1344			printf("\trm %s\n", log);
1345		else
1346			(void) unlink(log);
1347	} else {
1348		if (noaction)
1349			printf("\tmv %s to %s\n", log, file1);
1350		else {
1351			if (archtodir)
1352				movefile(log, file1, ent->permissions, ent->uid,
1353				    ent->gid);
1354			else
1355				(void) rename(log, file1);
1356		}
1357	}
1358
1359	/* Now move the new log file into place */
1360	/* XXX - We should replace the above 'rename' with 'link(log, file1)'
1361	 *	then replace the following with 'createfile(ent)' */
1362	strlcpy(tfile, log, sizeof(tfile));
1363	strlcat(tfile, ".XXXXXX", sizeof(tfile));
1364	if (noaction) {
1365		printf("Start new log...\n");
1366		printf("\tmktemp %s\n", tfile);
1367	} else {
1368		mkstemp(tfile);
1369		fd = creat(tfile, ent->permissions);
1370		if (fd < 0)
1371			err(1, "can't start new log");
1372		if (ent->uid != (uid_t)-1 || ent->gid != (gid_t)-1)
1373			if (fchown(fd, ent->uid, ent->gid))
1374			    err(1, "can't chown new log file");
1375		(void) close(fd);
1376		if (!(flags & CE_BINARY)) {
1377			/* Add status message to new log file */
1378			if (log_trim(tfile, ent))
1379				err(1, "can't add status message to log");
1380		}
1381	}
1382	if (noaction) {
1383		printf("\tchmod %o %s\n", ent->permissions, tfile);
1384		printf("\tmv %s %s\n", tfile, log);
1385	} else {
1386		(void) chmod(tfile, ent->permissions);
1387		if (rename(tfile, log) < 0) {
1388			err(1, "can't start new log");
1389			(void) unlink(tfile);
1390		}
1391	}
1392
1393	/*
1394	 * Find out if there is a process to signal.  If nosignal (-s) was
1395	 * specified, then do not signal any process.  Note that nosignal
1396	 * will trigger a warning message if the rotated logfile needs to
1397	 * be compressed, *unless* -R was specified.  This is because there
1398	 * presumably still are process(es) writing to the old logfile, but
1399	 * we assume that a -sR request comes from a process which writes
1400	 * to the logfile, and as such, that process has already made sure
1401	 * that the logfile is not presently in use.
1402	 */
1403	need_notification = notified = 0;
1404	if (ent->pid_file != NULL) {
1405		need_notification = 1;
1406		if (!nosignal)
1407			notified = send_signal(ent);	/* the normal case! */
1408		else if (rotatereq)
1409			need_notification = 0;
1410	}
1411
1412	if ((flags & CE_COMPACT) || (flags & CE_BZCOMPACT)) {
1413		if (need_notification && !notified)
1414			warnx(
1415			    "log %s.0 not compressed because daemon(s) not notified",
1416			    log);
1417		else if (noaction)
1418			if (flags & CE_COMPACT)
1419				printf("\tgzip %s.0\n", log);
1420			else
1421				printf("\tbzip2 %s.0\n", log);
1422		else {
1423			if (notified) {
1424				if (verbose)
1425					printf("small pause to allow daemon(s) to close log\n");
1426				sleep(10);
1427			}
1428			if (archtodir) {
1429				(void) snprintf(file1, sizeof(file1), "%s/%s",
1430				    dirpart, namepart);
1431				if (flags & CE_COMPACT)
1432					compress_log(file1,
1433					    flags & CE_COMPACTWAIT);
1434				else if (flags & CE_BZCOMPACT)
1435					bzcompress_log(file1,
1436					    flags & CE_COMPACTWAIT);
1437			} else {
1438				if (flags & CE_COMPACT)
1439					compress_log(log,
1440					    flags & CE_COMPACTWAIT);
1441				else if (flags & CE_BZCOMPACT)
1442					bzcompress_log(log,
1443					    flags & CE_COMPACTWAIT);
1444			}
1445		}
1446	}
1447}
1448
1449/* Log the fact that the logs were turned over */
1450static int
1451log_trim(const char *log, const struct conf_entry *log_ent)
1452{
1453	FILE *f;
1454	const char *xtra;
1455
1456	if ((f = fopen(log, "a")) == NULL)
1457		return (-1);
1458	xtra = "";
1459	if (log_ent->def_cfg)
1460		xtra = " using <default> rule";
1461	if (log_ent->firstcreate)
1462		fprintf(f, "%s %s newsyslog[%d]: logfile first created%s\n",
1463		    daytime, hostname, (int) getpid(), xtra);
1464	else if (log_ent->r_reason != NULL)
1465		fprintf(f, "%s %s newsyslog[%d]: logfile turned over%s%s\n",
1466		    daytime, hostname, (int) getpid(), log_ent->r_reason, xtra);
1467	else
1468		fprintf(f, "%s %s newsyslog[%d]: logfile turned over%s\n",
1469		    daytime, hostname, (int) getpid(), xtra);
1470	if (fclose(f) == EOF)
1471		err(1, "log_trim: fclose:");
1472	return (0);
1473}
1474
1475/* Fork of gzip to compress the old log file */
1476static void
1477compress_log(char *log, int dowait)
1478{
1479	pid_t pid;
1480	char tmp[MAXPATHLEN];
1481
1482	while (dowait && (wait(NULL) > 0 || errno == EINTR))
1483		;
1484	(void) snprintf(tmp, sizeof(tmp), "%s.0", log);
1485	pid = fork();
1486	if (pid < 0)
1487		err(1, "gzip fork");
1488	else if (!pid) {
1489		(void) execl(_PATH_GZIP, _PATH_GZIP, "-f", tmp, (char *)0);
1490		err(1, _PATH_GZIP);
1491	}
1492}
1493
1494/* Fork of bzip2 to compress the old log file */
1495static void
1496bzcompress_log(char *log, int dowait)
1497{
1498	pid_t pid;
1499	char tmp[MAXPATHLEN];
1500
1501	while (dowait && (wait(NULL) > 0 || errno == EINTR))
1502		;
1503	snprintf(tmp, sizeof(tmp), "%s.0", log);
1504	pid = fork();
1505	if (pid < 0)
1506		err(1, "bzip2 fork");
1507	else if (!pid) {
1508		execl(_PATH_BZIP2, _PATH_BZIP2, "-f", tmp, (char *)0);
1509		err(1, _PATH_BZIP2);
1510	}
1511}
1512
1513/* Return size in kilobytes of a file */
1514static int
1515sizefile(char *file)
1516{
1517	struct stat sb;
1518
1519	if (stat(file, &sb) < 0)
1520		return (-1);
1521	return (kbytes(dbtob(sb.st_blocks)));
1522}
1523
1524/* Return the age of old log file (file.0) */
1525static int
1526age_old_log(char *file)
1527{
1528	struct stat sb;
1529	char *endp;
1530	char tmp[MAXPATHLEN + sizeof(".0") + sizeof(COMPRESS_POSTFIX) +
1531		sizeof(BZCOMPRESS_POSTFIX) + 1];
1532
1533	if (archtodir) {
1534		char *p;
1535
1536		/* build name of archive directory into tmp */
1537		if (*archdirname == '/') {	/* absolute */
1538			strlcpy(tmp, archdirname, sizeof(tmp));
1539		} else {	/* relative */
1540			/* get directory part of logfile */
1541			strlcpy(tmp, file, sizeof(tmp));
1542			if ((p = rindex(tmp, '/')) == NULL)
1543				tmp[0] = '\0';
1544			else
1545				*(p + 1) = '\0';
1546			strlcat(tmp, archdirname, sizeof(tmp));
1547		}
1548
1549		strlcat(tmp, "/", sizeof(tmp));
1550
1551		/* get filename part of logfile */
1552		if ((p = rindex(file, '/')) == NULL)
1553			strlcat(tmp, file, sizeof(tmp));
1554		else
1555			strlcat(tmp, p + 1, sizeof(tmp));
1556	} else {
1557		(void) strlcpy(tmp, file, sizeof(tmp));
1558	}
1559
1560	strlcat(tmp, ".0", sizeof(tmp));
1561	if (stat(tmp, &sb) < 0) {
1562		/*
1563		 * A plain '.0' file does not exist.  Try again, first
1564		 * with the added suffix of '.gz', then with an added
1565		 * suffix of '.bz2' instead of '.gz'.
1566		 */
1567		endp = strchr(tmp, '\0');
1568		strlcat(tmp, COMPRESS_POSTFIX, sizeof(tmp));
1569		if (stat(tmp, &sb) < 0) {
1570			*endp = '\0';		/* Remove .gz */
1571			strlcat(tmp, BZCOMPRESS_POSTFIX, sizeof(tmp));
1572			if (stat(tmp, &sb) < 0)
1573				return (-1);
1574		}
1575	}
1576	return ((int)(timenow - sb.st_mtime + 1800) / 3600);
1577}
1578
1579/* Skip Over Blanks */
1580static char *
1581sob(char *p)
1582{
1583	while (p && *p && isspace(*p))
1584		p++;
1585	return (p);
1586}
1587
1588/* Skip Over Non-Blanks */
1589static char *
1590son(char *p)
1591{
1592	while (p && *p && !isspace(*p))
1593		p++;
1594	return (p);
1595}
1596
1597/* Check if string is actually a number */
1598static int
1599isnumberstr(const char *string)
1600{
1601	while (*string) {
1602		if (!isdigitch(*string++))
1603			return (0);
1604	}
1605	return (1);
1606}
1607
1608/* physically move file */
1609static void
1610movefile(char *from, char *to, int perm, uid_t owner_uid, gid_t group_gid)
1611{
1612	FILE *src, *dst;
1613	int c;
1614
1615	if ((src = fopen(from, "r")) == NULL)
1616		err(1, "can't fopen %s for reading", from);
1617	if ((dst = fopen(to, "w")) == NULL)
1618		err(1, "can't fopen %s for writing", to);
1619	if (owner_uid != (uid_t)-1 || group_gid != (gid_t)-1) {
1620		if (fchown(fileno(dst), owner_uid, group_gid))
1621			err(1, "can't fchown %s", to);
1622	}
1623	if (fchmod(fileno(dst), perm))
1624		err(1, "can't fchmod %s", to);
1625
1626	while ((c = getc(src)) != EOF) {
1627		if ((putc(c, dst)) == EOF)
1628			err(1, "error writing to %s", to);
1629	}
1630
1631	if (ferror(src))
1632		err(1, "error reading from %s", from);
1633	if ((fclose(src)) != 0)
1634		err(1, "can't fclose %s", to);
1635	if ((fclose(dst)) != 0)
1636		err(1, "can't fclose %s", from);
1637	if ((unlink(from)) != 0)
1638		err(1, "can't unlink %s", from);
1639}
1640
1641/* create one or more directory components of a path */
1642static void
1643createdir(const struct conf_entry *ent, char *dirpart)
1644{
1645	int res;
1646	char *s, *d;
1647	char mkdirpath[MAXPATHLEN];
1648	struct stat st;
1649
1650	s = dirpart;
1651	d = mkdirpath;
1652
1653	for (;;) {
1654		*d++ = *s++;
1655		if (*s != '/' && *s != '\0')
1656			continue;
1657		*d = '\0';
1658		res = lstat(mkdirpath, &st);
1659		if (res != 0) {
1660			if (noaction) {
1661				printf("\tmkdir %s\n", mkdirpath);
1662			} else {
1663				res = mkdir(mkdirpath, 0755);
1664				if (res != 0)
1665					err(1, "Error on mkdir(\"%s\") for -a",
1666					    mkdirpath);
1667			}
1668		}
1669		if (*s == '\0')
1670			break;
1671	}
1672	if (verbose) {
1673		if (ent->firstcreate)
1674			printf("Created directory '%s' for new %s\n",
1675			    dirpart, ent->log);
1676		else
1677			printf("Created directory '%s' for -a\n", dirpart);
1678	}
1679}
1680
1681/*
1682 * Create a new log file, destroying any currently-existing version
1683 * of the log file in the process.  If the caller wants a backup copy
1684 * of the file to exist, they should call 'link(logfile,logbackup)'
1685 * before calling this routine.
1686 */
1687void
1688createlog(const struct conf_entry *ent)
1689{
1690	int fd, failed;
1691	struct stat st;
1692	char *realfile, *slash, tempfile[MAXPATHLEN];
1693
1694	fd = -1;
1695	realfile = ent->log;
1696
1697	/*
1698	 * If this log file is being created for the first time (-C option),
1699	 * then it may also be true that the parent directory does not exist
1700	 * yet.  Check, and create that directory if it is missing.
1701	 */
1702	if (ent->firstcreate) {
1703		strlcpy(tempfile, realfile, sizeof(tempfile));
1704		slash = strrchr(tempfile, '/');
1705		if (slash != NULL) {
1706			*slash = '\0';
1707			failed = lstat(tempfile, &st);
1708			if (failed && errno != ENOENT)
1709				err(1, "Error on lstat(%s)", tempfile);
1710			if (failed)
1711				createdir(ent, tempfile);
1712			else if (!S_ISDIR(st.st_mode))
1713				errx(1, "%s exists but is not a directory",
1714				    tempfile);
1715		}
1716	}
1717
1718	/*
1719	 * First create an unused filename, so it can be chown'ed and
1720	 * chmod'ed before it is moved into the real location.  mkstemp
1721	 * will create the file mode=600 & owned by us.  Note that all
1722	 * temp files will have a suffix of '.z<something>'.
1723	 */
1724	strlcpy(tempfile, realfile, sizeof(tempfile));
1725	strlcat(tempfile, ".zXXXXXX", sizeof(tempfile));
1726	if (noaction)
1727		printf("\tmktemp %s\n", tempfile);
1728	else {
1729		fd = mkstemp(tempfile);
1730		if (fd < 0)
1731			err(1, "can't mkstemp logfile %s", tempfile);
1732
1733		/*
1734		 * Add status message to what will become the new log file.
1735		 */
1736		if (!(ent->flags & CE_BINARY)) {
1737			if (log_trim(tempfile, ent))
1738				err(1, "can't add status message to log");
1739		}
1740	}
1741
1742	/* Change the owner/group, if we are supposed to */
1743	if (ent->uid != (uid_t)-1 || ent->gid != (gid_t)-1) {
1744		if (noaction)
1745			printf("\tchown %u:%u %s\n", ent->uid, ent->gid,
1746			    tempfile);
1747		else {
1748			failed = fchown(fd, ent->uid, ent->gid);
1749			if (failed)
1750				err(1, "can't fchown temp file %s", tempfile);
1751		}
1752	}
1753
1754	/*
1755	 * Note that if the real logfile still exists, and if the call
1756	 * to rename() fails, then "neither the old file nor the new
1757	 * file shall be changed or created" (to quote the standard).
1758	 * If the call succeeds, then the file will be replaced without
1759	 * any window where some other process might find that the file
1760	 * did not exist.
1761	 * XXX - ? It may be that for some error conditions, we could
1762	 *	retry by first removing the realfile and then renaming.
1763	 */
1764	if (noaction) {
1765		printf("\tchmod %o %s\n", ent->permissions, tempfile);
1766		printf("\tmv %s %s\n", tempfile, realfile);
1767	} else {
1768		failed = fchmod(fd, ent->permissions);
1769		if (failed)
1770			err(1, "can't fchmod temp file '%s'", tempfile);
1771		failed = rename(tempfile, realfile);
1772		if (failed)
1773			err(1, "can't mv %s to %s", tempfile, realfile);
1774	}
1775
1776	if (fd >= 0)
1777		close(fd);
1778}
1779
1780/*-
1781 * Parse a limited subset of ISO 8601. The specific format is as follows:
1782 *
1783 * [CC[YY[MM[DD]]]][THH[MM[SS]]]	(where `T' is the literal letter)
1784 *
1785 * We don't accept a timezone specification; missing fields (including timezone)
1786 * are defaulted to the current date but time zero.
1787 */
1788static time_t
1789parse8601(const char *s)
1790{
1791	char *t;
1792	time_t tsecs;
1793	struct tm tm, *tmp;
1794	long l;
1795
1796	tmp = localtime(&timenow);
1797	tm = *tmp;
1798
1799	tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
1800
1801	l = strtol(s, &t, 10);
1802	if (l < 0 || l >= INT_MAX || (*t != '\0' && *t != 'T'))
1803		return (-1);
1804
1805	/*
1806	 * Now t points either to the end of the string (if no time was
1807	 * provided) or to the letter `T' which separates date and time in
1808	 * ISO 8601.  The pointer arithmetic is the same for either case.
1809	 */
1810	switch (t - s) {
1811	case 8:
1812		tm.tm_year = ((l / 1000000) - 19) * 100;
1813		l = l % 1000000;
1814	case 6:
1815		tm.tm_year -= tm.tm_year % 100;
1816		tm.tm_year += l / 10000;
1817		l = l % 10000;
1818	case 4:
1819		tm.tm_mon = (l / 100) - 1;
1820		l = l % 100;
1821	case 2:
1822		tm.tm_mday = l;
1823	case 0:
1824		break;
1825	default:
1826		return (-1);
1827	}
1828
1829	/* sanity check */
1830	if (tm.tm_year < 70 || tm.tm_mon < 0 || tm.tm_mon > 12
1831	    || tm.tm_mday < 1 || tm.tm_mday > 31)
1832		return (-1);
1833
1834	if (*t != '\0') {
1835		s = ++t;
1836		l = strtol(s, &t, 10);
1837		if (l < 0 || l >= INT_MAX || (*t != '\0' && !isspace(*t)))
1838			return (-1);
1839
1840		switch (t - s) {
1841		case 6:
1842			tm.tm_sec = l % 100;
1843			l /= 100;
1844		case 4:
1845			tm.tm_min = l % 100;
1846			l /= 100;
1847		case 2:
1848			tm.tm_hour = l;
1849		case 0:
1850			break;
1851		default:
1852			return (-1);
1853		}
1854
1855		/* sanity check */
1856		if (tm.tm_sec < 0 || tm.tm_sec > 60 || tm.tm_min < 0
1857		    || tm.tm_min > 59 || tm.tm_hour < 0 || tm.tm_hour > 23)
1858			return (-1);
1859	}
1860
1861	tsecs = mktime(&tm);
1862	/*
1863	 * Check for invalid times, including things like the missing
1864	 * hour when switching from "daylight savings" to "standard".
1865	 */
1866	if (tsecs == (time_t)-1)
1867		tsecs = (time_t)-2;
1868	return (tsecs);
1869}
1870
1871/*-
1872 * Parse a cyclic time specification, the format is as follows:
1873 *
1874 *	[Dhh] or [Wd[Dhh]] or [Mdd[Dhh]]
1875 *
1876 * to rotate a logfile cyclic at
1877 *
1878 *	- every day (D) within a specific hour (hh)	(hh = 0...23)
1879 *	- once a week (W) at a specific day (d)     OR	(d = 0..6, 0 = Sunday)
1880 *	- once a month (M) at a specific day (d)	(d = 1..31,l|L)
1881 *
1882 * We don't accept a timezone specification; missing fields
1883 * are defaulted to the current date but time zero.
1884 */
1885static time_t
1886parseDWM(char *s)
1887{
1888	char *t;
1889	time_t tsecs;
1890	struct tm tm, *tmp;
1891	long l;
1892	int nd;
1893	static int mtab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
1894	int WMseen = 0;
1895	int Dseen = 0;
1896
1897	tmp = localtime(&timenow);
1898	tm = *tmp;
1899
1900	/* set no. of days per month */
1901
1902	nd = mtab[tm.tm_mon];
1903
1904	if (tm.tm_mon == 1) {
1905		if (((tm.tm_year + 1900) % 4 == 0) &&
1906		    ((tm.tm_year + 1900) % 100 != 0) &&
1907		    ((tm.tm_year + 1900) % 400 == 0)) {
1908			nd++;	/* leap year, 29 days in february */
1909		}
1910	}
1911	tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
1912
1913	for (;;) {
1914		switch (*s) {
1915		case 'D':
1916			if (Dseen)
1917				return (-1);
1918			Dseen++;
1919			s++;
1920			l = strtol(s, &t, 10);
1921			if (l < 0 || l > 23)
1922				return (-1);
1923			tm.tm_hour = l;
1924			break;
1925
1926		case 'W':
1927			if (WMseen)
1928				return (-1);
1929			WMseen++;
1930			s++;
1931			l = strtol(s, &t, 10);
1932			if (l < 0 || l > 6)
1933				return (-1);
1934			if (l != tm.tm_wday) {
1935				int save;
1936
1937				if (l < tm.tm_wday) {
1938					save = 6 - tm.tm_wday;
1939					save += (l + 1);
1940				} else {
1941					save = l - tm.tm_wday;
1942				}
1943
1944				tm.tm_mday += save;
1945
1946				if (tm.tm_mday > nd) {
1947					tm.tm_mon++;
1948					tm.tm_mday = tm.tm_mday - nd;
1949				}
1950			}
1951			break;
1952
1953		case 'M':
1954			if (WMseen)
1955				return (-1);
1956			WMseen++;
1957			s++;
1958			if (tolower(*s) == 'l') {
1959				tm.tm_mday = nd;
1960				s++;
1961				t = s;
1962			} else {
1963				l = strtol(s, &t, 10);
1964				if (l < 1 || l > 31)
1965					return (-1);
1966
1967				if (l > nd)
1968					return (-1);
1969				tm.tm_mday = l;
1970			}
1971			break;
1972
1973		default:
1974			return (-1);
1975			break;
1976		}
1977
1978		if (*t == '\0' || isspace(*t))
1979			break;
1980		else
1981			s = t;
1982	}
1983
1984	tsecs = mktime(&tm);
1985	/*
1986	 * Check for invalid times, including things like the missing
1987	 * hour when switching from "daylight savings" to "standard".
1988	 */
1989	if (tsecs == (time_t)-1)
1990		tsecs = (time_t)-2;
1991	return (tsecs);
1992}
1993