newsyslog.c revision 59004
113244Sgraichen/*
213244Sgraichen * This file contains changes from the Open Software Foundation.
313244Sgraichen */
413244Sgraichen
513244Sgraichen/*
659004Shm * Copyright 1988, 1989 by the Massachusetts Institute of Technology
759004Shm *
859004Shm * Permission to use, copy, modify, and distribute this software and its
959004Shm * documentation for any purpose and without fee is hereby granted, provided
1059004Shm * that the above copyright notice appear in all copies and that both that
1159004Shm * copyright notice and this permission notice appear in supporting
1259004Shm * documentation, and that the names of M.I.T. and the M.I.T. S.I.P.B. not be
1359004Shm * used in advertising or publicity pertaining to distribution of the
1459004Shm * software without specific, written prior permission. M.I.T. and the M.I.T.
1559004Shm * S.I.P.B. make no representations about the suitability of this software
1659004Shm * for any purpose.  It is provided "as is" without express or implied
1759004Shm * warranty.
1859004Shm *
1959004Shm */
2013244Sgraichen
2113244Sgraichen/*
2259004Shm * newsyslog - roll over selected logs at the appropriate time, keeping the a
2359004Shm * specified number of backup files around.
2413244Sgraichen */
2513244Sgraichen
2613244Sgraichen#ifndef lint
2730160Scharnierstatic const char rcsid[] =
2859003Shm"$FreeBSD: head/usr.sbin/newsyslog/newsyslog.c 59004 2000-04-04 08:50:01Z hm $";
2959004Shm#endif	/* not lint */
3013244Sgraichen
3143071Swollman#define OSF
3213244Sgraichen#ifndef COMPRESS_POSTFIX
3343071Swollman#define COMPRESS_POSTFIX ".gz"
3413244Sgraichen#endif
3513244Sgraichen
3630160Scharnier#include <ctype.h>
3730160Scharnier#include <err.h>
3830160Scharnier#include <fcntl.h>
3930160Scharnier#include <grp.h>
4043071Swollman#include <paths.h>
4130160Scharnier#include <pwd.h>
4230160Scharnier#include <signal.h>
4313244Sgraichen#include <stdio.h>
4413244Sgraichen#include <stdlib.h>
4513244Sgraichen#include <string.h>
4643071Swollman#include <time.h>
4716240Salex#include <unistd.h>
4813244Sgraichen#include <sys/types.h>
4913244Sgraichen#include <sys/stat.h>
5013244Sgraichen#include <sys/param.h>
5113244Sgraichen#include <sys/wait.h>
5213244Sgraichen
5343071Swollman#include "pathnames.h"
5443071Swollman
5513244Sgraichen#define kbytes(size)  (((size) + 1023) >> 10)
5659004Shm
5713244Sgraichen#ifdef _IBMR2
5813244Sgraichen/* Calculates (db * DEV_BSIZE) */
5959003Shm#define dbtob(db)  ((unsigned)(db) << UBSHIFT)
6013244Sgraichen#endif
6113244Sgraichen
6259003Shm#define CE_COMPACT 1		/* Compact the achived log files */
6359003Shm#define CE_BINARY  2		/* Logfile is in binary, don't add */
6459004Shm				/*  status messages */
6543071Swollman#define	CE_TRIMAT  4		/* trim at a specific time */
6643071Swollman
6713244Sgraichen#define NONE -1
6859003Shm
6913244Sgraichenstruct conf_entry {
7059003Shm	char *log;		/* Name of the log */
7159003Shm	char *pid_file;		/* PID file */
7259003Shm	int uid;		/* Owner of log */
7359003Shm	int gid;		/* Group of log */
7459003Shm	int numlogs;		/* Number of logs to keep */
7559003Shm	int size;		/* Size cutoff to trigger trimming the log */
7659003Shm	int hours;		/* Hours between log trimming */
7759003Shm	time_t trim_at;		/* Specific time to do trimming */
7859003Shm	int permissions;	/* File permissions on the log */
7959003Shm	int flags;		/* Flags (CE_COMPACT & CE_BINARY)  */
8059003Shm	int sig;		/* Signal to send */
8159003Shm	struct conf_entry *next;/* Linked list pointer */
8213244Sgraichen};
8313244Sgraichen
8459004Shmint archtodir = 0;		/* Archive old logfiles to other directory */
8559003Shmint verbose = 0;		/* Print out what's going on */
8659003Shmint needroot = 1;		/* Root privs are necessary */
8759003Shmint noaction = 0;		/* Don't do anything, just show it */
8859003Shmint force = 0;			/* Force the trim no matter what */
8959004Shmchar *archdirname;		/* Directory path to old logfiles archive */
9059003Shmchar *conf = _PATH_CONF;	/* Configuration file to use */
9159003Shmtime_t timenow;
9259003Shm
9325443Sache#define MIN_PID         5
9459003Shm#define MAX_PID		99999	/* was lower, see /usr/include/sys/proc.h */
9559003Shmchar hostname[MAXHOSTNAMELEN + 1];	/* hostname */
9659003Shmchar *daytime;			/* timenow in human readable form */
9713244Sgraichen
9816240Salexstatic struct conf_entry *parse_file();
9916240Salexstatic char *sob(char *p);
10016240Salexstatic char *son(char *p);
10159003Shmstatic char *missing_field(char *p, char *errline);
10259003Shmstatic void do_entry(struct conf_entry * ent);
10359003Shmstatic void PRS(int argc, char **argv);
10416240Salexstatic void usage();
10559003Shmstatic void dotrim(char *log, char *pid_file, int numdays, int falgs, int perm, int owner_uid, int group_gid, int sig);
10616240Salexstatic int log_trim(char *log);
10716240Salexstatic void compress_log(char *log);
10816240Salexstatic int sizefile(char *file);
10916240Salexstatic int age_old_log(char *file);
11025443Sachestatic pid_t get_pid(char *pid_file);
11159004Shmstatic time_t parse8601(char *s);
11259004Shmstatic void movefile(char *from, char *to, int perm, int owner_uid, int group_gid);
11359004Shmstatic void createdir(char *dirpart);
11459004Shmstatic time_t parseDWM(char *s);
11513244Sgraichen
11659004Shmint
11759004Shmmain(int argc, char **argv)
11813244Sgraichen{
11959003Shm	struct conf_entry *p, *q;
12025443Sache
12159003Shm	PRS(argc, argv);
12259003Shm	if (needroot && getuid() && geteuid())
12359003Shm		errx(1, "must have root privs");
12459003Shm	p = q = parse_file();
12559003Shm
12659003Shm	while (p) {
12759003Shm		do_entry(p);
12859003Shm		p = p->next;
12959003Shm		free((char *) q);
13059003Shm		q = p;
13159003Shm	}
13259003Shm	return (0);
13313244Sgraichen}
13413244Sgraichen
13559004Shmstatic void
13659004Shmdo_entry(struct conf_entry * ent)
13713244Sgraichen{
13859003Shm	int size, modtime;
13959003Shm	char *pid_file;
14059003Shm
14159003Shm	if (verbose) {
14259003Shm		if (ent->flags & CE_COMPACT)
14359003Shm			printf("%s <%dZ>: ", ent->log, ent->numlogs);
14459003Shm		else
14559003Shm			printf("%s <%d>: ", ent->log, ent->numlogs);
14659003Shm	}
14759003Shm	size = sizefile(ent->log);
14859003Shm	modtime = age_old_log(ent->log);
14959003Shm	if (size < 0) {
15059003Shm		if (verbose)
15159003Shm			printf("does not exist.\n");
15259003Shm	} else {
15343071Swollman		if (ent->flags & CE_TRIMAT) {
15443071Swollman			if (timenow < ent->trim_at
15559003Shm			    || difftime(timenow, ent->trim_at) >= 60 * 60) {
15643071Swollman				if (verbose)
15743071Swollman					printf("--> will trim at %s",
15859003Shm					    ctime(&ent->trim_at));
15943071Swollman				return;
16043071Swollman			} else if (verbose && ent->hours <= 0) {
16143071Swollman				printf("--> time is up\n");
16243071Swollman			}
16343071Swollman		}
16459003Shm		if (verbose && (ent->size > 0))
16559003Shm			printf("size (Kb): %d [%d] ", size, ent->size);
16659003Shm		if (verbose && (ent->hours > 0))
16759003Shm			printf(" age (hr): %d [%d] ", modtime, ent->hours);
16859003Shm		if (force || ((ent->size > 0) && (size >= ent->size)) ||
16943071Swollman		    (ent->hours <= 0 && (ent->flags & CE_TRIMAT)) ||
17059003Shm		    ((ent->hours > 0) && ((modtime >= ent->hours)
17159003Shm			    || (modtime < 0)))) {
17259003Shm			if (verbose)
17359003Shm				printf("--> trimming log....\n");
17459003Shm			if (noaction && !verbose) {
17559003Shm				if (ent->flags & CE_COMPACT)
17659003Shm					printf("%s <%dZ>: trimming\n",
17759003Shm					    ent->log, ent->numlogs);
17859003Shm				else
17959003Shm					printf("%s <%d>: trimming\n",
18059003Shm					    ent->log, ent->numlogs);
18159003Shm			}
18235920Shoek			if (ent->pid_file) {
18335920Shoek				pid_file = ent->pid_file;
18435920Shoek			} else {
18535920Shoek				/* Only try to notify syslog if we are root */
18635920Shoek				if (needroot)
18743071Swollman					pid_file = _PATH_SYSLOGPID;
18835920Shoek				else
18935920Shoek					pid_file = NULL;
19035920Shoek			}
19159003Shm			dotrim(ent->log, pid_file, ent->numlogs,
19236817Sache			    ent->flags, ent->permissions, ent->uid, ent->gid, ent->sig);
19359003Shm		} else {
19459003Shm			if (verbose)
19559003Shm				printf("--> skipping\n");
19659003Shm		}
19759003Shm	}
19813244Sgraichen}
19913244Sgraichen
20059004Shmstatic void
20159004ShmPRS(int argc, char **argv)
20213244Sgraichen{
20359003Shm	int c;
20459003Shm	char *p;
20513244Sgraichen
20659003Shm	timenow = time((time_t *) 0);
20759003Shm	daytime = ctime(&timenow) + 4;
20859003Shm	daytime[15] = '\0';
20913244Sgraichen
21059003Shm	/* Let's get our hostname */
21159003Shm	(void) gethostname(hostname, sizeof(hostname));
21213244Sgraichen
21313244Sgraichen	/* Truncate domain */
21416240Salex	if ((p = strchr(hostname, '.'))) {
21513244Sgraichen		*p = '\0';
21613244Sgraichen	}
21759003Shm	optind = 1;		/* Start options parsing */
21859004Shm	while ((c = getopt(argc, argv, "nrvFf:a:t:")) != -1)
21959003Shm		switch (c) {
22059003Shm		case 'n':
22159003Shm			noaction++;
22235920Shoek			break;
22359004Shm		case 'a':
22459004Shm			archtodir++;
22559004Shm			archdirname = optarg;
22659004Shm			break;
22759003Shm		case 'r':
22859003Shm			needroot = 0;
22959003Shm			break;
23059003Shm		case 'v':
23159003Shm			verbose++;
23259003Shm			break;
23359003Shm		case 'f':
23459003Shm			conf = optarg;
23559003Shm			break;
23634584Spst		case 'F':
23734584Spst			force++;
23834584Spst			break;
23959003Shm		default:
24059003Shm			usage();
24159003Shm		}
24243071Swollman}
24313244Sgraichen
24459004Shmstatic void
24559004Shmusage(void)
24613244Sgraichen{
24759004Shm	fprintf(stderr, "usage: newsyslog [-Fnrv] [-f config-file] [-a directory]\n");
24859003Shm	exit(1);
24913244Sgraichen}
25013244Sgraichen
25159004Shm/*
25259004Shm * Parse a configuration file and return a linked list of all the logs to
25359004Shm * process
25413244Sgraichen */
25559003Shmstatic struct conf_entry *
25659004Shmparse_file(void)
25713244Sgraichen{
25859003Shm	FILE *f;
25959003Shm	char line[BUFSIZ], *parse, *q;
26059003Shm	char *errline, *group;
26159003Shm	struct conf_entry *first = NULL;
26259003Shm	struct conf_entry *working = NULL;
26359003Shm	struct passwd *pass;
26459003Shm	struct group *grp;
26525518Sbrian	int eol;
26613244Sgraichen
26759003Shm	if (strcmp(conf, "-"))
26859003Shm		f = fopen(conf, "r");
26959003Shm	else
27059003Shm		f = stdin;
27159003Shm	if (!f)
27259003Shm		err(1, "%s", conf);
27359003Shm	while (fgets(line, BUFSIZ, f)) {
27459003Shm		if ((line[0] == '\n') || (line[0] == '#'))
27559003Shm			continue;
27659003Shm		errline = strdup(line);
27759003Shm		if (!first) {
27859003Shm			working = (struct conf_entry *) malloc(sizeof(struct conf_entry));
27959003Shm			first = working;
28059003Shm		} else {
28159003Shm			working->next = (struct conf_entry *) malloc(sizeof(struct conf_entry));
28259003Shm			working = working->next;
28359003Shm		}
28413244Sgraichen
28559003Shm		q = parse = missing_field(sob(line), errline);
28659003Shm		parse = son(line);
28725518Sbrian		if (!*parse)
28859003Shm			errx(1, "malformed line (missing fields):\n%s", errline);
28959003Shm		*parse = '\0';
29059003Shm		working->log = strdup(q);
29113244Sgraichen
29259003Shm		q = parse = missing_field(sob(++parse), errline);
29359003Shm		parse = son(parse);
29425518Sbrian		if (!*parse)
29559003Shm			errx(1, "malformed line (missing fields):\n%s", errline);
29659003Shm		*parse = '\0';
29759003Shm		if ((group = strchr(q, ':')) != NULL ||
29859003Shm		    (group = strrchr(q, '.')) != NULL) {
29959003Shm			*group++ = '\0';
30059003Shm			if (*q) {
30159003Shm				if (!(isnumber(*q))) {
30259003Shm					if ((pass = getpwnam(q)) == NULL)
30359003Shm						errx(1,
30459003Shm						    "error in config file; unknown user:\n%s",
30559003Shm						    errline);
30659003Shm					working->uid = pass->pw_uid;
30759003Shm				} else
30859003Shm					working->uid = atoi(q);
30959003Shm			} else
31059003Shm				working->uid = NONE;
31113244Sgraichen
31259003Shm			q = group;
31359003Shm			if (*q) {
31459003Shm				if (!(isnumber(*q))) {
31559003Shm					if ((grp = getgrnam(q)) == NULL)
31659003Shm						errx(1,
31759003Shm						    "error in config file; unknown group:\n%s",
31859003Shm						    errline);
31959003Shm					working->gid = grp->gr_gid;
32059003Shm				} else
32159003Shm					working->gid = atoi(q);
32259003Shm			} else
32359003Shm				working->gid = NONE;
32413244Sgraichen
32559003Shm			q = parse = missing_field(sob(++parse), errline);
32659003Shm			parse = son(parse);
32759003Shm			if (!*parse)
32859003Shm				errx(1, "malformed line (missing fields):\n%s", errline);
32959003Shm			*parse = '\0';
33059003Shm		} else
33159003Shm			working->uid = working->gid = NONE;
33259003Shm
33359003Shm		if (!sscanf(q, "%o", &working->permissions))
33459003Shm			errx(1, "error in config file; bad permissions:\n%s",
33559003Shm			    errline);
33659003Shm
33759003Shm		q = parse = missing_field(sob(++parse), errline);
33859003Shm		parse = son(parse);
33925518Sbrian		if (!*parse)
34059003Shm			errx(1, "malformed line (missing fields):\n%s", errline);
34159003Shm		*parse = '\0';
34259003Shm		if (!sscanf(q, "%d", &working->numlogs))
34359003Shm			errx(1, "error in config file; bad number:\n%s",
34459003Shm			    errline);
34513244Sgraichen
34659003Shm		q = parse = missing_field(sob(++parse), errline);
34759003Shm		parse = son(parse);
34825518Sbrian		if (!*parse)
34959003Shm			errx(1, "malformed line (missing fields):\n%s", errline);
35059003Shm		*parse = '\0';
35159003Shm		if (isdigit(*q))
35259003Shm			working->size = atoi(q);
35359003Shm		else
35459003Shm			working->size = -1;
35559003Shm
35659003Shm		working->flags = 0;
35759003Shm		q = parse = missing_field(sob(++parse), errline);
35859003Shm		parse = son(parse);
35925518Sbrian		eol = !*parse;
36059003Shm		*parse = '\0';
36143071Swollman		{
36259003Shm			char *ep;
36359003Shm			u_long ul;
36413244Sgraichen
36543071Swollman			ul = strtoul(q, &ep, 10);
36643071Swollman			if (ep == q)
36743071Swollman				working->hours = 0;
36843071Swollman			else if (*ep == '*')
36943071Swollman				working->hours = -1;
37043071Swollman			else if (ul > INT_MAX)
37143071Swollman				errx(1, "interval is too large:\n%s", errline);
37243071Swollman			else
37343071Swollman				working->hours = ul;
37443071Swollman
37559004Shm			if (*ep != '\0' && *ep != '@' && *ep != '*' && *ep != '$')
37643071Swollman				errx(1, "malformed interval/at:\n%s", errline);
37743071Swollman			if (*ep == '@') {
37843071Swollman				if ((working->trim_at = parse8601(ep + 1))
37959003Shm				    == (time_t) - 1)
38043071Swollman					errx(1, "malformed at:\n%s", errline);
38143071Swollman				working->flags |= CE_TRIMAT;
38259004Shm			} else if (*ep == '$') {
38359004Shm				if ((working->trim_at = parseDWM(ep + 1))
38459004Shm				    == (time_t) - 1)
38559004Shm					errx(1, "malformed at:\n%s", errline);
38659004Shm				working->flags |= CE_TRIMAT;
38743071Swollman			}
38843071Swollman		}
38943071Swollman
39025518Sbrian		if (eol)
39159003Shm			q = NULL;
39225518Sbrian		else {
39359003Shm			q = parse = sob(++parse);	/* Optional field */
39459003Shm			parse = son(parse);
39559003Shm			if (!*parse)
39659003Shm				eol = 1;
39759003Shm			*parse = '\0';
39825518Sbrian		}
39925443Sache
40059003Shm		while (q && *q && !isspace(*q)) {
40159003Shm			if ((*q == 'Z') || (*q == 'z'))
40259003Shm				working->flags |= CE_COMPACT;
40359003Shm			else if ((*q == 'B') || (*q == 'b'))
40459003Shm				working->flags |= CE_BINARY;
40559003Shm			else if (*q != '-')
40659003Shm				errx(1, "illegal flag in config file -- %c", *q);
40759003Shm			q++;
40859003Shm		}
40959003Shm
41025518Sbrian		if (eol)
41159003Shm			q = NULL;
41225518Sbrian		else {
41359003Shm			q = parse = sob(++parse);	/* Optional field */
41459003Shm			parse = son(parse);
41559003Shm			if (!*parse)
41659003Shm				eol = 1;
41759003Shm			*parse = '\0';
41825518Sbrian		}
41925443Sache
42025443Sache		working->pid_file = NULL;
42125443Sache		if (q && *q) {
42225443Sache			if (*q == '/')
42325443Sache				working->pid_file = strdup(q);
42436817Sache			else if (isdigit(*q))
42536817Sache				goto got_sig;
42659003Shm			else
42759003Shm				errx(1, "illegal pid file or signal number in config file:\n%s", errline);
42825443Sache		}
42936817Sache		if (eol)
43059003Shm			q = NULL;
43136817Sache		else {
43259003Shm			q = parse = sob(++parse);	/* Optional field */
43359003Shm			*(parse = son(parse)) = '\0';
43436817Sache		}
43536817Sache
43636817Sache		working->sig = SIGHUP;
43736817Sache		if (q && *q) {
43836817Sache			if (isdigit(*q)) {
43959003Shm		got_sig:
44036817Sache				working->sig = atoi(q);
44136817Sache			} else {
44259003Shm		err_sig:
44359003Shm				errx(1, "illegal signal number in config file:\n%s", errline);
44436817Sache			}
44536817Sache			if (working->sig < 1 || working->sig >= NSIG)
44636817Sache				goto err_sig;
44736817Sache		}
44859003Shm		free(errline);
44959003Shm	}
45059003Shm	if (working)
45159003Shm		working->next = (struct conf_entry *) NULL;
45259003Shm	(void) fclose(f);
45359003Shm	return (first);
45413244Sgraichen}
45513244Sgraichen
45659003Shmstatic char *
45759004Shmmissing_field(char *p, char *errline)
45813244Sgraichen{
45959003Shm	if (!p || !*p)
46059003Shm		errx(1, "missing field in config file:\n%s", errline);
46159003Shm	return (p);
46213244Sgraichen}
46313244Sgraichen
46459004Shmstatic void
46559004Shmdotrim(char *log, char *pid_file, int numdays, int flags, int perm,
46659004Shm    int owner_uid, int group_gid, int sig)
46713244Sgraichen{
46859004Shm	char dirpart[MAXPATHLEN + 1], namepart[MAXPATHLEN + 1];
46959003Shm	char file1[MAXPATHLEN + 1], file2[MAXPATHLEN + 1];
47059003Shm	char zfile1[MAXPATHLEN + 1], zfile2[MAXPATHLEN + 1];
47159003Shm	int notified, need_notification, fd, _numdays;
47259003Shm	struct stat st;
47359003Shm	pid_t pid;
47413244Sgraichen
47513244Sgraichen#ifdef _IBMR2
47659004Shm	/*
47759004Shm	 * AIX 3.1 has a broken fchown- if the owner_uid is -1, it will
47859004Shm	 * actually change it to be owned by uid -1, instead of leaving it
47959004Shm	 * as is, as it is supposed to.
48059004Shm	 */
48159003Shm	if (owner_uid == -1)
48259003Shm		owner_uid = geteuid();
48313244Sgraichen#endif
48413244Sgraichen
48559004Shm	if (archtodir) {
48659004Shm		char *p;
48713244Sgraichen
48859004Shm		/* build complete name of archive directory into dirpart */
48959004Shm		if (*archdirname == '/') {	/* absolute */
49059004Shm			strcpy(dirpart, archdirname);
49159004Shm		} else {	/* relative */
49259004Shm			/* get directory part of logfile */
49359004Shm			strcpy(dirpart, log);
49459004Shm			if ((p = rindex(dirpart, '/')) == NULL)
49559004Shm				dirpart[0] = '\0';
49659004Shm			else
49759004Shm				*(p + 1) = '\0';
49859004Shm			strcat(dirpart, archdirname);
49959004Shm		}
50059004Shm
50159004Shm		/* check if archive directory exists, if not, create it */
50259004Shm		if (lstat(dirpart, &st))
50359004Shm			createdir(dirpart);
50459004Shm
50559004Shm		/* get filename part of logfile */
50659004Shm		if ((p = rindex(log, '/')) == NULL)
50759004Shm			strcpy(namepart, log);
50859004Shm		else
50959004Shm			strcpy(namepart, p + 1);
51059004Shm
51159004Shm		/* name of oldest log */
51259004Shm		(void) sprintf(file1, "%s/%s.%d", dirpart, namepart, numdays);
51359004Shm		(void) strcpy(zfile1, file1);
51459004Shm		(void) strcat(zfile1, COMPRESS_POSTFIX);
51559004Shm	} else {
51659004Shm		/* name of oldest log */
51759004Shm		(void) sprintf(file1, "%s.%d", log, numdays);
51859004Shm		(void) strcpy(zfile1, file1);
51959004Shm		(void) strcat(zfile1, COMPRESS_POSTFIX);
52059004Shm	}
52159004Shm
52259003Shm	if (noaction) {
52359003Shm		printf("rm -f %s\n", file1);
52459003Shm		printf("rm -f %s\n", zfile1);
52559003Shm	} else {
52659003Shm		(void) unlink(file1);
52759003Shm		(void) unlink(zfile1);
52859003Shm	}
52913244Sgraichen
53059003Shm	/* Move down log files */
53118188Sjkh	_numdays = numdays;	/* preserve */
53259003Shm	while (numdays--) {
53359004Shm
53459003Shm		(void) strcpy(file2, file1);
53559004Shm
53659004Shm		if (archtodir)
53759004Shm			(void) sprintf(file1, "%s/%s.%d", dirpart, namepart, numdays);
53859004Shm		else
53959004Shm			(void) sprintf(file1, "%s.%d", log, numdays);
54059004Shm
54159003Shm		(void) strcpy(zfile1, file1);
54259003Shm		(void) strcpy(zfile2, file2);
54359003Shm		if (lstat(file1, &st)) {
54459003Shm			(void) strcat(zfile1, COMPRESS_POSTFIX);
54559003Shm			(void) strcat(zfile2, COMPRESS_POSTFIX);
54659003Shm			if (lstat(zfile1, &st))
54759003Shm				continue;
54859003Shm		}
54959003Shm		if (noaction) {
55059003Shm			printf("mv %s %s\n", zfile1, zfile2);
55159003Shm			printf("chmod %o %s\n", perm, zfile2);
55259003Shm			printf("chown %d.%d %s\n",
55359003Shm			    owner_uid, group_gid, zfile2);
55459003Shm		} else {
55559003Shm			(void) rename(zfile1, zfile2);
55659003Shm			(void) chmod(zfile2, perm);
55759003Shm			(void) chown(zfile2, owner_uid, group_gid);
55859003Shm		}
55959003Shm	}
56059003Shm	if (!noaction && !(flags & CE_BINARY))
56159003Shm		(void) log_trim(log);	/* Report the trimming to the old log */
56213244Sgraichen
56318188Sjkh	if (!_numdays) {
56418075Sjkh		if (noaction)
56559003Shm			printf("rm %s\n", log);
56618075Sjkh		else
56759003Shm			(void) unlink(log);
56859003Shm	} else {
56918075Sjkh		if (noaction)
57059003Shm			printf("mv %s to %s\n", log, file1);
57159004Shm		else {
57259004Shm			if (archtodir)
57359004Shm				movefile(log, file1, perm, owner_uid, group_gid);
57459004Shm			else
57559004Shm				(void) rename(log, file1);
57659004Shm		}
57718075Sjkh	}
57818075Sjkh
57959003Shm	if (noaction)
58059003Shm		printf("Start new log...");
58159003Shm	else {
58259003Shm		fd = creat(log, perm);
58359003Shm		if (fd < 0)
58459003Shm			err(1, "can't start new log");
58559003Shm		if (fchown(fd, owner_uid, group_gid))
58659003Shm			err(1, "can't chmod new log file");
58759003Shm		(void) close(fd);
58859003Shm		if (!(flags & CE_BINARY))
58959003Shm			if (log_trim(log))	/* Add status message */
59059003Shm				err(1, "can't add status message to log");
59159003Shm	}
59259003Shm	if (noaction)
59359003Shm		printf("chmod %o %s...\n", perm, log);
59459003Shm	else
59559003Shm		(void) chmod(log, perm);
59625443Sache
59725443Sache	pid = 0;
59825443Sache	need_notification = notified = 0;
59925443Sache	if (pid_file != NULL) {
60025443Sache		need_notification = 1;
60125443Sache		pid = get_pid(pid_file);
60225443Sache	}
60325443Sache	if (pid) {
60425443Sache		if (noaction) {
60525443Sache			notified = 1;
60659003Shm			printf("kill -%d %d\n", sig, (int) pid);
60759003Shm		} else if (kill(pid, sig))
60859003Shm			warn("can't notify daemon, pid %d", (int) pid);
60925443Sache		else {
61025443Sache			notified = 1;
61125443Sache			if (verbose)
61259003Shm				printf("daemon pid %d notified\n", (int) pid);
61325443Sache		}
61425443Sache	}
61525443Sache	if ((flags & CE_COMPACT)) {
61625443Sache		if (need_notification && !notified)
61725443Sache			warnx("log not compressed because daemon not notified");
61825443Sache		else if (noaction)
61959003Shm			printf("Compress %s.0\n", log);
62025443Sache		else {
62125443Sache			if (notified) {
62225443Sache				if (verbose)
62325443Sache					printf("small pause to allow daemon to close log\n");
62431460Sache				sleep(10);
62525443Sache			}
62659004Shm			if (archtodir) {
62759004Shm				(void) sprintf(file1, "%s/%s", dirpart, namepart);
62859004Shm				compress_log(file1);
62959004Shm			} else {
63059004Shm				compress_log(log);
63159004Shm			}
63225443Sache		}
63359003Shm	}
63413244Sgraichen}
63513244Sgraichen
63613244Sgraichen/* Log the fact that the logs were turned over */
63759004Shmstatic int
63859004Shmlog_trim(char *log)
63913244Sgraichen{
64059003Shm	FILE *f;
64159003Shm
64259003Shm	if ((f = fopen(log, "a")) == NULL)
64359003Shm		return (-1);
64459003Shm	fprintf(f, "%s %s newsyslog[%d]: logfile turned over\n",
64559003Shm	    daytime, hostname, (int) getpid());
64659003Shm	if (fclose(f) == EOF)
64759003Shm		err(1, "log_trim: fclose:");
64859003Shm	return (0);
64913244Sgraichen}
65013244Sgraichen
65159004Shm/* Fork of gzip to compress the old log file */
65259004Shmstatic void
65359004Shmcompress_log(char *log)
65413244Sgraichen{
65559003Shm	pid_t pid;
65659003Shm	char tmp[MAXPATHLEN + 1];
65759003Shm
65859003Shm	(void) sprintf(tmp, "%s.0", log);
65925443Sache	pid = fork();
66059003Shm	if (pid < 0)
66159003Shm		err(1, "fork");
66259003Shm	else if (!pid) {
66343071Swollman		(void) execl(_PATH_GZIP, _PATH_GZIP, "-f", tmp, 0);
66443071Swollman		err(1, _PATH_GZIP);
66559003Shm	}
66613244Sgraichen}
66713244Sgraichen
66813244Sgraichen/* Return size in kilobytes of a file */
66959004Shmstatic int
67059004Shmsizefile(char *file)
67113244Sgraichen{
67259003Shm	struct stat sb;
67313244Sgraichen
67459003Shm	if (stat(file, &sb) < 0)
67559003Shm		return (-1);
67659003Shm	return (kbytes(dbtob(sb.st_blocks)));
67713244Sgraichen}
67813244Sgraichen
67913244Sgraichen/* Return the age of old log file (file.0) */
68059004Shmstatic int
68159004Shmage_old_log(char *file)
68213244Sgraichen{
68359003Shm	struct stat sb;
68459003Shm	char tmp[MAXPATHLEN + sizeof(".0") + sizeof(COMPRESS_POSTFIX) + 1];
68513244Sgraichen
68659004Shm	if (archtodir) {
68759004Shm		char *p;
68859004Shm
68959004Shm		/* build name of archive directory into tmp */
69059004Shm		if (*archdirname == '/') {	/* absolute */
69159004Shm			strcpy(tmp, archdirname);
69259004Shm		} else {	/* relative */
69359004Shm			/* get directory part of logfile */
69459004Shm			strcpy(tmp, file);
69559004Shm			if ((p = rindex(tmp, '/')) == NULL)
69659004Shm				tmp[0] = '\0';
69759004Shm			else
69859004Shm				*(p + 1) = '\0';
69959004Shm			strcat(tmp, archdirname);
70059004Shm		}
70159004Shm
70259004Shm		strcat(tmp, "/");
70359004Shm
70459004Shm		/* get filename part of logfile */
70559004Shm		if ((p = rindex(file, '/')) == NULL)
70659004Shm			strcat(tmp, file);
70759004Shm		else
70859004Shm			strcat(tmp, p + 1);
70959004Shm	} else {
71059004Shm		(void) strcpy(tmp, file);
71159004Shm	}
71259004Shm
71359003Shm	if (stat(strcat(tmp, ".0"), &sb) < 0)
71459003Shm		if (stat(strcat(tmp, COMPRESS_POSTFIX), &sb) < 0)
71559003Shm			return (-1);
71659003Shm	return ((int) (timenow - sb.st_mtime + 1800) / 3600);
71713244Sgraichen}
71813244Sgraichen
71959004Shmstatic pid_t
72059004Shmget_pid(char *pid_file)
72125443Sache{
72225443Sache	FILE *f;
72359003Shm	char line[BUFSIZ];
72425443Sache	pid_t pid = 0;
72513244Sgraichen
72659003Shm	if ((f = fopen(pid_file, "r")) == NULL)
72725443Sache		warn("can't open %s pid file to restart a daemon",
72859003Shm		    pid_file);
72925443Sache	else {
73059003Shm		if (fgets(line, BUFSIZ, f)) {
73125443Sache			pid = atol(line);
73225443Sache			if (pid < MIN_PID || pid > MAX_PID) {
73359003Shm				warnx("preposterous process number: %d", (int) pid);
73425443Sache				pid = 0;
73525443Sache			}
73625443Sache		} else
73725443Sache			warn("can't read %s pid file to restart a daemon",
73859003Shm			    pid_file);
73959003Shm		(void) fclose(f);
74025443Sache	}
74125443Sache	return pid;
74225443Sache}
74325443Sache
74413244Sgraichen/* Skip Over Blanks */
74559003Shmchar *
74659004Shmsob(char *p)
74713244Sgraichen{
74859003Shm	while (p && *p && isspace(*p))
74959003Shm		p++;
75059003Shm	return (p);
75113244Sgraichen}
75213244Sgraichen
75313244Sgraichen/* Skip Over Non-Blanks */
75459003Shmchar *
75559004Shmson(char *p)
75613244Sgraichen{
75759003Shm	while (p && *p && !isspace(*p))
75859003Shm		p++;
75959003Shm	return (p);
76013244Sgraichen}
76143071Swollman
76243071Swollman/*
76359004Shm * Parse a limited subset of ISO 8601. The specific format is as follows:
76443071Swollman *
76559004Shm * [CC[YY[MM[DD]]]][THH[MM[SS]]]	(where `T' is the literal letter)
76643071Swollman *
76759004Shm * We don't accept a timezone specification; missing fields (including timezone)
76859004Shm * are defaulted to the current date but time zero.
76943071Swollman */
77043071Swollmanstatic time_t
77159004Shmparse8601(char *s)
77243071Swollman{
77359003Shm	char *t;
77459003Shm	struct tm tm, *tmp;
77559003Shm	u_long ul;
77643071Swollman
77743071Swollman	tmp = localtime(&timenow);
77843071Swollman	tm = *tmp;
77943071Swollman
78043071Swollman	tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
78143071Swollman
78243071Swollman	ul = strtoul(s, &t, 10);
78343071Swollman	if (*t != '\0' && *t != 'T')
78443071Swollman		return -1;
78543071Swollman
78643071Swollman	/*
78759003Shm	 * Now t points either to the end of the string (if no time was
78859003Shm	 * provided) or to the letter `T' which separates date and time in
78959003Shm	 * ISO 8601.  The pointer arithmetic is the same for either case.
79043071Swollman	 */
79143071Swollman	switch (t - s) {
79243071Swollman	case 8:
79343071Swollman		tm.tm_year = ((ul / 1000000) - 19) * 100;
79443071Swollman		ul = ul % 1000000;
79543071Swollman	case 6:
79643071Swollman		tm.tm_year = tm.tm_year - (tm.tm_year % 100);
79743071Swollman		tm.tm_year += ul / 10000;
79843071Swollman		ul = ul % 10000;
79943071Swollman	case 4:
80043071Swollman		tm.tm_mon = (ul / 100) - 1;
80143071Swollman		ul = ul % 100;
80243071Swollman	case 2:
80343071Swollman		tm.tm_mday = ul;
80443071Swollman	case 0:
80543071Swollman		break;
80643071Swollman	default:
80743071Swollman		return -1;
80843071Swollman	}
80943071Swollman
81043071Swollman	/* sanity check */
81143071Swollman	if (tm.tm_year < 70 || tm.tm_mon < 0 || tm.tm_mon > 12
81243071Swollman	    || tm.tm_mday < 1 || tm.tm_mday > 31)
81343071Swollman		return -1;
81443071Swollman
81543071Swollman	if (*t != '\0') {
81643071Swollman		s = ++t;
81743071Swollman		ul = strtoul(s, &t, 10);
81843071Swollman		if (*t != '\0' && !isspace(*t))
81943071Swollman			return -1;
82043071Swollman
82143071Swollman		switch (t - s) {
82243071Swollman		case 6:
82343071Swollman			tm.tm_sec = ul % 100;
82443071Swollman			ul /= 100;
82543071Swollman		case 4:
82643071Swollman			tm.tm_min = ul % 100;
82743071Swollman			ul /= 100;
82843071Swollman		case 2:
82943071Swollman			tm.tm_hour = ul;
83043071Swollman		case 0:
83143071Swollman			break;
83243071Swollman		default:
83343071Swollman			return -1;
83443071Swollman		}
83543071Swollman
83643071Swollman		/* sanity check */
83743071Swollman		if (tm.tm_sec < 0 || tm.tm_sec > 60 || tm.tm_min < 0
83843071Swollman		    || tm.tm_min > 59 || tm.tm_hour < 0 || tm.tm_hour > 23)
83943071Swollman			return -1;
84043071Swollman	}
84143071Swollman	return mktime(&tm);
84243071Swollman}
84359004Shm
84459004Shm/* physically move file */
84559004Shmstatic void
84659004Shmmovefile(char *from, char *to, int perm, int owner_uid, int group_gid)
84759004Shm{
84859004Shm	FILE *src, *dst;
84959004Shm	int c;
85059004Shm
85159004Shm	if ((src = fopen(from, "r")) == NULL)
85259004Shm		err(1, "can't fopen %s for reading", from);
85359004Shm	if ((dst = fopen(to, "w")) == NULL)
85459004Shm		err(1, "can't fopen %s for writing", to);
85559004Shm	if (fchown(fileno(dst), owner_uid, group_gid))
85659004Shm		err(1, "can't fchown %s", to);
85759004Shm	if (fchmod(fileno(dst), perm))
85859004Shm		err(1, "can't fchmod %s", to);
85959004Shm
86059004Shm	while ((c = getc(src)) != EOF) {
86159004Shm		if ((putc(c, dst)) == EOF)
86259004Shm			err(1, "error writing to %s", to);
86359004Shm	}
86459004Shm
86559004Shm	if (ferror(src))
86659004Shm		err(1, "error reading from %s", from);
86759004Shm	if ((fclose(src)) != 0)
86859004Shm		err(1, "can't fclose %s", to);
86959004Shm	if ((fclose(dst)) != 0)
87059004Shm		err(1, "can't fclose %s", from);
87159004Shm	if ((unlink(from)) != 0)
87259004Shm		err(1, "can't unlink %s", from);
87359004Shm}
87459004Shm
87559004Shm/* create one or more directory components of a path */
87659004Shmstatic void
87759004Shmcreatedir(char *dirpart)
87859004Shm{
87959004Shm	char *s, *d;
88059004Shm	char mkdirpath[MAXPATHLEN + 1];
88159004Shm	struct stat st;
88259004Shm
88359004Shm	s = dirpart;
88459004Shm	d = mkdirpath;
88559004Shm
88659004Shm	for (;;) {
88759004Shm		*d++ = *s++;
88859004Shm		if (*s == '/' || *s == '\0') {
88959004Shm			*d = '\0';
89059004Shm			if (lstat(mkdirpath, &st))
89159004Shm				mkdir(mkdirpath, 0755);
89259004Shm		}
89359004Shm		if (*s == '\0')
89459004Shm			break;
89559004Shm	}
89659004Shm}
89759004Shm
89859004Shm/*-
89959004Shm * Parse a cyclic time specification, the format is as follows:
90059004Shm *
90159004Shm *	[Dhh] or [Wd[Dhh]] or [Mdd[Dhh]]
90259004Shm *
90359004Shm * to rotate a logfile cyclic at
90459004Shm *
90559004Shm *	- every day (D) within a specific hour (hh)	(hh = 0...23)
90659004Shm *	- once a week (W) at a specific day (d)     OR	(d = 0..6, 0 = Sunday)
90759004Shm *	- once a month (M) at a specific day (d)	(d = 1..31,l|L)
90859004Shm *
90959004Shm * We don't accept a timezone specification; missing fields
91059004Shm * are defaulted to the current date but time zero.
91159004Shm */
91259004Shmstatic time_t
91359004ShmparseDWM(char *s)
91459004Shm{
91559004Shm	char *t;
91659004Shm	struct tm tm, *tmp;
91759004Shm	u_long ul;
91859004Shm	int nd;
91959004Shm	static int mtab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
92059004Shm	int WMseen = 0;
92159004Shm	int Dseen = 0;
92259004Shm
92359004Shm	tmp = localtime(&timenow);
92459004Shm	tm = *tmp;
92559004Shm
92659004Shm	/* set no. of days per month */
92759004Shm
92859004Shm	nd = mtab[tm.tm_mon];
92959004Shm
93059004Shm	if (tm.tm_mon == 1) {
93159004Shm		if (((tm.tm_year + 1900) % 4 == 0) &&
93259004Shm		    ((tm.tm_year + 1900) % 100 != 0) &&
93359004Shm		    ((tm.tm_year + 1900) % 400 == 0)) {
93459004Shm			nd++;	/* leap year, 29 days in february */
93559004Shm		}
93659004Shm	}
93759004Shm	tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
93859004Shm
93959004Shm	for (;;) {
94059004Shm		switch (*s) {
94159004Shm		case 'D':
94259004Shm			if (Dseen)
94359004Shm				return -1;
94459004Shm			Dseen++;
94559004Shm			s++;
94659004Shm			ul = strtoul(s, &t, 10);
94759004Shm			if (ul < 0 || ul > 23)
94859004Shm				return -1;
94959004Shm			tm.tm_hour = ul;
95059004Shm			break;
95159004Shm
95259004Shm		case 'W':
95359004Shm			if (WMseen)
95459004Shm				return -1;
95559004Shm			WMseen++;
95659004Shm			s++;
95759004Shm			ul = strtoul(s, &t, 10);
95859004Shm			if (ul < 0 || ul > 6)
95959004Shm				return -1;
96059004Shm			if (ul != tm.tm_wday) {
96159004Shm				int save;
96259004Shm
96359004Shm				if (ul < tm.tm_wday) {
96459004Shm					save = 6 - tm.tm_wday;
96559004Shm					save += (ul + 1);
96659004Shm				} else {
96759004Shm					save = ul - tm.tm_wday;
96859004Shm				}
96959004Shm
97059004Shm				tm.tm_mday += save;
97159004Shm
97259004Shm				if (tm.tm_mday > nd) {
97359004Shm					tm.tm_mon++;
97459004Shm					tm.tm_mday = tm.tm_mday - nd;
97559004Shm				}
97659004Shm			}
97759004Shm			break;
97859004Shm
97959004Shm		case 'M':
98059004Shm			if (WMseen)
98159004Shm				return -1;
98259004Shm			WMseen++;
98359004Shm			s++;
98459004Shm			if (tolower(*s) == 'l') {
98559004Shm				tm.tm_mday = nd;
98659004Shm				s++;
98759004Shm				t = s;
98859004Shm			} else {
98959004Shm				ul = strtoul(s, &t, 10);
99059004Shm				if (ul < 1 || ul > 31)
99159004Shm					return -1;
99259004Shm
99359004Shm				if (ul > nd)
99459004Shm					return -1;
99559004Shm				tm.tm_mday = ul;
99659004Shm			}
99759004Shm			break;
99859004Shm
99959004Shm		default:
100059004Shm			return (-1);
100159004Shm			break;
100259004Shm		}
100359004Shm
100459004Shm		if (*t == '\0' || isspace(*t))
100559004Shm			break;
100659004Shm		else
100759004Shm			s = t;
100859004Shm	}
100959004Shm	return mktime(&tm);
101059004Shm}
1011