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