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