newsyslog.c revision 120361
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 120361 2003-09-23 00:00:26Z 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
77#define MIN_PID         5	/* Don't touch pids lower than this */
78#define MAX_PID		99999	/* was lower, see /usr/include/sys/proc.h */
79
80#define kbytes(size)  (((size) + 1023) >> 10)
81
82struct conf_entry {
83	char *log;		/* Name of the log */
84	char *pid_file;		/* PID file */
85	char *r_reason;		/* The reason this file is being rotated */
86	int firstcreate;	/* Creating log for the first time (-C). */
87	int rotate;		/* Non-zero if this file should be rotated */
88	uid_t uid;		/* Owner of log */
89	gid_t gid;		/* Group of log */
90	int numlogs;		/* Number of logs to keep */
91	int size;		/* Size cutoff to trigger trimming the log */
92	int hours;		/* Hours between log trimming */
93	struct ptime_data *trim_at;	/* Specific time to do trimming */
94	int permissions;	/* File permissions on the log */
95	int flags;		/* CE_COMPACT, CE_BZCOMPACT, CE_BINARY */
96	int sig;		/* Signal to send */
97	int def_cfg;		/* Using the <default> rule for this file */
98	struct conf_entry *next;/* Linked list pointer */
99};
100
101#define DEFAULT_MARKER "<default>"
102
103int dbg_at_times;		/* -D Show details of 'trim_at' code */
104
105int archtodir = 0;		/* Archive old logfiles to other directory */
106int createlogs;			/* Create (non-GLOB) logfiles which do not */
107				/*    already exist.  1=='for entries with */
108				/*    C flag', 2=='for all entries'. */
109int verbose = 0;		/* Print out what's going on */
110int needroot = 1;		/* Root privs are necessary */
111int noaction = 0;		/* Don't do anything, just show it */
112int nosignal;			/* Do not send any signals */
113int force = 0;			/* Force the trim no matter what */
114int rotatereq = 0;		/* -R = Always rotate the file(s) as given */
115				/*    on the command (this also requires   */
116				/*    that a list of files *are* given on  */
117				/*    the run command). */
118char *requestor;		/* The name given on a -R request */
119char *archdirname;		/* Directory path to old logfiles archive */
120const char *conf;		/* Configuration file to use */
121
122struct ptime_data *dbg_timenow;	/* A "timenow" value set via -D option */
123struct ptime_data *timenow;	/* The time to use for checking at-fields */
124
125char hostname[MAXHOSTNAMELEN];	/* hostname */
126char daytime[16];		/* The current time in human readable form,
127				 * used for rotation-tracking messages. */
128
129static struct conf_entry *get_worklist(char **files);
130static void parse_file(FILE *cf, const char *cfname, struct conf_entry **work_p,
131		struct conf_entry **glob_p, struct conf_entry **defconf_p);
132static char *sob(char *p);
133static char *son(char *p);
134static int isnumberstr(const char *);
135static char *missing_field(char *p, char *errline);
136static void do_entry(struct conf_entry * ent);
137static void expand_globs(struct conf_entry **work_p,
138		struct conf_entry **glob_p);
139static void free_clist(struct conf_entry **firstent);
140static void free_entry(struct conf_entry *ent);
141static struct conf_entry *init_entry(const char *fname,
142		struct conf_entry *src_entry);
143static void parse_args(int argc, char **argv);
144static int parse_doption(const char *doption);
145static void usage(void);
146static void dotrim(const struct conf_entry *ent);
147static int log_trim(const char *logname, const struct conf_entry *log_ent);
148static void compress_log(char *logname, int dowait);
149static void bzcompress_log(char *logname, int dowait);
150static int sizefile(char *file);
151static int age_old_log(char *file);
152static int send_signal(const struct conf_entry *ent);
153static void movefile(char *from, char *to, int perm, uid_t owner_uid,
154		gid_t group_gid);
155static void createdir(const struct conf_entry *ent, char *dirpart);
156static void createlog(const struct conf_entry *ent);
157
158/*
159 * All the following are defined to work on an 'int', in the
160 * range 0 to 255, plus EOF.  Define wrappers which can take
161 * values of type 'char', either signed or unsigned.
162 */
163#define isdigitch(Anychar)    isdigit(((int) Anychar) & 255)
164#define isprintch(Anychar)    isprint(((int) Anychar) & 255)
165#define isspacech(Anychar)    isspace(((int) Anychar) & 255)
166#define tolowerch(Anychar)    tolower(((int) Anychar) & 255)
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 'g':
1142				working->flags |= CE_GLOB;
1143				break;
1144			case 'j':
1145				working->flags |= CE_BZCOMPACT;
1146				break;
1147			case 'n':
1148				working->flags |= CE_NOSIGNAL;
1149				break;
1150			case 'u':
1151				working->flags |= CE_SIGNALGROUP;
1152				break;
1153			case 'w':
1154				working->flags |= CE_COMPACTWAIT;
1155				break;
1156			case 'z':
1157				working->flags |= CE_COMPACT;
1158				break;
1159			case '-':
1160				break;
1161			case 'f':	/* Used by OpenBSD for "CE_FOLLOW" */
1162			case 'm':	/* Used by OpenBSD for "CE_MONITOR" */
1163			case 'p':	/* Used by NetBSD  for "CE_PLAIN0" */
1164			default:
1165				errx(1, "illegal flag in config file -- %c",
1166				    *q);
1167			}
1168		}
1169
1170		if (eol)
1171			q = NULL;
1172		else {
1173			q = parse = sob(++parse);	/* Optional field */
1174			parse = son(parse);
1175			if (!*parse)
1176				eol = 1;
1177			*parse = '\0';
1178		}
1179
1180		working->pid_file = NULL;
1181		if (q && *q) {
1182			if (*q == '/')
1183				working->pid_file = strdup(q);
1184			else if (isdigit(*q))
1185				goto got_sig;
1186			else
1187				errx(1,
1188			"illegal pid file or signal number in config file:\n%s",
1189				    errline);
1190		}
1191		if (eol)
1192			q = NULL;
1193		else {
1194			q = parse = sob(++parse);	/* Optional field */
1195			*(parse = son(parse)) = '\0';
1196		}
1197
1198		working->sig = SIGHUP;
1199		if (q && *q) {
1200			if (isdigit(*q)) {
1201		got_sig:
1202				working->sig = atoi(q);
1203			} else {
1204		err_sig:
1205				errx(1,
1206				    "illegal signal number in config file:\n%s",
1207				    errline);
1208			}
1209			if (working->sig < 1 || working->sig >= NSIG)
1210				goto err_sig;
1211		}
1212
1213		/*
1214		 * Finish figuring out what pid-file to use (if any) in
1215		 * later processing if this logfile needs to be rotated.
1216		 */
1217		if ((working->flags & CE_NOSIGNAL) == CE_NOSIGNAL) {
1218			/*
1219			 * This config-entry specified 'n' for nosignal,
1220			 * see if it also specified an explicit pid_file.
1221			 * This would be a pretty pointless combination.
1222			 */
1223			if (working->pid_file != NULL) {
1224				warnx("Ignoring '%s' because flag 'n' was specified in line:\n%s",
1225				    working->pid_file, errline);
1226				free(working->pid_file);
1227				working->pid_file = NULL;
1228			}
1229		} else if (working->pid_file == NULL) {
1230			/*
1231			 * This entry did not specify the 'n' flag, which
1232			 * means it should signal syslogd unless it had
1233			 * specified some other pid-file (and obviously the
1234			 * syslog pid-file will not be for a process-group).
1235			 * Also, we should only try to notify syslog if we
1236			 * are root.
1237			 */
1238			if (working->flags & CE_SIGNALGROUP) {
1239				warnx("Ignoring flag 'U' in line:\n%s",
1240				    errline);
1241				working->flags &= ~CE_SIGNALGROUP;
1242			}
1243			if (needroot)
1244				working->pid_file = strdup(_PATH_SYSLOGPID);
1245		}
1246
1247		/*
1248		 * Add this entry to the appropriate list of entries, unless
1249		 * it was some kind of special entry (eg: <default>).
1250		 */
1251		if (special) {
1252			;			/* Do not add to any list */
1253		} else if (working->flags & CE_GLOB) {
1254			if (!*glob_p)
1255				*glob_p = working;
1256			else
1257				lastglob->next = working;
1258			lastglob = working;
1259		} else {
1260			if (!*work_p)
1261				*work_p = working;
1262			else
1263				lastwork->next = working;
1264			lastwork = working;
1265		}
1266
1267		free(errline);
1268		errline = NULL;
1269	}
1270}
1271
1272static char *
1273missing_field(char *p, char *errline)
1274{
1275
1276	if (!p || !*p)
1277		errx(1, "missing field in config file:\n%s", errline);
1278	return (p);
1279}
1280
1281static void
1282dotrim(const struct conf_entry *ent)
1283{
1284	char dirpart[MAXPATHLEN], namepart[MAXPATHLEN];
1285	char file1[MAXPATHLEN], file2[MAXPATHLEN];
1286	char zfile1[MAXPATHLEN], zfile2[MAXPATHLEN];
1287	char jfile1[MAXPATHLEN];
1288	char tfile[MAXPATHLEN];
1289	int flags, notified, need_notification, fd, numlogs_c;
1290	struct stat st;
1291
1292	flags = ent->flags;
1293
1294	if (archtodir) {
1295		char *p;
1296
1297		/* build complete name of archive directory into dirpart */
1298		if (*archdirname == '/') {	/* absolute */
1299			strlcpy(dirpart, archdirname, sizeof(dirpart));
1300		} else {	/* relative */
1301			/* get directory part of logfile */
1302			strlcpy(dirpart, ent->log, sizeof(dirpart));
1303			if ((p = rindex(dirpart, '/')) == NULL)
1304				dirpart[0] = '\0';
1305			else
1306				*(p + 1) = '\0';
1307			strlcat(dirpart, archdirname, sizeof(dirpart));
1308		}
1309
1310		/* check if archive directory exists, if not, create it */
1311		if (lstat(dirpart, &st))
1312			createdir(ent, dirpart);
1313
1314		/* get filename part of logfile */
1315		if ((p = rindex(ent->log, '/')) == NULL)
1316			strlcpy(namepart, ent->log, sizeof(namepart));
1317		else
1318			strlcpy(namepart, p + 1, sizeof(namepart));
1319
1320		/* name of oldest log */
1321		(void) snprintf(file1, sizeof(file1), "%s/%s.%d", dirpart,
1322		    namepart, ent->numlogs);
1323		(void) snprintf(zfile1, sizeof(zfile1), "%s%s", file1,
1324		    COMPRESS_POSTFIX);
1325		snprintf(jfile1, sizeof(jfile1), "%s%s", file1,
1326		    BZCOMPRESS_POSTFIX);
1327	} else {
1328		/* name of oldest log */
1329		(void) snprintf(file1, sizeof(file1), "%s.%d", ent->log,
1330		    ent->numlogs);
1331		(void) snprintf(zfile1, sizeof(zfile1), "%s%s", file1,
1332		    COMPRESS_POSTFIX);
1333		snprintf(jfile1, sizeof(jfile1), "%s%s", file1,
1334		    BZCOMPRESS_POSTFIX);
1335	}
1336
1337	if (noaction) {
1338		printf("\trm -f %s\n", file1);
1339		printf("\trm -f %s\n", zfile1);
1340		printf("\trm -f %s\n", jfile1);
1341	} else {
1342		(void) unlink(file1);
1343		(void) unlink(zfile1);
1344		(void) unlink(jfile1);
1345	}
1346
1347	/* Move down log files */
1348	numlogs_c = ent->numlogs;		/* copy for countdown */
1349	while (numlogs_c--) {
1350
1351		(void) strlcpy(file2, file1, sizeof(file2));
1352
1353		if (archtodir)
1354			(void) snprintf(file1, sizeof(file1), "%s/%s.%d",
1355			    dirpart, namepart, numlogs_c);
1356		else
1357			(void) snprintf(file1, sizeof(file1), "%s.%d",
1358			    ent->log, numlogs_c);
1359
1360		(void) strlcpy(zfile1, file1, sizeof(zfile1));
1361		(void) strlcpy(zfile2, file2, sizeof(zfile2));
1362		if (lstat(file1, &st)) {
1363			(void) strlcat(zfile1, COMPRESS_POSTFIX,
1364			    sizeof(zfile1));
1365			(void) strlcat(zfile2, COMPRESS_POSTFIX,
1366			    sizeof(zfile2));
1367			if (lstat(zfile1, &st)) {
1368				strlcpy(zfile1, file1, sizeof(zfile1));
1369				strlcpy(zfile2, file2, sizeof(zfile2));
1370				strlcat(zfile1, BZCOMPRESS_POSTFIX,
1371				    sizeof(zfile1));
1372				strlcat(zfile2, BZCOMPRESS_POSTFIX,
1373				    sizeof(zfile2));
1374				if (lstat(zfile1, &st))
1375					continue;
1376			}
1377		}
1378		if (noaction) {
1379			printf("\tmv %s %s\n", zfile1, zfile2);
1380			printf("\tchmod %o %s\n", ent->permissions, zfile2);
1381			if (ent->uid != (uid_t)-1 || ent->gid != (gid_t)-1)
1382				printf("\tchown %u:%u %s\n",
1383				    ent->uid, ent->gid, zfile2);
1384		} else {
1385			(void) rename(zfile1, zfile2);
1386			if (chmod(zfile2, ent->permissions))
1387				warn("can't chmod %s", file2);
1388			if (ent->uid != (uid_t)-1 || ent->gid != (gid_t)-1)
1389				if (chown(zfile2, ent->uid, ent->gid))
1390					warn("can't chown %s", zfile2);
1391		}
1392	}
1393	if (!noaction && !(flags & CE_BINARY)) {
1394		/* Report the trimming to the old log */
1395		(void) log_trim(ent->log, ent);
1396	}
1397
1398	if (ent->numlogs == 0) {
1399		if (noaction)
1400			printf("\trm %s\n", ent->log);
1401		else
1402			(void) unlink(ent->log);
1403	} else {
1404		if (noaction)
1405			printf("\tmv %s to %s\n", ent->log, file1);
1406		else {
1407			if (archtodir)
1408				movefile(ent->log, file1, ent->permissions,
1409				    ent->uid, ent->gid);
1410			else
1411				(void) rename(ent->log, file1);
1412		}
1413	}
1414
1415	/* Now move the new log file into place */
1416	/* XXX - We should replace the above 'rename' with
1417	*	'link(ent->log, file1)' and then replace
1418	 *	the following with 'createfile(ent)' */
1419	strlcpy(tfile, ent->log, sizeof(tfile));
1420	strlcat(tfile, ".XXXXXX", sizeof(tfile));
1421	if (noaction) {
1422		printf("Start new log...\n");
1423		printf("\tmktemp %s\n", tfile);
1424	} else {
1425		mkstemp(tfile);
1426		fd = creat(tfile, ent->permissions);
1427		if (fd < 0)
1428			err(1, "can't start new log");
1429		if (ent->uid != (uid_t)-1 || ent->gid != (gid_t)-1)
1430			if (fchown(fd, ent->uid, ent->gid))
1431			    err(1, "can't chown new log file");
1432		(void) close(fd);
1433		if (!(flags & CE_BINARY)) {
1434			/* Add status message to new log file */
1435			if (log_trim(tfile, ent))
1436				err(1, "can't add status message to log");
1437		}
1438	}
1439	if (noaction) {
1440		printf("\tchmod %o %s\n", ent->permissions, tfile);
1441		printf("\tmv %s %s\n", tfile, ent->log);
1442	} else {
1443		(void) chmod(tfile, ent->permissions);
1444		if (rename(tfile, ent->log) < 0) {
1445			err(1, "can't start new log");
1446			(void) unlink(tfile);
1447		}
1448	}
1449
1450	/*
1451	 * Find out if there is a process to signal.  If nosignal (-s) was
1452	 * specified, then do not signal any process.  Note that nosignal
1453	 * will trigger a warning message if the rotated logfile needs to
1454	 * be compressed, *unless* -R was specified.  This is because there
1455	 * presumably still are process(es) writing to the old logfile, but
1456	 * we assume that a -sR request comes from a process which writes
1457	 * to the logfile, and as such, that process has already made sure
1458	 * that the logfile is not presently in use.
1459	 */
1460	need_notification = notified = 0;
1461	if (ent->pid_file != NULL) {
1462		need_notification = 1;
1463		if (!nosignal)
1464			notified = send_signal(ent);	/* the normal case! */
1465		else if (rotatereq)
1466			need_notification = 0;
1467	}
1468
1469	if ((flags & CE_COMPACT) || (flags & CE_BZCOMPACT)) {
1470		if (need_notification && !notified)
1471			warnx(
1472			    "log %s.0 not compressed because daemon(s) not notified",
1473			    ent->log);
1474		else if (noaction)
1475			if (flags & CE_COMPACT)
1476				printf("\tgzip %s.0\n", ent->log);
1477			else
1478				printf("\tbzip2 %s.0\n", ent->log);
1479		else {
1480			if (notified) {
1481				if (verbose)
1482					printf("small pause to allow daemon(s) to close log\n");
1483				sleep(10);
1484			}
1485			if (archtodir) {
1486				(void) snprintf(file1, sizeof(file1), "%s/%s",
1487				    dirpart, namepart);
1488				if (flags & CE_COMPACT)
1489					compress_log(file1,
1490					    flags & CE_COMPACTWAIT);
1491				else if (flags & CE_BZCOMPACT)
1492					bzcompress_log(file1,
1493					    flags & CE_COMPACTWAIT);
1494			} else {
1495				if (flags & CE_COMPACT)
1496					compress_log(ent->log,
1497					    flags & CE_COMPACTWAIT);
1498				else if (flags & CE_BZCOMPACT)
1499					bzcompress_log(ent->log,
1500					    flags & CE_COMPACTWAIT);
1501			}
1502		}
1503	}
1504}
1505
1506/* Log the fact that the logs were turned over */
1507static int
1508log_trim(const char *logname, const struct conf_entry *log_ent)
1509{
1510	FILE *f;
1511	const char *xtra;
1512
1513	if ((f = fopen(logname, "a")) == NULL)
1514		return (-1);
1515	xtra = "";
1516	if (log_ent->def_cfg)
1517		xtra = " using <default> rule";
1518	if (log_ent->firstcreate)
1519		fprintf(f, "%s %s newsyslog[%d]: logfile first created%s\n",
1520		    daytime, hostname, (int) getpid(), xtra);
1521	else if (log_ent->r_reason != NULL)
1522		fprintf(f, "%s %s newsyslog[%d]: logfile turned over%s%s\n",
1523		    daytime, hostname, (int) getpid(), log_ent->r_reason, xtra);
1524	else
1525		fprintf(f, "%s %s newsyslog[%d]: logfile turned over%s\n",
1526		    daytime, hostname, (int) getpid(), xtra);
1527	if (fclose(f) == EOF)
1528		err(1, "log_trim: fclose:");
1529	return (0);
1530}
1531
1532/* Fork of gzip to compress the old log file */
1533static void
1534compress_log(char *logname, int dowait)
1535{
1536	pid_t pid;
1537	char tmp[MAXPATHLEN];
1538
1539	while (dowait && (wait(NULL) > 0 || errno == EINTR))
1540		;
1541	(void) snprintf(tmp, sizeof(tmp), "%s.0", logname);
1542	pid = fork();
1543	if (pid < 0)
1544		err(1, "gzip fork");
1545	else if (!pid) {
1546		(void) execl(_PATH_GZIP, _PATH_GZIP, "-f", tmp, (char *)0);
1547		err(1, _PATH_GZIP);
1548	}
1549}
1550
1551/* Fork of bzip2 to compress the old log file */
1552static void
1553bzcompress_log(char *logname, int dowait)
1554{
1555	pid_t pid;
1556	char tmp[MAXPATHLEN];
1557
1558	while (dowait && (wait(NULL) > 0 || errno == EINTR))
1559		;
1560	snprintf(tmp, sizeof(tmp), "%s.0", logname);
1561	pid = fork();
1562	if (pid < 0)
1563		err(1, "bzip2 fork");
1564	else if (!pid) {
1565		execl(_PATH_BZIP2, _PATH_BZIP2, "-f", tmp, (char *)0);
1566		err(1, _PATH_BZIP2);
1567	}
1568}
1569
1570/* Return size in kilobytes of a file */
1571static int
1572sizefile(char *file)
1573{
1574	struct stat sb;
1575
1576	if (stat(file, &sb) < 0)
1577		return (-1);
1578	return (kbytes(dbtob(sb.st_blocks)));
1579}
1580
1581/* Return the age of old log file (file.0) */
1582static int
1583age_old_log(char *file)
1584{
1585	struct stat sb;
1586	char *endp;
1587	char tmp[MAXPATHLEN + sizeof(".0") + sizeof(COMPRESS_POSTFIX) +
1588		sizeof(BZCOMPRESS_POSTFIX) + 1];
1589
1590	if (archtodir) {
1591		char *p;
1592
1593		/* build name of archive directory into tmp */
1594		if (*archdirname == '/') {	/* absolute */
1595			strlcpy(tmp, archdirname, sizeof(tmp));
1596		} else {	/* relative */
1597			/* get directory part of logfile */
1598			strlcpy(tmp, file, sizeof(tmp));
1599			if ((p = rindex(tmp, '/')) == NULL)
1600				tmp[0] = '\0';
1601			else
1602				*(p + 1) = '\0';
1603			strlcat(tmp, archdirname, sizeof(tmp));
1604		}
1605
1606		strlcat(tmp, "/", sizeof(tmp));
1607
1608		/* get filename part of logfile */
1609		if ((p = rindex(file, '/')) == NULL)
1610			strlcat(tmp, file, sizeof(tmp));
1611		else
1612			strlcat(tmp, p + 1, sizeof(tmp));
1613	} else {
1614		(void) strlcpy(tmp, file, sizeof(tmp));
1615	}
1616
1617	strlcat(tmp, ".0", sizeof(tmp));
1618	if (stat(tmp, &sb) < 0) {
1619		/*
1620		 * A plain '.0' file does not exist.  Try again, first
1621		 * with the added suffix of '.gz', then with an added
1622		 * suffix of '.bz2' instead of '.gz'.
1623		 */
1624		endp = strchr(tmp, '\0');
1625		strlcat(tmp, COMPRESS_POSTFIX, sizeof(tmp));
1626		if (stat(tmp, &sb) < 0) {
1627			*endp = '\0';		/* Remove .gz */
1628			strlcat(tmp, BZCOMPRESS_POSTFIX, sizeof(tmp));
1629			if (stat(tmp, &sb) < 0)
1630				return (-1);
1631		}
1632	}
1633	return ((int)(ptimeget_secs(timenow) - sb.st_mtime + 1800) / 3600);
1634}
1635
1636/* Skip Over Blanks */
1637static char *
1638sob(char *p)
1639{
1640	while (p && *p && isspace(*p))
1641		p++;
1642	return (p);
1643}
1644
1645/* Skip Over Non-Blanks */
1646static char *
1647son(char *p)
1648{
1649	while (p && *p && !isspace(*p))
1650		p++;
1651	return (p);
1652}
1653
1654/* Check if string is actually a number */
1655static int
1656isnumberstr(const char *string)
1657{
1658	while (*string) {
1659		if (!isdigitch(*string++))
1660			return (0);
1661	}
1662	return (1);
1663}
1664
1665/* physically move file */
1666static void
1667movefile(char *from, char *to, int perm, uid_t owner_uid, gid_t group_gid)
1668{
1669	FILE *src, *dst;
1670	int c;
1671
1672	if ((src = fopen(from, "r")) == NULL)
1673		err(1, "can't fopen %s for reading", from);
1674	if ((dst = fopen(to, "w")) == NULL)
1675		err(1, "can't fopen %s for writing", to);
1676	if (owner_uid != (uid_t)-1 || group_gid != (gid_t)-1) {
1677		if (fchown(fileno(dst), owner_uid, group_gid))
1678			err(1, "can't fchown %s", to);
1679	}
1680	if (fchmod(fileno(dst), perm))
1681		err(1, "can't fchmod %s", 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	if ((unlink(from)) != 0)
1695		err(1, "can't unlink %s", from);
1696}
1697
1698/* create one or more directory components of a path */
1699static void
1700createdir(const struct conf_entry *ent, char *dirpart)
1701{
1702	int res;
1703	char *s, *d;
1704	char mkdirpath[MAXPATHLEN];
1705	struct stat st;
1706
1707	s = dirpart;
1708	d = mkdirpath;
1709
1710	for (;;) {
1711		*d++ = *s++;
1712		if (*s != '/' && *s != '\0')
1713			continue;
1714		*d = '\0';
1715		res = lstat(mkdirpath, &st);
1716		if (res != 0) {
1717			if (noaction) {
1718				printf("\tmkdir %s\n", mkdirpath);
1719			} else {
1720				res = mkdir(mkdirpath, 0755);
1721				if (res != 0)
1722					err(1, "Error on mkdir(\"%s\") for -a",
1723					    mkdirpath);
1724			}
1725		}
1726		if (*s == '\0')
1727			break;
1728	}
1729	if (verbose) {
1730		if (ent->firstcreate)
1731			printf("Created directory '%s' for new %s\n",
1732			    dirpart, ent->log);
1733		else
1734			printf("Created directory '%s' for -a\n", dirpart);
1735	}
1736}
1737
1738/*
1739 * Create a new log file, destroying any currently-existing version
1740 * of the log file in the process.  If the caller wants a backup copy
1741 * of the file to exist, they should call 'link(logfile,logbackup)'
1742 * before calling this routine.
1743 */
1744void
1745createlog(const struct conf_entry *ent)
1746{
1747	int fd, failed;
1748	struct stat st;
1749	char *realfile, *slash, tempfile[MAXPATHLEN];
1750
1751	fd = -1;
1752	realfile = ent->log;
1753
1754	/*
1755	 * If this log file is being created for the first time (-C option),
1756	 * then it may also be true that the parent directory does not exist
1757	 * yet.  Check, and create that directory if it is missing.
1758	 */
1759	if (ent->firstcreate) {
1760		strlcpy(tempfile, realfile, sizeof(tempfile));
1761		slash = strrchr(tempfile, '/');
1762		if (slash != NULL) {
1763			*slash = '\0';
1764			failed = lstat(tempfile, &st);
1765			if (failed && errno != ENOENT)
1766				err(1, "Error on lstat(%s)", tempfile);
1767			if (failed)
1768				createdir(ent, tempfile);
1769			else if (!S_ISDIR(st.st_mode))
1770				errx(1, "%s exists but is not a directory",
1771				    tempfile);
1772		}
1773	}
1774
1775	/*
1776	 * First create an unused filename, so it can be chown'ed and
1777	 * chmod'ed before it is moved into the real location.  mkstemp
1778	 * will create the file mode=600 & owned by us.  Note that all
1779	 * temp files will have a suffix of '.z<something>'.
1780	 */
1781	strlcpy(tempfile, realfile, sizeof(tempfile));
1782	strlcat(tempfile, ".zXXXXXX", sizeof(tempfile));
1783	if (noaction)
1784		printf("\tmktemp %s\n", tempfile);
1785	else {
1786		fd = mkstemp(tempfile);
1787		if (fd < 0)
1788			err(1, "can't mkstemp logfile %s", tempfile);
1789
1790		/*
1791		 * Add status message to what will become the new log file.
1792		 */
1793		if (!(ent->flags & CE_BINARY)) {
1794			if (log_trim(tempfile, ent))
1795				err(1, "can't add status message to log");
1796		}
1797	}
1798
1799	/* Change the owner/group, if we are supposed to */
1800	if (ent->uid != (uid_t)-1 || ent->gid != (gid_t)-1) {
1801		if (noaction)
1802			printf("\tchown %u:%u %s\n", ent->uid, ent->gid,
1803			    tempfile);
1804		else {
1805			failed = fchown(fd, ent->uid, ent->gid);
1806			if (failed)
1807				err(1, "can't fchown temp file %s", tempfile);
1808		}
1809	}
1810
1811	/*
1812	 * Note that if the real logfile still exists, and if the call
1813	 * to rename() fails, then "neither the old file nor the new
1814	 * file shall be changed or created" (to quote the standard).
1815	 * If the call succeeds, then the file will be replaced without
1816	 * any window where some other process might find that the file
1817	 * did not exist.
1818	 * XXX - ? It may be that for some error conditions, we could
1819	 *	retry by first removing the realfile and then renaming.
1820	 */
1821	if (noaction) {
1822		printf("\tchmod %o %s\n", ent->permissions, tempfile);
1823		printf("\tmv %s %s\n", tempfile, realfile);
1824	} else {
1825		failed = fchmod(fd, ent->permissions);
1826		if (failed)
1827			err(1, "can't fchmod temp file '%s'", tempfile);
1828		failed = rename(tempfile, realfile);
1829		if (failed)
1830			err(1, "can't mv %s to %s", tempfile, realfile);
1831	}
1832
1833	if (fd >= 0)
1834		close(fd);
1835}
1836