newsyslog.c revision 208648
11541Srgrimes/*- 21541Srgrimes * ------+---------+---------+-------- + --------+---------+---------+---------* 31541Srgrimes * This file includes significant modifications done by: 41541Srgrimes * Copyright (c) 2003, 2004 - Garance Alistair Drosehn <gad@FreeBSD.org>. 51541Srgrimes * All rights reserved. 61541Srgrimes * 71541Srgrimes * Redistribution and use in source and binary forms, with or without 812623Sphk * modification, are permitted provided that the following conditions 912623Sphk * are met: 1012623Sphk * 1. Redistributions of source code must retain the above copyright 111541Srgrimes * notice, this list of conditions and the following disclaimer. 121541Srgrimes * 2. Redistributions in binary form must reproduce the above copyright 131541Srgrimes * notice, this list of conditions and the following disclaimer in the 141541Srgrimes * documentation and/or other materials provided with the distribution. 151541Srgrimes * 161541Srgrimes * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 171541Srgrimes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 181541Srgrimes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 191541Srgrimes * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 201541Srgrimes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 211541Srgrimes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 221541Srgrimes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 231541Srgrimes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 241541Srgrimes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 251541Srgrimes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 261541Srgrimes * SUCH DAMAGE. 271541Srgrimes * 281541Srgrimes * ------+---------+---------+-------- + --------+---------+---------+---------* 291541Srgrimes */ 301541Srgrimes 311541Srgrimes/* 321541Srgrimes * This file contains changes from the Open Software Foundation. 331541Srgrimes */ 341541Srgrimes 351541Srgrimes/* 361541Srgrimes * Copyright 1988, 1989 by the Massachusetts Institute of Technology 371541Srgrimes * 38116182Sobrien * Permission to use, copy, modify, and distribute this software and its 39116182Sobrien * documentation for any purpose and without fee is hereby granted, provided 40116182Sobrien * that the above copyright notice appear in all copies and that both that 41224159Srwatson * copyright notice and this permission notice appear in supporting 4231778Seivind * documentation, and that the names of M.I.T. and the M.I.T. S.I.P.B. not be 43189707Sjhb * used in advertising or publicity pertaining to distribution of the 4431778Seivind * software without specific, written prior permission. M.I.T. and the M.I.T. 451541Srgrimes * S.I.P.B. make no representations about the suitability of this software 46216060Smdf * for any purpose. It is provided "as is" without express or implied 4748274Speter * warranty. 48263233Srwatson * 4948274Speter */ 501541Srgrimes 5112623Sphk/* 52164033Srwatson * newsyslog - roll over selected logs at the appropriate time, keeping the a 5312662Sdg * specified number of backup files around. 54194368Sbz */ 5582746Sdillon 5682746Sdillon#include <sys/cdefs.h> 57287835Smjg__FBSDID("$FreeBSD: head/usr.sbin/newsyslog/newsyslog.c 208648 2010-05-29 22:52:17Z gordon $"); 58212750Smdf 5993616Salfred#define OSF 6015103Sphk#ifndef COMPRESS_POSTFIX 61185983Skib#define COMPRESS_POSTFIX ".gz" 62189707Sjhb#endif 63189707Sjhb#ifndef BZCOMPRESS_POSTFIX 64189707Sjhb#define BZCOMPRESS_POSTFIX ".bz2" 65163606Srwatson#endif 66195699Srwatson 67195699Srwatson#include <sys/param.h> 68163606Srwatson#include <sys/queue.h> 69163606Srwatson#include <sys/stat.h> 7012645Sbde#include <sys/wait.h> 7112662Sdg 7212645Sbde#include <ctype.h> 7330354Sphk#include <err.h> 7463212Sabial#include <errno.h> 75100833Struckman#include <fcntl.h> 7630309Sphk#include <fnmatch.h> 7712429Sphk#include <glob.h> 78188232Sjhb#include <grp.h> 79188232Sjhb#include <paths.h> 80188232Sjhb#include <pwd.h> 81287835Smjg#include <signal.h> 82188232Sjhb#include <stdio.h> 83188232Sjhb#include <stdlib.h> 84188232Sjhb#include <string.h> 85188232Sjhb#include <time.h> 86192125Sjhb#include <unistd.h> 87192125Sjhb 88192125Sjhb#include "pathnames.h" 8912429Sphk#include "extern.h" 90287835Smjg 91192125Sjhb/* 9212429Sphk * Bit-values for the 'flags' parsed from a config-file entry. 93287835Smjg */ 94287835Smjg#define CE_COMPACT 0x0001 /* Compact the archived log files with gzip. */ 95287835Smjg#define CE_BZCOMPACT 0x0002 /* Compact the archived log files with bzip2. */ 96287835Smjg#define CE_BINARY 0x0008 /* Logfile is in binary, do not add status */ 97287835Smjg /* messages to logfile(s) when rotating. */ 98287835Smjg#define CE_NOSIGNAL 0x0010 /* There is no process to signal when */ 99287835Smjg /* trimming this file. */ 100287835Smjg#define CE_TRIMAT 0x0020 /* trim file at a specific time. */ 101287835Smjg#define CE_GLOB 0x0040 /* name of the log is file name pattern. */ 102287835Smjg#define CE_SIGNALGROUP 0x0080 /* Signal a process-group instead of a single */ 103216060Smdf /* process when trimming this file. */ 104287835Smjg#define CE_CREATE 0x0100 /* Create the log file if it does not exist. */ 10593616Salfred#define CE_NODUMP 0x0200 /* Set 'nodump' on newly created log file. */ 10662573Sphk 10712429Sphk#define MIN_PID 5 /* Don't touch pids lower than this */ 108267992Shselasky#define MAX_PID 99999 /* was lower, see /usr/include/sys/proc.h */ 109267992Shselasky 11012152Sphk#define kbytes(size) (((size) + 1023) >> 10) 111188232Sjhb 112188232Sjhb#define DEFAULT_MARKER "<default>" 113267992Shselasky#define DEBUG_MARKER "<debug>" 114267992Shselasky 115188232Sjhbstruct conf_entry { 11663212Sabial STAILQ_ENTRY(conf_entry) cf_nextp; 11763212Sabial char *log; /* Name of the log */ 11863212Sabial char *pid_file; /* PID file */ 11963212Sabial char *r_reason; /* The reason this file is being rotated */ 12063212Sabial int firstcreate; /* Creating log for the first time (-C). */ 121273401Smjg int rotate; /* Non-zero if this file should be rotated */ 12263212Sabial int fsize; /* size found for the log file */ 12363212Sabial uid_t uid; /* Owner of log */ 12463212Sabial gid_t gid; /* Group of log */ 12563212Sabial int numlogs; /* Number of logs to keep */ 12663212Sabial int trsize; /* Size cutoff to trigger trimming the log */ 12763212Sabial int hours; /* Hours between log trimming */ 12863212Sabial struct ptime_data *trim_at; /* Specific time to do trimming */ 12963212Sabial unsigned int permissions; /* File permissions on the log */ 13012623Sphk int flags; /* CE_COMPACT, CE_BZCOMPACT, CE_BINARY */ 13112623Sphk int sig; /* Signal to send */ 13212623Sphk int def_cfg; /* Using the <default> rule for this file */ 13344078Sdfr}; 13412623Sphk 135188232Sjhbstruct sigwork_entry { 136287835Smjg SLIST_ENTRY(sigwork_entry) sw_nextp; 137188232Sjhb int sw_signum; /* the signal to send */ 13812429Sphk int sw_pidok; /* true if pid value is valid */ 139287835Smjg pid_t sw_pid; /* the process id from the PID file */ 140188232Sjhb const char *sw_pidtype; /* "daemon" or "process group" */ 141188232Sjhb char sw_fname[1]; /* file the PID was read from */ 14280338Sroam}; 143287835Smjg 144188232Sjhbstruct zipwork_entry { 145188232Sjhb SLIST_ENTRY(zipwork_entry) zw_nextp; 146287835Smjg const struct conf_entry *zw_conf; /* for chown/perm/flag info */ 147188232Sjhb const struct sigwork_entry *zw_swork; /* to know success of signal */ 148188232Sjhb int zw_fsize; /* size of the file to compress */ 149267992Shselasky char zw_fname[1]; /* the file to compress */ 150267992Shselasky}; 151287835Smjg 152267992Shselaskytypedef enum { 153267992Shselasky FREE_ENT, KEEP_ENT 154267992Shselasky} fk_entry; 155276341Smjg 156276341SmjgSTAILQ_HEAD(cflist, conf_entry); 157267992ShselaskySLIST_HEAD(swlisthead, sigwork_entry) swhead = SLIST_HEAD_INITIALIZER(swhead); 158287835SmjgSLIST_HEAD(zwlisthead, zipwork_entry) zwhead = SLIST_HEAD_INITIALIZER(zwhead); 159287835Smjg 160287835Smjgint dbg_at_times; /* -D Show details of 'trim_at' code */ 161287835Smjg 162287835Smjgint archtodir = 0; /* Archive old logfiles to other directory */ 163267992Shselaskyint createlogs; /* Create (non-GLOB) logfiles which do not */ 164267992Shselasky /* already exist. 1=='for entries with */ 165267992Shselasky /* C flag', 2=='for all entries'. */ 166267992Shselaskyint verbose = 0; /* Print out what's going on */ 167267992Shselaskyint needroot = 1; /* Root privs are necessary */ 168267992Shselaskyint noaction = 0; /* Don't do anything, just show it */ 169287835Smjgint norotate = 0; /* Don't rotate */ 170287835Smjgint nosignal; /* Do not send any signals */ 171287835Smjgint enforcepid = 0; /* If PID file does not exist or empty, do nothing */ 172287835Smjgint force = 0; /* Force the trim no matter what */ 173287835Smjgint rotatereq = 0; /* -R = Always rotate the file(s) as given */ 174276341Smjg /* on the command (this also requires */ 175276341Smjg /* that a list of files *are* given on */ 176276341Smjg /* the run command). */ 177276341Smjgchar *requestor; /* The name given on a -R request */ 178276341Smjgchar *archdirname; /* Directory path to old logfiles archive */ 179267992Shselaskychar *destdir = NULL; /* Directory to treat at root for logs */ 180267992Shselaskyconst char *conf; /* Configuration file to use */ 181267992Shselasky 182267992Shselaskystruct ptime_data *dbg_timenow; /* A "timenow" value set via -D option */ 183267992Shselaskystruct ptime_data *timenow; /* The time to use for checking at-fields */ 184267992Shselasky 185267992Shselasky#define DAYTIME_LEN 16 186267992Shselaskychar daytime[DAYTIME_LEN]; /* The current time in human readable form, 187267992Shselasky * used for rotation-tracking messages. */ 188268285Shselaskychar hostname[MAXHOSTNAMELEN]; /* hostname */ 189267992Shselasky 190267992Shselaskystatic struct cflist *get_worklist(char **files); 191267992Shselaskystatic void parse_file(FILE *cf, struct cflist *work_p, struct cflist *glob_p, 192267992Shselasky struct conf_entry *defconf_p); 193267992Shselaskystatic char *sob(char *p); 194267992Shselaskystatic char *son(char *p); 195268285Shselaskystatic int isnumberstr(const char *); 196267992Shselaskystatic char *missing_field(char *p, char *errline); 197267992Shselaskystatic void change_attrs(const char *, const struct conf_entry *); 198267992Shselaskystatic fk_entry do_entry(struct conf_entry *); 199267992Shselaskystatic fk_entry do_rotate(const struct conf_entry *); 200267992Shselaskystatic void do_sigwork(struct sigwork_entry *); 201267992Shselaskystatic void do_zipwork(struct zipwork_entry *); 202267992Shselaskystatic struct sigwork_entry * 203267992Shselasky save_sigwork(const struct conf_entry *); 204267992Shselaskystatic struct zipwork_entry * 205267992Shselasky save_zipwork(const struct conf_entry *, const struct 206267992Shselasky sigwork_entry *, int, const char *); 207267992Shselaskystatic void set_swpid(struct sigwork_entry *, const struct conf_entry *); 208267992Shselaskystatic int sizefile(const char *); 209267992Shselaskystatic void expand_globs(struct cflist *work_p, struct cflist *glob_p); 210267992Shselaskystatic void free_clist(struct cflist *list); 211267992Shselaskystatic void free_entry(struct conf_entry *ent); 212267992Shselaskystatic struct conf_entry *init_entry(const char *fname, 213267992Shselasky struct conf_entry *src_entry); 214267992Shselaskystatic void parse_args(int argc, char **argv); 215267992Shselaskystatic int parse_doption(const char *doption); 216267992Shselaskystatic void usage(void); 217267992Shselaskystatic int log_trim(const char *logname, const struct conf_entry *log_ent); 218267992Shselaskystatic int age_old_log(char *file); 219267992Shselaskystatic void savelog(char *from, char *to); 220267992Shselaskystatic void createdir(const struct conf_entry *ent, char *dirpart); 221267992Shselaskystatic void createlog(const struct conf_entry *ent); 222267992Shselasky 223268285Shselasky/* 224268285Shselasky * All the following take a parameter of 'int', but expect values in the 225267992Shselasky * range of unsigned char. Define wrappers which take values of type 'char', 226267992Shselasky * whether signed or unsigned, and ensure they end up in the right range. 227267992Shselasky */ 228267992Shselasky#define isdigitch(Anychar) isdigit((u_char)(Anychar)) 229268285Shselasky#define isprintch(Anychar) isprint((u_char)(Anychar)) 230268285Shselasky#define isspacech(Anychar) isspace((u_char)(Anychar)) 231267992Shselasky#define tolowerch(Anychar) tolower((u_char)(Anychar)) 232267992Shselasky 233267992Shselaskyint 234267992Shselaskymain(int argc, char **argv) 235268285Shselasky{ 236268285Shselasky struct cflist *worklist; 237267992Shselasky struct conf_entry *p; 238267992Shselasky struct sigwork_entry *stmp; 239267992Shselasky struct zipwork_entry *ztmp; 240267992Shselasky 241268285Shselasky SLIST_INIT(&swhead); 242268285Shselasky SLIST_INIT(&zwhead); 243267992Shselasky 244267992Shselasky parse_args(argc, argv); 245267992Shselasky argc -= optind; 246267992Shselasky argv += optind; 247268285Shselasky 248268285Shselasky if (needroot && getuid() && geteuid()) 249268285Shselasky errx(1, "must have root privs"); 250267992Shselasky worklist = get_worklist(argv); 251267992Shselasky 252267992Shselasky /* 253267992Shselasky * Rotate all the files which need to be rotated. Note that 254268285Shselasky * some users have *hundreds* of entries in newsyslog.conf! 255268285Shselasky */ 256268285Shselasky while (!STAILQ_EMPTY(worklist)) { 257268285Shselasky p = STAILQ_FIRST(worklist); 258267992Shselasky STAILQ_REMOVE_HEAD(worklist, cf_nextp); 259267992Shselasky if (do_entry(p) == FREE_ENT) 260267992Shselasky free_entry(p); 261267992Shselasky } 262273174Sdavide 263268285Shselasky /* 264268285Shselasky * Send signals to any processes which need a signal to tell 265267992Shselasky * them to close and re-open the log file(s) we have rotated. 266267992Shselasky * Note that zipwork_entries include pointers to these 267267992Shselasky * sigwork_entry's, so we can not free the entries here. 268267992Shselasky */ 269267992Shselasky if (!SLIST_EMPTY(&swhead)) { 270267992Shselasky if (noaction || verbose) 271267992Shselasky printf("Signal all daemon process(es)...\n"); 272287835Smjg SLIST_FOREACH(stmp, &swhead, sw_nextp) 273273564Sdes do_sigwork(stmp); 274280443Shselasky if (noaction) 275273564Sdes printf("\tsleep 10\n"); 276273564Sdes else { 277267992Shselasky if (verbose) 278267992Shselasky printf("Pause 10 seconds to allow daemon(s)" 279188232Sjhb " to close log file(s)\n"); 28080338Sroam sleep(10); 28112152Sphk } 28244078Sdfr } 28344078Sdfr /* 28444078Sdfr * Compress all files that we're expected to compress, now 285280495Shselasky * that all processes should have closed the files which 286280495Shselasky * have been rotated. 28712197Sbde */ 28844078Sdfr if (!SLIST_EMPTY(&zwhead)) { 28963212Sabial if (noaction || verbose) 29063212Sabial printf("Compress all rotated log file(s)...\n"); 29163212Sabial while (!SLIST_EMPTY(&zwhead)) { 292287835Smjg ztmp = SLIST_FIRST(&zwhead); 29363212Sabial do_zipwork(ztmp); 29463212Sabial SLIST_REMOVE_HEAD(&zwhead, zw_nextp); 29563212Sabial free(ztmp); 29663212Sabial } 29763212Sabial } 29863212Sabial /* Now free all the sigwork entries. */ 29963212Sabial while (!SLIST_EMPTY(&swhead)) { 30063212Sabial stmp = SLIST_FIRST(&swhead); 30163212Sabial SLIST_REMOVE_HEAD(&swhead, sw_nextp); 30263212Sabial free(stmp); 303280495Shselasky } 304280495Shselasky 305280495Shselasky while (wait(NULL) > 0 || errno == EINTR) 306280495Shselasky ; 307280495Shselasky return (0); 308280495Shselasky} 30963212Sabial 310280495Shselaskystatic struct conf_entry * 311280495Shselaskyinit_entry(const char *fname, struct conf_entry *src_entry) 312280495Shselasky{ 31380339Sroam struct conf_entry *tempwork; 31480339Sroam 31580339Sroam if (verbose > 4) 31644078Sdfr printf("\t--> [creating entry for %s]\n", fname); 317280495Shselasky 318280495Shselasky tempwork = malloc(sizeof(struct conf_entry)); 31971510Smckusick if (tempwork == NULL) 320280495Shselasky err(1, "malloc of conf_entry for %s", fname); 321280495Shselasky 322280495Shselasky if (destdir == NULL || fname[0] != '/') 323280495Shselasky tempwork->log = strdup(fname); 324280495Shselasky else 325280495Shselasky asprintf(&tempwork->log, "%s%s", destdir, fname); 326280495Shselasky if (tempwork->log == NULL) 327280495Shselasky err(1, "strdup for %s", fname); 32844078Sdfr 32944078Sdfr if (src_entry != NULL) { 33044078Sdfr tempwork->pid_file = NULL; 331280495Shselasky if (src_entry->pid_file) 33244078Sdfr tempwork->pid_file = strdup(src_entry->pid_file); 333280495Shselasky tempwork->r_reason = NULL; 33444078Sdfr tempwork->firstcreate = 0; 33544078Sdfr tempwork->rotate = 0; 336280495Shselasky tempwork->fsize = -1; 337280495Shselasky tempwork->uid = src_entry->uid; 338280495Shselasky tempwork->gid = src_entry->gid; 339280495Shselasky tempwork->numlogs = src_entry->numlogs; 340280495Shselasky tempwork->trsize = src_entry->trsize; 341280495Shselasky tempwork->hours = src_entry->hours; 342280495Shselasky tempwork->trim_at = NULL; 343280495Shselasky if (src_entry->trim_at != NULL) 344280495Shselasky tempwork->trim_at = ptime_init(src_entry->trim_at); 345280495Shselasky tempwork->permissions = src_entry->permissions; 346280495Shselasky tempwork->flags = src_entry->flags; 347280495Shselasky tempwork->sig = src_entry->sig; 348280495Shselasky tempwork->def_cfg = src_entry->def_cfg; 349280495Shselasky } else { 350280495Shselasky /* Initialize as a "do-nothing" entry */ 35144078Sdfr tempwork->pid_file = NULL; 35244078Sdfr tempwork->r_reason = NULL; 35344078Sdfr tempwork->firstcreate = 0; 354280495Shselasky tempwork->rotate = 0; 355280495Shselasky tempwork->fsize = -1; 356280495Shselasky tempwork->uid = (uid_t)-1; 357280495Shselasky tempwork->gid = (gid_t)-1; 358280495Shselasky tempwork->numlogs = 1; 359280495Shselasky tempwork->trsize = -1; 360280495Shselasky tempwork->hours = -1; 361280495Shselasky tempwork->trim_at = NULL; 362280495Shselasky tempwork->permissions = 0; 36344078Sdfr tempwork->flags = 0; 36444078Sdfr tempwork->sig = SIGHUP; 36544078Sdfr tempwork->def_cfg = 0; 366267992Shselasky } 367267992Shselasky 368267992Shselasky return (tempwork); 369267992Shselasky} 370267992Shselasky 371267992Shselaskystatic void 372267992Shselaskyfree_entry(struct conf_entry *ent) 373280450Shselasky{ 374280450Shselasky 375280450Shselasky if (ent == NULL) 376267992Shselasky return; 377267992Shselasky 37812152Sphk if (ent->log != NULL) { 37912131Sphk if (verbose > 4) 38080338Sroam printf("\t--> [freeing entry for %s]\n", ent->log); 38180338Sroam free(ent->log); 38212152Sphk ent->log = NULL; 383115391Smux } 384115391Smux 385115391Smux if (ent->pid_file != NULL) { 386287835Smjg free(ent->pid_file); 387115391Smux ent->pid_file = NULL; 388115391Smux } 389115391Smux 390115391Smux if (ent->r_reason != NULL) { 391115391Smux free(ent->r_reason); 392115391Smux ent->r_reason = NULL; 393115391Smux } 394115391Smux 395115391Smux if (ent->trim_at != NULL) { 396115391Smux ptime_free(ent->trim_at); 397115391Smux ent->trim_at = NULL; 398115391Smux } 399115391Smux 400115391Smux free(ent); 401115391Smux} 402115391Smux 403115391Smuxstatic void 404115391Smuxfree_clist(struct cflist *list) 405115391Smux{ 406115391Smux struct conf_entry *ent; 407115391Smux 40844078Sdfr while (!STAILQ_EMPTY(list)) { 40912152Sphk ent = STAILQ_FIRST(list); 41063212Sabial STAILQ_REMOVE_HEAD(list, cf_nextp); 41163212Sabial free_entry(ent); 41263212Sabial } 41363212Sabial 41463212Sabial free(list); 41563212Sabial list = NULL; 41663212Sabial} 41763212Sabial 418188232Sjhbstatic fk_entry 419188232Sjhbdo_entry(struct conf_entry * ent) 420188232Sjhb{ 421188232Sjhb#define REASON_MAX 80 422188232Sjhb int modtime; 423188232Sjhb fk_entry free_or_keep; 42463212Sabial double diffsecs; 42563212Sabial char temp_reason[REASON_MAX]; 42663212Sabial 42763212Sabial free_or_keep = FREE_ENT; 42863212Sabial if (verbose) { 42963212Sabial if (ent->flags & CE_COMPACT) 43063212Sabial printf("%s <%dZ>: ", ent->log, ent->numlogs); 43163212Sabial else if (ent->flags & CE_BZCOMPACT) 43263212Sabial printf("%s <%dJ>: ", ent->log, ent->numlogs); 43363212Sabial else 43463212Sabial printf("%s <%d>: ", ent->log, ent->numlogs); 43563212Sabial } 43663212Sabial ent->fsize = sizefile(ent->log); 43763212Sabial modtime = age_old_log(ent->log); 43863212Sabial ent->rotate = 0; 43963212Sabial ent->firstcreate = 0; 44063212Sabial if (ent->fsize < 0) { 44163212Sabial /* 442287835Smjg * If either the C flag or the -C option was specified, 44363212Sabial * and if we won't be creating the file, then have the 444188232Sjhb * verbose message include a hint as to why the file 44563212Sabial * will not be created. 44663212Sabial */ 44763212Sabial temp_reason[0] = '\0'; 44863212Sabial if (createlogs > 1) 44963212Sabial ent->firstcreate = 1; 45063212Sabial else if ((ent->flags & CE_CREATE) && createlogs) 45163212Sabial ent->firstcreate = 1; 45263212Sabial else if (ent->flags & CE_CREATE) 45363212Sabial strlcpy(temp_reason, " (no -C option)", REASON_MAX); 45463212Sabial else if (createlogs) 45563212Sabial strlcpy(temp_reason, " (no C flag)", REASON_MAX); 45663212Sabial 45763212Sabial if (ent->firstcreate) { 45863212Sabial if (verbose) 45963212Sabial printf("does not exist -> will create.\n"); 46063212Sabial createlog(ent); 461188232Sjhb } else if (verbose) { 462287835Smjg printf("does not exist, skipped%s.\n", temp_reason); 46363212Sabial } 464188232Sjhb } else { 46563212Sabial if (ent->flags & CE_TRIMAT && !force && !rotatereq) { 46663212Sabial diffsecs = ptimeget_diff(timenow, ent->trim_at); 46763212Sabial if (diffsecs < 0.0) { 46863212Sabial /* trim_at is some time in the future. */ 469188232Sjhb if (verbose) { 47063212Sabial ptime_adjust4dst(ent->trim_at, 47163212Sabial timenow); 47263212Sabial printf("--> will trim at %s", 47363212Sabial ptimeget_ctime(ent->trim_at)); 47463212Sabial } 47563212Sabial return (free_or_keep); 476287835Smjg } else if (diffsecs >= 3600.0) { 47763212Sabial /* 47863212Sabial * trim_at is more than an hour in the past, 47963212Sabial * so find the next valid trim_at time, and 48063212Sabial * tell the user what that will be. 48163212Sabial */ 48263212Sabial if (verbose && dbg_at_times) 48363212Sabial printf("\n\t--> prev trim at %s\t", 48463212Sabial ptimeget_ctime(ent->trim_at)); 48563212Sabial if (verbose) { 486287835Smjg ptimeset_nxtime(ent->trim_at); 48763212Sabial printf("--> will trim at %s", 48863212Sabial ptimeget_ctime(ent->trim_at)); 489111119Simp } 49063212Sabial return (free_or_keep); 49163212Sabial } else if (verbose && noaction && dbg_at_times) { 49263212Sabial /* 49363212Sabial * If we are just debugging at-times, then 49463212Sabial * a detailed message is helpful. Also 49563212Sabial * skip "doing" any commands, since they 49663212Sabial * would all be turned off by no-action. 49763212Sabial */ 49863212Sabial printf("\n\t--> timematch at %s", 49963212Sabial ptimeget_ctime(ent->trim_at)); 50063212Sabial return (free_or_keep); 501287835Smjg } else if (verbose && ent->hours <= 0) { 50263212Sabial printf("--> time is up\n"); 50363212Sabial } 50471999Sphk } 50563212Sabial if (verbose && (ent->trsize > 0)) 50663212Sabial printf("size (Kb): %d [%d] ", ent->fsize, ent->trsize); 50763212Sabial if (verbose && (ent->hours > 0)) 50863212Sabial printf(" age (hr): %d [%d] ", modtime, ent->hours); 50963212Sabial 51063212Sabial /* 51144078Sdfr * Figure out if this logfile needs to be rotated. 51263212Sabial */ 51363212Sabial temp_reason[0] = '\0'; 51463212Sabial if (rotatereq) { 51563212Sabial ent->rotate = 1; 51663212Sabial snprintf(temp_reason, REASON_MAX, " due to -R from %s", 51763212Sabial requestor); 51863212Sabial } else if (force) { 51963212Sabial ent->rotate = 1; 52063212Sabial snprintf(temp_reason, REASON_MAX, " due to -F request"); 52163212Sabial } else if ((ent->trsize > 0) && (ent->fsize >= ent->trsize)) { 52263212Sabial ent->rotate = 1; 523287835Smjg snprintf(temp_reason, REASON_MAX, " due to size>%dK", 52463212Sabial ent->trsize); 52563212Sabial } else if (ent->hours <= 0 && (ent->flags & CE_TRIMAT)) { 52663212Sabial ent->rotate = 1; 527287835Smjg } else if ((ent->hours > 0) && ((modtime >= ent->hours) || 52863212Sabial (modtime < 0))) { 52963212Sabial ent->rotate = 1; 530188232Sjhb } 531287835Smjg 53263212Sabial /* 533188232Sjhb * If the file needs to be rotated, then rotate it. 53463212Sabial */ 53563212Sabial if (ent->rotate && !norotate) { 53663212Sabial if (temp_reason[0] != '\0') 53763212Sabial ent->r_reason = strdup(temp_reason); 53863212Sabial if (verbose) 53963212Sabial printf("--> trimming log....\n"); 54063212Sabial if (noaction && !verbose) { 54163212Sabial if (ent->flags & CE_COMPACT) 54263212Sabial printf("%s <%dZ>: trimming\n", 54363212Sabial ent->log, ent->numlogs); 54463212Sabial else if (ent->flags & CE_BZCOMPACT) 545188232Sjhb printf("%s <%dJ>: trimming\n", 546188232Sjhb ent->log, ent->numlogs); 547287835Smjg else 548188232Sjhb printf("%s <%d>: trimming\n", 549287835Smjg ent->log, ent->numlogs); 550188232Sjhb } 551188232Sjhb free_or_keep = do_rotate(ent); 552188232Sjhb } else { 553219819Sjeff if (verbose) 554219819Sjeff printf("--> skipping\n"); 555219819Sjeff } 556219819Sjeff } 557219819Sjeff return (free_or_keep); 558219819Sjeff#undef REASON_MAX 559219819Sjeff} 560219819Sjeff 561287835Smjgstatic void 562219819Sjeffparse_args(int argc, char **argv) 563219819Sjeff{ 564219819Sjeff int ch; 565219819Sjeff char *p; 566219819Sjeff 567219819Sjeff timenow = ptime_init(NULL); 568287835Smjg ptimeset_time(timenow, time(NULL)); 569219819Sjeff strlcpy(daytime, ptimeget_ctime(timenow) + 4, DAYTIME_LEN); 570219819Sjeff 571219819Sjeff /* Let's get our hostname */ 572219819Sjeff (void)gethostname(hostname, sizeof(hostname)); 573219819Sjeff 574188232Sjhb /* Truncate domain */ 575188232Sjhb if ((p = strchr(hostname, '.')) != NULL) 576188232Sjhb *p = '\0'; 577219819Sjeff 57863212Sabial /* Parse command line options. */ 57963212Sabial while ((ch = getopt(argc, argv, "a:d:f:nrsvCD:FNPR:")) != -1) 580287835Smjg switch (ch) { 58163212Sabial case 'a': 58263212Sabial archtodir++; 58363212Sabial archdirname = optarg; 58463212Sabial break; 58563212Sabial case 'd': 58663212Sabial destdir = optarg; 58763212Sabial break; 58863212Sabial case 'f': 58963212Sabial conf = optarg; 59063212Sabial break; 59163212Sabial case 'n': 59263212Sabial noaction++; 59363212Sabial break; 59463212Sabial case 'r': 59563212Sabial needroot = 0; 596219819Sjeff break; 597219819Sjeff case 's': 598254115Sscottl nosignal = 1; 599254115Sscottl break; 600254115Sscottl case 'v': 601254115Sscottl verbose++; 60263212Sabial break; 603254115Sscottl case 'C': 604188232Sjhb /* Useful for things like rc.diskless... */ 605188232Sjhb createlogs++; 60663212Sabial break; 60763212Sabial case 'D': 60863212Sabial /* 60963212Sabial * Set some debugging option. The specific option 61063212Sabial * depends on the value of optarg. These options 61163212Sabial * may come and go without notice or documentation. 61263212Sabial */ 61363212Sabial if (parse_doption(optarg)) 61463212Sabial break; 61563212Sabial usage(); 61663212Sabial /* NOTREACHED */ 61763212Sabial case 'F': 61863212Sabial force++; 61963212Sabial break; 62063212Sabial case 'N': 621216060Smdf norotate++; 622216060Smdf break; 623216060Smdf case 'P': 624216060Smdf enforcepid++; 625216060Smdf break; 626216060Smdf case 'R': 627216060Smdf rotatereq++; 628216060Smdf requestor = strdup(optarg); 629216060Smdf break; 630216060Smdf case 'm': /* Used by OpenBSD for "monitor mode" */ 631141433Sphk default: 632247561Smarius usage(); 633247561Smarius /* NOTREACHED */ 634247561Smarius } 63563212Sabial 63663212Sabial if (force && norotate) { 63763212Sabial warnx("Only one of -F and -N may be specified."); 63863212Sabial usage(); 63963212Sabial /* NOTREACHED */ 64063212Sabial } 64163212Sabial 64263212Sabial if (rotatereq) { 64363212Sabial if (optind == argc) { 64463212Sabial warnx("At least one filename must be given when -R is specified."); 64563212Sabial usage(); 646219819Sjeff /* NOTREACHED */ 64770679Sjhb } 64863212Sabial /* Make sure "requestor" value is safe for a syslog message. */ 64963212Sabial for (p = requestor; *p != '\0'; p++) { 65063212Sabial if (!isprintch(*p) && (*p != '\t')) 65163212Sabial *p = '.'; 65263212Sabial } 65363212Sabial } 65463212Sabial 655287835Smjg if (dbg_timenow) { 65663212Sabial /* 65763212Sabial * Note that the 'daytime' variable is not changed. 65863212Sabial * That is only used in messages that track when a 65963212Sabial * logfile is rotated, and if a file *is* rotated, 66063212Sabial * then it will still rotated at the "real now" time. 66163212Sabial */ 66263212Sabial ptime_free(timenow); 663287835Smjg timenow = dbg_timenow; 66463212Sabial fprintf(stderr, "Debug: Running as if TimeNow is %s", 66563212Sabial ptimeget_ctime(dbg_timenow)); 666287835Smjg } 66763212Sabial 66863212Sabial} 66963212Sabial 67063212Sabial/* 671111119Simp * These debugging options are mainly meant for developer use, such 67263212Sabial * as writing regression-tests. They would not be needed by users 673267992Shselasky * during normal operation of newsyslog... 67463212Sabial */ 67563212Sabialstatic int 676247561Smariusparse_doption(const char *doption) 67763212Sabial{ 67863212Sabial const char TN[] = "TN="; 679267992Shselasky int res; 680267992Shselasky 68163212Sabial if (strncmp(doption, TN, sizeof(TN) - 1) == 0) { 682267992Shselasky /* 683247561Smarius * The "TimeNow" debugging option. This might be off 68463212Sabial * by an hour when crossing a timezone change. 68563212Sabial */ 68663212Sabial dbg_timenow = ptime_init(NULL); 68763212Sabial res = ptime_relparse(dbg_timenow, PTM_PARSE_ISO8601, 68863212Sabial time(NULL), doption + sizeof(TN) - 1); 689287835Smjg if (res == -2) { 69063212Sabial warnx("Non-existent time specified on -D %s", doption); 69163212Sabial return (0); /* failure */ 69263212Sabial } else if (res < 0) { 69363212Sabial warnx("Malformed time given on -D %s", doption); 694174113Speter return (0); /* failure */ 695174113Speter } 696174113Speter return (1); /* successfully parsed */ 697174113Speter 698174113Speter } 699174113Speter 700247561Smarius if (strcmp(doption, "ats") == 0) { 701174113Speter dbg_at_times++; 702247561Smarius return (1); /* successfully parsed */ 703287835Smjg } 704247561Smarius 705174113Speter /* XXX - This check could probably be dropped. */ 706287835Smjg if ((strcmp(doption, "neworder") == 0) || (strcmp(doption, "oldorder") 707174113Speter == 0)) { 708174113Speter warnx("NOTE: newsyslog always uses 'neworder'."); 709174113Speter return (1); /* successfully parsed */ 710174113Speter } 711126319Sdes 712126319Sdes warnx("Unknown -D (debug) option: '%s'", doption); 713126319Sdes return (0); /* failure */ 714126319Sdes} 715126319Sdes 716126319Sdesstatic void 717126319Sdesusage(void) 718287835Smjg{ 719188232Sjhb 720287835Smjg fprintf(stderr, 721126319Sdes "usage: newsyslog [-CFNnrsv] [-a directory] [-d directory] [-f config-file]\n" 722188232Sjhb " [ [-R requestor] filename ... ]\n"); 723126319Sdes exit(1); 724188232Sjhb} 725287835Smjg 726126319Sdes/* 727188232Sjhb * Parse a configuration file and return a linked list of all the logs 728126319Sdes * which should be processed. 729126319Sdes */ 730126319Sdesstatic struct cflist * 731126319Sdesget_worklist(char **files) 732287835Smjg{ 733126319Sdes FILE *f; 734126319Sdes const char *fname; 735126319Sdes char **given; 736126319Sdes struct cflist *filelist, *globlist, *cmdlist; 73744078Sdfr struct conf_entry *defconf, *dupent, *ent; 73844078Sdfr int gmatch, fnres; 73978161Speter 74012152Sphk defconf = NULL; 74180338Sroam 74280338Sroam filelist = malloc(sizeof(struct cflist)); 74338869Sbde if (filelist == NULL) 74478161Speter err(1, "malloc of filelist"); 74578161Speter STAILQ_INIT(filelist); 746192125Sjhb globlist = malloc(sizeof(struct cflist)); 74793625Srwatson if (globlist == NULL) 748287835Smjg err(1, "malloc of globlist"); 74978161Speter STAILQ_INIT(globlist); 75078161Speter 751287835Smjg fname = conf; 75238869Sbde if (fname == NULL) 753267992Shselasky fname = _PATH_CONF; 75444078Sdfr 75512623Sphk if (strcmp(fname, "-") != 0) 75612623Sphk f = fopen(fname, "r"); 75712623Sphk else { 75812650Sphk f = stdin; 75912650Sphk fname = "<stdin>"; 76012650Sphk } 76112650Sphk if (!f) 76212650Sphk err(1, "%s", fname); 76312650Sphk 76412650Sphk parse_file(f, filelist, globlist, defconf); 76512650Sphk (void) fclose(f); 76612623Sphk 76712623Sphk /* 76842467Sphk * All config-file information has been read in and turned into 76912623Sphk * a filelist and a globlist. If there were no specific files 77012650Sphk * given on the run command, then the only thing left to do is to 77188006Sluigi * call a routine which finds all files matched by the globlist 77212623Sphk * and adds them to the filelist. Then return the worklist. 77312623Sphk */ 774136999Srwatson if (*files == NULL) { 77512152Sphk expand_globs(filelist, globlist); 77644078Sdfr free_clist(globlist); 77712152Sphk if (defconf != NULL) 77844078Sdfr free_entry(defconf); 77944078Sdfr return (filelist); 78012152Sphk /* NOTREACHED */ 781273424Smjg } 78244078Sdfr 78312152Sphk /* 78412152Sphk * If newsyslog was given a specific list of files to process, 78512152Sphk * it may be that some of those files were not listed in any 78612152Sphk * config file. Those unlisted files should get the default 78744078Sdfr * rotation action. First, create the default-rotation action 78812152Sphk * if none was found in a system config file. 78912152Sphk */ 79044078Sdfr if (defconf == NULL) { 79144078Sdfr defconf = init_entry(DEFAULT_MARKER, NULL); 79212152Sphk defconf->numlogs = 3; 79344078Sdfr defconf->trsize = 50; 79415241Sphk defconf->permissions = S_IRUSR|S_IWUSR; 79515241Sphk } 79644078Sdfr 79712243Sphk /* 79815241Sphk * If newsyslog was run with a list of specific filenames, 79944078Sdfr * then create a new worklist which has only those files in 80012152Sphk * it, picking up the rotation-rules for those files from 801267992Shselasky * the original filelist. 80212152Sphk * 80312152Sphk * XXX - Note that this will copy multiple rules for a single 80412152Sphk * logfile, if multiple entries are an exact match for 805217616Smdf * that file. That matches the historic behavior, but do 806217616Smdf * we want to continue to allow it? If so, it should 807217616Smdf * probably be handled more intelligently. 80812152Sphk */ 809217616Smdf cmdlist = malloc(sizeof(struct cflist)); 810217616Smdf if (cmdlist == NULL) 81112152Sphk err(1, "malloc of cmdlist"); 81212152Sphk STAILQ_INIT(cmdlist); 81312152Sphk 81412152Sphk for (given = files; *given; ++given) { 81512152Sphk /* 81612152Sphk * First try to find exact-matches for this given file. 81712152Sphk */ 81812152Sphk gmatch = 0; 81962573Sphk STAILQ_FOREACH(ent, filelist, cf_nextp) { 82012152Sphk if (strcmp(ent->log, *given) == 0) { 821287835Smjg gmatch++; 82287024Speter dupent = init_entry(*given, ent); 82387024Speter STAILQ_INSERT_TAIL(cmdlist, dupent, cf_nextp); 824164033Srwatson } 82587024Speter } 826139483Spjd if (gmatch) { 827287835Smjg if (verbose > 2) 82844078Sdfr printf("\t+ Matched entry %s\n", *given); 829287835Smjg continue; 830139483Spjd } 83112152Sphk 83212152Sphk /* 833273424Smjg * There was no exact-match for this given file, so look 83412623Sphk * for a "glob" entry which does match. 835136999Srwatson */ 83612152Sphk gmatch = 0; 83712623Sphk if (verbose > 2 && globlist != NULL) 83862573Sphk printf("\t+ Checking globs for %s\n", *given); 83912623Sphk STAILQ_FOREACH(ent, globlist, cf_nextp) { 84012623Sphk fnres = fnmatch(ent->log, *given, FNM_PATHNAME); 84112623Sphk if (verbose > 2) 84244078Sdfr printf("\t+ = %d for pattern %s\n", fnres, 84344078Sdfr ent->log); 84444972Sphk if (fnres == 0) { 845287835Smjg gmatch++; 84612623Sphk dupent = init_entry(*given, ent); 84712131Sphk /* This new entry is not a glob! */ 848287835Smjg dupent->flags &= ~CE_GLOB; 84912623Sphk STAILQ_INSERT_TAIL(cmdlist, dupent, cf_nextp); 85012623Sphk /* Only allow a match to one glob-entry */ 85141514Sarchie break; 85212623Sphk } 85312623Sphk } 85412623Sphk if (gmatch) { 85512623Sphk if (verbose > 2) 85612623Sphk printf("\t+ Matched %s via %s\n", *given, 857216060Smdf ent->log); 85812623Sphk continue; 85912623Sphk } 86012623Sphk 86112623Sphk /* 86244972Sphk * This given file was not found in any config file, so 86344078Sdfr * add a worklist item based on the default entry. 86444078Sdfr */ 86512623Sphk if (verbose > 2) 86612131Sphk printf("\t+ No entry matched %s (will use %s)\n", 86712623Sphk *given, DEFAULT_MARKER); 86812623Sphk dupent = init_entry(*given, defconf); 86912623Sphk /* Mark that it was *not* found in a config file */ 87044078Sdfr dupent->def_cfg = 1; 87144078Sdfr STAILQ_INSERT_TAIL(cmdlist, dupent, cf_nextp); 87212623Sphk } 873216060Smdf 87412623Sphk /* 87512623Sphk * Free all the entries in the original work list, the list of 87612623Sphk * glob entries, and the default entry. 87712623Sphk */ 87844972Sphk free_clist(filelist); 87912623Sphk free_clist(globlist); 88012623Sphk free_entry(defconf); 88144078Sdfr 88212623Sphk /* And finally, return a worklist which matches the given files. */ 88312623Sphk return (cmdlist); 884216058Smdf} 88512623Sphk 88612623Sphk/* 88744972Sphk * Expand the list of entries with filename patterns, and add all files 88812623Sphk * which match those glob-entries onto the worklist. 889216060Smdf */ 890216060Smdfstatic void 891287835Smjgexpand_globs(struct cflist *work_p, struct cflist *glob_p) 892216060Smdf{ 89312623Sphk int gmatch, gres; 89412623Sphk size_t i; 895224159Srwatson char *mfname; 896224159Srwatson struct conf_entry *dupent, *ent, *globent; 897224159Srwatson glob_t pglob; 898224159Srwatson struct stat st_fm; 899273424Smjg 900224159Srwatson /* 90112623Sphk * The worklist contains all fully-specified (non-GLOB) names. 90212623Sphk * 90363978Speter * Now expand the list of filename-pattern (GLOB) entries into 90444078Sdfr * a second list, which (by definition) will only match files 90512623Sphk * that already exist. Do not add a glob-related entry for any 90644078Sdfr * file which already exists in the fully-specified list. 90712623Sphk */ 908273401Smjg STAILQ_FOREACH(globent, glob_p, cf_nextp) { 90912623Sphk gres = glob(globent->log, GLOB_NOCHECK, NULL, &pglob); 91044078Sdfr if (gres != 0) { 91144078Sdfr warn("cannot expand pattern (%d): %s", gres, 91244078Sdfr globent->log); 91312623Sphk continue; 914101650Smux } 915101650Smux 916101650Smux if (verbose > 2) 91712623Sphk printf("\t+ Expanding pattern %s\n", globent->log); 91844078Sdfr for (i = 0; i < pglob.gl_matchc; i++) { 919139483Spjd mfname = pglob.gl_pathv[i]; 92044078Sdfr 92112623Sphk /* See if this file already has a specific entry. */ 922139483Spjd gmatch = 0; 923216058Smdf STAILQ_FOREACH(ent, work_p, cf_nextp) { 92463978Speter if (strcmp(mfname, ent->log) == 0) { 92544078Sdfr gmatch++; 926139483Spjd break; 927111260Srwatson } 92812623Sphk } 92912623Sphk if (gmatch) 93044078Sdfr continue; 93112623Sphk 93212623Sphk /* Make sure the named matched is a file. */ 93344078Sdfr gres = lstat(mfname, &st_fm); 93444078Sdfr if (gres != 0) { 935139483Spjd /* Error on a file that glob() matched?!? */ 93644078Sdfr warn("Skipping %s - lstat() error", mfname); 937139483Spjd continue; 938216058Smdf } 93963978Speter if (!S_ISREG(st_fm.st_mode)) { 94044078Sdfr /* We only rotate files! */ 94112623Sphk if (verbose > 2) 94215241Sphk printf("\t+ . skipping %s (!file)\n", 94312623Sphk mfname); 94444078Sdfr continue; 94512623Sphk } 94612623Sphk 94744078Sdfr if (verbose > 2) 94812623Sphk printf("\t+ . add file %s\n", mfname); 94912623Sphk dupent = init_entry(mfname, globent); 950216058Smdf /* This new entry is not a glob! */ 95163978Speter dupent->flags &= ~CE_GLOB; 95244078Sdfr 95312623Sphk /* Add to the worklist. */ 95415241Sphk STAILQ_INSERT_TAIL(work_p, dupent, cf_nextp); 95512623Sphk } 956111260Srwatson globfree(&pglob); 95712623Sphk if (verbose > 2) 95812623Sphk printf("\t+ Done with pattern %s\n", globent->log); 959139483Spjd } 96012623Sphk} 96112623Sphk 96212623Sphk/* 96362573Sphk * Parse a configuration file and update a linked list of all the logs to 96412623Sphk * process. 96512623Sphk */ 96612623Sphkstatic void 96712623Sphkparse_file(FILE *cf, struct cflist *work_p, struct cflist *glob_p, 96812623Sphk struct conf_entry *defconf_p) 96944078Sdfr{ 970287835Smjg char line[BUFSIZ], *parse, *q; 97112623Sphk char *cp, *errline, *group; 97212623Sphk struct conf_entry *working; 973287835Smjg struct passwd *pwd; 97463978Speter struct group *grp; 975287835Smjg int eol, ptm_opts, res, special; 97612623Sphk 977139483Spjd errline = NULL; 97812650Sphk while (fgets(line, BUFSIZ, cf)) { 97912623Sphk if ((line[0] == '\n') || (line[0] == '#') || 98012623Sphk (strlen(line) == 0)) 98112623Sphk continue; 982224159Srwatson if (errline != NULL) 983224159Srwatson free(errline); 984224159Srwatson errline = strdup(line); 985224159Srwatson for (cp = line + 1; *cp != '\0'; cp++) { 986273424Smjg if (*cp != '#') 987224159Srwatson continue; 98812623Sphk if (*(cp - 1) == '\\') { 98912623Sphk strcpy(cp - 1, cp); 990189707Sjhb cp--; 99112623Sphk continue; 99244078Sdfr } 99344078Sdfr *cp = '\0'; 99412623Sphk break; 99512623Sphk } 996273401Smjg 997186564Sed q = parse = missing_field(sob(line), errline); 998247561Smarius parse = son(line); 999247561Smarius if (!*parse) 100012623Sphk errx(1, "malformed line (missing fields):\n%s", 1001247561Smarius errline); 1002247561Smarius *parse = '\0'; 1003247561Smarius 1004247561Smarius /* 1005247561Smarius * Allow people to set debug options via the config file. 1006247561Smarius * (NOTE: debug options are undocumented, and may disappear 100712623Sphk * at any time, etc). 100844078Sdfr */ 100912623Sphk if (strcasecmp(DEBUG_MARKER, q) == 0) { 101012623Sphk q = parse = missing_field(sob(++parse), errline); 1011247561Smarius parse = son(parse); 101244078Sdfr if (!*parse) 101344078Sdfr warnx("debug line specifies no option:\n%s", 101412623Sphk errline); 101512623Sphk else { 101612623Sphk *parse = '\0'; 101744078Sdfr parse_doption(q); 101812623Sphk } 101912623Sphk continue; 102044078Sdfr } 102112623Sphk 102212623Sphk special = 0; 1023216058Smdf working = init_entry(q, NULL); 102412623Sphk if (strcasecmp(DEFAULT_MARKER, q) == 0) { 1025139483Spjd special = 1; 102612623Sphk if (defconf_p != NULL) { 102712623Sphk warnx("Ignoring duplicate entry for %s!", q); 102812623Sphk free_entry(working); 102962573Sphk continue; 103012623Sphk } 103112623Sphk defconf_p = working; 1032216066Smdf } 103312623Sphk 1034287835Smjg q = parse = missing_field(sob(++parse), errline); 103512623Sphk parse = son(parse); 103612623Sphk if (!*parse) 1037139483Spjd errx(1, "malformed line (missing fields):\n%s", 103845140Sphk errline); 103945140Sphk *parse = '\0'; 104012623Sphk if ((group = strchr(q, ':')) != NULL || 1041111119Simp (group = strrchr(q, '.')) != NULL) { 104212623Sphk *group++ = '\0'; 104312623Sphk if (*q) { 104412623Sphk if (!(isnumberstr(q))) { 104512623Sphk if ((pwd = getpwnam(q)) == NULL) 104612623Sphk errx(1, 104712623Sphk "error in config file; unknown user:\n%s", 104812623Sphk errline); 104912623Sphk working->uid = pwd->pw_uid; 105012623Sphk } else 1051287835Smjg working->uid = atoi(q); 105212623Sphk } else 1053287835Smjg working->uid = (uid_t)-1; 105412623Sphk 105512623Sphk q = group; 105612623Sphk if (*q) { 105712623Sphk if (!(isnumberstr(q))) { 105812623Sphk if ((grp = getgrnam(q)) == NULL) 105912623Sphk errx(1, 106012650Sphk "error in config file; unknown group:\n%s", 106112623Sphk errline); 106212623Sphk working->gid = grp->gr_gid; 106312623Sphk } else 1064224159Srwatson working->gid = atoi(q); 1065224159Srwatson } else 1066224159Srwatson working->gid = (gid_t)-1; 1067224159Srwatson 1068217555Smdf q = parse = missing_field(sob(++parse), errline); 1069224159Srwatson parse = son(parse); 1070224159Srwatson if (!*parse) 107112623Sphk errx(1, "malformed line (missing fields):\n%s", 107212623Sphk errline); 107362573Sphk *parse = '\0'; 107412623Sphk } else { 107544078Sdfr working->uid = (uid_t)-1; 1076287835Smjg working->gid = (gid_t)-1; 107753977Sgreen } 107812623Sphk 1079287835Smjg if (!sscanf(q, "%o", &working->permissions)) 108053977Sgreen errx(1, "error in config file; bad permissions:\n%s", 108153977Sgreen errline); 1082216060Smdf 108312623Sphk q = parse = missing_field(sob(++parse), errline); 1084216060Smdf parse = son(parse); 1085216060Smdf if (!*parse) 1086216060Smdf errx(1, "malformed line (missing fields):\n%s", 1087216060Smdf errline); 108853977Sgreen *parse = '\0'; 108953977Sgreen if (!sscanf(q, "%d", &working->numlogs) || working->numlogs < 0) 1090216060Smdf errx(1, "error in config file; bad value for count of logs to save:\n%s", 109153977Sgreen errline); 1092216060Smdf 1093287835Smjg q = parse = missing_field(sob(++parse), errline); 109412650Sphk parse = son(parse); 109512623Sphk if (!*parse) 109612623Sphk errx(1, "malformed line (missing fields):\n%s", 109742467Sphk errline); 1098224159Srwatson *parse = '\0'; 1099187864Sed if (isdigitch(*q)) 110012623Sphk working->trsize = atoi(q); 110188006Sluigi else if (strcmp(q, "*") == 0) 110288006Sluigi working->trsize = -1; 110388006Sluigi else { 110488006Sluigi warnx("Invalid value of '%s' for 'size' in line:\n%s", 1105287835Smjg q, errline); 110688006Sluigi working->trsize = -1; 110788006Sluigi } 1108287835Smjg 110988006Sluigi working->flags = 0; 111088006Sluigi q = parse = missing_field(sob(++parse), errline); 1111216060Smdf parse = son(parse); 111288006Sluigi eol = !*parse; 1113216060Smdf *parse = '\0'; 1114216060Smdf { 1115216060Smdf char *ep; 1116216060Smdf u_long ul; 1117141433Sphk 1118216060Smdf ul = strtoul(q, &ep, 10); 1119287835Smjg if (ep == q) 112088006Sluigi working->hours = 0; 112188006Sluigi else if (*ep == '*') 112288006Sluigi working->hours = -1; 1123273424Smjg else if (ul > INT_MAX) 1124224159Srwatson errx(1, "interval is too large:\n%s", errline); 112588006Sluigi else 112612243Sphk working->hours = ul; 112712623Sphk 112812623Sphk if (*ep == '\0' || strcmp(ep, "*") == 0) 112912623Sphk goto no_trimat; 113012623Sphk if (*ep != '@' && *ep != '$') 113142095Sdfr errx(1, "malformed interval/at:\n%s", errline); 113212243Sphk 113312243Sphk working->flags |= CE_TRIMAT; 113412243Sphk working->trim_at = ptime_init(NULL); 113512243Sphk ptm_opts = PTM_PARSE_ISO8601; 113612243Sphk if (*ep == '$') 113711865Sphk ptm_opts = PTM_PARSE_DWM; 113862573Sphk ptm_opts |= PTM_PARSE_MATCHDOM; 113911863Sphk res = ptime_relparse(working->trim_at, ptm_opts, 1140100833Struckman ptimeget_secs(timenow), ep + 1); 114111863Sphk if (res == -2) 1142100833Struckman errx(1, "nonexistent time for 'at' value:\n%s", 1143100833Struckman errline); 1144100833Struckman else if (res < 0) 114512243Sphk errx(1, "malformed 'at' value:\n%s", errline); 1146100833Struckman } 114720506Sbdeno_trimat: 1148100833Struckman 1149100833Struckman if (eol) 115011863Sphk q = NULL; 115112243Sphk else { 115212243Sphk q = parse = sob(++parse); /* Optional field */ 115311863Sphk parse = son(parse); 115412243Sphk if (!*parse) 115512243Sphk eol = 1; 115612243Sphk *parse = '\0'; 115712243Sphk } 115812243Sphk 115911863Sphk for (; q && *q && !isspacech(*q); q++) { 116011863Sphk switch (tolowerch(*q)) { 116112243Sphk case 'b': 1162155758Sandre working->flags |= CE_BINARY; 1163195699Srwatson break; 1164155758Sandre case 'c': 1165155758Sandre /* 1166155758Sandre * XXX - Ick! Ugly! Remove ASAP! 1167155758Sandre * We want `c' and `C' for "create". But we 1168155758Sandre * will temporarily treat `c' as `g', because 1169155758Sandre * FreeBSD releases <= 4.8 have a typo of 1170155758Sandre * checking ('G' || 'c') for CE_GLOB. 1171191688Szec */ 1172155758Sandre if (*q == 'c') { 1173155758Sandre warnx("Assuming 'g' for 'c' in flags for line:\n%s", 1174155758Sandre errline); 1175155758Sandre warnx("The 'c' flag will eventually mean 'CREATE'"); 1176155758Sandre working->flags |= CE_GLOB; 1177155758Sandre break; 1178155758Sandre } 1179155758Sandre working->flags |= CE_CREATE; 1180155758Sandre break; 1181155758Sandre case 'd': 1182191688Szec working->flags |= CE_NODUMP; 1183155758Sandre break; 1184155758Sandre case 'g': 1185155758Sandre working->flags |= CE_GLOB; 1186155758Sandre break; 1187155758Sandre case 'j': 1188246696Smarius working->flags |= CE_BZCOMPACT; 1189246696Smarius break; 1190246696Smarius case 'n': 1191246696Smarius working->flags |= CE_NOSIGNAL; 119238517Sdfr break; 119338517Sdfr case 'u': 119438517Sdfr working->flags |= CE_SIGNALGROUP; 119562573Sphk break; 119638517Sdfr case 'w': 119738517Sdfr /* Depreciated flag - keep for compatibility purposes */ 1198136404Speter break; 1199136404Speter case 'z': 1200136404Speter working->flags |= CE_COMPACT; 1201136404Speter break; 120238517Sdfr case '-': 1203100833Struckman break; 1204100833Struckman case 'f': /* Used by OpenBSD for "CE_FOLLOW" */ 1205100833Struckman case 'm': /* Used by OpenBSD for "CE_MONITOR" */ 1206246689Smarius case 'p': /* Used by NetBSD for "CE_PLAIN0" */ 1207246689Smarius default: 1208246689Smarius errx(1, "illegal flag in config file -- %c", 1209246689Smarius *q); 1210136404Speter } 1211136404Speter } 1212136404Speter 1213136404Speter if (eol) 1214136404Speter q = NULL; 1215136404Speter else { 1216136404Speter q = parse = sob(++parse); /* Optional field */ 121738517Sdfr parse = son(parse); 121838517Sdfr if (!*parse) 121938517Sdfr eol = 1; 122038517Sdfr *parse = '\0'; 1221246689Smarius } 1222246689Smarius 1223136404Speter working->pid_file = NULL; 1224246689Smarius if (q && *q) { 1225136404Speter if (*q == '/') 1226136404Speter working->pid_file = strdup(q); 1227246689Smarius else if (isdigit(*q)) 1228136404Speter goto got_sig; 1229246689Smarius else 1230136404Speter errx(1, 123138517Sdfr "illegal pid file or signal number in config file:\n%s", 123238517Sdfr errline); 123338517Sdfr } 123438517Sdfr if (eol) 1235246696Smarius q = NULL; 1236246696Smarius else { 1237246696Smarius q = parse = sob(++parse); /* Optional field */ 1238246696Smarius *(parse = son(parse)) = '\0'; 1239170288Sdwmalone } 1240170288Sdwmalone 1241217616Smdf working->sig = SIGHUP; 1242170288Sdwmalone if (q && *q) { 1243170288Sdwmalone if (isdigit(*q)) { 1244170288Sdwmalone got_sig: 1245170288Sdwmalone working->sig = atoi(q); 1246170288Sdwmalone } else { 1247170288Sdwmalone err_sig: 1248170288Sdwmalone errx(1, 1249246689Smarius "illegal signal number in config file:\n%s", 1250246689Smarius errline); 1251246689Smarius } 1252246689Smarius if (working->sig < 1 || working->sig >= NSIG) 1253170288Sdwmalone goto err_sig; 1254170288Sdwmalone } 1255170288Sdwmalone 1256170288Sdwmalone /* 1257170288Sdwmalone * Finish figuring out what pid-file to use (if any) in 1258246689Smarius * later processing if this logfile needs to be rotated. 1259246689Smarius */ 1260246689Smarius if ((working->flags & CE_NOSIGNAL) == CE_NOSIGNAL) { 1261246689Smarius /* 1262170288Sdwmalone * This config-entry specified 'n' for nosignal, 1263170288Sdwmalone * see if it also specified an explicit pid_file. 1264170288Sdwmalone * This would be a pretty pointless combination. 1265170288Sdwmalone */ 126612243Sphk if (working->pid_file != NULL) { 126712243Sphk warnx("Ignoring '%s' because flag 'n' was specified in line:\n%s", 126812243Sphk working->pid_file, errline); 126912243Sphk free(working->pid_file); 127012243Sphk working->pid_file = NULL; 127112243Sphk } 127211865Sphk } else if (working->pid_file == NULL) { 127362573Sphk /* 127411863Sphk * This entry did not specify the 'n' flag, which 1275100833Struckman * means it should signal syslogd unless it had 1276268509Smjg * specified some other pid-file (and obviously the 127711863Sphk * syslog pid-file will not be for a process-group). 1278267993Shselasky * Also, we should only try to notify syslog if we 1279267993Shselasky * are root. 1280267993Shselasky */ 1281267993Shselasky if (working->flags & CE_SIGNALGROUP) { 1282268509Smjg warnx("Ignoring flag 'U' in line:\n%s", 1283267993Shselasky errline); 1284268509Smjg working->flags &= ~CE_SIGNALGROUP; 1285268509Smjg } 1286105354Srobert if (needroot) 1287267992Shselasky working->pid_file = strdup(_PATH_SYSLOGPID); 1288267992Shselasky } 1289267992Shselasky 1290268509Smjg /* 1291268509Smjg * Add this entry to the appropriate list of entries, unless 1292268509Smjg * it was some kind of special entry (eg: <default>). 1293268509Smjg */ 1294268509Smjg if (special) { 1295268509Smjg ; /* Do not add to any list */ 1296268509Smjg } else if (working->flags & CE_GLOB) { 1297267992Shselasky STAILQ_INSERT_TAIL(glob_p, working, cf_nextp); 1298267992Shselasky } else { 1299267992Shselasky STAILQ_INSERT_TAIL(work_p, working, cf_nextp); 1300267992Shselasky } 1301268509Smjg } 1302268509Smjg if (errline != NULL) 1303267992Shselasky free(errline); 1304267992Shselasky} 1305267992Shselasky 1306267985Sgjbstatic char * 130745140Sphkmissing_field(char *p, char *errline) 130812243Sphk{ 130911863Sphk 131045140Sphk if (!p || !*p) 131145140Sphk errx(1, "missing field in config file:\n%s", errline); 131212243Sphk return (p); 131312243Sphk} 131412243Sphk 131512243Sphkstatic fk_entry 131611863Sphkdo_rotate(const struct conf_entry *ent) 131712131Sphk{ 131811863Sphk char dirpart[MAXPATHLEN], namepart[MAXPATHLEN]; 131911863Sphk char file1[MAXPATHLEN], file2[MAXPATHLEN]; 132012243Sphk char zfile1[MAXPATHLEN], zfile2[MAXPATHLEN]; 132112243Sphk char jfile1[MAXPATHLEN]; 132212243Sphk int flags, numlogs_c; 132312243Sphk fk_entry free_or_keep; 132412243Sphk struct sigwork_entry *swork; 132511865Sphk struct stat st; 132662573Sphk 132711863Sphk flags = ent->flags; 1328120803Sbms free_or_keep = FREE_ENT; 1329120803Sbms 1330120813Sbms if (archtodir) { 133112243Sphk char *p; 1332100833Struckman 1333120803Sbms /* build complete name of archive directory into dirpart */ 1334120803Sbms if (*archdirname == '/') { /* absolute */ 1335120803Sbms strlcpy(dirpart, archdirname, sizeof(dirpart)); 1336120803Sbms } else { /* relative */ 1337120803Sbms /* get directory part of logfile */ 1338100833Struckman strlcpy(dirpart, ent->log, sizeof(dirpart)); 1339120803Sbms if ((p = rindex(dirpart, '/')) == NULL) 1340120813Sbms dirpart[0] = '\0'; 1341120813Sbms else 1342120813Sbms *(p + 1) = '\0'; 1343120813Sbms strlcat(dirpart, archdirname, sizeof(dirpart)); 1344120813Sbms } 1345120813Sbms 1346120813Sbms /* check if archive directory exists, if not, create it */ 1347120813Sbms if (lstat(dirpart, &st)) 1348120813Sbms createdir(ent, dirpart); 1349120813Sbms 1350120813Sbms /* get filename part of logfile */ 135112243Sphk if ((p = rindex(ent->log, '/')) == NULL) 135212243Sphk strlcpy(namepart, ent->log, sizeof(namepart)); 135312243Sphk else 135412243Sphk strlcpy(namepart, p + 1, sizeof(namepart)); 135512243Sphk 135612243Sphk /* name of oldest log */ 135712260Sphk (void) snprintf(file1, sizeof(file1), "%s/%s.%d", dirpart, 135812260Sphk namepart, ent->numlogs); 135912260Sphk (void) snprintf(zfile1, sizeof(zfile1), "%s%s", file1, 136012260Sphk COMPRESS_POSTFIX); 136112260Sphk snprintf(jfile1, sizeof(jfile1), "%s%s", file1, 136238517Sdfr BZCOMPRESS_POSTFIX); 136312243Sphk } else { 136438517Sdfr /* name of oldest log */ 136512260Sphk (void) snprintf(file1, sizeof(file1), "%s.%d", ent->log, 136612260Sphk ent->numlogs); 136738517Sdfr (void) snprintf(zfile1, sizeof(zfile1), "%s%s", file1, 136873971Stmm COMPRESS_POSTFIX); 136973971Stmm snprintf(jfile1, sizeof(jfile1), "%s%s", file1, 137073971Stmm BZCOMPRESS_POSTFIX); 137173971Stmm } 137273971Stmm 137312260Sphk if (noaction) { 137417971Sbde printf("\trm -f %s\n", file1); 137512243Sphk printf("\trm -f %s\n", zfile1); 1376192144Skib printf("\trm -f %s\n", jfile1); 137716282Snate } else { 137811863Sphk (void) unlink(file1); 137912260Sphk (void) unlink(zfile1); 138012243Sphk (void) unlink(jfile1); 138112243Sphk } 138212260Sphk 138338517Sdfr /* Move down log files */ 138412243Sphk numlogs_c = ent->numlogs; /* copy for countdown */ 138512260Sphk while (numlogs_c--) { 1386139483Spjd 138712260Sphk (void) strlcpy(file2, file1, sizeof(file2)); 138811863Sphk 138917971Sbde if (archtodir) 139012243Sphk (void) snprintf(file1, sizeof(file1), "%s/%s.%d", 139112131Sphk dirpart, namepart, numlogs_c); 139211863Sphk else 139311863Sphk (void) snprintf(file1, sizeof(file1), "%s.%d", 139416282Snate ent->log, numlogs_c); 139583366Sjulian 1396136404Speter (void) strlcpy(zfile1, file1, sizeof(zfile1)); 139716282Snate (void) strlcpy(zfile2, file2, sizeof(zfile2)); 139816282Snate if (lstat(file1, &st)) { 139916282Snate (void) strlcat(zfile1, COMPRESS_POSTFIX, 140016282Snate sizeof(zfile1)); 140116282Snate (void) strlcat(zfile2, COMPRESS_POSTFIX, 140216282Snate sizeof(zfile2)); 140386183Srwatson if (lstat(zfile1, &st)) { 1404136404Speter strlcpy(zfile1, file1, sizeof(zfile1)); 140516282Snate strlcpy(zfile2, file2, sizeof(zfile2)); 140616282Snate strlcat(zfile1, BZCOMPRESS_POSTFIX, 140716282Snate sizeof(zfile1)); 140816282Snate strlcat(zfile2, BZCOMPRESS_POSTFIX, 1409127052Struckman sizeof(zfile2)); 141016282Snate if (lstat(zfile1, &st)) 141116282Snate continue; 141216282Snate } 141316282Snate } 141416282Snate if (noaction) 141577646Sdd printf("\tmv %s %s\n", zfile1, zfile2); 141616282Snate else { 141716282Snate /* XXX - Ought to be checking for failure! */ 141816282Snate (void)rename(zfile1, zfile2); 141916282Snate } 142016282Snate change_attrs(zfile2, ent); 142116282Snate } 1422217915Smdf 142316282Snate if (ent->numlogs > 0) { 142416282Snate if (noaction) { 1425120813Sbms /* 1426127052Struckman * Note that savelog() may succeed with using link() 1427127052Struckman * for the archtodir case, but there is no good way 142816282Snate * of knowing if it will when doing "noaction", so 142916282Snate * here we claim that it will have to do a copy... 143016282Snate */ 143116282Snate if (archtodir) 143216282Snate printf("\tcp %s %s\n", ent->log, file1); 1433127052Struckman else 1434127052Struckman printf("\tln %s %s\n", ent->log, file1); 143516282Snate } else { 143616282Snate if (!(flags & CE_BINARY)) { 143716282Snate /* Report the trimming to the old log */ 143816282Snate log_trim(ent->log, ent); 143916282Snate } 144016282Snate savelog(ent->log, file1); 144176834Sjlemon } 144283366Sjulian change_attrs(file1, ent); 1443136404Speter } 144476834Sjlemon 144576834Sjlemon /* Create the new log file and move it into place */ 144678620Smjacob if (noaction) 144778620Smjacob printf("Start new log...\n"); 144876834Sjlemon createlog(ent); 144976834Sjlemon 145076834Sjlemon /* 145176834Sjlemon * Save all signalling and file-compression to be done after log 145276834Sjlemon * files from all entries have been rotated. This way any one 145383366Sjulian * process will not be sent the same signal multiple times when 1454136404Speter * multiple log files had to be rotated. 145576834Sjlemon */ 145676834Sjlemon swork = NULL; 145776834Sjlemon if (ent->pid_file != NULL) 145883366Sjulian swork = save_sigwork(ent); 1459136404Speter if (ent->numlogs > 0 && (flags & (CE_COMPACT | CE_BZCOMPACT))) { 146076834Sjlemon /* 146176834Sjlemon * The zipwork_entry will include a pointer to this 146276834Sjlemon * conf_entry, so the conf_entry should not be freed. 146312260Sphk */ 146412260Sphk free_or_keep = KEEP_ENT; 146512260Sphk save_zipwork(ent, swork, ent->fsize, file1); 146612260Sphk } 146738517Sdfr 146812243Sphk return (free_or_keep); 1469126253Struckman} 1470233291Salc 147112243Sphkstatic void 1472126253Struckmando_sigwork(struct sigwork_entry *swork) 1473192144Skib{ 1474192144Skib struct sigwork_entry *nextsig; 1475126253Struckman int kres, secs; 1476148864Scsjp 1477148864Scsjp if (!(swork->sw_pidok) || swork->sw_pid == 0) 1478148864Scsjp return; /* no work to do... */ 1479148864Scsjp 1480148864Scsjp /* 1481148864Scsjp * If nosignal (-s) was specified, then do not signal any process. 1482111883Sjhb * Note that a nosignal request triggers a warning message if the 1483111883Sjhb * rotated logfile needs to be compressed, *unless* -R was also 1484126253Struckman * specified. We assume that an `-sR' request came from a process 1485127052Struckman * which writes to the logfile, and as such, we assume that process 1486126253Struckman * has already made sure the logfile is not presently in use. This 1487126253Struckman * just sets swork->sw_pidok to a special value, and do_zipwork 1488126253Struckman * will print any necessary warning(s). 1489126253Struckman */ 1490126253Struckman if (nosignal) { 1491233291Salc if (!rotatereq) 1492233291Salc swork->sw_pidok = -1; 1493233291Salc return; 1494233291Salc } 1495233291Salc 1496233291Salc /* 1497233291Salc * Compute the pause between consecutive signals. Use a longer 149812260Sphk * sleep time if we will be sending two signals to the same 1499126253Struckman * deamon or process-group. 150012243Sphk */ 150112260Sphk secs = 0; 150212243Sphk nextsig = SLIST_NEXT(swork, sw_nextp); 150312243Sphk if (nextsig != NULL) { 150412260Sphk if (swork->sw_pid == nextsig->sw_pid) 150538517Sdfr secs = 10; 150612243Sphk else 150712285Sphk secs = 1; 150812260Sphk } 150912260Sphk 1510139483Spjd if (noaction) { 151112260Sphk printf("\tkill -%d %d \t\t# %s\n", swork->sw_signum, 151212243Sphk (int)swork->sw_pid, swork->sw_fname); 1513148873Scsjp if (secs > 0) 1514148873Scsjp printf("\tsleep %d\n", secs); 151517971Sbde return; 151612243Sphk } 151712243Sphk 151812243Sphk kres = kill(swork->sw_pid, swork->sw_signum); 151912243Sphk if (kres != 0) { 1520100487Struckman /* 1521100487Struckman * Assume that "no such process" (ESRCH) is something 1522100487Struckman * to warn about, but is not an error. Presumably the 1523100487Struckman * process which writes to the rotated log file(s) is 1524126253Struckman * gone, in which case we should have no problem with 1525100487Struckman * compressing the rotated log file(s). 1526100487Struckman */ 1527126253Struckman if (errno != ESRCH) 1528192160Sdes swork->sw_pidok = 0; 1529126253Struckman warn("can't notify %s, pid %d", swork->sw_pidtype, 1530126253Struckman (int)swork->sw_pid); 1531126253Struckman } else { 1532217915Smdf if (verbose) 1533120781Sbms printf("Notified %s pid %d = %s\n", swork->sw_pidtype, 1534127050Struckman (int)swork->sw_pid, swork->sw_fname); 1535127050Struckman if (secs > 0) { 1536130327Sgreen if (verbose) 1537130327Sgreen printf("Pause %d second(s) between signals\n", 1538130327Sgreen secs); 1539130327Sgreen sleep(secs); 1540130327Sgreen } 1541126253Struckman } 1542127050Struckman} 1543127052Struckman 1544100487Struckmanstatic void 1545127050Struckmando_zipwork(struct zipwork_entry *zwork) 1546100487Struckman{ 1547100487Struckman const char *pgm_name, *pgm_path; 15481541Srgrimes int errsav, fcount, zstatus; 154953977Sgreen pid_t pidzip, wpid; 155053977Sgreen char zresult[MAXPATHLEN]; 155112131Sphk 1552216059Smdf pgm_path = NULL; 155344078Sdfr strlcpy(zresult, zwork->zw_fname, sizeof(zresult)); 155453977Sgreen if (zwork != NULL && zwork->zw_conf != NULL) { 155512131Sphk if (zwork->zw_conf->flags & CE_COMPACT) { 1556273401Smjg pgm_path = _PATH_GZIP; 1557216059Smdf strlcat(zresult, COMPRESS_POSTFIX, sizeof(zresult)); 155812131Sphk } else if (zwork->zw_conf->flags & CE_BZCOMPACT) { 1559216059Smdf pgm_path = _PATH_BZIP2; 1560216059Smdf strlcat(zresult, BZCOMPRESS_POSTFIX, sizeof(zresult)); 1561216059Smdf } 1562216059Smdf } 1563216059Smdf if (pgm_path == NULL) { 1564216059Smdf warnx("invalid entry for %s in do_zipwork", zwork->zw_fname); 1565216059Smdf return; 1566216059Smdf } 1567216059Smdf pgm_name = strrchr(pgm_path, '/'); 1568216059Smdf if (pgm_name == NULL) 1569216059Smdf pgm_name = pgm_path; 157053977Sgreen else 157153977Sgreen pgm_name++; 157253977Sgreen 1573216060Smdf if (zwork->zw_swork != NULL && zwork->zw_swork->sw_pidok <= 0) { 1574216060Smdf warnx( 157553977Sgreen "log %s not compressed because daemon(s) not notified", 157612131Sphk zwork->zw_fname); 1577216059Smdf change_attrs(zwork->zw_fname, zwork->zw_conf); 1578216059Smdf return; 1579216059Smdf } 1580216059Smdf 1581216059Smdf if (noaction) { 1582216060Smdf printf("\t%s %s\n", pgm_name, zwork->zw_fname); 1583216060Smdf change_attrs(zresult, zwork->zw_conf); 1584216059Smdf return; 158512131Sphk } 1586216059Smdf 158712131Sphk fcount = 1; 158812131Sphk pidzip = fork(); 158953977Sgreen while (pidzip < 0) { 159053977Sgreen /* 159153977Sgreen * The fork failed. If the failure was due to a temporary 159253977Sgreen * problem, then wait a short time and try it again. 159353977Sgreen */ 159453977Sgreen errsav = errno; 159553977Sgreen warn("fork() for `%s %s'", pgm_name, zwork->zw_fname); 159653977Sgreen if (errsav != EAGAIN || fcount > 5) 1597104094Sphk errx(1, "Exiting..."); 159862573Sphk sleep(fcount * 12); 159953977Sgreen fcount++; 160053977Sgreen pidzip = fork(); 1601287835Smjg } 1602109246Sdillon if (!pidzip) { 160353977Sgreen /* The child process executes the compression command */ 1604287835Smjg execl(pgm_path, pgm_path, "-f", zwork->zw_fname, (char *)0); 1605186564Sed err(1, "execl(`%s -f %s')", pgm_path, zwork->zw_fname); 160653977Sgreen } 160753977Sgreen 1608287835Smjg wpid = waitpid(pidzip, &zstatus, 0); 160953977Sgreen if (wpid == -1) { 161053977Sgreen /* XXX - should this be a fatal error? */ 161153977Sgreen warn("%s: waitpid(%d)", pgm_path, pidzip); 161253977Sgreen return; 161353977Sgreen } 161453977Sgreen if (!WIFEXITED(zstatus)) { 161553977Sgreen warnx("`%s -f %s' did not terminate normally", pgm_name, 1616287835Smjg zwork->zw_fname); 1617287835Smjg return; 1618287835Smjg } 1619287835Smjg if (WEXITSTATUS(zstatus)) { 162053977Sgreen warnx("`%s -f %s' terminated with a non-zero status (%d)", 162153977Sgreen pgm_name, zwork->zw_fname, WEXITSTATUS(zstatus)); 162283968Srwatson return; 1623287835Smjg } 1624287835Smjg 1625287835Smjg /* Compression was successful, set file attributes on the result. */ 1626287835Smjg change_attrs(zresult, zwork->zw_conf); 162712131Sphk} 162892953Srwatson 162992953Srwatson/* 1630224159Srwatson * Save information on any process we need to signal. Any single 1631224159Srwatson * process may need to be sent different signal-values for different 1632224159Srwatson * log files, but usually a single signal-value will cause the process 1633224159Srwatson * to close and re-open all of it's log files. 1634224159Srwatson */ 1635224159Srwatsonstatic struct sigwork_entry * 1636287835Smjgsave_sigwork(const struct conf_entry *ent) 1637287835Smjg{ 1638287835Smjg struct sigwork_entry *sprev, *stmp; 1639287835Smjg int ndiff; 1640287835Smjg size_t tmpsiz; 1641224159Srwatson 1642224159Srwatson sprev = NULL; 1643224159Srwatson ndiff = 1; 164483968Srwatson SLIST_FOREACH(stmp, &swhead, sw_nextp) { 164583968Srwatson ndiff = strcmp(ent->pid_file, stmp->sw_fname); 1646109246Sdillon if (ndiff > 0) 1647109246Sdillon break; 164892953Srwatson if (ndiff == 0) { 1649287835Smjg if (ent->sig == stmp->sw_signum) 165083968Srwatson break; 165112910Sphk if (ent->sig > stmp->sw_signum) { 165283968Srwatson ndiff = 1; 165383968Srwatson break; 1654196176Sbz } 1655196176Sbz } 165692953Srwatson sprev = stmp; 1657196176Sbz } 1658196176Sbz if (stmp != NULL && ndiff == 0) 1659196176Sbz return (stmp); 1660196176Sbz 1661196176Sbz tmpsiz = sizeof(struct sigwork_entry) + strlen(ent->pid_file) + 1; 1662196176Sbz stmp = malloc(tmpsiz); 166392953Srwatson set_swpid(stmp, ent); 1664196176Sbz stmp->sw_signum = ent->sig; 1665196176Sbz strcpy(stmp->sw_fname, ent->pid_file); 166692953Srwatson if (sprev == NULL) 1667287835Smjg SLIST_INSERT_HEAD(&swhead, stmp, sw_nextp); 166883968Srwatson else 166983968Srwatson SLIST_INSERT_AFTER(sprev, stmp, sw_nextp); 1670287835Smjg return (stmp); 1671287835Smjg} 1672287835Smjg 1673287835Smjg/* 167412131Sphk * Save information on any file we need to compress. We may see the same 1675126121Spjd * file multiple times, so check the full list to avoid duplicates. The 1676132776Skan * list itself is sorted smallest-to-largest, because that's the order we 1677126121Spjd * want to compress the files. If the partition is very low on disk space, 1678126121Spjd * then the smallest files are the most likely to compress, and compressing 1679126121Spjd * them first will free up more space for the larger files. 1680126121Spjd */ 1681126121Spjdstatic struct zipwork_entry * 1682126121Spjdsave_zipwork(const struct conf_entry *ent, const struct sigwork_entry *swork, 1683172930Srwatson int zsize, const char *zipfname) 1684126121Spjd{ 1685126121Spjd struct zipwork_entry *zprev, *ztmp; 1686287835Smjg int ndiff; 1687126121Spjd size_t tmpsiz; 1688261590Sglebius 1689261590Sglebius /* Compute the size if the caller did not know it. */ 1690261590Sglebius if (zsize < 0) 1691261590Sglebius zsize = sizefile(zipfname); 1692287835Smjg 1693126121Spjd zprev = NULL; 1694216060Smdf ndiff = 1; 1695216060Smdf SLIST_FOREACH(ztmp, &zwhead, zw_nextp) { 1696287835Smjg ndiff = strcmp(zipfname, ztmp->zw_fname); 1697287835Smjg if (ndiff == 0) 169853977Sgreen break; 169912131Sphk if (zsize > ztmp->zw_fsize) 170012131Sphk zprev = ztmp; 170112221Sbde } 170212171Sphk if (ztmp != NULL && ndiff == 0) 170312171Sphk return (ztmp); 170412171Sphk 170512171Sphk tmpsiz = sizeof(struct zipwork_entry) + strlen(zipfname) + 1; 170612171Sphk ztmp = malloc(tmpsiz); 170712171Sphk ztmp->zw_conf = ent; 170812171Sphk ztmp->zw_swork = swork; 170912171Sphk ztmp->zw_fsize = zsize; 171012221Sbde strcpy(ztmp->zw_fname, zipfname); 171112131Sphk if (zprev == NULL) 1712225617Skmacy SLIST_INSERT_HEAD(&zwhead, ztmp, zw_nextp); 17131541Srgrimes else 1714188232Sjhb SLIST_INSERT_AFTER(zprev, ztmp, zw_nextp); 171538517Sdfr return (ztmp); 17161541Srgrimes} 17171541Srgrimes 17181541Srgrimes/* Send a signal to the pid specified by pidfile */ 171911863Sphkstatic void 17203308Sphkset_swpid(struct sigwork_entry *swork, const struct conf_entry *ent) 17213308Sphk{ 17221541Srgrimes FILE *f; 17231541Srgrimes long minok, maxok, rval; 172483366Sjulian char *endp, *linep, line[BUFSIZ]; 172512171Sphk 1726136404Speter minok = MIN_PID; 172712260Sphk maxok = MAX_PID; 1728186564Sed swork->sw_pidok = 0; 1729186664Sed swork->sw_pid = 0; 1730188232Sjhb swork->sw_pidtype = "daemon"; 1731186664Sed if (ent->flags & CE_SIGNALGROUP) { 1732186664Sed /* 1733186664Sed * If we are expected to signal a process-group when 173412260Sphk * rotating this logfile, then the value read in should 173512171Sphk * be the negative of a valid process ID. 173612171Sphk */ 173712171Sphk minok = -MAX_PID; 173812171Sphk maxok = -MIN_PID; 173912171Sphk swork->sw_pidtype = "process-group"; 174012171Sphk } 174112171Sphk 174283366Sjulian f = fopen(ent->pid_file, "r"); 1743136404Speter if (f == NULL) { 1744136404Speter if (errno == ENOENT && enforcepid == 0) { 174512171Sphk /* 1746192125Sjhb * Warn if the PID file doesn't exist, but do 1747127052Struckman * not consider it an error. Most likely it 174812171Sphk * means the process has been terminated, 174912243Sphk * so it should be safe to rotate any log 175012243Sphk * files that the process would have been using. 175186183Srwatson */ 1752136404Speter swork->sw_pidok = 1; 175312285Sphk warnx("pid file doesn't exist: %s", ent->pid_file); 175412171Sphk } else 175512171Sphk warn("can't open pid file: %s", ent->pid_file); 175612243Sphk return; 175712171Sphk } 175812260Sphk 175912171Sphk if (fgets(line, BUFSIZ, f) == NULL) { 176012171Sphk /* 176112171Sphk * Warn if the PID file is empty, but do not consider 176212171Sphk * it an error. Most likely it means the process has 1763127052Struckman * has terminated, so it should be safe to rotate any 176412171Sphk * log files that the process would have been using. 176512243Sphk */ 176652644Sphk if (feof(f) && enforcepid == 0) { 176712243Sphk swork->sw_pidok = 1; 176812243Sphk warnx("pid file is empty: %s", ent->pid_file); 176912243Sphk } else 177012131Sphk warn("can't read from pid file: %s", ent->pid_file); 177177646Sdd (void)fclose(f); 1772172038Srwatson return; 177312243Sphk } 177412243Sphk (void)fclose(f); 177512243Sphk 177611863Sphk errno = 0; 177712131Sphk linep = line; 177812243Sphk while (*linep == ' ') 177912243Sphk linep++; 1780217915Smdf rval = strtol(linep, &endp, 10); 178111863Sphk if (*endp != '\0' && !isspacech(*endp)) { 1782189707Sjhb warnx("pid file does not start with a valid number: %s", 1783189707Sjhb ent->pid_file); 1784189707Sjhb } else if (rval < minok || rval > maxok) { 1785189707Sjhb warnx("bad value '%ld' for process number in %s", 1786192125Sjhb rval, ent->pid_file); 1787285208Spkelsey if (verbose) 1788192125Sjhb warnx("\t(expecting value between %ld and %ld)", 1789192125Sjhb minok, maxok); 1790192125Sjhb } else { 1791192125Sjhb swork->sw_pidok = 1; 1792194252Sjamie swork->sw_pid = rval; 179312429Sphk } 1794185983Skib 1795127052Struckman return; 1796127052Struckman} 1797127052Struckman 1798185983Skib/* Log the fact that the logs were turned over */ 1799185983Skibstatic int 1800221829Smdflog_trim(const char *logname, const struct conf_entry *log_ent) 1801185983Skib{ 180212243Sphk FILE *f; 1803186564Sed const char *xtra; 1804186564Sed 1805127052Struckman if ((f = fopen(logname, "a")) == NULL) 1806127052Struckman return (-1); 1807192125Sjhb xtra = ""; 1808192125Sjhb if (log_ent->def_cfg) 180912429Sphk xtra = " using <default> rule"; 181012260Sphk if (log_ent->firstcreate) 181112260Sphk fprintf(f, "%s %s newsyslog[%d]: logfile first created%s\n", 181212260Sphk daytime, hostname, (int) getpid(), xtra); 181312260Sphk else if (log_ent->r_reason != NULL) 1814127052Struckman fprintf(f, "%s %s newsyslog[%d]: logfile turned over%s%s\n", 1815127052Struckman daytime, hostname, (int) getpid(), log_ent->r_reason, xtra); 181612260Sphk else 181712260Sphk fprintf(f, "%s %s newsyslog[%d]: logfile turned over%s\n", 181811863Sphk daytime, hostname, (int) getpid(), xtra); 181912260Sphk if (fclose(f) == EOF) 18201541Srgrimes err(1, "log_trim: fclose"); 1821212750Smdf return (0); 1822212750Smdf} 1823217916Smdf 1824217916Smdf/* Return size in kilobytes of a file */ 1825212750Smdfstatic int 1826212750Smdfsizefile(const char *file) 1827212750Smdf{ 1828212750Smdf struct stat sb; 1829212750Smdf 1830212750Smdf if (stat(file, &sb) < 0) 1831212750Smdf return (-1); 1832212750Smdf return (kbytes(dbtob(sb.st_blocks))); 1833212750Smdf} 1834212750Smdf 1835212750Smdf/* Return the age of old log file (file.0) */ 1836212750Smdfstatic int 1837212750Smdfage_old_log(char *file) 1838212750Smdf{ 1839212750Smdf struct stat sb; 1840212750Smdf char *endp; 1841212750Smdf char tmp[MAXPATHLEN + sizeof(".0") + sizeof(COMPRESS_POSTFIX) + 1842280192Sian sizeof(BZCOMPRESS_POSTFIX) + 1]; 1843280192Sian 1844280192Sian if (archtodir) { 1845279993Sian char *p; 1846212750Smdf 1847212750Smdf /* build name of archive directory into tmp */ 1848212750Smdf if (*archdirname == '/') { /* absolute */ 1849 strlcpy(tmp, archdirname, sizeof(tmp)); 1850 } else { /* relative */ 1851 /* get directory part of logfile */ 1852 strlcpy(tmp, file, sizeof(tmp)); 1853 if ((p = rindex(tmp, '/')) == NULL) 1854 tmp[0] = '\0'; 1855 else 1856 *(p + 1) = '\0'; 1857 strlcat(tmp, archdirname, sizeof(tmp)); 1858 } 1859 1860 strlcat(tmp, "/", sizeof(tmp)); 1861 1862 /* get filename part of logfile */ 1863 if ((p = rindex(file, '/')) == NULL) 1864 strlcat(tmp, file, sizeof(tmp)); 1865 else 1866 strlcat(tmp, p + 1, sizeof(tmp)); 1867 } else { 1868 (void) strlcpy(tmp, file, sizeof(tmp)); 1869 } 1870 1871 strlcat(tmp, ".0", sizeof(tmp)); 1872 if (stat(tmp, &sb) < 0) { 1873 /* 1874 * A plain '.0' file does not exist. Try again, first 1875 * with the added suffix of '.gz', then with an added 1876 * suffix of '.bz2' instead of '.gz'. 1877 */ 1878 endp = strchr(tmp, '\0'); 1879 strlcat(tmp, COMPRESS_POSTFIX, sizeof(tmp)); 1880 if (stat(tmp, &sb) < 0) { 1881 *endp = '\0'; /* Remove .gz */ 1882 strlcat(tmp, BZCOMPRESS_POSTFIX, sizeof(tmp)); 1883 if (stat(tmp, &sb) < 0) 1884 return (-1); 1885 } 1886 } 1887 return ((int)(ptimeget_secs(timenow) - sb.st_mtime + 1800) / 3600); 1888} 1889 1890/* Skip Over Blanks */ 1891static char * 1892sob(char *p) 1893{ 1894 while (p && *p && isspace(*p)) 1895 p++; 1896 return (p); 1897} 1898 1899/* Skip Over Non-Blanks */ 1900static char * 1901son(char *p) 1902{ 1903 while (p && *p && !isspace(*p)) 1904 p++; 1905 return (p); 1906} 1907 1908/* Check if string is actually a number */ 1909static int 1910isnumberstr(const char *string) 1911{ 1912 while (*string) { 1913 if (!isdigitch(*string++)) 1914 return (0); 1915 } 1916 return (1); 1917} 1918 1919/* 1920 * Save the active log file under a new name. A link to the new name 1921 * is the quick-and-easy way to do this. If that fails (which it will 1922 * if the destination is on another partition), then make a copy of 1923 * the file to the new location. 1924 */ 1925static void 1926savelog(char *from, char *to) 1927{ 1928 FILE *src, *dst; 1929 int c, res; 1930 1931 res = link(from, to); 1932 if (res == 0) 1933 return; 1934 1935 if ((src = fopen(from, "r")) == NULL) 1936 err(1, "can't fopen %s for reading", from); 1937 if ((dst = fopen(to, "w")) == NULL) 1938 err(1, "can't fopen %s for writing", to); 1939 1940 while ((c = getc(src)) != EOF) { 1941 if ((putc(c, dst)) == EOF) 1942 err(1, "error writing to %s", to); 1943 } 1944 1945 if (ferror(src)) 1946 err(1, "error reading from %s", from); 1947 if ((fclose(src)) != 0) 1948 err(1, "can't fclose %s", to); 1949 if ((fclose(dst)) != 0) 1950 err(1, "can't fclose %s", from); 1951} 1952 1953/* create one or more directory components of a path */ 1954static void 1955createdir(const struct conf_entry *ent, char *dirpart) 1956{ 1957 int res; 1958 char *s, *d; 1959 char mkdirpath[MAXPATHLEN]; 1960 struct stat st; 1961 1962 s = dirpart; 1963 d = mkdirpath; 1964 1965 for (;;) { 1966 *d++ = *s++; 1967 if (*s != '/' && *s != '\0') 1968 continue; 1969 *d = '\0'; 1970 res = lstat(mkdirpath, &st); 1971 if (res != 0) { 1972 if (noaction) { 1973 printf("\tmkdir %s\n", mkdirpath); 1974 } else { 1975 res = mkdir(mkdirpath, 0755); 1976 if (res != 0) 1977 err(1, "Error on mkdir(\"%s\") for -a", 1978 mkdirpath); 1979 } 1980 } 1981 if (*s == '\0') 1982 break; 1983 } 1984 if (verbose) { 1985 if (ent->firstcreate) 1986 printf("Created directory '%s' for new %s\n", 1987 dirpart, ent->log); 1988 else 1989 printf("Created directory '%s' for -a\n", dirpart); 1990 } 1991} 1992 1993/* 1994 * Create a new log file, destroying any currently-existing version 1995 * of the log file in the process. If the caller wants a backup copy 1996 * of the file to exist, they should call 'link(logfile,logbackup)' 1997 * before calling this routine. 1998 */ 1999void 2000createlog(const struct conf_entry *ent) 2001{ 2002 int fd, failed; 2003 struct stat st; 2004 char *realfile, *slash, tempfile[MAXPATHLEN]; 2005 2006 fd = -1; 2007 realfile = ent->log; 2008 2009 /* 2010 * If this log file is being created for the first time (-C option), 2011 * then it may also be true that the parent directory does not exist 2012 * yet. Check, and create that directory if it is missing. 2013 */ 2014 if (ent->firstcreate) { 2015 strlcpy(tempfile, realfile, sizeof(tempfile)); 2016 slash = strrchr(tempfile, '/'); 2017 if (slash != NULL) { 2018 *slash = '\0'; 2019 failed = stat(tempfile, &st); 2020 if (failed && errno != ENOENT) 2021 err(1, "Error on stat(%s)", tempfile); 2022 if (failed) 2023 createdir(ent, tempfile); 2024 else if (!S_ISDIR(st.st_mode)) 2025 errx(1, "%s exists but is not a directory", 2026 tempfile); 2027 } 2028 } 2029 2030 /* 2031 * First create an unused filename, so it can be chown'ed and 2032 * chmod'ed before it is moved into the real location. mkstemp 2033 * will create the file mode=600 & owned by us. Note that all 2034 * temp files will have a suffix of '.z<something>'. 2035 */ 2036 strlcpy(tempfile, realfile, sizeof(tempfile)); 2037 strlcat(tempfile, ".zXXXXXX", sizeof(tempfile)); 2038 if (noaction) 2039 printf("\tmktemp %s\n", tempfile); 2040 else { 2041 fd = mkstemp(tempfile); 2042 if (fd < 0) 2043 err(1, "can't mkstemp logfile %s", tempfile); 2044 2045 /* 2046 * Add status message to what will become the new log file. 2047 */ 2048 if (!(ent->flags & CE_BINARY)) { 2049 if (log_trim(tempfile, ent)) 2050 err(1, "can't add status message to log"); 2051 } 2052 } 2053 2054 /* Change the owner/group, if we are supposed to */ 2055 if (ent->uid != (uid_t)-1 || ent->gid != (gid_t)-1) { 2056 if (noaction) 2057 printf("\tchown %u:%u %s\n", ent->uid, ent->gid, 2058 tempfile); 2059 else { 2060 failed = fchown(fd, ent->uid, ent->gid); 2061 if (failed) 2062 err(1, "can't fchown temp file %s", tempfile); 2063 } 2064 } 2065 2066 /* Turn on NODUMP if it was requested in the config-file. */ 2067 if (ent->flags & CE_NODUMP) { 2068 if (noaction) 2069 printf("\tchflags nodump %s\n", tempfile); 2070 else { 2071 failed = fchflags(fd, UF_NODUMP); 2072 if (failed) { 2073 warn("log_trim: fchflags(NODUMP)"); 2074 } 2075 } 2076 } 2077 2078 /* 2079 * Note that if the real logfile still exists, and if the call 2080 * to rename() fails, then "neither the old file nor the new 2081 * file shall be changed or created" (to quote the standard). 2082 * If the call succeeds, then the file will be replaced without 2083 * any window where some other process might find that the file 2084 * did not exist. 2085 * XXX - ? It may be that for some error conditions, we could 2086 * retry by first removing the realfile and then renaming. 2087 */ 2088 if (noaction) { 2089 printf("\tchmod %o %s\n", ent->permissions, tempfile); 2090 printf("\tmv %s %s\n", tempfile, realfile); 2091 } else { 2092 failed = fchmod(fd, ent->permissions); 2093 if (failed) 2094 err(1, "can't fchmod temp file '%s'", tempfile); 2095 failed = rename(tempfile, realfile); 2096 if (failed) 2097 err(1, "can't mv %s to %s", tempfile, realfile); 2098 } 2099 2100 if (fd >= 0) 2101 close(fd); 2102} 2103 2104/* 2105 * Change the attributes of a given filename to what was specified in 2106 * the newsyslog.conf entry. This routine is only called for files 2107 * that newsyslog expects that it has created, and thus it is a fatal 2108 * error if this routine finds that the file does not exist. 2109 */ 2110static void 2111change_attrs(const char *fname, const struct conf_entry *ent) 2112{ 2113 int failed; 2114 2115 if (noaction) { 2116 printf("\tchmod %o %s\n", ent->permissions, fname); 2117 2118 if (ent->uid != (uid_t)-1 || ent->gid != (gid_t)-1) 2119 printf("\tchown %u:%u %s\n", 2120 ent->uid, ent->gid, fname); 2121 2122 if (ent->flags & CE_NODUMP) 2123 printf("\tchflags nodump %s\n", fname); 2124 return; 2125 } 2126 2127 failed = chmod(fname, ent->permissions); 2128 if (failed) { 2129 if (errno != EPERM) 2130 err(1, "chmod(%s) in change_attrs", fname); 2131 warn("change_attrs couldn't chmod(%s)", fname); 2132 } 2133 2134 if (ent->uid != (uid_t)-1 || ent->gid != (gid_t)-1) { 2135 failed = chown(fname, ent->uid, ent->gid); 2136 if (failed) 2137 warn("can't chown %s", fname); 2138 } 2139 2140 if (ent->flags & CE_NODUMP) { 2141 failed = chflags(fname, UF_NODUMP); 2142 if (failed) 2143 warn("can't chflags %s NODUMP", fname); 2144 } 2145} 2146