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