newsyslog.c revision 107737
1/*
2 * This file contains changes from the Open Software Foundation.
3 */
4
5/*
6 * Copyright 1988, 1989 by the Massachusetts Institute of Technology
7 *
8 * Permission to use, copy, modify, and distribute this software and its
9 * documentation for any purpose and without fee is hereby granted, provided
10 * that the above copyright notice appear in all copies and that both that
11 * copyright notice and this permission notice appear in supporting
12 * documentation, and that the names of M.I.T. and the M.I.T. S.I.P.B. not be
13 * used in advertising or publicity pertaining to distribution of the
14 * software without specific, written prior permission. M.I.T. and the M.I.T.
15 * S.I.P.B. make no representations about the suitability of this software
16 * for any purpose.  It is provided "as is" without express or implied
17 * warranty.
18 *
19 */
20
21/*
22 * newsyslog - roll over selected logs at the appropriate time, keeping the a
23 * specified number of backup files around.
24 */
25
26#ifndef lint
27static const char rcsid[] =
28"$FreeBSD: head/usr.sbin/newsyslog/newsyslog.c 107737 2002-12-11 01:19:56Z sobomax $";
29#endif	/* not lint */
30
31#define OSF
32#ifndef COMPRESS_POSTFIX
33#define COMPRESS_POSTFIX ".gz"
34#endif
35#ifndef	BZCOMPRESS_POSTFIX
36#define	BZCOMPRESS_POSTFIX ".bz2"
37#endif
38
39#include <sys/param.h>
40#include <sys/stat.h>
41#include <sys/wait.h>
42
43#include <ctype.h>
44#include <err.h>
45#include <errno.h>
46#include <fcntl.h>
47#include <glob.h>
48#include <grp.h>
49#include <paths.h>
50#include <pwd.h>
51#include <signal.h>
52#include <stdio.h>
53#include <stdlib.h>
54#include <string.h>
55#include <time.h>
56#include <unistd.h>
57
58#include "pathnames.h"
59
60#define kbytes(size)  (((size) + 1023) >> 10)
61
62#ifdef _IBMR2
63/* Calculates (db * DEV_BSIZE) */
64#define dbtob(db)  ((unsigned)(db) << UBSHIFT)
65#endif
66
67#define CE_COMPACT 1		/* Compact the achived log files */
68#define CE_BINARY  2		/* Logfile is in binary, don't add */
69#define CE_BZCOMPACT 8		/* Compact the achived log files with bzip2 */
70				/*  status messages */
71#define	CE_TRIMAT  4		/* trim at a specific time */
72#define	CE_GLOB    16		/* name of the log is file name pattern */
73
74#define NONE -1
75
76struct conf_entry {
77	char *log;		/* Name of the log */
78	char *pid_file;		/* PID file */
79	int uid;		/* Owner of log */
80	int gid;		/* Group of log */
81	int numlogs;		/* Number of logs to keep */
82	int size;		/* Size cutoff to trigger trimming the log */
83	int hours;		/* Hours between log trimming */
84	time_t trim_at;		/* Specific time to do trimming */
85	int permissions;	/* File permissions on the log */
86	int flags;		/* CE_COMPACT, CE_BZCOMPACT, CE_BINARY */
87	int sig;		/* Signal to send */
88	struct conf_entry *next;/* Linked list pointer */
89};
90
91int archtodir = 0;		/* Archive old logfiles to other directory */
92int verbose = 0;		/* Print out what's going on */
93int needroot = 1;		/* Root privs are necessary */
94int noaction = 0;		/* Don't do anything, just show it */
95int force = 0;			/* Force the trim no matter what */
96char *archdirname;		/* Directory path to old logfiles archive */
97const char *conf = _PATH_CONF;	/* Configuration file to use */
98time_t timenow;
99
100#define MIN_PID         5
101#define MAX_PID		99999	/* was lower, see /usr/include/sys/proc.h */
102char hostname[MAXHOSTNAMELEN];	/* hostname */
103char *daytime;			/* timenow in human readable form */
104
105static struct conf_entry *parse_file(char **files);
106static char *sob(char *p);
107static char *son(char *p);
108static char *missing_field(char *p, char *errline);
109static void do_entry(struct conf_entry * ent);
110static void PRS(int argc, char **argv);
111static void usage(void);
112static void dotrim(char *log, const char *pid_file, int numdays, int falgs,
113		int perm, int owner_uid, int group_gid, int sig);
114static int log_trim(char *log);
115static void compress_log(char *log);
116static void bzcompress_log(char *log);
117static int sizefile(char *file);
118static int age_old_log(char *file);
119static pid_t get_pid(const char *pid_file);
120static time_t parse8601(char *s, char *errline);
121static void movefile(char *from, char *to, int perm, int owner_uid,
122		int group_gid);
123static void createdir(char *dirpart);
124static time_t parseDWM(char *s, char *errline);
125
126int
127main(int argc, char **argv)
128{
129	struct conf_entry *p, *q;
130	glob_t pglob;
131	int i;
132
133	PRS(argc, argv);
134	if (needroot && getuid() && geteuid())
135		errx(1, "must have root privs");
136	p = q = parse_file(argv + optind);
137
138	while (p) {
139		if ((p->flags & CE_GLOB) == 0) {
140			do_entry(p);
141		} else {
142			if (glob(p->log, GLOB_NOCHECK, NULL, &pglob) != 0) {
143				warn("can't expand pattern: %s", p->log);
144			} else {
145				for (i = 0; i < pglob.gl_matchc; i++) {
146					p->log = pglob.gl_pathv[i];
147					do_entry(p);
148				}
149				globfree(&pglob);
150			}
151		}
152		p = p->next;
153		free((char *) q);
154		q = p;
155	}
156	while (wait(NULL) > 0 || errno == EINTR)
157		;
158	return (0);
159}
160
161static void
162do_entry(struct conf_entry * ent)
163{
164	int size, modtime;
165	const char *pid_file;
166
167	if (verbose) {
168		if (ent->flags & CE_COMPACT)
169			printf("%s <%dZ>: ", ent->log, ent->numlogs);
170		else if (ent->flags & CE_BZCOMPACT)
171			printf("%s <%dJ>: ", ent->log, ent->numlogs);
172		else
173			printf("%s <%d>: ", ent->log, ent->numlogs);
174	}
175	size = sizefile(ent->log);
176	modtime = age_old_log(ent->log);
177	if (size < 0) {
178		if (verbose)
179			printf("does not exist.\n");
180	} else {
181		if (ent->flags & CE_TRIMAT && !force) {
182			if (timenow < ent->trim_at
183			    || difftime(timenow, ent->trim_at) >= 60 * 60) {
184				if (verbose)
185					printf("--> will trim at %s",
186					    ctime(&ent->trim_at));
187				return;
188			} else if (verbose && ent->hours <= 0) {
189				printf("--> time is up\n");
190			}
191		}
192		if (verbose && (ent->size > 0))
193			printf("size (Kb): %d [%d] ", size, ent->size);
194		if (verbose && (ent->hours > 0))
195			printf(" age (hr): %d [%d] ", modtime, ent->hours);
196		if (force || ((ent->size > 0) && (size >= ent->size)) ||
197		    (ent->hours <= 0 && (ent->flags & CE_TRIMAT)) ||
198		    ((ent->hours > 0) && ((modtime >= ent->hours)
199			    || (modtime < 0)))) {
200			if (verbose)
201				printf("--> trimming log....\n");
202			if (noaction && !verbose) {
203				if (ent->flags & CE_COMPACT)
204					printf("%s <%dZ>: trimming\n",
205					    ent->log, ent->numlogs);
206				else if (ent->flags & CE_BZCOMPACT)
207					printf("%s <%dJ>: trimming\n",
208					    ent->log, ent->numlogs);
209				else
210					printf("%s <%d>: trimming\n",
211					    ent->log, ent->numlogs);
212			}
213			if (ent->pid_file) {
214				pid_file = ent->pid_file;
215			} else {
216				/* Only try to notify syslog if we are root */
217				if (needroot)
218					pid_file = _PATH_SYSLOGPID;
219				else
220					pid_file = NULL;
221			}
222			dotrim(ent->log, pid_file, ent->numlogs,
223			    ent->flags, ent->permissions, ent->uid, ent->gid,
224			    ent->sig);
225		} else {
226			if (verbose)
227				printf("--> skipping\n");
228		}
229	}
230}
231
232static void
233PRS(int argc, char **argv)
234{
235	int c;
236	char *p;
237
238	timenow = time((time_t *) 0);
239	daytime = ctime(&timenow) + 4;
240	daytime[15] = '\0';
241
242	/* Let's get our hostname */
243	(void) gethostname(hostname, sizeof(hostname));
244
245	/* Truncate domain */
246	if ((p = strchr(hostname, '.'))) {
247		*p = '\0';
248	}
249	while ((c = getopt(argc, argv, "nrvFf:a:")) != -1)
250		switch (c) {
251		case 'n':
252			noaction++;
253			break;
254		case 'a':
255			archtodir++;
256			archdirname = optarg;
257			break;
258		case 'r':
259			needroot = 0;
260			break;
261		case 'v':
262			verbose++;
263			break;
264		case 'f':
265			conf = optarg;
266			break;
267		case 'F':
268			force++;
269			break;
270		default:
271			usage();
272		}
273}
274
275static void
276usage(void)
277{
278
279	fprintf(stderr,
280	    "usage: newsyslog [-Fnrv] [-f config-file] [-a directory]\n");
281	exit(1);
282}
283
284/*
285 * Parse a configuration file and return a linked list of all the logs to
286 * process
287 */
288static struct conf_entry *
289parse_file(char **files)
290{
291	FILE *f;
292	char line[BUFSIZ], *parse, *q;
293	char *cp, *errline, *group;
294	char **p;
295	struct conf_entry *first, *working;
296	struct passwd *pass;
297	struct group *grp;
298	int eol;
299
300	first = working = NULL;
301
302	if (strcmp(conf, "-"))
303		f = fopen(conf, "r");
304	else
305		f = stdin;
306	if (!f)
307		err(1, "%s", conf);
308	while (fgets(line, BUFSIZ, f)) {
309		if ((line[0] == '\n') || (line[0] == '#') ||
310		    (strlen(line) == 0))
311			continue;
312		errline = strdup(line);
313		for (cp = line + 1; *cp != '\0'; cp++) {
314			if (*cp != '#')
315				continue;
316			if (*(cp - 1) == '\\') {
317				strcpy(cp - 1, cp);
318				cp--;
319				continue;
320			}
321			*cp = '\0';
322			break;
323		}
324
325		q = parse = missing_field(sob(line), errline);
326		parse = son(line);
327		if (!*parse)
328			errx(1, "malformed line (missing fields):\n%s",
329			    errline);
330		*parse = '\0';
331
332		if (*files) {
333			for (p = files; *p; ++p)
334				if (strcmp(*p, q) == 0)
335					break;
336			if (!*p)
337				continue;
338		}
339
340		if (!first) {
341			if ((working = malloc(sizeof(struct conf_entry))) ==
342			    NULL)
343				err(1, "malloc");
344			first = working;
345		} else {
346			if ((working->next = malloc(sizeof(struct conf_entry)))
347			    == NULL)
348				err(1, "malloc");
349			working = working->next;
350		}
351		if ((working->log = strdup(q)) == NULL)
352			err(1, "strdup");
353
354		q = parse = missing_field(sob(++parse), errline);
355		parse = son(parse);
356		if (!*parse)
357			errx(1, "malformed line (missing fields):\n%s",
358			    errline);
359		*parse = '\0';
360		if ((group = strchr(q, ':')) != NULL ||
361		    (group = strrchr(q, '.')) != NULL) {
362			*group++ = '\0';
363			if (*q) {
364				if (!(isnumber(*q))) {
365					if ((pass = getpwnam(q)) == NULL)
366						errx(1,
367				     "error in config file; unknown user:\n%s",
368						    errline);
369					working->uid = pass->pw_uid;
370				} else
371					working->uid = atoi(q);
372			} else
373				working->uid = NONE;
374
375			q = group;
376			if (*q) {
377				if (!(isnumber(*q))) {
378					if ((grp = getgrnam(q)) == NULL)
379						errx(1,
380				    "error in config file; unknown group:\n%s",
381						    errline);
382					working->gid = grp->gr_gid;
383				} else
384					working->gid = atoi(q);
385			} else
386				working->gid = NONE;
387
388			q = parse = missing_field(sob(++parse), errline);
389			parse = son(parse);
390			if (!*parse)
391				errx(1, "malformed line (missing fields):\n%s",
392				    errline);
393			*parse = '\0';
394		} else
395			working->uid = working->gid = NONE;
396
397		if (!sscanf(q, "%o", &working->permissions))
398			errx(1, "error in config file; bad permissions:\n%s",
399			    errline);
400
401		q = parse = missing_field(sob(++parse), errline);
402		parse = son(parse);
403		if (!*parse)
404			errx(1, "malformed line (missing fields):\n%s",
405			    errline);
406		*parse = '\0';
407		if (!sscanf(q, "%d", &working->numlogs))
408			errx(1, "error in config file; bad number:\n%s",
409			    errline);
410
411		q = parse = missing_field(sob(++parse), errline);
412		parse = son(parse);
413		if (!*parse)
414			errx(1, "malformed line (missing fields):\n%s",
415			    errline);
416		*parse = '\0';
417		if (isdigit(*q))
418			working->size = atoi(q);
419		else
420			working->size = -1;
421
422		working->flags = 0;
423		q = parse = missing_field(sob(++parse), errline);
424		parse = son(parse);
425		eol = !*parse;
426		*parse = '\0';
427		{
428			char *ep;
429			u_long ul;
430
431			ul = strtoul(q, &ep, 10);
432			if (ep == q)
433				working->hours = 0;
434			else if (*ep == '*')
435				working->hours = -1;
436			else if (ul > INT_MAX)
437				errx(1, "interval is too large:\n%s", errline);
438			else
439				working->hours = ul;
440
441			if (*ep != '\0' && *ep != '@' && *ep != '*' &&
442			    *ep != '$')
443				errx(1, "malformed interval/at:\n%s", errline);
444			if (*ep == '@') {
445				if ((working->trim_at = parse8601(ep + 1, errline))
446				    == (time_t) - 1)
447					errx(1, "malformed at:\n%s", errline);
448				working->flags |= CE_TRIMAT;
449			} else if (*ep == '$') {
450				if ((working->trim_at = parseDWM(ep + 1, errline))
451				    == (time_t) - 1)
452					errx(1, "malformed at:\n%s", errline);
453				working->flags |= CE_TRIMAT;
454			}
455		}
456
457		if (eol)
458			q = NULL;
459		else {
460			q = parse = sob(++parse);	/* Optional field */
461			parse = son(parse);
462			if (!*parse)
463				eol = 1;
464			*parse = '\0';
465		}
466
467		while (q && *q && !isspace(*q)) {
468			if ((*q == 'Z') || (*q == 'z'))
469				working->flags |= CE_COMPACT;
470			else if ((*q == 'J') || (*q == 'j'))
471				working->flags |= CE_BZCOMPACT;
472			else if ((*q == 'B') || (*q == 'b'))
473				working->flags |= CE_BINARY;
474			else if ((*q == 'G') || (*q == 'c'))
475				working->flags |= CE_GLOB;
476			else if (*q != '-')
477				errx(1, "illegal flag in config file -- %c",
478				    *q);
479			q++;
480		}
481
482		if (eol)
483			q = NULL;
484		else {
485			q = parse = sob(++parse);	/* Optional field */
486			parse = son(parse);
487			if (!*parse)
488				eol = 1;
489			*parse = '\0';
490		}
491
492		working->pid_file = NULL;
493		if (q && *q) {
494			if (*q == '/')
495				working->pid_file = strdup(q);
496			else if (isdigit(*q))
497				goto got_sig;
498			else
499				errx(1,
500			"illegal pid file or signal number in config file:\n%s",
501				    errline);
502		}
503		if (eol)
504			q = NULL;
505		else {
506			q = parse = sob(++parse);	/* Optional field */
507			*(parse = son(parse)) = '\0';
508		}
509
510		working->sig = SIGHUP;
511		if (q && *q) {
512			if (isdigit(*q)) {
513		got_sig:
514				working->sig = atoi(q);
515			} else {
516		err_sig:
517				errx(1,
518				    "illegal signal number in config file:\n%s",
519				    errline);
520			}
521			if (working->sig < 1 || working->sig >= NSIG)
522				goto err_sig;
523		}
524		free(errline);
525	}
526	if (working)
527		working->next = (struct conf_entry *) NULL;
528	(void) fclose(f);
529	return (first);
530}
531
532static char *
533missing_field(char *p, char *errline)
534{
535
536	if (!p || !*p)
537		errx(1, "missing field in config file:\n%s", errline);
538	return (p);
539}
540
541static void
542dotrim(char *log, const char *pid_file, int numdays, int flags, int perm,
543    int owner_uid, int group_gid, int sig)
544{
545	char dirpart[MAXPATHLEN], namepart[MAXPATHLEN];
546	char file1[MAXPATHLEN], file2[MAXPATHLEN];
547	char zfile1[MAXPATHLEN], zfile2[MAXPATHLEN];
548	char jfile1[MAXPATHLEN];
549	char tfile[MAXPATHLEN];
550	int notified, need_notification, fd, _numdays;
551	struct stat st;
552	pid_t pid;
553
554#ifdef _IBMR2
555	/*
556	 * AIX 3.1 has a broken fchown- if the owner_uid is -1, it will
557	 * actually change it to be owned by uid -1, instead of leaving it
558	 * as is, as it is supposed to.
559	 */
560	if (owner_uid == -1)
561		owner_uid = geteuid();
562#endif
563
564	if (archtodir) {
565		char *p;
566
567		/* build complete name of archive directory into dirpart */
568		if (*archdirname == '/') {	/* absolute */
569			strlcpy(dirpart, archdirname, sizeof(dirpart));
570		} else {	/* relative */
571			/* get directory part of logfile */
572			strlcpy(dirpart, log, sizeof(dirpart));
573			if ((p = rindex(dirpart, '/')) == NULL)
574				dirpart[0] = '\0';
575			else
576				*(p + 1) = '\0';
577			strlcat(dirpart, archdirname, sizeof(dirpart));
578		}
579
580		/* check if archive directory exists, if not, create it */
581		if (lstat(dirpart, &st))
582			createdir(dirpart);
583
584		/* get filename part of logfile */
585		if ((p = rindex(log, '/')) == NULL)
586			strlcpy(namepart, log, sizeof(namepart));
587		else
588			strlcpy(namepart, p + 1, sizeof(namepart));
589
590		/* name of oldest log */
591		(void) snprintf(file1, sizeof(file1), "%s/%s.%d", dirpart,
592		    namepart, numdays);
593		(void) snprintf(zfile1, sizeof(zfile1), "%s%s", file1,
594		    COMPRESS_POSTFIX);
595		snprintf(jfile1, sizeof(jfile1), "%s%s", file1,
596		    BZCOMPRESS_POSTFIX);
597	} else {
598		/* name of oldest log */
599		(void) snprintf(file1, sizeof(file1), "%s.%d", log, numdays);
600		(void) snprintf(zfile1, sizeof(zfile1), "%s%s", file1,
601		    COMPRESS_POSTFIX);
602		snprintf(jfile1, sizeof(jfile1), "%s%s", file1,
603		    BZCOMPRESS_POSTFIX);
604	}
605
606	if (noaction) {
607		printf("rm -f %s\n", file1);
608		printf("rm -f %s\n", zfile1);
609		printf("rm -f %s\n", jfile1);
610	} else {
611		(void) unlink(file1);
612		(void) unlink(zfile1);
613		(void) unlink(jfile1);
614	}
615
616	/* Move down log files */
617	_numdays = numdays;	/* preserve */
618	while (numdays--) {
619
620		(void) strlcpy(file2, file1, sizeof(file2));
621
622		if (archtodir)
623			(void) snprintf(file1, sizeof(file1), "%s/%s.%d",
624			    dirpart, namepart, numdays);
625		else
626			(void) snprintf(file1, sizeof(file1), "%s.%d", log,
627			    numdays);
628
629		(void) strlcpy(zfile1, file1, sizeof(zfile1));
630		(void) strlcpy(zfile2, file2, sizeof(zfile2));
631		if (lstat(file1, &st)) {
632			(void) strlcat(zfile1, COMPRESS_POSTFIX,
633			    sizeof(zfile1));
634			(void) strlcat(zfile2, COMPRESS_POSTFIX,
635			    sizeof(zfile2));
636			if (lstat(zfile1, &st)) {
637				strlcpy(zfile1, file1, sizeof(zfile1));
638				strlcpy(zfile2, file2, sizeof(zfile2));
639				strlcat(zfile1, BZCOMPRESS_POSTFIX,
640				    sizeof(zfile1));
641				strlcat(zfile2, BZCOMPRESS_POSTFIX,
642				    sizeof(zfile2));
643				if (lstat(zfile1, &st))
644					continue;
645			}
646		}
647		if (noaction) {
648			printf("mv %s %s\n", zfile1, zfile2);
649			printf("chmod %o %s\n", perm, zfile2);
650			printf("chown %d:%d %s\n",
651			    owner_uid, group_gid, zfile2);
652		} else {
653			(void) rename(zfile1, zfile2);
654			(void) chmod(zfile2, perm);
655			(void) chown(zfile2, owner_uid, group_gid);
656		}
657	}
658	if (!noaction && !(flags & CE_BINARY))
659		(void) log_trim(log);	/* Report the trimming to the old log */
660
661	if (!_numdays) {
662		if (noaction)
663			printf("rm %s\n", log);
664		else
665			(void) unlink(log);
666	} else {
667		if (noaction)
668			printf("mv %s to %s\n", log, file1);
669		else {
670			if (archtodir)
671				movefile(log, file1, perm, owner_uid,
672				    group_gid);
673			else
674				(void) rename(log, file1);
675		}
676	}
677
678	if (noaction)
679		printf("Start new log...");
680	else {
681		strlcpy(tfile, log, sizeof(tfile));
682		strlcat(tfile, ".XXXXXX", sizeof(tfile));
683		mkstemp(tfile);
684		fd = creat(tfile, perm);
685		if (fd < 0)
686			err(1, "can't start new log");
687		if (fchown(fd, owner_uid, group_gid))
688			err(1, "can't chmod new log file");
689		(void) close(fd);
690		if (!(flags & CE_BINARY))
691			if (log_trim(tfile))	/* Add status message */
692				err(1, "can't add status message to log");
693	}
694	if (noaction)
695		printf("chmod %o %s...\n", perm, log);
696	else {
697		(void) chmod(tfile, perm);
698		if (rename(tfile, log) < 0) {
699			err(1, "can't start new log");
700			(void) unlink(tfile);
701		}
702	}
703
704	pid = 0;
705	need_notification = notified = 0;
706	if (pid_file != NULL) {
707		need_notification = 1;
708		pid = get_pid(pid_file);
709	}
710	if (pid) {
711		if (noaction) {
712			notified = 1;
713			printf("kill -%d %d\n", sig, (int) pid);
714		} else if (kill(pid, sig))
715			warn("can't notify daemon, pid %d", (int) pid);
716		else {
717			notified = 1;
718			if (verbose)
719				printf("daemon pid %d notified\n", (int) pid);
720		}
721	}
722	if ((flags & CE_COMPACT) || (flags & CE_BZCOMPACT)) {
723		if (need_notification && !notified)
724			warnx(
725			    "log %s not compressed because daemon not notified",
726			    log);
727		else if (noaction)
728			printf("Compress %s.0\n", log);
729		else {
730			if (notified) {
731				if (verbose)
732					printf("small pause to allow daemon to close log\n");
733				sleep(10);
734			}
735			if (archtodir) {
736				(void) snprintf(file1, sizeof(file1), "%s/%s",
737				    dirpart, namepart);
738				if (flags & CE_COMPACT)
739					compress_log(file1);
740				else if (flags & CE_BZCOMPACT)
741					bzcompress_log(file1);
742			} else {
743				if (flags & CE_COMPACT)
744					compress_log(log);
745				else if (flags & CE_BZCOMPACT)
746					bzcompress_log(log);
747			}
748		}
749	}
750}
751
752/* Log the fact that the logs were turned over */
753static int
754log_trim(char *log)
755{
756	FILE *f;
757
758	if ((f = fopen(log, "a")) == NULL)
759		return (-1);
760	fprintf(f, "%s %s newsyslog[%d]: logfile turned over\n",
761	    daytime, hostname, (int) getpid());
762	if (fclose(f) == EOF)
763		err(1, "log_trim: fclose:");
764	return (0);
765}
766
767/* Fork of gzip to compress the old log file */
768static void
769compress_log(char *log)
770{
771	pid_t pid;
772	char tmp[MAXPATHLEN];
773
774	(void) snprintf(tmp, sizeof(tmp), "%s.0", log);
775	pid = fork();
776	if (pid < 0)
777		err(1, "gzip fork");
778	else if (!pid) {
779		(void) execl(_PATH_GZIP, _PATH_GZIP, "-f", tmp, (char *)0);
780		err(1, _PATH_GZIP);
781	}
782}
783
784/* Fork of bzip2 to compress the old log file */
785static void
786bzcompress_log(char *log)
787{
788	pid_t pid;
789	char tmp[MAXPATHLEN];
790
791	snprintf(tmp, sizeof(tmp), "%s.0", log);
792	pid = fork();
793	if (pid < 0)
794		err(1, "bzip2 fork");
795	else if (!pid) {
796		execl(_PATH_BZIP2, _PATH_BZIP2, "-f", tmp, (char *)0);
797		err(1, _PATH_BZIP2);
798	}
799}
800
801/* Return size in kilobytes of a file */
802static int
803sizefile(char *file)
804{
805	struct stat sb;
806
807	if (stat(file, &sb) < 0)
808		return (-1);
809	return (kbytes(dbtob(sb.st_blocks)));
810}
811
812/* Return the age of old log file (file.0) */
813static int
814age_old_log(char *file)
815{
816	struct stat sb;
817	char tmp[MAXPATHLEN + sizeof(".0") + sizeof(COMPRESS_POSTFIX) + 1];
818
819	if (archtodir) {
820		char *p;
821
822		/* build name of archive directory into tmp */
823		if (*archdirname == '/') {	/* absolute */
824			strlcpy(tmp, archdirname, sizeof(tmp));
825		} else {	/* relative */
826			/* get directory part of logfile */
827			strlcpy(tmp, file, sizeof(tmp));
828			if ((p = rindex(tmp, '/')) == NULL)
829				tmp[0] = '\0';
830			else
831				*(p + 1) = '\0';
832			strlcat(tmp, archdirname, sizeof(tmp));
833		}
834
835		strlcat(tmp, "/", sizeof(tmp));
836
837		/* get filename part of logfile */
838		if ((p = rindex(file, '/')) == NULL)
839			strlcat(tmp, file, sizeof(tmp));
840		else
841			strlcat(tmp, p + 1, sizeof(tmp));
842	} else {
843		(void) strlcpy(tmp, file, sizeof(tmp));
844	}
845
846	if (stat(strcat(tmp, ".0"), &sb) < 0)
847		if (stat(strcat(tmp, COMPRESS_POSTFIX), &sb) < 0)
848			return (-1);
849	return ((int) (timenow - sb.st_mtime + 1800) / 3600);
850}
851
852static pid_t
853get_pid(const char *pid_file)
854{
855	FILE *f;
856	char line[BUFSIZ];
857	pid_t pid = 0;
858
859	if ((f = fopen(pid_file, "r")) == NULL)
860		warn("can't open %s pid file to restart a daemon",
861		    pid_file);
862	else {
863		if (fgets(line, BUFSIZ, f)) {
864			pid = atol(line);
865			if (pid < MIN_PID || pid > MAX_PID) {
866				warnx("preposterous process number: %d",
867				   (int)pid);
868				pid = 0;
869			}
870		} else
871			warn("can't read %s pid file to restart a daemon",
872			    pid_file);
873		(void) fclose(f);
874	}
875	return pid;
876}
877
878/* Skip Over Blanks */
879char *
880sob(char *p)
881{
882	while (p && *p && isspace(*p))
883		p++;
884	return (p);
885}
886
887/* Skip Over Non-Blanks */
888char *
889son(char *p)
890{
891	while (p && *p && !isspace(*p))
892		p++;
893	return (p);
894}
895
896/*
897 * Parse a limited subset of ISO 8601. The specific format is as follows:
898 *
899 * [CC[YY[MM[DD]]]][THH[MM[SS]]]	(where `T' is the literal letter)
900 *
901 * We don't accept a timezone specification; missing fields (including timezone)
902 * are defaulted to the current date but time zero.
903 */
904static time_t
905parse8601(char *s, char *errline)
906{
907	char *t;
908	time_t tsecs;
909	struct tm tm, *tmp;
910	u_long ul;
911
912	tmp = localtime(&timenow);
913	tm = *tmp;
914
915	tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
916
917	ul = strtoul(s, &t, 10);
918	if (*t != '\0' && *t != 'T')
919		return -1;
920
921	/*
922	 * Now t points either to the end of the string (if no time was
923	 * provided) or to the letter `T' which separates date and time in
924	 * ISO 8601.  The pointer arithmetic is the same for either case.
925	 */
926	switch (t - s) {
927	case 8:
928		tm.tm_year = ((ul / 1000000) - 19) * 100;
929		ul = ul % 1000000;
930	case 6:
931		tm.tm_year -= tm.tm_year % 100;
932		tm.tm_year += ul / 10000;
933		ul = ul % 10000;
934	case 4:
935		tm.tm_mon = (ul / 100) - 1;
936		ul = ul % 100;
937	case 2:
938		tm.tm_mday = ul;
939	case 0:
940		break;
941	default:
942		return -1;
943	}
944
945	/* sanity check */
946	if (tm.tm_year < 70 || tm.tm_mon < 0 || tm.tm_mon > 12
947	    || tm.tm_mday < 1 || tm.tm_mday > 31)
948		return -1;
949
950	if (*t != '\0') {
951		s = ++t;
952		ul = strtoul(s, &t, 10);
953		if (*t != '\0' && !isspace(*t))
954			return -1;
955
956		switch (t - s) {
957		case 6:
958			tm.tm_sec = ul % 100;
959			ul /= 100;
960		case 4:
961			tm.tm_min = ul % 100;
962			ul /= 100;
963		case 2:
964			tm.tm_hour = ul;
965		case 0:
966			break;
967		default:
968			return -1;
969		}
970
971		/* sanity check */
972		if (tm.tm_sec < 0 || tm.tm_sec > 60 || tm.tm_min < 0
973		    || tm.tm_min > 59 || tm.tm_hour < 0 || tm.tm_hour > 23)
974			return -1;
975	}
976	if ((tsecs = mktime(&tm)) == -1)
977		errx(1, "nonexistent time:\n%s", errline);
978	return tsecs;
979}
980
981/* physically move file */
982static void
983movefile(char *from, char *to, int perm, int owner_uid, int group_gid)
984{
985	FILE *src, *dst;
986	int c;
987
988	if ((src = fopen(from, "r")) == NULL)
989		err(1, "can't fopen %s for reading", from);
990	if ((dst = fopen(to, "w")) == NULL)
991		err(1, "can't fopen %s for writing", to);
992	if (fchown(fileno(dst), owner_uid, group_gid))
993		err(1, "can't fchown %s", to);
994	if (fchmod(fileno(dst), perm))
995		err(1, "can't fchmod %s", to);
996
997	while ((c = getc(src)) != EOF) {
998		if ((putc(c, dst)) == EOF)
999			err(1, "error writing to %s", to);
1000	}
1001
1002	if (ferror(src))
1003		err(1, "error reading from %s", from);
1004	if ((fclose(src)) != 0)
1005		err(1, "can't fclose %s", to);
1006	if ((fclose(dst)) != 0)
1007		err(1, "can't fclose %s", from);
1008	if ((unlink(from)) != 0)
1009		err(1, "can't unlink %s", from);
1010}
1011
1012/* create one or more directory components of a path */
1013static void
1014createdir(char *dirpart)
1015{
1016	char *s, *d;
1017	char mkdirpath[MAXPATHLEN];
1018	struct stat st;
1019
1020	s = dirpart;
1021	d = mkdirpath;
1022
1023	for (;;) {
1024		*d++ = *s++;
1025		if (*s == '/' || *s == '\0') {
1026			*d = '\0';
1027			if (lstat(mkdirpath, &st))
1028				mkdir(mkdirpath, 0755);
1029		}
1030		if (*s == '\0')
1031			break;
1032	}
1033}
1034
1035/*-
1036 * Parse a cyclic time specification, the format is as follows:
1037 *
1038 *	[Dhh] or [Wd[Dhh]] or [Mdd[Dhh]]
1039 *
1040 * to rotate a logfile cyclic at
1041 *
1042 *	- every day (D) within a specific hour (hh)	(hh = 0...23)
1043 *	- once a week (W) at a specific day (d)     OR	(d = 0..6, 0 = Sunday)
1044 *	- once a month (M) at a specific day (d)	(d = 1..31,l|L)
1045 *
1046 * We don't accept a timezone specification; missing fields
1047 * are defaulted to the current date but time zero.
1048 */
1049static time_t
1050parseDWM(char *s, char *errline)
1051{
1052	char *t;
1053	time_t tsecs;
1054	struct tm tm, *tmp;
1055	long l;
1056	int nd;
1057	static int mtab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
1058	int WMseen = 0;
1059	int Dseen = 0;
1060
1061	tmp = localtime(&timenow);
1062	tm = *tmp;
1063
1064	/* set no. of days per month */
1065
1066	nd = mtab[tm.tm_mon];
1067
1068	if (tm.tm_mon == 1) {
1069		if (((tm.tm_year + 1900) % 4 == 0) &&
1070		    ((tm.tm_year + 1900) % 100 != 0) &&
1071		    ((tm.tm_year + 1900) % 400 == 0)) {
1072			nd++;	/* leap year, 29 days in february */
1073		}
1074	}
1075	tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
1076
1077	for (;;) {
1078		switch (*s) {
1079		case 'D':
1080			if (Dseen)
1081				return -1;
1082			Dseen++;
1083			s++;
1084			l = strtol(s, &t, 10);
1085			if (l < 0 || l > 23)
1086				return -1;
1087			tm.tm_hour = l;
1088			break;
1089
1090		case 'W':
1091			if (WMseen)
1092				return -1;
1093			WMseen++;
1094			s++;
1095			l = strtol(s, &t, 10);
1096			if (l < 0 || l > 6)
1097				return -1;
1098			if (l != tm.tm_wday) {
1099				int save;
1100
1101				if (l < tm.tm_wday) {
1102					save = 6 - tm.tm_wday;
1103					save += (l + 1);
1104				} else {
1105					save = l - tm.tm_wday;
1106				}
1107
1108				tm.tm_mday += save;
1109
1110				if (tm.tm_mday > nd) {
1111					tm.tm_mon++;
1112					tm.tm_mday = tm.tm_mday - nd;
1113				}
1114			}
1115			break;
1116
1117		case 'M':
1118			if (WMseen)
1119				return -1;
1120			WMseen++;
1121			s++;
1122			if (tolower(*s) == 'l') {
1123				tm.tm_mday = nd;
1124				s++;
1125				t = s;
1126			} else {
1127				l = strtol(s, &t, 10);
1128				if (l < 1 || l > 31)
1129					return -1;
1130
1131				if (l > nd)
1132					return -1;
1133				tm.tm_mday = l;
1134			}
1135			break;
1136
1137		default:
1138			return (-1);
1139			break;
1140		}
1141
1142		if (*t == '\0' || isspace(*t))
1143			break;
1144		else
1145			s = t;
1146	}
1147	if ((tsecs = mktime(&tm)) == -1)
1148		errx(1, "nonexistent time:\n%s", errline);
1149	return tsecs;
1150}
1151