xref: /freebsd/usr.sbin/newsyslog/newsyslog.c (revision eacee0ff7ec955b32e09515246bd97b6edcd2b0f)
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;		/* 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 const 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(void);
109 static void dotrim(char *log, const char *pid_file, int numdays, int falgs,
110 		int perm, int owner_uid, int group_gid, int sig);
111 static int log_trim(char *log);
112 static void compress_log(char *log);
113 static void bzcompress_log(char *log);
114 static int sizefile(char *file);
115 static int age_old_log(char *file);
116 static pid_t get_pid(const char *pid_file);
117 static time_t parse8601(char *s);
118 static void movefile(char *from, char *to, int perm, int owner_uid,
119 		int group_gid);
120 static void createdir(char *dirpart);
121 static time_t parseDWM(char *s);
122 
123 int
124 main(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 
142 static void
143 do_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 
213 static void
214 PRS(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 
256 static void
257 usage(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  */
269 static struct conf_entry *
270 parse_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))
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))
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 
499 static char *
500 missing_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 
508 static void
509 dotrim(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 	int notified, need_notification, fd, _numdays;
517 	struct stat st;
518 	pid_t pid;
519 
520 #ifdef _IBMR2
521 	/*
522 	 * AIX 3.1 has a broken fchown- if the owner_uid is -1, it will
523 	 * actually change it to be owned by uid -1, instead of leaving it
524 	 * as is, as it is supposed to.
525 	 */
526 	if (owner_uid == -1)
527 		owner_uid = geteuid();
528 #endif
529 
530 	if (archtodir) {
531 		char *p;
532 
533 		/* build complete name of archive directory into dirpart */
534 		if (*archdirname == '/') {	/* absolute */
535 			strlcpy(dirpart, archdirname, sizeof(dirpart));
536 		} else {	/* relative */
537 			/* get directory part of logfile */
538 			strlcpy(dirpart, log, sizeof(dirpart));
539 			if ((p = rindex(dirpart, '/')) == NULL)
540 				dirpart[0] = '\0';
541 			else
542 				*(p + 1) = '\0';
543 			strlcat(dirpart, archdirname, sizeof(dirpart));
544 		}
545 
546 		/* check if archive directory exists, if not, create it */
547 		if (lstat(dirpart, &st))
548 			createdir(dirpart);
549 
550 		/* get filename part of logfile */
551 		if ((p = rindex(log, '/')) == NULL)
552 			strlcpy(namepart, log, sizeof(namepart));
553 		else
554 			strlcpy(namepart, p + 1, sizeof(namepart));
555 
556 		/* name of oldest log */
557 		(void) snprintf(file1, sizeof(file1), "%s/%s.%d", dirpart,
558 		    namepart, numdays);
559 		(void) snprintf(zfile1, sizeof(zfile1), "%s%s", file1,
560 		    COMPRESS_POSTFIX);
561 		snprintf(jfile1, sizeof(jfile1), "%s%s", file1,
562 		    BZCOMPRESS_POSTFIX);
563 	} else {
564 		/* name of oldest log */
565 		(void) snprintf(file1, sizeof(file1), "%s.%d", log, numdays);
566 		(void) snprintf(zfile1, sizeof(zfile1), "%s%s", file1,
567 		    COMPRESS_POSTFIX);
568 		snprintf(jfile1, sizeof(jfile1), "%s%s", file1,
569 		    BZCOMPRESS_POSTFIX);
570 	}
571 
572 	if (noaction) {
573 		printf("rm -f %s\n", file1);
574 		printf("rm -f %s\n", zfile1);
575 		printf("rm -f %s\n", jfile1);
576 	} else {
577 		(void) unlink(file1);
578 		(void) unlink(zfile1);
579 		(void) unlink(jfile1);
580 	}
581 
582 	/* Move down log files */
583 	_numdays = numdays;	/* preserve */
584 	while (numdays--) {
585 
586 		(void) strlcpy(file2, file1, sizeof(file2));
587 
588 		if (archtodir)
589 			(void) snprintf(file1, sizeof(file1), "%s/%s.%d",
590 			    dirpart, namepart, numdays);
591 		else
592 			(void) snprintf(file1, sizeof(file1), "%s.%d", log,
593 			    numdays);
594 
595 		(void) strlcpy(zfile1, file1, sizeof(zfile1));
596 		(void) strlcpy(zfile2, file2, sizeof(zfile2));
597 		if (lstat(file1, &st)) {
598 			(void) strlcat(zfile1, COMPRESS_POSTFIX,
599 			    sizeof(zfile1));
600 			(void) strlcat(zfile2, COMPRESS_POSTFIX,
601 			    sizeof(zfile2));
602 			if (lstat(zfile1, &st)) {
603 				strlcpy(zfile1, file1, sizeof(zfile1));
604 				strlcpy(zfile2, file2, sizeof(zfile2));
605 				strlcat(zfile1, BZCOMPRESS_POSTFIX,
606 				    sizeof(zfile1));
607 				strlcat(zfile2, BZCOMPRESS_POSTFIX,
608 				    sizeof(zfile2));
609 				if (lstat(zfile1, &st))
610 					continue;
611 			}
612 		}
613 		if (noaction) {
614 			printf("mv %s %s\n", zfile1, zfile2);
615 			printf("chmod %o %s\n", perm, zfile2);
616 			printf("chown %d:%d %s\n",
617 			    owner_uid, group_gid, zfile2);
618 		} else {
619 			(void) rename(zfile1, zfile2);
620 			(void) chmod(zfile2, perm);
621 			(void) chown(zfile2, owner_uid, group_gid);
622 		}
623 	}
624 	if (!noaction && !(flags & CE_BINARY))
625 		(void) log_trim(log);	/* Report the trimming to the old log */
626 
627 	if (!_numdays) {
628 		if (noaction)
629 			printf("rm %s\n", log);
630 		else
631 			(void) unlink(log);
632 	} else {
633 		if (noaction)
634 			printf("mv %s to %s\n", log, file1);
635 		else {
636 			if (archtodir)
637 				movefile(log, file1, perm, owner_uid,
638 				    group_gid);
639 			else
640 				(void) rename(log, file1);
641 		}
642 	}
643 
644 	if (noaction)
645 		printf("Start new log...");
646 	else {
647 		fd = creat(log, perm);
648 		if (fd < 0)
649 			err(1, "can't start new log");
650 		if (fchown(fd, owner_uid, group_gid))
651 			err(1, "can't chmod new log file");
652 		(void) close(fd);
653 		if (!(flags & CE_BINARY))
654 			if (log_trim(log))	/* Add status message */
655 				err(1, "can't add status message to log");
656 	}
657 	if (noaction)
658 		printf("chmod %o %s...\n", perm, log);
659 	else
660 		(void) chmod(log, perm);
661 
662 	pid = 0;
663 	need_notification = notified = 0;
664 	if (pid_file != NULL) {
665 		need_notification = 1;
666 		pid = get_pid(pid_file);
667 	}
668 	if (pid) {
669 		if (noaction) {
670 			notified = 1;
671 			printf("kill -%d %d\n", sig, (int) pid);
672 		} else if (kill(pid, sig))
673 			warn("can't notify daemon, pid %d", (int) pid);
674 		else {
675 			notified = 1;
676 			if (verbose)
677 				printf("daemon pid %d notified\n", (int) pid);
678 		}
679 	}
680 	if ((flags & CE_COMPACT) || (flags & CE_BZCOMPACT)) {
681 		if (need_notification && !notified)
682 			warnx(
683 			    "log %s not compressed because daemon not notified",
684 			    log);
685 		else if (noaction)
686 			printf("Compress %s.0\n", log);
687 		else {
688 			if (notified) {
689 				if (verbose)
690 					printf("small pause to allow daemon to close log\n");
691 				sleep(10);
692 			}
693 			if (archtodir) {
694 				(void) snprintf(file1, sizeof(file1), "%s/%s",
695 				    dirpart, namepart);
696 				if (flags & CE_COMPACT)
697 					compress_log(file1);
698 				else if (flags & CE_BZCOMPACT)
699 					bzcompress_log(file1);
700 			} else {
701 				if (flags & CE_COMPACT)
702 					compress_log(log);
703 				else if (flags & CE_BZCOMPACT)
704 					bzcompress_log(log);
705 			}
706 		}
707 	}
708 }
709 
710 /* Log the fact that the logs were turned over */
711 static int
712 log_trim(char *log)
713 {
714 	FILE *f;
715 
716 	if ((f = fopen(log, "a")) == NULL)
717 		return (-1);
718 	fprintf(f, "%s %s newsyslog[%d]: logfile turned over\n",
719 	    daytime, hostname, (int) getpid());
720 	if (fclose(f) == EOF)
721 		err(1, "log_trim: fclose:");
722 	return (0);
723 }
724 
725 /* Fork of gzip to compress the old log file */
726 static void
727 compress_log(char *log)
728 {
729 	pid_t pid;
730 	char tmp[MAXPATHLEN];
731 
732 	(void) snprintf(tmp, sizeof(tmp), "%s.0", log);
733 	pid = fork();
734 	if (pid < 0)
735 		err(1, "gzip fork");
736 	else if (!pid) {
737 		(void) execl(_PATH_GZIP, _PATH_GZIP, "-f", tmp, (char *)0);
738 		err(1, _PATH_GZIP);
739 	}
740 }
741 
742 /* Fork of bzip2 to compress the old log file */
743 static void
744 bzcompress_log(char *log)
745 {
746 	pid_t pid;
747 	char tmp[MAXPATHLEN];
748 
749 	snprintf(tmp, sizeof(tmp), "%s.0", log);
750 	pid = fork();
751 	if (pid < 0)
752 		err(1, "bzip2 fork");
753 	else if (!pid) {
754 		execl(_PATH_BZIP2, _PATH_BZIP2, "-f", tmp, (char *)0);
755 		err(1, _PATH_BZIP2);
756 	}
757 }
758 
759 /* Return size in kilobytes of a file */
760 static int
761 sizefile(char *file)
762 {
763 	struct stat sb;
764 
765 	if (stat(file, &sb) < 0)
766 		return (-1);
767 	return (kbytes(dbtob(sb.st_blocks)));
768 }
769 
770 /* Return the age of old log file (file.0) */
771 static int
772 age_old_log(char *file)
773 {
774 	struct stat sb;
775 	char tmp[MAXPATHLEN + sizeof(".0") + sizeof(COMPRESS_POSTFIX) + 1];
776 
777 	if (archtodir) {
778 		char *p;
779 
780 		/* build name of archive directory into tmp */
781 		if (*archdirname == '/') {	/* absolute */
782 			strlcpy(tmp, archdirname, sizeof(tmp));
783 		} else {	/* relative */
784 			/* get directory part of logfile */
785 			strlcpy(tmp, file, sizeof(tmp));
786 			if ((p = rindex(tmp, '/')) == NULL)
787 				tmp[0] = '\0';
788 			else
789 				*(p + 1) = '\0';
790 			strlcat(tmp, archdirname, sizeof(tmp));
791 		}
792 
793 		strlcat(tmp, "/", sizeof(tmp));
794 
795 		/* get filename part of logfile */
796 		if ((p = rindex(file, '/')) == NULL)
797 			strlcat(tmp, file, sizeof(tmp));
798 		else
799 			strlcat(tmp, p + 1, sizeof(tmp));
800 	} else {
801 		(void) strlcpy(tmp, file, sizeof(tmp));
802 	}
803 
804 	if (stat(strcat(tmp, ".0"), &sb) < 0)
805 		if (stat(strcat(tmp, COMPRESS_POSTFIX), &sb) < 0)
806 			return (-1);
807 	return ((int) (timenow - sb.st_mtime + 1800) / 3600);
808 }
809 
810 static pid_t
811 get_pid(const char *pid_file)
812 {
813 	FILE *f;
814 	char line[BUFSIZ];
815 	pid_t pid = 0;
816 
817 	if ((f = fopen(pid_file, "r")) == NULL)
818 		warn("can't open %s pid file to restart a daemon",
819 		    pid_file);
820 	else {
821 		if (fgets(line, BUFSIZ, f)) {
822 			pid = atol(line);
823 			if (pid < MIN_PID || pid > MAX_PID) {
824 				warnx("preposterous process number: %d",
825 				   (int)pid);
826 				pid = 0;
827 			}
828 		} else
829 			warn("can't read %s pid file to restart a daemon",
830 			    pid_file);
831 		(void) fclose(f);
832 	}
833 	return pid;
834 }
835 
836 /* Skip Over Blanks */
837 char *
838 sob(char *p)
839 {
840 	while (p && *p && isspace(*p))
841 		p++;
842 	return (p);
843 }
844 
845 /* Skip Over Non-Blanks */
846 char *
847 son(char *p)
848 {
849 	while (p && *p && !isspace(*p))
850 		p++;
851 	return (p);
852 }
853 
854 /*
855  * Parse a limited subset of ISO 8601. The specific format is as follows:
856  *
857  * [CC[YY[MM[DD]]]][THH[MM[SS]]]	(where `T' is the literal letter)
858  *
859  * We don't accept a timezone specification; missing fields (including timezone)
860  * are defaulted to the current date but time zero.
861  */
862 static time_t
863 parse8601(char *s)
864 {
865 	char *t;
866 	struct tm tm, *tmp;
867 	u_long ul;
868 
869 	tmp = localtime(&timenow);
870 	tm = *tmp;
871 
872 	tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
873 
874 	ul = strtoul(s, &t, 10);
875 	if (*t != '\0' && *t != 'T')
876 		return -1;
877 
878 	/*
879 	 * Now t points either to the end of the string (if no time was
880 	 * provided) or to the letter `T' which separates date and time in
881 	 * ISO 8601.  The pointer arithmetic is the same for either case.
882 	 */
883 	switch (t - s) {
884 	case 8:
885 		tm.tm_year = ((ul / 1000000) - 19) * 100;
886 		ul = ul % 1000000;
887 	case 6:
888 		tm.tm_year -= tm.tm_year % 100;
889 		tm.tm_year += ul / 10000;
890 		ul = ul % 10000;
891 	case 4:
892 		tm.tm_mon = (ul / 100) - 1;
893 		ul = ul % 100;
894 	case 2:
895 		tm.tm_mday = ul;
896 	case 0:
897 		break;
898 	default:
899 		return -1;
900 	}
901 
902 	/* sanity check */
903 	if (tm.tm_year < 70 || tm.tm_mon < 0 || tm.tm_mon > 12
904 	    || tm.tm_mday < 1 || tm.tm_mday > 31)
905 		return -1;
906 
907 	if (*t != '\0') {
908 		s = ++t;
909 		ul = strtoul(s, &t, 10);
910 		if (*t != '\0' && !isspace(*t))
911 			return -1;
912 
913 		switch (t - s) {
914 		case 6:
915 			tm.tm_sec = ul % 100;
916 			ul /= 100;
917 		case 4:
918 			tm.tm_min = ul % 100;
919 			ul /= 100;
920 		case 2:
921 			tm.tm_hour = ul;
922 		case 0:
923 			break;
924 		default:
925 			return -1;
926 		}
927 
928 		/* sanity check */
929 		if (tm.tm_sec < 0 || tm.tm_sec > 60 || tm.tm_min < 0
930 		    || tm.tm_min > 59 || tm.tm_hour < 0 || tm.tm_hour > 23)
931 			return -1;
932 	}
933 	return mktime(&tm);
934 }
935 
936 /* physically move file */
937 static void
938 movefile(char *from, char *to, int perm, int owner_uid, int group_gid)
939 {
940 	FILE *src, *dst;
941 	int c;
942 
943 	if ((src = fopen(from, "r")) == NULL)
944 		err(1, "can't fopen %s for reading", from);
945 	if ((dst = fopen(to, "w")) == NULL)
946 		err(1, "can't fopen %s for writing", to);
947 	if (fchown(fileno(dst), owner_uid, group_gid))
948 		err(1, "can't fchown %s", to);
949 	if (fchmod(fileno(dst), perm))
950 		err(1, "can't fchmod %s", to);
951 
952 	while ((c = getc(src)) != EOF) {
953 		if ((putc(c, dst)) == EOF)
954 			err(1, "error writing to %s", to);
955 	}
956 
957 	if (ferror(src))
958 		err(1, "error reading from %s", from);
959 	if ((fclose(src)) != 0)
960 		err(1, "can't fclose %s", to);
961 	if ((fclose(dst)) != 0)
962 		err(1, "can't fclose %s", from);
963 	if ((unlink(from)) != 0)
964 		err(1, "can't unlink %s", from);
965 }
966 
967 /* create one or more directory components of a path */
968 static void
969 createdir(char *dirpart)
970 {
971 	char *s, *d;
972 	char mkdirpath[MAXPATHLEN];
973 	struct stat st;
974 
975 	s = dirpart;
976 	d = mkdirpath;
977 
978 	for (;;) {
979 		*d++ = *s++;
980 		if (*s == '/' || *s == '\0') {
981 			*d = '\0';
982 			if (lstat(mkdirpath, &st))
983 				mkdir(mkdirpath, 0755);
984 		}
985 		if (*s == '\0')
986 			break;
987 	}
988 }
989 
990 /*-
991  * Parse a cyclic time specification, the format is as follows:
992  *
993  *	[Dhh] or [Wd[Dhh]] or [Mdd[Dhh]]
994  *
995  * to rotate a logfile cyclic at
996  *
997  *	- every day (D) within a specific hour (hh)	(hh = 0...23)
998  *	- once a week (W) at a specific day (d)     OR	(d = 0..6, 0 = Sunday)
999  *	- once a month (M) at a specific day (d)	(d = 1..31,l|L)
1000  *
1001  * We don't accept a timezone specification; missing fields
1002  * are defaulted to the current date but time zero.
1003  */
1004 static time_t
1005 parseDWM(char *s)
1006 {
1007 	char *t;
1008 	struct tm tm, *tmp;
1009 	long l;
1010 	int nd;
1011 	static int mtab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
1012 	int WMseen = 0;
1013 	int Dseen = 0;
1014 
1015 	tmp = localtime(&timenow);
1016 	tm = *tmp;
1017 
1018 	/* set no. of days per month */
1019 
1020 	nd = mtab[tm.tm_mon];
1021 
1022 	if (tm.tm_mon == 1) {
1023 		if (((tm.tm_year + 1900) % 4 == 0) &&
1024 		    ((tm.tm_year + 1900) % 100 != 0) &&
1025 		    ((tm.tm_year + 1900) % 400 == 0)) {
1026 			nd++;	/* leap year, 29 days in february */
1027 		}
1028 	}
1029 	tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
1030 
1031 	for (;;) {
1032 		switch (*s) {
1033 		case 'D':
1034 			if (Dseen)
1035 				return -1;
1036 			Dseen++;
1037 			s++;
1038 			l = strtol(s, &t, 10);
1039 			if (l < 0 || l > 23)
1040 				return -1;
1041 			tm.tm_hour = l;
1042 			break;
1043 
1044 		case 'W':
1045 			if (WMseen)
1046 				return -1;
1047 			WMseen++;
1048 			s++;
1049 			l = strtol(s, &t, 10);
1050 			if (l < 0 || l > 6)
1051 				return -1;
1052 			if (l != tm.tm_wday) {
1053 				int save;
1054 
1055 				if (l < tm.tm_wday) {
1056 					save = 6 - tm.tm_wday;
1057 					save += (l + 1);
1058 				} else {
1059 					save = l - tm.tm_wday;
1060 				}
1061 
1062 				tm.tm_mday += save;
1063 
1064 				if (tm.tm_mday > nd) {
1065 					tm.tm_mon++;
1066 					tm.tm_mday = tm.tm_mday - nd;
1067 				}
1068 			}
1069 			break;
1070 
1071 		case 'M':
1072 			if (WMseen)
1073 				return -1;
1074 			WMseen++;
1075 			s++;
1076 			if (tolower(*s) == 'l') {
1077 				tm.tm_mday = nd;
1078 				s++;
1079 				t = s;
1080 			} else {
1081 				l = strtol(s, &t, 10);
1082 				if (l < 1 || l > 31)
1083 					return -1;
1084 
1085 				if (l > nd)
1086 					return -1;
1087 				tm.tm_mday = l;
1088 			}
1089 			break;
1090 
1091 		default:
1092 			return (-1);
1093 			break;
1094 		}
1095 
1096 		if (*t == '\0' || isspace(*t))
1097 			break;
1098 		else
1099 			s = t;
1100 	}
1101 	return mktime(&tm);
1102 }
1103