newsyslog.c revision 111388
159243Sobrien/*
259243Sobrien * This file contains changes from the Open Software Foundation.
359243Sobrien */
459243Sobrien
559243Sobrien/*
659243Sobrien * Copyright 1988, 1989 by the Massachusetts Institute of Technology
759243Sobrien *
859243Sobrien * Permission to use, copy, modify, and distribute this software and its
959243Sobrien * documentation for any purpose and without fee is hereby granted, provided
1059243Sobrien * that the above copyright notice appear in all copies and that both that
1159243Sobrien * copyright notice and this permission notice appear in supporting
1259243Sobrien * documentation, and that the names of M.I.T. and the M.I.T. S.I.P.B. not be
1359243Sobrien * used in advertising or publicity pertaining to distribution of the
1459243Sobrien * software without specific, written prior permission. M.I.T. and the M.I.T.
1559243Sobrien * S.I.P.B. make no representations about the suitability of this software
16100616Smp * for any purpose.  It is provided "as is" without express or implied
1759243Sobrien * warranty.
1859243Sobrien *
1959243Sobrien */
2059243Sobrien
2159243Sobrien/*
2259243Sobrien * newsyslog - roll over selected logs at the appropriate time, keeping the a
2359243Sobrien * specified number of backup files around.
2459243Sobrien */
2559243Sobrien
2659243Sobrien#ifndef lint
2759243Sobrienstatic const char rcsid[] =
2859243Sobrien"$FreeBSD: head/usr.sbin/newsyslog/newsyslog.c 111388 2003-02-24 00:51:41Z gad $";
2959243Sobrien#endif	/* not lint */
3059243Sobrien
3159243Sobrien#define OSF
3259243Sobrien#ifndef COMPRESS_POSTFIX
3359243Sobrien#define COMPRESS_POSTFIX ".gz"
3459243Sobrien#endif
3559243Sobrien#ifndef	BZCOMPRESS_POSTFIX
3659243Sobrien#define	BZCOMPRESS_POSTFIX ".bz2"
3759243Sobrien#endif
3859243Sobrien
3959243Sobrien#include <sys/param.h>
4059243Sobrien#include <sys/stat.h>
4159243Sobrien#include <sys/wait.h>
4259243Sobrien
4359243Sobrien#include <ctype.h>
4459243Sobrien#include <err.h>
4559243Sobrien#include <errno.h>
4659243Sobrien#include <fcntl.h>
4759243Sobrien#include <glob.h>
4859243Sobrien#include <grp.h>
4959243Sobrien#include <paths.h>
5059243Sobrien#include <pwd.h>
5159243Sobrien#include <signal.h>
5259243Sobrien#include <stdio.h>
5359243Sobrien#include <stdlib.h>
5459243Sobrien#include <string.h>
5559243Sobrien#include <time.h>
5659243Sobrien#include <unistd.h>
5759243Sobrien
5859243Sobrien#include "pathnames.h"
5959243Sobrien
6059243Sobrien#define kbytes(size)  (((size) + 1023) >> 10)
6159243Sobrien
6269408Sache#ifdef _IBMR2
6359243Sobrien/* Calculates (db * DEV_BSIZE) */
6469408Sache#define dbtob(db)  ((unsigned)(db) << UBSHIFT)
6559243Sobrien#endif
6659243Sobrien
6759243Sobrien#define CE_COMPACT 1		/* Compact the achived log files */
6859243Sobrien#define CE_BINARY  2		/* Logfile is in binary, don't add */
6959243Sobrien#define CE_BZCOMPACT 8		/* Compact the achived log files with bzip2 */
7059243Sobrien				/*  status messages */
7159243Sobrien#define	CE_TRIMAT  4		/* trim at a specific time */
7259243Sobrien#define	CE_GLOB    16		/* name of the log is file name pattern */
7359243Sobrien#define	CE_COMPACTWAIT 32	/* wait till compressing finishes before */
7459243Sobrien				/* starting the next one */
7559243Sobrien
7659243Sobrien#define NONE -1
7759243Sobrien
7859243Sobrienstruct conf_entry {
7959243Sobrien	char *log;		/* Name of the log */
8059243Sobrien	char *pid_file;		/* PID file */
8159243Sobrien	int uid;		/* Owner of log */
8259243Sobrien	int gid;		/* Group of log */
8359243Sobrien	int numlogs;		/* Number of logs to keep */
8459243Sobrien	int size;		/* Size cutoff to trigger trimming the log */
8559243Sobrien	int hours;		/* Hours between log trimming */
8659243Sobrien	time_t trim_at;		/* Specific time to do trimming */
8759243Sobrien	int permissions;	/* File permissions on the log */
8859243Sobrien	int flags;		/* CE_COMPACT, CE_BZCOMPACT, CE_BINARY */
8959243Sobrien	int sig;		/* Signal to send */
9059243Sobrien	int def_cfg;		/* Using the <default> rule for this file */
9159243Sobrien	struct conf_entry *next;/* Linked list pointer */
9259243Sobrien};
9359243Sobrien
9459243Sobrien#define DEFAULT_MARKER "<default>"
9559243Sobrien
9659243Sobrienint archtodir = 0;		/* Archive old logfiles to other directory */
9759243Sobrienint verbose = 0;		/* Print out what's going on */
9859243Sobrienint needroot = 1;		/* Root privs are necessary */
9959243Sobrienint noaction = 0;		/* Don't do anything, just show it */
10059243Sobrienint force = 0;			/* Force the trim no matter what */
10159243Sobrienchar *archdirname;		/* Directory path to old logfiles archive */
10259243Sobrienconst char *conf = _PATH_CONF;	/* Configuration file to use */
10359243Sobrientime_t timenow;
10459243Sobrien
10559243Sobrien#define MIN_PID         5
10659243Sobrien#define MAX_PID		99999	/* was lower, see /usr/include/sys/proc.h */
10759243Sobrienchar hostname[MAXHOSTNAMELEN];	/* hostname */
10859243Sobrienchar daytime[16];		/* timenow in human readable form */
10959243Sobrien
11059243Sobrienstatic struct conf_entry *parse_file(char **files);
11159243Sobrienstatic char *sob(char *p);
11259243Sobrienstatic char *son(char *p);
11359243Sobrienstatic char *missing_field(char *p, char *errline);
11459243Sobrienstatic void do_entry(struct conf_entry * ent);
11559243Sobrienstatic void free_entry(struct conf_entry *ent);
11659243Sobrienstatic struct conf_entry *init_entry(const char *fname,
11759243Sobrien		struct conf_entry *src_entry);
11859243Sobrienstatic void PRS(int argc, char **argv);
11959243Sobrienstatic void usage(void);
12059243Sobrienstatic void dotrim(char *log, const char *pid_file, int numdays, int falgs,
12159243Sobrien		int perm, int owner_uid, int group_gid, int sig, int def_cfg);
12259243Sobrienstatic int log_trim(char *log, int def_cfg);
12359243Sobrienstatic void compress_log(char *log, int dowait);
12459243Sobrienstatic void bzcompress_log(char *log, int dowait);
12559243Sobrienstatic int sizefile(char *file);
12659243Sobrienstatic int age_old_log(char *file);
12759243Sobrienstatic pid_t get_pid(const char *pid_file);
12859243Sobrienstatic time_t parse8601(char *s, char *errline);
12959243Sobrienstatic void movefile(char *from, char *to, int perm, int owner_uid,
13059243Sobrien		int group_gid);
13159243Sobrienstatic void createdir(char *dirpart);
13259243Sobrienstatic time_t parseDWM(char *s, char *errline);
13359243Sobrien
13459243Sobrienint
13559243Sobrienmain(int argc, char **argv)
13659243Sobrien{
13759243Sobrien	struct conf_entry *p, *q;
13859243Sobrien	glob_t pglob;
13959243Sobrien	int i;
14059243Sobrien
14159243Sobrien	PRS(argc, argv);
14259243Sobrien	if (needroot && getuid() && geteuid())
14359243Sobrien		errx(1, "must have root privs");
14459243Sobrien	p = q = parse_file(argv + optind);
14559243Sobrien
14659243Sobrien	while (p) {
14759243Sobrien		if ((p->flags & CE_GLOB) == 0) {
14859243Sobrien			do_entry(p);
149131962Smp		} else {
150131962Smp			if (glob(p->log, GLOB_NOCHECK, NULL, &pglob) != 0) {
15159243Sobrien				warn("can't expand pattern: %s", p->log);
15259243Sobrien			} else {
15359243Sobrien				for (i = 0; i < pglob.gl_matchc; i++) {
15459243Sobrien					p->log = pglob.gl_pathv[i];
15559243Sobrien					do_entry(p);
15659243Sobrien				}
15759415Sobrien				globfree(&pglob);
15859415Sobrien			}
15959415Sobrien		}
16059415Sobrien		p = p->next;
161131962Smp		free_entry(q);
162131962Smp		q = p;
163131962Smp	}
164131962Smp	while (wait(NULL) > 0 || errno == EINTR)
165131962Smp		;
166131962Smp	return (0);
167131962Smp}
16859243Sobrien
16959243Sobrienstatic struct conf_entry *
17059243Sobrieninit_entry(const char *fname, struct conf_entry *src_entry)
17159243Sobrien{
17259243Sobrien	struct conf_entry *tempwork;
17359243Sobrien
17459243Sobrien	if (verbose > 4)
17559243Sobrien		printf("\t--> [creating entry for %s]\n", fname);
17659243Sobrien
17759243Sobrien	tempwork = malloc(sizeof(struct conf_entry));
17859243Sobrien	if (tempwork == NULL)
17959243Sobrien		err(1, "malloc of conf_entry for %s", fname);
18059243Sobrien
18159243Sobrien	tempwork->log = strdup(fname);
18259243Sobrien	if (tempwork->log == NULL)
18359243Sobrien		err(1, "strdup for %s", fname);
18459243Sobrien
18559243Sobrien	if (src_entry != NULL) {
18659243Sobrien		tempwork->pid_file = NULL;
18759243Sobrien		if (src_entry->pid_file)
18859243Sobrien			tempwork->pid_file = strdup(src_entry->pid_file);
18959243Sobrien		tempwork->uid = src_entry->uid;
19059243Sobrien		tempwork->gid = src_entry->gid;
19159243Sobrien		tempwork->numlogs = src_entry->numlogs;
19259243Sobrien		tempwork->size = src_entry->size;
19359243Sobrien		tempwork->hours = src_entry->hours;
19459243Sobrien		tempwork->trim_at = src_entry->trim_at;
19559243Sobrien		tempwork->permissions = src_entry->permissions;
19659243Sobrien		tempwork->flags = src_entry->flags;
19759243Sobrien		tempwork->sig = src_entry->sig;
19859243Sobrien		tempwork->def_cfg = src_entry->def_cfg;
19959243Sobrien	} else {
20059243Sobrien		/* Initialize as a "do-nothing" entry */
20159243Sobrien		tempwork->pid_file = NULL;
20259243Sobrien		tempwork->uid = NONE;
20359243Sobrien		tempwork->gid = NONE;
20459243Sobrien		tempwork->numlogs = 1;
20559243Sobrien		tempwork->size = -1;
20659243Sobrien		tempwork->hours = -1;
20759243Sobrien		tempwork->trim_at = (time_t)0;
20859243Sobrien		tempwork->permissions = 0;
20959243Sobrien		tempwork->flags = 0;
21059243Sobrien		tempwork->sig = SIGHUP;
21159243Sobrien		tempwork->def_cfg = 0;
21259243Sobrien	}
21359243Sobrien	tempwork->next = NULL;
21459243Sobrien
21559243Sobrien	return (tempwork);
21659243Sobrien}
21759243Sobrien
21859243Sobrienstatic void
21959243Sobrienfree_entry(struct conf_entry *ent)
22059243Sobrien{
22159243Sobrien
22259243Sobrien	if (ent == NULL)
22359243Sobrien		return;
22459243Sobrien
22559243Sobrien	if (ent->log != NULL) {
22659243Sobrien		if (verbose > 4)
22759243Sobrien			printf("\t--> [freeing entry for %s]\n", ent->log);
22859243Sobrien		free(ent->log);
22959243Sobrien		ent->log = NULL;
23059243Sobrien	}
23159243Sobrien
23259243Sobrien	if (ent->pid_file != NULL) {
23359243Sobrien		free(ent->pid_file);
23459243Sobrien		ent->pid_file = NULL;
23559243Sobrien	}
23659243Sobrien
23759243Sobrien	free(ent);
23859243Sobrien}
23959243Sobrien
24059243Sobrienstatic void
24159243Sobriendo_entry(struct conf_entry * ent)
24259243Sobrien{
24359243Sobrien	int size, modtime;
24459243Sobrien	const char *pid_file;
24559243Sobrien
24659243Sobrien	if (verbose) {
24759243Sobrien		if (ent->flags & CE_COMPACT)
24859243Sobrien			printf("%s <%dZ>: ", ent->log, ent->numlogs);
24959243Sobrien		else if (ent->flags & CE_BZCOMPACT)
25059243Sobrien			printf("%s <%dJ>: ", ent->log, ent->numlogs);
25159243Sobrien		else
25259243Sobrien			printf("%s <%d>: ", ent->log, ent->numlogs);
25359243Sobrien	}
25459243Sobrien	size = sizefile(ent->log);
25559243Sobrien	modtime = age_old_log(ent->log);
25659243Sobrien	if (size < 0) {
25759243Sobrien		if (verbose)
25859243Sobrien			printf("does not exist.\n");
25959243Sobrien	} else {
26059243Sobrien		if (ent->flags & CE_TRIMAT && !force) {
26159243Sobrien			if (timenow < ent->trim_at
26259243Sobrien			    || difftime(timenow, ent->trim_at) >= 60 * 60) {
26359243Sobrien				if (verbose)
26459243Sobrien					printf("--> will trim at %s",
26559243Sobrien					    ctime(&ent->trim_at));
26659243Sobrien				return;
26759243Sobrien			} else if (verbose && ent->hours <= 0) {
26859243Sobrien				printf("--> time is up\n");
26959243Sobrien			}
27059243Sobrien		}
27159243Sobrien		if (verbose && (ent->size > 0))
27259243Sobrien			printf("size (Kb): %d [%d] ", size, ent->size);
27359243Sobrien		if (verbose && (ent->hours > 0))
27459243Sobrien			printf(" age (hr): %d [%d] ", modtime, ent->hours);
27559243Sobrien		if (force || ((ent->size > 0) && (size >= ent->size)) ||
27659243Sobrien		    (ent->hours <= 0 && (ent->flags & CE_TRIMAT)) ||
27759243Sobrien		    ((ent->hours > 0) && ((modtime >= ent->hours)
27859243Sobrien			    || (modtime < 0)))) {
27959243Sobrien			if (verbose)
28059243Sobrien				printf("--> trimming log....\n");
28159243Sobrien			if (noaction && !verbose) {
28259243Sobrien				if (ent->flags & CE_COMPACT)
28359243Sobrien					printf("%s <%dZ>: trimming\n",
28459243Sobrien					    ent->log, ent->numlogs);
28559243Sobrien				else if (ent->flags & CE_BZCOMPACT)
28659243Sobrien					printf("%s <%dJ>: trimming\n",
28759243Sobrien					    ent->log, ent->numlogs);
28859243Sobrien				else
28959243Sobrien					printf("%s <%d>: trimming\n",
29059243Sobrien					    ent->log, ent->numlogs);
29159243Sobrien			}
29259243Sobrien			if (ent->pid_file) {
29359243Sobrien				pid_file = ent->pid_file;
29459243Sobrien			} else {
29559243Sobrien				/* Only try to notify syslog if we are root */
29659243Sobrien				if (needroot)
29759243Sobrien					pid_file = _PATH_SYSLOGPID;
29859243Sobrien				else
29959243Sobrien					pid_file = NULL;
30059243Sobrien			}
30159243Sobrien			dotrim(ent->log, pid_file, ent->numlogs,
30259243Sobrien			    ent->flags, ent->permissions, ent->uid, ent->gid,
30359243Sobrien			    ent->sig, ent->def_cfg);
30459243Sobrien		} else {
30559243Sobrien			if (verbose)
30659243Sobrien				printf("--> skipping\n");
30759243Sobrien		}
30859243Sobrien	}
30959243Sobrien}
31059243Sobrien
31159243Sobrienstatic void
31259243SobrienPRS(int argc, char **argv)
31359243Sobrien{
31459243Sobrien	int c;
31559243Sobrien	char *p;
31659243Sobrien
31759243Sobrien	timenow = time((time_t *) 0);
31859243Sobrien	(void)strncpy(daytime, ctime(&timenow) + 4, 15);
31959243Sobrien	daytime[15] = '\0';
32059243Sobrien
32159243Sobrien	/* Let's get our hostname */
32259243Sobrien	(void) gethostname(hostname, sizeof(hostname));
32359243Sobrien
32459243Sobrien	/* Truncate domain */
32559243Sobrien	if ((p = strchr(hostname, '.'))) {
32659243Sobrien		*p = '\0';
32759243Sobrien	}
32859243Sobrien	while ((c = getopt(argc, argv, "nrvFf:a:")) != -1)
32959243Sobrien		switch (c) {
33059243Sobrien		case 'n':
33159243Sobrien			noaction++;
33259243Sobrien			break;
33359243Sobrien		case 'a':
33459243Sobrien			archtodir++;
33559243Sobrien			archdirname = optarg;
33659243Sobrien			break;
33759243Sobrien		case 'r':
33859243Sobrien			needroot = 0;
33959243Sobrien			break;
34059243Sobrien		case 'v':
34159243Sobrien			verbose++;
34259243Sobrien			break;
34359243Sobrien		case 'f':
34459243Sobrien			conf = optarg;
34559243Sobrien			break;
34659243Sobrien		case 'F':
34759243Sobrien			force++;
34859243Sobrien			break;
34959243Sobrien		default:
35059243Sobrien			usage();
35159243Sobrien		}
35259243Sobrien}
35359243Sobrien
35459243Sobrienstatic void
35559243Sobrienusage(void)
35659243Sobrien{
35759243Sobrien
35859415Sobrien	fprintf(stderr,
35959415Sobrien	    "usage: newsyslog [-Fnrv] [-f config-file] [-a directory] [ filename ... ]\n");
36059415Sobrien	exit(1);
36159415Sobrien}
36259415Sobrien
36359415Sobrien/*
36459415Sobrien * Parse a configuration file and return a linked list of all the logs to
36559415Sobrien * process
36659243Sobrien */
36759243Sobrienstatic struct conf_entry *
36859243Sobrienparse_file(char **files)
36959243Sobrien{
37059243Sobrien	FILE *f;
37159243Sobrien	char line[BUFSIZ], *parse, *q;
37259243Sobrien	char *cp, *errline, *group;
37359243Sobrien	char **given;
37459243Sobrien	struct conf_entry *defconf, *first, *working, *worklist;
37559243Sobrien	struct passwd *pass;
37659243Sobrien	struct group *grp;
37759243Sobrien	int eol;
37859243Sobrien
37959243Sobrien	defconf = first = working = worklist = NULL;
38059243Sobrien
38159243Sobrien	if (strcmp(conf, "-"))
38259243Sobrien		f = fopen(conf, "r");
38359243Sobrien	else
38459243Sobrien		f = stdin;
38559415Sobrien	if (!f)
38659415Sobrien		err(1, "%s", conf);
38759415Sobrien	while (fgets(line, BUFSIZ, f)) {
38859415Sobrien		if ((line[0] == '\n') || (line[0] == '#') ||
38959415Sobrien		    (strlen(line) == 0))
39059415Sobrien			continue;
39159415Sobrien		errline = strdup(line);
39259415Sobrien		for (cp = line + 1; *cp != '\0'; cp++) {
39359243Sobrien			if (*cp != '#')
39459243Sobrien				continue;
39559243Sobrien			if (*(cp - 1) == '\\') {
39659243Sobrien				strcpy(cp - 1, cp);
39759243Sobrien				cp--;
39859243Sobrien				continue;
39959243Sobrien			}
40059243Sobrien			*cp = '\0';
40159243Sobrien			break;
40259243Sobrien		}
40359243Sobrien
40459243Sobrien		q = parse = missing_field(sob(line), errline);
40559243Sobrien		parse = son(line);
40659243Sobrien		if (!*parse)
40759243Sobrien			errx(1, "malformed line (missing fields):\n%s",
40859243Sobrien			    errline);
40959243Sobrien		*parse = '\0';
41059243Sobrien
41159243Sobrien		/*
41259243Sobrien		 * If newsyslog was run with a list of specific filenames,
41359243Sobrien		 * then this line of the config file should be skipped if
41459243Sobrien		 * it is NOT one of those given files (except that we do
41559243Sobrien		 * want any line that defines the <default> action).
41659243Sobrien		 *
41759243Sobrien		 * XXX - note that CE_GLOB processing is *NOT* done when
41859243Sobrien		 *       trying to match a filename given on the command!
41959243Sobrien		 */
42059243Sobrien		if (*files) {
42159243Sobrien			if (strcasecmp(DEFAULT_MARKER, q) != 0) {
42259243Sobrien				for (given = files; *given; ++given) {
42359243Sobrien					if (strcmp(*given, q) == 0)
42459243Sobrien						break;
42559243Sobrien				}
42659243Sobrien				if (!*given)
42759243Sobrien					continue;
42859243Sobrien			}
42959243Sobrien			if (verbose > 2)
43059243Sobrien				printf("\t+ Matched entry %s\n", q);
43159243Sobrien		} else {
43259243Sobrien			/*
43359243Sobrien			 * If no files were specified on the command line,
43459243Sobrien			 * then we can skip any line which defines the
43559243Sobrien			 * default action.
43659243Sobrien			 */
43759243Sobrien			if (strcasecmp(DEFAULT_MARKER, q) == 0) {
43859243Sobrien				if (verbose > 2)
43959243Sobrien					printf("\t+ Ignoring entry for %s\n",
44059243Sobrien					    q);
44159243Sobrien				continue;
44259243Sobrien			}
44359243Sobrien		}
44459243Sobrien
44559243Sobrien		working = init_entry(q, NULL);
44659243Sobrien		if (strcasecmp(DEFAULT_MARKER, q) == 0) {
44759243Sobrien			if (defconf != NULL) {
44859243Sobrien				warnx("Ignoring duplicate entry for %s!", q);
44959243Sobrien				free_entry(working);
45059243Sobrien				continue;
45159243Sobrien			}
45259243Sobrien			defconf = working;
45359243Sobrien		} else {
45459243Sobrien			if (!first)
45559243Sobrien				first = working;
45659243Sobrien			else
45759243Sobrien				worklist->next = working;
45859243Sobrien			worklist = working;
45959243Sobrien		}
46059243Sobrien
46159243Sobrien		q = parse = missing_field(sob(++parse), errline);
46259243Sobrien		parse = son(parse);
46359243Sobrien		if (!*parse)
46459243Sobrien			errx(1, "malformed line (missing fields):\n%s",
46559243Sobrien			    errline);
46659243Sobrien		*parse = '\0';
46759243Sobrien		if ((group = strchr(q, ':')) != NULL ||
46859243Sobrien		    (group = strrchr(q, '.')) != NULL) {
46959243Sobrien			*group++ = '\0';
47059243Sobrien			if (*q) {
47159243Sobrien				if (!(isnumber(*q))) {
47259243Sobrien					if ((pass = getpwnam(q)) == NULL)
47359243Sobrien						errx(1,
47459243Sobrien				     "error in config file; unknown user:\n%s",
47559243Sobrien						    errline);
47659243Sobrien					working->uid = pass->pw_uid;
47759243Sobrien				} else
47859415Sobrien					working->uid = atoi(q);
47959243Sobrien			} else
48059243Sobrien				working->uid = NONE;
48159243Sobrien
48259243Sobrien			q = group;
48359243Sobrien			if (*q) {
48459243Sobrien				if (!(isnumber(*q))) {
48559243Sobrien					if ((grp = getgrnam(q)) == NULL)
48659243Sobrien						errx(1,
48759243Sobrien				    "error in config file; unknown group:\n%s",
48859243Sobrien						    errline);
48959243Sobrien					working->gid = grp->gr_gid;
49059243Sobrien				} else
49159243Sobrien					working->gid = atoi(q);
49259243Sobrien			} else
49359243Sobrien				working->gid = NONE;
49459243Sobrien
49559243Sobrien			q = parse = missing_field(sob(++parse), errline);
49659243Sobrien			parse = son(parse);
49759243Sobrien			if (!*parse)
49859243Sobrien				errx(1, "malformed line (missing fields):\n%s",
49959243Sobrien				    errline);
50059243Sobrien			*parse = '\0';
50159243Sobrien		} else
50259243Sobrien			working->uid = working->gid = NONE;
50359243Sobrien
50459243Sobrien		if (!sscanf(q, "%o", &working->permissions))
50559243Sobrien			errx(1, "error in config file; bad permissions:\n%s",
50659243Sobrien			    errline);
50759243Sobrien
50859243Sobrien		q = parse = missing_field(sob(++parse), errline);
50959243Sobrien		parse = son(parse);
51059243Sobrien		if (!*parse)
51159243Sobrien			errx(1, "malformed line (missing fields):\n%s",
51259243Sobrien			    errline);
51359243Sobrien		*parse = '\0';
51459243Sobrien		if (!sscanf(q, "%d", &working->numlogs))
51559243Sobrien			errx(1, "error in config file; bad number:\n%s",
51659243Sobrien			    errline);
51759243Sobrien
51859243Sobrien		q = parse = missing_field(sob(++parse), errline);
51959243Sobrien		parse = son(parse);
52059243Sobrien		if (!*parse)
52159243Sobrien			errx(1, "malformed line (missing fields):\n%s",
52259243Sobrien			    errline);
52359243Sobrien		*parse = '\0';
52459243Sobrien		if (isdigit(*q))
52559243Sobrien			working->size = atoi(q);
52659243Sobrien		else
52759243Sobrien			working->size = -1;
52859243Sobrien
52959243Sobrien		working->flags = 0;
53059243Sobrien		q = parse = missing_field(sob(++parse), errline);
53159243Sobrien		parse = son(parse);
53259243Sobrien		eol = !*parse;
53359243Sobrien		*parse = '\0';
53459243Sobrien		{
53559243Sobrien			char *ep;
53659243Sobrien			u_long ul;
53759243Sobrien
53859243Sobrien			ul = strtoul(q, &ep, 10);
53959243Sobrien			if (ep == q)
54059243Sobrien				working->hours = 0;
54159243Sobrien			else if (*ep == '*')
54259243Sobrien				working->hours = -1;
54359243Sobrien			else if (ul > INT_MAX)
54459243Sobrien				errx(1, "interval is too large:\n%s", errline);
54559243Sobrien			else
54659243Sobrien				working->hours = ul;
54759243Sobrien
54859243Sobrien			if (*ep != '\0' && *ep != '@' && *ep != '*' &&
54959243Sobrien			    *ep != '$')
55059243Sobrien				errx(1, "malformed interval/at:\n%s", errline);
55159243Sobrien			if (*ep == '@') {
55259243Sobrien				if ((working->trim_at = parse8601(ep + 1, errline))
55359243Sobrien				    == (time_t) - 1)
55459243Sobrien					errx(1, "malformed at:\n%s", errline);
55559243Sobrien				working->flags |= CE_TRIMAT;
55659243Sobrien			} else if (*ep == '$') {
55759243Sobrien				if ((working->trim_at = parseDWM(ep + 1, errline))
55859243Sobrien				    == (time_t) - 1)
55959243Sobrien					errx(1, "malformed at:\n%s", errline);
56059243Sobrien				working->flags |= CE_TRIMAT;
56159243Sobrien			}
56259243Sobrien		}
56359243Sobrien
56459243Sobrien		if (eol)
56559243Sobrien			q = NULL;
56659243Sobrien		else {
56759243Sobrien			q = parse = sob(++parse);	/* Optional field */
56859243Sobrien			parse = son(parse);
56959243Sobrien			if (!*parse)
57059243Sobrien				eol = 1;
57159243Sobrien			*parse = '\0';
57259243Sobrien		}
57359243Sobrien
57459243Sobrien		while (q && *q && !isspace(*q)) {
57559243Sobrien			if ((*q == 'Z') || (*q == 'z'))
57659243Sobrien				working->flags |= CE_COMPACT;
57759243Sobrien			else if ((*q == 'J') || (*q == 'j'))
57859243Sobrien				working->flags |= CE_BZCOMPACT;
57959243Sobrien			else if ((*q == 'B') || (*q == 'b'))
58059243Sobrien				working->flags |= CE_BINARY;
58159243Sobrien			else if ((*q == 'G') || (*q == 'c'))
58259243Sobrien				working->flags |= CE_GLOB;
58359243Sobrien			else if ((*q == 'W') || (*q == 'w'))
58459243Sobrien				working->flags |= CE_COMPACTWAIT;
58559243Sobrien			else if (*q != '-')
58659243Sobrien				errx(1, "illegal flag in config file -- %c",
58759243Sobrien				    *q);
58859243Sobrien			q++;
58959243Sobrien		}
59059243Sobrien
59159243Sobrien		if (eol)
59259243Sobrien			q = NULL;
59359243Sobrien		else {
59459243Sobrien			q = parse = sob(++parse);	/* Optional field */
59559243Sobrien			parse = son(parse);
59659243Sobrien			if (!*parse)
59759243Sobrien				eol = 1;
59859243Sobrien			*parse = '\0';
59959243Sobrien		}
60059243Sobrien
60159243Sobrien		working->pid_file = NULL;
60259243Sobrien		if (q && *q) {
60359243Sobrien			if (*q == '/')
60459243Sobrien				working->pid_file = strdup(q);
60559243Sobrien			else if (isdigit(*q))
60659243Sobrien				goto got_sig;
60759243Sobrien			else
60859243Sobrien				errx(1,
60959243Sobrien			"illegal pid file or signal number in config file:\n%s",
61059243Sobrien				    errline);
61159243Sobrien		}
61259243Sobrien		if (eol)
61359243Sobrien			q = NULL;
61459243Sobrien		else {
61559243Sobrien			q = parse = sob(++parse);	/* Optional field */
61659243Sobrien			*(parse = son(parse)) = '\0';
61759243Sobrien		}
61859243Sobrien
61959243Sobrien		working->sig = SIGHUP;
62059243Sobrien		if (q && *q) {
62159243Sobrien			if (isdigit(*q)) {
62259243Sobrien		got_sig:
62359243Sobrien				working->sig = atoi(q);
62459243Sobrien			} else {
62559243Sobrien		err_sig:
62659243Sobrien				errx(1,
62759243Sobrien				    "illegal signal number in config file:\n%s",
62859243Sobrien				    errline);
62959243Sobrien			}
63059243Sobrien			if (working->sig < 1 || working->sig >= NSIG)
63159243Sobrien				goto err_sig;
63259243Sobrien		}
63359243Sobrien		free(errline);
63459243Sobrien	}
63559243Sobrien	(void) fclose(f);
63659243Sobrien
63759243Sobrien	/*
63859243Sobrien	 * The entire config file has been processed.  If there were
63959243Sobrien	 * no specific files given on the run command, then the work
64059243Sobrien	 * of this routine is done.
64159243Sobrien	 */
64259243Sobrien	if (*files == NULL)
64359243Sobrien		return (first);
64459243Sobrien
64559243Sobrien	/*
64659243Sobrien	 * If the program was given a specific list of files to process,
64759243Sobrien	 * it may be that some of those files were not listed in the
64859243Sobrien	 * config file.  Those unlisted files should get the default
64959243Sobrien	 * rotation action.  First, create the default-rotation action
65059243Sobrien	 * if none was found in the config file.
65159243Sobrien	 */
65259243Sobrien	if (defconf == NULL) {
65359243Sobrien		working = init_entry(DEFAULT_MARKER, NULL);
65459243Sobrien		working->numlogs = 3;
65559243Sobrien		working->size = 50;
65659243Sobrien		working->permissions = S_IRUSR|S_IWUSR;
65759243Sobrien		defconf = working;
65859243Sobrien	}
65959243Sobrien
66059243Sobrien	for (given = files; *given; ++given) {
66159243Sobrien		for (working = first; working; working = working->next) {
66259243Sobrien			if (strcmp(*given, working->log) == 0)
66359243Sobrien				break;
66459243Sobrien		}
66559243Sobrien		if (working != NULL)
66659243Sobrien			continue;
66759243Sobrien		if (verbose > 2)
66859243Sobrien			printf("\t+ No entry for %s  (will use %s)\n",
66959243Sobrien			    *given, DEFAULT_MARKER);
67059243Sobrien		/*
67159243Sobrien		 * This given file was not found in the config file.
67259243Sobrien		 * Add another item on to our work list, based on the
67359243Sobrien		 * default entry.
67459243Sobrien		 */
67559243Sobrien		working = init_entry(*given, defconf);
67659243Sobrien		if (!first)
67759243Sobrien			first = working;
67859243Sobrien		else
67959243Sobrien			worklist->next = working;
68059243Sobrien		/* This is a file that was *not* found in config file */
68159243Sobrien		working->def_cfg = 1;
68259243Sobrien		worklist = working;
68359243Sobrien	}
68459243Sobrien
68559243Sobrien	free_entry(defconf);
68659243Sobrien	return (first);
68759243Sobrien}
68859243Sobrien
68959243Sobrienstatic char *
69059243Sobrienmissing_field(char *p, char *errline)
69159243Sobrien{
69259243Sobrien
69359243Sobrien	if (!p || !*p)
69459243Sobrien		errx(1, "missing field in config file:\n%s", errline);
69559243Sobrien	return (p);
69659243Sobrien}
69759243Sobrien
69859243Sobrienstatic void
69959243Sobriendotrim(char *log, const char *pid_file, int numdays, int flags, int perm,
70059243Sobrien    int owner_uid, int group_gid, int sig, int def_cfg)
70159243Sobrien{
70259243Sobrien	char dirpart[MAXPATHLEN], namepart[MAXPATHLEN];
70359243Sobrien	char file1[MAXPATHLEN], file2[MAXPATHLEN];
70459243Sobrien	char zfile1[MAXPATHLEN], zfile2[MAXPATHLEN];
70559243Sobrien	char jfile1[MAXPATHLEN];
70659243Sobrien	char tfile[MAXPATHLEN];
70759243Sobrien	int notified, need_notification, fd, _numdays;
70859243Sobrien	struct stat st;
709131962Smp	pid_t pid;
710131962Smp
71159243Sobrien#ifdef _IBMR2
71259243Sobrien	/*
71359243Sobrien	 * AIX 3.1 has a broken fchown- if the owner_uid is -1, it will
71459243Sobrien	 * actually change it to be owned by uid -1, instead of leaving it
71559243Sobrien	 * as is, as it is supposed to.
71659243Sobrien	 */
71759243Sobrien	if (owner_uid == -1)
71859243Sobrien		owner_uid = geteuid();
71959243Sobrien#endif
72059243Sobrien
72159243Sobrien	if (archtodir) {
72259243Sobrien		char *p;
72359243Sobrien
72459243Sobrien		/* build complete name of archive directory into dirpart */
72559243Sobrien		if (*archdirname == '/') {	/* absolute */
72659243Sobrien			strlcpy(dirpart, archdirname, sizeof(dirpart));
72759243Sobrien		} else {	/* relative */
72859243Sobrien			/* get directory part of logfile */
72959243Sobrien			strlcpy(dirpart, log, sizeof(dirpart));
73059243Sobrien			if ((p = rindex(dirpart, '/')) == NULL)
73159243Sobrien				dirpart[0] = '\0';
73259243Sobrien			else
73359243Sobrien				*(p + 1) = '\0';
73459243Sobrien			strlcat(dirpart, archdirname, sizeof(dirpart));
73559243Sobrien		}
73659243Sobrien
73759243Sobrien		/* check if archive directory exists, if not, create it */
73859243Sobrien		if (lstat(dirpart, &st))
73959243Sobrien			createdir(dirpart);
74059243Sobrien
74159243Sobrien		/* get filename part of logfile */
74259243Sobrien		if ((p = rindex(log, '/')) == NULL)
74359243Sobrien			strlcpy(namepart, log, sizeof(namepart));
74459243Sobrien		else
74559243Sobrien			strlcpy(namepart, p + 1, sizeof(namepart));
746
747		/* name of oldest log */
748		(void) snprintf(file1, sizeof(file1), "%s/%s.%d", dirpart,
749		    namepart, numdays);
750		(void) snprintf(zfile1, sizeof(zfile1), "%s%s", file1,
751		    COMPRESS_POSTFIX);
752		snprintf(jfile1, sizeof(jfile1), "%s%s", file1,
753		    BZCOMPRESS_POSTFIX);
754	} else {
755		/* name of oldest log */
756		(void) snprintf(file1, sizeof(file1), "%s.%d", log, numdays);
757		(void) snprintf(zfile1, sizeof(zfile1), "%s%s", file1,
758		    COMPRESS_POSTFIX);
759		snprintf(jfile1, sizeof(jfile1), "%s%s", file1,
760		    BZCOMPRESS_POSTFIX);
761	}
762
763	if (noaction) {
764		printf("rm -f %s\n", file1);
765		printf("rm -f %s\n", zfile1);
766		printf("rm -f %s\n", jfile1);
767	} else {
768		(void) unlink(file1);
769		(void) unlink(zfile1);
770		(void) unlink(jfile1);
771	}
772
773	/* Move down log files */
774	_numdays = numdays;	/* preserve */
775	while (numdays--) {
776
777		(void) strlcpy(file2, file1, sizeof(file2));
778
779		if (archtodir)
780			(void) snprintf(file1, sizeof(file1), "%s/%s.%d",
781			    dirpart, namepart, numdays);
782		else
783			(void) snprintf(file1, sizeof(file1), "%s.%d", log,
784			    numdays);
785
786		(void) strlcpy(zfile1, file1, sizeof(zfile1));
787		(void) strlcpy(zfile2, file2, sizeof(zfile2));
788		if (lstat(file1, &st)) {
789			(void) strlcat(zfile1, COMPRESS_POSTFIX,
790			    sizeof(zfile1));
791			(void) strlcat(zfile2, COMPRESS_POSTFIX,
792			    sizeof(zfile2));
793			if (lstat(zfile1, &st)) {
794				strlcpy(zfile1, file1, sizeof(zfile1));
795				strlcpy(zfile2, file2, sizeof(zfile2));
796				strlcat(zfile1, BZCOMPRESS_POSTFIX,
797				    sizeof(zfile1));
798				strlcat(zfile2, BZCOMPRESS_POSTFIX,
799				    sizeof(zfile2));
800				if (lstat(zfile1, &st))
801					continue;
802			}
803		}
804		if (noaction) {
805			printf("mv %s %s\n", zfile1, zfile2);
806			printf("chmod %o %s\n", perm, zfile2);
807			printf("chown %d:%d %s\n",
808			    owner_uid, group_gid, zfile2);
809		} else {
810			(void) rename(zfile1, zfile2);
811			(void) chmod(zfile2, perm);
812			(void) chown(zfile2, owner_uid, group_gid);
813		}
814	}
815	if (!noaction && !(flags & CE_BINARY)) {
816		/* Report the trimming to the old log */
817		(void) log_trim(log, def_cfg);
818	}
819
820	if (!_numdays) {
821		if (noaction)
822			printf("rm %s\n", log);
823		else
824			(void) unlink(log);
825	} else {
826		if (noaction)
827			printf("mv %s to %s\n", log, file1);
828		else {
829			if (archtodir)
830				movefile(log, file1, perm, owner_uid,
831				    group_gid);
832			else
833				(void) rename(log, file1);
834		}
835	}
836
837	if (noaction)
838		printf("Start new log...");
839	else {
840		strlcpy(tfile, log, sizeof(tfile));
841		strlcat(tfile, ".XXXXXX", sizeof(tfile));
842		mkstemp(tfile);
843		fd = creat(tfile, perm);
844		if (fd < 0)
845			err(1, "can't start new log");
846		if (fchown(fd, owner_uid, group_gid))
847			err(1, "can't chmod new log file");
848		(void) close(fd);
849		if (!(flags & CE_BINARY)) {
850			/* Add status message to new log file */
851			if (log_trim(tfile, def_cfg))
852				err(1, "can't add status message to log");
853		}
854	}
855	if (noaction)
856		printf("chmod %o %s...\n", perm, log);
857	else {
858		(void) chmod(tfile, perm);
859		if (rename(tfile, log) < 0) {
860			err(1, "can't start new log");
861			(void) unlink(tfile);
862		}
863	}
864
865	pid = 0;
866	need_notification = notified = 0;
867	if (pid_file != NULL) {
868		need_notification = 1;
869		pid = get_pid(pid_file);
870	}
871	if (pid) {
872		if (noaction) {
873			notified = 1;
874			printf("kill -%d %d\n", sig, (int) pid);
875		} else if (kill(pid, sig))
876			warn("can't notify daemon, pid %d", (int) pid);
877		else {
878			notified = 1;
879			if (verbose)
880				printf("daemon pid %d notified\n", (int) pid);
881		}
882	}
883	if ((flags & CE_COMPACT) || (flags & CE_BZCOMPACT)) {
884		if (need_notification && !notified)
885			warnx(
886			    "log %s not compressed because daemon not notified",
887			    log);
888		else if (noaction)
889			printf("Compress %s.0\n", log);
890		else {
891			if (notified) {
892				if (verbose)
893					printf("small pause to allow daemon to close log\n");
894				sleep(10);
895			}
896			if (archtodir) {
897				(void) snprintf(file1, sizeof(file1), "%s/%s",
898				    dirpart, namepart);
899				if (flags & CE_COMPACT)
900					compress_log(file1,
901					    flags & CE_COMPACTWAIT);
902				else if (flags & CE_BZCOMPACT)
903					bzcompress_log(file1,
904					    flags & CE_COMPACTWAIT);
905			} else {
906				if (flags & CE_COMPACT)
907					compress_log(log,
908					    flags & CE_COMPACTWAIT);
909				else if (flags & CE_BZCOMPACT)
910					bzcompress_log(log,
911					    flags & CE_COMPACTWAIT);
912			}
913		}
914	}
915}
916
917/* Log the fact that the logs were turned over */
918static int
919log_trim(char *log, int def_cfg)
920{
921	FILE *f;
922	const char *xtra;
923
924	if ((f = fopen(log, "a")) == NULL)
925		return (-1);
926	xtra = "";
927	if (def_cfg)
928		xtra = " using <default> rule";
929	fprintf(f, "%s %s newsyslog[%d]: logfile turned over%s\n",
930	    daytime, hostname, (int) getpid(), xtra);
931	if (fclose(f) == EOF)
932		err(1, "log_trim: fclose:");
933	return (0);
934}
935
936/* Fork of gzip to compress the old log file */
937static void
938compress_log(char *log, int dowait)
939{
940	pid_t pid;
941	char tmp[MAXPATHLEN];
942
943	while (dowait && (wait(NULL) > 0 || errno == EINTR))
944		;
945	(void) snprintf(tmp, sizeof(tmp), "%s.0", log);
946	pid = fork();
947	if (pid < 0)
948		err(1, "gzip fork");
949	else if (!pid) {
950		(void) execl(_PATH_GZIP, _PATH_GZIP, "-f", tmp, (char *)0);
951		err(1, _PATH_GZIP);
952	}
953}
954
955/* Fork of bzip2 to compress the old log file */
956static void
957bzcompress_log(char *log, int dowait)
958{
959	pid_t pid;
960	char tmp[MAXPATHLEN];
961
962	while (dowait && (wait(NULL) > 0 || errno == EINTR))
963		;
964	snprintf(tmp, sizeof(tmp), "%s.0", log);
965	pid = fork();
966	if (pid < 0)
967		err(1, "bzip2 fork");
968	else if (!pid) {
969		execl(_PATH_BZIP2, _PATH_BZIP2, "-f", tmp, (char *)0);
970		err(1, _PATH_BZIP2);
971	}
972}
973
974/* Return size in kilobytes of a file */
975static int
976sizefile(char *file)
977{
978	struct stat sb;
979
980	if (stat(file, &sb) < 0)
981		return (-1);
982	return (kbytes(dbtob(sb.st_blocks)));
983}
984
985/* Return the age of old log file (file.0) */
986static int
987age_old_log(char *file)
988{
989	struct stat sb;
990	char tmp[MAXPATHLEN + sizeof(".0") + sizeof(COMPRESS_POSTFIX) + 1];
991
992	if (archtodir) {
993		char *p;
994
995		/* build name of archive directory into tmp */
996		if (*archdirname == '/') {	/* absolute */
997			strlcpy(tmp, archdirname, sizeof(tmp));
998		} else {	/* relative */
999			/* get directory part of logfile */
1000			strlcpy(tmp, file, sizeof(tmp));
1001			if ((p = rindex(tmp, '/')) == NULL)
1002				tmp[0] = '\0';
1003			else
1004				*(p + 1) = '\0';
1005			strlcat(tmp, archdirname, sizeof(tmp));
1006		}
1007
1008		strlcat(tmp, "/", sizeof(tmp));
1009
1010		/* get filename part of logfile */
1011		if ((p = rindex(file, '/')) == NULL)
1012			strlcat(tmp, file, sizeof(tmp));
1013		else
1014			strlcat(tmp, p + 1, sizeof(tmp));
1015	} else {
1016		(void) strlcpy(tmp, file, sizeof(tmp));
1017	}
1018
1019	if (stat(strcat(tmp, ".0"), &sb) < 0)
1020		if (stat(strcat(tmp, COMPRESS_POSTFIX), &sb) < 0)
1021			return (-1);
1022	return ((int) (timenow - sb.st_mtime + 1800) / 3600);
1023}
1024
1025static pid_t
1026get_pid(const char *pid_file)
1027{
1028	FILE *f;
1029	char line[BUFSIZ];
1030	pid_t pid = 0;
1031
1032	if ((f = fopen(pid_file, "r")) == NULL)
1033		warn("can't open %s pid file to restart a daemon",
1034		    pid_file);
1035	else {
1036		if (fgets(line, BUFSIZ, f)) {
1037			pid = atol(line);
1038			if (pid < MIN_PID || pid > MAX_PID) {
1039				warnx("preposterous process number: %d",
1040				   (int)pid);
1041				pid = 0;
1042			}
1043		} else
1044			warn("can't read %s pid file to restart a daemon",
1045			    pid_file);
1046		(void) fclose(f);
1047	}
1048	return pid;
1049}
1050
1051/* Skip Over Blanks */
1052char *
1053sob(char *p)
1054{
1055	while (p && *p && isspace(*p))
1056		p++;
1057	return (p);
1058}
1059
1060/* Skip Over Non-Blanks */
1061char *
1062son(char *p)
1063{
1064	while (p && *p && !isspace(*p))
1065		p++;
1066	return (p);
1067}
1068
1069/*
1070 * Parse a limited subset of ISO 8601. The specific format is as follows:
1071 *
1072 * [CC[YY[MM[DD]]]][THH[MM[SS]]]	(where `T' is the literal letter)
1073 *
1074 * We don't accept a timezone specification; missing fields (including timezone)
1075 * are defaulted to the current date but time zero.
1076 */
1077static time_t
1078parse8601(char *s, char *errline)
1079{
1080	char *t;
1081	time_t tsecs;
1082	struct tm tm, *tmp;
1083	u_long ul;
1084
1085	tmp = localtime(&timenow);
1086	tm = *tmp;
1087
1088	tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
1089
1090	ul = strtoul(s, &t, 10);
1091	if (*t != '\0' && *t != 'T')
1092		return -1;
1093
1094	/*
1095	 * Now t points either to the end of the string (if no time was
1096	 * provided) or to the letter `T' which separates date and time in
1097	 * ISO 8601.  The pointer arithmetic is the same for either case.
1098	 */
1099	switch (t - s) {
1100	case 8:
1101		tm.tm_year = ((ul / 1000000) - 19) * 100;
1102		ul = ul % 1000000;
1103	case 6:
1104		tm.tm_year -= tm.tm_year % 100;
1105		tm.tm_year += ul / 10000;
1106		ul = ul % 10000;
1107	case 4:
1108		tm.tm_mon = (ul / 100) - 1;
1109		ul = ul % 100;
1110	case 2:
1111		tm.tm_mday = ul;
1112	case 0:
1113		break;
1114	default:
1115		return -1;
1116	}
1117
1118	/* sanity check */
1119	if (tm.tm_year < 70 || tm.tm_mon < 0 || tm.tm_mon > 12
1120	    || tm.tm_mday < 1 || tm.tm_mday > 31)
1121		return -1;
1122
1123	if (*t != '\0') {
1124		s = ++t;
1125		ul = strtoul(s, &t, 10);
1126		if (*t != '\0' && !isspace(*t))
1127			return -1;
1128
1129		switch (t - s) {
1130		case 6:
1131			tm.tm_sec = ul % 100;
1132			ul /= 100;
1133		case 4:
1134			tm.tm_min = ul % 100;
1135			ul /= 100;
1136		case 2:
1137			tm.tm_hour = ul;
1138		case 0:
1139			break;
1140		default:
1141			return -1;
1142		}
1143
1144		/* sanity check */
1145		if (tm.tm_sec < 0 || tm.tm_sec > 60 || tm.tm_min < 0
1146		    || tm.tm_min > 59 || tm.tm_hour < 0 || tm.tm_hour > 23)
1147			return -1;
1148	}
1149	if ((tsecs = mktime(&tm)) == -1)
1150		errx(1, "nonexistent time:\n%s", errline);
1151	return tsecs;
1152}
1153
1154/* physically move file */
1155static void
1156movefile(char *from, char *to, int perm, int owner_uid, int group_gid)
1157{
1158	FILE *src, *dst;
1159	int c;
1160
1161	if ((src = fopen(from, "r")) == NULL)
1162		err(1, "can't fopen %s for reading", from);
1163	if ((dst = fopen(to, "w")) == NULL)
1164		err(1, "can't fopen %s for writing", to);
1165	if (fchown(fileno(dst), owner_uid, group_gid))
1166		err(1, "can't fchown %s", to);
1167	if (fchmod(fileno(dst), perm))
1168		err(1, "can't fchmod %s", to);
1169
1170	while ((c = getc(src)) != EOF) {
1171		if ((putc(c, dst)) == EOF)
1172			err(1, "error writing to %s", to);
1173	}
1174
1175	if (ferror(src))
1176		err(1, "error reading from %s", from);
1177	if ((fclose(src)) != 0)
1178		err(1, "can't fclose %s", to);
1179	if ((fclose(dst)) != 0)
1180		err(1, "can't fclose %s", from);
1181	if ((unlink(from)) != 0)
1182		err(1, "can't unlink %s", from);
1183}
1184
1185/* create one or more directory components of a path */
1186static void
1187createdir(char *dirpart)
1188{
1189	char *s, *d;
1190	char mkdirpath[MAXPATHLEN];
1191	struct stat st;
1192
1193	s = dirpart;
1194	d = mkdirpath;
1195
1196	for (;;) {
1197		*d++ = *s++;
1198		if (*s == '/' || *s == '\0') {
1199			*d = '\0';
1200			if (lstat(mkdirpath, &st))
1201				mkdir(mkdirpath, 0755);
1202		}
1203		if (*s == '\0')
1204			break;
1205	}
1206}
1207
1208/*-
1209 * Parse a cyclic time specification, the format is as follows:
1210 *
1211 *	[Dhh] or [Wd[Dhh]] or [Mdd[Dhh]]
1212 *
1213 * to rotate a logfile cyclic at
1214 *
1215 *	- every day (D) within a specific hour (hh)	(hh = 0...23)
1216 *	- once a week (W) at a specific day (d)     OR	(d = 0..6, 0 = Sunday)
1217 *	- once a month (M) at a specific day (d)	(d = 1..31,l|L)
1218 *
1219 * We don't accept a timezone specification; missing fields
1220 * are defaulted to the current date but time zero.
1221 */
1222static time_t
1223parseDWM(char *s, char *errline)
1224{
1225	char *t;
1226	time_t tsecs;
1227	struct tm tm, *tmp;
1228	long l;
1229	int nd;
1230	static int mtab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
1231	int WMseen = 0;
1232	int Dseen = 0;
1233
1234	tmp = localtime(&timenow);
1235	tm = *tmp;
1236
1237	/* set no. of days per month */
1238
1239	nd = mtab[tm.tm_mon];
1240
1241	if (tm.tm_mon == 1) {
1242		if (((tm.tm_year + 1900) % 4 == 0) &&
1243		    ((tm.tm_year + 1900) % 100 != 0) &&
1244		    ((tm.tm_year + 1900) % 400 == 0)) {
1245			nd++;	/* leap year, 29 days in february */
1246		}
1247	}
1248	tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
1249
1250	for (;;) {
1251		switch (*s) {
1252		case 'D':
1253			if (Dseen)
1254				return -1;
1255			Dseen++;
1256			s++;
1257			l = strtol(s, &t, 10);
1258			if (l < 0 || l > 23)
1259				return -1;
1260			tm.tm_hour = l;
1261			break;
1262
1263		case 'W':
1264			if (WMseen)
1265				return -1;
1266			WMseen++;
1267			s++;
1268			l = strtol(s, &t, 10);
1269			if (l < 0 || l > 6)
1270				return -1;
1271			if (l != tm.tm_wday) {
1272				int save;
1273
1274				if (l < tm.tm_wday) {
1275					save = 6 - tm.tm_wday;
1276					save += (l + 1);
1277				} else {
1278					save = l - tm.tm_wday;
1279				}
1280
1281				tm.tm_mday += save;
1282
1283				if (tm.tm_mday > nd) {
1284					tm.tm_mon++;
1285					tm.tm_mday = tm.tm_mday - nd;
1286				}
1287			}
1288			break;
1289
1290		case 'M':
1291			if (WMseen)
1292				return -1;
1293			WMseen++;
1294			s++;
1295			if (tolower(*s) == 'l') {
1296				tm.tm_mday = nd;
1297				s++;
1298				t = s;
1299			} else {
1300				l = strtol(s, &t, 10);
1301				if (l < 1 || l > 31)
1302					return -1;
1303
1304				if (l > nd)
1305					return -1;
1306				tm.tm_mday = l;
1307			}
1308			break;
1309
1310		default:
1311			return (-1);
1312			break;
1313		}
1314
1315		if (*t == '\0' || isspace(*t))
1316			break;
1317		else
1318			s = t;
1319	}
1320	if ((tsecs = mktime(&tm)) == -1)
1321		errx(1, "nonexistent time:\n%s", errline);
1322	return tsecs;
1323}
1324