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