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