newsyslog.c revision 111772
11844Swollman/*
250476Speter * This file contains changes from the Open Software Foundation.
31844Swollman */
41638Srgrimes
594940Sru/*
61638Srgrimes * Copyright 1988, 1989 by the Massachusetts Institute of Technology
742915Sjdp *
842915Sjdp * Permission to use, copy, modify, and distribute this software and its
942915Sjdp * documentation for any purpose and without fee is hereby granted, provided
1042915Sjdp * that the above copyright notice appear in all copies and that both that
11139106Sru * copyright notice and this permission notice appear in supporting
1242915Sjdp * documentation, and that the names of M.I.T. and the M.I.T. S.I.P.B. not be
1342915Sjdp * used in advertising or publicity pertaining to distribution of the
1442915Sjdp * software without specific, written prior permission. M.I.T. and the M.I.T.
15129024Sdes * S.I.P.B. make no representations about the suitability of this software
16129024Sdes * for any purpose.  It is provided "as is" without express or implied
1729141Speter * warranty.
18129024Sdes *
19129024Sdes */
20129024Sdes
21125119Sru/*
22100332Sru * newsyslog - roll over selected logs at the appropriate time, keeping the a
23100332Sru * specified number of backup files around.
2442915Sjdp */
2542915Sjdp
2629141Speter#ifndef lint
27119607Srustatic const char rcsid[] =
28117034Sgordon"$FreeBSD: head/usr.sbin/newsyslog/newsyslog.c 111772 2003-03-02 23:23:11Z gad $";
29119607Sru#endif	/* not lint */
30117034Sgordon
312827Sjkh#define OSF
322827Sjkh#ifndef COMPRESS_POSTFIX
332827Sjkh#define COMPRESS_POSTFIX ".gz"
342827Sjkh#endif
352827Sjkh#ifndef	BZCOMPRESS_POSTFIX
361638Srgrimes#define	BZCOMPRESS_POSTFIX ".bz2"
372827Sjkh#endif
381638Srgrimes
3918529Sbde#include <sys/param.h>
4018529Sbde#include <sys/stat.h>
411638Srgrimes#include <sys/wait.h>
4242450Sjdp
431638Srgrimes#include <ctype.h>
44117173Sru#include <err.h>
451638Srgrimes#include <errno.h>
4696512Sru#include <fcntl.h>
4796512Sru#include <glob.h>
4896512Sru#include <grp.h>
4996512Sru#include <paths.h>
5096512Sru#include <pwd.h>
5196512Sru#include <signal.h>
5296512Sru#include <stdio.h>
5396512Sru#include <stdlib.h>
54126890Strhodes#include <string.h>
55126890Strhodes#include <time.h>
56126890Strhodes#include <unistd.h>
57126890Strhodes
58126890Strhodes#include "pathnames.h"
59126890Strhodes
601638Srgrimes#define kbytes(size)  (((size) + 1023) >> 10)
61126890Strhodes
621638Srgrimes#ifdef _IBMR2
6342450Sjdp/* Calculates (db * DEV_BSIZE) */
641844Swollman#define dbtob(db)  ((unsigned)(db) << UBSHIFT)
651844Swollman#endif
6636673Sdt
67126890Strhodes/*
681844Swollman * Bit-values for the 'flags' parsed from a config-file entry.
6942450Sjdp */
701844Swollman#define CE_COMPACT	0x0001	/* Compact the achived log files with gzip. */
711844Swollman#define CE_BZCOMPACT	0x0002	/* Compact the achived log files with bzip2. */
721844Swollman#define	CE_COMPACTWAIT	0x0004	/* wait until compressing one file finishes */
73127027Strhodes				/*    before starting the next step. */
741844Swollman#define CE_BINARY	0x0008	/* Logfile is in binary, do not add status */
7542450Sjdp				/*    messages to logfile(s) when rotating. */
761844Swollman#define	CE_NOSIGNAL	0x0010	/* There is no process to signal when */
771844Swollman				/*    trimming this file. */
7836054Sbde#define	CE_TRIMAT	0x0020	/* trim file at a specific time. */
7936054Sbde#define	CE_GLOB		0x0040	/* name of the log is file name pattern. */
8036054Sbde
8142450Sjdp#define NONE -1
8236054Sbde
8336054Sbdestruct conf_entry {
84117173Sru	char *log;		/* Name of the log */
85117159Sru	char *pid_file;		/* PID file */
861638Srgrimes	char *r_reason;		/* The reason this file is being rotated */
87117173Sru	int rotate;		/* Non-zero if this file should be rotated */
88117173Sru	int uid;		/* Owner of log */
89117173Sru	int gid;		/* Group of log */
90117173Sru	int numlogs;		/* Number of logs to keep */
91117173Sru	int size;		/* Size cutoff to trigger trimming the log */
92117173Sru	int hours;		/* Hours between log trimming */
93117173Sru	time_t trim_at;		/* Specific time to do trimming */
941844Swollman	int permissions;	/* File permissions on the log */
95117122Sru	int flags;		/* CE_COMPACT, CE_BZCOMPACT, CE_BINARY */
961844Swollman	int sig;		/* Signal to send */
9742450Sjdp	int def_cfg;		/* Using the <default> rule for this file */
98117122Sru	struct conf_entry *next;/* Linked list pointer */
991844Swollman};
10096512Sru
1011638Srgrimes#define DEFAULT_MARKER "<default>"
102156772Sdeischen
103156772Sdeischenint archtodir = 0;		/* Archive old logfiles to other directory */
104156772Sdeischenint verbose = 0;		/* Print out what's going on */
105156772Sdeischenint needroot = 1;		/* Root privs are necessary */
106156772Sdeischenint noaction = 0;		/* Don't do anything, just show it */
107156772Sdeischenint nosignal;			/* Do not send any signals */
108156772Sdeischenint force = 0;			/* Force the trim no matter what */
109156772Sdeischenint rotatereq = 0;		/* -R = Always rotate the file(s) as given */
110156772Sdeischen				/*    on the command (this also requires   */
111156772Sdeischen				/*    that a list of files *are* given on  */
112156772Sdeischen				/*    the run command). */
113156772Sdeischenchar *requestor;		/* The name given on a -R request */
114156772Sdeischenchar *archdirname;		/* Directory path to old logfiles archive */
115156772Sdeischenconst char *conf = _PATH_CONF;	/* Configuration file to use */
116156772Sdeischentime_t timenow;
117156772Sdeischen
118156772Sdeischen#define MIN_PID         5
119156772Sdeischen#define MAX_PID		99999	/* was lower, see /usr/include/sys/proc.h */
120156772Sdeischenchar hostname[MAXHOSTNAMELEN];	/* hostname */
121156772Sdeischenchar daytime[16];		/* timenow in human readable form */
122156772Sdeischen
123156772Sdeischenstatic struct conf_entry *parse_file(char **files);
124156772Sdeischenstatic char *sob(char *p);
125156772Sdeischenstatic char *son(char *p);
126156772Sdeischenstatic char *missing_field(char *p, char *errline);
127156772Sdeischenstatic void do_entry(struct conf_entry * ent);
128156772Sdeischenstatic void free_entry(struct conf_entry *ent);
129156772Sdeischenstatic struct conf_entry *init_entry(const char *fname,
130156772Sdeischen		struct conf_entry *src_entry);
131156772Sdeischenstatic void PRS(int argc, char **argv);
132156772Sdeischenstatic void usage(void);
133156772Sdeischenstatic void dotrim(const struct conf_entry *trim_ent, char *log,
134156772Sdeischen		int numdays, int flags, int perm, int owner_uid,
135156772Sdeischen		int group_gid, int sig);
136156772Sdeischenstatic int log_trim(const char *log, const struct conf_entry *log_ent);
137156772Sdeischenstatic void compress_log(char *log, int dowait);
138156772Sdeischenstatic void bzcompress_log(char *log, int dowait);
139156772Sdeischenstatic int sizefile(char *file);
140156772Sdeischenstatic int age_old_log(char *file);
141156772Sdeischenstatic pid_t get_pid(const char *pid_file);
14299362Srustatic time_t parse8601(char *s, char *errline);
14399362Srustatic void movefile(char *from, char *to, int perm, int owner_uid,
14499362Sru		int group_gid);
14599362Srustatic void createdir(char *dirpart);
14696512Srustatic time_t parseDWM(char *s, char *errline);
14796512Sru
1481638Srgrimes/*
14996512Sru * All the following are defined to work on an 'int', in the
15096512Sru * range 0 to 255, plus EOF.  Define wrappers which can take
15196512Sru * values of type 'char', either signed or unsigned.
15296512Sru */
15396512Sru#define isprintch(Anychar)    isprint(((int) Anychar) & 255)
15499362Sru#define isspacech(Anychar)    isspace(((int) Anychar) & 255)
1551638Srgrimes#define tolowerch(Anychar)    tolower(((int) Anychar) & 255)
15696512Sru
15795114Sobrienint
158156854Srumain(int argc, char **argv)
15996512Sru{
16096512Sru	struct conf_entry *p, *q;
16195306Sru	char *savglob;
16296512Sru	glob_t pglob;
16396512Sru	int i;
16496512Sru
16596512Sru	PRS(argc, argv);
16696512Sru	if (needroot && getuid() && geteuid())
16774805Sru		errx(1, "must have root privs");
1681844Swollman	p = q = parse_file(argv + optind);
16999362Sru
17099362Sru	while (p) {
17196512Sru		if ((p->flags & CE_GLOB) == 0) {
17299362Sru			do_entry(p);
1731844Swollman		} else {
17496512Sru			if (glob(p->log, GLOB_NOCHECK, NULL, &pglob) != 0) {
17596512Sru				warn("can't expand pattern: %s", p->log);
1761638Srgrimes			} else {
17742915Sjdp				savglob = p->log;
17842915Sjdp				for (i = 0; i < pglob.gl_matchc; i++) {
17996512Sru					p->log = pglob.gl_pathv[i];
18042915Sjdp					do_entry(p);
18196512Sru				}
18242915Sjdp				globfree(&pglob);
18396343Sobrien				p->log = savglob;
18496512Sru			}
18591011Sru		}
18628945Speter		p = p->next;
1871844Swollman		free_entry(q);
188156813Sru		q = p;
18996512Sru	}
19096512Sru	while (wait(NULL) > 0 || errno == EINTR)
19196512Sru		;
1922353Sbde	return (0);
19396512Sru}
19496512Sru
19596512Srustatic struct conf_entry *
1963859Sbdeinit_entry(const char *fname, struct conf_entry *src_entry)
1971844Swollman{
198139106Sru	struct conf_entry *tempwork;
19996512Sru
20096512Sru	if (verbose > 4)
20196512Sru		printf("\t--> [creating entry for %s]\n", fname);
20296512Sru
20392491Smarkm	tempwork = malloc(sizeof(struct conf_entry));
20496512Sru	if (tempwork == NULL)
20596512Sru		err(1, "malloc of conf_entry for %s", fname);
20692491Smarkm
20792491Smarkm	tempwork->log = strdup(fname);
2081638Srgrimes	if (tempwork->log == NULL)
209144893Sharti		err(1, "strdup for %s", fname);
21096512Sru
21196512Sru	if (src_entry != NULL) {
21296512Sru		tempwork->pid_file = NULL;
213156813Sru		if (src_entry->pid_file)
21496512Sru			tempwork->pid_file = strdup(src_entry->pid_file);
2151638Srgrimes		tempwork->r_reason = NULL;
2161638Srgrimes		tempwork->rotate = 0;
21734179Sbde		tempwork->uid = src_entry->uid;
21824750Sbde		tempwork->gid = src_entry->gid;
21942450Sjdp		tempwork->numlogs = src_entry->numlogs;
22024750Sbde		tempwork->size = src_entry->size;
22124750Sbde		tempwork->hours = src_entry->hours;
222139107Sru		tempwork->trim_at = src_entry->trim_at;
22331809Sbde		tempwork->permissions = src_entry->permissions;
22442915Sjdp		tempwork->flags = src_entry->flags;
22527910Sasami		tempwork->sig = src_entry->sig;
22628945Speter		tempwork->def_cfg = src_entry->def_cfg;
2271638Srgrimes	} else {
2281638Srgrimes		/* Initialize as a "do-nothing" entry */
2291638Srgrimes		tempwork->pid_file = NULL;
230136019Sru		tempwork->r_reason = NULL;
231139111Sru		tempwork->rotate = 0;
2322298Swollman		tempwork->uid = NONE;
2332298Swollman		tempwork->gid = NONE;
234136019Sru		tempwork->numlogs = 1;
235136019Sru		tempwork->size = -1;
2362298Swollman		tempwork->hours = -1;
23749328Shoek		tempwork->trim_at = (time_t)0;
23849328Shoek		tempwork->permissions = 0;
23949328Shoek		tempwork->flags = 0;
24049328Shoek		tempwork->sig = SIGHUP;
24156971Sru		tempwork->def_cfg = 0;
24249328Shoek	}
24349328Shoek	tempwork->next = NULL;
24449328Shoek
24549328Shoek	return (tempwork);
24699362Sru}
24795306Sru
24899343Srustatic void
24995306Srufree_entry(struct conf_entry *ent)
250139110Sru{
25192980Sdes
25249328Shoek	if (ent == NULL)
25396512Sru		return;
254156854Sru
25592980Sdes	if (ent->log != NULL) {
25649328Shoek		if (verbose > 4)
2571638Srgrimes			printf("\t--> [freeing entry for %s]\n", ent->log);
258116144Sobrien		free(ent->log);
259100872Sru		ent->log = NULL;
26049328Shoek	}
26142915Sjdp
26242915Sjdp	if (ent->pid_file != NULL) {
263119846Sru		free(ent->pid_file);
264119846Sru		ent->pid_file = NULL;
265119846Sru	}
266119846Sru
267119730Speter	if (ent->r_reason != NULL) {
268119846Sru		free(ent->r_reason);
269119846Sru		ent->r_reason = NULL;
270119846Sru	}
2711844Swollman
27228945Speter	free(ent);
273119730Speter}
274119846Sru
275156813Srustatic void
276100872Srudo_entry(struct conf_entry * ent)
27749328Shoek{
2781844Swollman#define REASON_MAX	80
279139106Sru	int size, modtime;
280100872Sru	char temp_reason[REASON_MAX];
28196462Sru
28296462Sru	if (verbose) {
283144893Sharti		if (ent->flags & CE_COMPACT)
28496462Sru			printf("%s <%dZ>: ", ent->log, ent->numlogs);
285141503Sphantom		else if (ent->flags & CE_BZCOMPACT)
28697769Sru			printf("%s <%dJ>: ", ent->log, ent->numlogs);
28796668Sru		else
28899256Sru			printf("%s <%d>: ", ent->log, ent->numlogs);
28996462Sru	}
290156813Sru	size = sizefile(ent->log);
29196164Sru	modtime = age_old_log(ent->log);
29299343Sru	ent->rotate = 0;
29396162Sru	if (size < 0) {
29496162Sru		if (verbose)
2951638Srgrimes			printf("does not exist.\n");
2961638Srgrimes	} else {
2971638Srgrimes		if (ent->flags & CE_TRIMAT && !force && !rotatereq) {
29895306Sru			if (timenow < ent->trim_at
299103713Smarkm			    || difftime(timenow, ent->trim_at) >= 60 * 60) {
3001638Srgrimes				if (verbose)
3011638Srgrimes					printf("--> will trim at %s",
302156813Sru					    ctime(&ent->trim_at));
3031638Srgrimes				return;
30474842Sru			} else if (verbose && ent->hours <= 0) {
3051844Swollman				printf("--> time is up\n");
3061844Swollman			}
30734092Sbde		}
30899362Sru		if (verbose && (ent->size > 0))
30996512Sru			printf("size (Kb): %d [%d] ", size, ent->size);
31099362Sru		if (verbose && (ent->hours > 0))
311124637Sru			printf(" age (hr): %d [%d] ", modtime, ent->hours);
312124637Sru
313124637Sru		/*
31434092Sbde		 * Figure out if this logfile needs to be rotated.
31599362Sru		 */
31699362Sru		temp_reason[0] = '\0';
31799362Sru		if (rotatereq) {
318124637Sru			ent->rotate = 1;
319124637Sru			snprintf(temp_reason, REASON_MAX, " due to -R from %s",
320124637Sru			    requestor);
32196512Sru		} else if (force) {
32299362Sru			ent->rotate = 1;
32334092Sbde			snprintf(temp_reason, REASON_MAX, " due to -F request");
324100457Sru		} else if ((ent->size > 0) && (size >= ent->size)) {
325100457Sru			ent->rotate = 1;
326100457Sru			snprintf(temp_reason, REASON_MAX, " due to size>%dK",
327100457Sru			    ent->size);
328100457Sru		} else if (ent->hours <= 0 && (ent->flags & CE_TRIMAT)) {
329100457Sru			ent->rotate = 1;
330100457Sru		} else if ((ent->hours > 0) && ((modtime >= ent->hours) ||
331100457Sru		    (modtime < 0))) {
332100457Sru			ent->rotate = 1;
333156854Sru		}
334100457Sru
335100457Sru		/*
336100457Sru		 * If the file needs to be rotated, then rotate it.
337100457Sru		 */
338100457Sru		if (ent->rotate) {
339100457Sru			if (temp_reason[0] != '\0')
340100457Sru				ent->r_reason = strdup(temp_reason);
341100457Sru			if (verbose)
342100457Sru				printf("--> trimming log....\n");
343100457Sru			if (noaction && !verbose) {
344100457Sru				if (ent->flags & CE_COMPACT)
345100457Sru					printf("%s <%dZ>: trimming\n",
346100457Sru					    ent->log, ent->numlogs);
347100457Sru				else if (ent->flags & CE_BZCOMPACT)
348100457Sru					printf("%s <%dJ>: trimming\n",
349100457Sru					    ent->log, ent->numlogs);
350100457Sru				else
351144893Sharti					printf("%s <%d>: trimming\n",
352100457Sru					    ent->log, ent->numlogs);
353100457Sru			}
354100457Sru			dotrim(ent, ent->log, ent->numlogs, ent->flags,
355100457Sru			    ent->permissions, ent->uid, ent->gid, ent->sig);
356100457Sru		} else {
357100457Sru			if (verbose)
358100457Sru				printf("--> skipping\n");
359100457Sru		}
36016663Sjkh	}
36176861Skris#undef REASON_MAX
36276861Skris}
363
364static void
365PRS(int argc, char **argv)
366{
367	int c;
368	char *p;
369
370	timenow = time((time_t *) 0);
371	(void)strncpy(daytime, ctime(&timenow) + 4, 15);
372	daytime[15] = '\0';
373
374	/* Let's get our hostname */
375	(void) gethostname(hostname, sizeof(hostname));
376
377	/* Truncate domain */
378	if ((p = strchr(hostname, '.'))) {
379		*p = '\0';
380	}
381
382	/* Parse command line options. */
383	while ((c = getopt(argc, argv, "a:f:nrsvFR:")) != -1)
384		switch (c) {
385		case 'a':
386			archtodir++;
387			archdirname = optarg;
388			break;
389		case 'f':
390			conf = optarg;
391			break;
392		case 'n':
393			noaction++;
394			break;
395		case 'r':
396			needroot = 0;
397			break;
398		case 's':
399			nosignal = 1;
400			break;
401		case 'v':
402			verbose++;
403			break;
404		case 'F':
405			force++;
406			break;
407		case 'R':
408			rotatereq++;
409			requestor = strdup(optarg);
410			break;
411		default:
412			usage();
413			/* NOTREACHED */
414		}
415
416	if (rotatereq) {
417		if (optind == argc) {
418			warnx("At least one filename must be given when -R is specified.");
419			usage();
420			/* NOTREACHED */
421		}
422		/* Make sure "requestor" value is safe for a syslog message. */
423		for (p = requestor; *p != '\0'; p++) {
424			if (!isprintch(*p) && (*p != '\t'))
425				*p = '.';
426		}
427	}
428}
429
430static void
431usage(void)
432{
433
434	fprintf(stderr,
435	    "usage: newsyslog [-Fnrsv] [-a directory] [-f config-file]\n"
436	    "                 [ [-R requestor] filename ... ]\n");
437	exit(1);
438}
439
440/*
441 * Parse a configuration file and return a linked list of all the logs to
442 * process
443 */
444static struct conf_entry *
445parse_file(char **files)
446{
447	FILE *f;
448	char line[BUFSIZ], *parse, *q;
449	char *cp, *errline, *group;
450	char **given;
451	struct conf_entry *defconf, *first, *working, *worklist;
452	struct passwd *pass;
453	struct group *grp;
454	int eol;
455
456	defconf = first = working = worklist = NULL;
457
458	if (strcmp(conf, "-"))
459		f = fopen(conf, "r");
460	else
461		f = stdin;
462	if (!f)
463		err(1, "%s", conf);
464	while (fgets(line, BUFSIZ, f)) {
465		if ((line[0] == '\n') || (line[0] == '#') ||
466		    (strlen(line) == 0))
467			continue;
468		errline = strdup(line);
469		for (cp = line + 1; *cp != '\0'; cp++) {
470			if (*cp != '#')
471				continue;
472			if (*(cp - 1) == '\\') {
473				strcpy(cp - 1, cp);
474				cp--;
475				continue;
476			}
477			*cp = '\0';
478			break;
479		}
480
481		q = parse = missing_field(sob(line), errline);
482		parse = son(line);
483		if (!*parse)
484			errx(1, "malformed line (missing fields):\n%s",
485			    errline);
486		*parse = '\0';
487
488		/*
489		 * If newsyslog was run with a list of specific filenames,
490		 * then this line of the config file should be skipped if
491		 * it is NOT one of those given files (except that we do
492		 * want any line that defines the <default> action).
493		 *
494		 * XXX - note that CE_GLOB processing is *NOT* done when
495		 *       trying to match a filename given on the command!
496		 */
497		if (*files) {
498			if (strcasecmp(DEFAULT_MARKER, q) != 0) {
499				for (given = files; *given; ++given) {
500					if (strcmp(*given, q) == 0)
501						break;
502				}
503				if (!*given)
504					continue;
505			}
506			if (verbose > 2)
507				printf("\t+ Matched entry %s\n", q);
508		} else {
509			/*
510			 * If no files were specified on the command line,
511			 * then we can skip any line which defines the
512			 * default action.
513			 */
514			if (strcasecmp(DEFAULT_MARKER, q) == 0) {
515				if (verbose > 2)
516					printf("\t+ Ignoring entry for %s\n",
517					    q);
518				continue;
519			}
520		}
521
522		working = init_entry(q, NULL);
523		if (strcasecmp(DEFAULT_MARKER, q) == 0) {
524			if (defconf != NULL) {
525				warnx("Ignoring duplicate entry for %s!", q);
526				free_entry(working);
527				continue;
528			}
529			defconf = working;
530		} else {
531			if (!first)
532				first = working;
533			else
534				worklist->next = working;
535			worklist = working;
536		}
537
538		q = parse = missing_field(sob(++parse), errline);
539		parse = son(parse);
540		if (!*parse)
541			errx(1, "malformed line (missing fields):\n%s",
542			    errline);
543		*parse = '\0';
544		if ((group = strchr(q, ':')) != NULL ||
545		    (group = strrchr(q, '.')) != NULL) {
546			*group++ = '\0';
547			if (*q) {
548				if (!(isnumber(*q))) {
549					if ((pass = getpwnam(q)) == NULL)
550						errx(1,
551				     "error in config file; unknown user:\n%s",
552						    errline);
553					working->uid = pass->pw_uid;
554				} else
555					working->uid = atoi(q);
556			} else
557				working->uid = NONE;
558
559			q = group;
560			if (*q) {
561				if (!(isnumber(*q))) {
562					if ((grp = getgrnam(q)) == NULL)
563						errx(1,
564				    "error in config file; unknown group:\n%s",
565						    errline);
566					working->gid = grp->gr_gid;
567				} else
568					working->gid = atoi(q);
569			} else
570				working->gid = NONE;
571
572			q = parse = missing_field(sob(++parse), errline);
573			parse = son(parse);
574			if (!*parse)
575				errx(1, "malformed line (missing fields):\n%s",
576				    errline);
577			*parse = '\0';
578		} else
579			working->uid = working->gid = NONE;
580
581		if (!sscanf(q, "%o", &working->permissions))
582			errx(1, "error in config file; bad permissions:\n%s",
583			    errline);
584
585		q = parse = missing_field(sob(++parse), errline);
586		parse = son(parse);
587		if (!*parse)
588			errx(1, "malformed line (missing fields):\n%s",
589			    errline);
590		*parse = '\0';
591		if (!sscanf(q, "%d", &working->numlogs) || working->numlogs < 0)
592			errx(1, "error in config file; bad value for count of logs to save:\n%s",
593			    errline);
594
595		q = parse = missing_field(sob(++parse), errline);
596		parse = son(parse);
597		if (!*parse)
598			errx(1, "malformed line (missing fields):\n%s",
599			    errline);
600		*parse = '\0';
601		if (isdigit(*q))
602			working->size = atoi(q);
603		else
604			working->size = -1;
605
606		working->flags = 0;
607		q = parse = missing_field(sob(++parse), errline);
608		parse = son(parse);
609		eol = !*parse;
610		*parse = '\0';
611		{
612			char *ep;
613			u_long ul;
614
615			ul = strtoul(q, &ep, 10);
616			if (ep == q)
617				working->hours = 0;
618			else if (*ep == '*')
619				working->hours = -1;
620			else if (ul > INT_MAX)
621				errx(1, "interval is too large:\n%s", errline);
622			else
623				working->hours = ul;
624
625			if (*ep != '\0' && *ep != '@' && *ep != '*' &&
626			    *ep != '$')
627				errx(1, "malformed interval/at:\n%s", errline);
628			if (*ep == '@') {
629				if ((working->trim_at = parse8601(ep + 1, errline))
630				    == (time_t) - 1)
631					errx(1, "malformed at:\n%s", errline);
632				working->flags |= CE_TRIMAT;
633			} else if (*ep == '$') {
634				if ((working->trim_at = parseDWM(ep + 1, errline))
635				    == (time_t) - 1)
636					errx(1, "malformed at:\n%s", errline);
637				working->flags |= CE_TRIMAT;
638			}
639		}
640
641		if (eol)
642			q = NULL;
643		else {
644			q = parse = sob(++parse);	/* Optional field */
645			parse = son(parse);
646			if (!*parse)
647				eol = 1;
648			*parse = '\0';
649		}
650
651		for (; q && *q && !isspacech(*q); q++) {
652			switch (tolowerch(*q)) {
653			case 'b':
654				working->flags |= CE_BINARY;
655				break;
656			case 'c':
657				/*
658				 * netbsd uses 'c' for "create".  We will
659				 * temporarily accept it for 'g', because
660				 * earlier freebsd versions had a typo
661				 * of ('G' || 'c')...
662				 */
663				warnx("Assuming 'g' for 'c' in flags for line:\n%s",
664				    errline);
665				/* FALLTHROUGH */
666			case 'g':
667				working->flags |= CE_GLOB;
668				break;
669			case 'j':
670				working->flags |= CE_BZCOMPACT;
671				break;
672			case 'n':
673				working->flags |= CE_NOSIGNAL;
674				break;
675			case 'w':
676				working->flags |= CE_COMPACTWAIT;
677				break;
678			case 'z':
679				working->flags |= CE_COMPACT;
680				break;
681			case '-':
682				break;
683			default:
684				errx(1, "illegal flag in config file -- %c",
685				    *q);
686			}
687		}
688
689		if (eol)
690			q = NULL;
691		else {
692			q = parse = sob(++parse);	/* Optional field */
693			parse = son(parse);
694			if (!*parse)
695				eol = 1;
696			*parse = '\0';
697		}
698
699		working->pid_file = NULL;
700		if (q && *q) {
701			if (*q == '/')
702				working->pid_file = strdup(q);
703			else if (isdigit(*q))
704				goto got_sig;
705			else
706				errx(1,
707			"illegal pid file or signal number in config file:\n%s",
708				    errline);
709		}
710		if (eol)
711			q = NULL;
712		else {
713			q = parse = sob(++parse);	/* Optional field */
714			*(parse = son(parse)) = '\0';
715		}
716
717		working->sig = SIGHUP;
718		if (q && *q) {
719			if (isdigit(*q)) {
720		got_sig:
721				working->sig = atoi(q);
722			} else {
723		err_sig:
724				errx(1,
725				    "illegal signal number in config file:\n%s",
726				    errline);
727			}
728			if (working->sig < 1 || working->sig >= NSIG)
729				goto err_sig;
730		}
731
732		/*
733		 * Finish figuring out what pid-file to use (if any) in
734		 * later processing if this logfile needs to be rotated.
735		 */
736		if ((working->flags & CE_NOSIGNAL) == CE_NOSIGNAL) {
737			/*
738			 * This config-entry specified 'n' for nosignal,
739			 * see if it also specified an explicit pid_file.
740			 * This would be a pretty pointless combination.
741			 */
742			if (working->pid_file != NULL) {
743				warnx("Ignoring '%s' because flag 'n' was specified in line:\n%s",
744				    working->pid_file, errline);
745				free(working->pid_file);
746				working->pid_file = NULL;
747			}
748		} else if (nosignal) {
749			/*
750			 * While this entry might usually signal some
751			 * process via the pid-file, newsyslog was run
752			 * with '-s', so quietly ignore the pid-file.
753			 */
754			if (working->pid_file != NULL) {
755				free(working->pid_file);
756				working->pid_file = NULL;
757			}
758		} else if (working->pid_file == NULL) {
759			/*
760			 * This entry did not specify the 'n' flag, which
761			 * means it should signal syslogd unless it had
762			 * specified some other pid-file.  But we only
763			 * try to notify syslog if we are root
764			 */
765			if (needroot)
766				working->pid_file = strdup(_PATH_SYSLOGPID);
767		}
768
769		free(errline);
770		errline = NULL;
771	}
772	(void) fclose(f);
773
774	/*
775	 * The entire config file has been processed.  If there were
776	 * no specific files given on the run command, then the work
777	 * of this routine is done.
778	 */
779	if (*files == NULL)
780		return (first);
781
782	/*
783	 * If the program was given a specific list of files to process,
784	 * it may be that some of those files were not listed in the
785	 * config file.  Those unlisted files should get the default
786	 * rotation action.  First, create the default-rotation action
787	 * if none was found in the config file.
788	 */
789	if (defconf == NULL) {
790		working = init_entry(DEFAULT_MARKER, NULL);
791		working->numlogs = 3;
792		working->size = 50;
793		working->permissions = S_IRUSR|S_IWUSR;
794		defconf = working;
795	}
796
797	for (given = files; *given; ++given) {
798		for (working = first; working; working = working->next) {
799			if (strcmp(*given, working->log) == 0)
800				break;
801		}
802		if (working != NULL)
803			continue;
804		if (verbose > 2)
805			printf("\t+ No entry for %s  (will use %s)\n",
806			    *given, DEFAULT_MARKER);
807		/*
808		 * This given file was not found in the config file.
809		 * Add another item on to our work list, based on the
810		 * default entry.
811		 */
812		working = init_entry(*given, defconf);
813		if (!first)
814			first = working;
815		else
816			worklist->next = working;
817		/* This is a file that was *not* found in config file */
818		working->def_cfg = 1;
819		worklist = working;
820	}
821
822	free_entry(defconf);
823	return (first);
824}
825
826static char *
827missing_field(char *p, char *errline)
828{
829
830	if (!p || !*p)
831		errx(1, "missing field in config file:\n%s", errline);
832	return (p);
833}
834
835static void
836dotrim(const struct conf_entry *trim_ent, char *log, int numdays, int flags,
837    int perm, int owner_uid, int group_gid, int sig)
838{
839	char dirpart[MAXPATHLEN], namepart[MAXPATHLEN];
840	char file1[MAXPATHLEN], file2[MAXPATHLEN];
841	char zfile1[MAXPATHLEN], zfile2[MAXPATHLEN];
842	char jfile1[MAXPATHLEN];
843	char tfile[MAXPATHLEN];
844	int notified, need_notification, fd, _numdays;
845	struct stat st;
846	pid_t pid;
847
848#ifdef _IBMR2
849	/*
850	 * AIX 3.1 has a broken fchown- if the owner_uid is -1, it will
851	 * actually change it to be owned by uid -1, instead of leaving it
852	 * as is, as it is supposed to.
853	 */
854	if (owner_uid == -1)
855		owner_uid = geteuid();
856#endif
857
858	if (archtodir) {
859		char *p;
860
861		/* build complete name of archive directory into dirpart */
862		if (*archdirname == '/') {	/* absolute */
863			strlcpy(dirpart, archdirname, sizeof(dirpart));
864		} else {	/* relative */
865			/* get directory part of logfile */
866			strlcpy(dirpart, log, sizeof(dirpart));
867			if ((p = rindex(dirpart, '/')) == NULL)
868				dirpart[0] = '\0';
869			else
870				*(p + 1) = '\0';
871			strlcat(dirpart, archdirname, sizeof(dirpart));
872		}
873
874		/* check if archive directory exists, if not, create it */
875		if (lstat(dirpart, &st))
876			createdir(dirpart);
877
878		/* get filename part of logfile */
879		if ((p = rindex(log, '/')) == NULL)
880			strlcpy(namepart, log, sizeof(namepart));
881		else
882			strlcpy(namepart, p + 1, sizeof(namepart));
883
884		/* name of oldest log */
885		(void) snprintf(file1, sizeof(file1), "%s/%s.%d", dirpart,
886		    namepart, numdays);
887		(void) snprintf(zfile1, sizeof(zfile1), "%s%s", file1,
888		    COMPRESS_POSTFIX);
889		snprintf(jfile1, sizeof(jfile1), "%s%s", file1,
890		    BZCOMPRESS_POSTFIX);
891	} else {
892		/* name of oldest log */
893		(void) snprintf(file1, sizeof(file1), "%s.%d", log, numdays);
894		(void) snprintf(zfile1, sizeof(zfile1), "%s%s", file1,
895		    COMPRESS_POSTFIX);
896		snprintf(jfile1, sizeof(jfile1), "%s%s", file1,
897		    BZCOMPRESS_POSTFIX);
898	}
899
900	if (noaction) {
901		printf("rm -f %s\n", file1);
902		printf("rm -f %s\n", zfile1);
903		printf("rm -f %s\n", jfile1);
904	} else {
905		(void) unlink(file1);
906		(void) unlink(zfile1);
907		(void) unlink(jfile1);
908	}
909
910	/* Move down log files */
911	_numdays = numdays;	/* preserve */
912	while (numdays--) {
913
914		(void) strlcpy(file2, file1, sizeof(file2));
915
916		if (archtodir)
917			(void) snprintf(file1, sizeof(file1), "%s/%s.%d",
918			    dirpart, namepart, numdays);
919		else
920			(void) snprintf(file1, sizeof(file1), "%s.%d", log,
921			    numdays);
922
923		(void) strlcpy(zfile1, file1, sizeof(zfile1));
924		(void) strlcpy(zfile2, file2, sizeof(zfile2));
925		if (lstat(file1, &st)) {
926			(void) strlcat(zfile1, COMPRESS_POSTFIX,
927			    sizeof(zfile1));
928			(void) strlcat(zfile2, COMPRESS_POSTFIX,
929			    sizeof(zfile2));
930			if (lstat(zfile1, &st)) {
931				strlcpy(zfile1, file1, sizeof(zfile1));
932				strlcpy(zfile2, file2, sizeof(zfile2));
933				strlcat(zfile1, BZCOMPRESS_POSTFIX,
934				    sizeof(zfile1));
935				strlcat(zfile2, BZCOMPRESS_POSTFIX,
936				    sizeof(zfile2));
937				if (lstat(zfile1, &st))
938					continue;
939			}
940		}
941		if (noaction) {
942			printf("mv %s %s\n", zfile1, zfile2);
943			printf("chmod %o %s\n", perm, zfile2);
944			printf("chown %d:%d %s\n",
945			    owner_uid, group_gid, zfile2);
946		} else {
947			(void) rename(zfile1, zfile2);
948			(void) chmod(zfile2, perm);
949			(void) chown(zfile2, owner_uid, group_gid);
950		}
951	}
952	if (!noaction && !(flags & CE_BINARY)) {
953		/* Report the trimming to the old log */
954		(void) log_trim(log, trim_ent);
955	}
956
957	if (!_numdays) {
958		if (noaction)
959			printf("rm %s\n", log);
960		else
961			(void) unlink(log);
962	} else {
963		if (noaction)
964			printf("mv %s to %s\n", log, file1);
965		else {
966			if (archtodir)
967				movefile(log, file1, perm, owner_uid,
968				    group_gid);
969			else
970				(void) rename(log, file1);
971		}
972	}
973
974	if (noaction)
975		printf("Start new log...");
976	else {
977		strlcpy(tfile, log, sizeof(tfile));
978		strlcat(tfile, ".XXXXXX", sizeof(tfile));
979		mkstemp(tfile);
980		fd = creat(tfile, perm);
981		if (fd < 0)
982			err(1, "can't start new log");
983		if (fchown(fd, owner_uid, group_gid))
984			err(1, "can't chmod new log file");
985		(void) close(fd);
986		if (!(flags & CE_BINARY)) {
987			/* Add status message to new log file */
988			if (log_trim(tfile, trim_ent))
989				err(1, "can't add status message to log");
990		}
991	}
992	if (noaction)
993		printf("chmod %o %s...\n", perm, log);
994	else {
995		(void) chmod(tfile, perm);
996		if (rename(tfile, log) < 0) {
997			err(1, "can't start new log");
998			(void) unlink(tfile);
999		}
1000	}
1001
1002	pid = 0;
1003	need_notification = notified = 0;
1004	if (trim_ent->pid_file != NULL) {
1005		need_notification = 1;
1006		pid = get_pid(trim_ent->pid_file);
1007	}
1008	if (pid) {
1009		if (noaction) {
1010			notified = 1;
1011			printf("kill -%d %d\n", sig, (int) pid);
1012		} else if (kill(pid, sig))
1013			warn("can't notify daemon, pid %d", (int) pid);
1014		else {
1015			notified = 1;
1016			if (verbose)
1017				printf("daemon pid %d notified\n", (int) pid);
1018		}
1019	}
1020	if ((flags & CE_COMPACT) || (flags & CE_BZCOMPACT)) {
1021		if (need_notification && !notified)
1022			warnx(
1023			    "log %s not compressed because daemon not notified",
1024			    log);
1025		else if (noaction)
1026			printf("Compress %s.0\n", log);
1027		else {
1028			if (notified) {
1029				if (verbose)
1030					printf("small pause to allow daemon to close log\n");
1031				sleep(10);
1032			}
1033			if (archtodir) {
1034				(void) snprintf(file1, sizeof(file1), "%s/%s",
1035				    dirpart, namepart);
1036				if (flags & CE_COMPACT)
1037					compress_log(file1,
1038					    flags & CE_COMPACTWAIT);
1039				else if (flags & CE_BZCOMPACT)
1040					bzcompress_log(file1,
1041					    flags & CE_COMPACTWAIT);
1042			} else {
1043				if (flags & CE_COMPACT)
1044					compress_log(log,
1045					    flags & CE_COMPACTWAIT);
1046				else if (flags & CE_BZCOMPACT)
1047					bzcompress_log(log,
1048					    flags & CE_COMPACTWAIT);
1049			}
1050		}
1051	}
1052}
1053
1054/* Log the fact that the logs were turned over */
1055static int
1056log_trim(const char *log, const struct conf_entry *log_ent)
1057{
1058	FILE *f;
1059	const char *xtra;
1060
1061	if ((f = fopen(log, "a")) == NULL)
1062		return (-1);
1063	xtra = "";
1064	if (log_ent->def_cfg)
1065		xtra = " using <default> rule";
1066	if (log_ent->r_reason != NULL)
1067		fprintf(f, "%s %s newsyslog[%d]: logfile turned over%s%s\n",
1068		    daytime, hostname, (int) getpid(), log_ent->r_reason, xtra);
1069	else
1070		fprintf(f, "%s %s newsyslog[%d]: logfile turned over%s\n",
1071		    daytime, hostname, (int) getpid(), xtra);
1072	if (fclose(f) == EOF)
1073		err(1, "log_trim: fclose:");
1074	return (0);
1075}
1076
1077/* Fork of gzip to compress the old log file */
1078static void
1079compress_log(char *log, int dowait)
1080{
1081	pid_t pid;
1082	char tmp[MAXPATHLEN];
1083
1084	while (dowait && (wait(NULL) > 0 || errno == EINTR))
1085		;
1086	(void) snprintf(tmp, sizeof(tmp), "%s.0", log);
1087	pid = fork();
1088	if (pid < 0)
1089		err(1, "gzip fork");
1090	else if (!pid) {
1091		(void) execl(_PATH_GZIP, _PATH_GZIP, "-f", tmp, (char *)0);
1092		err(1, _PATH_GZIP);
1093	}
1094}
1095
1096/* Fork of bzip2 to compress the old log file */
1097static void
1098bzcompress_log(char *log, int dowait)
1099{
1100	pid_t pid;
1101	char tmp[MAXPATHLEN];
1102
1103	while (dowait && (wait(NULL) > 0 || errno == EINTR))
1104		;
1105	snprintf(tmp, sizeof(tmp), "%s.0", log);
1106	pid = fork();
1107	if (pid < 0)
1108		err(1, "bzip2 fork");
1109	else if (!pid) {
1110		execl(_PATH_BZIP2, _PATH_BZIP2, "-f", tmp, (char *)0);
1111		err(1, _PATH_BZIP2);
1112	}
1113}
1114
1115/* Return size in kilobytes of a file */
1116static int
1117sizefile(char *file)
1118{
1119	struct stat sb;
1120
1121	if (stat(file, &sb) < 0)
1122		return (-1);
1123	return (kbytes(dbtob(sb.st_blocks)));
1124}
1125
1126/* Return the age of old log file (file.0) */
1127static int
1128age_old_log(char *file)
1129{
1130	struct stat sb;
1131	char tmp[MAXPATHLEN + sizeof(".0") + sizeof(COMPRESS_POSTFIX) + 1];
1132
1133	if (archtodir) {
1134		char *p;
1135
1136		/* build name of archive directory into tmp */
1137		if (*archdirname == '/') {	/* absolute */
1138			strlcpy(tmp, archdirname, sizeof(tmp));
1139		} else {	/* relative */
1140			/* get directory part of logfile */
1141			strlcpy(tmp, file, sizeof(tmp));
1142			if ((p = rindex(tmp, '/')) == NULL)
1143				tmp[0] = '\0';
1144			else
1145				*(p + 1) = '\0';
1146			strlcat(tmp, archdirname, sizeof(tmp));
1147		}
1148
1149		strlcat(tmp, "/", sizeof(tmp));
1150
1151		/* get filename part of logfile */
1152		if ((p = rindex(file, '/')) == NULL)
1153			strlcat(tmp, file, sizeof(tmp));
1154		else
1155			strlcat(tmp, p + 1, sizeof(tmp));
1156	} else {
1157		(void) strlcpy(tmp, file, sizeof(tmp));
1158	}
1159
1160	if (stat(strcat(tmp, ".0"), &sb) < 0)
1161		if (stat(strcat(tmp, COMPRESS_POSTFIX), &sb) < 0)
1162			return (-1);
1163	return ((int) (timenow - sb.st_mtime + 1800) / 3600);
1164}
1165
1166static pid_t
1167get_pid(const char *pid_file)
1168{
1169	FILE *f;
1170	char line[BUFSIZ];
1171	pid_t pid = 0;
1172
1173	if ((f = fopen(pid_file, "r")) == NULL)
1174		warn("can't open %s pid file to restart a daemon",
1175		    pid_file);
1176	else {
1177		if (fgets(line, BUFSIZ, f)) {
1178			pid = atol(line);
1179			if (pid < MIN_PID || pid > MAX_PID) {
1180				warnx("preposterous process number: %d",
1181				   (int)pid);
1182				pid = 0;
1183			}
1184		} else
1185			warn("can't read %s pid file to restart a daemon",
1186			    pid_file);
1187		(void) fclose(f);
1188	}
1189	return (pid);
1190}
1191
1192/* Skip Over Blanks */
1193char *
1194sob(char *p)
1195{
1196	while (p && *p && isspace(*p))
1197		p++;
1198	return (p);
1199}
1200
1201/* Skip Over Non-Blanks */
1202char *
1203son(char *p)
1204{
1205	while (p && *p && !isspace(*p))
1206		p++;
1207	return (p);
1208}
1209
1210/*
1211 * Parse a limited subset of ISO 8601. The specific format is as follows:
1212 *
1213 * [CC[YY[MM[DD]]]][THH[MM[SS]]]	(where `T' is the literal letter)
1214 *
1215 * We don't accept a timezone specification; missing fields (including timezone)
1216 * are defaulted to the current date but time zero.
1217 */
1218static time_t
1219parse8601(char *s, char *errline)
1220{
1221	char *t;
1222	time_t tsecs;
1223	struct tm tm, *tmp;
1224	u_long ul;
1225
1226	tmp = localtime(&timenow);
1227	tm = *tmp;
1228
1229	tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
1230
1231	ul = strtoul(s, &t, 10);
1232	if (*t != '\0' && *t != 'T')
1233		return (-1);
1234
1235	/*
1236	 * Now t points either to the end of the string (if no time was
1237	 * provided) or to the letter `T' which separates date and time in
1238	 * ISO 8601.  The pointer arithmetic is the same for either case.
1239	 */
1240	switch (t - s) {
1241	case 8:
1242		tm.tm_year = ((ul / 1000000) - 19) * 100;
1243		ul = ul % 1000000;
1244	case 6:
1245		tm.tm_year -= tm.tm_year % 100;
1246		tm.tm_year += ul / 10000;
1247		ul = ul % 10000;
1248	case 4:
1249		tm.tm_mon = (ul / 100) - 1;
1250		ul = ul % 100;
1251	case 2:
1252		tm.tm_mday = ul;
1253	case 0:
1254		break;
1255	default:
1256		return (-1);
1257	}
1258
1259	/* sanity check */
1260	if (tm.tm_year < 70 || tm.tm_mon < 0 || tm.tm_mon > 12
1261	    || tm.tm_mday < 1 || tm.tm_mday > 31)
1262		return (-1);
1263
1264	if (*t != '\0') {
1265		s = ++t;
1266		ul = strtoul(s, &t, 10);
1267		if (*t != '\0' && !isspace(*t))
1268			return (-1);
1269
1270		switch (t - s) {
1271		case 6:
1272			tm.tm_sec = ul % 100;
1273			ul /= 100;
1274		case 4:
1275			tm.tm_min = ul % 100;
1276			ul /= 100;
1277		case 2:
1278			tm.tm_hour = ul;
1279		case 0:
1280			break;
1281		default:
1282			return (-1);
1283		}
1284
1285		/* sanity check */
1286		if (tm.tm_sec < 0 || tm.tm_sec > 60 || tm.tm_min < 0
1287		    || tm.tm_min > 59 || tm.tm_hour < 0 || tm.tm_hour > 23)
1288			return (-1);
1289	}
1290	if ((tsecs = mktime(&tm)) == -1)
1291		errx(1, "nonexistent time:\n%s", errline);
1292	return (tsecs);
1293}
1294
1295/* physically move file */
1296static void
1297movefile(char *from, char *to, int perm, int owner_uid, int group_gid)
1298{
1299	FILE *src, *dst;
1300	int c;
1301
1302	if ((src = fopen(from, "r")) == NULL)
1303		err(1, "can't fopen %s for reading", from);
1304	if ((dst = fopen(to, "w")) == NULL)
1305		err(1, "can't fopen %s for writing", to);
1306	if (fchown(fileno(dst), owner_uid, group_gid))
1307		err(1, "can't fchown %s", to);
1308	if (fchmod(fileno(dst), perm))
1309		err(1, "can't fchmod %s", to);
1310
1311	while ((c = getc(src)) != EOF) {
1312		if ((putc(c, dst)) == EOF)
1313			err(1, "error writing to %s", to);
1314	}
1315
1316	if (ferror(src))
1317		err(1, "error reading from %s", from);
1318	if ((fclose(src)) != 0)
1319		err(1, "can't fclose %s", to);
1320	if ((fclose(dst)) != 0)
1321		err(1, "can't fclose %s", from);
1322	if ((unlink(from)) != 0)
1323		err(1, "can't unlink %s", from);
1324}
1325
1326/* create one or more directory components of a path */
1327static void
1328createdir(char *dirpart)
1329{
1330	int res;
1331	char *s, *d;
1332	char mkdirpath[MAXPATHLEN];
1333	struct stat st;
1334
1335	s = dirpart;
1336	d = mkdirpath;
1337
1338	for (;;) {
1339		*d++ = *s++;
1340		if (*s != '/' && *s != '\0')
1341			continue;
1342		*d = '\0';
1343		res = lstat(mkdirpath, &st);
1344		if (res != 0) {
1345			if (noaction) {
1346				printf("mkdir %s\n", mkdirpath);
1347			} else {
1348				res = mkdir(mkdirpath, 0755);
1349				if (res != 0)
1350					err(1, "Error on mkdir(\"%s\") for -a",
1351					    mkdirpath);
1352			}
1353		}
1354		if (*s == '\0')
1355			break;
1356	}
1357	if (verbose)
1358		printf("created directory '%s' for -a\n", dirpart);
1359}
1360
1361/*-
1362 * Parse a cyclic time specification, the format is as follows:
1363 *
1364 *	[Dhh] or [Wd[Dhh]] or [Mdd[Dhh]]
1365 *
1366 * to rotate a logfile cyclic at
1367 *
1368 *	- every day (D) within a specific hour (hh)	(hh = 0...23)
1369 *	- once a week (W) at a specific day (d)     OR	(d = 0..6, 0 = Sunday)
1370 *	- once a month (M) at a specific day (d)	(d = 1..31,l|L)
1371 *
1372 * We don't accept a timezone specification; missing fields
1373 * are defaulted to the current date but time zero.
1374 */
1375static time_t
1376parseDWM(char *s, char *errline)
1377{
1378	char *t;
1379	time_t tsecs;
1380	struct tm tm, *tmp;
1381	long l;
1382	int nd;
1383	static int mtab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
1384	int WMseen = 0;
1385	int Dseen = 0;
1386
1387	tmp = localtime(&timenow);
1388	tm = *tmp;
1389
1390	/* set no. of days per month */
1391
1392	nd = mtab[tm.tm_mon];
1393
1394	if (tm.tm_mon == 1) {
1395		if (((tm.tm_year + 1900) % 4 == 0) &&
1396		    ((tm.tm_year + 1900) % 100 != 0) &&
1397		    ((tm.tm_year + 1900) % 400 == 0)) {
1398			nd++;	/* leap year, 29 days in february */
1399		}
1400	}
1401	tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
1402
1403	for (;;) {
1404		switch (*s) {
1405		case 'D':
1406			if (Dseen)
1407				return (-1);
1408			Dseen++;
1409			s++;
1410			l = strtol(s, &t, 10);
1411			if (l < 0 || l > 23)
1412				return (-1);
1413			tm.tm_hour = l;
1414			break;
1415
1416		case 'W':
1417			if (WMseen)
1418				return (-1);
1419			WMseen++;
1420			s++;
1421			l = strtol(s, &t, 10);
1422			if (l < 0 || l > 6)
1423				return (-1);
1424			if (l != tm.tm_wday) {
1425				int save;
1426
1427				if (l < tm.tm_wday) {
1428					save = 6 - tm.tm_wday;
1429					save += (l + 1);
1430				} else {
1431					save = l - tm.tm_wday;
1432				}
1433
1434				tm.tm_mday += save;
1435
1436				if (tm.tm_mday > nd) {
1437					tm.tm_mon++;
1438					tm.tm_mday = tm.tm_mday - nd;
1439				}
1440			}
1441			break;
1442
1443		case 'M':
1444			if (WMseen)
1445				return (-1);
1446			WMseen++;
1447			s++;
1448			if (tolower(*s) == 'l') {
1449				tm.tm_mday = nd;
1450				s++;
1451				t = s;
1452			} else {
1453				l = strtol(s, &t, 10);
1454				if (l < 1 || l > 31)
1455					return (-1);
1456
1457				if (l > nd)
1458					return (-1);
1459				tm.tm_mday = l;
1460			}
1461			break;
1462
1463		default:
1464			return (-1);
1465			break;
1466		}
1467
1468		if (*t == '\0' || isspace(*t))
1469			break;
1470		else
1471			s = t;
1472	}
1473	if ((tsecs = mktime(&tm)) == -1)
1474		errx(1, "nonexistent time:\n%s", errline);
1475	return (tsecs);
1476}
1477