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