newsyslog.c revision 208648
11541Srgrimes/*-
21541Srgrimes * ------+---------+---------+-------- + --------+---------+---------+---------*
31541Srgrimes * This file includes significant modifications done by:
41541Srgrimes * Copyright (c) 2003, 2004  - Garance Alistair Drosehn <gad@FreeBSD.org>.
51541Srgrimes * All rights reserved.
61541Srgrimes *
71541Srgrimes * Redistribution and use in source and binary forms, with or without
812623Sphk * modification, are permitted provided that the following conditions
912623Sphk * are met:
1012623Sphk *   1. Redistributions of source code must retain the above copyright
111541Srgrimes *      notice, this list of conditions and the following disclaimer.
121541Srgrimes *   2. Redistributions in binary form must reproduce the above copyright
131541Srgrimes *      notice, this list of conditions and the following disclaimer in the
141541Srgrimes *      documentation and/or other materials provided with the distribution.
151541Srgrimes *
161541Srgrimes * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
171541Srgrimes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
181541Srgrimes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
191541Srgrimes * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
201541Srgrimes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
211541Srgrimes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
221541Srgrimes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
231541Srgrimes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
241541Srgrimes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
251541Srgrimes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
261541Srgrimes * SUCH DAMAGE.
271541Srgrimes *
281541Srgrimes * ------+---------+---------+-------- + --------+---------+---------+---------*
291541Srgrimes */
301541Srgrimes
311541Srgrimes/*
321541Srgrimes * This file contains changes from the Open Software Foundation.
331541Srgrimes */
341541Srgrimes
351541Srgrimes/*
361541Srgrimes * Copyright 1988, 1989 by the Massachusetts Institute of Technology
371541Srgrimes *
38116182Sobrien * Permission to use, copy, modify, and distribute this software and its
39116182Sobrien * documentation for any purpose and without fee is hereby granted, provided
40116182Sobrien * that the above copyright notice appear in all copies and that both that
41224159Srwatson * copyright notice and this permission notice appear in supporting
4231778Seivind * documentation, and that the names of M.I.T. and the M.I.T. S.I.P.B. not be
43189707Sjhb * used in advertising or publicity pertaining to distribution of the
4431778Seivind * software without specific, written prior permission. M.I.T. and the M.I.T.
451541Srgrimes * S.I.P.B. make no representations about the suitability of this software
46216060Smdf * for any purpose.  It is provided "as is" without express or implied
4748274Speter * warranty.
48263233Srwatson *
4948274Speter */
501541Srgrimes
5112623Sphk/*
52164033Srwatson * newsyslog - roll over selected logs at the appropriate time, keeping the a
5312662Sdg * specified number of backup files around.
54194368Sbz */
5582746Sdillon
5682746Sdillon#include <sys/cdefs.h>
57287835Smjg__FBSDID("$FreeBSD: head/usr.sbin/newsyslog/newsyslog.c 208648 2010-05-29 22:52:17Z gordon $");
58212750Smdf
5993616Salfred#define	OSF
6015103Sphk#ifndef COMPRESS_POSTFIX
61185983Skib#define	COMPRESS_POSTFIX ".gz"
62189707Sjhb#endif
63189707Sjhb#ifndef	BZCOMPRESS_POSTFIX
64189707Sjhb#define	BZCOMPRESS_POSTFIX ".bz2"
65163606Srwatson#endif
66195699Srwatson
67195699Srwatson#include <sys/param.h>
68163606Srwatson#include <sys/queue.h>
69163606Srwatson#include <sys/stat.h>
7012645Sbde#include <sys/wait.h>
7112662Sdg
7212645Sbde#include <ctype.h>
7330354Sphk#include <err.h>
7463212Sabial#include <errno.h>
75100833Struckman#include <fcntl.h>
7630309Sphk#include <fnmatch.h>
7712429Sphk#include <glob.h>
78188232Sjhb#include <grp.h>
79188232Sjhb#include <paths.h>
80188232Sjhb#include <pwd.h>
81287835Smjg#include <signal.h>
82188232Sjhb#include <stdio.h>
83188232Sjhb#include <stdlib.h>
84188232Sjhb#include <string.h>
85188232Sjhb#include <time.h>
86192125Sjhb#include <unistd.h>
87192125Sjhb
88192125Sjhb#include "pathnames.h"
8912429Sphk#include "extern.h"
90287835Smjg
91192125Sjhb/*
9212429Sphk * Bit-values for the 'flags' parsed from a config-file entry.
93287835Smjg */
94287835Smjg#define	CE_COMPACT	0x0001	/* Compact the archived log files with gzip. */
95287835Smjg#define	CE_BZCOMPACT	0x0002	/* Compact the archived log files with bzip2. */
96287835Smjg#define	CE_BINARY	0x0008	/* Logfile is in binary, do not add status */
97287835Smjg				/*    messages to logfile(s) when rotating. */
98287835Smjg#define	CE_NOSIGNAL	0x0010	/* There is no process to signal when */
99287835Smjg				/*    trimming this file. */
100287835Smjg#define	CE_TRIMAT	0x0020	/* trim file at a specific time. */
101287835Smjg#define	CE_GLOB		0x0040	/* name of the log is file name pattern. */
102287835Smjg#define	CE_SIGNALGROUP	0x0080	/* Signal a process-group instead of a single */
103216060Smdf				/*    process when trimming this file. */
104287835Smjg#define	CE_CREATE	0x0100	/* Create the log file if it does not exist. */
10593616Salfred#define	CE_NODUMP	0x0200	/* Set 'nodump' on newly created log file. */
10662573Sphk
10712429Sphk#define	MIN_PID         5	/* Don't touch pids lower than this */
108267992Shselasky#define	MAX_PID		99999	/* was lower, see /usr/include/sys/proc.h */
109267992Shselasky
11012152Sphk#define	kbytes(size)  (((size) + 1023) >> 10)
111188232Sjhb
112188232Sjhb#define	DEFAULT_MARKER	"<default>"
113267992Shselasky#define	DEBUG_MARKER	"<debug>"
114267992Shselasky
115188232Sjhbstruct conf_entry {
11663212Sabial	STAILQ_ENTRY(conf_entry) cf_nextp;
11763212Sabial	char *log;		/* Name of the log */
11863212Sabial	char *pid_file;		/* PID file */
11963212Sabial	char *r_reason;		/* The reason this file is being rotated */
12063212Sabial	int firstcreate;	/* Creating log for the first time (-C). */
121273401Smjg	int rotate;		/* Non-zero if this file should be rotated */
12263212Sabial	int fsize;		/* size found for the log file */
12363212Sabial	uid_t uid;		/* Owner of log */
12463212Sabial	gid_t gid;		/* Group of log */
12563212Sabial	int numlogs;		/* Number of logs to keep */
12663212Sabial	int trsize;		/* Size cutoff to trigger trimming the log */
12763212Sabial	int hours;		/* Hours between log trimming */
12863212Sabial	struct ptime_data *trim_at;	/* Specific time to do trimming */
12963212Sabial	unsigned int permissions;	/* File permissions on the log */
13012623Sphk	int flags;		/* CE_COMPACT, CE_BZCOMPACT, CE_BINARY */
13112623Sphk	int sig;		/* Signal to send */
13212623Sphk	int def_cfg;		/* Using the <default> rule for this file */
13344078Sdfr};
13412623Sphk
135188232Sjhbstruct sigwork_entry {
136287835Smjg	SLIST_ENTRY(sigwork_entry) sw_nextp;
137188232Sjhb	int	 sw_signum;		/* the signal to send */
13812429Sphk	int	 sw_pidok;		/* true if pid value is valid */
139287835Smjg	pid_t	 sw_pid;		/* the process id from the PID file */
140188232Sjhb	const char *sw_pidtype;		/* "daemon" or "process group" */
141188232Sjhb	char	 sw_fname[1];		/* file the PID was read from */
14280338Sroam};
143287835Smjg
144188232Sjhbstruct zipwork_entry {
145188232Sjhb	SLIST_ENTRY(zipwork_entry) zw_nextp;
146287835Smjg	const struct conf_entry *zw_conf;	/* for chown/perm/flag info */
147188232Sjhb	const struct sigwork_entry *zw_swork;	/* to know success of signal */
148188232Sjhb	int	 zw_fsize;		/* size of the file to compress */
149267992Shselasky	char	 zw_fname[1];		/* the file to compress */
150267992Shselasky};
151287835Smjg
152267992Shselaskytypedef enum {
153267992Shselasky	FREE_ENT, KEEP_ENT
154267992Shselasky}	fk_entry;
155276341Smjg
156276341SmjgSTAILQ_HEAD(cflist, conf_entry);
157267992ShselaskySLIST_HEAD(swlisthead, sigwork_entry) swhead = SLIST_HEAD_INITIALIZER(swhead);
158287835SmjgSLIST_HEAD(zwlisthead, zipwork_entry) zwhead = SLIST_HEAD_INITIALIZER(zwhead);
159287835Smjg
160287835Smjgint dbg_at_times;		/* -D Show details of 'trim_at' code */
161287835Smjg
162287835Smjgint archtodir = 0;		/* Archive old logfiles to other directory */
163267992Shselaskyint createlogs;			/* Create (non-GLOB) logfiles which do not */
164267992Shselasky				/*    already exist.  1=='for entries with */
165267992Shselasky				/*    C flag', 2=='for all entries'. */
166267992Shselaskyint verbose = 0;		/* Print out what's going on */
167267992Shselaskyint needroot = 1;		/* Root privs are necessary */
168267992Shselaskyint noaction = 0;		/* Don't do anything, just show it */
169287835Smjgint norotate = 0;		/* Don't rotate */
170287835Smjgint nosignal;			/* Do not send any signals */
171287835Smjgint enforcepid = 0;		/* If PID file does not exist or empty, do nothing */
172287835Smjgint force = 0;			/* Force the trim no matter what */
173287835Smjgint rotatereq = 0;		/* -R = Always rotate the file(s) as given */
174276341Smjg				/*    on the command (this also requires   */
175276341Smjg				/*    that a list of files *are* given on  */
176276341Smjg				/*    the run command). */
177276341Smjgchar *requestor;		/* The name given on a -R request */
178276341Smjgchar *archdirname;		/* Directory path to old logfiles archive */
179267992Shselaskychar *destdir = NULL;		/* Directory to treat at root for logs */
180267992Shselaskyconst char *conf;		/* Configuration file to use */
181267992Shselasky
182267992Shselaskystruct ptime_data *dbg_timenow;	/* A "timenow" value set via -D option */
183267992Shselaskystruct ptime_data *timenow;	/* The time to use for checking at-fields */
184267992Shselasky
185267992Shselasky#define	DAYTIME_LEN	16
186267992Shselaskychar daytime[DAYTIME_LEN];	/* The current time in human readable form,
187267992Shselasky				 * used for rotation-tracking messages. */
188268285Shselaskychar hostname[MAXHOSTNAMELEN];	/* hostname */
189267992Shselasky
190267992Shselaskystatic struct cflist *get_worklist(char **files);
191267992Shselaskystatic void parse_file(FILE *cf, struct cflist *work_p, struct cflist *glob_p,
192267992Shselasky		struct conf_entry *defconf_p);
193267992Shselaskystatic char *sob(char *p);
194267992Shselaskystatic char *son(char *p);
195268285Shselaskystatic int isnumberstr(const char *);
196267992Shselaskystatic char *missing_field(char *p, char *errline);
197267992Shselaskystatic void	 change_attrs(const char *, const struct conf_entry *);
198267992Shselaskystatic fk_entry	 do_entry(struct conf_entry *);
199267992Shselaskystatic fk_entry	 do_rotate(const struct conf_entry *);
200267992Shselaskystatic void	 do_sigwork(struct sigwork_entry *);
201267992Shselaskystatic void	 do_zipwork(struct zipwork_entry *);
202267992Shselaskystatic struct sigwork_entry *
203267992Shselasky		 save_sigwork(const struct conf_entry *);
204267992Shselaskystatic struct zipwork_entry *
205267992Shselasky		 save_zipwork(const struct conf_entry *, const struct
206267992Shselasky		    sigwork_entry *, int, const char *);
207267992Shselaskystatic void	 set_swpid(struct sigwork_entry *, const struct conf_entry *);
208267992Shselaskystatic int	 sizefile(const char *);
209267992Shselaskystatic void expand_globs(struct cflist *work_p, struct cflist *glob_p);
210267992Shselaskystatic void free_clist(struct cflist *list);
211267992Shselaskystatic void free_entry(struct conf_entry *ent);
212267992Shselaskystatic struct conf_entry *init_entry(const char *fname,
213267992Shselasky		struct conf_entry *src_entry);
214267992Shselaskystatic void parse_args(int argc, char **argv);
215267992Shselaskystatic int parse_doption(const char *doption);
216267992Shselaskystatic void usage(void);
217267992Shselaskystatic int log_trim(const char *logname, const struct conf_entry *log_ent);
218267992Shselaskystatic int age_old_log(char *file);
219267992Shselaskystatic void savelog(char *from, char *to);
220267992Shselaskystatic void createdir(const struct conf_entry *ent, char *dirpart);
221267992Shselaskystatic void createlog(const struct conf_entry *ent);
222267992Shselasky
223268285Shselasky/*
224268285Shselasky * All the following take a parameter of 'int', but expect values in the
225267992Shselasky * range of unsigned char.  Define wrappers which take values of type 'char',
226267992Shselasky * whether signed or unsigned, and ensure they end up in the right range.
227267992Shselasky */
228267992Shselasky#define	isdigitch(Anychar) isdigit((u_char)(Anychar))
229268285Shselasky#define	isprintch(Anychar) isprint((u_char)(Anychar))
230268285Shselasky#define	isspacech(Anychar) isspace((u_char)(Anychar))
231267992Shselasky#define	tolowerch(Anychar) tolower((u_char)(Anychar))
232267992Shselasky
233267992Shselaskyint
234267992Shselaskymain(int argc, char **argv)
235268285Shselasky{
236268285Shselasky	struct cflist *worklist;
237267992Shselasky	struct conf_entry *p;
238267992Shselasky	struct sigwork_entry *stmp;
239267992Shselasky	struct zipwork_entry *ztmp;
240267992Shselasky
241268285Shselasky	SLIST_INIT(&swhead);
242268285Shselasky	SLIST_INIT(&zwhead);
243267992Shselasky
244267992Shselasky	parse_args(argc, argv);
245267992Shselasky	argc -= optind;
246267992Shselasky	argv += optind;
247268285Shselasky
248268285Shselasky	if (needroot && getuid() && geteuid())
249268285Shselasky		errx(1, "must have root privs");
250267992Shselasky	worklist = get_worklist(argv);
251267992Shselasky
252267992Shselasky	/*
253267992Shselasky	 * Rotate all the files which need to be rotated.  Note that
254268285Shselasky	 * some users have *hundreds* of entries in newsyslog.conf!
255268285Shselasky	 */
256268285Shselasky	while (!STAILQ_EMPTY(worklist)) {
257268285Shselasky		p = STAILQ_FIRST(worklist);
258267992Shselasky		STAILQ_REMOVE_HEAD(worklist, cf_nextp);
259267992Shselasky		if (do_entry(p) == FREE_ENT)
260267992Shselasky			free_entry(p);
261267992Shselasky	}
262273174Sdavide
263268285Shselasky	/*
264268285Shselasky	 * Send signals to any processes which need a signal to tell
265267992Shselasky	 * them to close and re-open the log file(s) we have rotated.
266267992Shselasky	 * Note that zipwork_entries include pointers to these
267267992Shselasky	 * sigwork_entry's, so we can not free the entries here.
268267992Shselasky	 */
269267992Shselasky	if (!SLIST_EMPTY(&swhead)) {
270267992Shselasky		if (noaction || verbose)
271267992Shselasky			printf("Signal all daemon process(es)...\n");
272287835Smjg		SLIST_FOREACH(stmp, &swhead, sw_nextp)
273273564Sdes			do_sigwork(stmp);
274280443Shselasky		if (noaction)
275273564Sdes			printf("\tsleep 10\n");
276273564Sdes		else {
277267992Shselasky			if (verbose)
278267992Shselasky				printf("Pause 10 seconds to allow daemon(s)"
279188232Sjhb				    " to close log file(s)\n");
28080338Sroam			sleep(10);
28112152Sphk		}
28244078Sdfr	}
28344078Sdfr	/*
28444078Sdfr	 * Compress all files that we're expected to compress, now
285280495Shselasky	 * that all processes should have closed the files which
286280495Shselasky	 * have been rotated.
28712197Sbde	 */
28844078Sdfr	if (!SLIST_EMPTY(&zwhead)) {
28963212Sabial		if (noaction || verbose)
29063212Sabial			printf("Compress all rotated log file(s)...\n");
29163212Sabial		while (!SLIST_EMPTY(&zwhead)) {
292287835Smjg			ztmp = SLIST_FIRST(&zwhead);
29363212Sabial			do_zipwork(ztmp);
29463212Sabial			SLIST_REMOVE_HEAD(&zwhead, zw_nextp);
29563212Sabial			free(ztmp);
29663212Sabial		}
29763212Sabial	}
29863212Sabial	/* Now free all the sigwork entries. */
29963212Sabial	while (!SLIST_EMPTY(&swhead)) {
30063212Sabial		stmp = SLIST_FIRST(&swhead);
30163212Sabial		SLIST_REMOVE_HEAD(&swhead, sw_nextp);
30263212Sabial		free(stmp);
303280495Shselasky	}
304280495Shselasky
305280495Shselasky	while (wait(NULL) > 0 || errno == EINTR)
306280495Shselasky		;
307280495Shselasky	return (0);
308280495Shselasky}
30963212Sabial
310280495Shselaskystatic struct conf_entry *
311280495Shselaskyinit_entry(const char *fname, struct conf_entry *src_entry)
312280495Shselasky{
31380339Sroam	struct conf_entry *tempwork;
31480339Sroam
31580339Sroam	if (verbose > 4)
31644078Sdfr		printf("\t--> [creating entry for %s]\n", fname);
317280495Shselasky
318280495Shselasky	tempwork = malloc(sizeof(struct conf_entry));
31971510Smckusick	if (tempwork == NULL)
320280495Shselasky		err(1, "malloc of conf_entry for %s", fname);
321280495Shselasky
322280495Shselasky	if (destdir == NULL || fname[0] != '/')
323280495Shselasky		tempwork->log = strdup(fname);
324280495Shselasky	else
325280495Shselasky		asprintf(&tempwork->log, "%s%s", destdir, fname);
326280495Shselasky	if (tempwork->log == NULL)
327280495Shselasky		err(1, "strdup for %s", fname);
32844078Sdfr
32944078Sdfr	if (src_entry != NULL) {
33044078Sdfr		tempwork->pid_file = NULL;
331280495Shselasky		if (src_entry->pid_file)
33244078Sdfr			tempwork->pid_file = strdup(src_entry->pid_file);
333280495Shselasky		tempwork->r_reason = NULL;
33444078Sdfr		tempwork->firstcreate = 0;
33544078Sdfr		tempwork->rotate = 0;
336280495Shselasky		tempwork->fsize = -1;
337280495Shselasky		tempwork->uid = src_entry->uid;
338280495Shselasky		tempwork->gid = src_entry->gid;
339280495Shselasky		tempwork->numlogs = src_entry->numlogs;
340280495Shselasky		tempwork->trsize = src_entry->trsize;
341280495Shselasky		tempwork->hours = src_entry->hours;
342280495Shselasky		tempwork->trim_at = NULL;
343280495Shselasky		if (src_entry->trim_at != NULL)
344280495Shselasky			tempwork->trim_at = ptime_init(src_entry->trim_at);
345280495Shselasky		tempwork->permissions = src_entry->permissions;
346280495Shselasky		tempwork->flags = src_entry->flags;
347280495Shselasky		tempwork->sig = src_entry->sig;
348280495Shselasky		tempwork->def_cfg = src_entry->def_cfg;
349280495Shselasky	} else {
350280495Shselasky		/* Initialize as a "do-nothing" entry */
35144078Sdfr		tempwork->pid_file = NULL;
35244078Sdfr		tempwork->r_reason = NULL;
35344078Sdfr		tempwork->firstcreate = 0;
354280495Shselasky		tempwork->rotate = 0;
355280495Shselasky		tempwork->fsize = -1;
356280495Shselasky		tempwork->uid = (uid_t)-1;
357280495Shselasky		tempwork->gid = (gid_t)-1;
358280495Shselasky		tempwork->numlogs = 1;
359280495Shselasky		tempwork->trsize = -1;
360280495Shselasky		tempwork->hours = -1;
361280495Shselasky		tempwork->trim_at = NULL;
362280495Shselasky		tempwork->permissions = 0;
36344078Sdfr		tempwork->flags = 0;
36444078Sdfr		tempwork->sig = SIGHUP;
36544078Sdfr		tempwork->def_cfg = 0;
366267992Shselasky	}
367267992Shselasky
368267992Shselasky	return (tempwork);
369267992Shselasky}
370267992Shselasky
371267992Shselaskystatic void
372267992Shselaskyfree_entry(struct conf_entry *ent)
373280450Shselasky{
374280450Shselasky
375280450Shselasky	if (ent == NULL)
376267992Shselasky		return;
377267992Shselasky
37812152Sphk	if (ent->log != NULL) {
37912131Sphk		if (verbose > 4)
38080338Sroam			printf("\t--> [freeing entry for %s]\n", ent->log);
38180338Sroam		free(ent->log);
38212152Sphk		ent->log = NULL;
383115391Smux	}
384115391Smux
385115391Smux	if (ent->pid_file != NULL) {
386287835Smjg		free(ent->pid_file);
387115391Smux		ent->pid_file = NULL;
388115391Smux	}
389115391Smux
390115391Smux	if (ent->r_reason != NULL) {
391115391Smux		free(ent->r_reason);
392115391Smux		ent->r_reason = NULL;
393115391Smux	}
394115391Smux
395115391Smux	if (ent->trim_at != NULL) {
396115391Smux		ptime_free(ent->trim_at);
397115391Smux		ent->trim_at = NULL;
398115391Smux	}
399115391Smux
400115391Smux	free(ent);
401115391Smux}
402115391Smux
403115391Smuxstatic void
404115391Smuxfree_clist(struct cflist *list)
405115391Smux{
406115391Smux	struct conf_entry *ent;
407115391Smux
40844078Sdfr	while (!STAILQ_EMPTY(list)) {
40912152Sphk		ent = STAILQ_FIRST(list);
41063212Sabial		STAILQ_REMOVE_HEAD(list, cf_nextp);
41163212Sabial		free_entry(ent);
41263212Sabial	}
41363212Sabial
41463212Sabial	free(list);
41563212Sabial	list = NULL;
41663212Sabial}
41763212Sabial
418188232Sjhbstatic fk_entry
419188232Sjhbdo_entry(struct conf_entry * ent)
420188232Sjhb{
421188232Sjhb#define	REASON_MAX	80
422188232Sjhb	int modtime;
423188232Sjhb	fk_entry free_or_keep;
42463212Sabial	double diffsecs;
42563212Sabial	char temp_reason[REASON_MAX];
42663212Sabial
42763212Sabial	free_or_keep = FREE_ENT;
42863212Sabial	if (verbose) {
42963212Sabial		if (ent->flags & CE_COMPACT)
43063212Sabial			printf("%s <%dZ>: ", ent->log, ent->numlogs);
43163212Sabial		else if (ent->flags & CE_BZCOMPACT)
43263212Sabial			printf("%s <%dJ>: ", ent->log, ent->numlogs);
43363212Sabial		else
43463212Sabial			printf("%s <%d>: ", ent->log, ent->numlogs);
43563212Sabial	}
43663212Sabial	ent->fsize = sizefile(ent->log);
43763212Sabial	modtime = age_old_log(ent->log);
43863212Sabial	ent->rotate = 0;
43963212Sabial	ent->firstcreate = 0;
44063212Sabial	if (ent->fsize < 0) {
44163212Sabial		/*
442287835Smjg		 * If either the C flag or the -C option was specified,
44363212Sabial		 * and if we won't be creating the file, then have the
444188232Sjhb		 * verbose message include a hint as to why the file
44563212Sabial		 * will not be created.
44663212Sabial		 */
44763212Sabial		temp_reason[0] = '\0';
44863212Sabial		if (createlogs > 1)
44963212Sabial			ent->firstcreate = 1;
45063212Sabial		else if ((ent->flags & CE_CREATE) && createlogs)
45163212Sabial			ent->firstcreate = 1;
45263212Sabial		else if (ent->flags & CE_CREATE)
45363212Sabial			strlcpy(temp_reason, " (no -C option)", REASON_MAX);
45463212Sabial		else if (createlogs)
45563212Sabial			strlcpy(temp_reason, " (no C flag)", REASON_MAX);
45663212Sabial
45763212Sabial		if (ent->firstcreate) {
45863212Sabial			if (verbose)
45963212Sabial				printf("does not exist -> will create.\n");
46063212Sabial			createlog(ent);
461188232Sjhb		} else if (verbose) {
462287835Smjg			printf("does not exist, skipped%s.\n", temp_reason);
46363212Sabial		}
464188232Sjhb	} else {
46563212Sabial		if (ent->flags & CE_TRIMAT && !force && !rotatereq) {
46663212Sabial			diffsecs = ptimeget_diff(timenow, ent->trim_at);
46763212Sabial			if (diffsecs < 0.0) {
46863212Sabial				/* trim_at is some time in the future. */
469188232Sjhb				if (verbose) {
47063212Sabial					ptime_adjust4dst(ent->trim_at,
47163212Sabial					    timenow);
47263212Sabial					printf("--> will trim at %s",
47363212Sabial					    ptimeget_ctime(ent->trim_at));
47463212Sabial				}
47563212Sabial				return (free_or_keep);
476287835Smjg			} else if (diffsecs >= 3600.0) {
47763212Sabial				/*
47863212Sabial				 * trim_at is more than an hour in the past,
47963212Sabial				 * so find the next valid trim_at time, and
48063212Sabial				 * tell the user what that will be.
48163212Sabial				 */
48263212Sabial				if (verbose && dbg_at_times)
48363212Sabial					printf("\n\t--> prev trim at %s\t",
48463212Sabial					    ptimeget_ctime(ent->trim_at));
48563212Sabial				if (verbose) {
486287835Smjg					ptimeset_nxtime(ent->trim_at);
48763212Sabial					printf("--> will trim at %s",
48863212Sabial					    ptimeget_ctime(ent->trim_at));
489111119Simp				}
49063212Sabial				return (free_or_keep);
49163212Sabial			} else if (verbose && noaction && dbg_at_times) {
49263212Sabial				/*
49363212Sabial				 * If we are just debugging at-times, then
49463212Sabial				 * a detailed message is helpful.  Also
49563212Sabial				 * skip "doing" any commands, since they
49663212Sabial				 * would all be turned off by no-action.
49763212Sabial				 */
49863212Sabial				printf("\n\t--> timematch at %s",
49963212Sabial				    ptimeget_ctime(ent->trim_at));
50063212Sabial				return (free_or_keep);
501287835Smjg			} else if (verbose && ent->hours <= 0) {
50263212Sabial				printf("--> time is up\n");
50363212Sabial			}
50471999Sphk		}
50563212Sabial		if (verbose && (ent->trsize > 0))
50663212Sabial			printf("size (Kb): %d [%d] ", ent->fsize, ent->trsize);
50763212Sabial		if (verbose && (ent->hours > 0))
50863212Sabial			printf(" age (hr): %d [%d] ", modtime, ent->hours);
50963212Sabial
51063212Sabial		/*
51144078Sdfr		 * Figure out if this logfile needs to be rotated.
51263212Sabial		 */
51363212Sabial		temp_reason[0] = '\0';
51463212Sabial		if (rotatereq) {
51563212Sabial			ent->rotate = 1;
51663212Sabial			snprintf(temp_reason, REASON_MAX, " due to -R from %s",
51763212Sabial			    requestor);
51863212Sabial		} else if (force) {
51963212Sabial			ent->rotate = 1;
52063212Sabial			snprintf(temp_reason, REASON_MAX, " due to -F request");
52163212Sabial		} else if ((ent->trsize > 0) && (ent->fsize >= ent->trsize)) {
52263212Sabial			ent->rotate = 1;
523287835Smjg			snprintf(temp_reason, REASON_MAX, " due to size>%dK",
52463212Sabial			    ent->trsize);
52563212Sabial		} else if (ent->hours <= 0 && (ent->flags & CE_TRIMAT)) {
52663212Sabial			ent->rotate = 1;
527287835Smjg		} else if ((ent->hours > 0) && ((modtime >= ent->hours) ||
52863212Sabial		    (modtime < 0))) {
52963212Sabial			ent->rotate = 1;
530188232Sjhb		}
531287835Smjg
53263212Sabial		/*
533188232Sjhb		 * If the file needs to be rotated, then rotate it.
53463212Sabial		 */
53563212Sabial		if (ent->rotate && !norotate) {
53663212Sabial			if (temp_reason[0] != '\0')
53763212Sabial				ent->r_reason = strdup(temp_reason);
53863212Sabial			if (verbose)
53963212Sabial				printf("--> trimming log....\n");
54063212Sabial			if (noaction && !verbose) {
54163212Sabial				if (ent->flags & CE_COMPACT)
54263212Sabial					printf("%s <%dZ>: trimming\n",
54363212Sabial					    ent->log, ent->numlogs);
54463212Sabial				else if (ent->flags & CE_BZCOMPACT)
545188232Sjhb					printf("%s <%dJ>: trimming\n",
546188232Sjhb					    ent->log, ent->numlogs);
547287835Smjg				else
548188232Sjhb					printf("%s <%d>: trimming\n",
549287835Smjg					    ent->log, ent->numlogs);
550188232Sjhb			}
551188232Sjhb			free_or_keep = do_rotate(ent);
552188232Sjhb		} else {
553219819Sjeff			if (verbose)
554219819Sjeff				printf("--> skipping\n");
555219819Sjeff		}
556219819Sjeff	}
557219819Sjeff	return (free_or_keep);
558219819Sjeff#undef REASON_MAX
559219819Sjeff}
560219819Sjeff
561287835Smjgstatic void
562219819Sjeffparse_args(int argc, char **argv)
563219819Sjeff{
564219819Sjeff	int ch;
565219819Sjeff	char *p;
566219819Sjeff
567219819Sjeff	timenow = ptime_init(NULL);
568287835Smjg	ptimeset_time(timenow, time(NULL));
569219819Sjeff	strlcpy(daytime, ptimeget_ctime(timenow) + 4, DAYTIME_LEN);
570219819Sjeff
571219819Sjeff	/* Let's get our hostname */
572219819Sjeff	(void)gethostname(hostname, sizeof(hostname));
573219819Sjeff
574188232Sjhb	/* Truncate domain */
575188232Sjhb	if ((p = strchr(hostname, '.')) != NULL)
576188232Sjhb		*p = '\0';
577219819Sjeff
57863212Sabial	/* Parse command line options. */
57963212Sabial	while ((ch = getopt(argc, argv, "a:d:f:nrsvCD:FNPR:")) != -1)
580287835Smjg		switch (ch) {
58163212Sabial		case 'a':
58263212Sabial			archtodir++;
58363212Sabial			archdirname = optarg;
58463212Sabial			break;
58563212Sabial		case 'd':
58663212Sabial			destdir = optarg;
58763212Sabial			break;
58863212Sabial		case 'f':
58963212Sabial			conf = optarg;
59063212Sabial			break;
59163212Sabial		case 'n':
59263212Sabial			noaction++;
59363212Sabial			break;
59463212Sabial		case 'r':
59563212Sabial			needroot = 0;
596219819Sjeff			break;
597219819Sjeff		case 's':
598254115Sscottl			nosignal = 1;
599254115Sscottl			break;
600254115Sscottl		case 'v':
601254115Sscottl			verbose++;
60263212Sabial			break;
603254115Sscottl		case 'C':
604188232Sjhb			/* Useful for things like rc.diskless... */
605188232Sjhb			createlogs++;
60663212Sabial			break;
60763212Sabial		case 'D':
60863212Sabial			/*
60963212Sabial			 * Set some debugging option.  The specific option
61063212Sabial			 * depends on the value of optarg.  These options
61163212Sabial			 * may come and go without notice or documentation.
61263212Sabial			 */
61363212Sabial			if (parse_doption(optarg))
61463212Sabial				break;
61563212Sabial			usage();
61663212Sabial			/* NOTREACHED */
61763212Sabial		case 'F':
61863212Sabial			force++;
61963212Sabial			break;
62063212Sabial		case 'N':
621216060Smdf			norotate++;
622216060Smdf			break;
623216060Smdf		case 'P':
624216060Smdf			enforcepid++;
625216060Smdf			break;
626216060Smdf		case 'R':
627216060Smdf			rotatereq++;
628216060Smdf			requestor = strdup(optarg);
629216060Smdf			break;
630216060Smdf		case 'm':	/* Used by OpenBSD for "monitor mode" */
631141433Sphk		default:
632247561Smarius			usage();
633247561Smarius			/* NOTREACHED */
634247561Smarius		}
63563212Sabial
63663212Sabial	if (force && norotate) {
63763212Sabial		warnx("Only one of -F and -N may be specified.");
63863212Sabial		usage();
63963212Sabial		/* NOTREACHED */
64063212Sabial	}
64163212Sabial
64263212Sabial	if (rotatereq) {
64363212Sabial		if (optind == argc) {
64463212Sabial			warnx("At least one filename must be given when -R is specified.");
64563212Sabial			usage();
646219819Sjeff			/* NOTREACHED */
64770679Sjhb		}
64863212Sabial		/* Make sure "requestor" value is safe for a syslog message. */
64963212Sabial		for (p = requestor; *p != '\0'; p++) {
65063212Sabial			if (!isprintch(*p) && (*p != '\t'))
65163212Sabial				*p = '.';
65263212Sabial		}
65363212Sabial	}
65463212Sabial
655287835Smjg	if (dbg_timenow) {
65663212Sabial		/*
65763212Sabial		 * Note that the 'daytime' variable is not changed.
65863212Sabial		 * That is only used in messages that track when a
65963212Sabial		 * logfile is rotated, and if a file *is* rotated,
66063212Sabial		 * then it will still rotated at the "real now" time.
66163212Sabial		 */
66263212Sabial		ptime_free(timenow);
663287835Smjg		timenow = dbg_timenow;
66463212Sabial		fprintf(stderr, "Debug: Running as if TimeNow is %s",
66563212Sabial		    ptimeget_ctime(dbg_timenow));
666287835Smjg	}
66763212Sabial
66863212Sabial}
66963212Sabial
67063212Sabial/*
671111119Simp * These debugging options are mainly meant for developer use, such
67263212Sabial * as writing regression-tests.  They would not be needed by users
673267992Shselasky * during normal operation of newsyslog...
67463212Sabial */
67563212Sabialstatic int
676247561Smariusparse_doption(const char *doption)
67763212Sabial{
67863212Sabial	const char TN[] = "TN=";
679267992Shselasky	int res;
680267992Shselasky
68163212Sabial	if (strncmp(doption, TN, sizeof(TN) - 1) == 0) {
682267992Shselasky		/*
683247561Smarius		 * The "TimeNow" debugging option.  This might be off
68463212Sabial		 * by an hour when crossing a timezone change.
68563212Sabial		 */
68663212Sabial		dbg_timenow = ptime_init(NULL);
68763212Sabial		res = ptime_relparse(dbg_timenow, PTM_PARSE_ISO8601,
68863212Sabial		    time(NULL), doption + sizeof(TN) - 1);
689287835Smjg		if (res == -2) {
69063212Sabial			warnx("Non-existent time specified on -D %s", doption);
69163212Sabial			return (0);			/* failure */
69263212Sabial		} else if (res < 0) {
69363212Sabial			warnx("Malformed time given on -D %s", doption);
694174113Speter			return (0);			/* failure */
695174113Speter		}
696174113Speter		return (1);			/* successfully parsed */
697174113Speter
698174113Speter	}
699174113Speter
700247561Smarius	if (strcmp(doption, "ats") == 0) {
701174113Speter		dbg_at_times++;
702247561Smarius		return (1);			/* successfully parsed */
703287835Smjg	}
704247561Smarius
705174113Speter	/* XXX - This check could probably be dropped. */
706287835Smjg	if ((strcmp(doption, "neworder") == 0) || (strcmp(doption, "oldorder")
707174113Speter	    == 0)) {
708174113Speter		warnx("NOTE: newsyslog always uses 'neworder'.");
709174113Speter		return (1);			/* successfully parsed */
710174113Speter	}
711126319Sdes
712126319Sdes	warnx("Unknown -D (debug) option: '%s'", doption);
713126319Sdes	return (0);				/* failure */
714126319Sdes}
715126319Sdes
716126319Sdesstatic void
717126319Sdesusage(void)
718287835Smjg{
719188232Sjhb
720287835Smjg	fprintf(stderr,
721126319Sdes	    "usage: newsyslog [-CFNnrsv] [-a directory] [-d directory] [-f config-file]\n"
722188232Sjhb	    "                 [ [-R requestor] filename ... ]\n");
723126319Sdes	exit(1);
724188232Sjhb}
725287835Smjg
726126319Sdes/*
727188232Sjhb * Parse a configuration file and return a linked list of all the logs
728126319Sdes * which should be processed.
729126319Sdes */
730126319Sdesstatic struct cflist *
731126319Sdesget_worklist(char **files)
732287835Smjg{
733126319Sdes	FILE *f;
734126319Sdes	const char *fname;
735126319Sdes	char **given;
736126319Sdes	struct cflist *filelist, *globlist, *cmdlist;
73744078Sdfr	struct conf_entry *defconf, *dupent, *ent;
73844078Sdfr	int gmatch, fnres;
73978161Speter
74012152Sphk	defconf = NULL;
74180338Sroam
74280338Sroam	filelist = malloc(sizeof(struct cflist));
74338869Sbde	if (filelist == NULL)
74478161Speter		err(1, "malloc of filelist");
74578161Speter	STAILQ_INIT(filelist);
746192125Sjhb	globlist = malloc(sizeof(struct cflist));
74793625Srwatson	if (globlist == NULL)
748287835Smjg		err(1, "malloc of globlist");
74978161Speter	STAILQ_INIT(globlist);
75078161Speter
751287835Smjg	fname = conf;
75238869Sbde	if (fname == NULL)
753267992Shselasky		fname = _PATH_CONF;
75444078Sdfr
75512623Sphk	if (strcmp(fname, "-") != 0)
75612623Sphk		f = fopen(fname, "r");
75712623Sphk	else {
75812650Sphk		f = stdin;
75912650Sphk		fname = "<stdin>";
76012650Sphk	}
76112650Sphk	if (!f)
76212650Sphk		err(1, "%s", fname);
76312650Sphk
76412650Sphk	parse_file(f, filelist, globlist, defconf);
76512650Sphk	(void) fclose(f);
76612623Sphk
76712623Sphk	/*
76842467Sphk	 * All config-file information has been read in and turned into
76912623Sphk	 * a filelist and a globlist.  If there were no specific files
77012650Sphk	 * given on the run command, then the only thing left to do is to
77188006Sluigi	 * call a routine which finds all files matched by the globlist
77212623Sphk	 * and adds them to the filelist.  Then return the worklist.
77312623Sphk	 */
774136999Srwatson	if (*files == NULL) {
77512152Sphk		expand_globs(filelist, globlist);
77644078Sdfr		free_clist(globlist);
77712152Sphk		if (defconf != NULL)
77844078Sdfr			free_entry(defconf);
77944078Sdfr		return (filelist);
78012152Sphk		/* NOTREACHED */
781273424Smjg	}
78244078Sdfr
78312152Sphk	/*
78412152Sphk	 * If newsyslog was given a specific list of files to process,
78512152Sphk	 * it may be that some of those files were not listed in any
78612152Sphk	 * config file.  Those unlisted files should get the default
78744078Sdfr	 * rotation action.  First, create the default-rotation action
78812152Sphk	 * if none was found in a system config file.
78912152Sphk	 */
79044078Sdfr	if (defconf == NULL) {
79144078Sdfr		defconf = init_entry(DEFAULT_MARKER, NULL);
79212152Sphk		defconf->numlogs = 3;
79344078Sdfr		defconf->trsize = 50;
79415241Sphk		defconf->permissions = S_IRUSR|S_IWUSR;
79515241Sphk	}
79644078Sdfr
79712243Sphk	/*
79815241Sphk	 * If newsyslog was run with a list of specific filenames,
79944078Sdfr	 * then create a new worklist which has only those files in
80012152Sphk	 * it, picking up the rotation-rules for those files from
801267992Shselasky	 * the original filelist.
80212152Sphk	 *
80312152Sphk	 * XXX - Note that this will copy multiple rules for a single
80412152Sphk	 *	logfile, if multiple entries are an exact match for
805217616Smdf	 *	that file.  That matches the historic behavior, but do
806217616Smdf	 *	we want to continue to allow it?  If so, it should
807217616Smdf	 *	probably be handled more intelligently.
80812152Sphk	 */
809217616Smdf	cmdlist = malloc(sizeof(struct cflist));
810217616Smdf	if (cmdlist == NULL)
81112152Sphk		err(1, "malloc of cmdlist");
81212152Sphk	STAILQ_INIT(cmdlist);
81312152Sphk
81412152Sphk	for (given = files; *given; ++given) {
81512152Sphk		/*
81612152Sphk		 * First try to find exact-matches for this given file.
81712152Sphk		 */
81812152Sphk		gmatch = 0;
81962573Sphk		STAILQ_FOREACH(ent, filelist, cf_nextp) {
82012152Sphk			if (strcmp(ent->log, *given) == 0) {
821287835Smjg				gmatch++;
82287024Speter				dupent = init_entry(*given, ent);
82387024Speter				STAILQ_INSERT_TAIL(cmdlist, dupent, cf_nextp);
824164033Srwatson			}
82587024Speter		}
826139483Spjd		if (gmatch) {
827287835Smjg			if (verbose > 2)
82844078Sdfr				printf("\t+ Matched entry %s\n", *given);
829287835Smjg			continue;
830139483Spjd		}
83112152Sphk
83212152Sphk		/*
833273424Smjg		 * There was no exact-match for this given file, so look
83412623Sphk		 * for a "glob" entry which does match.
835136999Srwatson		 */
83612152Sphk		gmatch = 0;
83712623Sphk		if (verbose > 2 && globlist != NULL)
83862573Sphk			printf("\t+ Checking globs for %s\n", *given);
83912623Sphk		STAILQ_FOREACH(ent, globlist, cf_nextp) {
84012623Sphk			fnres = fnmatch(ent->log, *given, FNM_PATHNAME);
84112623Sphk			if (verbose > 2)
84244078Sdfr				printf("\t+    = %d for pattern %s\n", fnres,
84344078Sdfr				    ent->log);
84444972Sphk			if (fnres == 0) {
845287835Smjg				gmatch++;
84612623Sphk				dupent = init_entry(*given, ent);
84712131Sphk				/* This new entry is not a glob! */
848287835Smjg				dupent->flags &= ~CE_GLOB;
84912623Sphk				STAILQ_INSERT_TAIL(cmdlist, dupent, cf_nextp);
85012623Sphk				/* Only allow a match to one glob-entry */
85141514Sarchie				break;
85212623Sphk			}
85312623Sphk		}
85412623Sphk		if (gmatch) {
85512623Sphk			if (verbose > 2)
85612623Sphk				printf("\t+ Matched %s via %s\n", *given,
857216060Smdf				    ent->log);
85812623Sphk			continue;
85912623Sphk		}
86012623Sphk
86112623Sphk		/*
86244972Sphk		 * This given file was not found in any config file, so
86344078Sdfr		 * add a worklist item based on the default entry.
86444078Sdfr		 */
86512623Sphk		if (verbose > 2)
86612131Sphk			printf("\t+ No entry matched %s  (will use %s)\n",
86712623Sphk			    *given, DEFAULT_MARKER);
86812623Sphk		dupent = init_entry(*given, defconf);
86912623Sphk		/* Mark that it was *not* found in a config file */
87044078Sdfr		dupent->def_cfg = 1;
87144078Sdfr		STAILQ_INSERT_TAIL(cmdlist, dupent, cf_nextp);
87212623Sphk	}
873216060Smdf
87412623Sphk	/*
87512623Sphk	 * Free all the entries in the original work list, the list of
87612623Sphk	 * glob entries, and the default entry.
87712623Sphk	 */
87844972Sphk	free_clist(filelist);
87912623Sphk	free_clist(globlist);
88012623Sphk	free_entry(defconf);
88144078Sdfr
88212623Sphk	/* And finally, return a worklist which matches the given files. */
88312623Sphk	return (cmdlist);
884216058Smdf}
88512623Sphk
88612623Sphk/*
88744972Sphk * Expand the list of entries with filename patterns, and add all files
88812623Sphk * which match those glob-entries onto the worklist.
889216060Smdf */
890216060Smdfstatic void
891287835Smjgexpand_globs(struct cflist *work_p, struct cflist *glob_p)
892216060Smdf{
89312623Sphk	int gmatch, gres;
89412623Sphk	size_t i;
895224159Srwatson	char *mfname;
896224159Srwatson	struct conf_entry *dupent, *ent, *globent;
897224159Srwatson	glob_t pglob;
898224159Srwatson	struct stat st_fm;
899273424Smjg
900224159Srwatson	/*
90112623Sphk	 * The worklist contains all fully-specified (non-GLOB) names.
90212623Sphk	 *
90363978Speter	 * Now expand the list of filename-pattern (GLOB) entries into
90444078Sdfr	 * a second list, which (by definition) will only match files
90512623Sphk	 * that already exist.  Do not add a glob-related entry for any
90644078Sdfr	 * file which already exists in the fully-specified list.
90712623Sphk	 */
908273401Smjg	STAILQ_FOREACH(globent, glob_p, cf_nextp) {
90912623Sphk		gres = glob(globent->log, GLOB_NOCHECK, NULL, &pglob);
91044078Sdfr		if (gres != 0) {
91144078Sdfr			warn("cannot expand pattern (%d): %s", gres,
91244078Sdfr			    globent->log);
91312623Sphk			continue;
914101650Smux		}
915101650Smux
916101650Smux		if (verbose > 2)
91712623Sphk			printf("\t+ Expanding pattern %s\n", globent->log);
91844078Sdfr		for (i = 0; i < pglob.gl_matchc; i++) {
919139483Spjd			mfname = pglob.gl_pathv[i];
92044078Sdfr
92112623Sphk			/* See if this file already has a specific entry. */
922139483Spjd			gmatch = 0;
923216058Smdf			STAILQ_FOREACH(ent, work_p, cf_nextp) {
92463978Speter				if (strcmp(mfname, ent->log) == 0) {
92544078Sdfr					gmatch++;
926139483Spjd					break;
927111260Srwatson				}
92812623Sphk			}
92912623Sphk			if (gmatch)
93044078Sdfr				continue;
93112623Sphk
93212623Sphk			/* Make sure the named matched is a file. */
93344078Sdfr			gres = lstat(mfname, &st_fm);
93444078Sdfr			if (gres != 0) {
935139483Spjd				/* Error on a file that glob() matched?!? */
93644078Sdfr				warn("Skipping %s - lstat() error", mfname);
937139483Spjd				continue;
938216058Smdf			}
93963978Speter			if (!S_ISREG(st_fm.st_mode)) {
94044078Sdfr				/* We only rotate files! */
94112623Sphk				if (verbose > 2)
94215241Sphk					printf("\t+  . skipping %s (!file)\n",
94312623Sphk					    mfname);
94444078Sdfr				continue;
94512623Sphk			}
94612623Sphk
94744078Sdfr			if (verbose > 2)
94812623Sphk				printf("\t+  . add file %s\n", mfname);
94912623Sphk			dupent = init_entry(mfname, globent);
950216058Smdf			/* This new entry is not a glob! */
95163978Speter			dupent->flags &= ~CE_GLOB;
95244078Sdfr
95312623Sphk			/* Add to the worklist. */
95415241Sphk			STAILQ_INSERT_TAIL(work_p, dupent, cf_nextp);
95512623Sphk		}
956111260Srwatson		globfree(&pglob);
95712623Sphk		if (verbose > 2)
95812623Sphk			printf("\t+ Done with pattern %s\n", globent->log);
959139483Spjd	}
96012623Sphk}
96112623Sphk
96212623Sphk/*
96362573Sphk * Parse a configuration file and update a linked list of all the logs to
96412623Sphk * process.
96512623Sphk */
96612623Sphkstatic void
96712623Sphkparse_file(FILE *cf, struct cflist *work_p, struct cflist *glob_p,
96812623Sphk    struct conf_entry *defconf_p)
96944078Sdfr{
970287835Smjg	char line[BUFSIZ], *parse, *q;
97112623Sphk	char *cp, *errline, *group;
97212623Sphk	struct conf_entry *working;
973287835Smjg	struct passwd *pwd;
97463978Speter	struct group *grp;
975287835Smjg	int eol, ptm_opts, res, special;
97612623Sphk
977139483Spjd	errline = NULL;
97812650Sphk	while (fgets(line, BUFSIZ, cf)) {
97912623Sphk		if ((line[0] == '\n') || (line[0] == '#') ||
98012623Sphk		    (strlen(line) == 0))
98112623Sphk			continue;
982224159Srwatson		if (errline != NULL)
983224159Srwatson			free(errline);
984224159Srwatson		errline = strdup(line);
985224159Srwatson		for (cp = line + 1; *cp != '\0'; cp++) {
986273424Smjg			if (*cp != '#')
987224159Srwatson				continue;
98812623Sphk			if (*(cp - 1) == '\\') {
98912623Sphk				strcpy(cp - 1, cp);
990189707Sjhb				cp--;
99112623Sphk				continue;
99244078Sdfr			}
99344078Sdfr			*cp = '\0';
99412623Sphk			break;
99512623Sphk		}
996273401Smjg
997186564Sed		q = parse = missing_field(sob(line), errline);
998247561Smarius		parse = son(line);
999247561Smarius		if (!*parse)
100012623Sphk			errx(1, "malformed line (missing fields):\n%s",
1001247561Smarius			    errline);
1002247561Smarius		*parse = '\0';
1003247561Smarius
1004247561Smarius		/*
1005247561Smarius		 * Allow people to set debug options via the config file.
1006247561Smarius		 * (NOTE: debug options are undocumented, and may disappear
100712623Sphk		 * at any time, etc).
100844078Sdfr		 */
100912623Sphk		if (strcasecmp(DEBUG_MARKER, q) == 0) {
101012623Sphk			q = parse = missing_field(sob(++parse), errline);
1011247561Smarius			parse = son(parse);
101244078Sdfr			if (!*parse)
101344078Sdfr				warnx("debug line specifies no option:\n%s",
101412623Sphk				    errline);
101512623Sphk			else {
101612623Sphk				*parse = '\0';
101744078Sdfr				parse_doption(q);
101812623Sphk			}
101912623Sphk			continue;
102044078Sdfr		}
102112623Sphk
102212623Sphk		special = 0;
1023216058Smdf		working = init_entry(q, NULL);
102412623Sphk		if (strcasecmp(DEFAULT_MARKER, q) == 0) {
1025139483Spjd			special = 1;
102612623Sphk			if (defconf_p != NULL) {
102712623Sphk				warnx("Ignoring duplicate entry for %s!", q);
102812623Sphk				free_entry(working);
102962573Sphk				continue;
103012623Sphk			}
103112623Sphk			defconf_p = working;
1032216066Smdf		}
103312623Sphk
1034287835Smjg		q = parse = missing_field(sob(++parse), errline);
103512623Sphk		parse = son(parse);
103612623Sphk		if (!*parse)
1037139483Spjd			errx(1, "malformed line (missing fields):\n%s",
103845140Sphk			    errline);
103945140Sphk		*parse = '\0';
104012623Sphk		if ((group = strchr(q, ':')) != NULL ||
1041111119Simp		    (group = strrchr(q, '.')) != NULL) {
104212623Sphk			*group++ = '\0';
104312623Sphk			if (*q) {
104412623Sphk				if (!(isnumberstr(q))) {
104512623Sphk					if ((pwd = getpwnam(q)) == NULL)
104612623Sphk						errx(1,
104712623Sphk				     "error in config file; unknown user:\n%s",
104812623Sphk						    errline);
104912623Sphk					working->uid = pwd->pw_uid;
105012623Sphk				} else
1051287835Smjg					working->uid = atoi(q);
105212623Sphk			} else
1053287835Smjg				working->uid = (uid_t)-1;
105412623Sphk
105512623Sphk			q = group;
105612623Sphk			if (*q) {
105712623Sphk				if (!(isnumberstr(q))) {
105812623Sphk					if ((grp = getgrnam(q)) == NULL)
105912623Sphk						errx(1,
106012650Sphk				    "error in config file; unknown group:\n%s",
106112623Sphk						    errline);
106212623Sphk					working->gid = grp->gr_gid;
106312623Sphk				} else
1064224159Srwatson					working->gid = atoi(q);
1065224159Srwatson			} else
1066224159Srwatson				working->gid = (gid_t)-1;
1067224159Srwatson
1068217555Smdf			q = parse = missing_field(sob(++parse), errline);
1069224159Srwatson			parse = son(parse);
1070224159Srwatson			if (!*parse)
107112623Sphk				errx(1, "malformed line (missing fields):\n%s",
107212623Sphk				    errline);
107362573Sphk			*parse = '\0';
107412623Sphk		} else {
107544078Sdfr			working->uid = (uid_t)-1;
1076287835Smjg			working->gid = (gid_t)-1;
107753977Sgreen		}
107812623Sphk
1079287835Smjg		if (!sscanf(q, "%o", &working->permissions))
108053977Sgreen			errx(1, "error in config file; bad permissions:\n%s",
108153977Sgreen			    errline);
1082216060Smdf
108312623Sphk		q = parse = missing_field(sob(++parse), errline);
1084216060Smdf		parse = son(parse);
1085216060Smdf		if (!*parse)
1086216060Smdf			errx(1, "malformed line (missing fields):\n%s",
1087216060Smdf			    errline);
108853977Sgreen		*parse = '\0';
108953977Sgreen		if (!sscanf(q, "%d", &working->numlogs) || working->numlogs < 0)
1090216060Smdf			errx(1, "error in config file; bad value for count of logs to save:\n%s",
109153977Sgreen			    errline);
1092216060Smdf
1093287835Smjg		q = parse = missing_field(sob(++parse), errline);
109412650Sphk		parse = son(parse);
109512623Sphk		if (!*parse)
109612623Sphk			errx(1, "malformed line (missing fields):\n%s",
109742467Sphk			    errline);
1098224159Srwatson		*parse = '\0';
1099187864Sed		if (isdigitch(*q))
110012623Sphk			working->trsize = atoi(q);
110188006Sluigi		else if (strcmp(q, "*") == 0)
110288006Sluigi			working->trsize = -1;
110388006Sluigi		else {
110488006Sluigi			warnx("Invalid value of '%s' for 'size' in line:\n%s",
1105287835Smjg			    q, errline);
110688006Sluigi			working->trsize = -1;
110788006Sluigi		}
1108287835Smjg
110988006Sluigi		working->flags = 0;
111088006Sluigi		q = parse = missing_field(sob(++parse), errline);
1111216060Smdf		parse = son(parse);
111288006Sluigi		eol = !*parse;
1113216060Smdf		*parse = '\0';
1114216060Smdf		{
1115216060Smdf			char *ep;
1116216060Smdf			u_long ul;
1117141433Sphk
1118216060Smdf			ul = strtoul(q, &ep, 10);
1119287835Smjg			if (ep == q)
112088006Sluigi				working->hours = 0;
112188006Sluigi			else if (*ep == '*')
112288006Sluigi				working->hours = -1;
1123273424Smjg			else if (ul > INT_MAX)
1124224159Srwatson				errx(1, "interval is too large:\n%s", errline);
112588006Sluigi			else
112612243Sphk				working->hours = ul;
112712623Sphk
112812623Sphk			if (*ep == '\0' || strcmp(ep, "*") == 0)
112912623Sphk				goto no_trimat;
113012623Sphk			if (*ep != '@' && *ep != '$')
113142095Sdfr				errx(1, "malformed interval/at:\n%s", errline);
113212243Sphk
113312243Sphk			working->flags |= CE_TRIMAT;
113412243Sphk			working->trim_at = ptime_init(NULL);
113512243Sphk			ptm_opts = PTM_PARSE_ISO8601;
113612243Sphk			if (*ep == '$')
113711865Sphk				ptm_opts = PTM_PARSE_DWM;
113862573Sphk			ptm_opts |= PTM_PARSE_MATCHDOM;
113911863Sphk			res = ptime_relparse(working->trim_at, ptm_opts,
1140100833Struckman			    ptimeget_secs(timenow), ep + 1);
114111863Sphk			if (res == -2)
1142100833Struckman				errx(1, "nonexistent time for 'at' value:\n%s",
1143100833Struckman				    errline);
1144100833Struckman			else if (res < 0)
114512243Sphk				errx(1, "malformed 'at' value:\n%s", errline);
1146100833Struckman		}
114720506Sbdeno_trimat:
1148100833Struckman
1149100833Struckman		if (eol)
115011863Sphk			q = NULL;
115112243Sphk		else {
115212243Sphk			q = parse = sob(++parse);	/* Optional field */
115311863Sphk			parse = son(parse);
115412243Sphk			if (!*parse)
115512243Sphk				eol = 1;
115612243Sphk			*parse = '\0';
115712243Sphk		}
115812243Sphk
115911863Sphk		for (; q && *q && !isspacech(*q); q++) {
116011863Sphk			switch (tolowerch(*q)) {
116112243Sphk			case 'b':
1162155758Sandre				working->flags |= CE_BINARY;
1163195699Srwatson				break;
1164155758Sandre			case 'c':
1165155758Sandre				/*
1166155758Sandre				 * XXX - 	Ick! Ugly! Remove ASAP!
1167155758Sandre				 * We want `c' and `C' for "create".  But we
1168155758Sandre				 * will temporarily treat `c' as `g', because
1169155758Sandre				 * FreeBSD releases <= 4.8 have a typo of
1170155758Sandre				 * checking  ('G' || 'c')  for CE_GLOB.
1171191688Szec				 */
1172155758Sandre				if (*q == 'c') {
1173155758Sandre					warnx("Assuming 'g' for 'c' in flags for line:\n%s",
1174155758Sandre					    errline);
1175155758Sandre					warnx("The 'c' flag will eventually mean 'CREATE'");
1176155758Sandre					working->flags |= CE_GLOB;
1177155758Sandre					break;
1178155758Sandre				}
1179155758Sandre				working->flags |= CE_CREATE;
1180155758Sandre				break;
1181155758Sandre			case 'd':
1182191688Szec				working->flags |= CE_NODUMP;
1183155758Sandre				break;
1184155758Sandre			case 'g':
1185155758Sandre				working->flags |= CE_GLOB;
1186155758Sandre				break;
1187155758Sandre			case 'j':
1188246696Smarius				working->flags |= CE_BZCOMPACT;
1189246696Smarius				break;
1190246696Smarius			case 'n':
1191246696Smarius				working->flags |= CE_NOSIGNAL;
119238517Sdfr				break;
119338517Sdfr			case 'u':
119438517Sdfr				working->flags |= CE_SIGNALGROUP;
119562573Sphk				break;
119638517Sdfr			case 'w':
119738517Sdfr				/* Depreciated flag - keep for compatibility purposes */
1198136404Speter				break;
1199136404Speter			case 'z':
1200136404Speter				working->flags |= CE_COMPACT;
1201136404Speter				break;
120238517Sdfr			case '-':
1203100833Struckman				break;
1204100833Struckman			case 'f':	/* Used by OpenBSD for "CE_FOLLOW" */
1205100833Struckman			case 'm':	/* Used by OpenBSD for "CE_MONITOR" */
1206246689Smarius			case 'p':	/* Used by NetBSD  for "CE_PLAIN0" */
1207246689Smarius			default:
1208246689Smarius				errx(1, "illegal flag in config file -- %c",
1209246689Smarius				    *q);
1210136404Speter			}
1211136404Speter		}
1212136404Speter
1213136404Speter		if (eol)
1214136404Speter			q = NULL;
1215136404Speter		else {
1216136404Speter			q = parse = sob(++parse);	/* Optional field */
121738517Sdfr			parse = son(parse);
121838517Sdfr			if (!*parse)
121938517Sdfr				eol = 1;
122038517Sdfr			*parse = '\0';
1221246689Smarius		}
1222246689Smarius
1223136404Speter		working->pid_file = NULL;
1224246689Smarius		if (q && *q) {
1225136404Speter			if (*q == '/')
1226136404Speter				working->pid_file = strdup(q);
1227246689Smarius			else if (isdigit(*q))
1228136404Speter				goto got_sig;
1229246689Smarius			else
1230136404Speter				errx(1,
123138517Sdfr			"illegal pid file or signal number in config file:\n%s",
123238517Sdfr				    errline);
123338517Sdfr		}
123438517Sdfr		if (eol)
1235246696Smarius			q = NULL;
1236246696Smarius		else {
1237246696Smarius			q = parse = sob(++parse);	/* Optional field */
1238246696Smarius			*(parse = son(parse)) = '\0';
1239170288Sdwmalone		}
1240170288Sdwmalone
1241217616Smdf		working->sig = SIGHUP;
1242170288Sdwmalone		if (q && *q) {
1243170288Sdwmalone			if (isdigit(*q)) {
1244170288Sdwmalone		got_sig:
1245170288Sdwmalone				working->sig = atoi(q);
1246170288Sdwmalone			} else {
1247170288Sdwmalone		err_sig:
1248170288Sdwmalone				errx(1,
1249246689Smarius				    "illegal signal number in config file:\n%s",
1250246689Smarius				    errline);
1251246689Smarius			}
1252246689Smarius			if (working->sig < 1 || working->sig >= NSIG)
1253170288Sdwmalone				goto err_sig;
1254170288Sdwmalone		}
1255170288Sdwmalone
1256170288Sdwmalone		/*
1257170288Sdwmalone		 * Finish figuring out what pid-file to use (if any) in
1258246689Smarius		 * later processing if this logfile needs to be rotated.
1259246689Smarius		 */
1260246689Smarius		if ((working->flags & CE_NOSIGNAL) == CE_NOSIGNAL) {
1261246689Smarius			/*
1262170288Sdwmalone			 * This config-entry specified 'n' for nosignal,
1263170288Sdwmalone			 * see if it also specified an explicit pid_file.
1264170288Sdwmalone			 * This would be a pretty pointless combination.
1265170288Sdwmalone			 */
126612243Sphk			if (working->pid_file != NULL) {
126712243Sphk				warnx("Ignoring '%s' because flag 'n' was specified in line:\n%s",
126812243Sphk				    working->pid_file, errline);
126912243Sphk				free(working->pid_file);
127012243Sphk				working->pid_file = NULL;
127112243Sphk			}
127211865Sphk		} else if (working->pid_file == NULL) {
127362573Sphk			/*
127411863Sphk			 * This entry did not specify the 'n' flag, which
1275100833Struckman			 * means it should signal syslogd unless it had
1276268509Smjg			 * specified some other pid-file (and obviously the
127711863Sphk			 * syslog pid-file will not be for a process-group).
1278267993Shselasky			 * Also, we should only try to notify syslog if we
1279267993Shselasky			 * are root.
1280267993Shselasky			 */
1281267993Shselasky			if (working->flags & CE_SIGNALGROUP) {
1282268509Smjg				warnx("Ignoring flag 'U' in line:\n%s",
1283267993Shselasky				    errline);
1284268509Smjg				working->flags &= ~CE_SIGNALGROUP;
1285268509Smjg			}
1286105354Srobert			if (needroot)
1287267992Shselasky				working->pid_file = strdup(_PATH_SYSLOGPID);
1288267992Shselasky		}
1289267992Shselasky
1290268509Smjg		/*
1291268509Smjg		 * Add this entry to the appropriate list of entries, unless
1292268509Smjg		 * it was some kind of special entry (eg: <default>).
1293268509Smjg		 */
1294268509Smjg		if (special) {
1295268509Smjg			;			/* Do not add to any list */
1296268509Smjg		} else if (working->flags & CE_GLOB) {
1297267992Shselasky			STAILQ_INSERT_TAIL(glob_p, working, cf_nextp);
1298267992Shselasky		} else {
1299267992Shselasky			STAILQ_INSERT_TAIL(work_p, working, cf_nextp);
1300267992Shselasky		}
1301268509Smjg	}
1302268509Smjg	if (errline != NULL)
1303267992Shselasky		free(errline);
1304267992Shselasky}
1305267992Shselasky
1306267985Sgjbstatic char *
130745140Sphkmissing_field(char *p, char *errline)
130812243Sphk{
130911863Sphk
131045140Sphk	if (!p || !*p)
131145140Sphk		errx(1, "missing field in config file:\n%s", errline);
131212243Sphk	return (p);
131312243Sphk}
131412243Sphk
131512243Sphkstatic fk_entry
131611863Sphkdo_rotate(const struct conf_entry *ent)
131712131Sphk{
131811863Sphk	char dirpart[MAXPATHLEN], namepart[MAXPATHLEN];
131911863Sphk	char file1[MAXPATHLEN], file2[MAXPATHLEN];
132012243Sphk	char zfile1[MAXPATHLEN], zfile2[MAXPATHLEN];
132112243Sphk	char jfile1[MAXPATHLEN];
132212243Sphk	int flags, numlogs_c;
132312243Sphk	fk_entry free_or_keep;
132412243Sphk	struct sigwork_entry *swork;
132511865Sphk	struct stat st;
132662573Sphk
132711863Sphk	flags = ent->flags;
1328120803Sbms	free_or_keep = FREE_ENT;
1329120803Sbms
1330120813Sbms	if (archtodir) {
133112243Sphk		char *p;
1332100833Struckman
1333120803Sbms		/* build complete name of archive directory into dirpart */
1334120803Sbms		if (*archdirname == '/') {	/* absolute */
1335120803Sbms			strlcpy(dirpart, archdirname, sizeof(dirpart));
1336120803Sbms		} else {	/* relative */
1337120803Sbms			/* get directory part of logfile */
1338100833Struckman			strlcpy(dirpart, ent->log, sizeof(dirpart));
1339120803Sbms			if ((p = rindex(dirpart, '/')) == NULL)
1340120813Sbms				dirpart[0] = '\0';
1341120813Sbms			else
1342120813Sbms				*(p + 1) = '\0';
1343120813Sbms			strlcat(dirpart, archdirname, sizeof(dirpart));
1344120813Sbms		}
1345120813Sbms
1346120813Sbms		/* check if archive directory exists, if not, create it */
1347120813Sbms		if (lstat(dirpart, &st))
1348120813Sbms			createdir(ent, dirpart);
1349120813Sbms
1350120813Sbms		/* get filename part of logfile */
135112243Sphk		if ((p = rindex(ent->log, '/')) == NULL)
135212243Sphk			strlcpy(namepart, ent->log, sizeof(namepart));
135312243Sphk		else
135412243Sphk			strlcpy(namepart, p + 1, sizeof(namepart));
135512243Sphk
135612243Sphk		/* name of oldest log */
135712260Sphk		(void) snprintf(file1, sizeof(file1), "%s/%s.%d", dirpart,
135812260Sphk		    namepart, ent->numlogs);
135912260Sphk		(void) snprintf(zfile1, sizeof(zfile1), "%s%s", file1,
136012260Sphk		    COMPRESS_POSTFIX);
136112260Sphk		snprintf(jfile1, sizeof(jfile1), "%s%s", file1,
136238517Sdfr		    BZCOMPRESS_POSTFIX);
136312243Sphk	} else {
136438517Sdfr		/* name of oldest log */
136512260Sphk		(void) snprintf(file1, sizeof(file1), "%s.%d", ent->log,
136612260Sphk		    ent->numlogs);
136738517Sdfr		(void) snprintf(zfile1, sizeof(zfile1), "%s%s", file1,
136873971Stmm		    COMPRESS_POSTFIX);
136973971Stmm		snprintf(jfile1, sizeof(jfile1), "%s%s", file1,
137073971Stmm		    BZCOMPRESS_POSTFIX);
137173971Stmm	}
137273971Stmm
137312260Sphk	if (noaction) {
137417971Sbde		printf("\trm -f %s\n", file1);
137512243Sphk		printf("\trm -f %s\n", zfile1);
1376192144Skib		printf("\trm -f %s\n", jfile1);
137716282Snate	} else {
137811863Sphk		(void) unlink(file1);
137912260Sphk		(void) unlink(zfile1);
138012243Sphk		(void) unlink(jfile1);
138112243Sphk	}
138212260Sphk
138338517Sdfr	/* Move down log files */
138412243Sphk	numlogs_c = ent->numlogs;		/* copy for countdown */
138512260Sphk	while (numlogs_c--) {
1386139483Spjd
138712260Sphk		(void) strlcpy(file2, file1, sizeof(file2));
138811863Sphk
138917971Sbde		if (archtodir)
139012243Sphk			(void) snprintf(file1, sizeof(file1), "%s/%s.%d",
139112131Sphk			    dirpart, namepart, numlogs_c);
139211863Sphk		else
139311863Sphk			(void) snprintf(file1, sizeof(file1), "%s.%d",
139416282Snate			    ent->log, numlogs_c);
139583366Sjulian
1396136404Speter		(void) strlcpy(zfile1, file1, sizeof(zfile1));
139716282Snate		(void) strlcpy(zfile2, file2, sizeof(zfile2));
139816282Snate		if (lstat(file1, &st)) {
139916282Snate			(void) strlcat(zfile1, COMPRESS_POSTFIX,
140016282Snate			    sizeof(zfile1));
140116282Snate			(void) strlcat(zfile2, COMPRESS_POSTFIX,
140216282Snate			    sizeof(zfile2));
140386183Srwatson			if (lstat(zfile1, &st)) {
1404136404Speter				strlcpy(zfile1, file1, sizeof(zfile1));
140516282Snate				strlcpy(zfile2, file2, sizeof(zfile2));
140616282Snate				strlcat(zfile1, BZCOMPRESS_POSTFIX,
140716282Snate				    sizeof(zfile1));
140816282Snate				strlcat(zfile2, BZCOMPRESS_POSTFIX,
1409127052Struckman				    sizeof(zfile2));
141016282Snate				if (lstat(zfile1, &st))
141116282Snate					continue;
141216282Snate			}
141316282Snate		}
141416282Snate		if (noaction)
141577646Sdd			printf("\tmv %s %s\n", zfile1, zfile2);
141616282Snate		else {
141716282Snate			/* XXX - Ought to be checking for failure! */
141816282Snate			(void)rename(zfile1, zfile2);
141916282Snate		}
142016282Snate		change_attrs(zfile2, ent);
142116282Snate	}
1422217915Smdf
142316282Snate	if (ent->numlogs > 0) {
142416282Snate		if (noaction) {
1425120813Sbms			/*
1426127052Struckman			 * Note that savelog() may succeed with using link()
1427127052Struckman			 * for the archtodir case, but there is no good way
142816282Snate			 * of knowing if it will when doing "noaction", so
142916282Snate			 * here we claim that it will have to do a copy...
143016282Snate			 */
143116282Snate			if (archtodir)
143216282Snate				printf("\tcp %s %s\n", ent->log, file1);
1433127052Struckman			else
1434127052Struckman				printf("\tln %s %s\n", ent->log, file1);
143516282Snate		} else {
143616282Snate			if (!(flags & CE_BINARY)) {
143716282Snate				/* Report the trimming to the old log */
143816282Snate				log_trim(ent->log, ent);
143916282Snate			}
144016282Snate			savelog(ent->log, file1);
144176834Sjlemon		}
144283366Sjulian		change_attrs(file1, ent);
1443136404Speter	}
144476834Sjlemon
144576834Sjlemon	/* Create the new log file and move it into place */
144678620Smjacob	if (noaction)
144778620Smjacob		printf("Start new log...\n");
144876834Sjlemon	createlog(ent);
144976834Sjlemon
145076834Sjlemon	/*
145176834Sjlemon	 * Save all signalling and file-compression to be done after log
145276834Sjlemon	 * files from all entries have been rotated.  This way any one
145383366Sjulian	 * process will not be sent the same signal multiple times when
1454136404Speter	 * multiple log files had to be rotated.
145576834Sjlemon	 */
145676834Sjlemon	swork = NULL;
145776834Sjlemon	if (ent->pid_file != NULL)
145883366Sjulian		swork = save_sigwork(ent);
1459136404Speter	if (ent->numlogs > 0 && (flags & (CE_COMPACT | CE_BZCOMPACT))) {
146076834Sjlemon		/*
146176834Sjlemon		 * The zipwork_entry will include a pointer to this
146276834Sjlemon		 * conf_entry, so the conf_entry should not be freed.
146312260Sphk		 */
146412260Sphk		free_or_keep = KEEP_ENT;
146512260Sphk		save_zipwork(ent, swork, ent->fsize, file1);
146612260Sphk	}
146738517Sdfr
146812243Sphk	return (free_or_keep);
1469126253Struckman}
1470233291Salc
147112243Sphkstatic void
1472126253Struckmando_sigwork(struct sigwork_entry *swork)
1473192144Skib{
1474192144Skib	struct sigwork_entry *nextsig;
1475126253Struckman	int kres, secs;
1476148864Scsjp
1477148864Scsjp	if (!(swork->sw_pidok) || swork->sw_pid == 0)
1478148864Scsjp		return;			/* no work to do... */
1479148864Scsjp
1480148864Scsjp	/*
1481148864Scsjp	 * If nosignal (-s) was specified, then do not signal any process.
1482111883Sjhb	 * Note that a nosignal request triggers a warning message if the
1483111883Sjhb	 * rotated logfile needs to be compressed, *unless* -R was also
1484126253Struckman	 * specified.  We assume that an `-sR' request came from a process
1485127052Struckman	 * which writes to the logfile, and as such, we assume that process
1486126253Struckman	 * has already made sure the logfile is not presently in use.  This
1487126253Struckman	 * just sets swork->sw_pidok to a special value, and do_zipwork
1488126253Struckman	 * will print any necessary warning(s).
1489126253Struckman	 */
1490126253Struckman	if (nosignal) {
1491233291Salc		if (!rotatereq)
1492233291Salc			swork->sw_pidok = -1;
1493233291Salc		return;
1494233291Salc	}
1495233291Salc
1496233291Salc	/*
1497233291Salc	 * Compute the pause between consecutive signals.  Use a longer
149812260Sphk	 * sleep time if we will be sending two signals to the same
1499126253Struckman	 * deamon or process-group.
150012243Sphk	 */
150112260Sphk	secs = 0;
150212243Sphk	nextsig = SLIST_NEXT(swork, sw_nextp);
150312243Sphk	if (nextsig != NULL) {
150412260Sphk		if (swork->sw_pid == nextsig->sw_pid)
150538517Sdfr			secs = 10;
150612243Sphk		else
150712285Sphk			secs = 1;
150812260Sphk	}
150912260Sphk
1510139483Spjd	if (noaction) {
151112260Sphk		printf("\tkill -%d %d \t\t# %s\n", swork->sw_signum,
151212243Sphk		    (int)swork->sw_pid, swork->sw_fname);
1513148873Scsjp		if (secs > 0)
1514148873Scsjp			printf("\tsleep %d\n", secs);
151517971Sbde		return;
151612243Sphk	}
151712243Sphk
151812243Sphk	kres = kill(swork->sw_pid, swork->sw_signum);
151912243Sphk	if (kres != 0) {
1520100487Struckman		/*
1521100487Struckman		 * Assume that "no such process" (ESRCH) is something
1522100487Struckman		 * to warn about, but is not an error.  Presumably the
1523100487Struckman		 * process which writes to the rotated log file(s) is
1524126253Struckman		 * gone, in which case we should have no problem with
1525100487Struckman		 * compressing the rotated log file(s).
1526100487Struckman		 */
1527126253Struckman		if (errno != ESRCH)
1528192160Sdes			swork->sw_pidok = 0;
1529126253Struckman		warn("can't notify %s, pid %d", swork->sw_pidtype,
1530126253Struckman		    (int)swork->sw_pid);
1531126253Struckman	} else {
1532217915Smdf		if (verbose)
1533120781Sbms			printf("Notified %s pid %d = %s\n", swork->sw_pidtype,
1534127050Struckman			    (int)swork->sw_pid, swork->sw_fname);
1535127050Struckman		if (secs > 0) {
1536130327Sgreen			if (verbose)
1537130327Sgreen				printf("Pause %d second(s) between signals\n",
1538130327Sgreen				    secs);
1539130327Sgreen			sleep(secs);
1540130327Sgreen		}
1541126253Struckman	}
1542127050Struckman}
1543127052Struckman
1544100487Struckmanstatic void
1545127050Struckmando_zipwork(struct zipwork_entry *zwork)
1546100487Struckman{
1547100487Struckman	const char *pgm_name, *pgm_path;
15481541Srgrimes	int errsav, fcount, zstatus;
154953977Sgreen	pid_t pidzip, wpid;
155053977Sgreen	char zresult[MAXPATHLEN];
155112131Sphk
1552216059Smdf	pgm_path = NULL;
155344078Sdfr	strlcpy(zresult, zwork->zw_fname, sizeof(zresult));
155453977Sgreen	if (zwork != NULL && zwork->zw_conf != NULL) {
155512131Sphk		if (zwork->zw_conf->flags & CE_COMPACT) {
1556273401Smjg			pgm_path = _PATH_GZIP;
1557216059Smdf			strlcat(zresult, COMPRESS_POSTFIX, sizeof(zresult));
155812131Sphk		} else if (zwork->zw_conf->flags & CE_BZCOMPACT) {
1559216059Smdf			pgm_path = _PATH_BZIP2;
1560216059Smdf			strlcat(zresult, BZCOMPRESS_POSTFIX, sizeof(zresult));
1561216059Smdf		}
1562216059Smdf	}
1563216059Smdf	if (pgm_path == NULL) {
1564216059Smdf		warnx("invalid entry for %s in do_zipwork", zwork->zw_fname);
1565216059Smdf		return;
1566216059Smdf	}
1567216059Smdf	pgm_name = strrchr(pgm_path, '/');
1568216059Smdf	if (pgm_name == NULL)
1569216059Smdf		pgm_name = pgm_path;
157053977Sgreen	else
157153977Sgreen		pgm_name++;
157253977Sgreen
1573216060Smdf	if (zwork->zw_swork != NULL && zwork->zw_swork->sw_pidok <= 0) {
1574216060Smdf		warnx(
157553977Sgreen		    "log %s not compressed because daemon(s) not notified",
157612131Sphk		    zwork->zw_fname);
1577216059Smdf		change_attrs(zwork->zw_fname, zwork->zw_conf);
1578216059Smdf		return;
1579216059Smdf	}
1580216059Smdf
1581216059Smdf	if (noaction) {
1582216060Smdf		printf("\t%s %s\n", pgm_name, zwork->zw_fname);
1583216060Smdf		change_attrs(zresult, zwork->zw_conf);
1584216059Smdf		return;
158512131Sphk	}
1586216059Smdf
158712131Sphk	fcount = 1;
158812131Sphk	pidzip = fork();
158953977Sgreen	while (pidzip < 0) {
159053977Sgreen		/*
159153977Sgreen		 * The fork failed.  If the failure was due to a temporary
159253977Sgreen		 * problem, then wait a short time and try it again.
159353977Sgreen		 */
159453977Sgreen		errsav = errno;
159553977Sgreen		warn("fork() for `%s %s'", pgm_name, zwork->zw_fname);
159653977Sgreen		if (errsav != EAGAIN || fcount > 5)
1597104094Sphk			errx(1, "Exiting...");
159862573Sphk		sleep(fcount * 12);
159953977Sgreen		fcount++;
160053977Sgreen		pidzip = fork();
1601287835Smjg	}
1602109246Sdillon	if (!pidzip) {
160353977Sgreen		/* The child process executes the compression command */
1604287835Smjg		execl(pgm_path, pgm_path, "-f", zwork->zw_fname, (char *)0);
1605186564Sed		err(1, "execl(`%s -f %s')", pgm_path, zwork->zw_fname);
160653977Sgreen	}
160753977Sgreen
1608287835Smjg	wpid = waitpid(pidzip, &zstatus, 0);
160953977Sgreen	if (wpid == -1) {
161053977Sgreen		/* XXX - should this be a fatal error? */
161153977Sgreen		warn("%s: waitpid(%d)", pgm_path, pidzip);
161253977Sgreen		return;
161353977Sgreen	}
161453977Sgreen	if (!WIFEXITED(zstatus)) {
161553977Sgreen		warnx("`%s -f %s' did not terminate normally", pgm_name,
1616287835Smjg		    zwork->zw_fname);
1617287835Smjg		return;
1618287835Smjg	}
1619287835Smjg	if (WEXITSTATUS(zstatus)) {
162053977Sgreen		warnx("`%s -f %s' terminated with a non-zero status (%d)",
162153977Sgreen		    pgm_name, zwork->zw_fname, WEXITSTATUS(zstatus));
162283968Srwatson		return;
1623287835Smjg	}
1624287835Smjg
1625287835Smjg	/* Compression was successful, set file attributes on the result. */
1626287835Smjg	change_attrs(zresult, zwork->zw_conf);
162712131Sphk}
162892953Srwatson
162992953Srwatson/*
1630224159Srwatson * Save information on any process we need to signal.  Any single
1631224159Srwatson * process may need to be sent different signal-values for different
1632224159Srwatson * log files, but usually a single signal-value will cause the process
1633224159Srwatson * to close and re-open all of it's log files.
1634224159Srwatson */
1635224159Srwatsonstatic struct sigwork_entry *
1636287835Smjgsave_sigwork(const struct conf_entry *ent)
1637287835Smjg{
1638287835Smjg	struct sigwork_entry *sprev, *stmp;
1639287835Smjg	int ndiff;
1640287835Smjg	size_t tmpsiz;
1641224159Srwatson
1642224159Srwatson	sprev = NULL;
1643224159Srwatson	ndiff = 1;
164483968Srwatson	SLIST_FOREACH(stmp, &swhead, sw_nextp) {
164583968Srwatson		ndiff = strcmp(ent->pid_file, stmp->sw_fname);
1646109246Sdillon		if (ndiff > 0)
1647109246Sdillon			break;
164892953Srwatson		if (ndiff == 0) {
1649287835Smjg			if (ent->sig == stmp->sw_signum)
165083968Srwatson				break;
165112910Sphk			if (ent->sig > stmp->sw_signum) {
165283968Srwatson				ndiff = 1;
165383968Srwatson				break;
1654196176Sbz			}
1655196176Sbz		}
165692953Srwatson		sprev = stmp;
1657196176Sbz	}
1658196176Sbz	if (stmp != NULL && ndiff == 0)
1659196176Sbz		return (stmp);
1660196176Sbz
1661196176Sbz	tmpsiz = sizeof(struct sigwork_entry) + strlen(ent->pid_file) + 1;
1662196176Sbz	stmp = malloc(tmpsiz);
166392953Srwatson	set_swpid(stmp, ent);
1664196176Sbz	stmp->sw_signum = ent->sig;
1665196176Sbz	strcpy(stmp->sw_fname, ent->pid_file);
166692953Srwatson	if (sprev == NULL)
1667287835Smjg		SLIST_INSERT_HEAD(&swhead, stmp, sw_nextp);
166883968Srwatson	else
166983968Srwatson		SLIST_INSERT_AFTER(sprev, stmp, sw_nextp);
1670287835Smjg	return (stmp);
1671287835Smjg}
1672287835Smjg
1673287835Smjg/*
167412131Sphk * Save information on any file we need to compress.  We may see the same
1675126121Spjd * file multiple times, so check the full list to avoid duplicates.  The
1676132776Skan * list itself is sorted smallest-to-largest, because that's the order we
1677126121Spjd * want to compress the files.  If the partition is very low on disk space,
1678126121Spjd * then the smallest files are the most likely to compress, and compressing
1679126121Spjd * them first will free up more space for the larger files.
1680126121Spjd */
1681126121Spjdstatic struct zipwork_entry *
1682126121Spjdsave_zipwork(const struct conf_entry *ent, const struct sigwork_entry *swork,
1683172930Srwatson    int zsize, const char *zipfname)
1684126121Spjd{
1685126121Spjd	struct zipwork_entry *zprev, *ztmp;
1686287835Smjg	int ndiff;
1687126121Spjd	size_t tmpsiz;
1688261590Sglebius
1689261590Sglebius	/* Compute the size if the caller did not know it. */
1690261590Sglebius	if (zsize < 0)
1691261590Sglebius		zsize = sizefile(zipfname);
1692287835Smjg
1693126121Spjd	zprev = NULL;
1694216060Smdf	ndiff = 1;
1695216060Smdf	SLIST_FOREACH(ztmp, &zwhead, zw_nextp) {
1696287835Smjg		ndiff = strcmp(zipfname, ztmp->zw_fname);
1697287835Smjg		if (ndiff == 0)
169853977Sgreen			break;
169912131Sphk		if (zsize > ztmp->zw_fsize)
170012131Sphk			zprev = ztmp;
170112221Sbde	}
170212171Sphk	if (ztmp != NULL && ndiff == 0)
170312171Sphk		return (ztmp);
170412171Sphk
170512171Sphk	tmpsiz = sizeof(struct zipwork_entry) + strlen(zipfname) + 1;
170612171Sphk	ztmp = malloc(tmpsiz);
170712171Sphk	ztmp->zw_conf = ent;
170812171Sphk	ztmp->zw_swork = swork;
170912171Sphk	ztmp->zw_fsize = zsize;
171012221Sbde	strcpy(ztmp->zw_fname, zipfname);
171112131Sphk	if (zprev == NULL)
1712225617Skmacy		SLIST_INSERT_HEAD(&zwhead, ztmp, zw_nextp);
17131541Srgrimes	else
1714188232Sjhb		SLIST_INSERT_AFTER(zprev, ztmp, zw_nextp);
171538517Sdfr	return (ztmp);
17161541Srgrimes}
17171541Srgrimes
17181541Srgrimes/* Send a signal to the pid specified by pidfile */
171911863Sphkstatic void
17203308Sphkset_swpid(struct sigwork_entry *swork, const struct conf_entry *ent)
17213308Sphk{
17221541Srgrimes	FILE *f;
17231541Srgrimes	long minok, maxok, rval;
172483366Sjulian	char *endp, *linep, line[BUFSIZ];
172512171Sphk
1726136404Speter	minok = MIN_PID;
172712260Sphk	maxok = MAX_PID;
1728186564Sed	swork->sw_pidok = 0;
1729186664Sed	swork->sw_pid = 0;
1730188232Sjhb	swork->sw_pidtype = "daemon";
1731186664Sed	if (ent->flags & CE_SIGNALGROUP) {
1732186664Sed		/*
1733186664Sed		 * If we are expected to signal a process-group when
173412260Sphk		 * rotating this logfile, then the value read in should
173512171Sphk		 * be the negative of a valid process ID.
173612171Sphk		 */
173712171Sphk		minok = -MAX_PID;
173812171Sphk		maxok = -MIN_PID;
173912171Sphk		swork->sw_pidtype = "process-group";
174012171Sphk	}
174112171Sphk
174283366Sjulian	f = fopen(ent->pid_file, "r");
1743136404Speter	if (f == NULL) {
1744136404Speter		if (errno == ENOENT && enforcepid == 0) {
174512171Sphk			/*
1746192125Sjhb			 * Warn if the PID file doesn't exist, but do
1747127052Struckman			 * not consider it an error.  Most likely it
174812171Sphk			 * means the process has been terminated,
174912243Sphk			 * so it should be safe to rotate any log
175012243Sphk			 * files that the process would have been using.
175186183Srwatson			 */
1752136404Speter			swork->sw_pidok = 1;
175312285Sphk			warnx("pid file doesn't exist: %s", ent->pid_file);
175412171Sphk		} else
175512171Sphk			warn("can't open pid file: %s", ent->pid_file);
175612243Sphk		return;
175712171Sphk	}
175812260Sphk
175912171Sphk	if (fgets(line, BUFSIZ, f) == NULL) {
176012171Sphk		/*
176112171Sphk		 * Warn if the PID file is empty, but do not consider
176212171Sphk		 * it an error.  Most likely it means the process has
1763127052Struckman		 * has terminated, so it should be safe to rotate any
176412171Sphk		 * log files that the process would have been using.
176512243Sphk		 */
176652644Sphk		if (feof(f) && enforcepid == 0) {
176712243Sphk			swork->sw_pidok = 1;
176812243Sphk			warnx("pid file is empty: %s", ent->pid_file);
176912243Sphk		} else
177012131Sphk			warn("can't read from pid file: %s", ent->pid_file);
177177646Sdd		(void)fclose(f);
1772172038Srwatson		return;
177312243Sphk	}
177412243Sphk	(void)fclose(f);
177512243Sphk
177611863Sphk	errno = 0;
177712131Sphk	linep = line;
177812243Sphk	while (*linep == ' ')
177912243Sphk		linep++;
1780217915Smdf	rval = strtol(linep, &endp, 10);
178111863Sphk	if (*endp != '\0' && !isspacech(*endp)) {
1782189707Sjhb		warnx("pid file does not start with a valid number: %s",
1783189707Sjhb		    ent->pid_file);
1784189707Sjhb	} else if (rval < minok || rval > maxok) {
1785189707Sjhb		warnx("bad value '%ld' for process number in %s",
1786192125Sjhb		    rval, ent->pid_file);
1787285208Spkelsey		if (verbose)
1788192125Sjhb			warnx("\t(expecting value between %ld and %ld)",
1789192125Sjhb			    minok, maxok);
1790192125Sjhb	} else {
1791192125Sjhb		swork->sw_pidok = 1;
1792194252Sjamie		swork->sw_pid = rval;
179312429Sphk	}
1794185983Skib
1795127052Struckman	return;
1796127052Struckman}
1797127052Struckman
1798185983Skib/* Log the fact that the logs were turned over */
1799185983Skibstatic int
1800221829Smdflog_trim(const char *logname, const struct conf_entry *log_ent)
1801185983Skib{
180212243Sphk	FILE *f;
1803186564Sed	const char *xtra;
1804186564Sed
1805127052Struckman	if ((f = fopen(logname, "a")) == NULL)
1806127052Struckman		return (-1);
1807192125Sjhb	xtra = "";
1808192125Sjhb	if (log_ent->def_cfg)
180912429Sphk		xtra = " using <default> rule";
181012260Sphk	if (log_ent->firstcreate)
181112260Sphk		fprintf(f, "%s %s newsyslog[%d]: logfile first created%s\n",
181212260Sphk		    daytime, hostname, (int) getpid(), xtra);
181312260Sphk	else if (log_ent->r_reason != NULL)
1814127052Struckman		fprintf(f, "%s %s newsyslog[%d]: logfile turned over%s%s\n",
1815127052Struckman		    daytime, hostname, (int) getpid(), log_ent->r_reason, xtra);
181612260Sphk	else
181712260Sphk		fprintf(f, "%s %s newsyslog[%d]: logfile turned over%s\n",
181811863Sphk		    daytime, hostname, (int) getpid(), xtra);
181912260Sphk	if (fclose(f) == EOF)
18201541Srgrimes		err(1, "log_trim: fclose");
1821212750Smdf	return (0);
1822212750Smdf}
1823217916Smdf
1824217916Smdf/* Return size in kilobytes of a file */
1825212750Smdfstatic int
1826212750Smdfsizefile(const char *file)
1827212750Smdf{
1828212750Smdf	struct stat sb;
1829212750Smdf
1830212750Smdf	if (stat(file, &sb) < 0)
1831212750Smdf		return (-1);
1832212750Smdf	return (kbytes(dbtob(sb.st_blocks)));
1833212750Smdf}
1834212750Smdf
1835212750Smdf/* Return the age of old log file (file.0) */
1836212750Smdfstatic int
1837212750Smdfage_old_log(char *file)
1838212750Smdf{
1839212750Smdf	struct stat sb;
1840212750Smdf	char *endp;
1841212750Smdf	char tmp[MAXPATHLEN + sizeof(".0") + sizeof(COMPRESS_POSTFIX) +
1842280192Sian		sizeof(BZCOMPRESS_POSTFIX) + 1];
1843280192Sian
1844280192Sian	if (archtodir) {
1845279993Sian		char *p;
1846212750Smdf
1847212750Smdf		/* build name of archive directory into tmp */
1848212750Smdf		if (*archdirname == '/') {	/* absolute */
1849			strlcpy(tmp, archdirname, sizeof(tmp));
1850		} else {	/* relative */
1851			/* get directory part of logfile */
1852			strlcpy(tmp, file, sizeof(tmp));
1853			if ((p = rindex(tmp, '/')) == NULL)
1854				tmp[0] = '\0';
1855			else
1856				*(p + 1) = '\0';
1857			strlcat(tmp, archdirname, sizeof(tmp));
1858		}
1859
1860		strlcat(tmp, "/", sizeof(tmp));
1861
1862		/* get filename part of logfile */
1863		if ((p = rindex(file, '/')) == NULL)
1864			strlcat(tmp, file, sizeof(tmp));
1865		else
1866			strlcat(tmp, p + 1, sizeof(tmp));
1867	} else {
1868		(void) strlcpy(tmp, file, sizeof(tmp));
1869	}
1870
1871	strlcat(tmp, ".0", sizeof(tmp));
1872	if (stat(tmp, &sb) < 0) {
1873		/*
1874		 * A plain '.0' file does not exist.  Try again, first
1875		 * with the added suffix of '.gz', then with an added
1876		 * suffix of '.bz2' instead of '.gz'.
1877		 */
1878		endp = strchr(tmp, '\0');
1879		strlcat(tmp, COMPRESS_POSTFIX, sizeof(tmp));
1880		if (stat(tmp, &sb) < 0) {
1881			*endp = '\0';		/* Remove .gz */
1882			strlcat(tmp, BZCOMPRESS_POSTFIX, sizeof(tmp));
1883			if (stat(tmp, &sb) < 0)
1884				return (-1);
1885		}
1886	}
1887	return ((int)(ptimeget_secs(timenow) - sb.st_mtime + 1800) / 3600);
1888}
1889
1890/* Skip Over Blanks */
1891static char *
1892sob(char *p)
1893{
1894	while (p && *p && isspace(*p))
1895		p++;
1896	return (p);
1897}
1898
1899/* Skip Over Non-Blanks */
1900static char *
1901son(char *p)
1902{
1903	while (p && *p && !isspace(*p))
1904		p++;
1905	return (p);
1906}
1907
1908/* Check if string is actually a number */
1909static int
1910isnumberstr(const char *string)
1911{
1912	while (*string) {
1913		if (!isdigitch(*string++))
1914			return (0);
1915	}
1916	return (1);
1917}
1918
1919/*
1920 * Save the active log file under a new name.  A link to the new name
1921 * is the quick-and-easy way to do this.  If that fails (which it will
1922 * if the destination is on another partition), then make a copy of
1923 * the file to the new location.
1924 */
1925static void
1926savelog(char *from, char *to)
1927{
1928	FILE *src, *dst;
1929	int c, res;
1930
1931	res = link(from, to);
1932	if (res == 0)
1933		return;
1934
1935	if ((src = fopen(from, "r")) == NULL)
1936		err(1, "can't fopen %s for reading", from);
1937	if ((dst = fopen(to, "w")) == NULL)
1938		err(1, "can't fopen %s for writing", to);
1939
1940	while ((c = getc(src)) != EOF) {
1941		if ((putc(c, dst)) == EOF)
1942			err(1, "error writing to %s", to);
1943	}
1944
1945	if (ferror(src))
1946		err(1, "error reading from %s", from);
1947	if ((fclose(src)) != 0)
1948		err(1, "can't fclose %s", to);
1949	if ((fclose(dst)) != 0)
1950		err(1, "can't fclose %s", from);
1951}
1952
1953/* create one or more directory components of a path */
1954static void
1955createdir(const struct conf_entry *ent, char *dirpart)
1956{
1957	int res;
1958	char *s, *d;
1959	char mkdirpath[MAXPATHLEN];
1960	struct stat st;
1961
1962	s = dirpart;
1963	d = mkdirpath;
1964
1965	for (;;) {
1966		*d++ = *s++;
1967		if (*s != '/' && *s != '\0')
1968			continue;
1969		*d = '\0';
1970		res = lstat(mkdirpath, &st);
1971		if (res != 0) {
1972			if (noaction) {
1973				printf("\tmkdir %s\n", mkdirpath);
1974			} else {
1975				res = mkdir(mkdirpath, 0755);
1976				if (res != 0)
1977					err(1, "Error on mkdir(\"%s\") for -a",
1978					    mkdirpath);
1979			}
1980		}
1981		if (*s == '\0')
1982			break;
1983	}
1984	if (verbose) {
1985		if (ent->firstcreate)
1986			printf("Created directory '%s' for new %s\n",
1987			    dirpart, ent->log);
1988		else
1989			printf("Created directory '%s' for -a\n", dirpart);
1990	}
1991}
1992
1993/*
1994 * Create a new log file, destroying any currently-existing version
1995 * of the log file in the process.  If the caller wants a backup copy
1996 * of the file to exist, they should call 'link(logfile,logbackup)'
1997 * before calling this routine.
1998 */
1999void
2000createlog(const struct conf_entry *ent)
2001{
2002	int fd, failed;
2003	struct stat st;
2004	char *realfile, *slash, tempfile[MAXPATHLEN];
2005
2006	fd = -1;
2007	realfile = ent->log;
2008
2009	/*
2010	 * If this log file is being created for the first time (-C option),
2011	 * then it may also be true that the parent directory does not exist
2012	 * yet.  Check, and create that directory if it is missing.
2013	 */
2014	if (ent->firstcreate) {
2015		strlcpy(tempfile, realfile, sizeof(tempfile));
2016		slash = strrchr(tempfile, '/');
2017		if (slash != NULL) {
2018			*slash = '\0';
2019			failed = stat(tempfile, &st);
2020			if (failed && errno != ENOENT)
2021				err(1, "Error on stat(%s)", tempfile);
2022			if (failed)
2023				createdir(ent, tempfile);
2024			else if (!S_ISDIR(st.st_mode))
2025				errx(1, "%s exists but is not a directory",
2026				    tempfile);
2027		}
2028	}
2029
2030	/*
2031	 * First create an unused filename, so it can be chown'ed and
2032	 * chmod'ed before it is moved into the real location.  mkstemp
2033	 * will create the file mode=600 & owned by us.  Note that all
2034	 * temp files will have a suffix of '.z<something>'.
2035	 */
2036	strlcpy(tempfile, realfile, sizeof(tempfile));
2037	strlcat(tempfile, ".zXXXXXX", sizeof(tempfile));
2038	if (noaction)
2039		printf("\tmktemp %s\n", tempfile);
2040	else {
2041		fd = mkstemp(tempfile);
2042		if (fd < 0)
2043			err(1, "can't mkstemp logfile %s", tempfile);
2044
2045		/*
2046		 * Add status message to what will become the new log file.
2047		 */
2048		if (!(ent->flags & CE_BINARY)) {
2049			if (log_trim(tempfile, ent))
2050				err(1, "can't add status message to log");
2051		}
2052	}
2053
2054	/* Change the owner/group, if we are supposed to */
2055	if (ent->uid != (uid_t)-1 || ent->gid != (gid_t)-1) {
2056		if (noaction)
2057			printf("\tchown %u:%u %s\n", ent->uid, ent->gid,
2058			    tempfile);
2059		else {
2060			failed = fchown(fd, ent->uid, ent->gid);
2061			if (failed)
2062				err(1, "can't fchown temp file %s", tempfile);
2063		}
2064	}
2065
2066	/* Turn on NODUMP if it was requested in the config-file. */
2067	if (ent->flags & CE_NODUMP) {
2068		if (noaction)
2069			printf("\tchflags nodump %s\n", tempfile);
2070		else {
2071			failed = fchflags(fd, UF_NODUMP);
2072			if (failed) {
2073				warn("log_trim: fchflags(NODUMP)");
2074			}
2075		}
2076	}
2077
2078	/*
2079	 * Note that if the real logfile still exists, and if the call
2080	 * to rename() fails, then "neither the old file nor the new
2081	 * file shall be changed or created" (to quote the standard).
2082	 * If the call succeeds, then the file will be replaced without
2083	 * any window where some other process might find that the file
2084	 * did not exist.
2085	 * XXX - ? It may be that for some error conditions, we could
2086	 *	retry by first removing the realfile and then renaming.
2087	 */
2088	if (noaction) {
2089		printf("\tchmod %o %s\n", ent->permissions, tempfile);
2090		printf("\tmv %s %s\n", tempfile, realfile);
2091	} else {
2092		failed = fchmod(fd, ent->permissions);
2093		if (failed)
2094			err(1, "can't fchmod temp file '%s'", tempfile);
2095		failed = rename(tempfile, realfile);
2096		if (failed)
2097			err(1, "can't mv %s to %s", tempfile, realfile);
2098	}
2099
2100	if (fd >= 0)
2101		close(fd);
2102}
2103
2104/*
2105 * Change the attributes of a given filename to what was specified in
2106 * the newsyslog.conf entry.  This routine is only called for files
2107 * that newsyslog expects that it has created, and thus it is a fatal
2108 * error if this routine finds that the file does not exist.
2109 */
2110static void
2111change_attrs(const char *fname, const struct conf_entry *ent)
2112{
2113	int failed;
2114
2115	if (noaction) {
2116		printf("\tchmod %o %s\n", ent->permissions, fname);
2117
2118		if (ent->uid != (uid_t)-1 || ent->gid != (gid_t)-1)
2119			printf("\tchown %u:%u %s\n",
2120			    ent->uid, ent->gid, fname);
2121
2122		if (ent->flags & CE_NODUMP)
2123			printf("\tchflags nodump %s\n", fname);
2124		return;
2125	}
2126
2127	failed = chmod(fname, ent->permissions);
2128	if (failed) {
2129		if (errno != EPERM)
2130			err(1, "chmod(%s) in change_attrs", fname);
2131		warn("change_attrs couldn't chmod(%s)", fname);
2132	}
2133
2134	if (ent->uid != (uid_t)-1 || ent->gid != (gid_t)-1) {
2135		failed = chown(fname, ent->uid, ent->gid);
2136		if (failed)
2137			warn("can't chown %s", fname);
2138	}
2139
2140	if (ent->flags & CE_NODUMP) {
2141		failed = chflags(fname, UF_NODUMP);
2142		if (failed)
2143			warn("can't chflags %s NODUMP", fname);
2144	}
2145}
2146