newsyslog.c revision 111768
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 111768 2003-03-02 22:05:17Z gad $";
2959004Shm#endif	/* not lint */
3013244Sgraichen
3143071Swollman#define OSF
3213244Sgraichen#ifndef COMPRESS_POSTFIX
3343071Swollman#define COMPRESS_POSTFIX ".gz"
3413244Sgraichen#endif
3580638Sobrien#ifndef	BZCOMPRESS_POSTFIX
3680638Sobrien#define	BZCOMPRESS_POSTFIX ".bz2"
3780638Sobrien#endif
3813244Sgraichen
3996001Smaxim#include <sys/param.h>
4096001Smaxim#include <sys/stat.h>
4196001Smaxim#include <sys/wait.h>
4296001Smaxim
4330160Scharnier#include <ctype.h>
4430160Scharnier#include <err.h>
4595999Smaxim#include <errno.h>
4630160Scharnier#include <fcntl.h>
47106905Ssobomax#include <glob.h>
4830160Scharnier#include <grp.h>
4943071Swollman#include <paths.h>
5030160Scharnier#include <pwd.h>
5130160Scharnier#include <signal.h>
5213244Sgraichen#include <stdio.h>
5313244Sgraichen#include <stdlib.h>
5413244Sgraichen#include <string.h>
5543071Swollman#include <time.h>
5616240Salex#include <unistd.h>
5713244Sgraichen
5843071Swollman#include "pathnames.h"
5943071Swollman
6013244Sgraichen#define kbytes(size)  (((size) + 1023) >> 10)
6159004Shm
6213244Sgraichen#ifdef _IBMR2
6313244Sgraichen/* Calculates (db * DEV_BSIZE) */
6459003Shm#define dbtob(db)  ((unsigned)(db) << UBSHIFT)
6513244Sgraichen#endif
6613244Sgraichen
67111768Sgad/*
68111768Sgad * Bit-values for the 'flags' parsed from a config-file entry.
69111768Sgad */
70111768Sgad#define CE_COMPACT	0x0001	/* Compact the achived log files with gzip. */
71111768Sgad#define CE_BZCOMPACT	0x0002	/* Compact the achived log files with bzip2. */
72111768Sgad#define	CE_COMPACTWAIT	0x0004	/* wait until compressing one file finishes */
73111768Sgad				/*    before starting the next step. */
74111768Sgad#define CE_BINARY	0x0008	/* Logfile is in binary, do not add status */
75111768Sgad				/*    messages to logfile(s) when rotating. */
76111768Sgad#define	CE_NOSIGNAL	0x0010	/* There is no process to signal when */
77111768Sgad				/*    trimming this file. */
78111768Sgad#define	CE_TRIMAT	0x0020	/* trim file at a specific time. */
79111768Sgad#define	CE_GLOB		0x0040	/* name of the log is file name pattern. */
8043071Swollman
8113244Sgraichen#define NONE -1
8259003Shm
8313244Sgraichenstruct conf_entry {
8459003Shm	char *log;		/* Name of the log */
8559003Shm	char *pid_file;		/* PID file */
8659003Shm	int uid;		/* Owner of log */
8759003Shm	int gid;		/* Group of log */
8859003Shm	int numlogs;		/* Number of logs to keep */
8959003Shm	int size;		/* Size cutoff to trigger trimming the log */
9059003Shm	int hours;		/* Hours between log trimming */
9159003Shm	time_t trim_at;		/* Specific time to do trimming */
9259003Shm	int permissions;	/* File permissions on the log */
9380646Sobrien	int flags;		/* CE_COMPACT, CE_BZCOMPACT, CE_BINARY */
9459003Shm	int sig;		/* Signal to send */
95111388Sgad	int def_cfg;		/* Using the <default> rule for this file */
9659003Shm	struct conf_entry *next;/* Linked list pointer */
9713244Sgraichen};
9813244Sgraichen
99111388Sgad#define DEFAULT_MARKER "<default>"
100111388Sgad
10159004Shmint archtodir = 0;		/* Archive old logfiles to other directory */
10259003Shmint verbose = 0;		/* Print out what's going on */
10359003Shmint needroot = 1;		/* Root privs are necessary */
10459003Shmint noaction = 0;		/* Don't do anything, just show it */
105111768Sgadint nosignal;			/* Do not send any signals */
10659003Shmint force = 0;			/* Force the trim no matter what */
10759004Shmchar *archdirname;		/* Directory path to old logfiles archive */
10880640Sobrienconst char *conf = _PATH_CONF;	/* Configuration file to use */
10959003Shmtime_t timenow;
11059003Shm
11125443Sache#define MIN_PID         5
11259003Shm#define MAX_PID		99999	/* was lower, see /usr/include/sys/proc.h */
11371299Sjedgarchar hostname[MAXHOSTNAMELEN];	/* hostname */
114108164Strhodeschar daytime[16];		/* timenow in human readable form */
11513244Sgraichen
11660373Sdesstatic struct conf_entry *parse_file(char **files);
11716240Salexstatic char *sob(char *p);
11816240Salexstatic char *son(char *p);
11959003Shmstatic char *missing_field(char *p, char *errline);
12059003Shmstatic void do_entry(struct conf_entry * ent);
121111388Sgadstatic void free_entry(struct conf_entry *ent);
122111388Sgadstatic struct conf_entry *init_entry(const char *fname,
123111388Sgad		struct conf_entry *src_entry);
12459003Shmstatic void PRS(int argc, char **argv);
12580640Sobrienstatic void usage(void);
126111768Sgadstatic void dotrim(const struct conf_entry *trim_ent, char *log,
127111768Sgad		int numdays, int flags, int perm, int owner_uid,
128111768Sgad		int group_gid, int sig);
129111768Sgadstatic int log_trim(const char *log, const struct conf_entry *log_ent);
130107916Ssobomaxstatic void compress_log(char *log, int dowait);
131107916Ssobomaxstatic void bzcompress_log(char *log, int dowait);
13216240Salexstatic int sizefile(char *file);
13316240Salexstatic int age_old_log(char *file);
13480640Sobrienstatic pid_t get_pid(const char *pid_file);
13593659Scjcstatic time_t parse8601(char *s, char *errline);
13680646Sobrienstatic void movefile(char *from, char *to, int perm, int owner_uid,
13780646Sobrien		int group_gid);
13859004Shmstatic void createdir(char *dirpart);
13993659Scjcstatic time_t parseDWM(char *s, char *errline);
14013244Sgraichen
141111768Sgad/*
142111768Sgad * All the following are defined to work on an 'int', in the
143111768Sgad * range 0 to 255, plus EOF.  Define wrappers which can take
144111768Sgad * values of type 'char', either signed or unsigned.
145111768Sgad */
146111768Sgad#define isspacech(Anychar)    isspace(((int) Anychar) & 255)
147111768Sgad#define tolowerch(Anychar)    tolower(((int) Anychar) & 255)
148111768Sgad
14959004Shmint
15059004Shmmain(int argc, char **argv)
15113244Sgraichen{
15259003Shm	struct conf_entry *p, *q;
153111529Sgad	char *savglob;
154106905Ssobomax	glob_t pglob;
155106905Ssobomax	int i;
15625443Sache
15759003Shm	PRS(argc, argv);
15859003Shm	if (needroot && getuid() && geteuid())
15959003Shm		errx(1, "must have root privs");
16060373Sdes	p = q = parse_file(argv + optind);
16159003Shm
16259003Shm	while (p) {
163106905Ssobomax		if ((p->flags & CE_GLOB) == 0) {
164106905Ssobomax			do_entry(p);
165106905Ssobomax		} else {
166106905Ssobomax			if (glob(p->log, GLOB_NOCHECK, NULL, &pglob) != 0) {
167106905Ssobomax				warn("can't expand pattern: %s", p->log);
168106905Ssobomax			} else {
169111529Sgad				savglob = p->log;
170106905Ssobomax				for (i = 0; i < pglob.gl_matchc; i++) {
171106905Ssobomax					p->log = pglob.gl_pathv[i];
172106905Ssobomax					do_entry(p);
173106905Ssobomax				}
174106905Ssobomax				globfree(&pglob);
175111529Sgad				p->log = savglob;
176106905Ssobomax			}
177106905Ssobomax		}
17859003Shm		p = p->next;
179111388Sgad		free_entry(q);
18059003Shm		q = p;
18159003Shm	}
18295999Smaxim	while (wait(NULL) > 0 || errno == EINTR)
18395999Smaxim		;
18459003Shm	return (0);
18513244Sgraichen}
18613244Sgraichen
187111388Sgadstatic struct conf_entry *
188111388Sgadinit_entry(const char *fname, struct conf_entry *src_entry)
189111388Sgad{
190111388Sgad	struct conf_entry *tempwork;
191111388Sgad
192111388Sgad	if (verbose > 4)
193111388Sgad		printf("\t--> [creating entry for %s]\n", fname);
194111388Sgad
195111388Sgad	tempwork = malloc(sizeof(struct conf_entry));
196111388Sgad	if (tempwork == NULL)
197111388Sgad		err(1, "malloc of conf_entry for %s", fname);
198111388Sgad
199111388Sgad	tempwork->log = strdup(fname);
200111388Sgad	if (tempwork->log == NULL)
201111388Sgad		err(1, "strdup for %s", fname);
202111388Sgad
203111388Sgad	if (src_entry != NULL) {
204111388Sgad		tempwork->pid_file = NULL;
205111388Sgad		if (src_entry->pid_file)
206111388Sgad			tempwork->pid_file = strdup(src_entry->pid_file);
207111388Sgad		tempwork->uid = src_entry->uid;
208111388Sgad		tempwork->gid = src_entry->gid;
209111388Sgad		tempwork->numlogs = src_entry->numlogs;
210111388Sgad		tempwork->size = src_entry->size;
211111388Sgad		tempwork->hours = src_entry->hours;
212111388Sgad		tempwork->trim_at = src_entry->trim_at;
213111388Sgad		tempwork->permissions = src_entry->permissions;
214111388Sgad		tempwork->flags = src_entry->flags;
215111388Sgad		tempwork->sig = src_entry->sig;
216111388Sgad		tempwork->def_cfg = src_entry->def_cfg;
217111388Sgad	} else {
218111388Sgad		/* Initialize as a "do-nothing" entry */
219111388Sgad		tempwork->pid_file = NULL;
220111388Sgad		tempwork->uid = NONE;
221111388Sgad		tempwork->gid = NONE;
222111388Sgad		tempwork->numlogs = 1;
223111388Sgad		tempwork->size = -1;
224111388Sgad		tempwork->hours = -1;
225111388Sgad		tempwork->trim_at = (time_t)0;
226111388Sgad		tempwork->permissions = 0;
227111388Sgad		tempwork->flags = 0;
228111388Sgad		tempwork->sig = SIGHUP;
229111388Sgad		tempwork->def_cfg = 0;
230111388Sgad	}
231111388Sgad	tempwork->next = NULL;
232111388Sgad
233111388Sgad	return (tempwork);
234111388Sgad}
235111388Sgad
23659004Shmstatic void
237111388Sgadfree_entry(struct conf_entry *ent)
238111388Sgad{
239111388Sgad
240111388Sgad	if (ent == NULL)
241111388Sgad		return;
242111388Sgad
243111388Sgad	if (ent->log != NULL) {
244111388Sgad		if (verbose > 4)
245111388Sgad			printf("\t--> [freeing entry for %s]\n", ent->log);
246111388Sgad		free(ent->log);
247111388Sgad		ent->log = NULL;
248111388Sgad	}
249111388Sgad
250111388Sgad	if (ent->pid_file != NULL) {
251111388Sgad		free(ent->pid_file);
252111388Sgad		ent->pid_file = NULL;
253111388Sgad	}
254111388Sgad
255111388Sgad	free(ent);
256111388Sgad}
257111388Sgad
258111388Sgadstatic void
25959004Shmdo_entry(struct conf_entry * ent)
26013244Sgraichen{
26159003Shm	int size, modtime;
26259003Shm
26359003Shm	if (verbose) {
26459003Shm		if (ent->flags & CE_COMPACT)
26559003Shm			printf("%s <%dZ>: ", ent->log, ent->numlogs);
26680638Sobrien		else if (ent->flags & CE_BZCOMPACT)
26780638Sobrien			printf("%s <%dJ>: ", ent->log, ent->numlogs);
26859003Shm		else
26959003Shm			printf("%s <%d>: ", ent->log, ent->numlogs);
27059003Shm	}
27159003Shm	size = sizefile(ent->log);
27259003Shm	modtime = age_old_log(ent->log);
27359003Shm	if (size < 0) {
27459003Shm		if (verbose)
27559003Shm			printf("does not exist.\n");
27659003Shm	} else {
27790240Sroam		if (ent->flags & CE_TRIMAT && !force) {
27843071Swollman			if (timenow < ent->trim_at
27959003Shm			    || difftime(timenow, ent->trim_at) >= 60 * 60) {
28043071Swollman				if (verbose)
28143071Swollman					printf("--> will trim at %s",
28259003Shm					    ctime(&ent->trim_at));
28343071Swollman				return;
28443071Swollman			} else if (verbose && ent->hours <= 0) {
28543071Swollman				printf("--> time is up\n");
28643071Swollman			}
28743071Swollman		}
28859003Shm		if (verbose && (ent->size > 0))
28959003Shm			printf("size (Kb): %d [%d] ", size, ent->size);
29059003Shm		if (verbose && (ent->hours > 0))
29159003Shm			printf(" age (hr): %d [%d] ", modtime, ent->hours);
29259003Shm		if (force || ((ent->size > 0) && (size >= ent->size)) ||
29343071Swollman		    (ent->hours <= 0 && (ent->flags & CE_TRIMAT)) ||
29459003Shm		    ((ent->hours > 0) && ((modtime >= ent->hours)
29559003Shm			    || (modtime < 0)))) {
29659003Shm			if (verbose)
29759003Shm				printf("--> trimming log....\n");
29859003Shm			if (noaction && !verbose) {
29959003Shm				if (ent->flags & CE_COMPACT)
30059003Shm					printf("%s <%dZ>: trimming\n",
30159003Shm					    ent->log, ent->numlogs);
30280638Sobrien				else if (ent->flags & CE_BZCOMPACT)
30380638Sobrien					printf("%s <%dJ>: trimming\n",
30480638Sobrien					    ent->log, ent->numlogs);
30559003Shm				else
30659003Shm					printf("%s <%d>: trimming\n",
30759003Shm					    ent->log, ent->numlogs);
30859003Shm			}
309111768Sgad			dotrim(ent, ent->log, ent->numlogs, ent->flags,
310111768Sgad			    ent->permissions, ent->uid, ent->gid, ent->sig);
31159003Shm		} else {
31259003Shm			if (verbose)
31359003Shm				printf("--> skipping\n");
31459003Shm		}
31559003Shm	}
31613244Sgraichen}
31713244Sgraichen
31859004Shmstatic void
31959004ShmPRS(int argc, char **argv)
32013244Sgraichen{
32159003Shm	int c;
32259003Shm	char *p;
32313244Sgraichen
32459003Shm	timenow = time((time_t *) 0);
325108164Strhodes	(void)strncpy(daytime, ctime(&timenow) + 4, 15);
32659003Shm	daytime[15] = '\0';
32713244Sgraichen
32859003Shm	/* Let's get our hostname */
32959003Shm	(void) gethostname(hostname, sizeof(hostname));
33013244Sgraichen
33113244Sgraichen	/* Truncate domain */
33216240Salex	if ((p = strchr(hostname, '.'))) {
33313244Sgraichen		*p = '\0';
33413244Sgraichen	}
335111768Sgad
336111768Sgad	/* Parse command line options. */
337111768Sgad	while ((c = getopt(argc, argv, "a:f:nrsvF")) != -1)
33859003Shm		switch (c) {
33959004Shm		case 'a':
34059004Shm			archtodir++;
34159004Shm			archdirname = optarg;
34259004Shm			break;
343111768Sgad		case 'f':
344111768Sgad			conf = optarg;
345111768Sgad			break;
346111768Sgad		case 'n':
347111768Sgad			noaction++;
348111768Sgad			break;
34959003Shm		case 'r':
35059003Shm			needroot = 0;
35159003Shm			break;
352111768Sgad		case 's':
353111768Sgad			nosignal = 1;
354111768Sgad			break;
35559003Shm		case 'v':
35659003Shm			verbose++;
35759003Shm			break;
35834584Spst		case 'F':
35934584Spst			force++;
36034584Spst			break;
36159003Shm		default:
36259003Shm			usage();
363111768Sgad			/* NOTREACHED */
36459003Shm		}
36543071Swollman}
36613244Sgraichen
36759004Shmstatic void
36859004Shmusage(void)
36913244Sgraichen{
37080646Sobrien
37180646Sobrien	fprintf(stderr,
372111768Sgad	    "usage: newsyslog [-Fnrsv] [-f config-file] [-a directory] [ filename ... ]\n");
37359003Shm	exit(1);
37413244Sgraichen}
37513244Sgraichen
37659004Shm/*
37759004Shm * Parse a configuration file and return a linked list of all the logs to
37859004Shm * process
37913244Sgraichen */
38059003Shmstatic struct conf_entry *
38160373Sdesparse_file(char **files)
38213244Sgraichen{
38359003Shm	FILE *f;
38459003Shm	char line[BUFSIZ], *parse, *q;
385107737Ssobomax	char *cp, *errline, *group;
386111388Sgad	char **given;
387111388Sgad	struct conf_entry *defconf, *first, *working, *worklist;
38859003Shm	struct passwd *pass;
38959003Shm	struct group *grp;
39025518Sbrian	int eol;
39113244Sgraichen
392111388Sgad	defconf = first = working = worklist = NULL;
39380646Sobrien
39459003Shm	if (strcmp(conf, "-"))
39559003Shm		f = fopen(conf, "r");
39659003Shm	else
39759003Shm		f = stdin;
39859003Shm	if (!f)
39959003Shm		err(1, "%s", conf);
40059003Shm	while (fgets(line, BUFSIZ, f)) {
401107737Ssobomax		if ((line[0] == '\n') || (line[0] == '#') ||
402107737Ssobomax		    (strlen(line) == 0))
40359003Shm			continue;
40459003Shm		errline = strdup(line);
405107737Ssobomax		for (cp = line + 1; *cp != '\0'; cp++) {
406107737Ssobomax			if (*cp != '#')
407107737Ssobomax				continue;
408107737Ssobomax			if (*(cp - 1) == '\\') {
409107737Ssobomax				strcpy(cp - 1, cp);
410107737Ssobomax				cp--;
411107737Ssobomax				continue;
412107737Ssobomax			}
413107737Ssobomax			*cp = '\0';
414107737Ssobomax			break;
415107737Ssobomax		}
41660373Sdes
41760373Sdes		q = parse = missing_field(sob(line), errline);
41860373Sdes		parse = son(line);
41960373Sdes		if (!*parse)
42080646Sobrien			errx(1, "malformed line (missing fields):\n%s",
42180646Sobrien			    errline);
42260373Sdes		*parse = '\0';
42360373Sdes
424111388Sgad		/*
425111388Sgad		 * If newsyslog was run with a list of specific filenames,
426111388Sgad		 * then this line of the config file should be skipped if
427111388Sgad		 * it is NOT one of those given files (except that we do
428111388Sgad		 * want any line that defines the <default> action).
429111388Sgad		 *
430111388Sgad		 * XXX - note that CE_GLOB processing is *NOT* done when
431111388Sgad		 *       trying to match a filename given on the command!
432111388Sgad		 */
43360373Sdes		if (*files) {
434111388Sgad			if (strcasecmp(DEFAULT_MARKER, q) != 0) {
435111388Sgad				for (given = files; *given; ++given) {
436111388Sgad					if (strcmp(*given, q) == 0)
437111388Sgad						break;
438111388Sgad				}
439111388Sgad				if (!*given)
440111388Sgad					continue;
441111388Sgad			}
442111388Sgad			if (verbose > 2)
443111388Sgad				printf("\t+ Matched entry %s\n", q);
444111388Sgad		} else {
445111388Sgad			/*
446111388Sgad			 * If no files were specified on the command line,
447111388Sgad			 * then we can skip any line which defines the
448111388Sgad			 * default action.
449111388Sgad			 */
450111388Sgad			if (strcasecmp(DEFAULT_MARKER, q) == 0) {
451111388Sgad				if (verbose > 2)
452111388Sgad					printf("\t+ Ignoring entry for %s\n",
453111388Sgad					    q);
45460373Sdes				continue;
455111388Sgad			}
45660373Sdes		}
45760373Sdes
458111388Sgad		working = init_entry(q, NULL);
459111388Sgad		if (strcasecmp(DEFAULT_MARKER, q) == 0) {
460111388Sgad			if (defconf != NULL) {
461111388Sgad				warnx("Ignoring duplicate entry for %s!", q);
462111388Sgad				free_entry(working);
463111388Sgad				continue;
464111388Sgad			}
465111388Sgad			defconf = working;
46659003Shm		} else {
467111388Sgad			if (!first)
468111388Sgad				first = working;
469111388Sgad			else
470111388Sgad				worklist->next = working;
471111388Sgad			worklist = working;
47259003Shm		}
47313244Sgraichen
47459003Shm		q = parse = missing_field(sob(++parse), errline);
47559003Shm		parse = son(parse);
47625518Sbrian		if (!*parse)
47780646Sobrien			errx(1, "malformed line (missing fields):\n%s",
47880646Sobrien			    errline);
47959003Shm		*parse = '\0';
48059003Shm		if ((group = strchr(q, ':')) != NULL ||
48159003Shm		    (group = strrchr(q, '.')) != NULL) {
48259003Shm			*group++ = '\0';
48359003Shm			if (*q) {
48459003Shm				if (!(isnumber(*q))) {
48559003Shm					if ((pass = getpwnam(q)) == NULL)
48659003Shm						errx(1,
48780646Sobrien				     "error in config file; unknown user:\n%s",
48859003Shm						    errline);
48959003Shm					working->uid = pass->pw_uid;
49059003Shm				} else
49159003Shm					working->uid = atoi(q);
49259003Shm			} else
49359003Shm				working->uid = NONE;
49413244Sgraichen
49559003Shm			q = group;
49659003Shm			if (*q) {
49759003Shm				if (!(isnumber(*q))) {
49859003Shm					if ((grp = getgrnam(q)) == NULL)
49959003Shm						errx(1,
50080646Sobrien				    "error in config file; unknown group:\n%s",
50159003Shm						    errline);
50259003Shm					working->gid = grp->gr_gid;
50359003Shm				} else
50459003Shm					working->gid = atoi(q);
50559003Shm			} else
50659003Shm				working->gid = NONE;
50713244Sgraichen
50859003Shm			q = parse = missing_field(sob(++parse), errline);
50959003Shm			parse = son(parse);
51059003Shm			if (!*parse)
51180646Sobrien				errx(1, "malformed line (missing fields):\n%s",
51280646Sobrien				    errline);
51359003Shm			*parse = '\0';
51459003Shm		} else
51559003Shm			working->uid = working->gid = NONE;
51659003Shm
51759003Shm		if (!sscanf(q, "%o", &working->permissions))
51859003Shm			errx(1, "error in config file; bad permissions:\n%s",
51959003Shm			    errline);
52059003Shm
52159003Shm		q = parse = missing_field(sob(++parse), errline);
52259003Shm		parse = son(parse);
52325518Sbrian		if (!*parse)
52480646Sobrien			errx(1, "malformed line (missing fields):\n%s",
52580646Sobrien			    errline);
52659003Shm		*parse = '\0';
527111400Sgad		if (!sscanf(q, "%d", &working->numlogs) || working->numlogs < 0)
528111400Sgad			errx(1, "error in config file; bad value for count of logs to save:\n%s",
52959003Shm			    errline);
53013244Sgraichen
53159003Shm		q = parse = missing_field(sob(++parse), errline);
53259003Shm		parse = son(parse);
53325518Sbrian		if (!*parse)
53480646Sobrien			errx(1, "malformed line (missing fields):\n%s",
53580646Sobrien			    errline);
53659003Shm		*parse = '\0';
53759003Shm		if (isdigit(*q))
53859003Shm			working->size = atoi(q);
53959003Shm		else
54059003Shm			working->size = -1;
54159003Shm
54259003Shm		working->flags = 0;
54359003Shm		q = parse = missing_field(sob(++parse), errline);
54459003Shm		parse = son(parse);
54525518Sbrian		eol = !*parse;
54659003Shm		*parse = '\0';
54743071Swollman		{
54859003Shm			char *ep;
54959003Shm			u_long ul;
55013244Sgraichen
55143071Swollman			ul = strtoul(q, &ep, 10);
55243071Swollman			if (ep == q)
55343071Swollman				working->hours = 0;
55443071Swollman			else if (*ep == '*')
55543071Swollman				working->hours = -1;
55643071Swollman			else if (ul > INT_MAX)
55743071Swollman				errx(1, "interval is too large:\n%s", errline);
55843071Swollman			else
55943071Swollman				working->hours = ul;
56043071Swollman
56180646Sobrien			if (*ep != '\0' && *ep != '@' && *ep != '*' &&
56280646Sobrien			    *ep != '$')
56343071Swollman				errx(1, "malformed interval/at:\n%s", errline);
56443071Swollman			if (*ep == '@') {
56593659Scjc				if ((working->trim_at = parse8601(ep + 1, errline))
56659003Shm				    == (time_t) - 1)
56743071Swollman					errx(1, "malformed at:\n%s", errline);
56843071Swollman				working->flags |= CE_TRIMAT;
56959004Shm			} else if (*ep == '$') {
57093659Scjc				if ((working->trim_at = parseDWM(ep + 1, errline))
57159004Shm				    == (time_t) - 1)
57259004Shm					errx(1, "malformed at:\n%s", errline);
57359004Shm				working->flags |= CE_TRIMAT;
57443071Swollman			}
57543071Swollman		}
57643071Swollman
57725518Sbrian		if (eol)
57859003Shm			q = NULL;
57925518Sbrian		else {
58059003Shm			q = parse = sob(++parse);	/* Optional field */
58159003Shm			parse = son(parse);
58259003Shm			if (!*parse)
58359003Shm				eol = 1;
58459003Shm			*parse = '\0';
58525518Sbrian		}
58625443Sache
587111768Sgad		for (; q && *q && !isspacech(*q); q++) {
588111768Sgad			switch (tolowerch(*q)) {
589111768Sgad			case 'b':
59059003Shm				working->flags |= CE_BINARY;
591111768Sgad				break;
592111768Sgad			case 'c':
593111768Sgad				/*
594111768Sgad				 * netbsd uses 'c' for "create".  We will
595111768Sgad				 * temporarily accept it for 'g', because
596111768Sgad				 * earlier freebsd versions had a typo
597111768Sgad				 * of ('G' || 'c')...
598111768Sgad				 */
599111768Sgad				warnx("Assuming 'g' for 'c' in flags for line:\n%s",
600111768Sgad				    errline);
601111768Sgad				/* FALLTHROUGH */
602111768Sgad			case 'g':
603106905Ssobomax				working->flags |= CE_GLOB;
604111768Sgad				break;
605111768Sgad			case 'j':
606111768Sgad				working->flags |= CE_BZCOMPACT;
607111768Sgad				break;
608111768Sgad			case 'n':
609111768Sgad				working->flags |= CE_NOSIGNAL;
610111768Sgad				break;
611111768Sgad			case 'w':
612107916Ssobomax				working->flags |= CE_COMPACTWAIT;
613111768Sgad				break;
614111768Sgad			case 'z':
615111768Sgad				working->flags |= CE_COMPACT;
616111768Sgad				break;
617111768Sgad			case '-':
618111768Sgad				break;
619111768Sgad			default:
62080646Sobrien				errx(1, "illegal flag in config file -- %c",
62180646Sobrien				    *q);
622111768Sgad			}
62359003Shm		}
62459003Shm
62525518Sbrian		if (eol)
62659003Shm			q = NULL;
62725518Sbrian		else {
62859003Shm			q = parse = sob(++parse);	/* Optional field */
62959003Shm			parse = son(parse);
63059003Shm			if (!*parse)
63159003Shm				eol = 1;
63259003Shm			*parse = '\0';
63325518Sbrian		}
63425443Sache
63525443Sache		working->pid_file = NULL;
63625443Sache		if (q && *q) {
63725443Sache			if (*q == '/')
63825443Sache				working->pid_file = strdup(q);
63936817Sache			else if (isdigit(*q))
64036817Sache				goto got_sig;
64159003Shm			else
64280646Sobrien				errx(1,
64380646Sobrien			"illegal pid file or signal number in config file:\n%s",
64480646Sobrien				    errline);
64525443Sache		}
64636817Sache		if (eol)
64759003Shm			q = NULL;
64836817Sache		else {
64959003Shm			q = parse = sob(++parse);	/* Optional field */
65059003Shm			*(parse = son(parse)) = '\0';
65136817Sache		}
65236817Sache
65336817Sache		working->sig = SIGHUP;
65436817Sache		if (q && *q) {
65536817Sache			if (isdigit(*q)) {
65659003Shm		got_sig:
65736817Sache				working->sig = atoi(q);
65836817Sache			} else {
65959003Shm		err_sig:
66080646Sobrien				errx(1,
66180646Sobrien				    "illegal signal number in config file:\n%s",
66280646Sobrien				    errline);
66336817Sache			}
66436817Sache			if (working->sig < 1 || working->sig >= NSIG)
66536817Sache				goto err_sig;
66636817Sache		}
667111768Sgad
668111768Sgad		/*
669111768Sgad		 * Finish figuring out what pid-file to use (if any) in
670111768Sgad		 * later processing if this logfile needs to be rotated.
671111768Sgad		 */
672111768Sgad		if ((working->flags & CE_NOSIGNAL) == CE_NOSIGNAL) {
673111768Sgad			/*
674111768Sgad			 * This config-entry specified 'n' for nosignal,
675111768Sgad			 * see if it also specified an explicit pid_file.
676111768Sgad			 * This would be a pretty pointless combination.
677111768Sgad			 */
678111768Sgad			if (working->pid_file != NULL) {
679111768Sgad				warnx("Ignoring '%s' because flag 'n' was specified in line:\n%s",
680111768Sgad				    working->pid_file, errline);
681111768Sgad				free(working->pid_file);
682111768Sgad				working->pid_file = NULL;
683111768Sgad			}
684111768Sgad		} else if (nosignal) {
685111768Sgad			/*
686111768Sgad			 * While this entry might usually signal some
687111768Sgad			 * process via the pid-file, newsyslog was run
688111768Sgad			 * with '-s', so quietly ignore the pid-file.
689111768Sgad			 */
690111768Sgad			if (working->pid_file != NULL) {
691111768Sgad				free(working->pid_file);
692111768Sgad				working->pid_file = NULL;
693111768Sgad			}
694111768Sgad		} else if (working->pid_file == NULL) {
695111768Sgad			/*
696111768Sgad			 * This entry did not specify the 'n' flag, which
697111768Sgad			 * means it should signal syslogd unless it had
698111768Sgad			 * specified some other pid-file.  But we only
699111768Sgad			 * try to notify syslog if we are root
700111768Sgad			 */
701111768Sgad			if (needroot)
702111768Sgad				working->pid_file = strdup(_PATH_SYSLOGPID);
703111768Sgad		}
704111768Sgad
70559003Shm		free(errline);
706111768Sgad		errline = NULL;
70759003Shm	}
70859003Shm	(void) fclose(f);
709111388Sgad
710111388Sgad	/*
711111388Sgad	 * The entire config file has been processed.  If there were
712111388Sgad	 * no specific files given on the run command, then the work
713111388Sgad	 * of this routine is done.
714111388Sgad	 */
715111388Sgad	if (*files == NULL)
716111388Sgad		return (first);
717111388Sgad
718111388Sgad	/*
719111388Sgad	 * If the program was given a specific list of files to process,
720111388Sgad	 * it may be that some of those files were not listed in the
721111388Sgad	 * config file.  Those unlisted files should get the default
722111388Sgad	 * rotation action.  First, create the default-rotation action
723111388Sgad	 * if none was found in the config file.
724111388Sgad	 */
725111388Sgad	if (defconf == NULL) {
726111388Sgad		working = init_entry(DEFAULT_MARKER, NULL);
727111388Sgad		working->numlogs = 3;
728111388Sgad		working->size = 50;
729111388Sgad		working->permissions = S_IRUSR|S_IWUSR;
730111388Sgad		defconf = working;
731111388Sgad	}
732111388Sgad
733111388Sgad	for (given = files; *given; ++given) {
734111388Sgad		for (working = first; working; working = working->next) {
735111388Sgad			if (strcmp(*given, working->log) == 0)
736111388Sgad				break;
737111388Sgad		}
738111388Sgad		if (working != NULL)
739111388Sgad			continue;
740111388Sgad		if (verbose > 2)
741111388Sgad			printf("\t+ No entry for %s  (will use %s)\n",
742111388Sgad			    *given, DEFAULT_MARKER);
743111388Sgad		/*
744111388Sgad		 * This given file was not found in the config file.
745111388Sgad		 * Add another item on to our work list, based on the
746111388Sgad		 * default entry.
747111388Sgad		 */
748111388Sgad		working = init_entry(*given, defconf);
749111388Sgad		if (!first)
750111388Sgad			first = working;
751111388Sgad		else
752111388Sgad			worklist->next = working;
753111388Sgad		/* This is a file that was *not* found in config file */
754111388Sgad		working->def_cfg = 1;
755111388Sgad		worklist = working;
756111388Sgad	}
757111388Sgad
758111388Sgad	free_entry(defconf);
75959003Shm	return (first);
76013244Sgraichen}
76113244Sgraichen
76259003Shmstatic char *
76359004Shmmissing_field(char *p, char *errline)
76413244Sgraichen{
76580646Sobrien
76659003Shm	if (!p || !*p)
76759003Shm		errx(1, "missing field in config file:\n%s", errline);
76859003Shm	return (p);
76913244Sgraichen}
77013244Sgraichen
77159004Shmstatic void
772111768Sgaddotrim(const struct conf_entry *trim_ent, char *log, int numdays, int flags,
773111768Sgad    int perm, int owner_uid, int group_gid, int sig)
77413244Sgraichen{
77571299Sjedgar	char dirpart[MAXPATHLEN], namepart[MAXPATHLEN];
77671299Sjedgar	char file1[MAXPATHLEN], file2[MAXPATHLEN];
77771299Sjedgar	char zfile1[MAXPATHLEN], zfile2[MAXPATHLEN];
77880638Sobrien	char jfile1[MAXPATHLEN];
77994352Ssheldonh	char tfile[MAXPATHLEN];
78059003Shm	int notified, need_notification, fd, _numdays;
78159003Shm	struct stat st;
78259003Shm	pid_t pid;
78313244Sgraichen
78413244Sgraichen#ifdef _IBMR2
78559004Shm	/*
78659004Shm	 * AIX 3.1 has a broken fchown- if the owner_uid is -1, it will
78759004Shm	 * actually change it to be owned by uid -1, instead of leaving it
78859004Shm	 * as is, as it is supposed to.
78959004Shm	 */
79059003Shm	if (owner_uid == -1)
79159003Shm		owner_uid = geteuid();
79213244Sgraichen#endif
79313244Sgraichen
79459004Shm	if (archtodir) {
79559004Shm		char *p;
79613244Sgraichen
79759004Shm		/* build complete name of archive directory into dirpart */
79859004Shm		if (*archdirname == '/') {	/* absolute */
79971299Sjedgar			strlcpy(dirpart, archdirname, sizeof(dirpart));
80059004Shm		} else {	/* relative */
80159004Shm			/* get directory part of logfile */
80271299Sjedgar			strlcpy(dirpart, log, sizeof(dirpart));
80359004Shm			if ((p = rindex(dirpart, '/')) == NULL)
80459004Shm				dirpart[0] = '\0';
80559004Shm			else
80659004Shm				*(p + 1) = '\0';
80771299Sjedgar			strlcat(dirpart, archdirname, sizeof(dirpart));
80859004Shm		}
80959004Shm
81059004Shm		/* check if archive directory exists, if not, create it */
81159004Shm		if (lstat(dirpart, &st))
81259004Shm			createdir(dirpart);
81359004Shm
81459004Shm		/* get filename part of logfile */
81559004Shm		if ((p = rindex(log, '/')) == NULL)
81671299Sjedgar			strlcpy(namepart, log, sizeof(namepart));
81759004Shm		else
81871299Sjedgar			strlcpy(namepart, p + 1, sizeof(namepart));
81959004Shm
82059004Shm		/* name of oldest log */
82180646Sobrien		(void) snprintf(file1, sizeof(file1), "%s/%s.%d", dirpart,
82280646Sobrien		    namepart, numdays);
82371299Sjedgar		(void) snprintf(zfile1, sizeof(zfile1), "%s%s", file1,
82471299Sjedgar		    COMPRESS_POSTFIX);
82580638Sobrien		snprintf(jfile1, sizeof(jfile1), "%s%s", file1,
82680638Sobrien		    BZCOMPRESS_POSTFIX);
82759004Shm	} else {
82859004Shm		/* name of oldest log */
82971299Sjedgar		(void) snprintf(file1, sizeof(file1), "%s.%d", log, numdays);
83071299Sjedgar		(void) snprintf(zfile1, sizeof(zfile1), "%s%s", file1,
83171299Sjedgar		    COMPRESS_POSTFIX);
83280638Sobrien		snprintf(jfile1, sizeof(jfile1), "%s%s", file1,
83380638Sobrien		    BZCOMPRESS_POSTFIX);
83459004Shm	}
83559004Shm
83659003Shm	if (noaction) {
83759003Shm		printf("rm -f %s\n", file1);
83859003Shm		printf("rm -f %s\n", zfile1);
83980638Sobrien		printf("rm -f %s\n", jfile1);
84059003Shm	} else {
84159003Shm		(void) unlink(file1);
84259003Shm		(void) unlink(zfile1);
84380638Sobrien		(void) unlink(jfile1);
84459003Shm	}
84513244Sgraichen
84659003Shm	/* Move down log files */
84718188Sjkh	_numdays = numdays;	/* preserve */
84859003Shm	while (numdays--) {
84959004Shm
85071299Sjedgar		(void) strlcpy(file2, file1, sizeof(file2));
85159004Shm
85259004Shm		if (archtodir)
85380646Sobrien			(void) snprintf(file1, sizeof(file1), "%s/%s.%d",
85480646Sobrien			    dirpart, namepart, numdays);
85559004Shm		else
85680646Sobrien			(void) snprintf(file1, sizeof(file1), "%s.%d", log,
85780646Sobrien			    numdays);
85859004Shm
85971299Sjedgar		(void) strlcpy(zfile1, file1, sizeof(zfile1));
86071299Sjedgar		(void) strlcpy(zfile2, file2, sizeof(zfile2));
86159003Shm		if (lstat(file1, &st)) {
86280646Sobrien			(void) strlcat(zfile1, COMPRESS_POSTFIX,
86380646Sobrien			    sizeof(zfile1));
86480646Sobrien			(void) strlcat(zfile2, COMPRESS_POSTFIX,
86580646Sobrien			    sizeof(zfile2));
86680638Sobrien			if (lstat(zfile1, &st)) {
86780638Sobrien				strlcpy(zfile1, file1, sizeof(zfile1));
86880638Sobrien				strlcpy(zfile2, file2, sizeof(zfile2));
86980638Sobrien				strlcat(zfile1, BZCOMPRESS_POSTFIX,
87080638Sobrien				    sizeof(zfile1));
87180638Sobrien				strlcat(zfile2, BZCOMPRESS_POSTFIX,
87280638Sobrien				    sizeof(zfile2));
87380638Sobrien				if (lstat(zfile1, &st))
87480638Sobrien					continue;
87580638Sobrien			}
87659003Shm		}
87759003Shm		if (noaction) {
87859003Shm			printf("mv %s %s\n", zfile1, zfile2);
87959003Shm			printf("chmod %o %s\n", perm, zfile2);
88080684Sobrien			printf("chown %d:%d %s\n",
88159003Shm			    owner_uid, group_gid, zfile2);
88259003Shm		} else {
88359003Shm			(void) rename(zfile1, zfile2);
88459003Shm			(void) chmod(zfile2, perm);
88559003Shm			(void) chown(zfile2, owner_uid, group_gid);
88659003Shm		}
88759003Shm	}
888111388Sgad	if (!noaction && !(flags & CE_BINARY)) {
889111388Sgad		/* Report the trimming to the old log */
890111768Sgad		(void) log_trim(log, trim_ent);
891111388Sgad	}
89213244Sgraichen
89318188Sjkh	if (!_numdays) {
89418075Sjkh		if (noaction)
89559003Shm			printf("rm %s\n", log);
89618075Sjkh		else
89759003Shm			(void) unlink(log);
89859003Shm	} else {
89918075Sjkh		if (noaction)
90059003Shm			printf("mv %s to %s\n", log, file1);
90159004Shm		else {
90259004Shm			if (archtodir)
90380646Sobrien				movefile(log, file1, perm, owner_uid,
90480646Sobrien				    group_gid);
90559004Shm			else
90659004Shm				(void) rename(log, file1);
90759004Shm		}
90818075Sjkh	}
90918075Sjkh
91059003Shm	if (noaction)
91159003Shm		printf("Start new log...");
91259003Shm	else {
91394352Ssheldonh		strlcpy(tfile, log, sizeof(tfile));
91494352Ssheldonh		strlcat(tfile, ".XXXXXX", sizeof(tfile));
91594352Ssheldonh		mkstemp(tfile);
91694352Ssheldonh		fd = creat(tfile, perm);
91759003Shm		if (fd < 0)
91859003Shm			err(1, "can't start new log");
91959003Shm		if (fchown(fd, owner_uid, group_gid))
92059003Shm			err(1, "can't chmod new log file");
92159003Shm		(void) close(fd);
922111388Sgad		if (!(flags & CE_BINARY)) {
923111388Sgad			/* Add status message to new log file */
924111768Sgad			if (log_trim(tfile, trim_ent))
92559003Shm				err(1, "can't add status message to log");
926111388Sgad		}
92759003Shm	}
92859003Shm	if (noaction)
92959003Shm		printf("chmod %o %s...\n", perm, log);
93094352Ssheldonh	else {
93194352Ssheldonh		(void) chmod(tfile, perm);
93294352Ssheldonh		if (rename(tfile, log) < 0) {
93394352Ssheldonh			err(1, "can't start new log");
93494352Ssheldonh			(void) unlink(tfile);
93594352Ssheldonh		}
93694352Ssheldonh	}
93725443Sache
93825443Sache	pid = 0;
93925443Sache	need_notification = notified = 0;
940111768Sgad	if (trim_ent->pid_file != NULL) {
94125443Sache		need_notification = 1;
942111768Sgad		pid = get_pid(trim_ent->pid_file);
94325443Sache	}
94425443Sache	if (pid) {
94525443Sache		if (noaction) {
94625443Sache			notified = 1;
94759003Shm			printf("kill -%d %d\n", sig, (int) pid);
94859003Shm		} else if (kill(pid, sig))
94959003Shm			warn("can't notify daemon, pid %d", (int) pid);
95025443Sache		else {
95125443Sache			notified = 1;
95225443Sache			if (verbose)
95359003Shm				printf("daemon pid %d notified\n", (int) pid);
95425443Sache		}
95525443Sache	}
95680638Sobrien	if ((flags & CE_COMPACT) || (flags & CE_BZCOMPACT)) {
95725443Sache		if (need_notification && !notified)
95880646Sobrien			warnx(
95980646Sobrien			    "log %s not compressed because daemon not notified",
96080646Sobrien			    log);
96125443Sache		else if (noaction)
96259003Shm			printf("Compress %s.0\n", log);
96325443Sache		else {
96425443Sache			if (notified) {
96525443Sache				if (verbose)
96625443Sache					printf("small pause to allow daemon to close log\n");
96731460Sache				sleep(10);
96825443Sache			}
96959004Shm			if (archtodir) {
97080646Sobrien				(void) snprintf(file1, sizeof(file1), "%s/%s",
97180646Sobrien				    dirpart, namepart);
97280638Sobrien				if (flags & CE_COMPACT)
973107916Ssobomax					compress_log(file1,
974107916Ssobomax					    flags & CE_COMPACTWAIT);
97580638Sobrien				else if (flags & CE_BZCOMPACT)
976107916Ssobomax					bzcompress_log(file1,
977107916Ssobomax					    flags & CE_COMPACTWAIT);
97859004Shm			} else {
97980638Sobrien				if (flags & CE_COMPACT)
980107916Ssobomax					compress_log(log,
981107916Ssobomax					    flags & CE_COMPACTWAIT);
98280638Sobrien				else if (flags & CE_BZCOMPACT)
983107916Ssobomax					bzcompress_log(log,
984107916Ssobomax					    flags & CE_COMPACTWAIT);
98559004Shm			}
98625443Sache		}
98759003Shm	}
98813244Sgraichen}
98913244Sgraichen
99013244Sgraichen/* Log the fact that the logs were turned over */
99159004Shmstatic int
992111768Sgadlog_trim(const char *log, const struct conf_entry *log_ent)
99313244Sgraichen{
99459003Shm	FILE *f;
995111388Sgad	const char *xtra;
99659003Shm
99759003Shm	if ((f = fopen(log, "a")) == NULL)
99859003Shm		return (-1);
999111388Sgad	xtra = "";
1000111768Sgad	if (log_ent->def_cfg)
1001111388Sgad		xtra = " using <default> rule";
1002111388Sgad	fprintf(f, "%s %s newsyslog[%d]: logfile turned over%s\n",
1003111388Sgad	    daytime, hostname, (int) getpid(), xtra);
100459003Shm	if (fclose(f) == EOF)
100559003Shm		err(1, "log_trim: fclose:");
100659003Shm	return (0);
100713244Sgraichen}
100813244Sgraichen
100959004Shm/* Fork of gzip to compress the old log file */
101059004Shmstatic void
1011107916Ssobomaxcompress_log(char *log, int dowait)
101213244Sgraichen{
101359003Shm	pid_t pid;
101471299Sjedgar	char tmp[MAXPATHLEN];
101559003Shm
1016107916Ssobomax	while (dowait && (wait(NULL) > 0 || errno == EINTR))
1017107916Ssobomax		;
101871299Sjedgar	(void) snprintf(tmp, sizeof(tmp), "%s.0", log);
101925443Sache	pid = fork();
102059003Shm	if (pid < 0)
102180638Sobrien		err(1, "gzip fork");
102259003Shm	else if (!pid) {
102379452Sbrian		(void) execl(_PATH_GZIP, _PATH_GZIP, "-f", tmp, (char *)0);
102443071Swollman		err(1, _PATH_GZIP);
102559003Shm	}
102613244Sgraichen}
102713244Sgraichen
102880638Sobrien/* Fork of bzip2 to compress the old log file */
102980638Sobrienstatic void
1030107916Ssobomaxbzcompress_log(char *log, int dowait)
103180638Sobrien{
103280638Sobrien	pid_t pid;
103380638Sobrien	char tmp[MAXPATHLEN];
103480638Sobrien
1035107916Ssobomax	while (dowait && (wait(NULL) > 0 || errno == EINTR))
1036107916Ssobomax		;
103780638Sobrien	snprintf(tmp, sizeof(tmp), "%s.0", log);
103880638Sobrien	pid = fork();
103980638Sobrien	if (pid < 0)
104080638Sobrien		err(1, "bzip2 fork");
104180638Sobrien	else if (!pid) {
104286360Sobrien		execl(_PATH_BZIP2, _PATH_BZIP2, "-f", tmp, (char *)0);
104380638Sobrien		err(1, _PATH_BZIP2);
104480638Sobrien	}
104580638Sobrien}
104680638Sobrien
104713244Sgraichen/* Return size in kilobytes of a file */
104859004Shmstatic int
104959004Shmsizefile(char *file)
105013244Sgraichen{
105159003Shm	struct stat sb;
105213244Sgraichen
105359003Shm	if (stat(file, &sb) < 0)
105459003Shm		return (-1);
105559003Shm	return (kbytes(dbtob(sb.st_blocks)));
105613244Sgraichen}
105713244Sgraichen
105813244Sgraichen/* Return the age of old log file (file.0) */
105959004Shmstatic int
106059004Shmage_old_log(char *file)
106113244Sgraichen{
106259003Shm	struct stat sb;
106359003Shm	char tmp[MAXPATHLEN + sizeof(".0") + sizeof(COMPRESS_POSTFIX) + 1];
106413244Sgraichen
106559004Shm	if (archtodir) {
106659004Shm		char *p;
106759004Shm
106859004Shm		/* build name of archive directory into tmp */
106959004Shm		if (*archdirname == '/') {	/* absolute */
107071299Sjedgar			strlcpy(tmp, archdirname, sizeof(tmp));
107159004Shm		} else {	/* relative */
107259004Shm			/* get directory part of logfile */
107371299Sjedgar			strlcpy(tmp, file, sizeof(tmp));
107459004Shm			if ((p = rindex(tmp, '/')) == NULL)
107559004Shm				tmp[0] = '\0';
107659004Shm			else
107759004Shm				*(p + 1) = '\0';
107871299Sjedgar			strlcat(tmp, archdirname, sizeof(tmp));
107959004Shm		}
108059004Shm
108171299Sjedgar		strlcat(tmp, "/", sizeof(tmp));
108259004Shm
108359004Shm		/* get filename part of logfile */
108459004Shm		if ((p = rindex(file, '/')) == NULL)
108571299Sjedgar			strlcat(tmp, file, sizeof(tmp));
108659004Shm		else
108771299Sjedgar			strlcat(tmp, p + 1, sizeof(tmp));
108859004Shm	} else {
108971299Sjedgar		(void) strlcpy(tmp, file, sizeof(tmp));
109059004Shm	}
109159004Shm
109259003Shm	if (stat(strcat(tmp, ".0"), &sb) < 0)
109359003Shm		if (stat(strcat(tmp, COMPRESS_POSTFIX), &sb) < 0)
109459003Shm			return (-1);
109559003Shm	return ((int) (timenow - sb.st_mtime + 1800) / 3600);
109613244Sgraichen}
109713244Sgraichen
109859004Shmstatic pid_t
109980640Sobrienget_pid(const char *pid_file)
110025443Sache{
110125443Sache	FILE *f;
110259003Shm	char line[BUFSIZ];
110325443Sache	pid_t pid = 0;
110413244Sgraichen
110559003Shm	if ((f = fopen(pid_file, "r")) == NULL)
110625443Sache		warn("can't open %s pid file to restart a daemon",
110759003Shm		    pid_file);
110825443Sache	else {
110959003Shm		if (fgets(line, BUFSIZ, f)) {
111025443Sache			pid = atol(line);
111125443Sache			if (pid < MIN_PID || pid > MAX_PID) {
111280646Sobrien				warnx("preposterous process number: %d",
111380646Sobrien				   (int)pid);
111425443Sache				pid = 0;
111525443Sache			}
111625443Sache		} else
111725443Sache			warn("can't read %s pid file to restart a daemon",
111859003Shm			    pid_file);
111959003Shm		(void) fclose(f);
112025443Sache	}
1121111392Sgad	return (pid);
112225443Sache}
112325443Sache
112413244Sgraichen/* Skip Over Blanks */
112559003Shmchar *
112659004Shmsob(char *p)
112713244Sgraichen{
112859003Shm	while (p && *p && isspace(*p))
112959003Shm		p++;
113059003Shm	return (p);
113113244Sgraichen}
113213244Sgraichen
113313244Sgraichen/* Skip Over Non-Blanks */
113459003Shmchar *
113559004Shmson(char *p)
113613244Sgraichen{
113759003Shm	while (p && *p && !isspace(*p))
113859003Shm		p++;
113959003Shm	return (p);
114013244Sgraichen}
114143071Swollman
114243071Swollman/*
114359004Shm * Parse a limited subset of ISO 8601. The specific format is as follows:
114443071Swollman *
114559004Shm * [CC[YY[MM[DD]]]][THH[MM[SS]]]	(where `T' is the literal letter)
114643071Swollman *
114759004Shm * We don't accept a timezone specification; missing fields (including timezone)
114859004Shm * are defaulted to the current date but time zero.
114943071Swollman */
115043071Swollmanstatic time_t
115193659Scjcparse8601(char *s, char *errline)
115243071Swollman{
115359003Shm	char *t;
115493659Scjc	time_t tsecs;
115559003Shm	struct tm tm, *tmp;
115659003Shm	u_long ul;
115743071Swollman
115843071Swollman	tmp = localtime(&timenow);
115943071Swollman	tm = *tmp;
116043071Swollman
116143071Swollman	tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
116243071Swollman
116343071Swollman	ul = strtoul(s, &t, 10);
116443071Swollman	if (*t != '\0' && *t != 'T')
1165111392Sgad		return (-1);
116643071Swollman
116743071Swollman	/*
116859003Shm	 * Now t points either to the end of the string (if no time was
116959003Shm	 * provided) or to the letter `T' which separates date and time in
117059003Shm	 * ISO 8601.  The pointer arithmetic is the same for either case.
117143071Swollman	 */
117243071Swollman	switch (t - s) {
117343071Swollman	case 8:
117443071Swollman		tm.tm_year = ((ul / 1000000) - 19) * 100;
117543071Swollman		ul = ul % 1000000;
117643071Swollman	case 6:
117780666Swollman		tm.tm_year -= tm.tm_year % 100;
117843071Swollman		tm.tm_year += ul / 10000;
117943071Swollman		ul = ul % 10000;
118043071Swollman	case 4:
118143071Swollman		tm.tm_mon = (ul / 100) - 1;
118243071Swollman		ul = ul % 100;
118343071Swollman	case 2:
118443071Swollman		tm.tm_mday = ul;
118543071Swollman	case 0:
118643071Swollman		break;
118743071Swollman	default:
1188111392Sgad		return (-1);
118943071Swollman	}
119043071Swollman
119143071Swollman	/* sanity check */
119243071Swollman	if (tm.tm_year < 70 || tm.tm_mon < 0 || tm.tm_mon > 12
119343071Swollman	    || tm.tm_mday < 1 || tm.tm_mday > 31)
1194111392Sgad		return (-1);
119543071Swollman
119643071Swollman	if (*t != '\0') {
119743071Swollman		s = ++t;
119843071Swollman		ul = strtoul(s, &t, 10);
119943071Swollman		if (*t != '\0' && !isspace(*t))
1200111392Sgad			return (-1);
120143071Swollman
120243071Swollman		switch (t - s) {
120343071Swollman		case 6:
120443071Swollman			tm.tm_sec = ul % 100;
120543071Swollman			ul /= 100;
120643071Swollman		case 4:
120743071Swollman			tm.tm_min = ul % 100;
120843071Swollman			ul /= 100;
120943071Swollman		case 2:
121043071Swollman			tm.tm_hour = ul;
121143071Swollman		case 0:
121243071Swollman			break;
121343071Swollman		default:
1214111392Sgad			return (-1);
121543071Swollman		}
121643071Swollman
121743071Swollman		/* sanity check */
121843071Swollman		if (tm.tm_sec < 0 || tm.tm_sec > 60 || tm.tm_min < 0
121943071Swollman		    || tm.tm_min > 59 || tm.tm_hour < 0 || tm.tm_hour > 23)
1220111392Sgad			return (-1);
122143071Swollman	}
122293659Scjc	if ((tsecs = mktime(&tm)) == -1)
122399209Smaxim		errx(1, "nonexistent time:\n%s", errline);
1224111392Sgad	return (tsecs);
122543071Swollman}
122659004Shm
122759004Shm/* physically move file */
122859004Shmstatic void
122959004Shmmovefile(char *from, char *to, int perm, int owner_uid, int group_gid)
123059004Shm{
123159004Shm	FILE *src, *dst;
123259004Shm	int c;
123359004Shm
123459004Shm	if ((src = fopen(from, "r")) == NULL)
123559004Shm		err(1, "can't fopen %s for reading", from);
123659004Shm	if ((dst = fopen(to, "w")) == NULL)
123759004Shm		err(1, "can't fopen %s for writing", to);
123859004Shm	if (fchown(fileno(dst), owner_uid, group_gid))
123959004Shm		err(1, "can't fchown %s", to);
124059004Shm	if (fchmod(fileno(dst), perm))
124159004Shm		err(1, "can't fchmod %s", to);
124259004Shm
124359004Shm	while ((c = getc(src)) != EOF) {
124459004Shm		if ((putc(c, dst)) == EOF)
124559004Shm			err(1, "error writing to %s", to);
124659004Shm	}
124759004Shm
124859004Shm	if (ferror(src))
124959004Shm		err(1, "error reading from %s", from);
125059004Shm	if ((fclose(src)) != 0)
125159004Shm		err(1, "can't fclose %s", to);
125259004Shm	if ((fclose(dst)) != 0)
125359004Shm		err(1, "can't fclose %s", from);
125459004Shm	if ((unlink(from)) != 0)
125559004Shm		err(1, "can't unlink %s", from);
125659004Shm}
125759004Shm
125859004Shm/* create one or more directory components of a path */
125959004Shmstatic void
126059004Shmcreatedir(char *dirpart)
126159004Shm{
1262111398Sgad	int res;
126359004Shm	char *s, *d;
126471299Sjedgar	char mkdirpath[MAXPATHLEN];
126559004Shm	struct stat st;
126659004Shm
126759004Shm	s = dirpart;
126859004Shm	d = mkdirpath;
126959004Shm
127059004Shm	for (;;) {
127159004Shm		*d++ = *s++;
1272111398Sgad		if (*s != '/' && *s != '\0')
1273111398Sgad			continue;
1274111398Sgad		*d = '\0';
1275111398Sgad		res = lstat(mkdirpath, &st);
1276111398Sgad		if (res != 0) {
1277111398Sgad			if (noaction) {
1278111398Sgad				printf("mkdir %s\n", mkdirpath);
1279111398Sgad			} else {
1280111398Sgad				res = mkdir(mkdirpath, 0755);
1281111398Sgad				if (res != 0)
1282111398Sgad					err(1, "Error on mkdir(\"%s\") for -a",
1283111398Sgad					    mkdirpath);
1284111398Sgad			}
128559004Shm		}
128659004Shm		if (*s == '\0')
128759004Shm			break;
128859004Shm	}
1289111398Sgad	if (verbose)
1290111398Sgad		printf("created directory '%s' for -a\n", dirpart);
129159004Shm}
129259004Shm
129359004Shm/*-
129459004Shm * Parse a cyclic time specification, the format is as follows:
129559004Shm *
129659004Shm *	[Dhh] or [Wd[Dhh]] or [Mdd[Dhh]]
129759004Shm *
129859004Shm * to rotate a logfile cyclic at
129959004Shm *
130059004Shm *	- every day (D) within a specific hour (hh)	(hh = 0...23)
130159004Shm *	- once a week (W) at a specific day (d)     OR	(d = 0..6, 0 = Sunday)
130259004Shm *	- once a month (M) at a specific day (d)	(d = 1..31,l|L)
130359004Shm *
130459004Shm * We don't accept a timezone specification; missing fields
130559004Shm * are defaulted to the current date but time zero.
130659004Shm */
130759004Shmstatic time_t
130893659ScjcparseDWM(char *s, char *errline)
130959004Shm{
131059004Shm	char *t;
131193659Scjc	time_t tsecs;
131259004Shm	struct tm tm, *tmp;
131380742Sobrien	long l;
131459004Shm	int nd;
131559004Shm	static int mtab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
131659004Shm	int WMseen = 0;
131759004Shm	int Dseen = 0;
131859004Shm
131959004Shm	tmp = localtime(&timenow);
132059004Shm	tm = *tmp;
132159004Shm
132259004Shm	/* set no. of days per month */
132359004Shm
132459004Shm	nd = mtab[tm.tm_mon];
132559004Shm
132659004Shm	if (tm.tm_mon == 1) {
132759004Shm		if (((tm.tm_year + 1900) % 4 == 0) &&
132859004Shm		    ((tm.tm_year + 1900) % 100 != 0) &&
132959004Shm		    ((tm.tm_year + 1900) % 400 == 0)) {
133059004Shm			nd++;	/* leap year, 29 days in february */
133159004Shm		}
133259004Shm	}
133359004Shm	tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
133459004Shm
133559004Shm	for (;;) {
133659004Shm		switch (*s) {
133759004Shm		case 'D':
133859004Shm			if (Dseen)
1339111392Sgad				return (-1);
134059004Shm			Dseen++;
134159004Shm			s++;
134280742Sobrien			l = strtol(s, &t, 10);
134380742Sobrien			if (l < 0 || l > 23)
1344111392Sgad				return (-1);
134580742Sobrien			tm.tm_hour = l;
134659004Shm			break;
134759004Shm
134859004Shm		case 'W':
134959004Shm			if (WMseen)
1350111392Sgad				return (-1);
135159004Shm			WMseen++;
135259004Shm			s++;
135380742Sobrien			l = strtol(s, &t, 10);
135480742Sobrien			if (l < 0 || l > 6)
1355111392Sgad				return (-1);
135680742Sobrien			if (l != tm.tm_wday) {
135759004Shm				int save;
135859004Shm
135980742Sobrien				if (l < tm.tm_wday) {
136059004Shm					save = 6 - tm.tm_wday;
136180742Sobrien					save += (l + 1);
136259004Shm				} else {
136380742Sobrien					save = l - tm.tm_wday;
136459004Shm				}
136559004Shm
136659004Shm				tm.tm_mday += save;
136759004Shm
136859004Shm				if (tm.tm_mday > nd) {
136959004Shm					tm.tm_mon++;
137059004Shm					tm.tm_mday = tm.tm_mday - nd;
137159004Shm				}
137259004Shm			}
137359004Shm			break;
137459004Shm
137559004Shm		case 'M':
137659004Shm			if (WMseen)
1377111392Sgad				return (-1);
137859004Shm			WMseen++;
137959004Shm			s++;
138059004Shm			if (tolower(*s) == 'l') {
138159004Shm				tm.tm_mday = nd;
138259004Shm				s++;
138359004Shm				t = s;
138459004Shm			} else {
138580742Sobrien				l = strtol(s, &t, 10);
138680742Sobrien				if (l < 1 || l > 31)
1387111392Sgad					return (-1);
138859004Shm
138980742Sobrien				if (l > nd)
1390111392Sgad					return (-1);
139180742Sobrien				tm.tm_mday = l;
139259004Shm			}
139359004Shm			break;
139459004Shm
139559004Shm		default:
139659004Shm			return (-1);
139759004Shm			break;
139859004Shm		}
139959004Shm
140059004Shm		if (*t == '\0' || isspace(*t))
140159004Shm			break;
140259004Shm		else
140359004Shm			s = t;
140459004Shm	}
140593659Scjc	if ((tsecs = mktime(&tm)) == -1)
140699209Smaxim		errx(1, "nonexistent time:\n%s", errline);
1407111392Sgad	return (tsecs);
140859004Shm}
1409