newsyslog.c revision 95999
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 95999 2002-05-03 20:53:37Z maxim $"; 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 3930160Scharnier#include <ctype.h> 4030160Scharnier#include <err.h> 4195999Smaxim#include <errno.h> 4230160Scharnier#include <fcntl.h> 4330160Scharnier#include <grp.h> 4443071Swollman#include <paths.h> 4530160Scharnier#include <pwd.h> 4630160Scharnier#include <signal.h> 4713244Sgraichen#include <stdio.h> 4813244Sgraichen#include <stdlib.h> 4913244Sgraichen#include <string.h> 5043071Swollman#include <time.h> 5116240Salex#include <unistd.h> 5213244Sgraichen#include <sys/types.h> 5313244Sgraichen#include <sys/stat.h> 5413244Sgraichen#include <sys/param.h> 5513244Sgraichen#include <sys/wait.h> 5613244Sgraichen 5743071Swollman#include "pathnames.h" 5843071Swollman 5913244Sgraichen#define kbytes(size) (((size) + 1023) >> 10) 6059004Shm 6113244Sgraichen#ifdef _IBMR2 6213244Sgraichen/* Calculates (db * DEV_BSIZE) */ 6359003Shm#define dbtob(db) ((unsigned)(db) << UBSHIFT) 6413244Sgraichen#endif 6513244Sgraichen 6659003Shm#define CE_COMPACT 1 /* Compact the achived log files */ 6759003Shm#define CE_BINARY 2 /* Logfile is in binary, don't add */ 6880638Sobrien#define CE_BZCOMPACT 8 /* Compact the achived log files with bzip2 */ 6959004Shm /* status messages */ 7043071Swollman#define CE_TRIMAT 4 /* trim at a specific time */ 7143071Swollman 7213244Sgraichen#define NONE -1 7359003Shm 7413244Sgraichenstruct conf_entry { 7559003Shm char *log; /* Name of the log */ 7659003Shm char *pid_file; /* PID file */ 7759003Shm int uid; /* Owner of log */ 7859003Shm int gid; /* Group of log */ 7959003Shm int numlogs; /* Number of logs to keep */ 8059003Shm int size; /* Size cutoff to trigger trimming the log */ 8159003Shm int hours; /* Hours between log trimming */ 8259003Shm time_t trim_at; /* Specific time to do trimming */ 8359003Shm int permissions; /* File permissions on the log */ 8480646Sobrien int flags; /* CE_COMPACT, CE_BZCOMPACT, CE_BINARY */ 8559003Shm int sig; /* Signal to send */ 8659003Shm struct conf_entry *next;/* Linked list pointer */ 8713244Sgraichen}; 8813244Sgraichen 8959004Shmint archtodir = 0; /* Archive old logfiles to other directory */ 9059003Shmint verbose = 0; /* Print out what's going on */ 9159003Shmint needroot = 1; /* Root privs are necessary */ 9259003Shmint noaction = 0; /* Don't do anything, just show it */ 9359003Shmint force = 0; /* Force the trim no matter what */ 9459004Shmchar *archdirname; /* Directory path to old logfiles archive */ 9580640Sobrienconst char *conf = _PATH_CONF; /* Configuration file to use */ 9659003Shmtime_t timenow; 9759003Shm 9825443Sache#define MIN_PID 5 9959003Shm#define MAX_PID 99999 /* was lower, see /usr/include/sys/proc.h */ 10071299Sjedgarchar hostname[MAXHOSTNAMELEN]; /* hostname */ 10159003Shmchar *daytime; /* timenow in human readable form */ 10213244Sgraichen 10360373Sdesstatic struct conf_entry *parse_file(char **files); 10416240Salexstatic char *sob(char *p); 10516240Salexstatic char *son(char *p); 10659003Shmstatic char *missing_field(char *p, char *errline); 10759003Shmstatic void do_entry(struct conf_entry * ent); 10859003Shmstatic void PRS(int argc, char **argv); 10980640Sobrienstatic void usage(void); 11080646Sobrienstatic void dotrim(char *log, const char *pid_file, int numdays, int falgs, 11180646Sobrien int perm, int owner_uid, int group_gid, int sig); 11216240Salexstatic int log_trim(char *log); 11316240Salexstatic void compress_log(char *log); 11480638Sobrienstatic void bzcompress_log(char *log); 11516240Salexstatic int sizefile(char *file); 11616240Salexstatic int age_old_log(char *file); 11780640Sobrienstatic pid_t get_pid(const char *pid_file); 11893659Scjcstatic time_t parse8601(char *s, char *errline); 11980646Sobrienstatic void movefile(char *from, char *to, int perm, int owner_uid, 12080646Sobrien int group_gid); 12159004Shmstatic void createdir(char *dirpart); 12293659Scjcstatic time_t parseDWM(char *s, char *errline); 12313244Sgraichen 12459004Shmint 12559004Shmmain(int argc, char **argv) 12613244Sgraichen{ 12759003Shm struct conf_entry *p, *q; 12825443Sache 12959003Shm PRS(argc, argv); 13059003Shm if (needroot && getuid() && geteuid()) 13159003Shm errx(1, "must have root privs"); 13260373Sdes p = q = parse_file(argv + optind); 13359003Shm 13459003Shm while (p) { 13559003Shm do_entry(p); 13659003Shm p = p->next; 13759003Shm free((char *) q); 13859003Shm q = p; 13959003Shm } 14095999Smaxim while (wait(NULL) > 0 || errno == EINTR) 14195999Smaxim ; 14259003Shm return (0); 14313244Sgraichen} 14413244Sgraichen 14559004Shmstatic void 14659004Shmdo_entry(struct conf_entry * ent) 14713244Sgraichen{ 14859003Shm int size, modtime; 14980640Sobrien const char *pid_file; 15059003Shm 15159003Shm if (verbose) { 15259003Shm if (ent->flags & CE_COMPACT) 15359003Shm printf("%s <%dZ>: ", ent->log, ent->numlogs); 15480638Sobrien else if (ent->flags & CE_BZCOMPACT) 15580638Sobrien printf("%s <%dJ>: ", ent->log, ent->numlogs); 15659003Shm else 15759003Shm printf("%s <%d>: ", ent->log, ent->numlogs); 15859003Shm } 15959003Shm size = sizefile(ent->log); 16059003Shm modtime = age_old_log(ent->log); 16159003Shm if (size < 0) { 16259003Shm if (verbose) 16359003Shm printf("does not exist.\n"); 16459003Shm } else { 16590240Sroam if (ent->flags & CE_TRIMAT && !force) { 16643071Swollman if (timenow < ent->trim_at 16759003Shm || difftime(timenow, ent->trim_at) >= 60 * 60) { 16843071Swollman if (verbose) 16943071Swollman printf("--> will trim at %s", 17059003Shm ctime(&ent->trim_at)); 17143071Swollman return; 17243071Swollman } else if (verbose && ent->hours <= 0) { 17343071Swollman printf("--> time is up\n"); 17443071Swollman } 17543071Swollman } 17659003Shm if (verbose && (ent->size > 0)) 17759003Shm printf("size (Kb): %d [%d] ", size, ent->size); 17859003Shm if (verbose && (ent->hours > 0)) 17959003Shm printf(" age (hr): %d [%d] ", modtime, ent->hours); 18059003Shm if (force || ((ent->size > 0) && (size >= ent->size)) || 18143071Swollman (ent->hours <= 0 && (ent->flags & CE_TRIMAT)) || 18259003Shm ((ent->hours > 0) && ((modtime >= ent->hours) 18359003Shm || (modtime < 0)))) { 18459003Shm if (verbose) 18559003Shm printf("--> trimming log....\n"); 18659003Shm if (noaction && !verbose) { 18759003Shm if (ent->flags & CE_COMPACT) 18859003Shm printf("%s <%dZ>: trimming\n", 18959003Shm ent->log, ent->numlogs); 19080638Sobrien else if (ent->flags & CE_BZCOMPACT) 19180638Sobrien printf("%s <%dJ>: trimming\n", 19280638Sobrien ent->log, ent->numlogs); 19359003Shm else 19459003Shm printf("%s <%d>: trimming\n", 19559003Shm ent->log, ent->numlogs); 19659003Shm } 19735920Shoek if (ent->pid_file) { 19835920Shoek pid_file = ent->pid_file; 19935920Shoek } else { 20035920Shoek /* Only try to notify syslog if we are root */ 20135920Shoek if (needroot) 20243071Swollman pid_file = _PATH_SYSLOGPID; 20335920Shoek else 20435920Shoek pid_file = NULL; 20535920Shoek } 20659003Shm dotrim(ent->log, pid_file, ent->numlogs, 20780646Sobrien ent->flags, ent->permissions, ent->uid, ent->gid, 20880646Sobrien ent->sig); 20959003Shm } else { 21059003Shm if (verbose) 21159003Shm printf("--> skipping\n"); 21259003Shm } 21359003Shm } 21413244Sgraichen} 21513244Sgraichen 21659004Shmstatic void 21759004ShmPRS(int argc, char **argv) 21813244Sgraichen{ 21959003Shm int c; 22059003Shm char *p; 22113244Sgraichen 22259003Shm timenow = time((time_t *) 0); 22359003Shm daytime = ctime(&timenow) + 4; 22459003Shm daytime[15] = '\0'; 22513244Sgraichen 22659003Shm /* Let's get our hostname */ 22759003Shm (void) gethostname(hostname, sizeof(hostname)); 22813244Sgraichen 22913244Sgraichen /* Truncate domain */ 23016240Salex if ((p = strchr(hostname, '.'))) { 23113244Sgraichen *p = '\0'; 23213244Sgraichen } 23359004Shm while ((c = getopt(argc, argv, "nrvFf:a:t:")) != -1) 23459003Shm switch (c) { 23559003Shm case 'n': 23659003Shm noaction++; 23735920Shoek break; 23859004Shm case 'a': 23959004Shm archtodir++; 24059004Shm archdirname = optarg; 24159004Shm break; 24259003Shm case 'r': 24359003Shm needroot = 0; 24459003Shm break; 24559003Shm case 'v': 24659003Shm verbose++; 24759003Shm break; 24859003Shm case 'f': 24959003Shm conf = optarg; 25059003Shm break; 25134584Spst case 'F': 25234584Spst force++; 25334584Spst break; 25459003Shm default: 25559003Shm usage(); 25659003Shm } 25743071Swollman} 25813244Sgraichen 25959004Shmstatic void 26059004Shmusage(void) 26113244Sgraichen{ 26280646Sobrien 26380646Sobrien fprintf(stderr, 26480646Sobrien "usage: newsyslog [-Fnrv] [-f config-file] [-a directory]\n"); 26559003Shm exit(1); 26613244Sgraichen} 26713244Sgraichen 26859004Shm/* 26959004Shm * Parse a configuration file and return a linked list of all the logs to 27059004Shm * process 27113244Sgraichen */ 27259003Shmstatic struct conf_entry * 27360373Sdesparse_file(char **files) 27413244Sgraichen{ 27559003Shm FILE *f; 27659003Shm char line[BUFSIZ], *parse, *q; 27759003Shm char *errline, *group; 27860373Sdes char **p; 27980646Sobrien struct conf_entry *first, *working; 28059003Shm struct passwd *pass; 28159003Shm struct group *grp; 28225518Sbrian int eol; 28313244Sgraichen 28480646Sobrien first = working = NULL; 28580646Sobrien 28659003Shm if (strcmp(conf, "-")) 28759003Shm f = fopen(conf, "r"); 28859003Shm else 28959003Shm f = stdin; 29059003Shm if (!f) 29159003Shm err(1, "%s", conf); 29259003Shm while (fgets(line, BUFSIZ, f)) { 29359003Shm if ((line[0] == '\n') || (line[0] == '#')) 29459003Shm continue; 29559003Shm errline = strdup(line); 29660373Sdes 29760373Sdes q = parse = missing_field(sob(line), errline); 29860373Sdes parse = son(line); 29960373Sdes if (!*parse) 30080646Sobrien errx(1, "malformed line (missing fields):\n%s", 30180646Sobrien errline); 30260373Sdes *parse = '\0'; 30360373Sdes 30460373Sdes if (*files) { 30560373Sdes for (p = files; *p; ++p) 30660373Sdes if (strcmp(*p, q) == 0) 30760373Sdes break; 30860373Sdes if (!*p) 30960373Sdes continue; 31060373Sdes } 31160373Sdes 31259003Shm if (!first) { 31380646Sobrien if ((working = malloc(sizeof(struct conf_entry))) == 31480646Sobrien NULL) 31571299Sjedgar err(1, "malloc"); 31659003Shm first = working; 31759003Shm } else { 31880646Sobrien if ((working->next = malloc(sizeof(struct conf_entry))) 31980646Sobrien == NULL) 32071299Sjedgar err(1, "malloc"); 32159003Shm working = working->next; 32259003Shm } 32371299Sjedgar if ((working->log = strdup(q)) == NULL) 32471299Sjedgar err(1, "strdup"); 32513244Sgraichen 32659003Shm q = parse = missing_field(sob(++parse), errline); 32759003Shm parse = son(parse); 32825518Sbrian if (!*parse) 32980646Sobrien errx(1, "malformed line (missing fields):\n%s", 33080646Sobrien errline); 33159003Shm *parse = '\0'; 33259003Shm if ((group = strchr(q, ':')) != NULL || 33359003Shm (group = strrchr(q, '.')) != NULL) { 33459003Shm *group++ = '\0'; 33559003Shm if (*q) { 33659003Shm if (!(isnumber(*q))) { 33759003Shm if ((pass = getpwnam(q)) == NULL) 33859003Shm errx(1, 33980646Sobrien "error in config file; unknown user:\n%s", 34059003Shm errline); 34159003Shm working->uid = pass->pw_uid; 34259003Shm } else 34359003Shm working->uid = atoi(q); 34459003Shm } else 34559003Shm working->uid = NONE; 34613244Sgraichen 34759003Shm q = group; 34859003Shm if (*q) { 34959003Shm if (!(isnumber(*q))) { 35059003Shm if ((grp = getgrnam(q)) == NULL) 35159003Shm errx(1, 35280646Sobrien "error in config file; unknown group:\n%s", 35359003Shm errline); 35459003Shm working->gid = grp->gr_gid; 35559003Shm } else 35659003Shm working->gid = atoi(q); 35759003Shm } else 35859003Shm working->gid = NONE; 35913244Sgraichen 36059003Shm q = parse = missing_field(sob(++parse), errline); 36159003Shm parse = son(parse); 36259003Shm if (!*parse) 36380646Sobrien errx(1, "malformed line (missing fields):\n%s", 36480646Sobrien errline); 36559003Shm *parse = '\0'; 36659003Shm } else 36759003Shm working->uid = working->gid = NONE; 36859003Shm 36959003Shm if (!sscanf(q, "%o", &working->permissions)) 37059003Shm errx(1, "error in config file; bad permissions:\n%s", 37159003Shm errline); 37259003Shm 37359003Shm q = parse = missing_field(sob(++parse), errline); 37459003Shm parse = son(parse); 37525518Sbrian if (!*parse) 37680646Sobrien errx(1, "malformed line (missing fields):\n%s", 37780646Sobrien errline); 37859003Shm *parse = '\0'; 37959003Shm if (!sscanf(q, "%d", &working->numlogs)) 38059003Shm errx(1, "error in config file; bad number:\n%s", 38159003Shm errline); 38213244Sgraichen 38359003Shm q = parse = missing_field(sob(++parse), errline); 38459003Shm parse = son(parse); 38525518Sbrian if (!*parse) 38680646Sobrien errx(1, "malformed line (missing fields):\n%s", 38780646Sobrien errline); 38859003Shm *parse = '\0'; 38959003Shm if (isdigit(*q)) 39059003Shm working->size = atoi(q); 39159003Shm else 39259003Shm working->size = -1; 39359003Shm 39459003Shm working->flags = 0; 39559003Shm q = parse = missing_field(sob(++parse), errline); 39659003Shm parse = son(parse); 39725518Sbrian eol = !*parse; 39859003Shm *parse = '\0'; 39943071Swollman { 40059003Shm char *ep; 40159003Shm u_long ul; 40213244Sgraichen 40343071Swollman ul = strtoul(q, &ep, 10); 40443071Swollman if (ep == q) 40543071Swollman working->hours = 0; 40643071Swollman else if (*ep == '*') 40743071Swollman working->hours = -1; 40843071Swollman else if (ul > INT_MAX) 40943071Swollman errx(1, "interval is too large:\n%s", errline); 41043071Swollman else 41143071Swollman working->hours = ul; 41243071Swollman 41380646Sobrien if (*ep != '\0' && *ep != '@' && *ep != '*' && 41480646Sobrien *ep != '$') 41543071Swollman errx(1, "malformed interval/at:\n%s", errline); 41643071Swollman if (*ep == '@') { 41793659Scjc if ((working->trim_at = parse8601(ep + 1, errline)) 41859003Shm == (time_t) - 1) 41943071Swollman errx(1, "malformed at:\n%s", errline); 42043071Swollman working->flags |= CE_TRIMAT; 42159004Shm } else if (*ep == '$') { 42293659Scjc if ((working->trim_at = parseDWM(ep + 1, errline)) 42359004Shm == (time_t) - 1) 42459004Shm errx(1, "malformed at:\n%s", errline); 42559004Shm working->flags |= CE_TRIMAT; 42643071Swollman } 42743071Swollman } 42843071Swollman 42925518Sbrian if (eol) 43059003Shm q = NULL; 43125518Sbrian else { 43259003Shm q = parse = sob(++parse); /* Optional field */ 43359003Shm parse = son(parse); 43459003Shm if (!*parse) 43559003Shm eol = 1; 43659003Shm *parse = '\0'; 43725518Sbrian } 43825443Sache 43959003Shm while (q && *q && !isspace(*q)) { 44059003Shm if ((*q == 'Z') || (*q == 'z')) 44159003Shm working->flags |= CE_COMPACT; 44280638Sobrien else if ((*q == 'J') || (*q == 'j')) 44380638Sobrien working->flags |= CE_BZCOMPACT; 44459003Shm else if ((*q == 'B') || (*q == 'b')) 44559003Shm working->flags |= CE_BINARY; 44659003Shm else if (*q != '-') 44780646Sobrien errx(1, "illegal flag in config file -- %c", 44880646Sobrien *q); 44959003Shm q++; 45059003Shm } 45159003Shm 45225518Sbrian if (eol) 45359003Shm q = NULL; 45425518Sbrian else { 45559003Shm q = parse = sob(++parse); /* Optional field */ 45659003Shm parse = son(parse); 45759003Shm if (!*parse) 45859003Shm eol = 1; 45959003Shm *parse = '\0'; 46025518Sbrian } 46125443Sache 46225443Sache working->pid_file = NULL; 46325443Sache if (q && *q) { 46425443Sache if (*q == '/') 46525443Sache working->pid_file = strdup(q); 46636817Sache else if (isdigit(*q)) 46736817Sache goto got_sig; 46859003Shm else 46980646Sobrien errx(1, 47080646Sobrien "illegal pid file or signal number in config file:\n%s", 47180646Sobrien errline); 47225443Sache } 47336817Sache if (eol) 47459003Shm q = NULL; 47536817Sache else { 47659003Shm q = parse = sob(++parse); /* Optional field */ 47759003Shm *(parse = son(parse)) = '\0'; 47836817Sache } 47936817Sache 48036817Sache working->sig = SIGHUP; 48136817Sache if (q && *q) { 48236817Sache if (isdigit(*q)) { 48359003Shm got_sig: 48436817Sache working->sig = atoi(q); 48536817Sache } else { 48659003Shm err_sig: 48780646Sobrien errx(1, 48880646Sobrien "illegal signal number in config file:\n%s", 48980646Sobrien errline); 49036817Sache } 49136817Sache if (working->sig < 1 || working->sig >= NSIG) 49236817Sache goto err_sig; 49336817Sache } 49459003Shm free(errline); 49559003Shm } 49659003Shm if (working) 49759003Shm working->next = (struct conf_entry *) NULL; 49859003Shm (void) fclose(f); 49959003Shm return (first); 50013244Sgraichen} 50113244Sgraichen 50259003Shmstatic char * 50359004Shmmissing_field(char *p, char *errline) 50413244Sgraichen{ 50580646Sobrien 50659003Shm if (!p || !*p) 50759003Shm errx(1, "missing field in config file:\n%s", errline); 50859003Shm return (p); 50913244Sgraichen} 51013244Sgraichen 51159004Shmstatic void 51280640Sobriendotrim(char *log, const char *pid_file, int numdays, int flags, int perm, 51359004Shm int owner_uid, int group_gid, int sig) 51413244Sgraichen{ 51571299Sjedgar char dirpart[MAXPATHLEN], namepart[MAXPATHLEN]; 51671299Sjedgar char file1[MAXPATHLEN], file2[MAXPATHLEN]; 51771299Sjedgar char zfile1[MAXPATHLEN], zfile2[MAXPATHLEN]; 51880638Sobrien char jfile1[MAXPATHLEN]; 51994352Ssheldonh char tfile[MAXPATHLEN]; 52059003Shm int notified, need_notification, fd, _numdays; 52159003Shm struct stat st; 52259003Shm pid_t pid; 52313244Sgraichen 52413244Sgraichen#ifdef _IBMR2 52559004Shm /* 52659004Shm * AIX 3.1 has a broken fchown- if the owner_uid is -1, it will 52759004Shm * actually change it to be owned by uid -1, instead of leaving it 52859004Shm * as is, as it is supposed to. 52959004Shm */ 53059003Shm if (owner_uid == -1) 53159003Shm owner_uid = geteuid(); 53213244Sgraichen#endif 53313244Sgraichen 53459004Shm if (archtodir) { 53559004Shm char *p; 53613244Sgraichen 53759004Shm /* build complete name of archive directory into dirpart */ 53859004Shm if (*archdirname == '/') { /* absolute */ 53971299Sjedgar strlcpy(dirpart, archdirname, sizeof(dirpart)); 54059004Shm } else { /* relative */ 54159004Shm /* get directory part of logfile */ 54271299Sjedgar strlcpy(dirpart, log, sizeof(dirpart)); 54359004Shm if ((p = rindex(dirpart, '/')) == NULL) 54459004Shm dirpart[0] = '\0'; 54559004Shm else 54659004Shm *(p + 1) = '\0'; 54771299Sjedgar strlcat(dirpart, archdirname, sizeof(dirpart)); 54859004Shm } 54959004Shm 55059004Shm /* check if archive directory exists, if not, create it */ 55159004Shm if (lstat(dirpart, &st)) 55259004Shm createdir(dirpart); 55359004Shm 55459004Shm /* get filename part of logfile */ 55559004Shm if ((p = rindex(log, '/')) == NULL) 55671299Sjedgar strlcpy(namepart, log, sizeof(namepart)); 55759004Shm else 55871299Sjedgar strlcpy(namepart, p + 1, sizeof(namepart)); 55959004Shm 56059004Shm /* name of oldest log */ 56180646Sobrien (void) snprintf(file1, sizeof(file1), "%s/%s.%d", dirpart, 56280646Sobrien namepart, numdays); 56371299Sjedgar (void) snprintf(zfile1, sizeof(zfile1), "%s%s", file1, 56471299Sjedgar COMPRESS_POSTFIX); 56580638Sobrien snprintf(jfile1, sizeof(jfile1), "%s%s", file1, 56680638Sobrien BZCOMPRESS_POSTFIX); 56759004Shm } else { 56859004Shm /* name of oldest log */ 56971299Sjedgar (void) snprintf(file1, sizeof(file1), "%s.%d", log, numdays); 57071299Sjedgar (void) snprintf(zfile1, sizeof(zfile1), "%s%s", file1, 57171299Sjedgar COMPRESS_POSTFIX); 57280638Sobrien snprintf(jfile1, sizeof(jfile1), "%s%s", file1, 57380638Sobrien BZCOMPRESS_POSTFIX); 57459004Shm } 57559004Shm 57659003Shm if (noaction) { 57759003Shm printf("rm -f %s\n", file1); 57859003Shm printf("rm -f %s\n", zfile1); 57980638Sobrien printf("rm -f %s\n", jfile1); 58059003Shm } else { 58159003Shm (void) unlink(file1); 58259003Shm (void) unlink(zfile1); 58380638Sobrien (void) unlink(jfile1); 58459003Shm } 58513244Sgraichen 58659003Shm /* Move down log files */ 58718188Sjkh _numdays = numdays; /* preserve */ 58859003Shm while (numdays--) { 58959004Shm 59071299Sjedgar (void) strlcpy(file2, file1, sizeof(file2)); 59159004Shm 59259004Shm if (archtodir) 59380646Sobrien (void) snprintf(file1, sizeof(file1), "%s/%s.%d", 59480646Sobrien dirpart, namepart, numdays); 59559004Shm else 59680646Sobrien (void) snprintf(file1, sizeof(file1), "%s.%d", log, 59780646Sobrien numdays); 59859004Shm 59971299Sjedgar (void) strlcpy(zfile1, file1, sizeof(zfile1)); 60071299Sjedgar (void) strlcpy(zfile2, file2, sizeof(zfile2)); 60159003Shm if (lstat(file1, &st)) { 60280646Sobrien (void) strlcat(zfile1, COMPRESS_POSTFIX, 60380646Sobrien sizeof(zfile1)); 60480646Sobrien (void) strlcat(zfile2, COMPRESS_POSTFIX, 60580646Sobrien sizeof(zfile2)); 60680638Sobrien if (lstat(zfile1, &st)) { 60780638Sobrien strlcpy(zfile1, file1, sizeof(zfile1)); 60880638Sobrien strlcpy(zfile2, file2, sizeof(zfile2)); 60980638Sobrien strlcat(zfile1, BZCOMPRESS_POSTFIX, 61080638Sobrien sizeof(zfile1)); 61180638Sobrien strlcat(zfile2, BZCOMPRESS_POSTFIX, 61280638Sobrien sizeof(zfile2)); 61380638Sobrien if (lstat(zfile1, &st)) 61480638Sobrien continue; 61580638Sobrien } 61659003Shm } 61759003Shm if (noaction) { 61859003Shm printf("mv %s %s\n", zfile1, zfile2); 61959003Shm printf("chmod %o %s\n", perm, zfile2); 62080684Sobrien printf("chown %d:%d %s\n", 62159003Shm owner_uid, group_gid, zfile2); 62259003Shm } else { 62359003Shm (void) rename(zfile1, zfile2); 62459003Shm (void) chmod(zfile2, perm); 62559003Shm (void) chown(zfile2, owner_uid, group_gid); 62659003Shm } 62759003Shm } 62859003Shm if (!noaction && !(flags & CE_BINARY)) 62959003Shm (void) log_trim(log); /* Report the trimming to the old log */ 63013244Sgraichen 63118188Sjkh if (!_numdays) { 63218075Sjkh if (noaction) 63359003Shm printf("rm %s\n", log); 63418075Sjkh else 63559003Shm (void) unlink(log); 63659003Shm } else { 63718075Sjkh if (noaction) 63859003Shm printf("mv %s to %s\n", log, file1); 63959004Shm else { 64059004Shm if (archtodir) 64180646Sobrien movefile(log, file1, perm, owner_uid, 64280646Sobrien group_gid); 64359004Shm else 64459004Shm (void) rename(log, file1); 64559004Shm } 64618075Sjkh } 64718075Sjkh 64859003Shm if (noaction) 64959003Shm printf("Start new log..."); 65059003Shm else { 65194352Ssheldonh strlcpy(tfile, log, sizeof(tfile)); 65294352Ssheldonh strlcat(tfile, ".XXXXXX", sizeof(tfile)); 65394352Ssheldonh mkstemp(tfile); 65494352Ssheldonh fd = creat(tfile, perm); 65559003Shm if (fd < 0) 65659003Shm err(1, "can't start new log"); 65759003Shm if (fchown(fd, owner_uid, group_gid)) 65859003Shm err(1, "can't chmod new log file"); 65959003Shm (void) close(fd); 66059003Shm if (!(flags & CE_BINARY)) 66194352Ssheldonh if (log_trim(tfile)) /* Add status message */ 66259003Shm err(1, "can't add status message to log"); 66359003Shm } 66459003Shm if (noaction) 66559003Shm printf("chmod %o %s...\n", perm, log); 66694352Ssheldonh else { 66794352Ssheldonh (void) chmod(tfile, perm); 66894352Ssheldonh if (rename(tfile, log) < 0) { 66994352Ssheldonh err(1, "can't start new log"); 67094352Ssheldonh (void) unlink(tfile); 67194352Ssheldonh } 67294352Ssheldonh } 67325443Sache 67425443Sache pid = 0; 67525443Sache need_notification = notified = 0; 67625443Sache if (pid_file != NULL) { 67725443Sache need_notification = 1; 67825443Sache pid = get_pid(pid_file); 67925443Sache } 68025443Sache if (pid) { 68125443Sache if (noaction) { 68225443Sache notified = 1; 68359003Shm printf("kill -%d %d\n", sig, (int) pid); 68459003Shm } else if (kill(pid, sig)) 68559003Shm warn("can't notify daemon, pid %d", (int) pid); 68625443Sache else { 68725443Sache notified = 1; 68825443Sache if (verbose) 68959003Shm printf("daemon pid %d notified\n", (int) pid); 69025443Sache } 69125443Sache } 69280638Sobrien if ((flags & CE_COMPACT) || (flags & CE_BZCOMPACT)) { 69325443Sache if (need_notification && !notified) 69480646Sobrien warnx( 69580646Sobrien "log %s not compressed because daemon not notified", 69680646Sobrien log); 69725443Sache else if (noaction) 69859003Shm printf("Compress %s.0\n", log); 69925443Sache else { 70025443Sache if (notified) { 70125443Sache if (verbose) 70225443Sache printf("small pause to allow daemon to close log\n"); 70331460Sache sleep(10); 70425443Sache } 70559004Shm if (archtodir) { 70680646Sobrien (void) snprintf(file1, sizeof(file1), "%s/%s", 70780646Sobrien dirpart, namepart); 70880638Sobrien if (flags & CE_COMPACT) 70980638Sobrien compress_log(file1); 71080638Sobrien else if (flags & CE_BZCOMPACT) 71180638Sobrien bzcompress_log(file1); 71259004Shm } else { 71380638Sobrien if (flags & CE_COMPACT) 71480638Sobrien compress_log(log); 71580638Sobrien else if (flags & CE_BZCOMPACT) 71680638Sobrien bzcompress_log(log); 71759004Shm } 71825443Sache } 71959003Shm } 72013244Sgraichen} 72113244Sgraichen 72213244Sgraichen/* Log the fact that the logs were turned over */ 72359004Shmstatic int 72459004Shmlog_trim(char *log) 72513244Sgraichen{ 72659003Shm FILE *f; 72759003Shm 72859003Shm if ((f = fopen(log, "a")) == NULL) 72959003Shm return (-1); 73059003Shm fprintf(f, "%s %s newsyslog[%d]: logfile turned over\n", 73159003Shm daytime, hostname, (int) getpid()); 73259003Shm if (fclose(f) == EOF) 73359003Shm err(1, "log_trim: fclose:"); 73459003Shm return (0); 73513244Sgraichen} 73613244Sgraichen 73759004Shm/* Fork of gzip to compress the old log file */ 73859004Shmstatic void 73959004Shmcompress_log(char *log) 74013244Sgraichen{ 74159003Shm pid_t pid; 74271299Sjedgar char tmp[MAXPATHLEN]; 74359003Shm 74471299Sjedgar (void) snprintf(tmp, sizeof(tmp), "%s.0", log); 74525443Sache pid = fork(); 74659003Shm if (pid < 0) 74780638Sobrien err(1, "gzip fork"); 74859003Shm else if (!pid) { 74979452Sbrian (void) execl(_PATH_GZIP, _PATH_GZIP, "-f", tmp, (char *)0); 75043071Swollman err(1, _PATH_GZIP); 75159003Shm } 75213244Sgraichen} 75313244Sgraichen 75480638Sobrien/* Fork of bzip2 to compress the old log file */ 75580638Sobrienstatic void 75680638Sobrienbzcompress_log(char *log) 75780638Sobrien{ 75880638Sobrien pid_t pid; 75980638Sobrien char tmp[MAXPATHLEN]; 76080638Sobrien 76180638Sobrien snprintf(tmp, sizeof(tmp), "%s.0", log); 76280638Sobrien pid = fork(); 76380638Sobrien if (pid < 0) 76480638Sobrien err(1, "bzip2 fork"); 76580638Sobrien else if (!pid) { 76686360Sobrien execl(_PATH_BZIP2, _PATH_BZIP2, "-f", tmp, (char *)0); 76780638Sobrien err(1, _PATH_BZIP2); 76880638Sobrien } 76980638Sobrien} 77080638Sobrien 77113244Sgraichen/* Return size in kilobytes of a file */ 77259004Shmstatic int 77359004Shmsizefile(char *file) 77413244Sgraichen{ 77559003Shm struct stat sb; 77613244Sgraichen 77759003Shm if (stat(file, &sb) < 0) 77859003Shm return (-1); 77959003Shm return (kbytes(dbtob(sb.st_blocks))); 78013244Sgraichen} 78113244Sgraichen 78213244Sgraichen/* Return the age of old log file (file.0) */ 78359004Shmstatic int 78459004Shmage_old_log(char *file) 78513244Sgraichen{ 78659003Shm struct stat sb; 78759003Shm char tmp[MAXPATHLEN + sizeof(".0") + sizeof(COMPRESS_POSTFIX) + 1]; 78813244Sgraichen 78959004Shm if (archtodir) { 79059004Shm char *p; 79159004Shm 79259004Shm /* build name of archive directory into tmp */ 79359004Shm if (*archdirname == '/') { /* absolute */ 79471299Sjedgar strlcpy(tmp, archdirname, sizeof(tmp)); 79559004Shm } else { /* relative */ 79659004Shm /* get directory part of logfile */ 79771299Sjedgar strlcpy(tmp, file, sizeof(tmp)); 79859004Shm if ((p = rindex(tmp, '/')) == NULL) 79959004Shm tmp[0] = '\0'; 80059004Shm else 80159004Shm *(p + 1) = '\0'; 80271299Sjedgar strlcat(tmp, archdirname, sizeof(tmp)); 80359004Shm } 80459004Shm 80571299Sjedgar strlcat(tmp, "/", sizeof(tmp)); 80659004Shm 80759004Shm /* get filename part of logfile */ 80859004Shm if ((p = rindex(file, '/')) == NULL) 80971299Sjedgar strlcat(tmp, file, sizeof(tmp)); 81059004Shm else 81171299Sjedgar strlcat(tmp, p + 1, sizeof(tmp)); 81259004Shm } else { 81371299Sjedgar (void) strlcpy(tmp, file, sizeof(tmp)); 81459004Shm } 81559004Shm 81659003Shm if (stat(strcat(tmp, ".0"), &sb) < 0) 81759003Shm if (stat(strcat(tmp, COMPRESS_POSTFIX), &sb) < 0) 81859003Shm return (-1); 81959003Shm return ((int) (timenow - sb.st_mtime + 1800) / 3600); 82013244Sgraichen} 82113244Sgraichen 82259004Shmstatic pid_t 82380640Sobrienget_pid(const char *pid_file) 82425443Sache{ 82525443Sache FILE *f; 82659003Shm char line[BUFSIZ]; 82725443Sache pid_t pid = 0; 82813244Sgraichen 82959003Shm if ((f = fopen(pid_file, "r")) == NULL) 83025443Sache warn("can't open %s pid file to restart a daemon", 83159003Shm pid_file); 83225443Sache else { 83359003Shm if (fgets(line, BUFSIZ, f)) { 83425443Sache pid = atol(line); 83525443Sache if (pid < MIN_PID || pid > MAX_PID) { 83680646Sobrien warnx("preposterous process number: %d", 83780646Sobrien (int)pid); 83825443Sache pid = 0; 83925443Sache } 84025443Sache } else 84125443Sache warn("can't read %s pid file to restart a daemon", 84259003Shm pid_file); 84359003Shm (void) fclose(f); 84425443Sache } 84525443Sache return pid; 84625443Sache} 84725443Sache 84813244Sgraichen/* Skip Over Blanks */ 84959003Shmchar * 85059004Shmsob(char *p) 85113244Sgraichen{ 85259003Shm while (p && *p && isspace(*p)) 85359003Shm p++; 85459003Shm return (p); 85513244Sgraichen} 85613244Sgraichen 85713244Sgraichen/* Skip Over Non-Blanks */ 85859003Shmchar * 85959004Shmson(char *p) 86013244Sgraichen{ 86159003Shm while (p && *p && !isspace(*p)) 86259003Shm p++; 86359003Shm return (p); 86413244Sgraichen} 86543071Swollman 86643071Swollman/* 86759004Shm * Parse a limited subset of ISO 8601. The specific format is as follows: 86843071Swollman * 86959004Shm * [CC[YY[MM[DD]]]][THH[MM[SS]]] (where `T' is the literal letter) 87043071Swollman * 87159004Shm * We don't accept a timezone specification; missing fields (including timezone) 87259004Shm * are defaulted to the current date but time zero. 87343071Swollman */ 87443071Swollmanstatic time_t 87593659Scjcparse8601(char *s, char *errline) 87643071Swollman{ 87759003Shm char *t; 87893659Scjc time_t tsecs; 87959003Shm struct tm tm, *tmp; 88059003Shm u_long ul; 88143071Swollman 88243071Swollman tmp = localtime(&timenow); 88343071Swollman tm = *tmp; 88443071Swollman 88543071Swollman tm.tm_hour = tm.tm_min = tm.tm_sec = 0; 88643071Swollman 88743071Swollman ul = strtoul(s, &t, 10); 88843071Swollman if (*t != '\0' && *t != 'T') 88943071Swollman return -1; 89043071Swollman 89143071Swollman /* 89259003Shm * Now t points either to the end of the string (if no time was 89359003Shm * provided) or to the letter `T' which separates date and time in 89459003Shm * ISO 8601. The pointer arithmetic is the same for either case. 89543071Swollman */ 89643071Swollman switch (t - s) { 89743071Swollman case 8: 89843071Swollman tm.tm_year = ((ul / 1000000) - 19) * 100; 89943071Swollman ul = ul % 1000000; 90043071Swollman case 6: 90180666Swollman tm.tm_year -= tm.tm_year % 100; 90243071Swollman tm.tm_year += ul / 10000; 90343071Swollman ul = ul % 10000; 90443071Swollman case 4: 90543071Swollman tm.tm_mon = (ul / 100) - 1; 90643071Swollman ul = ul % 100; 90743071Swollman case 2: 90843071Swollman tm.tm_mday = ul; 90943071Swollman case 0: 91043071Swollman break; 91143071Swollman default: 91243071Swollman return -1; 91343071Swollman } 91443071Swollman 91543071Swollman /* sanity check */ 91643071Swollman if (tm.tm_year < 70 || tm.tm_mon < 0 || tm.tm_mon > 12 91743071Swollman || tm.tm_mday < 1 || tm.tm_mday > 31) 91843071Swollman return -1; 91943071Swollman 92043071Swollman if (*t != '\0') { 92143071Swollman s = ++t; 92243071Swollman ul = strtoul(s, &t, 10); 92343071Swollman if (*t != '\0' && !isspace(*t)) 92443071Swollman return -1; 92543071Swollman 92643071Swollman switch (t - s) { 92743071Swollman case 6: 92843071Swollman tm.tm_sec = ul % 100; 92943071Swollman ul /= 100; 93043071Swollman case 4: 93143071Swollman tm.tm_min = ul % 100; 93243071Swollman ul /= 100; 93343071Swollman case 2: 93443071Swollman tm.tm_hour = ul; 93543071Swollman case 0: 93643071Swollman break; 93743071Swollman default: 93843071Swollman return -1; 93943071Swollman } 94043071Swollman 94143071Swollman /* sanity check */ 94243071Swollman if (tm.tm_sec < 0 || tm.tm_sec > 60 || tm.tm_min < 0 94343071Swollman || tm.tm_min > 59 || tm.tm_hour < 0 || tm.tm_hour > 23) 94443071Swollman return -1; 94543071Swollman } 94693659Scjc if ((tsecs = mktime(&tm)) == -1) 94793659Scjc errx(1, "nonexistent time:\n%s", errline); 94893659Scjc return tsecs; 94943071Swollman} 95059004Shm 95159004Shm/* physically move file */ 95259004Shmstatic void 95359004Shmmovefile(char *from, char *to, int perm, int owner_uid, int group_gid) 95459004Shm{ 95559004Shm FILE *src, *dst; 95659004Shm int c; 95759004Shm 95859004Shm if ((src = fopen(from, "r")) == NULL) 95959004Shm err(1, "can't fopen %s for reading", from); 96059004Shm if ((dst = fopen(to, "w")) == NULL) 96159004Shm err(1, "can't fopen %s for writing", to); 96259004Shm if (fchown(fileno(dst), owner_uid, group_gid)) 96359004Shm err(1, "can't fchown %s", to); 96459004Shm if (fchmod(fileno(dst), perm)) 96559004Shm err(1, "can't fchmod %s", to); 96659004Shm 96759004Shm while ((c = getc(src)) != EOF) { 96859004Shm if ((putc(c, dst)) == EOF) 96959004Shm err(1, "error writing to %s", to); 97059004Shm } 97159004Shm 97259004Shm if (ferror(src)) 97359004Shm err(1, "error reading from %s", from); 97459004Shm if ((fclose(src)) != 0) 97559004Shm err(1, "can't fclose %s", to); 97659004Shm if ((fclose(dst)) != 0) 97759004Shm err(1, "can't fclose %s", from); 97859004Shm if ((unlink(from)) != 0) 97959004Shm err(1, "can't unlink %s", from); 98059004Shm} 98159004Shm 98259004Shm/* create one or more directory components of a path */ 98359004Shmstatic void 98459004Shmcreatedir(char *dirpart) 98559004Shm{ 98659004Shm char *s, *d; 98771299Sjedgar char mkdirpath[MAXPATHLEN]; 98859004Shm struct stat st; 98959004Shm 99059004Shm s = dirpart; 99159004Shm d = mkdirpath; 99259004Shm 99359004Shm for (;;) { 99459004Shm *d++ = *s++; 99559004Shm if (*s == '/' || *s == '\0') { 99659004Shm *d = '\0'; 99759004Shm if (lstat(mkdirpath, &st)) 99859004Shm mkdir(mkdirpath, 0755); 99959004Shm } 100059004Shm if (*s == '\0') 100159004Shm break; 100259004Shm } 100359004Shm} 100459004Shm 100559004Shm/*- 100659004Shm * Parse a cyclic time specification, the format is as follows: 100759004Shm * 100859004Shm * [Dhh] or [Wd[Dhh]] or [Mdd[Dhh]] 100959004Shm * 101059004Shm * to rotate a logfile cyclic at 101159004Shm * 101259004Shm * - every day (D) within a specific hour (hh) (hh = 0...23) 101359004Shm * - once a week (W) at a specific day (d) OR (d = 0..6, 0 = Sunday) 101459004Shm * - once a month (M) at a specific day (d) (d = 1..31,l|L) 101559004Shm * 101659004Shm * We don't accept a timezone specification; missing fields 101759004Shm * are defaulted to the current date but time zero. 101859004Shm */ 101959004Shmstatic time_t 102093659ScjcparseDWM(char *s, char *errline) 102159004Shm{ 102259004Shm char *t; 102393659Scjc time_t tsecs; 102459004Shm struct tm tm, *tmp; 102580742Sobrien long l; 102659004Shm int nd; 102759004Shm static int mtab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; 102859004Shm int WMseen = 0; 102959004Shm int Dseen = 0; 103059004Shm 103159004Shm tmp = localtime(&timenow); 103259004Shm tm = *tmp; 103359004Shm 103459004Shm /* set no. of days per month */ 103559004Shm 103659004Shm nd = mtab[tm.tm_mon]; 103759004Shm 103859004Shm if (tm.tm_mon == 1) { 103959004Shm if (((tm.tm_year + 1900) % 4 == 0) && 104059004Shm ((tm.tm_year + 1900) % 100 != 0) && 104159004Shm ((tm.tm_year + 1900) % 400 == 0)) { 104259004Shm nd++; /* leap year, 29 days in february */ 104359004Shm } 104459004Shm } 104559004Shm tm.tm_hour = tm.tm_min = tm.tm_sec = 0; 104659004Shm 104759004Shm for (;;) { 104859004Shm switch (*s) { 104959004Shm case 'D': 105059004Shm if (Dseen) 105159004Shm return -1; 105259004Shm Dseen++; 105359004Shm s++; 105480742Sobrien l = strtol(s, &t, 10); 105580742Sobrien if (l < 0 || l > 23) 105659004Shm return -1; 105780742Sobrien tm.tm_hour = l; 105859004Shm break; 105959004Shm 106059004Shm case 'W': 106159004Shm if (WMseen) 106259004Shm return -1; 106359004Shm WMseen++; 106459004Shm s++; 106580742Sobrien l = strtol(s, &t, 10); 106680742Sobrien if (l < 0 || l > 6) 106759004Shm return -1; 106880742Sobrien if (l != tm.tm_wday) { 106959004Shm int save; 107059004Shm 107180742Sobrien if (l < tm.tm_wday) { 107259004Shm save = 6 - tm.tm_wday; 107380742Sobrien save += (l + 1); 107459004Shm } else { 107580742Sobrien save = l - tm.tm_wday; 107659004Shm } 107759004Shm 107859004Shm tm.tm_mday += save; 107959004Shm 108059004Shm if (tm.tm_mday > nd) { 108159004Shm tm.tm_mon++; 108259004Shm tm.tm_mday = tm.tm_mday - nd; 108359004Shm } 108459004Shm } 108559004Shm break; 108659004Shm 108759004Shm case 'M': 108859004Shm if (WMseen) 108959004Shm return -1; 109059004Shm WMseen++; 109159004Shm s++; 109259004Shm if (tolower(*s) == 'l') { 109359004Shm tm.tm_mday = nd; 109459004Shm s++; 109559004Shm t = s; 109659004Shm } else { 109780742Sobrien l = strtol(s, &t, 10); 109880742Sobrien if (l < 1 || l > 31) 109959004Shm return -1; 110059004Shm 110180742Sobrien if (l > nd) 110259004Shm return -1; 110380742Sobrien tm.tm_mday = l; 110459004Shm } 110559004Shm break; 110659004Shm 110759004Shm default: 110859004Shm return (-1); 110959004Shm break; 111059004Shm } 111159004Shm 111259004Shm if (*t == '\0' || isspace(*t)) 111359004Shm break; 111459004Shm else 111559004Shm s = t; 111659004Shm } 111793659Scjc if ((tsecs = mktime(&tm)) == -1) 111893659Scjc errx(1, "nonexistent time:\n%s", errline); 111993659Scjc return tsecs; 112059004Shm} 1121