newsyslog.c revision 90240
1275970Scy/*
2275970Scy * This file contains changes from the Open Software Foundation.
3275970Scy */
4275970Scy
5275970Scy/*
6275970Scy * Copyright 1988, 1989 by the Massachusetts Institute of Technology
7275970Scy *
8275970Scy * Permission to use, copy, modify, and distribute this software and its
9275970Scy * documentation for any purpose and without fee is hereby granted, provided
10275970Scy * that the above copyright notice appear in all copies and that both that
11275970Scy * copyright notice and this permission notice appear in supporting
12275970Scy * documentation, and that the names of M.I.T. and the M.I.T. S.I.P.B. not be
13275970Scy * used in advertising or publicity pertaining to distribution of the
14275970Scy * software without specific, written prior permission. M.I.T. and the M.I.T.
15275970Scy * S.I.P.B. make no representations about the suitability of this software
16275970Scy * for any purpose.  It is provided "as is" without express or implied
17275970Scy * warranty.
18275970Scy *
19275970Scy */
20275970Scy
21275970Scy/*
22275970Scy * newsyslog - roll over selected logs at the appropriate time, keeping the a
23275970Scy * specified number of backup files around.
24275970Scy */
25275970Scy
26275970Scy#ifndef lint
27275970Scystatic const char rcsid[] =
28275970Scy"$FreeBSD: head/usr.sbin/newsyslog/newsyslog.c 90240 2002-02-05 09:33:07Z roam $";
29275970Scy#endif	/* not lint */
30275970Scy
31275970Scy#define OSF
32275970Scy#ifndef COMPRESS_POSTFIX
33275970Scy#define COMPRESS_POSTFIX ".gz"
34275970Scy#endif
35275970Scy#ifndef	BZCOMPRESS_POSTFIX
36275970Scy#define	BZCOMPRESS_POSTFIX ".bz2"
37275970Scy#endif
38275970Scy
39275970Scy#include <ctype.h>
40275970Scy#include <err.h>
41275970Scy#include <fcntl.h>
42275970Scy#include <grp.h>
43275970Scy#include <paths.h>
44275970Scy#include <pwd.h>
45275970Scy#include <signal.h>
46275970Scy#include <stdio.h>
47275970Scy#include <stdlib.h>
48275970Scy#include <string.h>
49275970Scy#include <time.h>
50275970Scy#include <unistd.h>
51275970Scy#include <sys/types.h>
52275970Scy#include <sys/stat.h>
53275970Scy#include <sys/param.h>
54275970Scy#include <sys/wait.h>
55275970Scy
56275970Scy#include "pathnames.h"
57275970Scy
58275970Scy#define kbytes(size)  (((size) + 1023) >> 10)
59275970Scy
60275970Scy#ifdef _IBMR2
61275970Scy/* Calculates (db * DEV_BSIZE) */
62275970Scy#define dbtob(db)  ((unsigned)(db) << UBSHIFT)
63275970Scy#endif
64275970Scy
65275970Scy#define CE_COMPACT 1		/* Compact the achived log files */
66275970Scy#define CE_BINARY  2		/* Logfile is in binary, don't add */
67275970Scy#define CE_BZCOMPACT 8		/* Compact the achived log files with bzip2 */
68275970Scy				/*  status messages */
69275970Scy#define	CE_TRIMAT  4		/* trim at a specific time */
70275970Scy
71275970Scy#define NONE -1
72275970Scy
73275970Scystruct conf_entry {
74275970Scy	char *log;		/* Name of the log */
75275970Scy	char *pid_file;		/* PID file */
76275970Scy	int uid;		/* Owner of log */
77275970Scy	int gid;		/* Group of log */
78275970Scy	int numlogs;		/* Number of logs to keep */
79275970Scy	int size;		/* Size cutoff to trigger trimming the log */
80275970Scy	int hours;		/* Hours between log trimming */
81275970Scy	time_t trim_at;		/* Specific time to do trimming */
82275970Scy	int permissions;	/* File permissions on the log */
83275970Scy	int flags;		/* CE_COMPACT, CE_BZCOMPACT, CE_BINARY */
84275970Scy	int sig;		/* Signal to send */
85275970Scy	struct conf_entry *next;/* Linked list pointer */
86275970Scy};
87275970Scy
88275970Scyint archtodir = 0;		/* Archive old logfiles to other directory */
89275970Scyint verbose = 0;		/* Print out what's going on */
90275970Scyint needroot = 1;		/* Root privs are necessary */
91275970Scyint noaction = 0;		/* Don't do anything, just show it */
92275970Scyint force = 0;			/* Force the trim no matter what */
93275970Scychar *archdirname;		/* Directory path to old logfiles archive */
94275970Scyconst char *conf = _PATH_CONF;	/* Configuration file to use */
95275970Scytime_t timenow;
96275970Scy
97275970Scy#define MIN_PID         5
98275970Scy#define MAX_PID		99999	/* was lower, see /usr/include/sys/proc.h */
99275970Scychar hostname[MAXHOSTNAMELEN];	/* hostname */
100275970Scychar *daytime;			/* timenow in human readable form */
101275970Scy
102275970Scystatic struct conf_entry *parse_file(char **files);
103275970Scystatic char *sob(char *p);
104275970Scystatic char *son(char *p);
105275970Scystatic char *missing_field(char *p, char *errline);
106275970Scystatic void do_entry(struct conf_entry * ent);
107275970Scystatic void PRS(int argc, char **argv);
108275970Scystatic void usage(void);
109275970Scystatic void dotrim(char *log, const char *pid_file, int numdays, int falgs,
110275970Scy		int perm, int owner_uid, int group_gid, int sig);
111275970Scystatic int log_trim(char *log);
112275970Scystatic void compress_log(char *log);
113275970Scystatic void bzcompress_log(char *log);
114275970Scystatic int sizefile(char *file);
115275970Scystatic int age_old_log(char *file);
116275970Scystatic pid_t get_pid(const char *pid_file);
117275970Scystatic time_t parse8601(char *s);
118275970Scystatic void movefile(char *from, char *to, int perm, int owner_uid,
119275970Scy		int group_gid);
120275970Scystatic void createdir(char *dirpart);
121275970Scystatic time_t parseDWM(char *s);
122275970Scy
123275970Scyint
124275970Scymain(int argc, char **argv)
125275970Scy{
126275970Scy	struct conf_entry *p, *q;
127275970Scy
128275970Scy	PRS(argc, argv);
129275970Scy	if (needroot && getuid() && geteuid())
130275970Scy		errx(1, "must have root privs");
131275970Scy	p = q = parse_file(argv + optind);
132275970Scy
133275970Scy	while (p) {
134275970Scy		do_entry(p);
135275970Scy		p = p->next;
136275970Scy		free((char *) q);
137275970Scy		q = p;
138275970Scy	}
139275970Scy	return (0);
140275970Scy}
141275970Scy
142275970Scystatic void
143275970Scydo_entry(struct conf_entry * ent)
144275970Scy{
145275970Scy	int size, modtime;
146275970Scy	const char *pid_file;
147275970Scy
148275970Scy	if (verbose) {
149275970Scy		if (ent->flags & CE_COMPACT)
150275970Scy			printf("%s <%dZ>: ", ent->log, ent->numlogs);
151275970Scy		else if (ent->flags & CE_BZCOMPACT)
152275970Scy			printf("%s <%dJ>: ", ent->log, ent->numlogs);
153275970Scy		else
154275970Scy			printf("%s <%d>: ", ent->log, ent->numlogs);
155275970Scy	}
156275970Scy	size = sizefile(ent->log);
157275970Scy	modtime = age_old_log(ent->log);
158275970Scy	if (size < 0) {
159275970Scy		if (verbose)
160275970Scy			printf("does not exist.\n");
161275970Scy	} else {
162275970Scy		if (ent->flags & CE_TRIMAT && !force) {
163275970Scy			if (timenow < ent->trim_at
164275970Scy			    || difftime(timenow, ent->trim_at) >= 60 * 60) {
165275970Scy				if (verbose)
166275970Scy					printf("--> will trim at %s",
167275970Scy					    ctime(&ent->trim_at));
168275970Scy				return;
169275970Scy			} else if (verbose && ent->hours <= 0) {
170275970Scy				printf("--> time is up\n");
171275970Scy			}
172275970Scy		}
173275970Scy		if (verbose && (ent->size > 0))
174275970Scy			printf("size (Kb): %d [%d] ", size, ent->size);
175275970Scy		if (verbose && (ent->hours > 0))
176275970Scy			printf(" age (hr): %d [%d] ", modtime, ent->hours);
177275970Scy		if (force || ((ent->size > 0) && (size >= ent->size)) ||
178275970Scy		    (ent->hours <= 0 && (ent->flags & CE_TRIMAT)) ||
179275970Scy		    ((ent->hours > 0) && ((modtime >= ent->hours)
180275970Scy			    || (modtime < 0)))) {
181275970Scy			if (verbose)
182275970Scy				printf("--> trimming log....\n");
183275970Scy			if (noaction && !verbose) {
184275970Scy				if (ent->flags & CE_COMPACT)
185275970Scy					printf("%s <%dZ>: trimming\n",
186275970Scy					    ent->log, ent->numlogs);
187275970Scy				else if (ent->flags & CE_BZCOMPACT)
188275970Scy					printf("%s <%dJ>: trimming\n",
189275970Scy					    ent->log, ent->numlogs);
190275970Scy				else
191275970Scy					printf("%s <%d>: trimming\n",
192275970Scy					    ent->log, ent->numlogs);
193275970Scy			}
194275970Scy			if (ent->pid_file) {
195275970Scy				pid_file = ent->pid_file;
196275970Scy			} else {
197275970Scy				/* Only try to notify syslog if we are root */
198275970Scy				if (needroot)
199275970Scy					pid_file = _PATH_SYSLOGPID;
200275970Scy				else
201275970Scy					pid_file = NULL;
202275970Scy			}
203275970Scy			dotrim(ent->log, pid_file, ent->numlogs,
204275970Scy			    ent->flags, ent->permissions, ent->uid, ent->gid,
205275970Scy			    ent->sig);
206275970Scy		} else {
207275970Scy			if (verbose)
208275970Scy				printf("--> skipping\n");
209275970Scy		}
210275970Scy	}
211275970Scy}
212275970Scy
213275970Scystatic void
214275970ScyPRS(int argc, char **argv)
215275970Scy{
216275970Scy	int c;
217275970Scy	char *p;
218275970Scy
219275970Scy	timenow = time((time_t *) 0);
220275970Scy	daytime = ctime(&timenow) + 4;
221275970Scy	daytime[15] = '\0';
222275970Scy
223275970Scy	/* Let's get our hostname */
224275970Scy	(void) gethostname(hostname, sizeof(hostname));
225275970Scy
226275970Scy	/* Truncate domain */
227275970Scy	if ((p = strchr(hostname, '.'))) {
228275970Scy		*p = '\0';
229275970Scy	}
230275970Scy	while ((c = getopt(argc, argv, "nrvFf:a:t:")) != -1)
231275970Scy		switch (c) {
232275970Scy		case 'n':
233275970Scy			noaction++;
234275970Scy			break;
235275970Scy		case 'a':
236275970Scy			archtodir++;
237275970Scy			archdirname = optarg;
238275970Scy			break;
239275970Scy		case 'r':
240275970Scy			needroot = 0;
241275970Scy			break;
242275970Scy		case 'v':
243275970Scy			verbose++;
244275970Scy			break;
245275970Scy		case 'f':
246275970Scy			conf = optarg;
247275970Scy			break;
248275970Scy		case 'F':
249275970Scy			force++;
250275970Scy			break;
251275970Scy		default:
252275970Scy			usage();
253275970Scy		}
254275970Scy}
255275970Scy
256275970Scystatic void
257275970Scyusage(void)
258275970Scy{
259275970Scy
260275970Scy	fprintf(stderr,
261275970Scy	    "usage: newsyslog [-Fnrv] [-f config-file] [-a directory]\n");
262275970Scy	exit(1);
263275970Scy}
264275970Scy
265275970Scy/*
266275970Scy * Parse a configuration file and return a linked list of all the logs to
267275970Scy * process
268275970Scy */
269275970Scystatic struct conf_entry *
270275970Scyparse_file(char **files)
271275970Scy{
272275970Scy	FILE *f;
273275970Scy	char line[BUFSIZ], *parse, *q;
274275970Scy	char *errline, *group;
275275970Scy	char **p;
276275970Scy	struct conf_entry *first, *working;
277275970Scy	struct passwd *pass;
278275970Scy	struct group *grp;
279275970Scy	int eol;
280275970Scy
281275970Scy	first = working = NULL;
282275970Scy
283275970Scy	if (strcmp(conf, "-"))
284275970Scy		f = fopen(conf, "r");
285275970Scy	else
286275970Scy		f = stdin;
287275970Scy	if (!f)
288275970Scy		err(1, "%s", conf);
289275970Scy	while (fgets(line, BUFSIZ, f)) {
290275970Scy		if ((line[0] == '\n') || (line[0] == '#'))
291275970Scy			continue;
292275970Scy		errline = strdup(line);
293275970Scy
294275970Scy		q = parse = missing_field(sob(line), errline);
295275970Scy		parse = son(line);
296275970Scy		if (!*parse)
297275970Scy			errx(1, "malformed line (missing fields):\n%s",
298275970Scy			    errline);
299275970Scy		*parse = '\0';
300275970Scy
301275970Scy		if (*files) {
302275970Scy			for (p = files; *p; ++p)
303275970Scy				if (strcmp(*p, q) == 0)
304275970Scy					break;
305275970Scy			if (!*p)
306275970Scy				continue;
307275970Scy		}
308275970Scy
309275970Scy		if (!first) {
310275970Scy			if ((working = malloc(sizeof(struct conf_entry))) ==
311275970Scy			    NULL)
312275970Scy				err(1, "malloc");
313275970Scy			first = working;
314275970Scy		} else {
315275970Scy			if ((working->next = malloc(sizeof(struct conf_entry)))
316275970Scy			    == NULL)
317275970Scy				err(1, "malloc");
318275970Scy			working = working->next;
319275970Scy		}
320275970Scy		if ((working->log = strdup(q)) == NULL)
321275970Scy			err(1, "strdup");
322275970Scy
323275970Scy		q = parse = missing_field(sob(++parse), errline);
324275970Scy		parse = son(parse);
325275970Scy		if (!*parse)
326275970Scy			errx(1, "malformed line (missing fields):\n%s",
327275970Scy			    errline);
328275970Scy		*parse = '\0';
329275970Scy		if ((group = strchr(q, ':')) != NULL ||
330275970Scy		    (group = strrchr(q, '.')) != NULL) {
331275970Scy			*group++ = '\0';
332275970Scy			if (*q) {
333275970Scy				if (!(isnumber(*q))) {
334275970Scy					if ((pass = getpwnam(q)) == NULL)
335275970Scy						errx(1,
336275970Scy				     "error in config file; unknown user:\n%s",
337275970Scy						    errline);
338275970Scy					working->uid = pass->pw_uid;
339275970Scy				} else
340275970Scy					working->uid = atoi(q);
341275970Scy			} else
342275970Scy				working->uid = NONE;
343275970Scy
344275970Scy			q = group;
345275970Scy			if (*q) {
346275970Scy				if (!(isnumber(*q))) {
347275970Scy					if ((grp = getgrnam(q)) == NULL)
348275970Scy						errx(1,
349275970Scy				    "error in config file; unknown group:\n%s",
350275970Scy						    errline);
351275970Scy					working->gid = grp->gr_gid;
352275970Scy				} else
353275970Scy					working->gid = atoi(q);
354275970Scy			} else
355275970Scy				working->gid = NONE;
356275970Scy
357275970Scy			q = parse = missing_field(sob(++parse), errline);
358275970Scy			parse = son(parse);
359275970Scy			if (!*parse)
360275970Scy				errx(1, "malformed line (missing fields):\n%s",
361275970Scy				    errline);
362275970Scy			*parse = '\0';
363275970Scy		} else
364275970Scy			working->uid = working->gid = NONE;
365275970Scy
366275970Scy		if (!sscanf(q, "%o", &working->permissions))
367275970Scy			errx(1, "error in config file; bad permissions:\n%s",
368275970Scy			    errline);
369275970Scy
370275970Scy		q = parse = missing_field(sob(++parse), errline);
371275970Scy		parse = son(parse);
372275970Scy		if (!*parse)
373275970Scy			errx(1, "malformed line (missing fields):\n%s",
374275970Scy			    errline);
375275970Scy		*parse = '\0';
376275970Scy		if (!sscanf(q, "%d", &working->numlogs))
377275970Scy			errx(1, "error in config file; bad number:\n%s",
378275970Scy			    errline);
379275970Scy
380275970Scy		q = parse = missing_field(sob(++parse), errline);
381275970Scy		parse = son(parse);
382275970Scy		if (!*parse)
383275970Scy			errx(1, "malformed line (missing fields):\n%s",
384275970Scy			    errline);
385275970Scy		*parse = '\0';
386275970Scy		if (isdigit(*q))
387275970Scy			working->size = atoi(q);
388275970Scy		else
389275970Scy			working->size = -1;
390275970Scy
391275970Scy		working->flags = 0;
392275970Scy		q = parse = missing_field(sob(++parse), errline);
393275970Scy		parse = son(parse);
394275970Scy		eol = !*parse;
395275970Scy		*parse = '\0';
396275970Scy		{
397275970Scy			char *ep;
398275970Scy			u_long ul;
399275970Scy
400275970Scy			ul = strtoul(q, &ep, 10);
401275970Scy			if (ep == q)
402275970Scy				working->hours = 0;
403275970Scy			else if (*ep == '*')
404275970Scy				working->hours = -1;
405275970Scy			else if (ul > INT_MAX)
406275970Scy				errx(1, "interval is too large:\n%s", errline);
407275970Scy			else
408275970Scy				working->hours = ul;
409275970Scy
410			if (*ep != '\0' && *ep != '@' && *ep != '*' &&
411			    *ep != '$')
412				errx(1, "malformed interval/at:\n%s", errline);
413			if (*ep == '@') {
414				if ((working->trim_at = parse8601(ep + 1))
415				    == (time_t) - 1)
416					errx(1, "malformed at:\n%s", errline);
417				working->flags |= CE_TRIMAT;
418			} else if (*ep == '$') {
419				if ((working->trim_at = parseDWM(ep + 1))
420				    == (time_t) - 1)
421					errx(1, "malformed at:\n%s", errline);
422				working->flags |= CE_TRIMAT;
423			}
424		}
425
426		if (eol)
427			q = NULL;
428		else {
429			q = parse = sob(++parse);	/* Optional field */
430			parse = son(parse);
431			if (!*parse)
432				eol = 1;
433			*parse = '\0';
434		}
435
436		while (q && *q && !isspace(*q)) {
437			if ((*q == 'Z') || (*q == 'z'))
438				working->flags |= CE_COMPACT;
439			else if ((*q == 'J') || (*q == 'j'))
440				working->flags |= CE_BZCOMPACT;
441			else if ((*q == 'B') || (*q == 'b'))
442				working->flags |= CE_BINARY;
443			else if (*q != '-')
444				errx(1, "illegal flag in config file -- %c",
445				    *q);
446			q++;
447		}
448
449		if (eol)
450			q = NULL;
451		else {
452			q = parse = sob(++parse);	/* Optional field */
453			parse = son(parse);
454			if (!*parse)
455				eol = 1;
456			*parse = '\0';
457		}
458
459		working->pid_file = NULL;
460		if (q && *q) {
461			if (*q == '/')
462				working->pid_file = strdup(q);
463			else if (isdigit(*q))
464				goto got_sig;
465			else
466				errx(1,
467			"illegal pid file or signal number in config file:\n%s",
468				    errline);
469		}
470		if (eol)
471			q = NULL;
472		else {
473			q = parse = sob(++parse);	/* Optional field */
474			*(parse = son(parse)) = '\0';
475		}
476
477		working->sig = SIGHUP;
478		if (q && *q) {
479			if (isdigit(*q)) {
480		got_sig:
481				working->sig = atoi(q);
482			} else {
483		err_sig:
484				errx(1,
485				    "illegal signal number in config file:\n%s",
486				    errline);
487			}
488			if (working->sig < 1 || working->sig >= NSIG)
489				goto err_sig;
490		}
491		free(errline);
492	}
493	if (working)
494		working->next = (struct conf_entry *) NULL;
495	(void) fclose(f);
496	return (first);
497}
498
499static char *
500missing_field(char *p, char *errline)
501{
502
503	if (!p || !*p)
504		errx(1, "missing field in config file:\n%s", errline);
505	return (p);
506}
507
508static void
509dotrim(char *log, const char *pid_file, int numdays, int flags, int perm,
510    int owner_uid, int group_gid, int sig)
511{
512	char dirpart[MAXPATHLEN], namepart[MAXPATHLEN];
513	char file1[MAXPATHLEN], file2[MAXPATHLEN];
514	char zfile1[MAXPATHLEN], zfile2[MAXPATHLEN];
515	char jfile1[MAXPATHLEN];
516	int notified, need_notification, fd, _numdays;
517	struct stat st;
518	pid_t pid;
519
520#ifdef _IBMR2
521	/*
522	 * AIX 3.1 has a broken fchown- if the owner_uid is -1, it will
523	 * actually change it to be owned by uid -1, instead of leaving it
524	 * as is, as it is supposed to.
525	 */
526	if (owner_uid == -1)
527		owner_uid = geteuid();
528#endif
529
530	if (archtodir) {
531		char *p;
532
533		/* build complete name of archive directory into dirpart */
534		if (*archdirname == '/') {	/* absolute */
535			strlcpy(dirpart, archdirname, sizeof(dirpart));
536		} else {	/* relative */
537			/* get directory part of logfile */
538			strlcpy(dirpart, log, sizeof(dirpart));
539			if ((p = rindex(dirpart, '/')) == NULL)
540				dirpart[0] = '\0';
541			else
542				*(p + 1) = '\0';
543			strlcat(dirpart, archdirname, sizeof(dirpart));
544		}
545
546		/* check if archive directory exists, if not, create it */
547		if (lstat(dirpart, &st))
548			createdir(dirpart);
549
550		/* get filename part of logfile */
551		if ((p = rindex(log, '/')) == NULL)
552			strlcpy(namepart, log, sizeof(namepart));
553		else
554			strlcpy(namepart, p + 1, sizeof(namepart));
555
556		/* name of oldest log */
557		(void) snprintf(file1, sizeof(file1), "%s/%s.%d", dirpart,
558		    namepart, numdays);
559		(void) snprintf(zfile1, sizeof(zfile1), "%s%s", file1,
560		    COMPRESS_POSTFIX);
561		snprintf(jfile1, sizeof(jfile1), "%s%s", file1,
562		    BZCOMPRESS_POSTFIX);
563	} else {
564		/* name of oldest log */
565		(void) snprintf(file1, sizeof(file1), "%s.%d", log, numdays);
566		(void) snprintf(zfile1, sizeof(zfile1), "%s%s", file1,
567		    COMPRESS_POSTFIX);
568		snprintf(jfile1, sizeof(jfile1), "%s%s", file1,
569		    BZCOMPRESS_POSTFIX);
570	}
571
572	if (noaction) {
573		printf("rm -f %s\n", file1);
574		printf("rm -f %s\n", zfile1);
575		printf("rm -f %s\n", jfile1);
576	} else {
577		(void) unlink(file1);
578		(void) unlink(zfile1);
579		(void) unlink(jfile1);
580	}
581
582	/* Move down log files */
583	_numdays = numdays;	/* preserve */
584	while (numdays--) {
585
586		(void) strlcpy(file2, file1, sizeof(file2));
587
588		if (archtodir)
589			(void) snprintf(file1, sizeof(file1), "%s/%s.%d",
590			    dirpart, namepart, numdays);
591		else
592			(void) snprintf(file1, sizeof(file1), "%s.%d", log,
593			    numdays);
594
595		(void) strlcpy(zfile1, file1, sizeof(zfile1));
596		(void) strlcpy(zfile2, file2, sizeof(zfile2));
597		if (lstat(file1, &st)) {
598			(void) strlcat(zfile1, COMPRESS_POSTFIX,
599			    sizeof(zfile1));
600			(void) strlcat(zfile2, COMPRESS_POSTFIX,
601			    sizeof(zfile2));
602			if (lstat(zfile1, &st)) {
603				strlcpy(zfile1, file1, sizeof(zfile1));
604				strlcpy(zfile2, file2, sizeof(zfile2));
605				strlcat(zfile1, BZCOMPRESS_POSTFIX,
606				    sizeof(zfile1));
607				strlcat(zfile2, BZCOMPRESS_POSTFIX,
608				    sizeof(zfile2));
609				if (lstat(zfile1, &st))
610					continue;
611			}
612		}
613		if (noaction) {
614			printf("mv %s %s\n", zfile1, zfile2);
615			printf("chmod %o %s\n", perm, zfile2);
616			printf("chown %d:%d %s\n",
617			    owner_uid, group_gid, zfile2);
618		} else {
619			(void) rename(zfile1, zfile2);
620			(void) chmod(zfile2, perm);
621			(void) chown(zfile2, owner_uid, group_gid);
622		}
623	}
624	if (!noaction && !(flags & CE_BINARY))
625		(void) log_trim(log);	/* Report the trimming to the old log */
626
627	if (!_numdays) {
628		if (noaction)
629			printf("rm %s\n", log);
630		else
631			(void) unlink(log);
632	} else {
633		if (noaction)
634			printf("mv %s to %s\n", log, file1);
635		else {
636			if (archtodir)
637				movefile(log, file1, perm, owner_uid,
638				    group_gid);
639			else
640				(void) rename(log, file1);
641		}
642	}
643
644	if (noaction)
645		printf("Start new log...");
646	else {
647		fd = creat(log, perm);
648		if (fd < 0)
649			err(1, "can't start new log");
650		if (fchown(fd, owner_uid, group_gid))
651			err(1, "can't chmod new log file");
652		(void) close(fd);
653		if (!(flags & CE_BINARY))
654			if (log_trim(log))	/* Add status message */
655				err(1, "can't add status message to log");
656	}
657	if (noaction)
658		printf("chmod %o %s...\n", perm, log);
659	else
660		(void) chmod(log, perm);
661
662	pid = 0;
663	need_notification = notified = 0;
664	if (pid_file != NULL) {
665		need_notification = 1;
666		pid = get_pid(pid_file);
667	}
668	if (pid) {
669		if (noaction) {
670			notified = 1;
671			printf("kill -%d %d\n", sig, (int) pid);
672		} else if (kill(pid, sig))
673			warn("can't notify daemon, pid %d", (int) pid);
674		else {
675			notified = 1;
676			if (verbose)
677				printf("daemon pid %d notified\n", (int) pid);
678		}
679	}
680	if ((flags & CE_COMPACT) || (flags & CE_BZCOMPACT)) {
681		if (need_notification && !notified)
682			warnx(
683			    "log %s not compressed because daemon not notified",
684			    log);
685		else if (noaction)
686			printf("Compress %s.0\n", log);
687		else {
688			if (notified) {
689				if (verbose)
690					printf("small pause to allow daemon to close log\n");
691				sleep(10);
692			}
693			if (archtodir) {
694				(void) snprintf(file1, sizeof(file1), "%s/%s",
695				    dirpart, namepart);
696				if (flags & CE_COMPACT)
697					compress_log(file1);
698				else if (flags & CE_BZCOMPACT)
699					bzcompress_log(file1);
700			} else {
701				if (flags & CE_COMPACT)
702					compress_log(log);
703				else if (flags & CE_BZCOMPACT)
704					bzcompress_log(log);
705			}
706		}
707	}
708}
709
710/* Log the fact that the logs were turned over */
711static int
712log_trim(char *log)
713{
714	FILE *f;
715
716	if ((f = fopen(log, "a")) == NULL)
717		return (-1);
718	fprintf(f, "%s %s newsyslog[%d]: logfile turned over\n",
719	    daytime, hostname, (int) getpid());
720	if (fclose(f) == EOF)
721		err(1, "log_trim: fclose:");
722	return (0);
723}
724
725/* Fork of gzip to compress the old log file */
726static void
727compress_log(char *log)
728{
729	pid_t pid;
730	char tmp[MAXPATHLEN];
731
732	(void) snprintf(tmp, sizeof(tmp), "%s.0", log);
733	pid = fork();
734	if (pid < 0)
735		err(1, "gzip fork");
736	else if (!pid) {
737		(void) execl(_PATH_GZIP, _PATH_GZIP, "-f", tmp, (char *)0);
738		err(1, _PATH_GZIP);
739	}
740}
741
742/* Fork of bzip2 to compress the old log file */
743static void
744bzcompress_log(char *log)
745{
746	pid_t pid;
747	char tmp[MAXPATHLEN];
748
749	snprintf(tmp, sizeof(tmp), "%s.0", log);
750	pid = fork();
751	if (pid < 0)
752		err(1, "bzip2 fork");
753	else if (!pid) {
754		execl(_PATH_BZIP2, _PATH_BZIP2, "-f", tmp, (char *)0);
755		err(1, _PATH_BZIP2);
756	}
757}
758
759/* Return size in kilobytes of a file */
760static int
761sizefile(char *file)
762{
763	struct stat sb;
764
765	if (stat(file, &sb) < 0)
766		return (-1);
767	return (kbytes(dbtob(sb.st_blocks)));
768}
769
770/* Return the age of old log file (file.0) */
771static int
772age_old_log(char *file)
773{
774	struct stat sb;
775	char tmp[MAXPATHLEN + sizeof(".0") + sizeof(COMPRESS_POSTFIX) + 1];
776
777	if (archtodir) {
778		char *p;
779
780		/* build name of archive directory into tmp */
781		if (*archdirname == '/') {	/* absolute */
782			strlcpy(tmp, archdirname, sizeof(tmp));
783		} else {	/* relative */
784			/* get directory part of logfile */
785			strlcpy(tmp, file, sizeof(tmp));
786			if ((p = rindex(tmp, '/')) == NULL)
787				tmp[0] = '\0';
788			else
789				*(p + 1) = '\0';
790			strlcat(tmp, archdirname, sizeof(tmp));
791		}
792
793		strlcat(tmp, "/", sizeof(tmp));
794
795		/* get filename part of logfile */
796		if ((p = rindex(file, '/')) == NULL)
797			strlcat(tmp, file, sizeof(tmp));
798		else
799			strlcat(tmp, p + 1, sizeof(tmp));
800	} else {
801		(void) strlcpy(tmp, file, sizeof(tmp));
802	}
803
804	if (stat(strcat(tmp, ".0"), &sb) < 0)
805		if (stat(strcat(tmp, COMPRESS_POSTFIX), &sb) < 0)
806			return (-1);
807	return ((int) (timenow - sb.st_mtime + 1800) / 3600);
808}
809
810static pid_t
811get_pid(const char *pid_file)
812{
813	FILE *f;
814	char line[BUFSIZ];
815	pid_t pid = 0;
816
817	if ((f = fopen(pid_file, "r")) == NULL)
818		warn("can't open %s pid file to restart a daemon",
819		    pid_file);
820	else {
821		if (fgets(line, BUFSIZ, f)) {
822			pid = atol(line);
823			if (pid < MIN_PID || pid > MAX_PID) {
824				warnx("preposterous process number: %d",
825				   (int)pid);
826				pid = 0;
827			}
828		} else
829			warn("can't read %s pid file to restart a daemon",
830			    pid_file);
831		(void) fclose(f);
832	}
833	return pid;
834}
835
836/* Skip Over Blanks */
837char *
838sob(char *p)
839{
840	while (p && *p && isspace(*p))
841		p++;
842	return (p);
843}
844
845/* Skip Over Non-Blanks */
846char *
847son(char *p)
848{
849	while (p && *p && !isspace(*p))
850		p++;
851	return (p);
852}
853
854/*
855 * Parse a limited subset of ISO 8601. The specific format is as follows:
856 *
857 * [CC[YY[MM[DD]]]][THH[MM[SS]]]	(where `T' is the literal letter)
858 *
859 * We don't accept a timezone specification; missing fields (including timezone)
860 * are defaulted to the current date but time zero.
861 */
862static time_t
863parse8601(char *s)
864{
865	char *t;
866	struct tm tm, *tmp;
867	u_long ul;
868
869	tmp = localtime(&timenow);
870	tm = *tmp;
871
872	tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
873
874	ul = strtoul(s, &t, 10);
875	if (*t != '\0' && *t != 'T')
876		return -1;
877
878	/*
879	 * Now t points either to the end of the string (if no time was
880	 * provided) or to the letter `T' which separates date and time in
881	 * ISO 8601.  The pointer arithmetic is the same for either case.
882	 */
883	switch (t - s) {
884	case 8:
885		tm.tm_year = ((ul / 1000000) - 19) * 100;
886		ul = ul % 1000000;
887	case 6:
888		tm.tm_year -= tm.tm_year % 100;
889		tm.tm_year += ul / 10000;
890		ul = ul % 10000;
891	case 4:
892		tm.tm_mon = (ul / 100) - 1;
893		ul = ul % 100;
894	case 2:
895		tm.tm_mday = ul;
896	case 0:
897		break;
898	default:
899		return -1;
900	}
901
902	/* sanity check */
903	if (tm.tm_year < 70 || tm.tm_mon < 0 || tm.tm_mon > 12
904	    || tm.tm_mday < 1 || tm.tm_mday > 31)
905		return -1;
906
907	if (*t != '\0') {
908		s = ++t;
909		ul = strtoul(s, &t, 10);
910		if (*t != '\0' && !isspace(*t))
911			return -1;
912
913		switch (t - s) {
914		case 6:
915			tm.tm_sec = ul % 100;
916			ul /= 100;
917		case 4:
918			tm.tm_min = ul % 100;
919			ul /= 100;
920		case 2:
921			tm.tm_hour = ul;
922		case 0:
923			break;
924		default:
925			return -1;
926		}
927
928		/* sanity check */
929		if (tm.tm_sec < 0 || tm.tm_sec > 60 || tm.tm_min < 0
930		    || tm.tm_min > 59 || tm.tm_hour < 0 || tm.tm_hour > 23)
931			return -1;
932	}
933	return mktime(&tm);
934}
935
936/* physically move file */
937static void
938movefile(char *from, char *to, int perm, int owner_uid, int group_gid)
939{
940	FILE *src, *dst;
941	int c;
942
943	if ((src = fopen(from, "r")) == NULL)
944		err(1, "can't fopen %s for reading", from);
945	if ((dst = fopen(to, "w")) == NULL)
946		err(1, "can't fopen %s for writing", to);
947	if (fchown(fileno(dst), owner_uid, group_gid))
948		err(1, "can't fchown %s", to);
949	if (fchmod(fileno(dst), perm))
950		err(1, "can't fchmod %s", to);
951
952	while ((c = getc(src)) != EOF) {
953		if ((putc(c, dst)) == EOF)
954			err(1, "error writing to %s", to);
955	}
956
957	if (ferror(src))
958		err(1, "error reading from %s", from);
959	if ((fclose(src)) != 0)
960		err(1, "can't fclose %s", to);
961	if ((fclose(dst)) != 0)
962		err(1, "can't fclose %s", from);
963	if ((unlink(from)) != 0)
964		err(1, "can't unlink %s", from);
965}
966
967/* create one or more directory components of a path */
968static void
969createdir(char *dirpart)
970{
971	char *s, *d;
972	char mkdirpath[MAXPATHLEN];
973	struct stat st;
974
975	s = dirpart;
976	d = mkdirpath;
977
978	for (;;) {
979		*d++ = *s++;
980		if (*s == '/' || *s == '\0') {
981			*d = '\0';
982			if (lstat(mkdirpath, &st))
983				mkdir(mkdirpath, 0755);
984		}
985		if (*s == '\0')
986			break;
987	}
988}
989
990/*-
991 * Parse a cyclic time specification, the format is as follows:
992 *
993 *	[Dhh] or [Wd[Dhh]] or [Mdd[Dhh]]
994 *
995 * to rotate a logfile cyclic at
996 *
997 *	- every day (D) within a specific hour (hh)	(hh = 0...23)
998 *	- once a week (W) at a specific day (d)     OR	(d = 0..6, 0 = Sunday)
999 *	- once a month (M) at a specific day (d)	(d = 1..31,l|L)
1000 *
1001 * We don't accept a timezone specification; missing fields
1002 * are defaulted to the current date but time zero.
1003 */
1004static time_t
1005parseDWM(char *s)
1006{
1007	char *t;
1008	struct tm tm, *tmp;
1009	long l;
1010	int nd;
1011	static int mtab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
1012	int WMseen = 0;
1013	int Dseen = 0;
1014
1015	tmp = localtime(&timenow);
1016	tm = *tmp;
1017
1018	/* set no. of days per month */
1019
1020	nd = mtab[tm.tm_mon];
1021
1022	if (tm.tm_mon == 1) {
1023		if (((tm.tm_year + 1900) % 4 == 0) &&
1024		    ((tm.tm_year + 1900) % 100 != 0) &&
1025		    ((tm.tm_year + 1900) % 400 == 0)) {
1026			nd++;	/* leap year, 29 days in february */
1027		}
1028	}
1029	tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
1030
1031	for (;;) {
1032		switch (*s) {
1033		case 'D':
1034			if (Dseen)
1035				return -1;
1036			Dseen++;
1037			s++;
1038			l = strtol(s, &t, 10);
1039			if (l < 0 || l > 23)
1040				return -1;
1041			tm.tm_hour = l;
1042			break;
1043
1044		case 'W':
1045			if (WMseen)
1046				return -1;
1047			WMseen++;
1048			s++;
1049			l = strtol(s, &t, 10);
1050			if (l < 0 || l > 6)
1051				return -1;
1052			if (l != tm.tm_wday) {
1053				int save;
1054
1055				if (l < tm.tm_wday) {
1056					save = 6 - tm.tm_wday;
1057					save += (l + 1);
1058				} else {
1059					save = l - tm.tm_wday;
1060				}
1061
1062				tm.tm_mday += save;
1063
1064				if (tm.tm_mday > nd) {
1065					tm.tm_mon++;
1066					tm.tm_mday = tm.tm_mday - nd;
1067				}
1068			}
1069			break;
1070
1071		case 'M':
1072			if (WMseen)
1073				return -1;
1074			WMseen++;
1075			s++;
1076			if (tolower(*s) == 'l') {
1077				tm.tm_mday = nd;
1078				s++;
1079				t = s;
1080			} else {
1081				l = strtol(s, &t, 10);
1082				if (l < 1 || l > 31)
1083					return -1;
1084
1085				if (l > nd)
1086					return -1;
1087				tm.tm_mday = l;
1088			}
1089			break;
1090
1091		default:
1092			return (-1);
1093			break;
1094		}
1095
1096		if (*t == '\0' || isspace(*t))
1097			break;
1098		else
1099			s = t;
1100	}
1101	return mktime(&tm);
1102}
1103