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