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