newsyslog.c revision 112003
113244Sgraichen/*
213244Sgraichen * This file contains changes from the Open Software Foundation.
313244Sgraichen */
413244Sgraichen
513244Sgraichen/*
659004Shm * Copyright 1988, 1989 by the Massachusetts Institute of Technology
759004Shm *
859004Shm * Permission to use, copy, modify, and distribute this software and its
959004Shm * documentation for any purpose and without fee is hereby granted, provided
1059004Shm * that the above copyright notice appear in all copies and that both that
1159004Shm * copyright notice and this permission notice appear in supporting
1259004Shm * documentation, and that the names of M.I.T. and the M.I.T. S.I.P.B. not be
1359004Shm * used in advertising or publicity pertaining to distribution of the
1459004Shm * software without specific, written prior permission. M.I.T. and the M.I.T.
1559004Shm * S.I.P.B. make no representations about the suitability of this software
1659004Shm * for any purpose.  It is provided "as is" without express or implied
1759004Shm * warranty.
1859004Shm *
1959004Shm */
2013244Sgraichen
2113244Sgraichen/*
2259004Shm * newsyslog - roll over selected logs at the appropriate time, keeping the a
2359004Shm * specified number of backup files around.
2413244Sgraichen */
2513244Sgraichen
2613244Sgraichen#ifndef lint
2730160Scharnierstatic const char rcsid[] =
2859003Shm"$FreeBSD: head/usr.sbin/newsyslog/newsyslog.c 112003 2003-03-08 20:07:01Z gad $";
2959004Shm#endif	/* not lint */
3013244Sgraichen
3143071Swollman#define OSF
3213244Sgraichen#ifndef COMPRESS_POSTFIX
3343071Swollman#define COMPRESS_POSTFIX ".gz"
3413244Sgraichen#endif
3580638Sobrien#ifndef	BZCOMPRESS_POSTFIX
3680638Sobrien#define	BZCOMPRESS_POSTFIX ".bz2"
3780638Sobrien#endif
3813244Sgraichen
3996001Smaxim#include <sys/param.h>
4096001Smaxim#include <sys/stat.h>
4196001Smaxim#include <sys/wait.h>
4296001Smaxim
4330160Scharnier#include <ctype.h>
4430160Scharnier#include <err.h>
4595999Smaxim#include <errno.h>
4630160Scharnier#include <fcntl.h>
47111773Sgad#include <fnmatch.h>
48106905Ssobomax#include <glob.h>
4930160Scharnier#include <grp.h>
5043071Swollman#include <paths.h>
5130160Scharnier#include <pwd.h>
5230160Scharnier#include <signal.h>
5313244Sgraichen#include <stdio.h>
5413244Sgraichen#include <stdlib.h>
5513244Sgraichen#include <string.h>
5643071Swollman#include <time.h>
5716240Salex#include <unistd.h>
5813244Sgraichen
5943071Swollman#include "pathnames.h"
6043071Swollman
61111768Sgad/*
62111768Sgad * Bit-values for the 'flags' parsed from a config-file entry.
63111768Sgad */
64111768Sgad#define CE_COMPACT	0x0001	/* Compact the achived log files with gzip. */
65111768Sgad#define CE_BZCOMPACT	0x0002	/* Compact the achived log files with bzip2. */
66111781Sgad#define CE_COMPACTWAIT	0x0004	/* wait until compressing one file finishes */
67111768Sgad				/*    before starting the next step. */
68111768Sgad#define CE_BINARY	0x0008	/* Logfile is in binary, do not add status */
69111768Sgad				/*    messages to logfile(s) when rotating. */
70111781Sgad#define CE_NOSIGNAL	0x0010	/* There is no process to signal when */
71111768Sgad				/*    trimming this file. */
72111781Sgad#define CE_TRIMAT	0x0020	/* trim file at a specific time. */
73111781Sgad#define CE_GLOB		0x0040	/* name of the log is file name pattern. */
74112003Sgad#define CE_SIGNALGROUP	0x0080	/* Signal a process-group instead of a single */
75112003Sgad				/*    process when trimming this file. */
7643071Swollman
77111781Sgad#define MIN_PID         5	/* Don't touch pids lower than this */
78111781Sgad#define MAX_PID		99999	/* was lower, see /usr/include/sys/proc.h */
79111781Sgad
80111781Sgad#define kbytes(size)  (((size) + 1023) >> 10)
81111781Sgad
8213244Sgraichenstruct conf_entry {
8359003Shm	char *log;		/* Name of the log */
8459003Shm	char *pid_file;		/* PID file */
85111772Sgad	char *r_reason;		/* The reason this file is being rotated */
86111772Sgad	int rotate;		/* Non-zero if this file should be rotated */
87111779Sgad	uid_t uid;		/* Owner of log */
88111779Sgad	gid_t gid;		/* Group of log */
8959003Shm	int numlogs;		/* Number of logs to keep */
9059003Shm	int size;		/* Size cutoff to trigger trimming the log */
9159003Shm	int hours;		/* Hours between log trimming */
9259003Shm	time_t trim_at;		/* Specific time to do trimming */
9359003Shm	int permissions;	/* File permissions on the log */
9480646Sobrien	int flags;		/* CE_COMPACT, CE_BZCOMPACT, CE_BINARY */
9559003Shm	int sig;		/* Signal to send */
96111388Sgad	int def_cfg;		/* Using the <default> rule for this file */
9759003Shm	struct conf_entry *next;/* Linked list pointer */
9813244Sgraichen};
9913244Sgraichen
100111388Sgad#define DEFAULT_MARKER "<default>"
101111388Sgad
10259004Shmint archtodir = 0;		/* Archive old logfiles to other directory */
10359003Shmint verbose = 0;		/* Print out what's going on */
10459003Shmint needroot = 1;		/* Root privs are necessary */
10559003Shmint noaction = 0;		/* Don't do anything, just show it */
106111768Sgadint nosignal;			/* Do not send any signals */
10759003Shmint force = 0;			/* Force the trim no matter what */
108111772Sgadint rotatereq = 0;		/* -R = Always rotate the file(s) as given */
109111772Sgad				/*    on the command (this also requires   */
110111772Sgad				/*    that a list of files *are* given on  */
111111772Sgad				/*    the run command). */
112111772Sgadchar *requestor;		/* The name given on a -R request */
11359004Shmchar *archdirname;		/* Directory path to old logfiles archive */
114111773Sgadconst char *conf;		/* Configuration file to use */
11559003Shmtime_t timenow;
11659003Shm
11771299Sjedgarchar hostname[MAXHOSTNAMELEN];	/* hostname */
118108164Strhodeschar daytime[16];		/* timenow in human readable form */
11913244Sgraichen
120111773Sgadstatic struct conf_entry *get_worklist(char **files);
121111773Sgadstatic void parse_file(FILE *cf, const char *cfname, struct conf_entry **work_p,
122111773Sgad		struct conf_entry **defconf_p);
12316240Salexstatic char *sob(char *p);
12416240Salexstatic char *son(char *p);
12559003Shmstatic char *missing_field(char *p, char *errline);
12659003Shmstatic void do_entry(struct conf_entry * ent);
127111388Sgadstatic void free_entry(struct conf_entry *ent);
128111388Sgadstatic struct conf_entry *init_entry(const char *fname,
129111388Sgad		struct conf_entry *src_entry);
130111781Sgadstatic void parse_args(int argc, char **argv);
13180640Sobrienstatic void usage(void);
132111779Sgadstatic void dotrim(const struct conf_entry *ent, char *log,
133111780Sgad		int numdays, int flags);
134111768Sgadstatic int log_trim(const char *log, const struct conf_entry *log_ent);
135107916Ssobomaxstatic void compress_log(char *log, int dowait);
136107916Ssobomaxstatic void bzcompress_log(char *log, int dowait);
13716240Salexstatic int sizefile(char *file);
13816240Salexstatic int age_old_log(char *file);
139112003Sgadstatic int send_signal(const struct conf_entry *ent);
14093659Scjcstatic time_t parse8601(char *s, char *errline);
141111779Sgadstatic void movefile(char *from, char *to, int perm, uid_t owner_uid,
142111779Sgad		gid_t group_gid);
14359004Shmstatic void createdir(char *dirpart);
14493659Scjcstatic time_t parseDWM(char *s, char *errline);
14513244Sgraichen
146111768Sgad/*
147111768Sgad * All the following are defined to work on an 'int', in the
148111768Sgad * range 0 to 255, plus EOF.  Define wrappers which can take
149111768Sgad * values of type 'char', either signed or unsigned.
150111768Sgad */
151111772Sgad#define isprintch(Anychar)    isprint(((int) Anychar) & 255)
152111768Sgad#define isspacech(Anychar)    isspace(((int) Anychar) & 255)
153111768Sgad#define tolowerch(Anychar)    tolower(((int) Anychar) & 255)
154111768Sgad
15559004Shmint
15659004Shmmain(int argc, char **argv)
15713244Sgraichen{
15859003Shm	struct conf_entry *p, *q;
159111529Sgad	char *savglob;
160106905Ssobomax	glob_t pglob;
161106905Ssobomax	int i;
16225443Sache
163111781Sgad	parse_args(argc, argv);
164111773Sgad	argc -= optind;
165111773Sgad	argv += optind;
166111773Sgad
16759003Shm	if (needroot && getuid() && geteuid())
16859003Shm		errx(1, "must have root privs");
169111773Sgad	p = q = get_worklist(argv);
17059003Shm
17159003Shm	while (p) {
172106905Ssobomax		if ((p->flags & CE_GLOB) == 0) {
173106905Ssobomax			do_entry(p);
174106905Ssobomax		} else {
175111773Sgad			if (verbose > 2)
176111773Sgad				printf("\t+ Processing pattern %s\n", p->log);
177106905Ssobomax			if (glob(p->log, GLOB_NOCHECK, NULL, &pglob) != 0) {
178106905Ssobomax				warn("can't expand pattern: %s", p->log);
179106905Ssobomax			} else {
180111529Sgad				savglob = p->log;
181106905Ssobomax				for (i = 0; i < pglob.gl_matchc; i++) {
182106905Ssobomax					p->log = pglob.gl_pathv[i];
183106905Ssobomax					do_entry(p);
184106905Ssobomax				}
185106905Ssobomax				globfree(&pglob);
186111529Sgad				p->log = savglob;
187111773Sgad				if (verbose > 2)
188111773Sgad					printf("\t+ Done with pattern\n");
189106905Ssobomax			}
190106905Ssobomax		}
19159003Shm		p = p->next;
192111388Sgad		free_entry(q);
19359003Shm		q = p;
19459003Shm	}
19595999Smaxim	while (wait(NULL) > 0 || errno == EINTR)
19695999Smaxim		;
19759003Shm	return (0);
19813244Sgraichen}
19913244Sgraichen
200111388Sgadstatic struct conf_entry *
201111388Sgadinit_entry(const char *fname, struct conf_entry *src_entry)
202111388Sgad{
203111388Sgad	struct conf_entry *tempwork;
204111388Sgad
205111388Sgad	if (verbose > 4)
206111388Sgad		printf("\t--> [creating entry for %s]\n", fname);
207111388Sgad
208111388Sgad	tempwork = malloc(sizeof(struct conf_entry));
209111388Sgad	if (tempwork == NULL)
210111388Sgad		err(1, "malloc of conf_entry for %s", fname);
211111388Sgad
212111388Sgad	tempwork->log = strdup(fname);
213111388Sgad	if (tempwork->log == NULL)
214111388Sgad		err(1, "strdup for %s", fname);
215111388Sgad
216111388Sgad	if (src_entry != NULL) {
217111388Sgad		tempwork->pid_file = NULL;
218111388Sgad		if (src_entry->pid_file)
219111388Sgad			tempwork->pid_file = strdup(src_entry->pid_file);
220111772Sgad		tempwork->r_reason = NULL;
221111772Sgad		tempwork->rotate = 0;
222111388Sgad		tempwork->uid = src_entry->uid;
223111388Sgad		tempwork->gid = src_entry->gid;
224111388Sgad		tempwork->numlogs = src_entry->numlogs;
225111388Sgad		tempwork->size = src_entry->size;
226111388Sgad		tempwork->hours = src_entry->hours;
227111388Sgad		tempwork->trim_at = src_entry->trim_at;
228111388Sgad		tempwork->permissions = src_entry->permissions;
229111388Sgad		tempwork->flags = src_entry->flags;
230111388Sgad		tempwork->sig = src_entry->sig;
231111388Sgad		tempwork->def_cfg = src_entry->def_cfg;
232111388Sgad	} else {
233111388Sgad		/* Initialize as a "do-nothing" entry */
234111388Sgad		tempwork->pid_file = NULL;
235111772Sgad		tempwork->r_reason = NULL;
236111772Sgad		tempwork->rotate = 0;
237111779Sgad		tempwork->uid = (uid_t)-1;
238111779Sgad		tempwork->gid = (gid_t)-1;
239111388Sgad		tempwork->numlogs = 1;
240111388Sgad		tempwork->size = -1;
241111388Sgad		tempwork->hours = -1;
242111388Sgad		tempwork->trim_at = (time_t)0;
243111388Sgad		tempwork->permissions = 0;
244111388Sgad		tempwork->flags = 0;
245111388Sgad		tempwork->sig = SIGHUP;
246111388Sgad		tempwork->def_cfg = 0;
247111388Sgad	}
248111388Sgad	tempwork->next = NULL;
249111388Sgad
250111388Sgad	return (tempwork);
251111388Sgad}
252111388Sgad
25359004Shmstatic void
254111388Sgadfree_entry(struct conf_entry *ent)
255111388Sgad{
256111388Sgad
257111388Sgad	if (ent == NULL)
258111388Sgad		return;
259111388Sgad
260111388Sgad	if (ent->log != NULL) {
261111388Sgad		if (verbose > 4)
262111388Sgad			printf("\t--> [freeing entry for %s]\n", ent->log);
263111388Sgad		free(ent->log);
264111388Sgad		ent->log = NULL;
265111388Sgad	}
266111388Sgad
267111388Sgad	if (ent->pid_file != NULL) {
268111388Sgad		free(ent->pid_file);
269111388Sgad		ent->pid_file = NULL;
270111388Sgad	}
271111388Sgad
272111772Sgad	if (ent->r_reason != NULL) {
273111772Sgad		free(ent->r_reason);
274111772Sgad		ent->r_reason = NULL;
275111772Sgad	}
276111772Sgad
277111388Sgad	free(ent);
278111388Sgad}
279111388Sgad
280111388Sgadstatic void
28159004Shmdo_entry(struct conf_entry * ent)
28213244Sgraichen{
283111772Sgad#define REASON_MAX	80
28459003Shm	int size, modtime;
285111772Sgad	char temp_reason[REASON_MAX];
28659003Shm
28759003Shm	if (verbose) {
28859003Shm		if (ent->flags & CE_COMPACT)
28959003Shm			printf("%s <%dZ>: ", ent->log, ent->numlogs);
29080638Sobrien		else if (ent->flags & CE_BZCOMPACT)
29180638Sobrien			printf("%s <%dJ>: ", ent->log, ent->numlogs);
29259003Shm		else
29359003Shm			printf("%s <%d>: ", ent->log, ent->numlogs);
29459003Shm	}
29559003Shm	size = sizefile(ent->log);
29659003Shm	modtime = age_old_log(ent->log);
297111772Sgad	ent->rotate = 0;
29859003Shm	if (size < 0) {
29959003Shm		if (verbose)
30059003Shm			printf("does not exist.\n");
30159003Shm	} else {
302111772Sgad		if (ent->flags & CE_TRIMAT && !force && !rotatereq) {
30343071Swollman			if (timenow < ent->trim_at
30459003Shm			    || difftime(timenow, ent->trim_at) >= 60 * 60) {
30543071Swollman				if (verbose)
30643071Swollman					printf("--> will trim at %s",
30759003Shm					    ctime(&ent->trim_at));
30843071Swollman				return;
30943071Swollman			} else if (verbose && ent->hours <= 0) {
31043071Swollman				printf("--> time is up\n");
31143071Swollman			}
31243071Swollman		}
31359003Shm		if (verbose && (ent->size > 0))
31459003Shm			printf("size (Kb): %d [%d] ", size, ent->size);
31559003Shm		if (verbose && (ent->hours > 0))
31659003Shm			printf(" age (hr): %d [%d] ", modtime, ent->hours);
317111772Sgad
318111772Sgad		/*
319111772Sgad		 * Figure out if this logfile needs to be rotated.
320111772Sgad		 */
321111772Sgad		temp_reason[0] = '\0';
322111772Sgad		if (rotatereq) {
323111772Sgad			ent->rotate = 1;
324111772Sgad			snprintf(temp_reason, REASON_MAX, " due to -R from %s",
325111772Sgad			    requestor);
326111772Sgad		} else if (force) {
327111772Sgad			ent->rotate = 1;
328111772Sgad			snprintf(temp_reason, REASON_MAX, " due to -F request");
329111772Sgad		} else if ((ent->size > 0) && (size >= ent->size)) {
330111772Sgad			ent->rotate = 1;
331111772Sgad			snprintf(temp_reason, REASON_MAX, " due to size>%dK",
332111772Sgad			    ent->size);
333111772Sgad		} else if (ent->hours <= 0 && (ent->flags & CE_TRIMAT)) {
334111772Sgad			ent->rotate = 1;
335111772Sgad		} else if ((ent->hours > 0) && ((modtime >= ent->hours) ||
336111772Sgad		    (modtime < 0))) {
337111772Sgad			ent->rotate = 1;
338111772Sgad		}
339111772Sgad
340111772Sgad		/*
341111772Sgad		 * If the file needs to be rotated, then rotate it.
342111772Sgad		 */
343111772Sgad		if (ent->rotate) {
344111772Sgad			if (temp_reason[0] != '\0')
345111772Sgad				ent->r_reason = strdup(temp_reason);
34659003Shm			if (verbose)
34759003Shm				printf("--> trimming log....\n");
34859003Shm			if (noaction && !verbose) {
34959003Shm				if (ent->flags & CE_COMPACT)
35059003Shm					printf("%s <%dZ>: trimming\n",
35159003Shm					    ent->log, ent->numlogs);
35280638Sobrien				else if (ent->flags & CE_BZCOMPACT)
35380638Sobrien					printf("%s <%dJ>: trimming\n",
35480638Sobrien					    ent->log, ent->numlogs);
35559003Shm				else
35659003Shm					printf("%s <%d>: trimming\n",
35759003Shm					    ent->log, ent->numlogs);
35859003Shm			}
359111780Sgad			dotrim(ent, ent->log, ent->numlogs, ent->flags);
36059003Shm		} else {
36159003Shm			if (verbose)
36259003Shm				printf("--> skipping\n");
36359003Shm		}
36459003Shm	}
365111772Sgad#undef REASON_MAX
36613244Sgraichen}
36713244Sgraichen
368112003Sgad/* Send a signal to the pid specified by pidfile */
369112003Sgadstatic int
370112003Sgadsend_signal(const struct conf_entry *ent)
371112003Sgad{
372112003Sgad	pid_t target_pid;
373112003Sgad	int did_notify;
374112003Sgad	FILE *f;
375112003Sgad	long minok, maxok, rval;
376112003Sgad	const char *target_name;
377112003Sgad	char *endp, *linep, line[BUFSIZ];
378112003Sgad
379112003Sgad	did_notify = 0;
380112003Sgad	f = fopen(ent->pid_file, "r");
381112003Sgad	if (f == NULL) {
382112003Sgad		warn("can't open pid file: %s", ent->pid_file);
383112003Sgad		return (did_notify);
384112003Sgad		/* NOTREACHED */
385112003Sgad	}
386112003Sgad
387112003Sgad	if (fgets(line, BUFSIZ, f) == NULL) {
388112003Sgad		/*
389112003Sgad		 * XXX - If the pid file is empty, is that really a
390112003Sgad		 *	problem?  Wouldn't that mean that the process
391112003Sgad		 *	has shut down?  In that case there would be no
392112003Sgad		 *	problem with compressing the rotated log file.
393112003Sgad		 */
394112003Sgad		if (feof(f))
395112003Sgad			warnx("pid file is empty: %s",  ent->pid_file);
396112003Sgad		else
397112003Sgad			warn("can't read from pid file: %s", ent->pid_file);
398112003Sgad		(void) fclose(f);
399112003Sgad		return (did_notify);
400112003Sgad		/* NOTREACHED */
401112003Sgad	}
402112003Sgad	(void) fclose(f);
403112003Sgad
404112003Sgad	target_name = "daemon";
405112003Sgad	minok = MIN_PID;
406112003Sgad	maxok = MAX_PID;
407112003Sgad	if (ent->flags & CE_SIGNALGROUP) {
408112003Sgad		/*
409112003Sgad		 * If we are expected to signal a process-group when
410112003Sgad		 * rotating this logfile, then the value read in should
411112003Sgad		 * be the negative of a valid process ID.
412112003Sgad		 */
413112003Sgad		target_name = "process-group";
414112003Sgad		minok = -MAX_PID;
415112003Sgad		maxok = -MIN_PID;
416112003Sgad	}
417112003Sgad
418112003Sgad	errno = 0;
419112003Sgad	linep = line;
420112003Sgad	while (*linep == ' ')
421112003Sgad		linep++;
422112003Sgad	rval = strtol(linep, &endp, 10);
423112003Sgad	if (*endp != '\0' && !isspacech(*endp)) {
424112003Sgad		warnx("pid file does not start with a valid number: %s",
425112003Sgad		    ent->pid_file);
426112003Sgad		rval = 0;
427112003Sgad	} else if (rval < minok || rval > maxok) {
428112003Sgad		warnx("bad value '%ld' for process number in %s",
429112003Sgad		    rval, ent->pid_file);
430112003Sgad		if (verbose)
431112003Sgad			warnx("\t(expecting value between %ld and %ld)",
432112003Sgad			    minok, maxok);
433112003Sgad		rval = 0;
434112003Sgad	}
435112003Sgad	if (rval == 0) {
436112003Sgad		return (did_notify);
437112003Sgad		/* NOTREACHED */
438112003Sgad	}
439112003Sgad
440112003Sgad	target_pid = rval;
441112003Sgad
442112003Sgad	if (noaction) {
443112003Sgad		did_notify = 1;
444112003Sgad		printf("\tkill -%d %d\n", ent->sig, (int) target_pid);
445112003Sgad	} else if (kill(target_pid, ent->sig)) {
446112003Sgad		/*
447112003Sgad		 * XXX - Iff the error was "no such process", should that
448112003Sgad		 *	really be an error for us?  Perhaps the process
449112003Sgad		 *	is already gone, in which case there would be no
450112003Sgad		 *	problem with compressing the rotated log file.
451112003Sgad		 */
452112003Sgad		warn("can't notify %s, pid %d", target_name,
453112003Sgad		    (int) target_pid);
454112003Sgad	} else {
455112003Sgad		did_notify = 1;
456112003Sgad		if (verbose)
457112003Sgad			printf("%s pid %d notified\n", target_name,
458112003Sgad			    (int) target_pid);
459112003Sgad	}
460112003Sgad
461112003Sgad	return (did_notify);
462112003Sgad}
463112003Sgad
46459004Shmstatic void
465111781Sgadparse_args(int argc, char **argv)
46613244Sgraichen{
467111781Sgad	int ch;
46859003Shm	char *p;
46913244Sgraichen
470111781Sgad	timenow = time(NULL);
471108164Strhodes	(void)strncpy(daytime, ctime(&timenow) + 4, 15);
47259003Shm	daytime[15] = '\0';
47313244Sgraichen
47459003Shm	/* Let's get our hostname */
475111781Sgad	(void)gethostname(hostname, sizeof(hostname));
47613244Sgraichen
47713244Sgraichen	/* Truncate domain */
478111781Sgad	if ((p = strchr(hostname, '.')) != NULL)
47913244Sgraichen		*p = '\0';
480111768Sgad
481111768Sgad	/* Parse command line options. */
482111781Sgad	while ((ch = getopt(argc, argv, "a:f:nrsvFR:")) != -1)
483111781Sgad		switch (ch) {
48459004Shm		case 'a':
48559004Shm			archtodir++;
48659004Shm			archdirname = optarg;
48759004Shm			break;
488111768Sgad		case 'f':
489111768Sgad			conf = optarg;
490111768Sgad			break;
491111768Sgad		case 'n':
492111768Sgad			noaction++;
493111768Sgad			break;
49459003Shm		case 'r':
49559003Shm			needroot = 0;
49659003Shm			break;
497111768Sgad		case 's':
498111768Sgad			nosignal = 1;
499111768Sgad			break;
50059003Shm		case 'v':
50159003Shm			verbose++;
50259003Shm			break;
50334584Spst		case 'F':
50434584Spst			force++;
50534584Spst			break;
506111772Sgad		case 'R':
507111772Sgad			rotatereq++;
508111772Sgad			requestor = strdup(optarg);
509111772Sgad			break;
510111781Sgad		case 'm':	/* Used by OpenBSD for "monitor mode" */
51159003Shm		default:
51259003Shm			usage();
513111768Sgad			/* NOTREACHED */
51459003Shm		}
515111772Sgad
516111772Sgad	if (rotatereq) {
517111772Sgad		if (optind == argc) {
518111772Sgad			warnx("At least one filename must be given when -R is specified.");
519111772Sgad			usage();
520111772Sgad			/* NOTREACHED */
521111772Sgad		}
522111772Sgad		/* Make sure "requestor" value is safe for a syslog message. */
523111772Sgad		for (p = requestor; *p != '\0'; p++) {
524111772Sgad			if (!isprintch(*p) && (*p != '\t'))
525111772Sgad				*p = '.';
526111772Sgad		}
527111772Sgad	}
52843071Swollman}
52913244Sgraichen
53059004Shmstatic void
53159004Shmusage(void)
53213244Sgraichen{
53380646Sobrien
53480646Sobrien	fprintf(stderr,
535111772Sgad	    "usage: newsyslog [-Fnrsv] [-a directory] [-f config-file]\n"
536111772Sgad	    "                 [ [-R requestor] filename ... ]\n");
53759003Shm	exit(1);
53813244Sgraichen}
53913244Sgraichen
54059004Shm/*
541111773Sgad * Parse a configuration file and return a linked list of all the logs
542111773Sgad * which should be processed.
54313244Sgraichen */
54459003Shmstatic struct conf_entry *
545111773Sgadget_worklist(char **files)
54613244Sgraichen{
54759003Shm	FILE *f;
548111773Sgad	const char *fname;
549111773Sgad	char **given;
550111773Sgad	struct conf_entry *defconf, *dupent, *ent, *firstnew;
551111773Sgad	struct conf_entry *newlist, *worklist;
552111773Sgad	int gmatch;
553111773Sgad
554111773Sgad	defconf = worklist = NULL;
555111773Sgad
556111773Sgad	fname = conf;
557111773Sgad	if (fname == NULL)
558111773Sgad		fname = _PATH_CONF;
559111773Sgad
560111773Sgad	if (strcmp(fname, "-") != 0)
561111773Sgad		f = fopen(fname, "r");
562111773Sgad	else {
563111773Sgad		f = stdin;
564111773Sgad		fname = "<stdin>";
565111773Sgad	}
566111773Sgad	if (!f)
567111773Sgad		err(1, "%s", conf);
568111773Sgad
569111773Sgad	parse_file(f, fname, &worklist, &defconf);
570111773Sgad	(void) fclose(f);
571111773Sgad
572111773Sgad	/*
573111773Sgad	 * All config-file information has been read in and turned into
574111773Sgad	 * a worklist.  If there were no specific files given on the run
575111773Sgad	 * command, then the work of this routine is done.
576111773Sgad	 */
577111773Sgad	if (*files == NULL) {
578111773Sgad		if (defconf != NULL)
579111773Sgad			free_entry(defconf);
580111773Sgad		return (worklist);
581111773Sgad		/* NOTREACHED */
582111773Sgad	}
583111773Sgad
584111773Sgad	/*
585111773Sgad	 * If newsyslog was given a specific list of files to process,
586111773Sgad	 * it may be that some of those files were not listed in any
587111773Sgad	 * config file.  Those unlisted files should get the default
588111773Sgad	 * rotation action.  First, create the default-rotation action
589111773Sgad	 * if none was found in a system config file.
590111773Sgad	 */
591111773Sgad	if (defconf == NULL) {
592111773Sgad		defconf = init_entry(DEFAULT_MARKER, NULL);
593111773Sgad		defconf->numlogs = 3;
594111773Sgad		defconf->size = 50;
595111773Sgad		defconf->permissions = S_IRUSR|S_IWUSR;
596111773Sgad	}
597111773Sgad
598111773Sgad	/*
599111773Sgad	 * If newsyslog was run with a list of specific filenames,
600111773Sgad	 * then create a new worklist which has only those files in
601111773Sgad	 * it, picking up the rotation-rules for those files from
602111773Sgad	 * the original worklist.
603111773Sgad	 *
604111773Sgad	 * XXX - Note that this will copy multiple rules for a single
605111773Sgad	 *	logfile, if multiple entries are an exact match for
606111773Sgad	 *	that file.  That matches the historic behavior, but do
607111773Sgad	 *	we want to continue to allow it?  If so, it should
608111773Sgad	 *	probably be handled more intelligently.
609111773Sgad	 */
610111773Sgad	firstnew = newlist = NULL;
611111773Sgad	for (given = files; *given; ++given) {
612111773Sgad		gmatch = 0;
613111773Sgad		/*
614111773Sgad		 * First try to find exact-matches for this given file.
615111773Sgad		 */
616111773Sgad		for (ent = worklist; ent; ent = ent->next) {
617111773Sgad			if ((ent->flags & CE_GLOB) != 0)
618111773Sgad				continue;
619111773Sgad			if (strcmp(ent->log, *given) == 0) {
620111773Sgad				gmatch++;
621111773Sgad				dupent = init_entry(*given, ent);
622111773Sgad				if (!firstnew)
623111773Sgad					firstnew = dupent;
624111773Sgad				else
625111773Sgad					newlist->next = dupent;
626111773Sgad				newlist = dupent;
627111773Sgad			}
628111773Sgad		}
629111773Sgad		if (gmatch) {
630111773Sgad			if (verbose > 2)
631111773Sgad				printf("\t+ Matched entry %s\n", *given);
632111773Sgad			continue;
633111773Sgad		}
634111773Sgad
635111773Sgad		/*
636111773Sgad		 * There was no exact-match for this given file, so look
637111773Sgad		 * for a "glob" entry which does match.
638111773Sgad		 */
639111773Sgad		for (ent = worklist; ent; ent = ent->next) {
640111773Sgad			if ((ent->flags & CE_GLOB) == 0)
641111773Sgad				continue;
642111773Sgad			if (fnmatch(ent->log, *given, FNM_PATHNAME) == 0) {
643111773Sgad				gmatch++;
644111773Sgad				dupent = init_entry(*given, ent);
645111773Sgad				if (!firstnew)
646111773Sgad					firstnew = dupent;
647111773Sgad				else
648111773Sgad					newlist->next = dupent;
649111773Sgad				newlist = dupent;
650111773Sgad				/* This work entry is *not* a glob! */
651111773Sgad				dupent->flags &= ~CE_GLOB;
652111773Sgad				/* Only allow a match to one glob-entry */
653111773Sgad				break;
654111773Sgad			}
655111773Sgad		}
656111773Sgad		if (gmatch) {
657111773Sgad			if (verbose > 2)
658111773Sgad				printf("\t+ Matched %s via %s\n", *given,
659111773Sgad				    ent->log);
660111773Sgad			continue;
661111773Sgad		}
662111773Sgad
663111773Sgad		/*
664111773Sgad		 * This given file was not found in any config file, so
665111773Sgad		 * add a worklist item based on the default entry.
666111773Sgad		 */
667111773Sgad		if (verbose > 2)
668111773Sgad			printf("\t+ No entry matched %s  (will use %s)\n",
669111773Sgad			    *given, DEFAULT_MARKER);
670111773Sgad		dupent = init_entry(*given, defconf);
671111773Sgad		if (!firstnew)
672111773Sgad			firstnew = dupent;
673111773Sgad		else
674111773Sgad			newlist->next = dupent;
675111773Sgad		/* Mark that it was *not* found in a config file */
676111773Sgad		dupent->def_cfg = 1;
677111773Sgad		newlist = dupent;
678111773Sgad	}
679111773Sgad
680111773Sgad	/*
681111773Sgad	 * Free all the entries in the original work list, and then
682111773Sgad	 * return the new work list.
683111773Sgad	 */
684111773Sgad	while (worklist) {
685111773Sgad		ent = worklist->next;
686111773Sgad		free_entry(worklist);
687111773Sgad		worklist = ent;
688111773Sgad	}
689111773Sgad
690111773Sgad	free_entry(defconf);
691111773Sgad	return (newlist);
692111773Sgad}
693111773Sgad
694111773Sgad/*
695111773Sgad * Parse a configuration file and update a linked list of all the logs to
696111773Sgad * process.
697111773Sgad */
698111773Sgadstatic void
699111773Sgadparse_file(FILE *cf, const char *cfname, struct conf_entry **work_p,
700111773Sgad    struct conf_entry **defconf_p)
701111773Sgad{
70259003Shm	char line[BUFSIZ], *parse, *q;
703107737Ssobomax	char *cp, *errline, *group;
704111773Sgad	struct conf_entry *working, *worklist;
705111781Sgad	struct passwd *pwd;
70659003Shm	struct group *grp;
70725518Sbrian	int eol;
70813244Sgraichen
709111773Sgad	/*
710111773Sgad	 * XXX - for now, assume that only one config file will be read,
711111773Sgad	 *	ie, this routine is only called one time.
712111773Sgad	 */
713111773Sgad	worklist = NULL;
71480646Sobrien
715111773Sgad	while (fgets(line, BUFSIZ, cf)) {
716107737Ssobomax		if ((line[0] == '\n') || (line[0] == '#') ||
717107737Ssobomax		    (strlen(line) == 0))
71859003Shm			continue;
71959003Shm		errline = strdup(line);
720107737Ssobomax		for (cp = line + 1; *cp != '\0'; cp++) {
721107737Ssobomax			if (*cp != '#')
722107737Ssobomax				continue;
723107737Ssobomax			if (*(cp - 1) == '\\') {
724107737Ssobomax				strcpy(cp - 1, cp);
725107737Ssobomax				cp--;
726107737Ssobomax				continue;
727107737Ssobomax			}
728107737Ssobomax			*cp = '\0';
729107737Ssobomax			break;
730107737Ssobomax		}
73160373Sdes
73260373Sdes		q = parse = missing_field(sob(line), errline);
73360373Sdes		parse = son(line);
73460373Sdes		if (!*parse)
73580646Sobrien			errx(1, "malformed line (missing fields):\n%s",
73680646Sobrien			    errline);
73760373Sdes		*parse = '\0';
73860373Sdes
739111388Sgad		working = init_entry(q, NULL);
740111388Sgad		if (strcasecmp(DEFAULT_MARKER, q) == 0) {
741111773Sgad			if (defconf_p == NULL) {
742111773Sgad				warnx("Ignoring entry for %s in %s!", q,
743111773Sgad				    cfname);
744111773Sgad				free_entry(working);
745111773Sgad				continue;
746111773Sgad			} else if (*defconf_p != NULL) {
747111388Sgad				warnx("Ignoring duplicate entry for %s!", q);
748111388Sgad				free_entry(working);
749111388Sgad				continue;
750111388Sgad			}
751111773Sgad			*defconf_p = working;
75259003Shm		} else {
753111773Sgad			if (!*work_p)
754111773Sgad				*work_p = working;
755111388Sgad			else
756111388Sgad				worklist->next = working;
757111388Sgad			worklist = working;
75859003Shm		}
75913244Sgraichen
76059003Shm		q = parse = missing_field(sob(++parse), errline);
76159003Shm		parse = son(parse);
76225518Sbrian		if (!*parse)
76380646Sobrien			errx(1, "malformed line (missing fields):\n%s",
76480646Sobrien			    errline);
76559003Shm		*parse = '\0';
76659003Shm		if ((group = strchr(q, ':')) != NULL ||
76759003Shm		    (group = strrchr(q, '.')) != NULL) {
76859003Shm			*group++ = '\0';
76959003Shm			if (*q) {
77059003Shm				if (!(isnumber(*q))) {
771111781Sgad					if ((pwd = getpwnam(q)) == NULL)
77259003Shm						errx(1,
77380646Sobrien				     "error in config file; unknown user:\n%s",
77459003Shm						    errline);
775111781Sgad					working->uid = pwd->pw_uid;
77659003Shm				} else
77759003Shm					working->uid = atoi(q);
77859003Shm			} else
779111779Sgad				working->uid = (uid_t)-1;
78013244Sgraichen
78159003Shm			q = group;
78259003Shm			if (*q) {
78359003Shm				if (!(isnumber(*q))) {
78459003Shm					if ((grp = getgrnam(q)) == NULL)
78559003Shm						errx(1,
78680646Sobrien				    "error in config file; unknown group:\n%s",
78759003Shm						    errline);
78859003Shm					working->gid = grp->gr_gid;
78959003Shm				} else
79059003Shm					working->gid = atoi(q);
79159003Shm			} else
792111779Sgad				working->gid = (gid_t)-1;
79313244Sgraichen
79459003Shm			q = parse = missing_field(sob(++parse), errline);
79559003Shm			parse = son(parse);
79659003Shm			if (!*parse)
79780646Sobrien				errx(1, "malformed line (missing fields):\n%s",
79880646Sobrien				    errline);
79959003Shm			*parse = '\0';
800111779Sgad		} else {
801111779Sgad			working->uid = (uid_t)-1;
802111779Sgad			working->gid = (gid_t)-1;
803111779Sgad		}
80459003Shm
80559003Shm		if (!sscanf(q, "%o", &working->permissions))
80659003Shm			errx(1, "error in config file; bad permissions:\n%s",
80759003Shm			    errline);
80859003Shm
80959003Shm		q = parse = missing_field(sob(++parse), errline);
81059003Shm		parse = son(parse);
81125518Sbrian		if (!*parse)
81280646Sobrien			errx(1, "malformed line (missing fields):\n%s",
81380646Sobrien			    errline);
81459003Shm		*parse = '\0';
815111400Sgad		if (!sscanf(q, "%d", &working->numlogs) || working->numlogs < 0)
816111400Sgad			errx(1, "error in config file; bad value for count of logs to save:\n%s",
81759003Shm			    errline);
81813244Sgraichen
81959003Shm		q = parse = missing_field(sob(++parse), errline);
82059003Shm		parse = son(parse);
82125518Sbrian		if (!*parse)
82280646Sobrien			errx(1, "malformed line (missing fields):\n%s",
82380646Sobrien			    errline);
82459003Shm		*parse = '\0';
82559003Shm		if (isdigit(*q))
82659003Shm			working->size = atoi(q);
82759003Shm		else
82859003Shm			working->size = -1;
82959003Shm
83059003Shm		working->flags = 0;
83159003Shm		q = parse = missing_field(sob(++parse), errline);
83259003Shm		parse = son(parse);
83325518Sbrian		eol = !*parse;
83459003Shm		*parse = '\0';
83543071Swollman		{
83659003Shm			char *ep;
83759003Shm			u_long ul;
83813244Sgraichen
83943071Swollman			ul = strtoul(q, &ep, 10);
84043071Swollman			if (ep == q)
84143071Swollman				working->hours = 0;
84243071Swollman			else if (*ep == '*')
84343071Swollman				working->hours = -1;
84443071Swollman			else if (ul > INT_MAX)
84543071Swollman				errx(1, "interval is too large:\n%s", errline);
84643071Swollman			else
84743071Swollman				working->hours = ul;
84843071Swollman
84980646Sobrien			if (*ep != '\0' && *ep != '@' && *ep != '*' &&
85080646Sobrien			    *ep != '$')
85143071Swollman				errx(1, "malformed interval/at:\n%s", errline);
85243071Swollman			if (*ep == '@') {
85393659Scjc				if ((working->trim_at = parse8601(ep + 1, errline))
85459003Shm				    == (time_t) - 1)
85543071Swollman					errx(1, "malformed at:\n%s", errline);
85643071Swollman				working->flags |= CE_TRIMAT;
85759004Shm			} else if (*ep == '$') {
85893659Scjc				if ((working->trim_at = parseDWM(ep + 1, errline))
85959004Shm				    == (time_t) - 1)
86059004Shm					errx(1, "malformed at:\n%s", errline);
86159004Shm				working->flags |= CE_TRIMAT;
86243071Swollman			}
86343071Swollman		}
86443071Swollman
86525518Sbrian		if (eol)
86659003Shm			q = NULL;
86725518Sbrian		else {
86859003Shm			q = parse = sob(++parse);	/* Optional field */
86959003Shm			parse = son(parse);
87059003Shm			if (!*parse)
87159003Shm				eol = 1;
87259003Shm			*parse = '\0';
87325518Sbrian		}
87425443Sache
875111768Sgad		for (; q && *q && !isspacech(*q); q++) {
876111768Sgad			switch (tolowerch(*q)) {
877111768Sgad			case 'b':
87859003Shm				working->flags |= CE_BINARY;
879111768Sgad				break;
880111781Sgad			case 'c':	/* Used by NetBSD  for "CE_CREATE" */
881111768Sgad				/*
882111768Sgad				 * netbsd uses 'c' for "create".  We will
883111768Sgad				 * temporarily accept it for 'g', because
884111768Sgad				 * earlier freebsd versions had a typo
885111768Sgad				 * of ('G' || 'c')...
886111768Sgad				 */
887111768Sgad				warnx("Assuming 'g' for 'c' in flags for line:\n%s",
888111768Sgad				    errline);
889111768Sgad				/* FALLTHROUGH */
890111768Sgad			case 'g':
891106905Ssobomax				working->flags |= CE_GLOB;
892111768Sgad				break;
893111768Sgad			case 'j':
894111768Sgad				working->flags |= CE_BZCOMPACT;
895111768Sgad				break;
896111768Sgad			case 'n':
897111768Sgad				working->flags |= CE_NOSIGNAL;
898111768Sgad				break;
899112003Sgad			case 'u':
900112003Sgad				working->flags |= CE_SIGNALGROUP;
901112003Sgad				break;
902111768Sgad			case 'w':
903107916Ssobomax				working->flags |= CE_COMPACTWAIT;
904111768Sgad				break;
905111768Sgad			case 'z':
906111768Sgad				working->flags |= CE_COMPACT;
907111768Sgad				break;
908111768Sgad			case '-':
909111768Sgad				break;
910111781Sgad			case 'f':	/* Used by OpenBSD for "CE_FOLLOW" */
911111781Sgad			case 'm':	/* Used by OpenBSD for "CE_MONITOR" */
912111781Sgad			case 'p':	/* Used by NetBSD  for "CE_PLAIN0" */
913111768Sgad			default:
91480646Sobrien				errx(1, "illegal flag in config file -- %c",
91580646Sobrien				    *q);
916111768Sgad			}
91759003Shm		}
91859003Shm
91925518Sbrian		if (eol)
92059003Shm			q = NULL;
92125518Sbrian		else {
92259003Shm			q = parse = sob(++parse);	/* Optional field */
92359003Shm			parse = son(parse);
92459003Shm			if (!*parse)
92559003Shm				eol = 1;
92659003Shm			*parse = '\0';
92725518Sbrian		}
92825443Sache
92925443Sache		working->pid_file = NULL;
93025443Sache		if (q && *q) {
93125443Sache			if (*q == '/')
93225443Sache				working->pid_file = strdup(q);
93336817Sache			else if (isdigit(*q))
93436817Sache				goto got_sig;
93559003Shm			else
93680646Sobrien				errx(1,
93780646Sobrien			"illegal pid file or signal number in config file:\n%s",
93880646Sobrien				    errline);
93925443Sache		}
94036817Sache		if (eol)
94159003Shm			q = NULL;
94236817Sache		else {
94359003Shm			q = parse = sob(++parse);	/* Optional field */
94459003Shm			*(parse = son(parse)) = '\0';
94536817Sache		}
94636817Sache
94736817Sache		working->sig = SIGHUP;
94836817Sache		if (q && *q) {
94936817Sache			if (isdigit(*q)) {
95059003Shm		got_sig:
95136817Sache				working->sig = atoi(q);
95236817Sache			} else {
95359003Shm		err_sig:
95480646Sobrien				errx(1,
95580646Sobrien				    "illegal signal number in config file:\n%s",
95680646Sobrien				    errline);
95736817Sache			}
95836817Sache			if (working->sig < 1 || working->sig >= NSIG)
95936817Sache				goto err_sig;
96036817Sache		}
961111768Sgad
962111768Sgad		/*
963111768Sgad		 * Finish figuring out what pid-file to use (if any) in
964111768Sgad		 * later processing if this logfile needs to be rotated.
965111768Sgad		 */
966111768Sgad		if ((working->flags & CE_NOSIGNAL) == CE_NOSIGNAL) {
967111768Sgad			/*
968111768Sgad			 * This config-entry specified 'n' for nosignal,
969111768Sgad			 * see if it also specified an explicit pid_file.
970111768Sgad			 * This would be a pretty pointless combination.
971111768Sgad			 */
972111768Sgad			if (working->pid_file != NULL) {
973111768Sgad				warnx("Ignoring '%s' because flag 'n' was specified in line:\n%s",
974111768Sgad				    working->pid_file, errline);
975111768Sgad				free(working->pid_file);
976111768Sgad				working->pid_file = NULL;
977111768Sgad			}
978111768Sgad		} else if (working->pid_file == NULL) {
979111768Sgad			/*
980111768Sgad			 * This entry did not specify the 'n' flag, which
981111768Sgad			 * means it should signal syslogd unless it had
982112003Sgad			 * specified some other pid-file (and obviously the
983112003Sgad			 * syslog pid-file will not be for a process-group).
984112003Sgad			 * Also, we should only try to notify syslog if we
985112003Sgad			 * are root.
986111768Sgad			 */
987112003Sgad			if (working->flags & CE_SIGNALGROUP) {
988112003Sgad				warnx("Ignoring flag 'U' in line:\n%s",
989112003Sgad				    errline);
990112003Sgad				working->flags &= ~CE_SIGNALGROUP;
991112003Sgad			}
992111768Sgad			if (needroot)
993111768Sgad				working->pid_file = strdup(_PATH_SYSLOGPID);
994111768Sgad		}
995111768Sgad
99659003Shm		free(errline);
997111768Sgad		errline = NULL;
99859003Shm	}
99913244Sgraichen}
100013244Sgraichen
100159003Shmstatic char *
100259004Shmmissing_field(char *p, char *errline)
100313244Sgraichen{
100480646Sobrien
100559003Shm	if (!p || !*p)
100659003Shm		errx(1, "missing field in config file:\n%s", errline);
100759003Shm	return (p);
100813244Sgraichen}
100913244Sgraichen
101059004Shmstatic void
1011111780Sgaddotrim(const struct conf_entry *ent, char *log, int numdays, int flags)
101213244Sgraichen{
101371299Sjedgar	char dirpart[MAXPATHLEN], namepart[MAXPATHLEN];
101471299Sjedgar	char file1[MAXPATHLEN], file2[MAXPATHLEN];
101571299Sjedgar	char zfile1[MAXPATHLEN], zfile2[MAXPATHLEN];
101680638Sobrien	char jfile1[MAXPATHLEN];
101794352Ssheldonh	char tfile[MAXPATHLEN];
101859003Shm	int notified, need_notification, fd, _numdays;
101959003Shm	struct stat st;
102013244Sgraichen
102159004Shm	if (archtodir) {
102259004Shm		char *p;
102313244Sgraichen
102459004Shm		/* build complete name of archive directory into dirpart */
102559004Shm		if (*archdirname == '/') {	/* absolute */
102671299Sjedgar			strlcpy(dirpart, archdirname, sizeof(dirpart));
102759004Shm		} else {	/* relative */
102859004Shm			/* get directory part of logfile */
102971299Sjedgar			strlcpy(dirpart, log, sizeof(dirpart));
103059004Shm			if ((p = rindex(dirpart, '/')) == NULL)
103159004Shm				dirpart[0] = '\0';
103259004Shm			else
103359004Shm				*(p + 1) = '\0';
103471299Sjedgar			strlcat(dirpart, archdirname, sizeof(dirpart));
103559004Shm		}
103659004Shm
103759004Shm		/* check if archive directory exists, if not, create it */
103859004Shm		if (lstat(dirpart, &st))
103959004Shm			createdir(dirpart);
104059004Shm
104159004Shm		/* get filename part of logfile */
104259004Shm		if ((p = rindex(log, '/')) == NULL)
104371299Sjedgar			strlcpy(namepart, log, sizeof(namepart));
104459004Shm		else
104571299Sjedgar			strlcpy(namepart, p + 1, sizeof(namepart));
104659004Shm
104759004Shm		/* name of oldest log */
104880646Sobrien		(void) snprintf(file1, sizeof(file1), "%s/%s.%d", dirpart,
104980646Sobrien		    namepart, numdays);
105071299Sjedgar		(void) snprintf(zfile1, sizeof(zfile1), "%s%s", file1,
105171299Sjedgar		    COMPRESS_POSTFIX);
105280638Sobrien		snprintf(jfile1, sizeof(jfile1), "%s%s", file1,
105380638Sobrien		    BZCOMPRESS_POSTFIX);
105459004Shm	} else {
105559004Shm		/* name of oldest log */
105671299Sjedgar		(void) snprintf(file1, sizeof(file1), "%s.%d", log, numdays);
105771299Sjedgar		(void) snprintf(zfile1, sizeof(zfile1), "%s%s", file1,
105871299Sjedgar		    COMPRESS_POSTFIX);
105980638Sobrien		snprintf(jfile1, sizeof(jfile1), "%s%s", file1,
106080638Sobrien		    BZCOMPRESS_POSTFIX);
106159004Shm	}
106259004Shm
106359003Shm	if (noaction) {
1064111967Sgad		printf("\trm -f %s\n", file1);
1065111967Sgad		printf("\trm -f %s\n", zfile1);
1066111967Sgad		printf("\trm -f %s\n", jfile1);
106759003Shm	} else {
106859003Shm		(void) unlink(file1);
106959003Shm		(void) unlink(zfile1);
107080638Sobrien		(void) unlink(jfile1);
107159003Shm	}
107213244Sgraichen
107359003Shm	/* Move down log files */
107418188Sjkh	_numdays = numdays;	/* preserve */
107559003Shm	while (numdays--) {
107659004Shm
107771299Sjedgar		(void) strlcpy(file2, file1, sizeof(file2));
107859004Shm
107959004Shm		if (archtodir)
108080646Sobrien			(void) snprintf(file1, sizeof(file1), "%s/%s.%d",
108180646Sobrien			    dirpart, namepart, numdays);
108259004Shm		else
108380646Sobrien			(void) snprintf(file1, sizeof(file1), "%s.%d", log,
108480646Sobrien			    numdays);
108559004Shm
108671299Sjedgar		(void) strlcpy(zfile1, file1, sizeof(zfile1));
108771299Sjedgar		(void) strlcpy(zfile2, file2, sizeof(zfile2));
108859003Shm		if (lstat(file1, &st)) {
108980646Sobrien			(void) strlcat(zfile1, COMPRESS_POSTFIX,
109080646Sobrien			    sizeof(zfile1));
109180646Sobrien			(void) strlcat(zfile2, COMPRESS_POSTFIX,
109280646Sobrien			    sizeof(zfile2));
109380638Sobrien			if (lstat(zfile1, &st)) {
109480638Sobrien				strlcpy(zfile1, file1, sizeof(zfile1));
109580638Sobrien				strlcpy(zfile2, file2, sizeof(zfile2));
109680638Sobrien				strlcat(zfile1, BZCOMPRESS_POSTFIX,
109780638Sobrien				    sizeof(zfile1));
109880638Sobrien				strlcat(zfile2, BZCOMPRESS_POSTFIX,
109980638Sobrien				    sizeof(zfile2));
110080638Sobrien				if (lstat(zfile1, &st))
110180638Sobrien					continue;
110280638Sobrien			}
110359003Shm		}
110459003Shm		if (noaction) {
1105111967Sgad			printf("\tmv %s %s\n", zfile1, zfile2);
1106111967Sgad			printf("\tchmod %o %s\n", ent->permissions, zfile2);
1107111779Sgad			if (ent->uid != (uid_t)-1 || ent->gid != (gid_t)-1)
1108111967Sgad				printf("\tchown %u:%u %s\n",
1109111779Sgad				    ent->uid, ent->gid, zfile2);
111059003Shm		} else {
111159003Shm			(void) rename(zfile1, zfile2);
1112111780Sgad			if (chmod(zfile2, ent->permissions))
1113111780Sgad				warn("can't chmod %s", file2);
1114111779Sgad			if (ent->uid != (uid_t)-1 || ent->gid != (gid_t)-1)
1115111779Sgad				if (chown(zfile2, ent->uid, ent->gid))
1116111779Sgad					warn("can't chown %s", zfile2);
111759003Shm		}
111859003Shm	}
1119111388Sgad	if (!noaction && !(flags & CE_BINARY)) {
1120111388Sgad		/* Report the trimming to the old log */
1121111779Sgad		(void) log_trim(log, ent);
1122111388Sgad	}
112313244Sgraichen
112418188Sjkh	if (!_numdays) {
112518075Sjkh		if (noaction)
1126111967Sgad			printf("\trm %s\n", log);
112718075Sjkh		else
112859003Shm			(void) unlink(log);
112959003Shm	} else {
113018075Sjkh		if (noaction)
1131111967Sgad			printf("\tmv %s to %s\n", log, file1);
113259004Shm		else {
113359004Shm			if (archtodir)
1134111780Sgad				movefile(log, file1, ent->permissions, ent->uid,
1135111779Sgad				    ent->gid);
113659004Shm			else
113759004Shm				(void) rename(log, file1);
113859004Shm		}
113918075Sjkh	}
114018075Sjkh
1141111781Sgad	/* Now move the new log file into place */
1142111967Sgad	strlcpy(tfile, log, sizeof(tfile));
1143111967Sgad	strlcat(tfile, ".XXXXXX", sizeof(tfile));
1144111967Sgad	if (noaction) {
1145111781Sgad		printf("Start new log...\n");
1146111967Sgad		printf("\tmktemp %s\n", tfile);
1147111967Sgad	} else {
114894352Ssheldonh		mkstemp(tfile);
1149111780Sgad		fd = creat(tfile, ent->permissions);
115059003Shm		if (fd < 0)
115159003Shm			err(1, "can't start new log");
1152111779Sgad		if (ent->uid != (uid_t)-1 || ent->gid != (gid_t)-1)
1153111779Sgad			if (fchown(fd, ent->uid, ent->gid))
1154111779Sgad			    err(1, "can't chown new log file");
115559003Shm		(void) close(fd);
1156111388Sgad		if (!(flags & CE_BINARY)) {
1157111388Sgad			/* Add status message to new log file */
1158111779Sgad			if (log_trim(tfile, ent))
115959003Shm				err(1, "can't add status message to log");
1160111388Sgad		}
116159003Shm	}
1162111967Sgad	if (noaction) {
1163111967Sgad		printf("\tchmod %o %s\n", ent->permissions, tfile);
1164111967Sgad		printf("\tmv %s %s\n", tfile, log);
1165111967Sgad	} else {
1166111780Sgad		(void) chmod(tfile, ent->permissions);
116794352Ssheldonh		if (rename(tfile, log) < 0) {
116894352Ssheldonh			err(1, "can't start new log");
116994352Ssheldonh			(void) unlink(tfile);
117094352Ssheldonh		}
117194352Ssheldonh	}
117225443Sache
1173111966Sgad	/*
1174111966Sgad	 * Find out if there is a process to signal.  If nosignal (-s) was
1175111966Sgad	 * specified, then do not signal any process.  Note that nosignal
1176111966Sgad	 * will trigger a warning message if the rotated logfile needs to
1177111966Sgad	 * be compressed, *unless* -R was specified.  This is because there
1178111966Sgad	 * presumably still are process(es) writing to the old logfile, but
1179111966Sgad	 * we assume that a -sR request comes from a process which writes
1180111966Sgad	 * to the logfile, and as such, that process has already made sure
1181111966Sgad	 * that the logfile is not presently in use.
1182111966Sgad	 */
118325443Sache	need_notification = notified = 0;
1184111779Sgad	if (ent->pid_file != NULL) {
118525443Sache		need_notification = 1;
1186111966Sgad		if (!nosignal)
1187112003Sgad			notified = send_signal(ent);	/* the normal case! */
1188111966Sgad		else if (rotatereq)
1189111966Sgad			need_notification = 0;
119025443Sache	}
1191112003Sgad
119280638Sobrien	if ((flags & CE_COMPACT) || (flags & CE_BZCOMPACT)) {
119325443Sache		if (need_notification && !notified)
119480646Sobrien			warnx(
1195112003Sgad			    "log %s.0 not compressed because daemon(s) not notified",
119680646Sobrien			    log);
119725443Sache		else if (noaction)
1198111967Sgad			if (flags & CE_COMPACT)
1199111967Sgad				printf("\tgzip %s.0\n", log);
1200111967Sgad			else
1201111967Sgad				printf("\tbzip2 %s.0\n", log);
120225443Sache		else {
120325443Sache			if (notified) {
120425443Sache				if (verbose)
1205112003Sgad					printf("small pause to allow daemon(s) to close log\n");
120631460Sache				sleep(10);
120725443Sache			}
120859004Shm			if (archtodir) {
120980646Sobrien				(void) snprintf(file1, sizeof(file1), "%s/%s",
121080646Sobrien				    dirpart, namepart);
121180638Sobrien				if (flags & CE_COMPACT)
1212107916Ssobomax					compress_log(file1,
1213107916Ssobomax					    flags & CE_COMPACTWAIT);
121480638Sobrien				else if (flags & CE_BZCOMPACT)
1215107916Ssobomax					bzcompress_log(file1,
1216107916Ssobomax					    flags & CE_COMPACTWAIT);
121759004Shm			} else {
121880638Sobrien				if (flags & CE_COMPACT)
1219107916Ssobomax					compress_log(log,
1220107916Ssobomax					    flags & CE_COMPACTWAIT);
122180638Sobrien				else if (flags & CE_BZCOMPACT)
1222107916Ssobomax					bzcompress_log(log,
1223107916Ssobomax					    flags & CE_COMPACTWAIT);
122459004Shm			}
122525443Sache		}
122659003Shm	}
122713244Sgraichen}
122813244Sgraichen
122913244Sgraichen/* Log the fact that the logs were turned over */
123059004Shmstatic int
1231111768Sgadlog_trim(const char *log, const struct conf_entry *log_ent)
123213244Sgraichen{
123359003Shm	FILE *f;
1234111388Sgad	const char *xtra;
123559003Shm
123659003Shm	if ((f = fopen(log, "a")) == NULL)
123759003Shm		return (-1);
1238111388Sgad	xtra = "";
1239111768Sgad	if (log_ent->def_cfg)
1240111388Sgad		xtra = " using <default> rule";
1241111772Sgad	if (log_ent->r_reason != NULL)
1242111772Sgad		fprintf(f, "%s %s newsyslog[%d]: logfile turned over%s%s\n",
1243111772Sgad		    daytime, hostname, (int) getpid(), log_ent->r_reason, xtra);
1244111772Sgad	else
1245111772Sgad		fprintf(f, "%s %s newsyslog[%d]: logfile turned over%s\n",
1246111772Sgad		    daytime, hostname, (int) getpid(), xtra);
124759003Shm	if (fclose(f) == EOF)
124859003Shm		err(1, "log_trim: fclose:");
124959003Shm	return (0);
125013244Sgraichen}
125113244Sgraichen
125259004Shm/* Fork of gzip to compress the old log file */
125359004Shmstatic void
1254107916Ssobomaxcompress_log(char *log, int dowait)
125513244Sgraichen{
125659003Shm	pid_t pid;
125771299Sjedgar	char tmp[MAXPATHLEN];
125859003Shm
1259107916Ssobomax	while (dowait && (wait(NULL) > 0 || errno == EINTR))
1260107916Ssobomax		;
126171299Sjedgar	(void) snprintf(tmp, sizeof(tmp), "%s.0", log);
126225443Sache	pid = fork();
126359003Shm	if (pid < 0)
126480638Sobrien		err(1, "gzip fork");
126559003Shm	else if (!pid) {
126679452Sbrian		(void) execl(_PATH_GZIP, _PATH_GZIP, "-f", tmp, (char *)0);
126743071Swollman		err(1, _PATH_GZIP);
126859003Shm	}
126913244Sgraichen}
127013244Sgraichen
127180638Sobrien/* Fork of bzip2 to compress the old log file */
127280638Sobrienstatic void
1273107916Ssobomaxbzcompress_log(char *log, int dowait)
127480638Sobrien{
127580638Sobrien	pid_t pid;
127680638Sobrien	char tmp[MAXPATHLEN];
127780638Sobrien
1278107916Ssobomax	while (dowait && (wait(NULL) > 0 || errno == EINTR))
1279107916Ssobomax		;
128080638Sobrien	snprintf(tmp, sizeof(tmp), "%s.0", log);
128180638Sobrien	pid = fork();
128280638Sobrien	if (pid < 0)
128380638Sobrien		err(1, "bzip2 fork");
128480638Sobrien	else if (!pid) {
128586360Sobrien		execl(_PATH_BZIP2, _PATH_BZIP2, "-f", tmp, (char *)0);
128680638Sobrien		err(1, _PATH_BZIP2);
128780638Sobrien	}
128880638Sobrien}
128980638Sobrien
129013244Sgraichen/* Return size in kilobytes of a file */
129159004Shmstatic int
129259004Shmsizefile(char *file)
129313244Sgraichen{
129459003Shm	struct stat sb;
129513244Sgraichen
129659003Shm	if (stat(file, &sb) < 0)
129759003Shm		return (-1);
129859003Shm	return (kbytes(dbtob(sb.st_blocks)));
129913244Sgraichen}
130013244Sgraichen
130113244Sgraichen/* Return the age of old log file (file.0) */
130259004Shmstatic int
130359004Shmage_old_log(char *file)
130413244Sgraichen{
130559003Shm	struct stat sb;
130659003Shm	char tmp[MAXPATHLEN + sizeof(".0") + sizeof(COMPRESS_POSTFIX) + 1];
130713244Sgraichen
130859004Shm	if (archtodir) {
130959004Shm		char *p;
131059004Shm
131159004Shm		/* build name of archive directory into tmp */
131259004Shm		if (*archdirname == '/') {	/* absolute */
131371299Sjedgar			strlcpy(tmp, archdirname, sizeof(tmp));
131459004Shm		} else {	/* relative */
131559004Shm			/* get directory part of logfile */
131671299Sjedgar			strlcpy(tmp, file, sizeof(tmp));
131759004Shm			if ((p = rindex(tmp, '/')) == NULL)
131859004Shm				tmp[0] = '\0';
131959004Shm			else
132059004Shm				*(p + 1) = '\0';
132171299Sjedgar			strlcat(tmp, archdirname, sizeof(tmp));
132259004Shm		}
132359004Shm
132471299Sjedgar		strlcat(tmp, "/", sizeof(tmp));
132559004Shm
132659004Shm		/* get filename part of logfile */
132759004Shm		if ((p = rindex(file, '/')) == NULL)
132871299Sjedgar			strlcat(tmp, file, sizeof(tmp));
132959004Shm		else
133071299Sjedgar			strlcat(tmp, p + 1, sizeof(tmp));
133159004Shm	} else {
133271299Sjedgar		(void) strlcpy(tmp, file, sizeof(tmp));
133359004Shm	}
133459004Shm
133559003Shm	if (stat(strcat(tmp, ".0"), &sb) < 0)
133659003Shm		if (stat(strcat(tmp, COMPRESS_POSTFIX), &sb) < 0)
133759003Shm			return (-1);
1338111781Sgad	return ((int)(timenow - sb.st_mtime + 1800) / 3600);
133913244Sgraichen}
134013244Sgraichen
134113244Sgraichen/* Skip Over Blanks */
1342111820Sgadstatic char *
134359004Shmsob(char *p)
134413244Sgraichen{
134559003Shm	while (p && *p && isspace(*p))
134659003Shm		p++;
134759003Shm	return (p);
134813244Sgraichen}
134913244Sgraichen
135013244Sgraichen/* Skip Over Non-Blanks */
1351111820Sgadstatic char *
135259004Shmson(char *p)
135313244Sgraichen{
135459003Shm	while (p && *p && !isspace(*p))
135559003Shm		p++;
135659003Shm	return (p);
135713244Sgraichen}
135843071Swollman
135943071Swollman/*
136059004Shm * Parse a limited subset of ISO 8601. The specific format is as follows:
136143071Swollman *
136259004Shm * [CC[YY[MM[DD]]]][THH[MM[SS]]]	(where `T' is the literal letter)
136343071Swollman *
136459004Shm * We don't accept a timezone specification; missing fields (including timezone)
136559004Shm * are defaulted to the current date but time zero.
136643071Swollman */
136743071Swollmanstatic time_t
136893659Scjcparse8601(char *s, char *errline)
136943071Swollman{
137059003Shm	char *t;
137193659Scjc	time_t tsecs;
137259003Shm	struct tm tm, *tmp;
137359003Shm	u_long ul;
137443071Swollman
137543071Swollman	tmp = localtime(&timenow);
137643071Swollman	tm = *tmp;
137743071Swollman
137843071Swollman	tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
137943071Swollman
138043071Swollman	ul = strtoul(s, &t, 10);
138143071Swollman	if (*t != '\0' && *t != 'T')
1382111392Sgad		return (-1);
138343071Swollman
138443071Swollman	/*
138559003Shm	 * Now t points either to the end of the string (if no time was
138659003Shm	 * provided) or to the letter `T' which separates date and time in
138759003Shm	 * ISO 8601.  The pointer arithmetic is the same for either case.
138843071Swollman	 */
138943071Swollman	switch (t - s) {
139043071Swollman	case 8:
139143071Swollman		tm.tm_year = ((ul / 1000000) - 19) * 100;
139243071Swollman		ul = ul % 1000000;
139343071Swollman	case 6:
139480666Swollman		tm.tm_year -= tm.tm_year % 100;
139543071Swollman		tm.tm_year += ul / 10000;
139643071Swollman		ul = ul % 10000;
139743071Swollman	case 4:
139843071Swollman		tm.tm_mon = (ul / 100) - 1;
139943071Swollman		ul = ul % 100;
140043071Swollman	case 2:
140143071Swollman		tm.tm_mday = ul;
140243071Swollman	case 0:
140343071Swollman		break;
140443071Swollman	default:
1405111392Sgad		return (-1);
140643071Swollman	}
140743071Swollman
140843071Swollman	/* sanity check */
140943071Swollman	if (tm.tm_year < 70 || tm.tm_mon < 0 || tm.tm_mon > 12
141043071Swollman	    || tm.tm_mday < 1 || tm.tm_mday > 31)
1411111392Sgad		return (-1);
141243071Swollman
141343071Swollman	if (*t != '\0') {
141443071Swollman		s = ++t;
141543071Swollman		ul = strtoul(s, &t, 10);
141643071Swollman		if (*t != '\0' && !isspace(*t))
1417111392Sgad			return (-1);
141843071Swollman
141943071Swollman		switch (t - s) {
142043071Swollman		case 6:
142143071Swollman			tm.tm_sec = ul % 100;
142243071Swollman			ul /= 100;
142343071Swollman		case 4:
142443071Swollman			tm.tm_min = ul % 100;
142543071Swollman			ul /= 100;
142643071Swollman		case 2:
142743071Swollman			tm.tm_hour = ul;
142843071Swollman		case 0:
142943071Swollman			break;
143043071Swollman		default:
1431111392Sgad			return (-1);
143243071Swollman		}
143343071Swollman
143443071Swollman		/* sanity check */
143543071Swollman		if (tm.tm_sec < 0 || tm.tm_sec > 60 || tm.tm_min < 0
143643071Swollman		    || tm.tm_min > 59 || tm.tm_hour < 0 || tm.tm_hour > 23)
1437111392Sgad			return (-1);
143843071Swollman	}
143993659Scjc	if ((tsecs = mktime(&tm)) == -1)
144099209Smaxim		errx(1, "nonexistent time:\n%s", errline);
1441111392Sgad	return (tsecs);
144243071Swollman}
144359004Shm
144459004Shm/* physically move file */
144559004Shmstatic void
1446111779Sgadmovefile(char *from, char *to, int perm, uid_t owner_uid, gid_t group_gid)
144759004Shm{
144859004Shm	FILE *src, *dst;
144959004Shm	int c;
145059004Shm
145159004Shm	if ((src = fopen(from, "r")) == NULL)
145259004Shm		err(1, "can't fopen %s for reading", from);
145359004Shm	if ((dst = fopen(to, "w")) == NULL)
145459004Shm		err(1, "can't fopen %s for writing", to);
1455111779Sgad	if (owner_uid != (uid_t)-1 || group_gid != (gid_t)-1) {
1456111779Sgad		if (fchown(fileno(dst), owner_uid, group_gid))
1457111779Sgad			err(1, "can't fchown %s", to);
1458111779Sgad	}
145959004Shm	if (fchmod(fileno(dst), perm))
146059004Shm		err(1, "can't fchmod %s", to);
146159004Shm
146259004Shm	while ((c = getc(src)) != EOF) {
146359004Shm		if ((putc(c, dst)) == EOF)
146459004Shm			err(1, "error writing to %s", to);
146559004Shm	}
146659004Shm
146759004Shm	if (ferror(src))
146859004Shm		err(1, "error reading from %s", from);
146959004Shm	if ((fclose(src)) != 0)
147059004Shm		err(1, "can't fclose %s", to);
147159004Shm	if ((fclose(dst)) != 0)
147259004Shm		err(1, "can't fclose %s", from);
147359004Shm	if ((unlink(from)) != 0)
147459004Shm		err(1, "can't unlink %s", from);
147559004Shm}
147659004Shm
147759004Shm/* create one or more directory components of a path */
147859004Shmstatic void
147959004Shmcreatedir(char *dirpart)
148059004Shm{
1481111398Sgad	int res;
148259004Shm	char *s, *d;
148371299Sjedgar	char mkdirpath[MAXPATHLEN];
148459004Shm	struct stat st;
148559004Shm
148659004Shm	s = dirpart;
148759004Shm	d = mkdirpath;
148859004Shm
148959004Shm	for (;;) {
149059004Shm		*d++ = *s++;
1491111398Sgad		if (*s != '/' && *s != '\0')
1492111398Sgad			continue;
1493111398Sgad		*d = '\0';
1494111398Sgad		res = lstat(mkdirpath, &st);
1495111398Sgad		if (res != 0) {
1496111398Sgad			if (noaction) {
1497111967Sgad				printf("\tmkdir %s\n", mkdirpath);
1498111398Sgad			} else {
1499111398Sgad				res = mkdir(mkdirpath, 0755);
1500111398Sgad				if (res != 0)
1501111398Sgad					err(1, "Error on mkdir(\"%s\") for -a",
1502111398Sgad					    mkdirpath);
1503111398Sgad			}
150459004Shm		}
150559004Shm		if (*s == '\0')
150659004Shm			break;
150759004Shm	}
1508111398Sgad	if (verbose)
1509111398Sgad		printf("created directory '%s' for -a\n", dirpart);
151059004Shm}
151159004Shm
151259004Shm/*-
151359004Shm * Parse a cyclic time specification, the format is as follows:
151459004Shm *
151559004Shm *	[Dhh] or [Wd[Dhh]] or [Mdd[Dhh]]
151659004Shm *
151759004Shm * to rotate a logfile cyclic at
151859004Shm *
151959004Shm *	- every day (D) within a specific hour (hh)	(hh = 0...23)
152059004Shm *	- once a week (W) at a specific day (d)     OR	(d = 0..6, 0 = Sunday)
152159004Shm *	- once a month (M) at a specific day (d)	(d = 1..31,l|L)
152259004Shm *
152359004Shm * We don't accept a timezone specification; missing fields
152459004Shm * are defaulted to the current date but time zero.
152559004Shm */
152659004Shmstatic time_t
152793659ScjcparseDWM(char *s, char *errline)
152859004Shm{
152959004Shm	char *t;
153093659Scjc	time_t tsecs;
153159004Shm	struct tm tm, *tmp;
153280742Sobrien	long l;
153359004Shm	int nd;
153459004Shm	static int mtab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
153559004Shm	int WMseen = 0;
153659004Shm	int Dseen = 0;
153759004Shm
153859004Shm	tmp = localtime(&timenow);
153959004Shm	tm = *tmp;
154059004Shm
154159004Shm	/* set no. of days per month */
154259004Shm
154359004Shm	nd = mtab[tm.tm_mon];
154459004Shm
154559004Shm	if (tm.tm_mon == 1) {
154659004Shm		if (((tm.tm_year + 1900) % 4 == 0) &&
154759004Shm		    ((tm.tm_year + 1900) % 100 != 0) &&
154859004Shm		    ((tm.tm_year + 1900) % 400 == 0)) {
154959004Shm			nd++;	/* leap year, 29 days in february */
155059004Shm		}
155159004Shm	}
155259004Shm	tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
155359004Shm
155459004Shm	for (;;) {
155559004Shm		switch (*s) {
155659004Shm		case 'D':
155759004Shm			if (Dseen)
1558111392Sgad				return (-1);
155959004Shm			Dseen++;
156059004Shm			s++;
156180742Sobrien			l = strtol(s, &t, 10);
156280742Sobrien			if (l < 0 || l > 23)
1563111392Sgad				return (-1);
156480742Sobrien			tm.tm_hour = l;
156559004Shm			break;
156659004Shm
156759004Shm		case 'W':
156859004Shm			if (WMseen)
1569111392Sgad				return (-1);
157059004Shm			WMseen++;
157159004Shm			s++;
157280742Sobrien			l = strtol(s, &t, 10);
157380742Sobrien			if (l < 0 || l > 6)
1574111392Sgad				return (-1);
157580742Sobrien			if (l != tm.tm_wday) {
157659004Shm				int save;
157759004Shm
157880742Sobrien				if (l < tm.tm_wday) {
157959004Shm					save = 6 - tm.tm_wday;
158080742Sobrien					save += (l + 1);
158159004Shm				} else {
158280742Sobrien					save = l - tm.tm_wday;
158359004Shm				}
158459004Shm
158559004Shm				tm.tm_mday += save;
158659004Shm
158759004Shm				if (tm.tm_mday > nd) {
158859004Shm					tm.tm_mon++;
158959004Shm					tm.tm_mday = tm.tm_mday - nd;
159059004Shm				}
159159004Shm			}
159259004Shm			break;
159359004Shm
159459004Shm		case 'M':
159559004Shm			if (WMseen)
1596111392Sgad				return (-1);
159759004Shm			WMseen++;
159859004Shm			s++;
159959004Shm			if (tolower(*s) == 'l') {
160059004Shm				tm.tm_mday = nd;
160159004Shm				s++;
160259004Shm				t = s;
160359004Shm			} else {
160480742Sobrien				l = strtol(s, &t, 10);
160580742Sobrien				if (l < 1 || l > 31)
1606111392Sgad					return (-1);
160759004Shm
160880742Sobrien				if (l > nd)
1609111392Sgad					return (-1);
161080742Sobrien				tm.tm_mday = l;
161159004Shm			}
161259004Shm			break;
161359004Shm
161459004Shm		default:
161559004Shm			return (-1);
161659004Shm			break;
161759004Shm		}
161859004Shm
161959004Shm		if (*t == '\0' || isspace(*t))
162059004Shm			break;
162159004Shm		else
162259004Shm			s = t;
162359004Shm	}
162493659Scjc	if ((tsecs = mktime(&tm)) == -1)
162599209Smaxim		errx(1, "nonexistent time:\n%s", errline);
1626111392Sgad	return (tsecs);
162759004Shm}
1628