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