newsyslog.c revision 111388
159243Sobrien/* 259243Sobrien * This file contains changes from the Open Software Foundation. 359243Sobrien */ 459243Sobrien 559243Sobrien/* 659243Sobrien * Copyright 1988, 1989 by the Massachusetts Institute of Technology 759243Sobrien * 859243Sobrien * Permission to use, copy, modify, and distribute this software and its 959243Sobrien * documentation for any purpose and without fee is hereby granted, provided 1059243Sobrien * that the above copyright notice appear in all copies and that both that 1159243Sobrien * copyright notice and this permission notice appear in supporting 1259243Sobrien * documentation, and that the names of M.I.T. and the M.I.T. S.I.P.B. not be 1359243Sobrien * used in advertising or publicity pertaining to distribution of the 1459243Sobrien * software without specific, written prior permission. M.I.T. and the M.I.T. 1559243Sobrien * S.I.P.B. make no representations about the suitability of this software 16100616Smp * for any purpose. It is provided "as is" without express or implied 1759243Sobrien * warranty. 1859243Sobrien * 1959243Sobrien */ 2059243Sobrien 2159243Sobrien/* 2259243Sobrien * newsyslog - roll over selected logs at the appropriate time, keeping the a 2359243Sobrien * specified number of backup files around. 2459243Sobrien */ 2559243Sobrien 2659243Sobrien#ifndef lint 2759243Sobrienstatic const char rcsid[] = 2859243Sobrien"$FreeBSD: head/usr.sbin/newsyslog/newsyslog.c 111388 2003-02-24 00:51:41Z gad $"; 2959243Sobrien#endif /* not lint */ 3059243Sobrien 3159243Sobrien#define OSF 3259243Sobrien#ifndef COMPRESS_POSTFIX 3359243Sobrien#define COMPRESS_POSTFIX ".gz" 3459243Sobrien#endif 3559243Sobrien#ifndef BZCOMPRESS_POSTFIX 3659243Sobrien#define BZCOMPRESS_POSTFIX ".bz2" 3759243Sobrien#endif 3859243Sobrien 3959243Sobrien#include <sys/param.h> 4059243Sobrien#include <sys/stat.h> 4159243Sobrien#include <sys/wait.h> 4259243Sobrien 4359243Sobrien#include <ctype.h> 4459243Sobrien#include <err.h> 4559243Sobrien#include <errno.h> 4659243Sobrien#include <fcntl.h> 4759243Sobrien#include <glob.h> 4859243Sobrien#include <grp.h> 4959243Sobrien#include <paths.h> 5059243Sobrien#include <pwd.h> 5159243Sobrien#include <signal.h> 5259243Sobrien#include <stdio.h> 5359243Sobrien#include <stdlib.h> 5459243Sobrien#include <string.h> 5559243Sobrien#include <time.h> 5659243Sobrien#include <unistd.h> 5759243Sobrien 5859243Sobrien#include "pathnames.h" 5959243Sobrien 6059243Sobrien#define kbytes(size) (((size) + 1023) >> 10) 6159243Sobrien 6269408Sache#ifdef _IBMR2 6359243Sobrien/* Calculates (db * DEV_BSIZE) */ 6469408Sache#define dbtob(db) ((unsigned)(db) << UBSHIFT) 6559243Sobrien#endif 6659243Sobrien 6759243Sobrien#define CE_COMPACT 1 /* Compact the achived log files */ 6859243Sobrien#define CE_BINARY 2 /* Logfile is in binary, don't add */ 6959243Sobrien#define CE_BZCOMPACT 8 /* Compact the achived log files with bzip2 */ 7059243Sobrien /* status messages */ 7159243Sobrien#define CE_TRIMAT 4 /* trim at a specific time */ 7259243Sobrien#define CE_GLOB 16 /* name of the log is file name pattern */ 7359243Sobrien#define CE_COMPACTWAIT 32 /* wait till compressing finishes before */ 7459243Sobrien /* starting the next one */ 7559243Sobrien 7659243Sobrien#define NONE -1 7759243Sobrien 7859243Sobrienstruct conf_entry { 7959243Sobrien char *log; /* Name of the log */ 8059243Sobrien char *pid_file; /* PID file */ 8159243Sobrien int uid; /* Owner of log */ 8259243Sobrien int gid; /* Group of log */ 8359243Sobrien int numlogs; /* Number of logs to keep */ 8459243Sobrien int size; /* Size cutoff to trigger trimming the log */ 8559243Sobrien int hours; /* Hours between log trimming */ 8659243Sobrien time_t trim_at; /* Specific time to do trimming */ 8759243Sobrien int permissions; /* File permissions on the log */ 8859243Sobrien int flags; /* CE_COMPACT, CE_BZCOMPACT, CE_BINARY */ 8959243Sobrien int sig; /* Signal to send */ 9059243Sobrien int def_cfg; /* Using the <default> rule for this file */ 9159243Sobrien struct conf_entry *next;/* Linked list pointer */ 9259243Sobrien}; 9359243Sobrien 9459243Sobrien#define DEFAULT_MARKER "<default>" 9559243Sobrien 9659243Sobrienint archtodir = 0; /* Archive old logfiles to other directory */ 9759243Sobrienint verbose = 0; /* Print out what's going on */ 9859243Sobrienint needroot = 1; /* Root privs are necessary */ 9959243Sobrienint noaction = 0; /* Don't do anything, just show it */ 10059243Sobrienint force = 0; /* Force the trim no matter what */ 10159243Sobrienchar *archdirname; /* Directory path to old logfiles archive */ 10259243Sobrienconst char *conf = _PATH_CONF; /* Configuration file to use */ 10359243Sobrientime_t timenow; 10459243Sobrien 10559243Sobrien#define MIN_PID 5 10659243Sobrien#define MAX_PID 99999 /* was lower, see /usr/include/sys/proc.h */ 10759243Sobrienchar hostname[MAXHOSTNAMELEN]; /* hostname */ 10859243Sobrienchar daytime[16]; /* timenow in human readable form */ 10959243Sobrien 11059243Sobrienstatic struct conf_entry *parse_file(char **files); 11159243Sobrienstatic char *sob(char *p); 11259243Sobrienstatic char *son(char *p); 11359243Sobrienstatic char *missing_field(char *p, char *errline); 11459243Sobrienstatic void do_entry(struct conf_entry * ent); 11559243Sobrienstatic void free_entry(struct conf_entry *ent); 11659243Sobrienstatic struct conf_entry *init_entry(const char *fname, 11759243Sobrien struct conf_entry *src_entry); 11859243Sobrienstatic void PRS(int argc, char **argv); 11959243Sobrienstatic void usage(void); 12059243Sobrienstatic void dotrim(char *log, const char *pid_file, int numdays, int falgs, 12159243Sobrien int perm, int owner_uid, int group_gid, int sig, int def_cfg); 12259243Sobrienstatic int log_trim(char *log, int def_cfg); 12359243Sobrienstatic void compress_log(char *log, int dowait); 12459243Sobrienstatic void bzcompress_log(char *log, int dowait); 12559243Sobrienstatic int sizefile(char *file); 12659243Sobrienstatic int age_old_log(char *file); 12759243Sobrienstatic pid_t get_pid(const char *pid_file); 12859243Sobrienstatic time_t parse8601(char *s, char *errline); 12959243Sobrienstatic void movefile(char *from, char *to, int perm, int owner_uid, 13059243Sobrien int group_gid); 13159243Sobrienstatic void createdir(char *dirpart); 13259243Sobrienstatic time_t parseDWM(char *s, char *errline); 13359243Sobrien 13459243Sobrienint 13559243Sobrienmain(int argc, char **argv) 13659243Sobrien{ 13759243Sobrien struct conf_entry *p, *q; 13859243Sobrien glob_t pglob; 13959243Sobrien int i; 14059243Sobrien 14159243Sobrien PRS(argc, argv); 14259243Sobrien if (needroot && getuid() && geteuid()) 14359243Sobrien errx(1, "must have root privs"); 14459243Sobrien p = q = parse_file(argv + optind); 14559243Sobrien 14659243Sobrien while (p) { 14759243Sobrien if ((p->flags & CE_GLOB) == 0) { 14859243Sobrien do_entry(p); 149131962Smp } else { 150131962Smp if (glob(p->log, GLOB_NOCHECK, NULL, &pglob) != 0) { 15159243Sobrien warn("can't expand pattern: %s", p->log); 15259243Sobrien } else { 15359243Sobrien for (i = 0; i < pglob.gl_matchc; i++) { 15459243Sobrien p->log = pglob.gl_pathv[i]; 15559243Sobrien do_entry(p); 15659243Sobrien } 15759415Sobrien globfree(&pglob); 15859415Sobrien } 15959415Sobrien } 16059415Sobrien p = p->next; 161131962Smp free_entry(q); 162131962Smp q = p; 163131962Smp } 164131962Smp while (wait(NULL) > 0 || errno == EINTR) 165131962Smp ; 166131962Smp return (0); 167131962Smp} 16859243Sobrien 16959243Sobrienstatic struct conf_entry * 17059243Sobrieninit_entry(const char *fname, struct conf_entry *src_entry) 17159243Sobrien{ 17259243Sobrien struct conf_entry *tempwork; 17359243Sobrien 17459243Sobrien if (verbose > 4) 17559243Sobrien printf("\t--> [creating entry for %s]\n", fname); 17659243Sobrien 17759243Sobrien tempwork = malloc(sizeof(struct conf_entry)); 17859243Sobrien if (tempwork == NULL) 17959243Sobrien err(1, "malloc of conf_entry for %s", fname); 18059243Sobrien 18159243Sobrien tempwork->log = strdup(fname); 18259243Sobrien if (tempwork->log == NULL) 18359243Sobrien err(1, "strdup for %s", fname); 18459243Sobrien 18559243Sobrien if (src_entry != NULL) { 18659243Sobrien tempwork->pid_file = NULL; 18759243Sobrien if (src_entry->pid_file) 18859243Sobrien tempwork->pid_file = strdup(src_entry->pid_file); 18959243Sobrien tempwork->uid = src_entry->uid; 19059243Sobrien tempwork->gid = src_entry->gid; 19159243Sobrien tempwork->numlogs = src_entry->numlogs; 19259243Sobrien tempwork->size = src_entry->size; 19359243Sobrien tempwork->hours = src_entry->hours; 19459243Sobrien tempwork->trim_at = src_entry->trim_at; 19559243Sobrien tempwork->permissions = src_entry->permissions; 19659243Sobrien tempwork->flags = src_entry->flags; 19759243Sobrien tempwork->sig = src_entry->sig; 19859243Sobrien tempwork->def_cfg = src_entry->def_cfg; 19959243Sobrien } else { 20059243Sobrien /* Initialize as a "do-nothing" entry */ 20159243Sobrien tempwork->pid_file = NULL; 20259243Sobrien tempwork->uid = NONE; 20359243Sobrien tempwork->gid = NONE; 20459243Sobrien tempwork->numlogs = 1; 20559243Sobrien tempwork->size = -1; 20659243Sobrien tempwork->hours = -1; 20759243Sobrien tempwork->trim_at = (time_t)0; 20859243Sobrien tempwork->permissions = 0; 20959243Sobrien tempwork->flags = 0; 21059243Sobrien tempwork->sig = SIGHUP; 21159243Sobrien tempwork->def_cfg = 0; 21259243Sobrien } 21359243Sobrien tempwork->next = NULL; 21459243Sobrien 21559243Sobrien return (tempwork); 21659243Sobrien} 21759243Sobrien 21859243Sobrienstatic void 21959243Sobrienfree_entry(struct conf_entry *ent) 22059243Sobrien{ 22159243Sobrien 22259243Sobrien if (ent == NULL) 22359243Sobrien return; 22459243Sobrien 22559243Sobrien if (ent->log != NULL) { 22659243Sobrien if (verbose > 4) 22759243Sobrien printf("\t--> [freeing entry for %s]\n", ent->log); 22859243Sobrien free(ent->log); 22959243Sobrien ent->log = NULL; 23059243Sobrien } 23159243Sobrien 23259243Sobrien if (ent->pid_file != NULL) { 23359243Sobrien free(ent->pid_file); 23459243Sobrien ent->pid_file = NULL; 23559243Sobrien } 23659243Sobrien 23759243Sobrien free(ent); 23859243Sobrien} 23959243Sobrien 24059243Sobrienstatic void 24159243Sobriendo_entry(struct conf_entry * ent) 24259243Sobrien{ 24359243Sobrien int size, modtime; 24459243Sobrien const char *pid_file; 24559243Sobrien 24659243Sobrien if (verbose) { 24759243Sobrien if (ent->flags & CE_COMPACT) 24859243Sobrien printf("%s <%dZ>: ", ent->log, ent->numlogs); 24959243Sobrien else if (ent->flags & CE_BZCOMPACT) 25059243Sobrien printf("%s <%dJ>: ", ent->log, ent->numlogs); 25159243Sobrien else 25259243Sobrien printf("%s <%d>: ", ent->log, ent->numlogs); 25359243Sobrien } 25459243Sobrien size = sizefile(ent->log); 25559243Sobrien modtime = age_old_log(ent->log); 25659243Sobrien if (size < 0) { 25759243Sobrien if (verbose) 25859243Sobrien printf("does not exist.\n"); 25959243Sobrien } else { 26059243Sobrien if (ent->flags & CE_TRIMAT && !force) { 26159243Sobrien if (timenow < ent->trim_at 26259243Sobrien || difftime(timenow, ent->trim_at) >= 60 * 60) { 26359243Sobrien if (verbose) 26459243Sobrien printf("--> will trim at %s", 26559243Sobrien ctime(&ent->trim_at)); 26659243Sobrien return; 26759243Sobrien } else if (verbose && ent->hours <= 0) { 26859243Sobrien printf("--> time is up\n"); 26959243Sobrien } 27059243Sobrien } 27159243Sobrien if (verbose && (ent->size > 0)) 27259243Sobrien printf("size (Kb): %d [%d] ", size, ent->size); 27359243Sobrien if (verbose && (ent->hours > 0)) 27459243Sobrien printf(" age (hr): %d [%d] ", modtime, ent->hours); 27559243Sobrien if (force || ((ent->size > 0) && (size >= ent->size)) || 27659243Sobrien (ent->hours <= 0 && (ent->flags & CE_TRIMAT)) || 27759243Sobrien ((ent->hours > 0) && ((modtime >= ent->hours) 27859243Sobrien || (modtime < 0)))) { 27959243Sobrien if (verbose) 28059243Sobrien printf("--> trimming log....\n"); 28159243Sobrien if (noaction && !verbose) { 28259243Sobrien if (ent->flags & CE_COMPACT) 28359243Sobrien printf("%s <%dZ>: trimming\n", 28459243Sobrien ent->log, ent->numlogs); 28559243Sobrien else if (ent->flags & CE_BZCOMPACT) 28659243Sobrien printf("%s <%dJ>: trimming\n", 28759243Sobrien ent->log, ent->numlogs); 28859243Sobrien else 28959243Sobrien printf("%s <%d>: trimming\n", 29059243Sobrien ent->log, ent->numlogs); 29159243Sobrien } 29259243Sobrien if (ent->pid_file) { 29359243Sobrien pid_file = ent->pid_file; 29459243Sobrien } else { 29559243Sobrien /* Only try to notify syslog if we are root */ 29659243Sobrien if (needroot) 29759243Sobrien pid_file = _PATH_SYSLOGPID; 29859243Sobrien else 29959243Sobrien pid_file = NULL; 30059243Sobrien } 30159243Sobrien dotrim(ent->log, pid_file, ent->numlogs, 30259243Sobrien ent->flags, ent->permissions, ent->uid, ent->gid, 30359243Sobrien ent->sig, ent->def_cfg); 30459243Sobrien } else { 30559243Sobrien if (verbose) 30659243Sobrien printf("--> skipping\n"); 30759243Sobrien } 30859243Sobrien } 30959243Sobrien} 31059243Sobrien 31159243Sobrienstatic void 31259243SobrienPRS(int argc, char **argv) 31359243Sobrien{ 31459243Sobrien int c; 31559243Sobrien char *p; 31659243Sobrien 31759243Sobrien timenow = time((time_t *) 0); 31859243Sobrien (void)strncpy(daytime, ctime(&timenow) + 4, 15); 31959243Sobrien daytime[15] = '\0'; 32059243Sobrien 32159243Sobrien /* Let's get our hostname */ 32259243Sobrien (void) gethostname(hostname, sizeof(hostname)); 32359243Sobrien 32459243Sobrien /* Truncate domain */ 32559243Sobrien if ((p = strchr(hostname, '.'))) { 32659243Sobrien *p = '\0'; 32759243Sobrien } 32859243Sobrien while ((c = getopt(argc, argv, "nrvFf:a:")) != -1) 32959243Sobrien switch (c) { 33059243Sobrien case 'n': 33159243Sobrien noaction++; 33259243Sobrien break; 33359243Sobrien case 'a': 33459243Sobrien archtodir++; 33559243Sobrien archdirname = optarg; 33659243Sobrien break; 33759243Sobrien case 'r': 33859243Sobrien needroot = 0; 33959243Sobrien break; 34059243Sobrien case 'v': 34159243Sobrien verbose++; 34259243Sobrien break; 34359243Sobrien case 'f': 34459243Sobrien conf = optarg; 34559243Sobrien break; 34659243Sobrien case 'F': 34759243Sobrien force++; 34859243Sobrien break; 34959243Sobrien default: 35059243Sobrien usage(); 35159243Sobrien } 35259243Sobrien} 35359243Sobrien 35459243Sobrienstatic void 35559243Sobrienusage(void) 35659243Sobrien{ 35759243Sobrien 35859415Sobrien fprintf(stderr, 35959415Sobrien "usage: newsyslog [-Fnrv] [-f config-file] [-a directory] [ filename ... ]\n"); 36059415Sobrien exit(1); 36159415Sobrien} 36259415Sobrien 36359415Sobrien/* 36459415Sobrien * Parse a configuration file and return a linked list of all the logs to 36559415Sobrien * process 36659243Sobrien */ 36759243Sobrienstatic struct conf_entry * 36859243Sobrienparse_file(char **files) 36959243Sobrien{ 37059243Sobrien FILE *f; 37159243Sobrien char line[BUFSIZ], *parse, *q; 37259243Sobrien char *cp, *errline, *group; 37359243Sobrien char **given; 37459243Sobrien struct conf_entry *defconf, *first, *working, *worklist; 37559243Sobrien struct passwd *pass; 37659243Sobrien struct group *grp; 37759243Sobrien int eol; 37859243Sobrien 37959243Sobrien defconf = first = working = worklist = NULL; 38059243Sobrien 38159243Sobrien if (strcmp(conf, "-")) 38259243Sobrien f = fopen(conf, "r"); 38359243Sobrien else 38459243Sobrien f = stdin; 38559415Sobrien if (!f) 38659415Sobrien err(1, "%s", conf); 38759415Sobrien while (fgets(line, BUFSIZ, f)) { 38859415Sobrien if ((line[0] == '\n') || (line[0] == '#') || 38959415Sobrien (strlen(line) == 0)) 39059415Sobrien continue; 39159415Sobrien errline = strdup(line); 39259415Sobrien for (cp = line + 1; *cp != '\0'; cp++) { 39359243Sobrien if (*cp != '#') 39459243Sobrien continue; 39559243Sobrien if (*(cp - 1) == '\\') { 39659243Sobrien strcpy(cp - 1, cp); 39759243Sobrien cp--; 39859243Sobrien continue; 39959243Sobrien } 40059243Sobrien *cp = '\0'; 40159243Sobrien break; 40259243Sobrien } 40359243Sobrien 40459243Sobrien q = parse = missing_field(sob(line), errline); 40559243Sobrien parse = son(line); 40659243Sobrien if (!*parse) 40759243Sobrien errx(1, "malformed line (missing fields):\n%s", 40859243Sobrien errline); 40959243Sobrien *parse = '\0'; 41059243Sobrien 41159243Sobrien /* 41259243Sobrien * If newsyslog was run with a list of specific filenames, 41359243Sobrien * then this line of the config file should be skipped if 41459243Sobrien * it is NOT one of those given files (except that we do 41559243Sobrien * want any line that defines the <default> action). 41659243Sobrien * 41759243Sobrien * XXX - note that CE_GLOB processing is *NOT* done when 41859243Sobrien * trying to match a filename given on the command! 41959243Sobrien */ 42059243Sobrien if (*files) { 42159243Sobrien if (strcasecmp(DEFAULT_MARKER, q) != 0) { 42259243Sobrien for (given = files; *given; ++given) { 42359243Sobrien if (strcmp(*given, q) == 0) 42459243Sobrien break; 42559243Sobrien } 42659243Sobrien if (!*given) 42759243Sobrien continue; 42859243Sobrien } 42959243Sobrien if (verbose > 2) 43059243Sobrien printf("\t+ Matched entry %s\n", q); 43159243Sobrien } else { 43259243Sobrien /* 43359243Sobrien * If no files were specified on the command line, 43459243Sobrien * then we can skip any line which defines the 43559243Sobrien * default action. 43659243Sobrien */ 43759243Sobrien if (strcasecmp(DEFAULT_MARKER, q) == 0) { 43859243Sobrien if (verbose > 2) 43959243Sobrien printf("\t+ Ignoring entry for %s\n", 44059243Sobrien q); 44159243Sobrien continue; 44259243Sobrien } 44359243Sobrien } 44459243Sobrien 44559243Sobrien working = init_entry(q, NULL); 44659243Sobrien if (strcasecmp(DEFAULT_MARKER, q) == 0) { 44759243Sobrien if (defconf != NULL) { 44859243Sobrien warnx("Ignoring duplicate entry for %s!", q); 44959243Sobrien free_entry(working); 45059243Sobrien continue; 45159243Sobrien } 45259243Sobrien defconf = working; 45359243Sobrien } else { 45459243Sobrien if (!first) 45559243Sobrien first = working; 45659243Sobrien else 45759243Sobrien worklist->next = working; 45859243Sobrien worklist = working; 45959243Sobrien } 46059243Sobrien 46159243Sobrien q = parse = missing_field(sob(++parse), errline); 46259243Sobrien parse = son(parse); 46359243Sobrien if (!*parse) 46459243Sobrien errx(1, "malformed line (missing fields):\n%s", 46559243Sobrien errline); 46659243Sobrien *parse = '\0'; 46759243Sobrien if ((group = strchr(q, ':')) != NULL || 46859243Sobrien (group = strrchr(q, '.')) != NULL) { 46959243Sobrien *group++ = '\0'; 47059243Sobrien if (*q) { 47159243Sobrien if (!(isnumber(*q))) { 47259243Sobrien if ((pass = getpwnam(q)) == NULL) 47359243Sobrien errx(1, 47459243Sobrien "error in config file; unknown user:\n%s", 47559243Sobrien errline); 47659243Sobrien working->uid = pass->pw_uid; 47759243Sobrien } else 47859415Sobrien working->uid = atoi(q); 47959243Sobrien } else 48059243Sobrien working->uid = NONE; 48159243Sobrien 48259243Sobrien q = group; 48359243Sobrien if (*q) { 48459243Sobrien if (!(isnumber(*q))) { 48559243Sobrien if ((grp = getgrnam(q)) == NULL) 48659243Sobrien errx(1, 48759243Sobrien "error in config file; unknown group:\n%s", 48859243Sobrien errline); 48959243Sobrien working->gid = grp->gr_gid; 49059243Sobrien } else 49159243Sobrien working->gid = atoi(q); 49259243Sobrien } else 49359243Sobrien working->gid = NONE; 49459243Sobrien 49559243Sobrien q = parse = missing_field(sob(++parse), errline); 49659243Sobrien parse = son(parse); 49759243Sobrien if (!*parse) 49859243Sobrien errx(1, "malformed line (missing fields):\n%s", 49959243Sobrien errline); 50059243Sobrien *parse = '\0'; 50159243Sobrien } else 50259243Sobrien working->uid = working->gid = NONE; 50359243Sobrien 50459243Sobrien if (!sscanf(q, "%o", &working->permissions)) 50559243Sobrien errx(1, "error in config file; bad permissions:\n%s", 50659243Sobrien errline); 50759243Sobrien 50859243Sobrien q = parse = missing_field(sob(++parse), errline); 50959243Sobrien parse = son(parse); 51059243Sobrien if (!*parse) 51159243Sobrien errx(1, "malformed line (missing fields):\n%s", 51259243Sobrien errline); 51359243Sobrien *parse = '\0'; 51459243Sobrien if (!sscanf(q, "%d", &working->numlogs)) 51559243Sobrien errx(1, "error in config file; bad number:\n%s", 51659243Sobrien errline); 51759243Sobrien 51859243Sobrien q = parse = missing_field(sob(++parse), errline); 51959243Sobrien parse = son(parse); 52059243Sobrien if (!*parse) 52159243Sobrien errx(1, "malformed line (missing fields):\n%s", 52259243Sobrien errline); 52359243Sobrien *parse = '\0'; 52459243Sobrien if (isdigit(*q)) 52559243Sobrien working->size = atoi(q); 52659243Sobrien else 52759243Sobrien working->size = -1; 52859243Sobrien 52959243Sobrien working->flags = 0; 53059243Sobrien q = parse = missing_field(sob(++parse), errline); 53159243Sobrien parse = son(parse); 53259243Sobrien eol = !*parse; 53359243Sobrien *parse = '\0'; 53459243Sobrien { 53559243Sobrien char *ep; 53659243Sobrien u_long ul; 53759243Sobrien 53859243Sobrien ul = strtoul(q, &ep, 10); 53959243Sobrien if (ep == q) 54059243Sobrien working->hours = 0; 54159243Sobrien else if (*ep == '*') 54259243Sobrien working->hours = -1; 54359243Sobrien else if (ul > INT_MAX) 54459243Sobrien errx(1, "interval is too large:\n%s", errline); 54559243Sobrien else 54659243Sobrien working->hours = ul; 54759243Sobrien 54859243Sobrien if (*ep != '\0' && *ep != '@' && *ep != '*' && 54959243Sobrien *ep != '$') 55059243Sobrien errx(1, "malformed interval/at:\n%s", errline); 55159243Sobrien if (*ep == '@') { 55259243Sobrien if ((working->trim_at = parse8601(ep + 1, errline)) 55359243Sobrien == (time_t) - 1) 55459243Sobrien errx(1, "malformed at:\n%s", errline); 55559243Sobrien working->flags |= CE_TRIMAT; 55659243Sobrien } else if (*ep == '$') { 55759243Sobrien if ((working->trim_at = parseDWM(ep + 1, errline)) 55859243Sobrien == (time_t) - 1) 55959243Sobrien errx(1, "malformed at:\n%s", errline); 56059243Sobrien working->flags |= CE_TRIMAT; 56159243Sobrien } 56259243Sobrien } 56359243Sobrien 56459243Sobrien if (eol) 56559243Sobrien q = NULL; 56659243Sobrien else { 56759243Sobrien q = parse = sob(++parse); /* Optional field */ 56859243Sobrien parse = son(parse); 56959243Sobrien if (!*parse) 57059243Sobrien eol = 1; 57159243Sobrien *parse = '\0'; 57259243Sobrien } 57359243Sobrien 57459243Sobrien while (q && *q && !isspace(*q)) { 57559243Sobrien if ((*q == 'Z') || (*q == 'z')) 57659243Sobrien working->flags |= CE_COMPACT; 57759243Sobrien else if ((*q == 'J') || (*q == 'j')) 57859243Sobrien working->flags |= CE_BZCOMPACT; 57959243Sobrien else if ((*q == 'B') || (*q == 'b')) 58059243Sobrien working->flags |= CE_BINARY; 58159243Sobrien else if ((*q == 'G') || (*q == 'c')) 58259243Sobrien working->flags |= CE_GLOB; 58359243Sobrien else if ((*q == 'W') || (*q == 'w')) 58459243Sobrien working->flags |= CE_COMPACTWAIT; 58559243Sobrien else if (*q != '-') 58659243Sobrien errx(1, "illegal flag in config file -- %c", 58759243Sobrien *q); 58859243Sobrien q++; 58959243Sobrien } 59059243Sobrien 59159243Sobrien if (eol) 59259243Sobrien q = NULL; 59359243Sobrien else { 59459243Sobrien q = parse = sob(++parse); /* Optional field */ 59559243Sobrien parse = son(parse); 59659243Sobrien if (!*parse) 59759243Sobrien eol = 1; 59859243Sobrien *parse = '\0'; 59959243Sobrien } 60059243Sobrien 60159243Sobrien working->pid_file = NULL; 60259243Sobrien if (q && *q) { 60359243Sobrien if (*q == '/') 60459243Sobrien working->pid_file = strdup(q); 60559243Sobrien else if (isdigit(*q)) 60659243Sobrien goto got_sig; 60759243Sobrien else 60859243Sobrien errx(1, 60959243Sobrien "illegal pid file or signal number in config file:\n%s", 61059243Sobrien errline); 61159243Sobrien } 61259243Sobrien if (eol) 61359243Sobrien q = NULL; 61459243Sobrien else { 61559243Sobrien q = parse = sob(++parse); /* Optional field */ 61659243Sobrien *(parse = son(parse)) = '\0'; 61759243Sobrien } 61859243Sobrien 61959243Sobrien working->sig = SIGHUP; 62059243Sobrien if (q && *q) { 62159243Sobrien if (isdigit(*q)) { 62259243Sobrien got_sig: 62359243Sobrien working->sig = atoi(q); 62459243Sobrien } else { 62559243Sobrien err_sig: 62659243Sobrien errx(1, 62759243Sobrien "illegal signal number in config file:\n%s", 62859243Sobrien errline); 62959243Sobrien } 63059243Sobrien if (working->sig < 1 || working->sig >= NSIG) 63159243Sobrien goto err_sig; 63259243Sobrien } 63359243Sobrien free(errline); 63459243Sobrien } 63559243Sobrien (void) fclose(f); 63659243Sobrien 63759243Sobrien /* 63859243Sobrien * The entire config file has been processed. If there were 63959243Sobrien * no specific files given on the run command, then the work 64059243Sobrien * of this routine is done. 64159243Sobrien */ 64259243Sobrien if (*files == NULL) 64359243Sobrien return (first); 64459243Sobrien 64559243Sobrien /* 64659243Sobrien * If the program was given a specific list of files to process, 64759243Sobrien * it may be that some of those files were not listed in the 64859243Sobrien * config file. Those unlisted files should get the default 64959243Sobrien * rotation action. First, create the default-rotation action 65059243Sobrien * if none was found in the config file. 65159243Sobrien */ 65259243Sobrien if (defconf == NULL) { 65359243Sobrien working = init_entry(DEFAULT_MARKER, NULL); 65459243Sobrien working->numlogs = 3; 65559243Sobrien working->size = 50; 65659243Sobrien working->permissions = S_IRUSR|S_IWUSR; 65759243Sobrien defconf = working; 65859243Sobrien } 65959243Sobrien 66059243Sobrien for (given = files; *given; ++given) { 66159243Sobrien for (working = first; working; working = working->next) { 66259243Sobrien if (strcmp(*given, working->log) == 0) 66359243Sobrien break; 66459243Sobrien } 66559243Sobrien if (working != NULL) 66659243Sobrien continue; 66759243Sobrien if (verbose > 2) 66859243Sobrien printf("\t+ No entry for %s (will use %s)\n", 66959243Sobrien *given, DEFAULT_MARKER); 67059243Sobrien /* 67159243Sobrien * This given file was not found in the config file. 67259243Sobrien * Add another item on to our work list, based on the 67359243Sobrien * default entry. 67459243Sobrien */ 67559243Sobrien working = init_entry(*given, defconf); 67659243Sobrien if (!first) 67759243Sobrien first = working; 67859243Sobrien else 67959243Sobrien worklist->next = working; 68059243Sobrien /* This is a file that was *not* found in config file */ 68159243Sobrien working->def_cfg = 1; 68259243Sobrien worklist = working; 68359243Sobrien } 68459243Sobrien 68559243Sobrien free_entry(defconf); 68659243Sobrien return (first); 68759243Sobrien} 68859243Sobrien 68959243Sobrienstatic char * 69059243Sobrienmissing_field(char *p, char *errline) 69159243Sobrien{ 69259243Sobrien 69359243Sobrien if (!p || !*p) 69459243Sobrien errx(1, "missing field in config file:\n%s", errline); 69559243Sobrien return (p); 69659243Sobrien} 69759243Sobrien 69859243Sobrienstatic void 69959243Sobriendotrim(char *log, const char *pid_file, int numdays, int flags, int perm, 70059243Sobrien int owner_uid, int group_gid, int sig, int def_cfg) 70159243Sobrien{ 70259243Sobrien char dirpart[MAXPATHLEN], namepart[MAXPATHLEN]; 70359243Sobrien char file1[MAXPATHLEN], file2[MAXPATHLEN]; 70459243Sobrien char zfile1[MAXPATHLEN], zfile2[MAXPATHLEN]; 70559243Sobrien char jfile1[MAXPATHLEN]; 70659243Sobrien char tfile[MAXPATHLEN]; 70759243Sobrien int notified, need_notification, fd, _numdays; 70859243Sobrien struct stat st; 709131962Smp pid_t pid; 710131962Smp 71159243Sobrien#ifdef _IBMR2 71259243Sobrien /* 71359243Sobrien * AIX 3.1 has a broken fchown- if the owner_uid is -1, it will 71459243Sobrien * actually change it to be owned by uid -1, instead of leaving it 71559243Sobrien * as is, as it is supposed to. 71659243Sobrien */ 71759243Sobrien if (owner_uid == -1) 71859243Sobrien owner_uid = geteuid(); 71959243Sobrien#endif 72059243Sobrien 72159243Sobrien if (archtodir) { 72259243Sobrien char *p; 72359243Sobrien 72459243Sobrien /* build complete name of archive directory into dirpart */ 72559243Sobrien if (*archdirname == '/') { /* absolute */ 72659243Sobrien strlcpy(dirpart, archdirname, sizeof(dirpart)); 72759243Sobrien } else { /* relative */ 72859243Sobrien /* get directory part of logfile */ 72959243Sobrien strlcpy(dirpart, log, sizeof(dirpart)); 73059243Sobrien if ((p = rindex(dirpart, '/')) == NULL) 73159243Sobrien dirpart[0] = '\0'; 73259243Sobrien else 73359243Sobrien *(p + 1) = '\0'; 73459243Sobrien strlcat(dirpart, archdirname, sizeof(dirpart)); 73559243Sobrien } 73659243Sobrien 73759243Sobrien /* check if archive directory exists, if not, create it */ 73859243Sobrien if (lstat(dirpart, &st)) 73959243Sobrien createdir(dirpart); 74059243Sobrien 74159243Sobrien /* get filename part of logfile */ 74259243Sobrien if ((p = rindex(log, '/')) == NULL) 74359243Sobrien strlcpy(namepart, log, sizeof(namepart)); 74459243Sobrien else 74559243Sobrien strlcpy(namepart, p + 1, sizeof(namepart)); 746 747 /* name of oldest log */ 748 (void) snprintf(file1, sizeof(file1), "%s/%s.%d", dirpart, 749 namepart, numdays); 750 (void) snprintf(zfile1, sizeof(zfile1), "%s%s", file1, 751 COMPRESS_POSTFIX); 752 snprintf(jfile1, sizeof(jfile1), "%s%s", file1, 753 BZCOMPRESS_POSTFIX); 754 } else { 755 /* name of oldest log */ 756 (void) snprintf(file1, sizeof(file1), "%s.%d", log, numdays); 757 (void) snprintf(zfile1, sizeof(zfile1), "%s%s", file1, 758 COMPRESS_POSTFIX); 759 snprintf(jfile1, sizeof(jfile1), "%s%s", file1, 760 BZCOMPRESS_POSTFIX); 761 } 762 763 if (noaction) { 764 printf("rm -f %s\n", file1); 765 printf("rm -f %s\n", zfile1); 766 printf("rm -f %s\n", jfile1); 767 } else { 768 (void) unlink(file1); 769 (void) unlink(zfile1); 770 (void) unlink(jfile1); 771 } 772 773 /* Move down log files */ 774 _numdays = numdays; /* preserve */ 775 while (numdays--) { 776 777 (void) strlcpy(file2, file1, sizeof(file2)); 778 779 if (archtodir) 780 (void) snprintf(file1, sizeof(file1), "%s/%s.%d", 781 dirpart, namepart, numdays); 782 else 783 (void) snprintf(file1, sizeof(file1), "%s.%d", log, 784 numdays); 785 786 (void) strlcpy(zfile1, file1, sizeof(zfile1)); 787 (void) strlcpy(zfile2, file2, sizeof(zfile2)); 788 if (lstat(file1, &st)) { 789 (void) strlcat(zfile1, COMPRESS_POSTFIX, 790 sizeof(zfile1)); 791 (void) strlcat(zfile2, COMPRESS_POSTFIX, 792 sizeof(zfile2)); 793 if (lstat(zfile1, &st)) { 794 strlcpy(zfile1, file1, sizeof(zfile1)); 795 strlcpy(zfile2, file2, sizeof(zfile2)); 796 strlcat(zfile1, BZCOMPRESS_POSTFIX, 797 sizeof(zfile1)); 798 strlcat(zfile2, BZCOMPRESS_POSTFIX, 799 sizeof(zfile2)); 800 if (lstat(zfile1, &st)) 801 continue; 802 } 803 } 804 if (noaction) { 805 printf("mv %s %s\n", zfile1, zfile2); 806 printf("chmod %o %s\n", perm, zfile2); 807 printf("chown %d:%d %s\n", 808 owner_uid, group_gid, zfile2); 809 } else { 810 (void) rename(zfile1, zfile2); 811 (void) chmod(zfile2, perm); 812 (void) chown(zfile2, owner_uid, group_gid); 813 } 814 } 815 if (!noaction && !(flags & CE_BINARY)) { 816 /* Report the trimming to the old log */ 817 (void) log_trim(log, def_cfg); 818 } 819 820 if (!_numdays) { 821 if (noaction) 822 printf("rm %s\n", log); 823 else 824 (void) unlink(log); 825 } else { 826 if (noaction) 827 printf("mv %s to %s\n", log, file1); 828 else { 829 if (archtodir) 830 movefile(log, file1, perm, owner_uid, 831 group_gid); 832 else 833 (void) rename(log, file1); 834 } 835 } 836 837 if (noaction) 838 printf("Start new log..."); 839 else { 840 strlcpy(tfile, log, sizeof(tfile)); 841 strlcat(tfile, ".XXXXXX", sizeof(tfile)); 842 mkstemp(tfile); 843 fd = creat(tfile, perm); 844 if (fd < 0) 845 err(1, "can't start new log"); 846 if (fchown(fd, owner_uid, group_gid)) 847 err(1, "can't chmod new log file"); 848 (void) close(fd); 849 if (!(flags & CE_BINARY)) { 850 /* Add status message to new log file */ 851 if (log_trim(tfile, def_cfg)) 852 err(1, "can't add status message to log"); 853 } 854 } 855 if (noaction) 856 printf("chmod %o %s...\n", perm, log); 857 else { 858 (void) chmod(tfile, perm); 859 if (rename(tfile, log) < 0) { 860 err(1, "can't start new log"); 861 (void) unlink(tfile); 862 } 863 } 864 865 pid = 0; 866 need_notification = notified = 0; 867 if (pid_file != NULL) { 868 need_notification = 1; 869 pid = get_pid(pid_file); 870 } 871 if (pid) { 872 if (noaction) { 873 notified = 1; 874 printf("kill -%d %d\n", sig, (int) pid); 875 } else if (kill(pid, sig)) 876 warn("can't notify daemon, pid %d", (int) pid); 877 else { 878 notified = 1; 879 if (verbose) 880 printf("daemon pid %d notified\n", (int) pid); 881 } 882 } 883 if ((flags & CE_COMPACT) || (flags & CE_BZCOMPACT)) { 884 if (need_notification && !notified) 885 warnx( 886 "log %s not compressed because daemon not notified", 887 log); 888 else if (noaction) 889 printf("Compress %s.0\n", log); 890 else { 891 if (notified) { 892 if (verbose) 893 printf("small pause to allow daemon to close log\n"); 894 sleep(10); 895 } 896 if (archtodir) { 897 (void) snprintf(file1, sizeof(file1), "%s/%s", 898 dirpart, namepart); 899 if (flags & CE_COMPACT) 900 compress_log(file1, 901 flags & CE_COMPACTWAIT); 902 else if (flags & CE_BZCOMPACT) 903 bzcompress_log(file1, 904 flags & CE_COMPACTWAIT); 905 } else { 906 if (flags & CE_COMPACT) 907 compress_log(log, 908 flags & CE_COMPACTWAIT); 909 else if (flags & CE_BZCOMPACT) 910 bzcompress_log(log, 911 flags & CE_COMPACTWAIT); 912 } 913 } 914 } 915} 916 917/* Log the fact that the logs were turned over */ 918static int 919log_trim(char *log, int def_cfg) 920{ 921 FILE *f; 922 const char *xtra; 923 924 if ((f = fopen(log, "a")) == NULL) 925 return (-1); 926 xtra = ""; 927 if (def_cfg) 928 xtra = " using <default> rule"; 929 fprintf(f, "%s %s newsyslog[%d]: logfile turned over%s\n", 930 daytime, hostname, (int) getpid(), xtra); 931 if (fclose(f) == EOF) 932 err(1, "log_trim: fclose:"); 933 return (0); 934} 935 936/* Fork of gzip to compress the old log file */ 937static void 938compress_log(char *log, int dowait) 939{ 940 pid_t pid; 941 char tmp[MAXPATHLEN]; 942 943 while (dowait && (wait(NULL) > 0 || errno == EINTR)) 944 ; 945 (void) snprintf(tmp, sizeof(tmp), "%s.0", log); 946 pid = fork(); 947 if (pid < 0) 948 err(1, "gzip fork"); 949 else if (!pid) { 950 (void) execl(_PATH_GZIP, _PATH_GZIP, "-f", tmp, (char *)0); 951 err(1, _PATH_GZIP); 952 } 953} 954 955/* Fork of bzip2 to compress the old log file */ 956static void 957bzcompress_log(char *log, int dowait) 958{ 959 pid_t pid; 960 char tmp[MAXPATHLEN]; 961 962 while (dowait && (wait(NULL) > 0 || errno == EINTR)) 963 ; 964 snprintf(tmp, sizeof(tmp), "%s.0", log); 965 pid = fork(); 966 if (pid < 0) 967 err(1, "bzip2 fork"); 968 else if (!pid) { 969 execl(_PATH_BZIP2, _PATH_BZIP2, "-f", tmp, (char *)0); 970 err(1, _PATH_BZIP2); 971 } 972} 973 974/* Return size in kilobytes of a file */ 975static int 976sizefile(char *file) 977{ 978 struct stat sb; 979 980 if (stat(file, &sb) < 0) 981 return (-1); 982 return (kbytes(dbtob(sb.st_blocks))); 983} 984 985/* Return the age of old log file (file.0) */ 986static int 987age_old_log(char *file) 988{ 989 struct stat sb; 990 char tmp[MAXPATHLEN + sizeof(".0") + sizeof(COMPRESS_POSTFIX) + 1]; 991 992 if (archtodir) { 993 char *p; 994 995 /* build name of archive directory into tmp */ 996 if (*archdirname == '/') { /* absolute */ 997 strlcpy(tmp, archdirname, sizeof(tmp)); 998 } else { /* relative */ 999 /* get directory part of logfile */ 1000 strlcpy(tmp, file, sizeof(tmp)); 1001 if ((p = rindex(tmp, '/')) == NULL) 1002 tmp[0] = '\0'; 1003 else 1004 *(p + 1) = '\0'; 1005 strlcat(tmp, archdirname, sizeof(tmp)); 1006 } 1007 1008 strlcat(tmp, "/", sizeof(tmp)); 1009 1010 /* get filename part of logfile */ 1011 if ((p = rindex(file, '/')) == NULL) 1012 strlcat(tmp, file, sizeof(tmp)); 1013 else 1014 strlcat(tmp, p + 1, sizeof(tmp)); 1015 } else { 1016 (void) strlcpy(tmp, file, sizeof(tmp)); 1017 } 1018 1019 if (stat(strcat(tmp, ".0"), &sb) < 0) 1020 if (stat(strcat(tmp, COMPRESS_POSTFIX), &sb) < 0) 1021 return (-1); 1022 return ((int) (timenow - sb.st_mtime + 1800) / 3600); 1023} 1024 1025static pid_t 1026get_pid(const char *pid_file) 1027{ 1028 FILE *f; 1029 char line[BUFSIZ]; 1030 pid_t pid = 0; 1031 1032 if ((f = fopen(pid_file, "r")) == NULL) 1033 warn("can't open %s pid file to restart a daemon", 1034 pid_file); 1035 else { 1036 if (fgets(line, BUFSIZ, f)) { 1037 pid = atol(line); 1038 if (pid < MIN_PID || pid > MAX_PID) { 1039 warnx("preposterous process number: %d", 1040 (int)pid); 1041 pid = 0; 1042 } 1043 } else 1044 warn("can't read %s pid file to restart a daemon", 1045 pid_file); 1046 (void) fclose(f); 1047 } 1048 return pid; 1049} 1050 1051/* Skip Over Blanks */ 1052char * 1053sob(char *p) 1054{ 1055 while (p && *p && isspace(*p)) 1056 p++; 1057 return (p); 1058} 1059 1060/* Skip Over Non-Blanks */ 1061char * 1062son(char *p) 1063{ 1064 while (p && *p && !isspace(*p)) 1065 p++; 1066 return (p); 1067} 1068 1069/* 1070 * Parse a limited subset of ISO 8601. The specific format is as follows: 1071 * 1072 * [CC[YY[MM[DD]]]][THH[MM[SS]]] (where `T' is the literal letter) 1073 * 1074 * We don't accept a timezone specification; missing fields (including timezone) 1075 * are defaulted to the current date but time zero. 1076 */ 1077static time_t 1078parse8601(char *s, char *errline) 1079{ 1080 char *t; 1081 time_t tsecs; 1082 struct tm tm, *tmp; 1083 u_long ul; 1084 1085 tmp = localtime(&timenow); 1086 tm = *tmp; 1087 1088 tm.tm_hour = tm.tm_min = tm.tm_sec = 0; 1089 1090 ul = strtoul(s, &t, 10); 1091 if (*t != '\0' && *t != 'T') 1092 return -1; 1093 1094 /* 1095 * Now t points either to the end of the string (if no time was 1096 * provided) or to the letter `T' which separates date and time in 1097 * ISO 8601. The pointer arithmetic is the same for either case. 1098 */ 1099 switch (t - s) { 1100 case 8: 1101 tm.tm_year = ((ul / 1000000) - 19) * 100; 1102 ul = ul % 1000000; 1103 case 6: 1104 tm.tm_year -= tm.tm_year % 100; 1105 tm.tm_year += ul / 10000; 1106 ul = ul % 10000; 1107 case 4: 1108 tm.tm_mon = (ul / 100) - 1; 1109 ul = ul % 100; 1110 case 2: 1111 tm.tm_mday = ul; 1112 case 0: 1113 break; 1114 default: 1115 return -1; 1116 } 1117 1118 /* sanity check */ 1119 if (tm.tm_year < 70 || tm.tm_mon < 0 || tm.tm_mon > 12 1120 || tm.tm_mday < 1 || tm.tm_mday > 31) 1121 return -1; 1122 1123 if (*t != '\0') { 1124 s = ++t; 1125 ul = strtoul(s, &t, 10); 1126 if (*t != '\0' && !isspace(*t)) 1127 return -1; 1128 1129 switch (t - s) { 1130 case 6: 1131 tm.tm_sec = ul % 100; 1132 ul /= 100; 1133 case 4: 1134 tm.tm_min = ul % 100; 1135 ul /= 100; 1136 case 2: 1137 tm.tm_hour = ul; 1138 case 0: 1139 break; 1140 default: 1141 return -1; 1142 } 1143 1144 /* sanity check */ 1145 if (tm.tm_sec < 0 || tm.tm_sec > 60 || tm.tm_min < 0 1146 || tm.tm_min > 59 || tm.tm_hour < 0 || tm.tm_hour > 23) 1147 return -1; 1148 } 1149 if ((tsecs = mktime(&tm)) == -1) 1150 errx(1, "nonexistent time:\n%s", errline); 1151 return tsecs; 1152} 1153 1154/* physically move file */ 1155static void 1156movefile(char *from, char *to, int perm, int owner_uid, int group_gid) 1157{ 1158 FILE *src, *dst; 1159 int c; 1160 1161 if ((src = fopen(from, "r")) == NULL) 1162 err(1, "can't fopen %s for reading", from); 1163 if ((dst = fopen(to, "w")) == NULL) 1164 err(1, "can't fopen %s for writing", to); 1165 if (fchown(fileno(dst), owner_uid, group_gid)) 1166 err(1, "can't fchown %s", to); 1167 if (fchmod(fileno(dst), perm)) 1168 err(1, "can't fchmod %s", to); 1169 1170 while ((c = getc(src)) != EOF) { 1171 if ((putc(c, dst)) == EOF) 1172 err(1, "error writing to %s", to); 1173 } 1174 1175 if (ferror(src)) 1176 err(1, "error reading from %s", from); 1177 if ((fclose(src)) != 0) 1178 err(1, "can't fclose %s", to); 1179 if ((fclose(dst)) != 0) 1180 err(1, "can't fclose %s", from); 1181 if ((unlink(from)) != 0) 1182 err(1, "can't unlink %s", from); 1183} 1184 1185/* create one or more directory components of a path */ 1186static void 1187createdir(char *dirpart) 1188{ 1189 char *s, *d; 1190 char mkdirpath[MAXPATHLEN]; 1191 struct stat st; 1192 1193 s = dirpart; 1194 d = mkdirpath; 1195 1196 for (;;) { 1197 *d++ = *s++; 1198 if (*s == '/' || *s == '\0') { 1199 *d = '\0'; 1200 if (lstat(mkdirpath, &st)) 1201 mkdir(mkdirpath, 0755); 1202 } 1203 if (*s == '\0') 1204 break; 1205 } 1206} 1207 1208/*- 1209 * Parse a cyclic time specification, the format is as follows: 1210 * 1211 * [Dhh] or [Wd[Dhh]] or [Mdd[Dhh]] 1212 * 1213 * to rotate a logfile cyclic at 1214 * 1215 * - every day (D) within a specific hour (hh) (hh = 0...23) 1216 * - once a week (W) at a specific day (d) OR (d = 0..6, 0 = Sunday) 1217 * - once a month (M) at a specific day (d) (d = 1..31,l|L) 1218 * 1219 * We don't accept a timezone specification; missing fields 1220 * are defaulted to the current date but time zero. 1221 */ 1222static time_t 1223parseDWM(char *s, char *errline) 1224{ 1225 char *t; 1226 time_t tsecs; 1227 struct tm tm, *tmp; 1228 long l; 1229 int nd; 1230 static int mtab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; 1231 int WMseen = 0; 1232 int Dseen = 0; 1233 1234 tmp = localtime(&timenow); 1235 tm = *tmp; 1236 1237 /* set no. of days per month */ 1238 1239 nd = mtab[tm.tm_mon]; 1240 1241 if (tm.tm_mon == 1) { 1242 if (((tm.tm_year + 1900) % 4 == 0) && 1243 ((tm.tm_year + 1900) % 100 != 0) && 1244 ((tm.tm_year + 1900) % 400 == 0)) { 1245 nd++; /* leap year, 29 days in february */ 1246 } 1247 } 1248 tm.tm_hour = tm.tm_min = tm.tm_sec = 0; 1249 1250 for (;;) { 1251 switch (*s) { 1252 case 'D': 1253 if (Dseen) 1254 return -1; 1255 Dseen++; 1256 s++; 1257 l = strtol(s, &t, 10); 1258 if (l < 0 || l > 23) 1259 return -1; 1260 tm.tm_hour = l; 1261 break; 1262 1263 case 'W': 1264 if (WMseen) 1265 return -1; 1266 WMseen++; 1267 s++; 1268 l = strtol(s, &t, 10); 1269 if (l < 0 || l > 6) 1270 return -1; 1271 if (l != tm.tm_wday) { 1272 int save; 1273 1274 if (l < tm.tm_wday) { 1275 save = 6 - tm.tm_wday; 1276 save += (l + 1); 1277 } else { 1278 save = l - tm.tm_wday; 1279 } 1280 1281 tm.tm_mday += save; 1282 1283 if (tm.tm_mday > nd) { 1284 tm.tm_mon++; 1285 tm.tm_mday = tm.tm_mday - nd; 1286 } 1287 } 1288 break; 1289 1290 case 'M': 1291 if (WMseen) 1292 return -1; 1293 WMseen++; 1294 s++; 1295 if (tolower(*s) == 'l') { 1296 tm.tm_mday = nd; 1297 s++; 1298 t = s; 1299 } else { 1300 l = strtol(s, &t, 10); 1301 if (l < 1 || l > 31) 1302 return -1; 1303 1304 if (l > nd) 1305 return -1; 1306 tm.tm_mday = l; 1307 } 1308 break; 1309 1310 default: 1311 return (-1); 1312 break; 1313 } 1314 1315 if (*t == '\0' || isspace(*t)) 1316 break; 1317 else 1318 s = t; 1319 } 1320 if ((tsecs = mktime(&tm)) == -1) 1321 errx(1, "nonexistent time:\n%s", errline); 1322 return tsecs; 1323} 1324