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