xref: /freebsd/usr.sbin/newsyslog/newsyslog.c (revision e12f0d2fac9a974fd424bbbd930d2ca40fb1f062)
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
27 static const char rcsid[] =
28 "$FreeBSD$";
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 
73 struct 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 
88 int archtodir = 0;		/* Archive old logfiles to other directory */
89 int verbose = 0;		/* Print out what's going on */
90 int needroot = 1;		/* Root privs are necessary */
91 int noaction = 0;		/* Don't do anything, just show it */
92 int force = 0;			/* Force the trim no matter what */
93 char *archdirname;		/* Directory path to old logfiles archive */
94 char *conf = _PATH_CONF;	/* Configuration file to use */
95 time_t timenow;
96 
97 #define MIN_PID         5
98 #define MAX_PID		99999	/* was lower, see /usr/include/sys/proc.h */
99 char hostname[MAXHOSTNAMELEN];	/* hostname */
100 char *daytime;			/* timenow in human readable form */
101 
102 static struct conf_entry *parse_file(char **files);
103 static char *sob(char *p);
104 static char *son(char *p);
105 static char *missing_field(char *p, char *errline);
106 static void do_entry(struct conf_entry * ent);
107 static void PRS(int argc, char **argv);
108 static void usage();
109 static void dotrim(char *log, char *pid_file, int numdays, int falgs, int perm, int owner_uid, int group_gid, int sig);
110 static int log_trim(char *log);
111 static void compress_log(char *log);
112 static void bzcompress_log(char *log);
113 static int sizefile(char *file);
114 static int age_old_log(char *file);
115 static pid_t get_pid(char *pid_file);
116 static time_t parse8601(char *s);
117 static void movefile(char *from, char *to, int perm, int owner_uid, int group_gid);
118 static void createdir(char *dirpart);
119 static time_t parseDWM(char *s);
120 
121 int
122 main(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 
140 static void
141 do_entry(struct conf_entry * ent)
142 {
143 	int size, modtime;
144 	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 
210 static void
211 PRS(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 
253 static void
254 usage(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  */
264 static struct conf_entry *
265 parse_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 
480 static char *
481 missing_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 
488 static void
489 dotrim(char *log, 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 */
682 static int
683 log_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 */
697 static void
698 compress_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 */
714 static void
715 bzcompress_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 */
731 static int
732 sizefile(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) */
742 static int
743 age_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 
781 static pid_t
782 get_pid(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 */
807 char *
808 sob(char *p)
809 {
810 	while (p && *p && isspace(*p))
811 		p++;
812 	return (p);
813 }
814 
815 /* Skip Over Non-Blanks */
816 char *
817 son(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  */
832 static time_t
833 parse8601(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 */
907 static void
908 movefile(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 */
938 static void
939 createdir(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  */
974 static time_t
975 parseDWM(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 < 0 || 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 < 0 || ul > 6)
1021 				return -1;
1022 			if (ul != tm.tm_wday) {
1023 				int save;
1024 
1025 				if (ul < 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 > 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