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