newsyslog.c revision 111820
1130803Smarcel/* 2130803Smarcel * This file contains changes from the Open Software Foundation. 3130803Smarcel */ 4130803Smarcel 5130803Smarcel/* 6130803Smarcel * Copyright 1988, 1989 by the Massachusetts Institute of Technology 7130803Smarcel * 8130803Smarcel * Permission to use, copy, modify, and distribute this software and its 9130803Smarcel * documentation for any purpose and without fee is hereby granted, provided 10130803Smarcel * that the above copyright notice appear in all copies and that both that 11130803Smarcel * copyright notice and this permission notice appear in supporting 12130803Smarcel * documentation, and that the names of M.I.T. and the M.I.T. S.I.P.B. not be 13130803Smarcel * used in advertising or publicity pertaining to distribution of the 14130803Smarcel * software without specific, written prior permission. M.I.T. and the M.I.T. 15130803Smarcel * S.I.P.B. make no representations about the suitability of this software 16130803Smarcel * for any purpose. It is provided "as is" without express or implied 17130803Smarcel * warranty. 18130803Smarcel * 19130803Smarcel */ 20130803Smarcel 21130803Smarcel/* 22130803Smarcel * newsyslog - roll over selected logs at the appropriate time, keeping the a 23130803Smarcel * specified number of backup files around. 24130803Smarcel */ 25130803Smarcel 26130803Smarcel#ifndef lint 27130803Smarcelstatic const char rcsid[] = 28130803Smarcel"$FreeBSD: head/usr.sbin/newsyslog/newsyslog.c 111820 2003-03-03 15:31:35Z gad $"; 29130803Smarcel#endif /* not lint */ 30130803Smarcel 31130803Smarcel#define OSF 32130803Smarcel#ifndef COMPRESS_POSTFIX 33130803Smarcel#define COMPRESS_POSTFIX ".gz" 34130803Smarcel#endif 35130803Smarcel#ifndef BZCOMPRESS_POSTFIX 36130803Smarcel#define BZCOMPRESS_POSTFIX ".bz2" 37130803Smarcel#endif 38130803Smarcel 39130803Smarcel#include <sys/param.h> 40130803Smarcel#include <sys/stat.h> 41130803Smarcel#include <sys/wait.h> 42130803Smarcel 43130803Smarcel#include <ctype.h> 44130803Smarcel#include <err.h> 45130803Smarcel#include <errno.h> 46130803Smarcel#include <fcntl.h> 47130803Smarcel#include <fnmatch.h> 48130803Smarcel#include <glob.h> 49130803Smarcel#include <grp.h> 50130803Smarcel#include <paths.h> 51130803Smarcel#include <pwd.h> 52130803Smarcel#include <signal.h> 53130803Smarcel#include <stdio.h> 54130803Smarcel#include <stdlib.h> 55130803Smarcel#include <string.h> 56130803Smarcel#include <time.h> 57130803Smarcel#include <unistd.h> 58130803Smarcel 59130803Smarcel#include "pathnames.h" 60130803Smarcel 61130803Smarcel/* 62130803Smarcel * Bit-values for the 'flags' parsed from a config-file entry. 63130803Smarcel */ 64130803Smarcel#define CE_COMPACT 0x0001 /* Compact the achived log files with gzip. */ 65130803Smarcel#define CE_BZCOMPACT 0x0002 /* Compact the achived log files with bzip2. */ 66130803Smarcel#define CE_COMPACTWAIT 0x0004 /* wait until compressing one file finishes */ 67130803Smarcel /* before starting the next step. */ 68130803Smarcel#define CE_BINARY 0x0008 /* Logfile is in binary, do not add status */ 69130803Smarcel /* messages to logfile(s) when rotating. */ 70130803Smarcel#define CE_NOSIGNAL 0x0010 /* There is no process to signal when */ 71130803Smarcel /* trimming this file. */ 72130803Smarcel#define CE_TRIMAT 0x0020 /* trim file at a specific time. */ 73130803Smarcel#define CE_GLOB 0x0040 /* name of the log is file name pattern. */ 74130803Smarcel 75130803Smarcel#define MIN_PID 5 /* Don't touch pids lower than this */ 76130803Smarcel#define MAX_PID 99999 /* was lower, see /usr/include/sys/proc.h */ 77130803Smarcel 78130803Smarcel#define kbytes(size) (((size) + 1023) >> 10) 79130803Smarcel 80130803Smarcelstruct conf_entry { 81130803Smarcel char *log; /* Name of the log */ 82130803Smarcel char *pid_file; /* PID file */ 83130803Smarcel char *r_reason; /* The reason this file is being rotated */ 84130803Smarcel int rotate; /* Non-zero if this file should be rotated */ 85130803Smarcel uid_t uid; /* Owner of log */ 86130803Smarcel gid_t gid; /* Group of log */ 87130803Smarcel int numlogs; /* Number of logs to keep */ 88130803Smarcel int size; /* Size cutoff to trigger trimming the log */ 89130803Smarcel int hours; /* Hours between log trimming */ 90130803Smarcel time_t trim_at; /* Specific time to do trimming */ 91130803Smarcel int permissions; /* File permissions on the log */ 92130803Smarcel int flags; /* CE_COMPACT, CE_BZCOMPACT, CE_BINARY */ 93130803Smarcel int sig; /* Signal to send */ 94130803Smarcel int def_cfg; /* Using the <default> rule for this file */ 95130803Smarcel struct conf_entry *next;/* Linked list pointer */ 96130803Smarcel}; 97130803Smarcel 98130803Smarcel#define DEFAULT_MARKER "<default>" 99130803Smarcel 100130803Smarcelint archtodir = 0; /* Archive old logfiles to other directory */ 101130803Smarcelint verbose = 0; /* Print out what's going on */ 102130803Smarcelint needroot = 1; /* Root privs are necessary */ 103130803Smarcelint noaction = 0; /* Don't do anything, just show it */ 104130803Smarcelint nosignal; /* Do not send any signals */ 105130803Smarcelint force = 0; /* Force the trim no matter what */ 106130803Smarcelint rotatereq = 0; /* -R = Always rotate the file(s) as given */ 107130803Smarcel /* on the command (this also requires */ 108130803Smarcel /* that a list of files *are* given on */ 109130803Smarcel /* the run command). */ 110130803Smarcelchar *requestor; /* The name given on a -R request */ 111130803Smarcelchar *archdirname; /* Directory path to old logfiles archive */ 112130803Smarcelconst char *conf; /* Configuration file to use */ 113130803Smarceltime_t timenow; 114130803Smarcel 115130803Smarcelchar hostname[MAXHOSTNAMELEN]; /* hostname */ 116130803Smarcelchar daytime[16]; /* timenow in human readable form */ 117130803Smarcel 118130803Smarcelstatic struct conf_entry *get_worklist(char **files); 119130803Smarcelstatic void parse_file(FILE *cf, const char *cfname, struct conf_entry **work_p, 120130803Smarcel struct conf_entry **defconf_p); 121130803Smarcelstatic char *sob(char *p); 122130803Smarcelstatic char *son(char *p); 123130803Smarcelstatic char *missing_field(char *p, char *errline); 124130803Smarcelstatic void do_entry(struct conf_entry * ent); 125130803Smarcelstatic void free_entry(struct conf_entry *ent); 126130803Smarcelstatic struct conf_entry *init_entry(const char *fname, 127130803Smarcel struct conf_entry *src_entry); 128130803Smarcelstatic void parse_args(int argc, char **argv); 129130803Smarcelstatic void usage(void); 130130803Smarcelstatic void dotrim(const struct conf_entry *ent, char *log, 131130803Smarcel int numdays, int flags); 132130803Smarcelstatic int log_trim(const char *log, const struct conf_entry *log_ent); 133130803Smarcelstatic void compress_log(char *log, int dowait); 134130803Smarcelstatic void bzcompress_log(char *log, int dowait); 135130803Smarcelstatic int sizefile(char *file); 136130803Smarcelstatic int age_old_log(char *file); 137130803Smarcelstatic pid_t get_pid(const char *pid_file); 138130803Smarcelstatic time_t parse8601(char *s, char *errline); 139130803Smarcelstatic void movefile(char *from, char *to, int perm, uid_t owner_uid, 140130803Smarcel gid_t group_gid); 141130803Smarcelstatic void createdir(char *dirpart); 142130803Smarcelstatic time_t parseDWM(char *s, char *errline); 143130803Smarcel 144130803Smarcel/* 145130803Smarcel * All the following are defined to work on an 'int', in the 146130803Smarcel * range 0 to 255, plus EOF. Define wrappers which can take 147130803Smarcel * values of type 'char', either signed or unsigned. 148130803Smarcel */ 149130803Smarcel#define isprintch(Anychar) isprint(((int) Anychar) & 255) 150130803Smarcel#define isspacech(Anychar) isspace(((int) Anychar) & 255) 151130803Smarcel#define tolowerch(Anychar) tolower(((int) Anychar) & 255) 152130803Smarcel 153130803Smarcelint 154130803Smarcelmain(int argc, char **argv) 155130803Smarcel{ 156130803Smarcel struct conf_entry *p, *q; 157130803Smarcel char *savglob; 158130803Smarcel glob_t pglob; 159130803Smarcel int i; 160130803Smarcel 161130803Smarcel parse_args(argc, argv); 162130803Smarcel argc -= optind; 163130803Smarcel argv += optind; 164130803Smarcel 165130803Smarcel if (needroot && getuid() && geteuid()) 166130803Smarcel errx(1, "must have root privs"); 167130803Smarcel p = q = get_worklist(argv); 168130803Smarcel 169130803Smarcel while (p) { 170130803Smarcel if ((p->flags & CE_GLOB) == 0) { 171130803Smarcel do_entry(p); 172130803Smarcel } else { 173130803Smarcel if (verbose > 2) 174130803Smarcel printf("\t+ Processing pattern %s\n", p->log); 175130803Smarcel if (glob(p->log, GLOB_NOCHECK, NULL, &pglob) != 0) { 176130803Smarcel warn("can't expand pattern: %s", p->log); 177130803Smarcel } else { 178130803Smarcel savglob = p->log; 179130803Smarcel for (i = 0; i < pglob.gl_matchc; i++) { 180130803Smarcel p->log = pglob.gl_pathv[i]; 181130803Smarcel do_entry(p); 182130803Smarcel } 183130803Smarcel globfree(&pglob); 184130803Smarcel p->log = savglob; 185130803Smarcel if (verbose > 2) 186130803Smarcel printf("\t+ Done with pattern\n"); 187130803Smarcel } 188130803Smarcel } 189130803Smarcel p = p->next; 190130803Smarcel free_entry(q); 191130803Smarcel q = p; 192130803Smarcel } 193130803Smarcel while (wait(NULL) > 0 || errno == EINTR) 194130803Smarcel ; 195130803Smarcel return (0); 196130803Smarcel} 197130803Smarcel 198130803Smarcelstatic struct conf_entry * 199130803Smarcelinit_entry(const char *fname, struct conf_entry *src_entry) 200130803Smarcel{ 201130803Smarcel struct conf_entry *tempwork; 202130803Smarcel 203130803Smarcel if (verbose > 4) 204130803Smarcel printf("\t--> [creating entry for %s]\n", fname); 205130803Smarcel 206130803Smarcel tempwork = malloc(sizeof(struct conf_entry)); 207130803Smarcel if (tempwork == NULL) 208130803Smarcel err(1, "malloc of conf_entry for %s", fname); 209130803Smarcel 210130803Smarcel tempwork->log = strdup(fname); 211130803Smarcel if (tempwork->log == NULL) 212130803Smarcel err(1, "strdup for %s", fname); 213130803Smarcel 214130803Smarcel if (src_entry != NULL) { 215130803Smarcel tempwork->pid_file = NULL; 216130803Smarcel if (src_entry->pid_file) 217130803Smarcel tempwork->pid_file = strdup(src_entry->pid_file); 218130803Smarcel tempwork->r_reason = NULL; 219130803Smarcel tempwork->rotate = 0; 220130803Smarcel tempwork->uid = src_entry->uid; 221130803Smarcel tempwork->gid = src_entry->gid; 222130803Smarcel tempwork->numlogs = src_entry->numlogs; 223130803Smarcel tempwork->size = src_entry->size; 224130803Smarcel tempwork->hours = src_entry->hours; 225130803Smarcel tempwork->trim_at = src_entry->trim_at; 226130803Smarcel tempwork->permissions = src_entry->permissions; 227130803Smarcel tempwork->flags = src_entry->flags; 228130803Smarcel tempwork->sig = src_entry->sig; 229130803Smarcel tempwork->def_cfg = src_entry->def_cfg; 230130803Smarcel } else { 231130803Smarcel /* Initialize as a "do-nothing" entry */ 232130803Smarcel tempwork->pid_file = NULL; 233130803Smarcel tempwork->r_reason = NULL; 234130803Smarcel tempwork->rotate = 0; 235130803Smarcel tempwork->uid = (uid_t)-1; 236130803Smarcel tempwork->gid = (gid_t)-1; 237130803Smarcel tempwork->numlogs = 1; 238130803Smarcel tempwork->size = -1; 239130803Smarcel tempwork->hours = -1; 240130803Smarcel tempwork->trim_at = (time_t)0; 241130803Smarcel tempwork->permissions = 0; 242130803Smarcel tempwork->flags = 0; 243130803Smarcel tempwork->sig = SIGHUP; 244130803Smarcel tempwork->def_cfg = 0; 245130803Smarcel } 246130803Smarcel tempwork->next = NULL; 247130803Smarcel 248130803Smarcel return (tempwork); 249130803Smarcel} 250130803Smarcel 251130803Smarcelstatic void 252130803Smarcelfree_entry(struct conf_entry *ent) 253130803Smarcel{ 254130803Smarcel 255130803Smarcel if (ent == NULL) 256130803Smarcel return; 257130803Smarcel 258130803Smarcel if (ent->log != NULL) { 259130803Smarcel if (verbose > 4) 260130803Smarcel printf("\t--> [freeing entry for %s]\n", ent->log); 261130803Smarcel free(ent->log); 262130803Smarcel ent->log = NULL; 263130803Smarcel } 264130803Smarcel 265130803Smarcel if (ent->pid_file != NULL) { 266130803Smarcel free(ent->pid_file); 267130803Smarcel ent->pid_file = NULL; 268130803Smarcel } 269130803Smarcel 270130803Smarcel if (ent->r_reason != NULL) { 271130803Smarcel free(ent->r_reason); 272130803Smarcel ent->r_reason = NULL; 273130803Smarcel } 274130803Smarcel 275130803Smarcel free(ent); 276130803Smarcel} 277130803Smarcel 278130803Smarcelstatic void 279130803Smarceldo_entry(struct conf_entry * ent) 280130803Smarcel{ 281130803Smarcel#define REASON_MAX 80 282130803Smarcel int size, modtime; 283130803Smarcel char temp_reason[REASON_MAX]; 284130803Smarcel 285130803Smarcel if (verbose) { 286130803Smarcel if (ent->flags & CE_COMPACT) 287130803Smarcel printf("%s <%dZ>: ", ent->log, ent->numlogs); 288130803Smarcel else if (ent->flags & CE_BZCOMPACT) 289130803Smarcel printf("%s <%dJ>: ", ent->log, ent->numlogs); 290130803Smarcel else 291130803Smarcel printf("%s <%d>: ", ent->log, ent->numlogs); 292130803Smarcel } 293130803Smarcel size = sizefile(ent->log); 294130803Smarcel modtime = age_old_log(ent->log); 295130803Smarcel ent->rotate = 0; 296130803Smarcel if (size < 0) { 297130803Smarcel if (verbose) 298130803Smarcel printf("does not exist.\n"); 299130803Smarcel } else { 300130803Smarcel if (ent->flags & CE_TRIMAT && !force && !rotatereq) { 301130803Smarcel if (timenow < ent->trim_at 302130803Smarcel || difftime(timenow, ent->trim_at) >= 60 * 60) { 303130803Smarcel if (verbose) 304130803Smarcel printf("--> will trim at %s", 305130803Smarcel ctime(&ent->trim_at)); 306130803Smarcel return; 307130803Smarcel } else if (verbose && ent->hours <= 0) { 308130803Smarcel printf("--> time is up\n"); 309130803Smarcel } 310130803Smarcel } 311130803Smarcel if (verbose && (ent->size > 0)) 312130803Smarcel printf("size (Kb): %d [%d] ", size, ent->size); 313130803Smarcel if (verbose && (ent->hours > 0)) 314130803Smarcel printf(" age (hr): %d [%d] ", modtime, ent->hours); 315130803Smarcel 316130803Smarcel /* 317130803Smarcel * Figure out if this logfile needs to be rotated. 318130803Smarcel */ 319130803Smarcel temp_reason[0] = '\0'; 320130803Smarcel if (rotatereq) { 321130803Smarcel ent->rotate = 1; 322130803Smarcel snprintf(temp_reason, REASON_MAX, " due to -R from %s", 323130803Smarcel requestor); 324130803Smarcel } else if (force) { 325130803Smarcel ent->rotate = 1; 326130803Smarcel snprintf(temp_reason, REASON_MAX, " due to -F request"); 327130803Smarcel } else if ((ent->size > 0) && (size >= ent->size)) { 328130803Smarcel ent->rotate = 1; 329130803Smarcel snprintf(temp_reason, REASON_MAX, " due to size>%dK", 330130803Smarcel ent->size); 331130803Smarcel } else if (ent->hours <= 0 && (ent->flags & CE_TRIMAT)) { 332130803Smarcel ent->rotate = 1; 333130803Smarcel } else if ((ent->hours > 0) && ((modtime >= ent->hours) || 334130803Smarcel (modtime < 0))) { 335130803Smarcel ent->rotate = 1; 336130803Smarcel } 337130803Smarcel 338130803Smarcel /* 339130803Smarcel * If the file needs to be rotated, then rotate it. 340130803Smarcel */ 341130803Smarcel if (ent->rotate) { 342130803Smarcel if (temp_reason[0] != '\0') 343130803Smarcel ent->r_reason = strdup(temp_reason); 344130803Smarcel if (verbose) 345130803Smarcel printf("--> trimming log....\n"); 346130803Smarcel if (noaction && !verbose) { 347130803Smarcel if (ent->flags & CE_COMPACT) 348130803Smarcel printf("%s <%dZ>: trimming\n", 349130803Smarcel ent->log, ent->numlogs); 350130803Smarcel else if (ent->flags & CE_BZCOMPACT) 351130803Smarcel printf("%s <%dJ>: trimming\n", 352130803Smarcel ent->log, ent->numlogs); 353130803Smarcel else 354130803Smarcel printf("%s <%d>: trimming\n", 355130803Smarcel ent->log, ent->numlogs); 356130803Smarcel } 357130803Smarcel dotrim(ent, ent->log, ent->numlogs, ent->flags); 358130803Smarcel } else { 359130803Smarcel if (verbose) 360130803Smarcel printf("--> skipping\n"); 361130803Smarcel } 362130803Smarcel } 363130803Smarcel#undef REASON_MAX 364130803Smarcel} 365130803Smarcel 366130803Smarcelstatic void 367130803Smarcelparse_args(int argc, char **argv) 368130803Smarcel{ 369130803Smarcel int ch; 370130803Smarcel char *p; 371130803Smarcel 372130803Smarcel timenow = time(NULL); 373130803Smarcel (void)strncpy(daytime, ctime(&timenow) + 4, 15); 374130803Smarcel daytime[15] = '\0'; 375130803Smarcel 376130803Smarcel /* Let's get our hostname */ 377130803Smarcel (void)gethostname(hostname, sizeof(hostname)); 378130803Smarcel 379130803Smarcel /* Truncate domain */ 380130803Smarcel if ((p = strchr(hostname, '.')) != NULL) 381130803Smarcel *p = '\0'; 382130803Smarcel 383130803Smarcel /* Parse command line options. */ 384130803Smarcel while ((ch = getopt(argc, argv, "a:f:nrsvFR:")) != -1) 385130803Smarcel switch (ch) { 386130803Smarcel case 'a': 387130803Smarcel archtodir++; 388130803Smarcel archdirname = optarg; 389130803Smarcel break; 390130803Smarcel case 'f': 391130803Smarcel conf = optarg; 392130803Smarcel break; 393130803Smarcel case 'n': 394130803Smarcel noaction++; 395130803Smarcel break; 396130803Smarcel case 'r': 397130803Smarcel needroot = 0; 398130803Smarcel break; 399130803Smarcel case 's': 400130803Smarcel nosignal = 1; 401130803Smarcel break; 402130803Smarcel case 'v': 403130803Smarcel verbose++; 404130803Smarcel break; 405130803Smarcel case 'F': 406130803Smarcel force++; 407130803Smarcel break; 408130803Smarcel case 'R': 409130803Smarcel rotatereq++; 410130803Smarcel requestor = strdup(optarg); 411130803Smarcel break; 412130803Smarcel case 'm': /* Used by OpenBSD for "monitor mode" */ 413130803Smarcel default: 414130803Smarcel usage(); 415130803Smarcel /* NOTREACHED */ 416130803Smarcel } 417130803Smarcel 418130803Smarcel if (rotatereq) { 419130803Smarcel if (optind == argc) { 420130803Smarcel warnx("At least one filename must be given when -R is specified."); 421130803Smarcel usage(); 422130803Smarcel /* NOTREACHED */ 423130803Smarcel } 424130803Smarcel /* Make sure "requestor" value is safe for a syslog message. */ 425130803Smarcel for (p = requestor; *p != '\0'; p++) { 426130803Smarcel if (!isprintch(*p) && (*p != '\t')) 427130803Smarcel *p = '.'; 428130803Smarcel } 429130803Smarcel } 430130803Smarcel} 431130803Smarcel 432130803Smarcelstatic void 433130803Smarcelusage(void) 434130803Smarcel{ 435130803Smarcel 436130803Smarcel fprintf(stderr, 437130803Smarcel "usage: newsyslog [-Fnrsv] [-a directory] [-f config-file]\n" 438130803Smarcel " [ [-R requestor] filename ... ]\n"); 439130803Smarcel exit(1); 440130803Smarcel} 441130803Smarcel 442130803Smarcel/* 443130803Smarcel * Parse a configuration file and return a linked list of all the logs 444130803Smarcel * which should be processed. 445130803Smarcel */ 446130803Smarcelstatic struct conf_entry * 447130803Smarcelget_worklist(char **files) 448130803Smarcel{ 449130803Smarcel FILE *f; 450130803Smarcel const char *fname; 451130803Smarcel char **given; 452130803Smarcel struct conf_entry *defconf, *dupent, *ent, *firstnew; 453130803Smarcel struct conf_entry *newlist, *worklist; 454130803Smarcel int gmatch; 455130803Smarcel 456130803Smarcel defconf = worklist = NULL; 457130803Smarcel 458130803Smarcel fname = conf; 459130803Smarcel if (fname == NULL) 460130803Smarcel fname = _PATH_CONF; 461130803Smarcel 462130803Smarcel if (strcmp(fname, "-") != 0) 463130803Smarcel f = fopen(fname, "r"); 464130803Smarcel else { 465130803Smarcel f = stdin; 466130803Smarcel fname = "<stdin>"; 467130803Smarcel } 468130803Smarcel if (!f) 469130803Smarcel err(1, "%s", conf); 470130803Smarcel 471130803Smarcel parse_file(f, fname, &worklist, &defconf); 472130803Smarcel (void) fclose(f); 473130803Smarcel 474130803Smarcel /* 475130803Smarcel * All config-file information has been read in and turned into 476130803Smarcel * a worklist. If there were no specific files given on the run 477130803Smarcel * command, then the work of this routine is done. 478130803Smarcel */ 479130803Smarcel if (*files == NULL) { 480130803Smarcel if (defconf != NULL) 481130803Smarcel free_entry(defconf); 482130803Smarcel return (worklist); 483130803Smarcel /* NOTREACHED */ 484130803Smarcel } 485130803Smarcel 486130803Smarcel /* 487130803Smarcel * If newsyslog was given a specific list of files to process, 488130803Smarcel * it may be that some of those files were not listed in any 489130803Smarcel * config file. Those unlisted files should get the default 490130803Smarcel * rotation action. First, create the default-rotation action 491130803Smarcel * if none was found in a system config file. 492130803Smarcel */ 493130803Smarcel if (defconf == NULL) { 494130803Smarcel defconf = init_entry(DEFAULT_MARKER, NULL); 495130803Smarcel defconf->numlogs = 3; 496130803Smarcel defconf->size = 50; 497130803Smarcel defconf->permissions = S_IRUSR|S_IWUSR; 498130803Smarcel } 499130803Smarcel 500130803Smarcel /* 501130803Smarcel * If newsyslog was run with a list of specific filenames, 502130803Smarcel * then create a new worklist which has only those files in 503130803Smarcel * it, picking up the rotation-rules for those files from 504130803Smarcel * the original worklist. 505130803Smarcel * 506130803Smarcel * XXX - Note that this will copy multiple rules for a single 507130803Smarcel * logfile, if multiple entries are an exact match for 508130803Smarcel * that file. That matches the historic behavior, but do 509130803Smarcel * we want to continue to allow it? If so, it should 510130803Smarcel * probably be handled more intelligently. 511130803Smarcel */ 512130803Smarcel firstnew = newlist = NULL; 513130803Smarcel for (given = files; *given; ++given) { 514130803Smarcel gmatch = 0; 515130803Smarcel /* 516130803Smarcel * First try to find exact-matches for this given file. 517130803Smarcel */ 518130803Smarcel for (ent = worklist; ent; ent = ent->next) { 519130803Smarcel if ((ent->flags & CE_GLOB) != 0) 520130803Smarcel continue; 521130803Smarcel if (strcmp(ent->log, *given) == 0) { 522130803Smarcel gmatch++; 523130803Smarcel dupent = init_entry(*given, ent); 524130803Smarcel if (!firstnew) 525130803Smarcel firstnew = dupent; 526130803Smarcel else 527130803Smarcel newlist->next = dupent; 528130803Smarcel newlist = dupent; 529130803Smarcel } 530130803Smarcel } 531130803Smarcel if (gmatch) { 532130803Smarcel if (verbose > 2) 533130803Smarcel printf("\t+ Matched entry %s\n", *given); 534130803Smarcel continue; 535130803Smarcel } 536130803Smarcel 537130803Smarcel /* 538130803Smarcel * There was no exact-match for this given file, so look 539130803Smarcel * for a "glob" entry which does match. 540130803Smarcel */ 541130803Smarcel for (ent = worklist; ent; ent = ent->next) { 542130803Smarcel if ((ent->flags & CE_GLOB) == 0) 543130803Smarcel continue; 544130803Smarcel if (fnmatch(ent->log, *given, FNM_PATHNAME) == 0) { 545130803Smarcel gmatch++; 546130803Smarcel dupent = init_entry(*given, ent); 547130803Smarcel if (!firstnew) 548130803Smarcel firstnew = dupent; 549130803Smarcel else 550130803Smarcel newlist->next = dupent; 551130803Smarcel newlist = dupent; 552130803Smarcel /* This work entry is *not* a glob! */ 553130803Smarcel dupent->flags &= ~CE_GLOB; 554130803Smarcel /* Only allow a match to one glob-entry */ 555130803Smarcel break; 556130803Smarcel } 557130803Smarcel } 558130803Smarcel if (gmatch) { 559130803Smarcel if (verbose > 2) 560130803Smarcel printf("\t+ Matched %s via %s\n", *given, 561130803Smarcel ent->log); 562130803Smarcel continue; 563130803Smarcel } 564130803Smarcel 565130803Smarcel /* 566130803Smarcel * This given file was not found in any config file, so 567130803Smarcel * add a worklist item based on the default entry. 568130803Smarcel */ 569130803Smarcel if (verbose > 2) 570130803Smarcel printf("\t+ No entry matched %s (will use %s)\n", 571130803Smarcel *given, DEFAULT_MARKER); 572 dupent = init_entry(*given, defconf); 573 if (!firstnew) 574 firstnew = dupent; 575 else 576 newlist->next = dupent; 577 /* Mark that it was *not* found in a config file */ 578 dupent->def_cfg = 1; 579 newlist = dupent; 580 } 581 582 /* 583 * Free all the entries in the original work list, and then 584 * return the new work list. 585 */ 586 while (worklist) { 587 ent = worklist->next; 588 free_entry(worklist); 589 worklist = ent; 590 } 591 592 free_entry(defconf); 593 return (newlist); 594} 595 596/* 597 * Parse a configuration file and update a linked list of all the logs to 598 * process. 599 */ 600static void 601parse_file(FILE *cf, const char *cfname, struct conf_entry **work_p, 602 struct conf_entry **defconf_p) 603{ 604 char line[BUFSIZ], *parse, *q; 605 char *cp, *errline, *group; 606 struct conf_entry *working, *worklist; 607 struct passwd *pwd; 608 struct group *grp; 609 int eol; 610 611 /* 612 * XXX - for now, assume that only one config file will be read, 613 * ie, this routine is only called one time. 614 */ 615 worklist = NULL; 616 617 while (fgets(line, BUFSIZ, cf)) { 618 if ((line[0] == '\n') || (line[0] == '#') || 619 (strlen(line) == 0)) 620 continue; 621 errline = strdup(line); 622 for (cp = line + 1; *cp != '\0'; cp++) { 623 if (*cp != '#') 624 continue; 625 if (*(cp - 1) == '\\') { 626 strcpy(cp - 1, cp); 627 cp--; 628 continue; 629 } 630 *cp = '\0'; 631 break; 632 } 633 634 q = parse = missing_field(sob(line), errline); 635 parse = son(line); 636 if (!*parse) 637 errx(1, "malformed line (missing fields):\n%s", 638 errline); 639 *parse = '\0'; 640 641 working = init_entry(q, NULL); 642 if (strcasecmp(DEFAULT_MARKER, q) == 0) { 643 if (defconf_p == NULL) { 644 warnx("Ignoring entry for %s in %s!", q, 645 cfname); 646 free_entry(working); 647 continue; 648 } else if (*defconf_p != NULL) { 649 warnx("Ignoring duplicate entry for %s!", q); 650 free_entry(working); 651 continue; 652 } 653 *defconf_p = working; 654 } else { 655 if (!*work_p) 656 *work_p = working; 657 else 658 worklist->next = working; 659 worklist = working; 660 } 661 662 q = parse = missing_field(sob(++parse), errline); 663 parse = son(parse); 664 if (!*parse) 665 errx(1, "malformed line (missing fields):\n%s", 666 errline); 667 *parse = '\0'; 668 if ((group = strchr(q, ':')) != NULL || 669 (group = strrchr(q, '.')) != NULL) { 670 *group++ = '\0'; 671 if (*q) { 672 if (!(isnumber(*q))) { 673 if ((pwd = getpwnam(q)) == NULL) 674 errx(1, 675 "error in config file; unknown user:\n%s", 676 errline); 677 working->uid = pwd->pw_uid; 678 } else 679 working->uid = atoi(q); 680 } else 681 working->uid = (uid_t)-1; 682 683 q = group; 684 if (*q) { 685 if (!(isnumber(*q))) { 686 if ((grp = getgrnam(q)) == NULL) 687 errx(1, 688 "error in config file; unknown group:\n%s", 689 errline); 690 working->gid = grp->gr_gid; 691 } else 692 working->gid = atoi(q); 693 } else 694 working->gid = (gid_t)-1; 695 696 q = parse = missing_field(sob(++parse), errline); 697 parse = son(parse); 698 if (!*parse) 699 errx(1, "malformed line (missing fields):\n%s", 700 errline); 701 *parse = '\0'; 702 } else { 703 working->uid = (uid_t)-1; 704 working->gid = (gid_t)-1; 705 } 706 707 if (!sscanf(q, "%o", &working->permissions)) 708 errx(1, "error in config file; bad permissions:\n%s", 709 errline); 710 711 q = parse = missing_field(sob(++parse), errline); 712 parse = son(parse); 713 if (!*parse) 714 errx(1, "malformed line (missing fields):\n%s", 715 errline); 716 *parse = '\0'; 717 if (!sscanf(q, "%d", &working->numlogs) || working->numlogs < 0) 718 errx(1, "error in config file; bad value for count of logs to save:\n%s", 719 errline); 720 721 q = parse = missing_field(sob(++parse), errline); 722 parse = son(parse); 723 if (!*parse) 724 errx(1, "malformed line (missing fields):\n%s", 725 errline); 726 *parse = '\0'; 727 if (isdigit(*q)) 728 working->size = atoi(q); 729 else 730 working->size = -1; 731 732 working->flags = 0; 733 q = parse = missing_field(sob(++parse), errline); 734 parse = son(parse); 735 eol = !*parse; 736 *parse = '\0'; 737 { 738 char *ep; 739 u_long ul; 740 741 ul = strtoul(q, &ep, 10); 742 if (ep == q) 743 working->hours = 0; 744 else if (*ep == '*') 745 working->hours = -1; 746 else if (ul > INT_MAX) 747 errx(1, "interval is too large:\n%s", errline); 748 else 749 working->hours = ul; 750 751 if (*ep != '\0' && *ep != '@' && *ep != '*' && 752 *ep != '$') 753 errx(1, "malformed interval/at:\n%s", errline); 754 if (*ep == '@') { 755 if ((working->trim_at = parse8601(ep + 1, errline)) 756 == (time_t) - 1) 757 errx(1, "malformed at:\n%s", errline); 758 working->flags |= CE_TRIMAT; 759 } else if (*ep == '$') { 760 if ((working->trim_at = parseDWM(ep + 1, errline)) 761 == (time_t) - 1) 762 errx(1, "malformed at:\n%s", errline); 763 working->flags |= CE_TRIMAT; 764 } 765 } 766 767 if (eol) 768 q = NULL; 769 else { 770 q = parse = sob(++parse); /* Optional field */ 771 parse = son(parse); 772 if (!*parse) 773 eol = 1; 774 *parse = '\0'; 775 } 776 777 for (; q && *q && !isspacech(*q); q++) { 778 switch (tolowerch(*q)) { 779 case 'b': 780 working->flags |= CE_BINARY; 781 break; 782 case 'c': /* Used by NetBSD for "CE_CREATE" */ 783 /* 784 * netbsd uses 'c' for "create". We will 785 * temporarily accept it for 'g', because 786 * earlier freebsd versions had a typo 787 * of ('G' || 'c')... 788 */ 789 warnx("Assuming 'g' for 'c' in flags for line:\n%s", 790 errline); 791 /* FALLTHROUGH */ 792 case 'g': 793 working->flags |= CE_GLOB; 794 break; 795 case 'j': 796 working->flags |= CE_BZCOMPACT; 797 break; 798 case 'n': 799 working->flags |= CE_NOSIGNAL; 800 break; 801 case 'w': 802 working->flags |= CE_COMPACTWAIT; 803 break; 804 case 'z': 805 working->flags |= CE_COMPACT; 806 break; 807 case '-': 808 break; 809 case 'f': /* Used by OpenBSD for "CE_FOLLOW" */ 810 case 'm': /* Used by OpenBSD for "CE_MONITOR" */ 811 case 'p': /* Used by NetBSD for "CE_PLAIN0" */ 812 default: 813 errx(1, "illegal flag in config file -- %c", 814 *q); 815 } 816 } 817 818 if (eol) 819 q = NULL; 820 else { 821 q = parse = sob(++parse); /* Optional field */ 822 parse = son(parse); 823 if (!*parse) 824 eol = 1; 825 *parse = '\0'; 826 } 827 828 working->pid_file = NULL; 829 if (q && *q) { 830 if (*q == '/') 831 working->pid_file = strdup(q); 832 else if (isdigit(*q)) 833 goto got_sig; 834 else 835 errx(1, 836 "illegal pid file or signal number in config file:\n%s", 837 errline); 838 } 839 if (eol) 840 q = NULL; 841 else { 842 q = parse = sob(++parse); /* Optional field */ 843 *(parse = son(parse)) = '\0'; 844 } 845 846 working->sig = SIGHUP; 847 if (q && *q) { 848 if (isdigit(*q)) { 849 got_sig: 850 working->sig = atoi(q); 851 } else { 852 err_sig: 853 errx(1, 854 "illegal signal number in config file:\n%s", 855 errline); 856 } 857 if (working->sig < 1 || working->sig >= NSIG) 858 goto err_sig; 859 } 860 861 /* 862 * Finish figuring out what pid-file to use (if any) in 863 * later processing if this logfile needs to be rotated. 864 */ 865 if ((working->flags & CE_NOSIGNAL) == CE_NOSIGNAL) { 866 /* 867 * This config-entry specified 'n' for nosignal, 868 * see if it also specified an explicit pid_file. 869 * This would be a pretty pointless combination. 870 */ 871 if (working->pid_file != NULL) { 872 warnx("Ignoring '%s' because flag 'n' was specified in line:\n%s", 873 working->pid_file, errline); 874 free(working->pid_file); 875 working->pid_file = NULL; 876 } 877 } else if (nosignal) { 878 /* 879 * While this entry might usually signal some 880 * process via the pid-file, newsyslog was run 881 * with '-s', so quietly ignore the pid-file. 882 */ 883 if (working->pid_file != NULL) { 884 free(working->pid_file); 885 working->pid_file = NULL; 886 } 887 } else if (working->pid_file == NULL) { 888 /* 889 * This entry did not specify the 'n' flag, which 890 * means it should signal syslogd unless it had 891 * specified some other pid-file. But we only 892 * try to notify syslog if we are root 893 */ 894 if (needroot) 895 working->pid_file = strdup(_PATH_SYSLOGPID); 896 } 897 898 free(errline); 899 errline = NULL; 900 } 901} 902 903static char * 904missing_field(char *p, char *errline) 905{ 906 907 if (!p || !*p) 908 errx(1, "missing field in config file:\n%s", errline); 909 return (p); 910} 911 912static void 913dotrim(const struct conf_entry *ent, char *log, int numdays, int flags) 914{ 915 char dirpart[MAXPATHLEN], namepart[MAXPATHLEN]; 916 char file1[MAXPATHLEN], file2[MAXPATHLEN]; 917 char zfile1[MAXPATHLEN], zfile2[MAXPATHLEN]; 918 char jfile1[MAXPATHLEN]; 919 char tfile[MAXPATHLEN]; 920 int notified, need_notification, fd, _numdays; 921 struct stat st; 922 pid_t pid; 923 924 if (archtodir) { 925 char *p; 926 927 /* build complete name of archive directory into dirpart */ 928 if (*archdirname == '/') { /* absolute */ 929 strlcpy(dirpart, archdirname, sizeof(dirpart)); 930 } else { /* relative */ 931 /* get directory part of logfile */ 932 strlcpy(dirpart, log, sizeof(dirpart)); 933 if ((p = rindex(dirpart, '/')) == NULL) 934 dirpart[0] = '\0'; 935 else 936 *(p + 1) = '\0'; 937 strlcat(dirpart, archdirname, sizeof(dirpart)); 938 } 939 940 /* check if archive directory exists, if not, create it */ 941 if (lstat(dirpart, &st)) 942 createdir(dirpart); 943 944 /* get filename part of logfile */ 945 if ((p = rindex(log, '/')) == NULL) 946 strlcpy(namepart, log, sizeof(namepart)); 947 else 948 strlcpy(namepart, p + 1, sizeof(namepart)); 949 950 /* name of oldest log */ 951 (void) snprintf(file1, sizeof(file1), "%s/%s.%d", dirpart, 952 namepart, numdays); 953 (void) snprintf(zfile1, sizeof(zfile1), "%s%s", file1, 954 COMPRESS_POSTFIX); 955 snprintf(jfile1, sizeof(jfile1), "%s%s", file1, 956 BZCOMPRESS_POSTFIX); 957 } else { 958 /* name of oldest log */ 959 (void) snprintf(file1, sizeof(file1), "%s.%d", log, numdays); 960 (void) snprintf(zfile1, sizeof(zfile1), "%s%s", file1, 961 COMPRESS_POSTFIX); 962 snprintf(jfile1, sizeof(jfile1), "%s%s", file1, 963 BZCOMPRESS_POSTFIX); 964 } 965 966 if (noaction) { 967 printf("rm -f %s\n", file1); 968 printf("rm -f %s\n", zfile1); 969 printf("rm -f %s\n", jfile1); 970 } else { 971 (void) unlink(file1); 972 (void) unlink(zfile1); 973 (void) unlink(jfile1); 974 } 975 976 /* Move down log files */ 977 _numdays = numdays; /* preserve */ 978 while (numdays--) { 979 980 (void) strlcpy(file2, file1, sizeof(file2)); 981 982 if (archtodir) 983 (void) snprintf(file1, sizeof(file1), "%s/%s.%d", 984 dirpart, namepart, numdays); 985 else 986 (void) snprintf(file1, sizeof(file1), "%s.%d", log, 987 numdays); 988 989 (void) strlcpy(zfile1, file1, sizeof(zfile1)); 990 (void) strlcpy(zfile2, file2, sizeof(zfile2)); 991 if (lstat(file1, &st)) { 992 (void) strlcat(zfile1, COMPRESS_POSTFIX, 993 sizeof(zfile1)); 994 (void) strlcat(zfile2, COMPRESS_POSTFIX, 995 sizeof(zfile2)); 996 if (lstat(zfile1, &st)) { 997 strlcpy(zfile1, file1, sizeof(zfile1)); 998 strlcpy(zfile2, file2, sizeof(zfile2)); 999 strlcat(zfile1, BZCOMPRESS_POSTFIX, 1000 sizeof(zfile1)); 1001 strlcat(zfile2, BZCOMPRESS_POSTFIX, 1002 sizeof(zfile2)); 1003 if (lstat(zfile1, &st)) 1004 continue; 1005 } 1006 } 1007 if (noaction) { 1008 printf("mv %s %s\n", zfile1, zfile2); 1009 printf("chmod %o %s\n", ent->permissions, zfile2); 1010 if (ent->uid != (uid_t)-1 || ent->gid != (gid_t)-1) 1011 printf("chown %u:%u %s\n", 1012 ent->uid, ent->gid, zfile2); 1013 } else { 1014 (void) rename(zfile1, zfile2); 1015 if (chmod(zfile2, ent->permissions)) 1016 warn("can't chmod %s", file2); 1017 if (ent->uid != (uid_t)-1 || ent->gid != (gid_t)-1) 1018 if (chown(zfile2, ent->uid, ent->gid)) 1019 warn("can't chown %s", zfile2); 1020 } 1021 } 1022 if (!noaction && !(flags & CE_BINARY)) { 1023 /* Report the trimming to the old log */ 1024 (void) log_trim(log, ent); 1025 } 1026 1027 if (!_numdays) { 1028 if (noaction) 1029 printf("rm %s\n", log); 1030 else 1031 (void) unlink(log); 1032 } else { 1033 if (noaction) 1034 printf("mv %s to %s\n", log, file1); 1035 else { 1036 if (archtodir) 1037 movefile(log, file1, ent->permissions, ent->uid, 1038 ent->gid); 1039 else 1040 (void) rename(log, file1); 1041 } 1042 } 1043 1044 /* Now move the new log file into place */ 1045 if (noaction) 1046 printf("Start new log...\n"); 1047 else { 1048 strlcpy(tfile, log, sizeof(tfile)); 1049 strlcat(tfile, ".XXXXXX", sizeof(tfile)); 1050 mkstemp(tfile); 1051 fd = creat(tfile, ent->permissions); 1052 if (fd < 0) 1053 err(1, "can't start new log"); 1054 if (ent->uid != (uid_t)-1 || ent->gid != (gid_t)-1) 1055 if (fchown(fd, ent->uid, ent->gid)) 1056 err(1, "can't chown new log file"); 1057 (void) close(fd); 1058 if (!(flags & CE_BINARY)) { 1059 /* Add status message to new log file */ 1060 if (log_trim(tfile, ent)) 1061 err(1, "can't add status message to log"); 1062 } 1063 } 1064 if (noaction) 1065 printf("chmod %o %s...\n", ent->permissions, log); 1066 else { 1067 (void) chmod(tfile, ent->permissions); 1068 if (rename(tfile, log) < 0) { 1069 err(1, "can't start new log"); 1070 (void) unlink(tfile); 1071 } 1072 } 1073 1074 pid = 0; 1075 need_notification = notified = 0; 1076 if (ent->pid_file != NULL) { 1077 need_notification = 1; 1078 pid = get_pid(ent->pid_file); 1079 } 1080 if (pid) { 1081 if (noaction) { 1082 notified = 1; 1083 printf("kill -%d %d\n", ent->sig, (int) pid); 1084 } else if (kill(pid, ent->sig)) 1085 warn("can't notify daemon, pid %d", (int) pid); 1086 else { 1087 notified = 1; 1088 if (verbose) 1089 printf("daemon pid %d notified\n", (int) pid); 1090 } 1091 } 1092 if ((flags & CE_COMPACT) || (flags & CE_BZCOMPACT)) { 1093 if (need_notification && !notified) 1094 warnx( 1095 "log %s not compressed because daemon not notified", 1096 log); 1097 else if (noaction) 1098 printf("Compress %s.0\n", log); 1099 else { 1100 if (notified) { 1101 if (verbose) 1102 printf("small pause to allow daemon to close log\n"); 1103 sleep(10); 1104 } 1105 if (archtodir) { 1106 (void) snprintf(file1, sizeof(file1), "%s/%s", 1107 dirpart, namepart); 1108 if (flags & CE_COMPACT) 1109 compress_log(file1, 1110 flags & CE_COMPACTWAIT); 1111 else if (flags & CE_BZCOMPACT) 1112 bzcompress_log(file1, 1113 flags & CE_COMPACTWAIT); 1114 } else { 1115 if (flags & CE_COMPACT) 1116 compress_log(log, 1117 flags & CE_COMPACTWAIT); 1118 else if (flags & CE_BZCOMPACT) 1119 bzcompress_log(log, 1120 flags & CE_COMPACTWAIT); 1121 } 1122 } 1123 } 1124} 1125 1126/* Log the fact that the logs were turned over */ 1127static int 1128log_trim(const char *log, const struct conf_entry *log_ent) 1129{ 1130 FILE *f; 1131 const char *xtra; 1132 1133 if ((f = fopen(log, "a")) == NULL) 1134 return (-1); 1135 xtra = ""; 1136 if (log_ent->def_cfg) 1137 xtra = " using <default> rule"; 1138 if (log_ent->r_reason != NULL) 1139 fprintf(f, "%s %s newsyslog[%d]: logfile turned over%s%s\n", 1140 daytime, hostname, (int) getpid(), log_ent->r_reason, xtra); 1141 else 1142 fprintf(f, "%s %s newsyslog[%d]: logfile turned over%s\n", 1143 daytime, hostname, (int) getpid(), xtra); 1144 if (fclose(f) == EOF) 1145 err(1, "log_trim: fclose:"); 1146 return (0); 1147} 1148 1149/* Fork of gzip to compress the old log file */ 1150static void 1151compress_log(char *log, int dowait) 1152{ 1153 pid_t pid; 1154 char tmp[MAXPATHLEN]; 1155 1156 while (dowait && (wait(NULL) > 0 || errno == EINTR)) 1157 ; 1158 (void) snprintf(tmp, sizeof(tmp), "%s.0", log); 1159 pid = fork(); 1160 if (pid < 0) 1161 err(1, "gzip fork"); 1162 else if (!pid) { 1163 (void) execl(_PATH_GZIP, _PATH_GZIP, "-f", tmp, (char *)0); 1164 err(1, _PATH_GZIP); 1165 } 1166} 1167 1168/* Fork of bzip2 to compress the old log file */ 1169static void 1170bzcompress_log(char *log, int dowait) 1171{ 1172 pid_t pid; 1173 char tmp[MAXPATHLEN]; 1174 1175 while (dowait && (wait(NULL) > 0 || errno == EINTR)) 1176 ; 1177 snprintf(tmp, sizeof(tmp), "%s.0", log); 1178 pid = fork(); 1179 if (pid < 0) 1180 err(1, "bzip2 fork"); 1181 else if (!pid) { 1182 execl(_PATH_BZIP2, _PATH_BZIP2, "-f", tmp, (char *)0); 1183 err(1, _PATH_BZIP2); 1184 } 1185} 1186 1187/* Return size in kilobytes of a file */ 1188static int 1189sizefile(char *file) 1190{ 1191 struct stat sb; 1192 1193 if (stat(file, &sb) < 0) 1194 return (-1); 1195 return (kbytes(dbtob(sb.st_blocks))); 1196} 1197 1198/* Return the age of old log file (file.0) */ 1199static int 1200age_old_log(char *file) 1201{ 1202 struct stat sb; 1203 char tmp[MAXPATHLEN + sizeof(".0") + sizeof(COMPRESS_POSTFIX) + 1]; 1204 1205 if (archtodir) { 1206 char *p; 1207 1208 /* build name of archive directory into tmp */ 1209 if (*archdirname == '/') { /* absolute */ 1210 strlcpy(tmp, archdirname, sizeof(tmp)); 1211 } else { /* relative */ 1212 /* get directory part of logfile */ 1213 strlcpy(tmp, file, sizeof(tmp)); 1214 if ((p = rindex(tmp, '/')) == NULL) 1215 tmp[0] = '\0'; 1216 else 1217 *(p + 1) = '\0'; 1218 strlcat(tmp, archdirname, sizeof(tmp)); 1219 } 1220 1221 strlcat(tmp, "/", sizeof(tmp)); 1222 1223 /* get filename part of logfile */ 1224 if ((p = rindex(file, '/')) == NULL) 1225 strlcat(tmp, file, sizeof(tmp)); 1226 else 1227 strlcat(tmp, p + 1, sizeof(tmp)); 1228 } else { 1229 (void) strlcpy(tmp, file, sizeof(tmp)); 1230 } 1231 1232 if (stat(strcat(tmp, ".0"), &sb) < 0) 1233 if (stat(strcat(tmp, COMPRESS_POSTFIX), &sb) < 0) 1234 return (-1); 1235 return ((int)(timenow - sb.st_mtime + 1800) / 3600); 1236} 1237 1238static pid_t 1239get_pid(const char *pid_file) 1240{ 1241 FILE *f; 1242 char line[BUFSIZ]; 1243 pid_t pid = 0; 1244 1245 if ((f = fopen(pid_file, "r")) == NULL) 1246 warn("can't open %s pid file to restart a daemon", 1247 pid_file); 1248 else { 1249 if (fgets(line, BUFSIZ, f)) { 1250 pid = atol(line); 1251 if (pid < MIN_PID || pid > MAX_PID) { 1252 warnx("preposterous process number: %d", 1253 (int)pid); 1254 pid = 0; 1255 } 1256 } else 1257 warn("can't read %s pid file to restart a daemon", 1258 pid_file); 1259 (void) fclose(f); 1260 } 1261 return (pid); 1262} 1263 1264/* Skip Over Blanks */ 1265static char * 1266sob(char *p) 1267{ 1268 while (p && *p && isspace(*p)) 1269 p++; 1270 return (p); 1271} 1272 1273/* Skip Over Non-Blanks */ 1274static char * 1275son(char *p) 1276{ 1277 while (p && *p && !isspace(*p)) 1278 p++; 1279 return (p); 1280} 1281 1282/* 1283 * Parse a limited subset of ISO 8601. The specific format is as follows: 1284 * 1285 * [CC[YY[MM[DD]]]][THH[MM[SS]]] (where `T' is the literal letter) 1286 * 1287 * We don't accept a timezone specification; missing fields (including timezone) 1288 * are defaulted to the current date but time zero. 1289 */ 1290static time_t 1291parse8601(char *s, char *errline) 1292{ 1293 char *t; 1294 time_t tsecs; 1295 struct tm tm, *tmp; 1296 u_long ul; 1297 1298 tmp = localtime(&timenow); 1299 tm = *tmp; 1300 1301 tm.tm_hour = tm.tm_min = tm.tm_sec = 0; 1302 1303 ul = strtoul(s, &t, 10); 1304 if (*t != '\0' && *t != 'T') 1305 return (-1); 1306 1307 /* 1308 * Now t points either to the end of the string (if no time was 1309 * provided) or to the letter `T' which separates date and time in 1310 * ISO 8601. The pointer arithmetic is the same for either case. 1311 */ 1312 switch (t - s) { 1313 case 8: 1314 tm.tm_year = ((ul / 1000000) - 19) * 100; 1315 ul = ul % 1000000; 1316 case 6: 1317 tm.tm_year -= tm.tm_year % 100; 1318 tm.tm_year += ul / 10000; 1319 ul = ul % 10000; 1320 case 4: 1321 tm.tm_mon = (ul / 100) - 1; 1322 ul = ul % 100; 1323 case 2: 1324 tm.tm_mday = ul; 1325 case 0: 1326 break; 1327 default: 1328 return (-1); 1329 } 1330 1331 /* sanity check */ 1332 if (tm.tm_year < 70 || tm.tm_mon < 0 || tm.tm_mon > 12 1333 || tm.tm_mday < 1 || tm.tm_mday > 31) 1334 return (-1); 1335 1336 if (*t != '\0') { 1337 s = ++t; 1338 ul = strtoul(s, &t, 10); 1339 if (*t != '\0' && !isspace(*t)) 1340 return (-1); 1341 1342 switch (t - s) { 1343 case 6: 1344 tm.tm_sec = ul % 100; 1345 ul /= 100; 1346 case 4: 1347 tm.tm_min = ul % 100; 1348 ul /= 100; 1349 case 2: 1350 tm.tm_hour = ul; 1351 case 0: 1352 break; 1353 default: 1354 return (-1); 1355 } 1356 1357 /* sanity check */ 1358 if (tm.tm_sec < 0 || tm.tm_sec > 60 || tm.tm_min < 0 1359 || tm.tm_min > 59 || tm.tm_hour < 0 || tm.tm_hour > 23) 1360 return (-1); 1361 } 1362 if ((tsecs = mktime(&tm)) == -1) 1363 errx(1, "nonexistent time:\n%s", errline); 1364 return (tsecs); 1365} 1366 1367/* physically move file */ 1368static void 1369movefile(char *from, char *to, int perm, uid_t owner_uid, gid_t group_gid) 1370{ 1371 FILE *src, *dst; 1372 int c; 1373 1374 if ((src = fopen(from, "r")) == NULL) 1375 err(1, "can't fopen %s for reading", from); 1376 if ((dst = fopen(to, "w")) == NULL) 1377 err(1, "can't fopen %s for writing", to); 1378 if (owner_uid != (uid_t)-1 || group_gid != (gid_t)-1) { 1379 if (fchown(fileno(dst), owner_uid, group_gid)) 1380 err(1, "can't fchown %s", to); 1381 } 1382 if (fchmod(fileno(dst), perm)) 1383 err(1, "can't fchmod %s", to); 1384 1385 while ((c = getc(src)) != EOF) { 1386 if ((putc(c, dst)) == EOF) 1387 err(1, "error writing to %s", to); 1388 } 1389 1390 if (ferror(src)) 1391 err(1, "error reading from %s", from); 1392 if ((fclose(src)) != 0) 1393 err(1, "can't fclose %s", to); 1394 if ((fclose(dst)) != 0) 1395 err(1, "can't fclose %s", from); 1396 if ((unlink(from)) != 0) 1397 err(1, "can't unlink %s", from); 1398} 1399 1400/* create one or more directory components of a path */ 1401static void 1402createdir(char *dirpart) 1403{ 1404 int res; 1405 char *s, *d; 1406 char mkdirpath[MAXPATHLEN]; 1407 struct stat st; 1408 1409 s = dirpart; 1410 d = mkdirpath; 1411 1412 for (;;) { 1413 *d++ = *s++; 1414 if (*s != '/' && *s != '\0') 1415 continue; 1416 *d = '\0'; 1417 res = lstat(mkdirpath, &st); 1418 if (res != 0) { 1419 if (noaction) { 1420 printf("mkdir %s\n", mkdirpath); 1421 } else { 1422 res = mkdir(mkdirpath, 0755); 1423 if (res != 0) 1424 err(1, "Error on mkdir(\"%s\") for -a", 1425 mkdirpath); 1426 } 1427 } 1428 if (*s == '\0') 1429 break; 1430 } 1431 if (verbose) 1432 printf("created directory '%s' for -a\n", dirpart); 1433} 1434 1435/*- 1436 * Parse a cyclic time specification, the format is as follows: 1437 * 1438 * [Dhh] or [Wd[Dhh]] or [Mdd[Dhh]] 1439 * 1440 * to rotate a logfile cyclic at 1441 * 1442 * - every day (D) within a specific hour (hh) (hh = 0...23) 1443 * - once a week (W) at a specific day (d) OR (d = 0..6, 0 = Sunday) 1444 * - once a month (M) at a specific day (d) (d = 1..31,l|L) 1445 * 1446 * We don't accept a timezone specification; missing fields 1447 * are defaulted to the current date but time zero. 1448 */ 1449static time_t 1450parseDWM(char *s, char *errline) 1451{ 1452 char *t; 1453 time_t tsecs; 1454 struct tm tm, *tmp; 1455 long l; 1456 int nd; 1457 static int mtab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; 1458 int WMseen = 0; 1459 int Dseen = 0; 1460 1461 tmp = localtime(&timenow); 1462 tm = *tmp; 1463 1464 /* set no. of days per month */ 1465 1466 nd = mtab[tm.tm_mon]; 1467 1468 if (tm.tm_mon == 1) { 1469 if (((tm.tm_year + 1900) % 4 == 0) && 1470 ((tm.tm_year + 1900) % 100 != 0) && 1471 ((tm.tm_year + 1900) % 400 == 0)) { 1472 nd++; /* leap year, 29 days in february */ 1473 } 1474 } 1475 tm.tm_hour = tm.tm_min = tm.tm_sec = 0; 1476 1477 for (;;) { 1478 switch (*s) { 1479 case 'D': 1480 if (Dseen) 1481 return (-1); 1482 Dseen++; 1483 s++; 1484 l = strtol(s, &t, 10); 1485 if (l < 0 || l > 23) 1486 return (-1); 1487 tm.tm_hour = l; 1488 break; 1489 1490 case 'W': 1491 if (WMseen) 1492 return (-1); 1493 WMseen++; 1494 s++; 1495 l = strtol(s, &t, 10); 1496 if (l < 0 || l > 6) 1497 return (-1); 1498 if (l != tm.tm_wday) { 1499 int save; 1500 1501 if (l < tm.tm_wday) { 1502 save = 6 - tm.tm_wday; 1503 save += (l + 1); 1504 } else { 1505 save = l - tm.tm_wday; 1506 } 1507 1508 tm.tm_mday += save; 1509 1510 if (tm.tm_mday > nd) { 1511 tm.tm_mon++; 1512 tm.tm_mday = tm.tm_mday - nd; 1513 } 1514 } 1515 break; 1516 1517 case 'M': 1518 if (WMseen) 1519 return (-1); 1520 WMseen++; 1521 s++; 1522 if (tolower(*s) == 'l') { 1523 tm.tm_mday = nd; 1524 s++; 1525 t = s; 1526 } else { 1527 l = strtol(s, &t, 10); 1528 if (l < 1 || l > 31) 1529 return (-1); 1530 1531 if (l > nd) 1532 return (-1); 1533 tm.tm_mday = l; 1534 } 1535 break; 1536 1537 default: 1538 return (-1); 1539 break; 1540 } 1541 1542 if (*t == '\0' || isspace(*t)) 1543 break; 1544 else 1545 s = t; 1546 } 1547 if ((tsecs = mktime(&tm)) == -1) 1548 errx(1, "nonexistent time:\n%s", errline); 1549 return (tsecs); 1550} 1551