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