newsyslog.c revision 111398
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 111398 2003-02-24 02:09:02Z 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>
47106905Ssobomax#include <glob.h>
4830160Scharnier#include <grp.h>
4943071Swollman#include <paths.h>
5030160Scharnier#include <pwd.h>
5130160Scharnier#include <signal.h>
5213244Sgraichen#include <stdio.h>
5313244Sgraichen#include <stdlib.h>
5413244Sgraichen#include <string.h>
5543071Swollman#include <time.h>
5616240Salex#include <unistd.h>
5713244Sgraichen
5843071Swollman#include "pathnames.h"
5943071Swollman
6013244Sgraichen#define kbytes(size)  (((size) + 1023) >> 10)
6159004Shm
6213244Sgraichen#ifdef _IBMR2
6313244Sgraichen/* Calculates (db * DEV_BSIZE) */
6459003Shm#define dbtob(db)  ((unsigned)(db) << UBSHIFT)
6513244Sgraichen#endif
6613244Sgraichen
6759003Shm#define CE_COMPACT 1		/* Compact the achived log files */
6859003Shm#define CE_BINARY  2		/* Logfile is in binary, don't add */
6980638Sobrien#define CE_BZCOMPACT 8		/* Compact the achived log files with bzip2 */
7059004Shm				/*  status messages */
7143071Swollman#define	CE_TRIMAT  4		/* trim at a specific time */
72106905Ssobomax#define	CE_GLOB    16		/* name of the log is file name pattern */
73107916Ssobomax#define	CE_COMPACTWAIT 32	/* wait till compressing finishes before */
74107916Ssobomax				/* starting the next one */
7543071Swollman
7613244Sgraichen#define NONE -1
7759003Shm
7813244Sgraichenstruct conf_entry {
7959003Shm	char *log;		/* Name of the log */
8059003Shm	char *pid_file;		/* PID file */
8159003Shm	int uid;		/* Owner of log */
8259003Shm	int gid;		/* Group of log */
8359003Shm	int numlogs;		/* Number of logs to keep */
8459003Shm	int size;		/* Size cutoff to trigger trimming the log */
8559003Shm	int hours;		/* Hours between log trimming */
8659003Shm	time_t trim_at;		/* Specific time to do trimming */
8759003Shm	int permissions;	/* File permissions on the log */
8880646Sobrien	int flags;		/* CE_COMPACT, CE_BZCOMPACT, CE_BINARY */
8959003Shm	int sig;		/* Signal to send */
90111388Sgad	int def_cfg;		/* Using the <default> rule for this file */
9159003Shm	struct conf_entry *next;/* Linked list pointer */
9213244Sgraichen};
9313244Sgraichen
94111388Sgad#define DEFAULT_MARKER "<default>"
95111388Sgad
9659004Shmint archtodir = 0;		/* Archive old logfiles to other directory */
9759003Shmint verbose = 0;		/* Print out what's going on */
9859003Shmint needroot = 1;		/* Root privs are necessary */
9959003Shmint noaction = 0;		/* Don't do anything, just show it */
10059003Shmint force = 0;			/* Force the trim no matter what */
10159004Shmchar *archdirname;		/* Directory path to old logfiles archive */
10280640Sobrienconst char *conf = _PATH_CONF;	/* Configuration file to use */
10359003Shmtime_t timenow;
10459003Shm
10525443Sache#define MIN_PID         5
10659003Shm#define MAX_PID		99999	/* was lower, see /usr/include/sys/proc.h */
10771299Sjedgarchar hostname[MAXHOSTNAMELEN];	/* hostname */
108108164Strhodeschar daytime[16];		/* timenow in human readable form */
10913244Sgraichen
11060373Sdesstatic struct conf_entry *parse_file(char **files);
11116240Salexstatic char *sob(char *p);
11216240Salexstatic char *son(char *p);
11359003Shmstatic char *missing_field(char *p, char *errline);
11459003Shmstatic void do_entry(struct conf_entry * ent);
115111388Sgadstatic void free_entry(struct conf_entry *ent);
116111388Sgadstatic struct conf_entry *init_entry(const char *fname,
117111388Sgad		struct conf_entry *src_entry);
11859003Shmstatic void PRS(int argc, char **argv);
11980640Sobrienstatic void usage(void);
12080646Sobrienstatic void dotrim(char *log, const char *pid_file, int numdays, int falgs,
121111388Sgad		int perm, int owner_uid, int group_gid, int sig, int def_cfg);
122111388Sgadstatic int log_trim(char *log, int def_cfg);
123107916Ssobomaxstatic void compress_log(char *log, int dowait);
124107916Ssobomaxstatic void bzcompress_log(char *log, int dowait);
12516240Salexstatic int sizefile(char *file);
12616240Salexstatic int age_old_log(char *file);
12780640Sobrienstatic pid_t get_pid(const char *pid_file);
12893659Scjcstatic time_t parse8601(char *s, char *errline);
12980646Sobrienstatic void movefile(char *from, char *to, int perm, int owner_uid,
13080646Sobrien		int group_gid);
13159004Shmstatic void createdir(char *dirpart);
13293659Scjcstatic time_t parseDWM(char *s, char *errline);
13313244Sgraichen
13459004Shmint
13559004Shmmain(int argc, char **argv)
13613244Sgraichen{
13759003Shm	struct conf_entry *p, *q;
138106905Ssobomax	glob_t pglob;
139106905Ssobomax	int i;
14025443Sache
14159003Shm	PRS(argc, argv);
14259003Shm	if (needroot && getuid() && geteuid())
14359003Shm		errx(1, "must have root privs");
14460373Sdes	p = q = parse_file(argv + optind);
14559003Shm
14659003Shm	while (p) {
147106905Ssobomax		if ((p->flags & CE_GLOB) == 0) {
148106905Ssobomax			do_entry(p);
149106905Ssobomax		} else {
150106905Ssobomax			if (glob(p->log, GLOB_NOCHECK, NULL, &pglob) != 0) {
151106905Ssobomax				warn("can't expand pattern: %s", p->log);
152106905Ssobomax			} else {
153106905Ssobomax				for (i = 0; i < pglob.gl_matchc; i++) {
154106905Ssobomax					p->log = pglob.gl_pathv[i];
155106905Ssobomax					do_entry(p);
156106905Ssobomax				}
157106905Ssobomax				globfree(&pglob);
158106905Ssobomax			}
159106905Ssobomax		}
16059003Shm		p = p->next;
161111388Sgad		free_entry(q);
16259003Shm		q = p;
16359003Shm	}
16495999Smaxim	while (wait(NULL) > 0 || errno == EINTR)
16595999Smaxim		;
16659003Shm	return (0);
16713244Sgraichen}
16813244Sgraichen
169111388Sgadstatic struct conf_entry *
170111388Sgadinit_entry(const char *fname, struct conf_entry *src_entry)
171111388Sgad{
172111388Sgad	struct conf_entry *tempwork;
173111388Sgad
174111388Sgad	if (verbose > 4)
175111388Sgad		printf("\t--> [creating entry for %s]\n", fname);
176111388Sgad
177111388Sgad	tempwork = malloc(sizeof(struct conf_entry));
178111388Sgad	if (tempwork == NULL)
179111388Sgad		err(1, "malloc of conf_entry for %s", fname);
180111388Sgad
181111388Sgad	tempwork->log = strdup(fname);
182111388Sgad	if (tempwork->log == NULL)
183111388Sgad		err(1, "strdup for %s", fname);
184111388Sgad
185111388Sgad	if (src_entry != NULL) {
186111388Sgad		tempwork->pid_file = NULL;
187111388Sgad		if (src_entry->pid_file)
188111388Sgad			tempwork->pid_file = strdup(src_entry->pid_file);
189111388Sgad		tempwork->uid = src_entry->uid;
190111388Sgad		tempwork->gid = src_entry->gid;
191111388Sgad		tempwork->numlogs = src_entry->numlogs;
192111388Sgad		tempwork->size = src_entry->size;
193111388Sgad		tempwork->hours = src_entry->hours;
194111388Sgad		tempwork->trim_at = src_entry->trim_at;
195111388Sgad		tempwork->permissions = src_entry->permissions;
196111388Sgad		tempwork->flags = src_entry->flags;
197111388Sgad		tempwork->sig = src_entry->sig;
198111388Sgad		tempwork->def_cfg = src_entry->def_cfg;
199111388Sgad	} else {
200111388Sgad		/* Initialize as a "do-nothing" entry */
201111388Sgad		tempwork->pid_file = NULL;
202111388Sgad		tempwork->uid = NONE;
203111388Sgad		tempwork->gid = NONE;
204111388Sgad		tempwork->numlogs = 1;
205111388Sgad		tempwork->size = -1;
206111388Sgad		tempwork->hours = -1;
207111388Sgad		tempwork->trim_at = (time_t)0;
208111388Sgad		tempwork->permissions = 0;
209111388Sgad		tempwork->flags = 0;
210111388Sgad		tempwork->sig = SIGHUP;
211111388Sgad		tempwork->def_cfg = 0;
212111388Sgad	}
213111388Sgad	tempwork->next = NULL;
214111388Sgad
215111388Sgad	return (tempwork);
216111388Sgad}
217111388Sgad
21859004Shmstatic void
219111388Sgadfree_entry(struct conf_entry *ent)
220111388Sgad{
221111388Sgad
222111388Sgad	if (ent == NULL)
223111388Sgad		return;
224111388Sgad
225111388Sgad	if (ent->log != NULL) {
226111388Sgad		if (verbose > 4)
227111388Sgad			printf("\t--> [freeing entry for %s]\n", ent->log);
228111388Sgad		free(ent->log);
229111388Sgad		ent->log = NULL;
230111388Sgad	}
231111388Sgad
232111388Sgad	if (ent->pid_file != NULL) {
233111388Sgad		free(ent->pid_file);
234111388Sgad		ent->pid_file = NULL;
235111388Sgad	}
236111388Sgad
237111388Sgad	free(ent);
238111388Sgad}
239111388Sgad
240111388Sgadstatic void
24159004Shmdo_entry(struct conf_entry * ent)
24213244Sgraichen{
24359003Shm	int size, modtime;
24480640Sobrien	const char *pid_file;
24559003Shm
24659003Shm	if (verbose) {
24759003Shm		if (ent->flags & CE_COMPACT)
24859003Shm			printf("%s <%dZ>: ", ent->log, ent->numlogs);
24980638Sobrien		else if (ent->flags & CE_BZCOMPACT)
25080638Sobrien			printf("%s <%dJ>: ", ent->log, ent->numlogs);
25159003Shm		else
25259003Shm			printf("%s <%d>: ", ent->log, ent->numlogs);
25359003Shm	}
25459003Shm	size = sizefile(ent->log);
25559003Shm	modtime = age_old_log(ent->log);
25659003Shm	if (size < 0) {
25759003Shm		if (verbose)
25859003Shm			printf("does not exist.\n");
25959003Shm	} else {
26090240Sroam		if (ent->flags & CE_TRIMAT && !force) {
26143071Swollman			if (timenow < ent->trim_at
26259003Shm			    || difftime(timenow, ent->trim_at) >= 60 * 60) {
26343071Swollman				if (verbose)
26443071Swollman					printf("--> will trim at %s",
26559003Shm					    ctime(&ent->trim_at));
26643071Swollman				return;
26743071Swollman			} else if (verbose && ent->hours <= 0) {
26843071Swollman				printf("--> time is up\n");
26943071Swollman			}
27043071Swollman		}
27159003Shm		if (verbose && (ent->size > 0))
27259003Shm			printf("size (Kb): %d [%d] ", size, ent->size);
27359003Shm		if (verbose && (ent->hours > 0))
27459003Shm			printf(" age (hr): %d [%d] ", modtime, ent->hours);
27559003Shm		if (force || ((ent->size > 0) && (size >= ent->size)) ||
27643071Swollman		    (ent->hours <= 0 && (ent->flags & CE_TRIMAT)) ||
27759003Shm		    ((ent->hours > 0) && ((modtime >= ent->hours)
27859003Shm			    || (modtime < 0)))) {
27959003Shm			if (verbose)
28059003Shm				printf("--> trimming log....\n");
28159003Shm			if (noaction && !verbose) {
28259003Shm				if (ent->flags & CE_COMPACT)
28359003Shm					printf("%s <%dZ>: trimming\n",
28459003Shm					    ent->log, ent->numlogs);
28580638Sobrien				else if (ent->flags & CE_BZCOMPACT)
28680638Sobrien					printf("%s <%dJ>: trimming\n",
28780638Sobrien					    ent->log, ent->numlogs);
28859003Shm				else
28959003Shm					printf("%s <%d>: trimming\n",
29059003Shm					    ent->log, ent->numlogs);
29159003Shm			}
29235920Shoek			if (ent->pid_file) {
29335920Shoek				pid_file = ent->pid_file;
29435920Shoek			} else {
29535920Shoek				/* Only try to notify syslog if we are root */
29635920Shoek				if (needroot)
29743071Swollman					pid_file = _PATH_SYSLOGPID;
29835920Shoek				else
29935920Shoek					pid_file = NULL;
30035920Shoek			}
30159003Shm			dotrim(ent->log, pid_file, ent->numlogs,
30280646Sobrien			    ent->flags, ent->permissions, ent->uid, ent->gid,
303111388Sgad			    ent->sig, ent->def_cfg);
30459003Shm		} else {
30559003Shm			if (verbose)
30659003Shm				printf("--> skipping\n");
30759003Shm		}
30859003Shm	}
30913244Sgraichen}
31013244Sgraichen
31159004Shmstatic void
31259004ShmPRS(int argc, char **argv)
31313244Sgraichen{
31459003Shm	int c;
31559003Shm	char *p;
31613244Sgraichen
31759003Shm	timenow = time((time_t *) 0);
318108164Strhodes	(void)strncpy(daytime, ctime(&timenow) + 4, 15);
31959003Shm	daytime[15] = '\0';
32013244Sgraichen
32159003Shm	/* Let's get our hostname */
32259003Shm	(void) gethostname(hostname, sizeof(hostname));
32313244Sgraichen
32413244Sgraichen	/* Truncate domain */
32516240Salex	if ((p = strchr(hostname, '.'))) {
32613244Sgraichen		*p = '\0';
32713244Sgraichen	}
328105248Smaxim	while ((c = getopt(argc, argv, "nrvFf:a:")) != -1)
32959003Shm		switch (c) {
33059003Shm		case 'n':
33159003Shm			noaction++;
33235920Shoek			break;
33359004Shm		case 'a':
33459004Shm			archtodir++;
33559004Shm			archdirname = optarg;
33659004Shm			break;
33759003Shm		case 'r':
33859003Shm			needroot = 0;
33959003Shm			break;
34059003Shm		case 'v':
34159003Shm			verbose++;
34259003Shm			break;
34359003Shm		case 'f':
34459003Shm			conf = optarg;
34559003Shm			break;
34634584Spst		case 'F':
34734584Spst			force++;
34834584Spst			break;
34959003Shm		default:
35059003Shm			usage();
35159003Shm		}
35243071Swollman}
35313244Sgraichen
35459004Shmstatic void
35559004Shmusage(void)
35613244Sgraichen{
35780646Sobrien
35880646Sobrien	fprintf(stderr,
359111388Sgad	    "usage: newsyslog [-Fnrv] [-f config-file] [-a directory] [ filename ... ]\n");
36059003Shm	exit(1);
36113244Sgraichen}
36213244Sgraichen
36359004Shm/*
36459004Shm * Parse a configuration file and return a linked list of all the logs to
36559004Shm * process
36613244Sgraichen */
36759003Shmstatic struct conf_entry *
36860373Sdesparse_file(char **files)
36913244Sgraichen{
37059003Shm	FILE *f;
37159003Shm	char line[BUFSIZ], *parse, *q;
372107737Ssobomax	char *cp, *errline, *group;
373111388Sgad	char **given;
374111388Sgad	struct conf_entry *defconf, *first, *working, *worklist;
37559003Shm	struct passwd *pass;
37659003Shm	struct group *grp;
37725518Sbrian	int eol;
37813244Sgraichen
379111388Sgad	defconf = first = working = worklist = NULL;
38080646Sobrien
38159003Shm	if (strcmp(conf, "-"))
38259003Shm		f = fopen(conf, "r");
38359003Shm	else
38459003Shm		f = stdin;
38559003Shm	if (!f)
38659003Shm		err(1, "%s", conf);
38759003Shm	while (fgets(line, BUFSIZ, f)) {
388107737Ssobomax		if ((line[0] == '\n') || (line[0] == '#') ||
389107737Ssobomax		    (strlen(line) == 0))
39059003Shm			continue;
39159003Shm		errline = strdup(line);
392107737Ssobomax		for (cp = line + 1; *cp != '\0'; cp++) {
393107737Ssobomax			if (*cp != '#')
394107737Ssobomax				continue;
395107737Ssobomax			if (*(cp - 1) == '\\') {
396107737Ssobomax				strcpy(cp - 1, cp);
397107737Ssobomax				cp--;
398107737Ssobomax				continue;
399107737Ssobomax			}
400107737Ssobomax			*cp = '\0';
401107737Ssobomax			break;
402107737Ssobomax		}
40360373Sdes
40460373Sdes		q = parse = missing_field(sob(line), errline);
40560373Sdes		parse = son(line);
40660373Sdes		if (!*parse)
40780646Sobrien			errx(1, "malformed line (missing fields):\n%s",
40880646Sobrien			    errline);
40960373Sdes		*parse = '\0';
41060373Sdes
411111388Sgad		/*
412111388Sgad		 * If newsyslog was run with a list of specific filenames,
413111388Sgad		 * then this line of the config file should be skipped if
414111388Sgad		 * it is NOT one of those given files (except that we do
415111388Sgad		 * want any line that defines the <default> action).
416111388Sgad		 *
417111388Sgad		 * XXX - note that CE_GLOB processing is *NOT* done when
418111388Sgad		 *       trying to match a filename given on the command!
419111388Sgad		 */
42060373Sdes		if (*files) {
421111388Sgad			if (strcasecmp(DEFAULT_MARKER, q) != 0) {
422111388Sgad				for (given = files; *given; ++given) {
423111388Sgad					if (strcmp(*given, q) == 0)
424111388Sgad						break;
425111388Sgad				}
426111388Sgad				if (!*given)
427111388Sgad					continue;
428111388Sgad			}
429111388Sgad			if (verbose > 2)
430111388Sgad				printf("\t+ Matched entry %s\n", q);
431111388Sgad		} else {
432111388Sgad			/*
433111388Sgad			 * If no files were specified on the command line,
434111388Sgad			 * then we can skip any line which defines the
435111388Sgad			 * default action.
436111388Sgad			 */
437111388Sgad			if (strcasecmp(DEFAULT_MARKER, q) == 0) {
438111388Sgad				if (verbose > 2)
439111388Sgad					printf("\t+ Ignoring entry for %s\n",
440111388Sgad					    q);
44160373Sdes				continue;
442111388Sgad			}
44360373Sdes		}
44460373Sdes
445111388Sgad		working = init_entry(q, NULL);
446111388Sgad		if (strcasecmp(DEFAULT_MARKER, q) == 0) {
447111388Sgad			if (defconf != NULL) {
448111388Sgad				warnx("Ignoring duplicate entry for %s!", q);
449111388Sgad				free_entry(working);
450111388Sgad				continue;
451111388Sgad			}
452111388Sgad			defconf = working;
45359003Shm		} else {
454111388Sgad			if (!first)
455111388Sgad				first = working;
456111388Sgad			else
457111388Sgad				worklist->next = working;
458111388Sgad			worklist = working;
45959003Shm		}
46013244Sgraichen
46159003Shm		q = parse = missing_field(sob(++parse), errline);
46259003Shm		parse = son(parse);
46325518Sbrian		if (!*parse)
46480646Sobrien			errx(1, "malformed line (missing fields):\n%s",
46580646Sobrien			    errline);
46659003Shm		*parse = '\0';
46759003Shm		if ((group = strchr(q, ':')) != NULL ||
46859003Shm		    (group = strrchr(q, '.')) != NULL) {
46959003Shm			*group++ = '\0';
47059003Shm			if (*q) {
47159003Shm				if (!(isnumber(*q))) {
47259003Shm					if ((pass = getpwnam(q)) == NULL)
47359003Shm						errx(1,
47480646Sobrien				     "error in config file; unknown user:\n%s",
47559003Shm						    errline);
47659003Shm					working->uid = pass->pw_uid;
47759003Shm				} else
47859003Shm					working->uid = atoi(q);
47959003Shm			} else
48059003Shm				working->uid = NONE;
48113244Sgraichen
48259003Shm			q = group;
48359003Shm			if (*q) {
48459003Shm				if (!(isnumber(*q))) {
48559003Shm					if ((grp = getgrnam(q)) == NULL)
48659003Shm						errx(1,
48780646Sobrien				    "error in config file; unknown group:\n%s",
48859003Shm						    errline);
48959003Shm					working->gid = grp->gr_gid;
49059003Shm				} else
49159003Shm					working->gid = atoi(q);
49259003Shm			} else
49359003Shm				working->gid = NONE;
49413244Sgraichen
49559003Shm			q = parse = missing_field(sob(++parse), errline);
49659003Shm			parse = son(parse);
49759003Shm			if (!*parse)
49880646Sobrien				errx(1, "malformed line (missing fields):\n%s",
49980646Sobrien				    errline);
50059003Shm			*parse = '\0';
50159003Shm		} else
50259003Shm			working->uid = working->gid = NONE;
50359003Shm
50459003Shm		if (!sscanf(q, "%o", &working->permissions))
50559003Shm			errx(1, "error in config file; bad permissions:\n%s",
50659003Shm			    errline);
50759003Shm
50859003Shm		q = parse = missing_field(sob(++parse), errline);
50959003Shm		parse = son(parse);
51025518Sbrian		if (!*parse)
51180646Sobrien			errx(1, "malformed line (missing fields):\n%s",
51280646Sobrien			    errline);
51359003Shm		*parse = '\0';
51459003Shm		if (!sscanf(q, "%d", &working->numlogs))
51559003Shm			errx(1, "error in config file; bad number:\n%s",
51659003Shm			    errline);
51713244Sgraichen
51859003Shm		q = parse = missing_field(sob(++parse), errline);
51959003Shm		parse = son(parse);
52025518Sbrian		if (!*parse)
52180646Sobrien			errx(1, "malformed line (missing fields):\n%s",
52280646Sobrien			    errline);
52359003Shm		*parse = '\0';
52459003Shm		if (isdigit(*q))
52559003Shm			working->size = atoi(q);
52659003Shm		else
52759003Shm			working->size = -1;
52859003Shm
52959003Shm		working->flags = 0;
53059003Shm		q = parse = missing_field(sob(++parse), errline);
53159003Shm		parse = son(parse);
53225518Sbrian		eol = !*parse;
53359003Shm		*parse = '\0';
53443071Swollman		{
53559003Shm			char *ep;
53659003Shm			u_long ul;
53713244Sgraichen
53843071Swollman			ul = strtoul(q, &ep, 10);
53943071Swollman			if (ep == q)
54043071Swollman				working->hours = 0;
54143071Swollman			else if (*ep == '*')
54243071Swollman				working->hours = -1;
54343071Swollman			else if (ul > INT_MAX)
54443071Swollman				errx(1, "interval is too large:\n%s", errline);
54543071Swollman			else
54643071Swollman				working->hours = ul;
54743071Swollman
54880646Sobrien			if (*ep != '\0' && *ep != '@' && *ep != '*' &&
54980646Sobrien			    *ep != '$')
55043071Swollman				errx(1, "malformed interval/at:\n%s", errline);
55143071Swollman			if (*ep == '@') {
55293659Scjc				if ((working->trim_at = parse8601(ep + 1, errline))
55359003Shm				    == (time_t) - 1)
55443071Swollman					errx(1, "malformed at:\n%s", errline);
55543071Swollman				working->flags |= CE_TRIMAT;
55659004Shm			} else if (*ep == '$') {
55793659Scjc				if ((working->trim_at = parseDWM(ep + 1, errline))
55859004Shm				    == (time_t) - 1)
55959004Shm					errx(1, "malformed at:\n%s", errline);
56059004Shm				working->flags |= CE_TRIMAT;
56143071Swollman			}
56243071Swollman		}
56343071Swollman
56425518Sbrian		if (eol)
56559003Shm			q = NULL;
56625518Sbrian		else {
56759003Shm			q = parse = sob(++parse);	/* Optional field */
56859003Shm			parse = son(parse);
56959003Shm			if (!*parse)
57059003Shm				eol = 1;
57159003Shm			*parse = '\0';
57225518Sbrian		}
57325443Sache
57459003Shm		while (q && *q && !isspace(*q)) {
57559003Shm			if ((*q == 'Z') || (*q == 'z'))
57659003Shm				working->flags |= CE_COMPACT;
57780638Sobrien			else if ((*q == 'J') || (*q == 'j'))
57880638Sobrien				working->flags |= CE_BZCOMPACT;
57959003Shm			else if ((*q == 'B') || (*q == 'b'))
58059003Shm				working->flags |= CE_BINARY;
581106905Ssobomax			else if ((*q == 'G') || (*q == 'c'))
582106905Ssobomax				working->flags |= CE_GLOB;
583107916Ssobomax			else if ((*q == 'W') || (*q == 'w'))
584107916Ssobomax				working->flags |= CE_COMPACTWAIT;
58559003Shm			else if (*q != '-')
58680646Sobrien				errx(1, "illegal flag in config file -- %c",
58780646Sobrien				    *q);
58859003Shm			q++;
58959003Shm		}
59059003Shm
59125518Sbrian		if (eol)
59259003Shm			q = NULL;
59325518Sbrian		else {
59459003Shm			q = parse = sob(++parse);	/* Optional field */
59559003Shm			parse = son(parse);
59659003Shm			if (!*parse)
59759003Shm				eol = 1;
59859003Shm			*parse = '\0';
59925518Sbrian		}
60025443Sache
60125443Sache		working->pid_file = NULL;
60225443Sache		if (q && *q) {
60325443Sache			if (*q == '/')
60425443Sache				working->pid_file = strdup(q);
60536817Sache			else if (isdigit(*q))
60636817Sache				goto got_sig;
60759003Shm			else
60880646Sobrien				errx(1,
60980646Sobrien			"illegal pid file or signal number in config file:\n%s",
61080646Sobrien				    errline);
61125443Sache		}
61236817Sache		if (eol)
61359003Shm			q = NULL;
61436817Sache		else {
61559003Shm			q = parse = sob(++parse);	/* Optional field */
61659003Shm			*(parse = son(parse)) = '\0';
61736817Sache		}
61836817Sache
61936817Sache		working->sig = SIGHUP;
62036817Sache		if (q && *q) {
62136817Sache			if (isdigit(*q)) {
62259003Shm		got_sig:
62336817Sache				working->sig = atoi(q);
62436817Sache			} else {
62559003Shm		err_sig:
62680646Sobrien				errx(1,
62780646Sobrien				    "illegal signal number in config file:\n%s",
62880646Sobrien				    errline);
62936817Sache			}
63036817Sache			if (working->sig < 1 || working->sig >= NSIG)
63136817Sache				goto err_sig;
63236817Sache		}
63359003Shm		free(errline);
63459003Shm	}
63559003Shm	(void) fclose(f);
636111388Sgad
637111388Sgad	/*
638111388Sgad	 * The entire config file has been processed.  If there were
639111388Sgad	 * no specific files given on the run command, then the work
640111388Sgad	 * of this routine is done.
641111388Sgad	 */
642111388Sgad	if (*files == NULL)
643111388Sgad		return (first);
644111388Sgad
645111388Sgad	/*
646111388Sgad	 * If the program was given a specific list of files to process,
647111388Sgad	 * it may be that some of those files were not listed in the
648111388Sgad	 * config file.  Those unlisted files should get the default
649111388Sgad	 * rotation action.  First, create the default-rotation action
650111388Sgad	 * if none was found in the config file.
651111388Sgad	 */
652111388Sgad	if (defconf == NULL) {
653111388Sgad		working = init_entry(DEFAULT_MARKER, NULL);
654111388Sgad		working->numlogs = 3;
655111388Sgad		working->size = 50;
656111388Sgad		working->permissions = S_IRUSR|S_IWUSR;
657111388Sgad		defconf = working;
658111388Sgad	}
659111388Sgad
660111388Sgad	for (given = files; *given; ++given) {
661111388Sgad		for (working = first; working; working = working->next) {
662111388Sgad			if (strcmp(*given, working->log) == 0)
663111388Sgad				break;
664111388Sgad		}
665111388Sgad		if (working != NULL)
666111388Sgad			continue;
667111388Sgad		if (verbose > 2)
668111388Sgad			printf("\t+ No entry for %s  (will use %s)\n",
669111388Sgad			    *given, DEFAULT_MARKER);
670111388Sgad		/*
671111388Sgad		 * This given file was not found in the config file.
672111388Sgad		 * Add another item on to our work list, based on the
673111388Sgad		 * default entry.
674111388Sgad		 */
675111388Sgad		working = init_entry(*given, defconf);
676111388Sgad		if (!first)
677111388Sgad			first = working;
678111388Sgad		else
679111388Sgad			worklist->next = working;
680111388Sgad		/* This is a file that was *not* found in config file */
681111388Sgad		working->def_cfg = 1;
682111388Sgad		worklist = working;
683111388Sgad	}
684111388Sgad
685111388Sgad	free_entry(defconf);
68659003Shm	return (first);
68713244Sgraichen}
68813244Sgraichen
68959003Shmstatic char *
69059004Shmmissing_field(char *p, char *errline)
69113244Sgraichen{
69280646Sobrien
69359003Shm	if (!p || !*p)
69459003Shm		errx(1, "missing field in config file:\n%s", errline);
69559003Shm	return (p);
69613244Sgraichen}
69713244Sgraichen
69859004Shmstatic void
69980640Sobriendotrim(char *log, const char *pid_file, int numdays, int flags, int perm,
700111388Sgad    int owner_uid, int group_gid, int sig, int def_cfg)
70113244Sgraichen{
70271299Sjedgar	char dirpart[MAXPATHLEN], namepart[MAXPATHLEN];
70371299Sjedgar	char file1[MAXPATHLEN], file2[MAXPATHLEN];
70471299Sjedgar	char zfile1[MAXPATHLEN], zfile2[MAXPATHLEN];
70580638Sobrien	char jfile1[MAXPATHLEN];
70694352Ssheldonh	char tfile[MAXPATHLEN];
70759003Shm	int notified, need_notification, fd, _numdays;
70859003Shm	struct stat st;
70959003Shm	pid_t pid;
71013244Sgraichen
71113244Sgraichen#ifdef _IBMR2
71259004Shm	/*
71359004Shm	 * AIX 3.1 has a broken fchown- if the owner_uid is -1, it will
71459004Shm	 * actually change it to be owned by uid -1, instead of leaving it
71559004Shm	 * as is, as it is supposed to.
71659004Shm	 */
71759003Shm	if (owner_uid == -1)
71859003Shm		owner_uid = geteuid();
71913244Sgraichen#endif
72013244Sgraichen
72159004Shm	if (archtodir) {
72259004Shm		char *p;
72313244Sgraichen
72459004Shm		/* build complete name of archive directory into dirpart */
72559004Shm		if (*archdirname == '/') {	/* absolute */
72671299Sjedgar			strlcpy(dirpart, archdirname, sizeof(dirpart));
72759004Shm		} else {	/* relative */
72859004Shm			/* get directory part of logfile */
72971299Sjedgar			strlcpy(dirpart, log, sizeof(dirpart));
73059004Shm			if ((p = rindex(dirpart, '/')) == NULL)
73159004Shm				dirpart[0] = '\0';
73259004Shm			else
73359004Shm				*(p + 1) = '\0';
73471299Sjedgar			strlcat(dirpart, archdirname, sizeof(dirpart));
73559004Shm		}
73659004Shm
73759004Shm		/* check if archive directory exists, if not, create it */
73859004Shm		if (lstat(dirpart, &st))
73959004Shm			createdir(dirpart);
74059004Shm
74159004Shm		/* get filename part of logfile */
74259004Shm		if ((p = rindex(log, '/')) == NULL)
74371299Sjedgar			strlcpy(namepart, log, sizeof(namepart));
74459004Shm		else
74571299Sjedgar			strlcpy(namepart, p + 1, sizeof(namepart));
74659004Shm
74759004Shm		/* name of oldest log */
74880646Sobrien		(void) snprintf(file1, sizeof(file1), "%s/%s.%d", dirpart,
74980646Sobrien		    namepart, numdays);
75071299Sjedgar		(void) snprintf(zfile1, sizeof(zfile1), "%s%s", file1,
75171299Sjedgar		    COMPRESS_POSTFIX);
75280638Sobrien		snprintf(jfile1, sizeof(jfile1), "%s%s", file1,
75380638Sobrien		    BZCOMPRESS_POSTFIX);
75459004Shm	} else {
75559004Shm		/* name of oldest log */
75671299Sjedgar		(void) snprintf(file1, sizeof(file1), "%s.%d", log, numdays);
75771299Sjedgar		(void) snprintf(zfile1, sizeof(zfile1), "%s%s", file1,
75871299Sjedgar		    COMPRESS_POSTFIX);
75980638Sobrien		snprintf(jfile1, sizeof(jfile1), "%s%s", file1,
76080638Sobrien		    BZCOMPRESS_POSTFIX);
76159004Shm	}
76259004Shm
76359003Shm	if (noaction) {
76459003Shm		printf("rm -f %s\n", file1);
76559003Shm		printf("rm -f %s\n", zfile1);
76680638Sobrien		printf("rm -f %s\n", jfile1);
76759003Shm	} else {
76859003Shm		(void) unlink(file1);
76959003Shm		(void) unlink(zfile1);
77080638Sobrien		(void) unlink(jfile1);
77159003Shm	}
77213244Sgraichen
77359003Shm	/* Move down log files */
77418188Sjkh	_numdays = numdays;	/* preserve */
77559003Shm	while (numdays--) {
77659004Shm
77771299Sjedgar		(void) strlcpy(file2, file1, sizeof(file2));
77859004Shm
77959004Shm		if (archtodir)
78080646Sobrien			(void) snprintf(file1, sizeof(file1), "%s/%s.%d",
78180646Sobrien			    dirpart, namepart, numdays);
78259004Shm		else
78380646Sobrien			(void) snprintf(file1, sizeof(file1), "%s.%d", log,
78480646Sobrien			    numdays);
78559004Shm
78671299Sjedgar		(void) strlcpy(zfile1, file1, sizeof(zfile1));
78771299Sjedgar		(void) strlcpy(zfile2, file2, sizeof(zfile2));
78859003Shm		if (lstat(file1, &st)) {
78980646Sobrien			(void) strlcat(zfile1, COMPRESS_POSTFIX,
79080646Sobrien			    sizeof(zfile1));
79180646Sobrien			(void) strlcat(zfile2, COMPRESS_POSTFIX,
79280646Sobrien			    sizeof(zfile2));
79380638Sobrien			if (lstat(zfile1, &st)) {
79480638Sobrien				strlcpy(zfile1, file1, sizeof(zfile1));
79580638Sobrien				strlcpy(zfile2, file2, sizeof(zfile2));
79680638Sobrien				strlcat(zfile1, BZCOMPRESS_POSTFIX,
79780638Sobrien				    sizeof(zfile1));
79880638Sobrien				strlcat(zfile2, BZCOMPRESS_POSTFIX,
79980638Sobrien				    sizeof(zfile2));
80080638Sobrien				if (lstat(zfile1, &st))
80180638Sobrien					continue;
80280638Sobrien			}
80359003Shm		}
80459003Shm		if (noaction) {
80559003Shm			printf("mv %s %s\n", zfile1, zfile2);
80659003Shm			printf("chmod %o %s\n", perm, zfile2);
80780684Sobrien			printf("chown %d:%d %s\n",
80859003Shm			    owner_uid, group_gid, zfile2);
80959003Shm		} else {
81059003Shm			(void) rename(zfile1, zfile2);
81159003Shm			(void) chmod(zfile2, perm);
81259003Shm			(void) chown(zfile2, owner_uid, group_gid);
81359003Shm		}
81459003Shm	}
815111388Sgad	if (!noaction && !(flags & CE_BINARY)) {
816111388Sgad		/* Report the trimming to the old log */
817111388Sgad		(void) log_trim(log, def_cfg);
818111388Sgad	}
81913244Sgraichen
82018188Sjkh	if (!_numdays) {
82118075Sjkh		if (noaction)
82259003Shm			printf("rm %s\n", log);
82318075Sjkh		else
82459003Shm			(void) unlink(log);
82559003Shm	} else {
82618075Sjkh		if (noaction)
82759003Shm			printf("mv %s to %s\n", log, file1);
82859004Shm		else {
82959004Shm			if (archtodir)
83080646Sobrien				movefile(log, file1, perm, owner_uid,
83180646Sobrien				    group_gid);
83259004Shm			else
83359004Shm				(void) rename(log, file1);
83459004Shm		}
83518075Sjkh	}
83618075Sjkh
83759003Shm	if (noaction)
83859003Shm		printf("Start new log...");
83959003Shm	else {
84094352Ssheldonh		strlcpy(tfile, log, sizeof(tfile));
84194352Ssheldonh		strlcat(tfile, ".XXXXXX", sizeof(tfile));
84294352Ssheldonh		mkstemp(tfile);
84394352Ssheldonh		fd = creat(tfile, perm);
84459003Shm		if (fd < 0)
84559003Shm			err(1, "can't start new log");
84659003Shm		if (fchown(fd, owner_uid, group_gid))
84759003Shm			err(1, "can't chmod new log file");
84859003Shm		(void) close(fd);
849111388Sgad		if (!(flags & CE_BINARY)) {
850111388Sgad			/* Add status message to new log file */
851111388Sgad			if (log_trim(tfile, def_cfg))
85259003Shm				err(1, "can't add status message to log");
853111388Sgad		}
85459003Shm	}
85559003Shm	if (noaction)
85659003Shm		printf("chmod %o %s...\n", perm, log);
85794352Ssheldonh	else {
85894352Ssheldonh		(void) chmod(tfile, perm);
85994352Ssheldonh		if (rename(tfile, log) < 0) {
86094352Ssheldonh			err(1, "can't start new log");
86194352Ssheldonh			(void) unlink(tfile);
86294352Ssheldonh		}
86394352Ssheldonh	}
86425443Sache
86525443Sache	pid = 0;
86625443Sache	need_notification = notified = 0;
86725443Sache	if (pid_file != NULL) {
86825443Sache		need_notification = 1;
86925443Sache		pid = get_pid(pid_file);
87025443Sache	}
87125443Sache	if (pid) {
87225443Sache		if (noaction) {
87325443Sache			notified = 1;
87459003Shm			printf("kill -%d %d\n", sig, (int) pid);
87559003Shm		} else if (kill(pid, sig))
87659003Shm			warn("can't notify daemon, pid %d", (int) pid);
87725443Sache		else {
87825443Sache			notified = 1;
87925443Sache			if (verbose)
88059003Shm				printf("daemon pid %d notified\n", (int) pid);
88125443Sache		}
88225443Sache	}
88380638Sobrien	if ((flags & CE_COMPACT) || (flags & CE_BZCOMPACT)) {
88425443Sache		if (need_notification && !notified)
88580646Sobrien			warnx(
88680646Sobrien			    "log %s not compressed because daemon not notified",
88780646Sobrien			    log);
88825443Sache		else if (noaction)
88959003Shm			printf("Compress %s.0\n", log);
89025443Sache		else {
89125443Sache			if (notified) {
89225443Sache				if (verbose)
89325443Sache					printf("small pause to allow daemon to close log\n");
89431460Sache				sleep(10);
89525443Sache			}
89659004Shm			if (archtodir) {
89780646Sobrien				(void) snprintf(file1, sizeof(file1), "%s/%s",
89880646Sobrien				    dirpart, namepart);
89980638Sobrien				if (flags & CE_COMPACT)
900107916Ssobomax					compress_log(file1,
901107916Ssobomax					    flags & CE_COMPACTWAIT);
90280638Sobrien				else if (flags & CE_BZCOMPACT)
903107916Ssobomax					bzcompress_log(file1,
904107916Ssobomax					    flags & CE_COMPACTWAIT);
90559004Shm			} else {
90680638Sobrien				if (flags & CE_COMPACT)
907107916Ssobomax					compress_log(log,
908107916Ssobomax					    flags & CE_COMPACTWAIT);
90980638Sobrien				else if (flags & CE_BZCOMPACT)
910107916Ssobomax					bzcompress_log(log,
911107916Ssobomax					    flags & CE_COMPACTWAIT);
91259004Shm			}
91325443Sache		}
91459003Shm	}
91513244Sgraichen}
91613244Sgraichen
91713244Sgraichen/* Log the fact that the logs were turned over */
91859004Shmstatic int
919111388Sgadlog_trim(char *log, int def_cfg)
92013244Sgraichen{
92159003Shm	FILE *f;
922111388Sgad	const char *xtra;
92359003Shm
92459003Shm	if ((f = fopen(log, "a")) == NULL)
92559003Shm		return (-1);
926111388Sgad	xtra = "";
927111388Sgad	if (def_cfg)
928111388Sgad		xtra = " using <default> rule";
929111388Sgad	fprintf(f, "%s %s newsyslog[%d]: logfile turned over%s\n",
930111388Sgad	    daytime, hostname, (int) getpid(), xtra);
93159003Shm	if (fclose(f) == EOF)
93259003Shm		err(1, "log_trim: fclose:");
93359003Shm	return (0);
93413244Sgraichen}
93513244Sgraichen
93659004Shm/* Fork of gzip to compress the old log file */
93759004Shmstatic void
938107916Ssobomaxcompress_log(char *log, int dowait)
93913244Sgraichen{
94059003Shm	pid_t pid;
94171299Sjedgar	char tmp[MAXPATHLEN];
94259003Shm
943107916Ssobomax	while (dowait && (wait(NULL) > 0 || errno == EINTR))
944107916Ssobomax		;
94571299Sjedgar	(void) snprintf(tmp, sizeof(tmp), "%s.0", log);
94625443Sache	pid = fork();
94759003Shm	if (pid < 0)
94880638Sobrien		err(1, "gzip fork");
94959003Shm	else if (!pid) {
95079452Sbrian		(void) execl(_PATH_GZIP, _PATH_GZIP, "-f", tmp, (char *)0);
95143071Swollman		err(1, _PATH_GZIP);
95259003Shm	}
95313244Sgraichen}
95413244Sgraichen
95580638Sobrien/* Fork of bzip2 to compress the old log file */
95680638Sobrienstatic void
957107916Ssobomaxbzcompress_log(char *log, int dowait)
95880638Sobrien{
95980638Sobrien	pid_t pid;
96080638Sobrien	char tmp[MAXPATHLEN];
96180638Sobrien
962107916Ssobomax	while (dowait && (wait(NULL) > 0 || errno == EINTR))
963107916Ssobomax		;
96480638Sobrien	snprintf(tmp, sizeof(tmp), "%s.0", log);
96580638Sobrien	pid = fork();
96680638Sobrien	if (pid < 0)
96780638Sobrien		err(1, "bzip2 fork");
96880638Sobrien	else if (!pid) {
96986360Sobrien		execl(_PATH_BZIP2, _PATH_BZIP2, "-f", tmp, (char *)0);
97080638Sobrien		err(1, _PATH_BZIP2);
97180638Sobrien	}
97280638Sobrien}
97380638Sobrien
97413244Sgraichen/* Return size in kilobytes of a file */
97559004Shmstatic int
97659004Shmsizefile(char *file)
97713244Sgraichen{
97859003Shm	struct stat sb;
97913244Sgraichen
98059003Shm	if (stat(file, &sb) < 0)
98159003Shm		return (-1);
98259003Shm	return (kbytes(dbtob(sb.st_blocks)));
98313244Sgraichen}
98413244Sgraichen
98513244Sgraichen/* Return the age of old log file (file.0) */
98659004Shmstatic int
98759004Shmage_old_log(char *file)
98813244Sgraichen{
98959003Shm	struct stat sb;
99059003Shm	char tmp[MAXPATHLEN + sizeof(".0") + sizeof(COMPRESS_POSTFIX) + 1];
99113244Sgraichen
99259004Shm	if (archtodir) {
99359004Shm		char *p;
99459004Shm
99559004Shm		/* build name of archive directory into tmp */
99659004Shm		if (*archdirname == '/') {	/* absolute */
99771299Sjedgar			strlcpy(tmp, archdirname, sizeof(tmp));
99859004Shm		} else {	/* relative */
99959004Shm			/* get directory part of logfile */
100071299Sjedgar			strlcpy(tmp, file, sizeof(tmp));
100159004Shm			if ((p = rindex(tmp, '/')) == NULL)
100259004Shm				tmp[0] = '\0';
100359004Shm			else
100459004Shm				*(p + 1) = '\0';
100571299Sjedgar			strlcat(tmp, archdirname, sizeof(tmp));
100659004Shm		}
100759004Shm
100871299Sjedgar		strlcat(tmp, "/", sizeof(tmp));
100959004Shm
101059004Shm		/* get filename part of logfile */
101159004Shm		if ((p = rindex(file, '/')) == NULL)
101271299Sjedgar			strlcat(tmp, file, sizeof(tmp));
101359004Shm		else
101471299Sjedgar			strlcat(tmp, p + 1, sizeof(tmp));
101559004Shm	} else {
101671299Sjedgar		(void) strlcpy(tmp, file, sizeof(tmp));
101759004Shm	}
101859004Shm
101959003Shm	if (stat(strcat(tmp, ".0"), &sb) < 0)
102059003Shm		if (stat(strcat(tmp, COMPRESS_POSTFIX), &sb) < 0)
102159003Shm			return (-1);
102259003Shm	return ((int) (timenow - sb.st_mtime + 1800) / 3600);
102313244Sgraichen}
102413244Sgraichen
102559004Shmstatic pid_t
102680640Sobrienget_pid(const char *pid_file)
102725443Sache{
102825443Sache	FILE *f;
102959003Shm	char line[BUFSIZ];
103025443Sache	pid_t pid = 0;
103113244Sgraichen
103259003Shm	if ((f = fopen(pid_file, "r")) == NULL)
103325443Sache		warn("can't open %s pid file to restart a daemon",
103459003Shm		    pid_file);
103525443Sache	else {
103659003Shm		if (fgets(line, BUFSIZ, f)) {
103725443Sache			pid = atol(line);
103825443Sache			if (pid < MIN_PID || pid > MAX_PID) {
103980646Sobrien				warnx("preposterous process number: %d",
104080646Sobrien				   (int)pid);
104125443Sache				pid = 0;
104225443Sache			}
104325443Sache		} else
104425443Sache			warn("can't read %s pid file to restart a daemon",
104559003Shm			    pid_file);
104659003Shm		(void) fclose(f);
104725443Sache	}
1048111392Sgad	return (pid);
104925443Sache}
105025443Sache
105113244Sgraichen/* Skip Over Blanks */
105259003Shmchar *
105359004Shmsob(char *p)
105413244Sgraichen{
105559003Shm	while (p && *p && isspace(*p))
105659003Shm		p++;
105759003Shm	return (p);
105813244Sgraichen}
105913244Sgraichen
106013244Sgraichen/* Skip Over Non-Blanks */
106159003Shmchar *
106259004Shmson(char *p)
106313244Sgraichen{
106459003Shm	while (p && *p && !isspace(*p))
106559003Shm		p++;
106659003Shm	return (p);
106713244Sgraichen}
106843071Swollman
106943071Swollman/*
107059004Shm * Parse a limited subset of ISO 8601. The specific format is as follows:
107143071Swollman *
107259004Shm * [CC[YY[MM[DD]]]][THH[MM[SS]]]	(where `T' is the literal letter)
107343071Swollman *
107459004Shm * We don't accept a timezone specification; missing fields (including timezone)
107559004Shm * are defaulted to the current date but time zero.
107643071Swollman */
107743071Swollmanstatic time_t
107893659Scjcparse8601(char *s, char *errline)
107943071Swollman{
108059003Shm	char *t;
108193659Scjc	time_t tsecs;
108259003Shm	struct tm tm, *tmp;
108359003Shm	u_long ul;
108443071Swollman
108543071Swollman	tmp = localtime(&timenow);
108643071Swollman	tm = *tmp;
108743071Swollman
108843071Swollman	tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
108943071Swollman
109043071Swollman	ul = strtoul(s, &t, 10);
109143071Swollman	if (*t != '\0' && *t != 'T')
1092111392Sgad		return (-1);
109343071Swollman
109443071Swollman	/*
109559003Shm	 * Now t points either to the end of the string (if no time was
109659003Shm	 * provided) or to the letter `T' which separates date and time in
109759003Shm	 * ISO 8601.  The pointer arithmetic is the same for either case.
109843071Swollman	 */
109943071Swollman	switch (t - s) {
110043071Swollman	case 8:
110143071Swollman		tm.tm_year = ((ul / 1000000) - 19) * 100;
110243071Swollman		ul = ul % 1000000;
110343071Swollman	case 6:
110480666Swollman		tm.tm_year -= tm.tm_year % 100;
110543071Swollman		tm.tm_year += ul / 10000;
110643071Swollman		ul = ul % 10000;
110743071Swollman	case 4:
110843071Swollman		tm.tm_mon = (ul / 100) - 1;
110943071Swollman		ul = ul % 100;
111043071Swollman	case 2:
111143071Swollman		tm.tm_mday = ul;
111243071Swollman	case 0:
111343071Swollman		break;
111443071Swollman	default:
1115111392Sgad		return (-1);
111643071Swollman	}
111743071Swollman
111843071Swollman	/* sanity check */
111943071Swollman	if (tm.tm_year < 70 || tm.tm_mon < 0 || tm.tm_mon > 12
112043071Swollman	    || tm.tm_mday < 1 || tm.tm_mday > 31)
1121111392Sgad		return (-1);
112243071Swollman
112343071Swollman	if (*t != '\0') {
112443071Swollman		s = ++t;
112543071Swollman		ul = strtoul(s, &t, 10);
112643071Swollman		if (*t != '\0' && !isspace(*t))
1127111392Sgad			return (-1);
112843071Swollman
112943071Swollman		switch (t - s) {
113043071Swollman		case 6:
113143071Swollman			tm.tm_sec = ul % 100;
113243071Swollman			ul /= 100;
113343071Swollman		case 4:
113443071Swollman			tm.tm_min = ul % 100;
113543071Swollman			ul /= 100;
113643071Swollman		case 2:
113743071Swollman			tm.tm_hour = ul;
113843071Swollman		case 0:
113943071Swollman			break;
114043071Swollman		default:
1141111392Sgad			return (-1);
114243071Swollman		}
114343071Swollman
114443071Swollman		/* sanity check */
114543071Swollman		if (tm.tm_sec < 0 || tm.tm_sec > 60 || tm.tm_min < 0
114643071Swollman		    || tm.tm_min > 59 || tm.tm_hour < 0 || tm.tm_hour > 23)
1147111392Sgad			return (-1);
114843071Swollman	}
114993659Scjc	if ((tsecs = mktime(&tm)) == -1)
115099209Smaxim		errx(1, "nonexistent time:\n%s", errline);
1151111392Sgad	return (tsecs);
115243071Swollman}
115359004Shm
115459004Shm/* physically move file */
115559004Shmstatic void
115659004Shmmovefile(char *from, char *to, int perm, int owner_uid, int group_gid)
115759004Shm{
115859004Shm	FILE *src, *dst;
115959004Shm	int c;
116059004Shm
116159004Shm	if ((src = fopen(from, "r")) == NULL)
116259004Shm		err(1, "can't fopen %s for reading", from);
116359004Shm	if ((dst = fopen(to, "w")) == NULL)
116459004Shm		err(1, "can't fopen %s for writing", to);
116559004Shm	if (fchown(fileno(dst), owner_uid, group_gid))
116659004Shm		err(1, "can't fchown %s", to);
116759004Shm	if (fchmod(fileno(dst), perm))
116859004Shm		err(1, "can't fchmod %s", to);
116959004Shm
117059004Shm	while ((c = getc(src)) != EOF) {
117159004Shm		if ((putc(c, dst)) == EOF)
117259004Shm			err(1, "error writing to %s", to);
117359004Shm	}
117459004Shm
117559004Shm	if (ferror(src))
117659004Shm		err(1, "error reading from %s", from);
117759004Shm	if ((fclose(src)) != 0)
117859004Shm		err(1, "can't fclose %s", to);
117959004Shm	if ((fclose(dst)) != 0)
118059004Shm		err(1, "can't fclose %s", from);
118159004Shm	if ((unlink(from)) != 0)
118259004Shm		err(1, "can't unlink %s", from);
118359004Shm}
118459004Shm
118559004Shm/* create one or more directory components of a path */
118659004Shmstatic void
118759004Shmcreatedir(char *dirpart)
118859004Shm{
1189111398Sgad	int res;
119059004Shm	char *s, *d;
119171299Sjedgar	char mkdirpath[MAXPATHLEN];
119259004Shm	struct stat st;
119359004Shm
119459004Shm	s = dirpart;
119559004Shm	d = mkdirpath;
119659004Shm
119759004Shm	for (;;) {
119859004Shm		*d++ = *s++;
1199111398Sgad		if (*s != '/' && *s != '\0')
1200111398Sgad			continue;
1201111398Sgad		*d = '\0';
1202111398Sgad		res = lstat(mkdirpath, &st);
1203111398Sgad		if (res != 0) {
1204111398Sgad			if (noaction) {
1205111398Sgad				printf("mkdir %s\n", mkdirpath);
1206111398Sgad			} else {
1207111398Sgad				res = mkdir(mkdirpath, 0755);
1208111398Sgad				if (res != 0)
1209111398Sgad					err(1, "Error on mkdir(\"%s\") for -a",
1210111398Sgad					    mkdirpath);
1211111398Sgad			}
121259004Shm		}
121359004Shm		if (*s == '\0')
121459004Shm			break;
121559004Shm	}
1216111398Sgad	if (verbose)
1217111398Sgad		printf("created directory '%s' for -a\n", dirpart);
121859004Shm}
121959004Shm
122059004Shm/*-
122159004Shm * Parse a cyclic time specification, the format is as follows:
122259004Shm *
122359004Shm *	[Dhh] or [Wd[Dhh]] or [Mdd[Dhh]]
122459004Shm *
122559004Shm * to rotate a logfile cyclic at
122659004Shm *
122759004Shm *	- every day (D) within a specific hour (hh)	(hh = 0...23)
122859004Shm *	- once a week (W) at a specific day (d)     OR	(d = 0..6, 0 = Sunday)
122959004Shm *	- once a month (M) at a specific day (d)	(d = 1..31,l|L)
123059004Shm *
123159004Shm * We don't accept a timezone specification; missing fields
123259004Shm * are defaulted to the current date but time zero.
123359004Shm */
123459004Shmstatic time_t
123593659ScjcparseDWM(char *s, char *errline)
123659004Shm{
123759004Shm	char *t;
123893659Scjc	time_t tsecs;
123959004Shm	struct tm tm, *tmp;
124080742Sobrien	long l;
124159004Shm	int nd;
124259004Shm	static int mtab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
124359004Shm	int WMseen = 0;
124459004Shm	int Dseen = 0;
124559004Shm
124659004Shm	tmp = localtime(&timenow);
124759004Shm	tm = *tmp;
124859004Shm
124959004Shm	/* set no. of days per month */
125059004Shm
125159004Shm	nd = mtab[tm.tm_mon];
125259004Shm
125359004Shm	if (tm.tm_mon == 1) {
125459004Shm		if (((tm.tm_year + 1900) % 4 == 0) &&
125559004Shm		    ((tm.tm_year + 1900) % 100 != 0) &&
125659004Shm		    ((tm.tm_year + 1900) % 400 == 0)) {
125759004Shm			nd++;	/* leap year, 29 days in february */
125859004Shm		}
125959004Shm	}
126059004Shm	tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
126159004Shm
126259004Shm	for (;;) {
126359004Shm		switch (*s) {
126459004Shm		case 'D':
126559004Shm			if (Dseen)
1266111392Sgad				return (-1);
126759004Shm			Dseen++;
126859004Shm			s++;
126980742Sobrien			l = strtol(s, &t, 10);
127080742Sobrien			if (l < 0 || l > 23)
1271111392Sgad				return (-1);
127280742Sobrien			tm.tm_hour = l;
127359004Shm			break;
127459004Shm
127559004Shm		case 'W':
127659004Shm			if (WMseen)
1277111392Sgad				return (-1);
127859004Shm			WMseen++;
127959004Shm			s++;
128080742Sobrien			l = strtol(s, &t, 10);
128180742Sobrien			if (l < 0 || l > 6)
1282111392Sgad				return (-1);
128380742Sobrien			if (l != tm.tm_wday) {
128459004Shm				int save;
128559004Shm
128680742Sobrien				if (l < tm.tm_wday) {
128759004Shm					save = 6 - tm.tm_wday;
128880742Sobrien					save += (l + 1);
128959004Shm				} else {
129080742Sobrien					save = l - tm.tm_wday;
129159004Shm				}
129259004Shm
129359004Shm				tm.tm_mday += save;
129459004Shm
129559004Shm				if (tm.tm_mday > nd) {
129659004Shm					tm.tm_mon++;
129759004Shm					tm.tm_mday = tm.tm_mday - nd;
129859004Shm				}
129959004Shm			}
130059004Shm			break;
130159004Shm
130259004Shm		case 'M':
130359004Shm			if (WMseen)
1304111392Sgad				return (-1);
130559004Shm			WMseen++;
130659004Shm			s++;
130759004Shm			if (tolower(*s) == 'l') {
130859004Shm				tm.tm_mday = nd;
130959004Shm				s++;
131059004Shm				t = s;
131159004Shm			} else {
131280742Sobrien				l = strtol(s, &t, 10);
131380742Sobrien				if (l < 1 || l > 31)
1314111392Sgad					return (-1);
131559004Shm
131680742Sobrien				if (l > nd)
1317111392Sgad					return (-1);
131880742Sobrien				tm.tm_mday = l;
131959004Shm			}
132059004Shm			break;
132159004Shm
132259004Shm		default:
132359004Shm			return (-1);
132459004Shm			break;
132559004Shm		}
132659004Shm
132759004Shm		if (*t == '\0' || isspace(*t))
132859004Shm			break;
132959004Shm		else
133059004Shm			s = t;
133159004Shm	}
133293659Scjc	if ((tsecs = mktime(&tm)) == -1)
133399209Smaxim		errx(1, "nonexistent time:\n%s", errline);
1334111392Sgad	return (tsecs);
133559004Shm}
1336