2 * ------+---------+---------+-------- + --------+---------+---------+---------*
3 * This file includes significant modifications done by:
4 * Copyright (c) 2003, 2004 - Garance Alistair Drosehn <gad@FreeBSD.org>.
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28 * ------+---------+---------+-------- + --------+---------+---------+---------*
32 * This file contains changes from the Open Software Foundation.
36 * Copyright 1988, 1989 by the Massachusetts Institute of Technology
38 * Permission to use, copy, modify, and distribute this software and its
39 * documentation for any purpose and without fee is hereby granted, provided
40 * that the above copyright notice appear in all copies and that both that
41 * copyright notice and this permission notice appear in supporting
42 * documentation, and that the names of M.I.T. and the M.I.T. S.I.P.B. not be
43 * used in advertising or publicity pertaining to distribution of the
44 * software without specific, written prior permission. M.I.T. and the M.I.T.
45 * S.I.P.B. make no representations about the suitability of this software
46 * for any purpose. It is provided "as is" without express or implied
49 * $FreeBSD: src/usr.sbin/newsyslog/newsyslog.c,v 1.117 2011/01/31 10:57:54 mm Exp $
53 * newsyslog - roll over selected logs at the appropriate time, keeping the a
54 * specified number of backup files around.
59 #include <sys/param.h>
60 #include <sys/queue.h>
83 #include "pathnames.h"
87 * Compression suffixes
89 #ifndef COMPRESS_SUFFIX_GZ
90 #define COMPRESS_SUFFIX_GZ ".gz"
93 #ifndef COMPRESS_SUFFIX_BZ2
94 #define COMPRESS_SUFFIX_BZ2 ".bz2"
97 #ifndef COMPRESS_SUFFIX_XZ
98 #define COMPRESS_SUFFIX_XZ ".xz"
101 #define COMPRESS_SUFFIX_MAXLEN MAX(MAX(sizeof(COMPRESS_SUFFIX_GZ),sizeof(COMPRESS_SUFFIX_BZ2)),sizeof(COMPRESS_SUFFIX_XZ))
106 #define COMPRESS_TYPES 4 /* Number of supported compression types */
108 #define COMPRESS_NONE 0
109 #define COMPRESS_GZIP 1
110 #define COMPRESS_BZIP2 2
111 #define COMPRESS_XZ 3
114 * Bit-values for the 'flags' parsed from a config-file entry.
116 #define CE_BINARY 0x0008 /* Logfile is in binary, do not add status */
117 /* messages to logfile(s) when rotating. */
118 #define CE_NOSIGNAL 0x0010 /* There is no process to signal when */
119 /* trimming this file. */
120 #define CE_TRIMAT 0x0020 /* trim file at a specific time. */
121 #define CE_GLOB 0x0040 /* name of the log is file name pattern. */
122 #define CE_SIGNALGROUP 0x0080 /* Signal a process-group instead of a single */
123 /* process when trimming this file. */
124 #define CE_CREATE 0x0100 /* Create the log file if it does not exist. */
125 #define CE_NODUMP 0x0200 /* Set 'nodump' on newly created log file. */
127 #define MIN_PID 5 /* Don't touch pids lower than this */
128 #define MAX_PID 99999 /* was lower, see /usr/include/sys/proc.h */
130 #define kbytes(size) (((size) + 1023) >> 10)
132 #define DEFAULT_MARKER "<default>"
133 #define DEBUG_MARKER "<debug>"
134 #define INCLUDE_MARKER "<include>"
135 #define DEFAULT_TIMEFNAME_FMT "%Y%m%dT%H%M%S"
137 #define MAX_OLDLOGS 65536 /* Default maximum number of old logfiles */
139 struct compress_types
{
140 const char *flag
; /* Flag in configuration file */
141 const char *suffix
; /* Compression suffix */
142 const char *path
; /* Path to compression program */
145 const struct compress_types compress_type
[COMPRESS_TYPES
] = {
146 { "", "", "" }, /* no compression */
147 { "Z", COMPRESS_SUFFIX_GZ
, _PATH_GZIP
}, /* gzip compression */
148 { "J", COMPRESS_SUFFIX_BZ2
, _PATH_BZIP2
}, /* bzip2 compression */
149 { "X", COMPRESS_SUFFIX_XZ
, _PATH_XZ
} /* xz compression */
153 STAILQ_ENTRY(conf_entry
) cf_nextp
;
154 char *log
; /* Name of the log */
155 char *pid_file
; /* PID file */
156 char *r_reason
; /* The reason this file is being rotated */
157 int firstcreate
; /* Creating log for the first time (-C). */
158 int rotate
; /* Non-zero if this file should be rotated */
159 int fsize
; /* size found for the log file */
160 uid_t uid
; /* Owner of log */
161 gid_t gid
; /* Group of log */
162 int numlogs
; /* Number of logs to keep */
163 int trsize
; /* Size cutoff to trigger trimming the log */
164 int hours
; /* Hours between log trimming */
165 struct ptime_data
*trim_at
; /* Specific time to do trimming */
166 unsigned int permissions
; /* File permissions on the log */
167 int flags
; /* CE_BINARY */
168 int compress
; /* Compression */
169 int sig
; /* Signal to send */
170 int def_cfg
; /* Using the <default> rule for this file */
173 struct sigwork_entry
{
174 SLIST_ENTRY(sigwork_entry
) sw_nextp
;
175 int sw_signum
; /* the signal to send */
176 int sw_pidok
; /* true if pid value is valid */
177 pid_t sw_pid
; /* the process id from the PID file */
178 const char *sw_pidtype
; /* "daemon" or "process group" */
179 char sw_fname
[1]; /* file the PID was read from */
182 struct zipwork_entry
{
183 SLIST_ENTRY(zipwork_entry
) zw_nextp
;
184 const struct conf_entry
*zw_conf
; /* for chown/perm/flag info */
185 const struct sigwork_entry
*zw_swork
; /* to know success of signal */
186 int zw_fsize
; /* size of the file to compress */
187 char zw_fname
[1]; /* the file to compress */
190 struct include_entry
{
191 STAILQ_ENTRY(include_entry
) inc_nextp
;
192 const char *file
; /* Name of file to process */
195 struct oldlog_entry
{
196 char *fname
; /* Filename of the log file */
197 time_t t
; /* Parsed timestamp of the logfile */
204 STAILQ_HEAD(cflist
, conf_entry
);
205 SLIST_HEAD(swlisthead
, sigwork_entry
) swhead
= SLIST_HEAD_INITIALIZER(swhead
);
206 SLIST_HEAD(zwlisthead
, zipwork_entry
) zwhead
= SLIST_HEAD_INITIALIZER(zwhead
);
207 STAILQ_HEAD(ilist
, include_entry
);
209 int dbg_at_times
; /* -D Show details of 'trim_at' code */
211 int archtodir
= 0; /* Archive old logfiles to other directory */
212 int createlogs
; /* Create (non-GLOB) logfiles which do not */
213 /* already exist. 1=='for entries with */
214 /* C flag', 2=='for all entries'. */
215 int verbose
= 0; /* Print out what's going on */
216 int needroot
= 1; /* Root privs are necessary */
217 int noaction
= 0; /* Don't do anything, just show it */
218 int norotate
= 0; /* Don't rotate */
219 int nosignal
; /* Do not send any signals */
220 int enforcepid
= 0; /* If PID file does not exist or empty, do nothing */
221 int force
= 0; /* Force the trim no matter what */
222 int rotatereq
= 0; /* -R = Always rotate the file(s) as given */
223 /* on the command (this also requires */
224 /* that a list of files *are* given on */
225 /* the run command). */
226 char *requestor
; /* The name given on a -R request */
227 char *timefnamefmt
= NULL
; /* Use time based filenames instead of .0 etc */
228 char *archdirname
; /* Directory path to old logfiles archive */
229 char *destdir
= NULL
; /* Directory to treat at root for logs */
230 const char *conf
; /* Configuration file to use */
232 struct ptime_data
*dbg_timenow
; /* A "timenow" value set via -D option */
233 struct ptime_data
*timenow
; /* The time to use for checking at-fields */
235 #define DAYTIME_LEN 16
236 char daytime
[DAYTIME_LEN
]; /* The current time in human readable form,
237 * used for rotation-tracking messages. */
238 char hostname
[MAXHOSTNAMELEN
]; /* hostname */
240 const char *path_syslogpid
= _PATH_SYSLOGPID
;
242 static struct cflist
*get_worklist(char **files
);
243 static void parse_file(FILE *cf
, struct cflist
*work_p
, struct cflist
*glob_p
,
244 struct conf_entry
*defconf_p
, struct ilist
*inclist
);
245 static void add_to_queue(const char *fname
, struct ilist
*inclist
);
246 static char *sob(char *p
);
247 static char *son(char *p
);
248 static int isnumberstr(const char *);
249 static int isglobstr(const char *);
250 static char *missing_field(char *p
, char *errline
);
251 static void change_attrs(const char *, const struct conf_entry
*);
252 static const char *get_logfile_suffix(const char *logfile
);
253 static fk_entry
do_entry(struct conf_entry
*);
254 static fk_entry
do_rotate(const struct conf_entry
*);
255 static void do_sigwork(struct sigwork_entry
*);
256 static void do_zipwork(struct zipwork_entry
*);
257 static struct sigwork_entry
*
258 save_sigwork(const struct conf_entry
*);
259 static struct zipwork_entry
*
260 save_zipwork(const struct conf_entry
*, const struct
261 sigwork_entry
*, int, const char *);
262 static void set_swpid(struct sigwork_entry
*, const struct conf_entry
*);
263 static int sizefile(const char *);
264 static void expand_globs(struct cflist
*work_p
, struct cflist
*glob_p
);
265 static void free_clist(struct cflist
*list
);
266 static void free_entry(struct conf_entry
*ent
);
267 static struct conf_entry
*init_entry(const char *fname
,
268 struct conf_entry
*src_entry
);
269 static void parse_args(int argc
, char **argv
);
270 static int parse_doption(const char *doption
);
271 static void usage(void);
272 static int log_trim(const char *logname
, const struct conf_entry
*log_ent
);
273 static int age_old_log(char *file
);
274 static void savelog(char *from
, char *to
);
275 static void createdir(const struct conf_entry
*ent
, char *dirpart
);
276 static void createlog(const struct conf_entry
*ent
);
279 * All the following take a parameter of 'int', but expect values in the
280 * range of unsigned char. Define wrappers which take values of type 'char',
281 * whether signed or unsigned, and ensure they end up in the right range.
283 #define isdigitch(Anychar) isdigit((u_char)(Anychar))
284 #define isprintch(Anychar) isprint((u_char)(Anychar))
285 #define isspacech(Anychar) isspace((u_char)(Anychar))
286 #define tolowerch(Anychar) tolower((u_char)(Anychar))
289 main(int argc
, char **argv
)
291 struct cflist
*worklist
;
292 struct conf_entry
*p
;
293 struct sigwork_entry
*stmp
;
294 struct zipwork_entry
*ztmp
;
299 parse_args(argc
, argv
);
303 if (needroot
&& getuid() && geteuid())
304 errx(1, "must have root privs");
305 worklist
= get_worklist(argv
);
308 * Rotate all the files which need to be rotated. Note that
309 * some users have *hundreds* of entries in newsyslog.conf!
311 while (!STAILQ_EMPTY(worklist
)) {
312 p
= STAILQ_FIRST(worklist
);
313 STAILQ_REMOVE_HEAD(worklist
, cf_nextp
);
314 if (do_entry(p
) == FREE_ENT
)
319 * Send signals to any processes which need a signal to tell
320 * them to close and re-open the log file(s) we have rotated.
321 * Note that zipwork_entries include pointers to these
322 * sigwork_entry's, so we can not free the entries here.
324 if (!SLIST_EMPTY(&swhead
)) {
325 if (noaction
|| verbose
)
326 printf("Signal all daemon process(es)...\n");
327 SLIST_FOREACH(stmp
, &swhead
, sw_nextp
)
330 printf("\tsleep 10\n");
333 printf("Pause 10 seconds to allow daemon(s)"
334 " to close log file(s)\n");
339 * Compress all files that we're expected to compress, now
340 * that all processes should have closed the files which
343 if (!SLIST_EMPTY(&zwhead
)) {
344 if (noaction
|| verbose
)
345 printf("Compress all rotated log file(s)...\n");
346 while (!SLIST_EMPTY(&zwhead
)) {
347 ztmp
= SLIST_FIRST(&zwhead
);
349 SLIST_REMOVE_HEAD(&zwhead
, zw_nextp
);
353 /* Now free all the sigwork entries. */
354 while (!SLIST_EMPTY(&swhead
)) {
355 stmp
= SLIST_FIRST(&swhead
);
356 SLIST_REMOVE_HEAD(&swhead
, sw_nextp
);
360 while (wait(NULL
) > 0 || errno
== EINTR
)
365 static struct conf_entry
*
366 init_entry(const char *fname
, struct conf_entry
*src_entry
)
368 struct conf_entry
*tempwork
;
371 printf("\t--> [creating entry for %s]\n", fname
);
373 tempwork
= malloc(sizeof(struct conf_entry
));
374 if (tempwork
== NULL
)
375 err(1, "malloc of conf_entry for %s", fname
);
377 if (destdir
== NULL
|| fname
[0] != '/')
378 tempwork
->log
= strdup(fname
);
380 asprintf(&tempwork
->log
, "%s%s", destdir
, fname
);
381 if (tempwork
->log
== NULL
)
382 err(1, "strdup for %s", fname
);
384 if (src_entry
!= NULL
) {
385 tempwork
->pid_file
= NULL
;
386 if (src_entry
->pid_file
)
387 tempwork
->pid_file
= strdup(src_entry
->pid_file
);
388 tempwork
->r_reason
= NULL
;
389 tempwork
->firstcreate
= 0;
390 tempwork
->rotate
= 0;
391 tempwork
->fsize
= -1;
392 tempwork
->uid
= src_entry
->uid
;
393 tempwork
->gid
= src_entry
->gid
;
394 tempwork
->numlogs
= src_entry
->numlogs
;
395 tempwork
->trsize
= src_entry
->trsize
;
396 tempwork
->hours
= src_entry
->hours
;
397 tempwork
->trim_at
= NULL
;
398 if (src_entry
->trim_at
!= NULL
)
399 tempwork
->trim_at
= ptime_init(src_entry
->trim_at
);
400 tempwork
->permissions
= src_entry
->permissions
;
401 tempwork
->flags
= src_entry
->flags
;
402 tempwork
->compress
= src_entry
->compress
;
403 tempwork
->sig
= src_entry
->sig
;
404 tempwork
->def_cfg
= src_entry
->def_cfg
;
406 /* Initialize as a "do-nothing" entry */
407 tempwork
->pid_file
= NULL
;
408 tempwork
->r_reason
= NULL
;
409 tempwork
->firstcreate
= 0;
410 tempwork
->rotate
= 0;
411 tempwork
->fsize
= -1;
412 tempwork
->uid
= (uid_t
)-1;
413 tempwork
->gid
= (gid_t
)-1;
414 tempwork
->numlogs
= 1;
415 tempwork
->trsize
= -1;
416 tempwork
->hours
= -1;
417 tempwork
->trim_at
= NULL
;
418 tempwork
->permissions
= 0;
420 tempwork
->compress
= COMPRESS_NONE
;
421 tempwork
->sig
= SIGHUP
;
422 tempwork
->def_cfg
= 0;
429 free_entry(struct conf_entry
*ent
)
435 if (ent
->log
!= NULL
) {
437 printf("\t--> [freeing entry for %s]\n", ent
->log
);
442 if (ent
->pid_file
!= NULL
) {
444 ent
->pid_file
= NULL
;
447 if (ent
->r_reason
!= NULL
) {
449 ent
->r_reason
= NULL
;
452 if (ent
->trim_at
!= NULL
) {
453 ptime_free(ent
->trim_at
);
461 free_clist(struct cflist
*list
)
463 struct conf_entry
*ent
;
465 while (!STAILQ_EMPTY(list
)) {
466 ent
= STAILQ_FIRST(list
);
467 STAILQ_REMOVE_HEAD(list
, cf_nextp
);
476 do_entry(struct conf_entry
* ent
)
478 #define REASON_MAX 80
480 fk_entry free_or_keep
;
482 char temp_reason
[REASON_MAX
];
484 free_or_keep
= FREE_ENT
;
486 printf("%s <%d%s>: ", ent
->log
, ent
->numlogs
,
487 compress_type
[ent
->compress
].flag
);
488 ent
->fsize
= sizefile(ent
->log
);
489 modtime
= age_old_log(ent
->log
);
491 ent
->firstcreate
= 0;
492 if (ent
->fsize
< 0) {
494 * If either the C flag or the -C option was specified,
495 * and if we won't be creating the file, then have the
496 * verbose message include a hint as to why the file
497 * will not be created.
499 temp_reason
[0] = '\0';
501 ent
->firstcreate
= 1;
502 else if ((ent
->flags
& CE_CREATE
) && createlogs
)
503 ent
->firstcreate
= 1;
504 else if (ent
->flags
& CE_CREATE
)
505 strlcpy(temp_reason
, " (no -C option)", REASON_MAX
);
507 strlcpy(temp_reason
, " (no C flag)", REASON_MAX
);
509 if (ent
->firstcreate
) {
511 printf("does not exist -> will create.\n");
513 } else if (verbose
) {
514 printf("does not exist, skipped%s.\n", temp_reason
);
517 if (ent
->flags
& CE_TRIMAT
&& !force
&& !rotatereq
) {
518 diffsecs
= ptimeget_diff(timenow
, ent
->trim_at
);
519 if (diffsecs
< 0.0) {
520 /* trim_at is some time in the future. */
522 ptime_adjust4dst(ent
->trim_at
,
524 printf("--> will trim at %s",
525 ptimeget_ctime(ent
->trim_at
));
527 return (free_or_keep
);
528 } else if (diffsecs
>= 3600.0) {
530 * trim_at is more than an hour in the past,
531 * so find the next valid trim_at time, and
532 * tell the user what that will be.
534 if (verbose
&& dbg_at_times
)
535 printf("\n\t--> prev trim at %s\t",
536 ptimeget_ctime(ent
->trim_at
));
538 ptimeset_nxtime(ent
->trim_at
);
539 printf("--> will trim at %s",
540 ptimeget_ctime(ent
->trim_at
));
542 return (free_or_keep
);
543 } else if (verbose
&& noaction
&& dbg_at_times
) {
545 * If we are just debugging at-times, then
546 * a detailed message is helpful. Also
547 * skip "doing" any commands, since they
548 * would all be turned off by no-action.
550 printf("\n\t--> timematch at %s",
551 ptimeget_ctime(ent
->trim_at
));
552 return (free_or_keep
);
553 } else if (verbose
&& ent
->hours
<= 0) {
554 printf("--> time is up\n");
557 if (verbose
&& (ent
->trsize
> 0))
558 printf("size (Kb): %d [%d] ", ent
->fsize
, ent
->trsize
);
559 if (verbose
&& (ent
->hours
> 0))
560 printf(" age (hr): %d [%d] ", modtime
, ent
->hours
);
563 * Figure out if this logfile needs to be rotated.
565 temp_reason
[0] = '\0';
568 snprintf(temp_reason
, REASON_MAX
, " due to -R from %s",
572 snprintf(temp_reason
, REASON_MAX
, " due to -F request");
573 } else if ((ent
->trsize
> 0) && (ent
->fsize
>= ent
->trsize
)) {
575 snprintf(temp_reason
, REASON_MAX
, " due to size>%dK",
577 } else if (ent
->hours
<= 0 && (ent
->flags
& CE_TRIMAT
)) {
579 } else if ((ent
->hours
> 0) && ((modtime
>= ent
->hours
) ||
585 * If the file needs to be rotated, then rotate it.
587 if (ent
->rotate
&& !norotate
) {
588 if (temp_reason
[0] != '\0')
589 ent
->r_reason
= strdup(temp_reason
);
591 printf("--> trimming log....\n");
592 if (noaction
&& !verbose
)
593 printf("%s <%d%s>: trimming\n", ent
->log
,
595 compress_type
[ent
->compress
].flag
);
596 free_or_keep
= do_rotate(ent
);
599 printf("--> skipping\n");
602 return (free_or_keep
);
607 parse_args(int argc
, char **argv
)
612 timenow
= ptime_init(NULL
);
613 ptimeset_time(timenow
, time(NULL
));
614 strlcpy(daytime
, ptimeget_ctime(timenow
) + 4, DAYTIME_LEN
);
616 /* Let's get our hostname */
617 gethostname(hostname
, sizeof(hostname
));
619 /* Truncate domain */
620 if ((p
= strchr(hostname
, '.')) != NULL
)
623 /* Parse command line options. */
624 while ((ch
= getopt(argc
, argv
, "a:d:f:nrst:vCD:FNPR:S:")) != -1)
628 archdirname
= optarg
;
646 if (optarg
[0] == '\0' ||
647 strcmp(optarg
, "DEFAULT") == 0)
648 timefnamefmt
= strdup(DEFAULT_TIMEFNAME_FMT
);
650 timefnamefmt
= strdup(optarg
);
656 /* Useful for things like rc.diskless... */
661 * Set some debugging option. The specific option
662 * depends on the value of optarg. These options
663 * may come and go without notice or documentation.
665 if (parse_doption(optarg
))
680 requestor
= strdup(optarg
);
683 path_syslogpid
= optarg
;
685 case 'm': /* Used by OpenBSD for "monitor mode" */
691 if (force
&& norotate
) {
692 warnx("Only one of -F and -N may be specified.");
698 if (optind
== argc
) {
699 warnx("At least one filename must be given when -R is specified.");
703 /* Make sure "requestor" value is safe for a syslog message. */
704 for (p
= requestor
; *p
!= '\0'; p
++) {
705 if (!isprintch(*p
) && (*p
!= '\t'))
712 * Note that the 'daytime' variable is not changed.
713 * That is only used in messages that track when a
714 * logfile is rotated, and if a file *is* rotated,
715 * then it will still rotated at the "real now" time.
718 timenow
= dbg_timenow
;
719 fprintf(stderr
, "Debug: Running as if TimeNow is %s",
720 ptimeget_ctime(dbg_timenow
));
726 * These debugging options are mainly meant for developer use, such
727 * as writing regression-tests. They would not be needed by users
728 * during normal operation of newsyslog...
731 parse_doption(const char *doption
)
733 const char TN
[] = "TN=";
736 if (strncmp(doption
, TN
, sizeof(TN
) - 1) == 0) {
738 * The "TimeNow" debugging option. This might be off
739 * by an hour when crossing a timezone change.
741 dbg_timenow
= ptime_init(NULL
);
742 res
= ptime_relparse(dbg_timenow
, PTM_PARSE_ISO8601
,
743 time(NULL
), doption
+ sizeof(TN
) - 1);
745 warnx("Non-existent time specified on -D %s", doption
);
746 return (0); /* failure */
747 } else if (res
< 0) {
748 warnx("Malformed time given on -D %s", doption
);
749 return (0); /* failure */
751 return (1); /* successfully parsed */
755 if (strcmp(doption
, "ats") == 0) {
757 return (1); /* successfully parsed */
760 /* XXX - This check could probably be dropped. */
761 if ((strcmp(doption
, "neworder") == 0) || (strcmp(doption
, "oldorder")
763 warnx("NOTE: newsyslog always uses 'neworder'.");
764 return (1); /* successfully parsed */
767 warnx("Unknown -D (debug) option: '%s'", doption
);
768 return (0); /* failure */
776 "usage: newsyslog [-CFNnrsv] [-a directory] [-d directory] [-f config-file]\n"
777 " [-S pidfile] [-t timefmt ] [ [-R requestor] filename ... ]\n");
782 * Parse a configuration file and return a linked list of all the logs
783 * which should be processed.
785 static struct cflist
*
786 get_worklist(char **files
)
790 struct cflist
*cmdlist
, *filelist
, *globlist
;
791 struct conf_entry
*defconf
, *dupent
, *ent
;
792 struct ilist inclist
;
793 struct include_entry
*inc
;
797 STAILQ_INIT(&inclist
);
799 filelist
= malloc(sizeof(struct cflist
));
800 if (filelist
== NULL
)
801 err(1, "malloc of filelist");
802 STAILQ_INIT(filelist
);
803 globlist
= malloc(sizeof(struct cflist
));
804 if (globlist
== NULL
)
805 err(1, "malloc of globlist");
806 STAILQ_INIT(globlist
);
808 inc
= malloc(sizeof(struct include_entry
));
810 err(1, "malloc of inc");
812 if (inc
->file
== NULL
)
813 inc
->file
= _PATH_CONF
;
814 STAILQ_INSERT_TAIL(&inclist
, inc
, inc_nextp
);
816 STAILQ_FOREACH(inc
, &inclist
, inc_nextp
) {
817 if (strcmp(inc
->file
, "-") != 0)
818 f
= fopen(inc
->file
, "r");
821 inc
->file
= "<stdin>";
824 err(1, "%s", inc
->file
);
827 printf("Processing %s\n", inc
->file
);
828 parse_file(f
, filelist
, globlist
, defconf
, &inclist
);
833 * All config-file information has been read in and turned into
834 * a filelist and a globlist. If there were no specific files
835 * given on the run command, then the only thing left to do is to
836 * call a routine which finds all files matched by the globlist
837 * and adds them to the filelist. Then return the worklist.
839 if (*files
== NULL
) {
840 expand_globs(filelist
, globlist
);
841 free_clist(globlist
);
849 * If newsyslog was given a specific list of files to process,
850 * it may be that some of those files were not listed in any
851 * config file. Those unlisted files should get the default
852 * rotation action. First, create the default-rotation action
853 * if none was found in a system config file.
855 if (defconf
== NULL
) {
856 defconf
= init_entry(DEFAULT_MARKER
, NULL
);
857 defconf
->numlogs
= 3;
858 defconf
->trsize
= 50;
859 defconf
->permissions
= S_IRUSR
|S_IWUSR
;
863 * If newsyslog was run with a list of specific filenames,
864 * then create a new worklist which has only those files in
865 * it, picking up the rotation-rules for those files from
866 * the original filelist.
868 * XXX - Note that this will copy multiple rules for a single
869 * logfile, if multiple entries are an exact match for
870 * that file. That matches the historic behavior, but do
871 * we want to continue to allow it? If so, it should
872 * probably be handled more intelligently.
874 cmdlist
= malloc(sizeof(struct cflist
));
876 err(1, "malloc of cmdlist");
877 STAILQ_INIT(cmdlist
);
879 for (given
= files
; *given
; ++given
) {
881 * First try to find exact-matches for this given file.
884 STAILQ_FOREACH(ent
, filelist
, cf_nextp
) {
885 if (strcmp(ent
->log
, *given
) == 0) {
887 dupent
= init_entry(*given
, ent
);
888 STAILQ_INSERT_TAIL(cmdlist
, dupent
, cf_nextp
);
893 printf("\t+ Matched entry %s\n", *given
);
898 * There was no exact-match for this given file, so look
899 * for a "glob" entry which does match.
902 if (verbose
> 2 && globlist
!= NULL
)
903 printf("\t+ Checking globs for %s\n", *given
);
904 STAILQ_FOREACH(ent
, globlist
, cf_nextp
) {
905 fnres
= fnmatch(ent
->log
, *given
, FNM_PATHNAME
);
907 printf("\t+ = %d for pattern %s\n", fnres
,
911 dupent
= init_entry(*given
, ent
);
912 /* This new entry is not a glob! */
913 dupent
->flags
&= ~CE_GLOB
;
914 STAILQ_INSERT_TAIL(cmdlist
, dupent
, cf_nextp
);
915 /* Only allow a match to one glob-entry */
921 printf("\t+ Matched %s via %s\n", *given
,
927 * This given file was not found in any config file, so
928 * add a worklist item based on the default entry.
931 printf("\t+ No entry matched %s (will use %s)\n",
932 *given
, DEFAULT_MARKER
);
933 dupent
= init_entry(*given
, defconf
);
934 /* Mark that it was *not* found in a config file */
936 STAILQ_INSERT_TAIL(cmdlist
, dupent
, cf_nextp
);
940 * Free all the entries in the original work list, the list of
941 * glob entries, and the default entry.
943 free_clist(filelist
);
944 free_clist(globlist
);
947 /* And finally, return a worklist which matches the given files. */
952 * Expand the list of entries with filename patterns, and add all files
953 * which match those glob-entries onto the worklist.
956 expand_globs(struct cflist
*work_p
, struct cflist
*glob_p
)
961 struct conf_entry
*dupent
, *ent
, *globent
;
966 * The worklist contains all fully-specified (non-GLOB) names.
968 * Now expand the list of filename-pattern (GLOB) entries into
969 * a second list, which (by definition) will only match files
970 * that already exist. Do not add a glob-related entry for any
971 * file which already exists in the fully-specified list.
973 STAILQ_FOREACH(globent
, glob_p
, cf_nextp
) {
974 gres
= glob(globent
->log
, GLOB_NOCHECK
, NULL
, &pglob
);
976 warn("cannot expand pattern (%d): %s", gres
,
982 printf("\t+ Expanding pattern %s\n", globent
->log
);
983 for (i
= 0; i
< pglob
.gl_matchc
; i
++) {
984 mfname
= pglob
.gl_pathv
[i
];
986 /* See if this file already has a specific entry. */
988 STAILQ_FOREACH(ent
, work_p
, cf_nextp
) {
989 if (strcmp(mfname
, ent
->log
) == 0) {
997 /* Make sure the named matched is a file. */
998 gres
= lstat(mfname
, &st_fm
);
1000 /* Error on a file that glob() matched?!? */
1001 warn("Skipping %s - lstat() error", mfname
);
1004 if (!S_ISREG(st_fm
.st_mode
)) {
1005 /* We only rotate files! */
1007 printf("\t+ . skipping %s (!file)\n",
1013 printf("\t+ . add file %s\n", mfname
);
1014 dupent
= init_entry(mfname
, globent
);
1015 /* This new entry is not a glob! */
1016 dupent
->flags
&= ~CE_GLOB
;
1018 /* Add to the worklist. */
1019 STAILQ_INSERT_TAIL(work_p
, dupent
, cf_nextp
);
1023 printf("\t+ Done with pattern %s\n", globent
->log
);
1028 * Parse a configuration file and update a linked list of all the logs to
1032 parse_file(FILE *cf
, struct cflist
*work_p
, struct cflist
*glob_p
,
1033 struct conf_entry
*defconf_p
, struct ilist
*inclist
)
1035 char line
[BUFSIZ
], *parse
, *q
;
1036 char *cp
, *errline
, *group
;
1037 struct conf_entry
*working
;
1041 int eol
, ptm_opts
, res
, special
;
1045 while (fgets(line
, BUFSIZ
, cf
)) {
1046 if ((line
[0] == '\n') || (line
[0] == '#') ||
1047 (strlen(line
) == 0))
1049 if (errline
!= NULL
)
1051 errline
= strdup(line
);
1052 for (cp
= line
+ 1; *cp
!= '\0'; cp
++) {
1055 if (*(cp
- 1) == '\\') {
1064 q
= parse
= missing_field(sob(line
), errline
);
1067 errx(1, "malformed line (missing fields):\n%s",
1072 * Allow people to set debug options via the config file.
1073 * (NOTE: debug options are undocumented, and may disappear
1074 * at any time, etc).
1076 if (strcasecmp(DEBUG_MARKER
, q
) == 0) {
1077 q
= parse
= missing_field(sob(++parse
), errline
);
1080 warnx("debug line specifies no option:\n%s",
1087 } else if (strcasecmp(INCLUDE_MARKER
, q
) == 0) {
1089 printf("Found: %s", errline
);
1090 q
= parse
= missing_field(sob(++parse
), errline
);
1093 warnx("include line missing argument:\n%s",
1101 res
= glob(q
, GLOB_NOCHECK
, NULL
, &pglob
);
1103 warn("cannot expand pattern (%d): %s",
1109 printf("\t+ Expanding pattern %s\n", q
);
1111 for (i
= 0; i
< pglob
.gl_matchc
; i
++)
1112 add_to_queue(pglob
.gl_pathv
[i
],
1116 add_to_queue(q
, inclist
);
1121 working
= init_entry(q
, NULL
);
1122 if (strcasecmp(DEFAULT_MARKER
, q
) == 0) {
1124 if (defconf_p
!= NULL
) {
1125 warnx("Ignoring duplicate entry for %s!", q
);
1126 free_entry(working
);
1129 defconf_p
= working
;
1132 q
= parse
= missing_field(sob(++parse
), errline
);
1135 errx(1, "malformed line (missing fields):\n%s",
1138 if ((group
= strchr(q
, ':')) != NULL
||
1139 (group
= strrchr(q
, '.')) != NULL
) {
1142 if (!(isnumberstr(q
))) {
1143 if ((pwd
= getpwnam(q
)) == NULL
)
1145 "error in config file; unknown user:\n%s",
1147 working
->uid
= pwd
->pw_uid
;
1149 working
->uid
= atoi(q
);
1151 working
->uid
= (uid_t
)-1;
1155 if (!(isnumberstr(q
))) {
1156 if ((grp
= getgrnam(q
)) == NULL
)
1158 "error in config file; unknown group:\n%s",
1160 working
->gid
= grp
->gr_gid
;
1162 working
->gid
= atoi(q
);
1164 working
->gid
= (gid_t
)-1;
1166 q
= parse
= missing_field(sob(++parse
), errline
);
1169 errx(1, "malformed line (missing fields):\n%s",
1173 working
->uid
= (uid_t
)-1;
1174 working
->gid
= (gid_t
)-1;
1177 if (!sscanf(q
, "%o", &working
->permissions
))
1178 errx(1, "error in config file; bad permissions:\n%s",
1181 q
= parse
= missing_field(sob(++parse
), errline
);
1184 errx(1, "malformed line (missing fields):\n%s",
1187 if (!sscanf(q
, "%d", &working
->numlogs
) || working
->numlogs
< 0)
1188 errx(1, "error in config file; bad value for count of logs to save:\n%s",
1191 q
= parse
= missing_field(sob(++parse
), errline
);
1194 errx(1, "malformed line (missing fields):\n%s",
1198 working
->trsize
= atoi(q
);
1199 else if (strcmp(q
, "*") == 0)
1200 working
->trsize
= -1;
1202 warnx("Invalid value of '%s' for 'size' in line:\n%s",
1204 working
->trsize
= -1;
1208 working
->compress
= COMPRESS_NONE
;
1209 q
= parse
= missing_field(sob(++parse
), errline
);
1217 ul
= strtoul(q
, &ep
, 10);
1220 else if (*ep
== '*')
1221 working
->hours
= -1;
1222 else if (ul
> INT_MAX
)
1223 errx(1, "interval is too large:\n%s", errline
);
1225 working
->hours
= ul
;
1227 if (*ep
== '\0' || strcmp(ep
, "*") == 0)
1229 if (*ep
!= '@' && *ep
!= '$')
1230 errx(1, "malformed interval/at:\n%s", errline
);
1232 working
->flags
|= CE_TRIMAT
;
1233 working
->trim_at
= ptime_init(NULL
);
1234 ptm_opts
= PTM_PARSE_ISO8601
;
1236 ptm_opts
= PTM_PARSE_DWM
;
1237 ptm_opts
|= PTM_PARSE_MATCHDOM
;
1238 res
= ptime_relparse(working
->trim_at
, ptm_opts
,
1239 ptimeget_secs(timenow
), ep
+ 1);
1241 errx(1, "nonexistent time for 'at' value:\n%s",
1244 errx(1, "malformed 'at' value:\n%s", errline
);
1251 q
= parse
= sob(++parse
); /* Optional field */
1258 for (; q
&& *q
&& !isspacech(*q
); q
++) {
1259 switch (tolowerch(*q
)) {
1261 working
->flags
|= CE_BINARY
;
1265 * XXX - Ick! Ugly! Remove ASAP!
1266 * We want `c' and `C' for "create". But we
1267 * will temporarily treat `c' as `g', because
1268 * FreeBSD releases <= 4.8 have a typo of
1269 * checking ('G' || 'c') for CE_GLOB.
1272 warnx("Assuming 'g' for 'c' in flags for line:\n%s",
1274 warnx("The 'c' flag will eventually mean 'CREATE'");
1275 working
->flags
|= CE_GLOB
;
1278 working
->flags
|= CE_CREATE
;
1281 working
->flags
|= CE_NODUMP
;
1284 working
->flags
|= CE_GLOB
;
1287 working
->compress
= COMPRESS_BZIP2
;
1290 working
->flags
|= CE_NOSIGNAL
;
1293 working
->flags
|= CE_SIGNALGROUP
;
1296 /* Depreciated flag - keep for compatibility purposes */
1299 working
->compress
= COMPRESS_XZ
;
1302 working
->compress
= COMPRESS_GZIP
;
1306 case 'f': /* Used by OpenBSD for "CE_FOLLOW" */
1307 case 'm': /* Used by OpenBSD for "CE_MONITOR" */
1308 case 'p': /* Used by NetBSD for "CE_PLAIN0" */
1310 errx(1, "illegal flag in config file -- %c",
1318 q
= parse
= sob(++parse
); /* Optional field */
1325 working
->pid_file
= NULL
;
1328 working
->pid_file
= strdup(q
);
1329 else if (isdigit(*q
))
1333 "illegal pid file or signal number in config file:\n%s",
1339 q
= parse
= sob(++parse
); /* Optional field */
1340 *(parse
= son(parse
)) = '\0';
1343 working
->sig
= SIGHUP
;
1347 working
->sig
= atoi(q
);
1351 "illegal signal number in config file:\n%s",
1354 if (working
->sig
< 1 || working
->sig
>= NSIG
)
1359 * Finish figuring out what pid-file to use (if any) in
1360 * later processing if this logfile needs to be rotated.
1362 if ((working
->flags
& CE_NOSIGNAL
) == CE_NOSIGNAL
) {
1364 * This config-entry specified 'n' for nosignal,
1365 * see if it also specified an explicit pid_file.
1366 * This would be a pretty pointless combination.
1368 if (working
->pid_file
!= NULL
) {
1369 warnx("Ignoring '%s' because flag 'n' was specified in line:\n%s",
1370 working
->pid_file
, errline
);
1371 free(working
->pid_file
);
1372 working
->pid_file
= NULL
;
1374 } else if (working
->pid_file
== NULL
) {
1376 * This entry did not specify the 'n' flag, which
1377 * means it should signal syslogd unless it had
1378 * specified some other pid-file (and obviously the
1379 * syslog pid-file will not be for a process-group).
1380 * Also, we should only try to notify syslog if we
1383 if (working
->flags
& CE_SIGNALGROUP
) {
1384 warnx("Ignoring flag 'U' in line:\n%s",
1386 working
->flags
&= ~CE_SIGNALGROUP
;
1389 working
->pid_file
= strdup(path_syslogpid
);
1393 * Add this entry to the appropriate list of entries, unless
1394 * it was some kind of special entry (eg: <default>).
1397 ; /* Do not add to any list */
1398 } else if (working
->flags
& CE_GLOB
) {
1399 STAILQ_INSERT_TAIL(glob_p
, working
, cf_nextp
);
1401 STAILQ_INSERT_TAIL(work_p
, working
, cf_nextp
);
1404 if (errline
!= NULL
)
1409 missing_field(char *p
, char *errline
)
1413 errx(1, "missing field in config file:\n%s", errline
);
1418 * In our sort we return it in the reverse of what qsort normally
1419 * would do, as we want the newest files first. If we have two
1420 * entries with the same time we don't really care about order.
1422 * Support function for qsort() in delete_oldest_timelog().
1425 oldlog_entry_compare(const void *a
, const void *b
)
1427 const struct oldlog_entry
*ola
= a
, *olb
= b
;
1429 if (ola
->t
> olb
->t
)
1431 else if (ola
->t
< olb
->t
)
1438 * Delete the oldest logfiles, when using time based filenames.
1441 delete_oldest_timelog(const struct conf_entry
*ent
, const char *archive_dir
)
1443 char *logfname
, *s
, *dir
, errbuf
[80];
1444 int dirfd
, i
, logcnt
, max_logcnt
, valid
;
1445 struct oldlog_entry
*oldlogs
;
1446 size_t logfname_len
;
1452 oldlogs
= malloc(MAX_OLDLOGS
* sizeof(struct oldlog_entry
));
1453 max_logcnt
= MAX_OLDLOGS
;
1456 if (archive_dir
!= NULL
&& archive_dir
[0] != '\0')
1459 if ((cdir
= dirname(ent
->log
)) == NULL
)
1460 err(1, "dirname()");
1461 if ((dir
= strdup(cdir
)) == NULL
)
1464 if ((s
= basename(ent
->log
)) == NULL
)
1465 err(1, "basename()");
1466 if ((logfname
= strdup(s
)) == NULL
)
1468 logfname_len
= strlen(logfname
);
1469 if (strcmp(logfname
, "/") == 0)
1470 errx(1, "Invalid log filename - became '/'");
1473 printf("Searching for old logs in %s\n", dir
);
1475 /* First we create a 'list' of all archived logfiles */
1476 if ((dirp
= opendir(dir
)) == NULL
)
1477 err(1, "Cannot open log directory '%s'", dir
);
1478 dirfd
= dirfd(dirp
);
1479 while ((dp
= readdir(dirp
)) != NULL
) {
1480 if (dp
->d_type
!= DT_REG
)
1483 /* Ignore everything but files with our logfile prefix */
1484 if (strncmp(dp
->d_name
, logfname
, logfname_len
) != 0)
1486 /* Ignore the actual non-rotated logfile */
1487 if (dp
->d_namlen
== logfname_len
)
1490 * Make sure we created have found a logfile, so the
1491 * postfix is valid, IE format is: '.<time>(.[bg]z)?'.
1493 if (dp
->d_name
[logfname_len
] != '.') {
1495 printf("Ignoring %s which has unexpected "
1496 "extension '%s'\n", dp
->d_name
,
1497 &dp
->d_name
[logfname_len
]);
1500 if ((s
= strptime(&dp
->d_name
[logfname_len
+ 1],
1501 timefnamefmt
, &tm
)) == NULL
) {
1503 * We could special case "old" sequentially
1504 * named logfiles here, but we do not as that
1505 * would require special handling to decide
1506 * which one was the oldest compared to "new"
1507 * time based logfiles.
1510 printf("Ignoring %s which does not "
1511 "match time format\n", dp
->d_name
);
1515 for (int c
= 0; c
< COMPRESS_TYPES
; c
++)
1516 if (strcmp(s
, compress_type
[c
].suffix
) == 0)
1520 printf("Ignoring %s which has unexpected "
1521 "extension '%s'\n", dp
->d_name
, s
);
1526 * We should now have old an old rotated logfile, so
1527 * add it to the 'list'.
1529 if ((oldlogs
[logcnt
].t
= timegm(&tm
)) == -1)
1530 err(1, "Could not convert time string to time value");
1531 if ((oldlogs
[logcnt
].fname
= strdup(dp
->d_name
)) == NULL
)
1536 * It is very unlikely we ever run out of space in the
1537 * logfile array from the default size, but lets
1538 * handle it anyway...
1540 if (logcnt
>= max_logcnt
) {
1542 /* Detect integer overflow */
1543 if (max_logcnt
< logcnt
)
1544 errx(1, "Too many old logfiles found");
1545 oldlogs
= realloc(oldlogs
,
1546 max_logcnt
* sizeof(struct oldlog_entry
));
1547 if (oldlogs
== NULL
)
1548 err(1, "realloc()");
1552 /* Second, if needed we delete oldest archived logfiles */
1553 if (logcnt
> 0 && logcnt
>= ent
->numlogs
&& ent
->numlogs
> 1) {
1554 oldlogs
= realloc(oldlogs
, logcnt
*
1555 sizeof(struct oldlog_entry
));
1556 if (oldlogs
== NULL
)
1557 err(1, "realloc()");
1560 * We now sort the logs in the order of newest to
1561 * oldest. That way we can simply skip over the
1562 * number of records we want to keep.
1564 qsort(oldlogs
, logcnt
, sizeof(struct oldlog_entry
),
1565 oldlog_entry_compare
);
1566 for (i
= ent
->numlogs
- 1; i
< logcnt
; i
++) {
1568 printf("\trm -f %s/%s\n", dir
,
1570 else if (unlinkat(dirfd
, oldlogs
[i
].fname
, 0) != 0) {
1571 snprintf(errbuf
, sizeof(errbuf
),
1572 "Could not delete old logfile '%s'",
1577 } else if (verbose
> 1)
1578 printf("No old logs to delete for logfile %s\n", ent
->log
);
1580 /* Third, cleanup */
1582 for (i
= 0; i
< logcnt
; i
++) {
1583 assert(oldlogs
[i
].fname
!= NULL
);
1584 free(oldlogs
[i
].fname
);
1592 * Only add to the queue if the file hasn't already been added. This is
1593 * done to prevent circular include loops.
1596 add_to_queue(const char *fname
, struct ilist
*inclist
)
1598 struct include_entry
*inc
;
1600 STAILQ_FOREACH(inc
, inclist
, inc_nextp
) {
1601 if (strcmp(fname
, inc
->file
) == 0) {
1602 warnx("duplicate include detected: %s", fname
);
1607 inc
= malloc(sizeof(struct include_entry
));
1609 err(1, "malloc of inc");
1610 inc
->file
= strdup(fname
);
1613 printf("\t+ Adding %s to the processing queue.\n", fname
);
1615 STAILQ_INSERT_TAIL(inclist
, inc
, inc_nextp
);
1619 * Search for logfile and return its compression suffix (if supported)
1620 * The suffix detection is first-match in the order of compress_types
1622 * Note: if logfile without suffix exists (uncompressed, COMPRESS_NONE)
1623 * a zero-length string is returned
1626 get_logfile_suffix(const char *logfile
)
1629 char zfile
[MAXPATHLEN
];
1631 for (int c
= 0; c
< COMPRESS_TYPES
; c
++) {
1632 strlcpy(zfile
, logfile
, MAXPATHLEN
);
1633 strlcat(zfile
, compress_type
[c
].suffix
, MAXPATHLEN
);
1634 if (lstat(zfile
, &st
) == 0)
1635 return (compress_type
[c
].suffix
);
1641 do_rotate(const struct conf_entry
*ent
)
1643 char dirpart
[MAXPATHLEN
], namepart
[MAXPATHLEN
];
1644 char file1
[MAXPATHLEN
], file2
[MAXPATHLEN
];
1645 char zfile1
[MAXPATHLEN
], zfile2
[MAXPATHLEN
];
1646 const char *logfile_suffix
;
1647 char datetimestr
[30];
1648 int flags
, numlogs_c
;
1649 fk_entry free_or_keep
;
1650 struct sigwork_entry
*swork
;
1656 free_or_keep
= FREE_ENT
;
1661 /* build complete name of archive directory into dirpart */
1662 if (*archdirname
== '/') { /* absolute */
1663 strlcpy(dirpart
, archdirname
, sizeof(dirpart
));
1664 } else { /* relative */
1665 /* get directory part of logfile */
1666 strlcpy(dirpart
, ent
->log
, sizeof(dirpart
));
1667 if ((p
= strrchr(dirpart
, '/')) == NULL
)
1671 strlcat(dirpart
, archdirname
, sizeof(dirpart
));
1674 /* check if archive directory exists, if not, create it */
1675 if (lstat(dirpart
, &st
))
1676 createdir(ent
, dirpart
);
1678 /* get filename part of logfile */
1679 if ((p
= strrchr(ent
->log
, '/')) == NULL
)
1680 strlcpy(namepart
, ent
->log
, sizeof(namepart
));
1682 strlcpy(namepart
, p
+ 1, sizeof(namepart
));
1684 /* name of oldest log */
1685 snprintf(file1
, sizeof(file1
), "%s/%s.%d", dirpart
,
1686 namepart
, ent
->numlogs
);
1689 * Tell delete_oldest_timelog() we are not using an
1694 /* name of oldest log */
1695 snprintf(file1
, sizeof(file1
), "%s.%d", ent
->log
,
1699 /* Delete old logs */
1700 if (timefnamefmt
!= NULL
)
1701 delete_oldest_timelog(ent
, dirpart
);
1703 /* name of oldest log */
1704 for (int c
= 0; c
< COMPRESS_TYPES
; c
++) {
1705 snprintf(zfile1
, sizeof(zfile1
), "%s%s", file1
,
1706 compress_type
[c
].suffix
);
1708 printf("\trm -f %s\n", zfile1
);
1714 if (timefnamefmt
!= NULL
) {
1715 /* If time functions fails we can't really do any sensible */
1716 if (time(&now
) == (time_t)-1 ||
1717 localtime_r(&now
, &tm
) == NULL
)
1718 bzero(&tm
, sizeof(tm
));
1720 strftime(datetimestr
, sizeof(datetimestr
), timefnamefmt
, &tm
);
1722 snprintf(file1
, sizeof(file1
), "%s/%s.%s",
1723 dirpart
, namepart
, datetimestr
);
1725 snprintf(file1
, sizeof(file1
), "%s.%s",
1726 ent
->log
, datetimestr
);
1729 /* Don't run the code to move down logs */
1732 numlogs_c
= ent
->numlogs
; /* copy for countdown */
1734 /* Move down log files */
1735 while (numlogs_c
--) {
1737 strlcpy(file2
, file1
, sizeof(file2
));
1740 snprintf(file1
, sizeof(file1
), "%s/%s.%d",
1741 dirpart
, namepart
, numlogs_c
);
1743 snprintf(file1
, sizeof(file1
), "%s.%d",
1744 ent
->log
, numlogs_c
);
1746 logfile_suffix
= get_logfile_suffix(file1
);
1747 if (logfile_suffix
== NULL
)
1749 strlcpy(zfile1
, file1
, MAXPATHLEN
);
1750 strlcpy(zfile2
, file2
, MAXPATHLEN
);
1751 strlcat(zfile1
, logfile_suffix
, MAXPATHLEN
);
1752 strlcat(zfile2
, logfile_suffix
, MAXPATHLEN
);
1755 printf("\tmv %s %s\n", zfile1
, zfile2
);
1757 /* XXX - Ought to be checking for failure! */
1758 rename(zfile1
, zfile2
);
1760 change_attrs(zfile2
, ent
);
1763 if (ent
->numlogs
> 0) {
1766 * Note that savelog() may succeed with using link()
1767 * for the archtodir case, but there is no good way
1768 * of knowing if it will when doing "noaction", so
1769 * here we claim that it will have to do a copy...
1772 printf("\tcp %s %s\n", ent
->log
, file1
);
1774 printf("\tln %s %s\n", ent
->log
, file1
);
1776 if (!(flags
& CE_BINARY
)) {
1777 /* Report the trimming to the old log */
1778 log_trim(ent
->log
, ent
);
1780 savelog(ent
->log
, file1
);
1782 change_attrs(file1
, ent
);
1785 /* Create the new log file and move it into place */
1787 printf("Start new log...\n");
1791 * Save all signalling and file-compression to be done after log
1792 * files from all entries have been rotated. This way any one
1793 * process will not be sent the same signal multiple times when
1794 * multiple log files had to be rotated.
1797 if (ent
->pid_file
!= NULL
)
1798 swork
= save_sigwork(ent
);
1799 if (ent
->numlogs
> 0 && ent
->compress
> COMPRESS_NONE
) {
1801 * The zipwork_entry will include a pointer to this
1802 * conf_entry, so the conf_entry should not be freed.
1804 free_or_keep
= KEEP_ENT
;
1805 save_zipwork(ent
, swork
, ent
->fsize
, file1
);
1808 return (free_or_keep
);
1812 do_sigwork(struct sigwork_entry
*swork
)
1814 struct sigwork_entry
*nextsig
;
1817 if (!(swork
->sw_pidok
) || swork
->sw_pid
== 0)
1818 return; /* no work to do... */
1821 * If nosignal (-s) was specified, then do not signal any process.
1822 * Note that a nosignal request triggers a warning message if the
1823 * rotated logfile needs to be compressed, *unless* -R was also
1824 * specified. We assume that an `-sR' request came from a process
1825 * which writes to the logfile, and as such, we assume that process
1826 * has already made sure the logfile is not presently in use. This
1827 * just sets swork->sw_pidok to a special value, and do_zipwork
1828 * will print any necessary warning(s).
1832 swork
->sw_pidok
= -1;
1837 * Compute the pause between consecutive signals. Use a longer
1838 * sleep time if we will be sending two signals to the same
1839 * deamon or process-group.
1842 nextsig
= SLIST_NEXT(swork
, sw_nextp
);
1843 if (nextsig
!= NULL
) {
1844 if (swork
->sw_pid
== nextsig
->sw_pid
)
1851 printf("\tkill -%d %d \t\t# %s\n", swork
->sw_signum
,
1852 (int)swork
->sw_pid
, swork
->sw_fname
);
1854 printf("\tsleep %d\n", secs
);
1858 kres
= kill(swork
->sw_pid
, swork
->sw_signum
);
1861 * Assume that "no such process" (ESRCH) is something
1862 * to warn about, but is not an error. Presumably the
1863 * process which writes to the rotated log file(s) is
1864 * gone, in which case we should have no problem with
1865 * compressing the rotated log file(s).
1868 swork
->sw_pidok
= 0;
1869 warn("can't notify %s, pid %d", swork
->sw_pidtype
,
1870 (int)swork
->sw_pid
);
1873 printf("Notified %s pid %d = %s\n", swork
->sw_pidtype
,
1874 (int)swork
->sw_pid
, swork
->sw_fname
);
1877 printf("Pause %d second(s) between signals\n",
1885 do_zipwork(struct zipwork_entry
*zwork
)
1887 const char *pgm_name
, *pgm_path
;
1888 int errsav
, fcount
, zstatus
;
1890 char zresult
[MAXPATHLEN
];
1893 strlcpy(zresult
, zwork
->zw_fname
, sizeof(zresult
));
1894 if (zwork
!= NULL
&& zwork
->zw_conf
!= NULL
&&
1895 zwork
->zw_conf
->compress
> COMPRESS_NONE
)
1896 for (int c
= 1; c
< COMPRESS_TYPES
; c
++) {
1897 if (zwork
->zw_conf
->compress
== c
) {
1898 pgm_path
= compress_type
[c
].path
;
1900 compress_type
[c
].suffix
, sizeof(zresult
));
1904 if (pgm_path
== NULL
) {
1905 warnx("invalid entry for %s in do_zipwork", zwork
->zw_fname
);
1908 pgm_name
= strrchr(pgm_path
, '/');
1909 if (pgm_name
== NULL
)
1910 pgm_name
= pgm_path
;
1914 if (zwork
->zw_swork
!= NULL
&& zwork
->zw_swork
->sw_pidok
<= 0) {
1916 "log %s not compressed because daemon(s) not notified",
1918 change_attrs(zwork
->zw_fname
, zwork
->zw_conf
);
1923 printf("\t%s %s\n", pgm_name
, zwork
->zw_fname
);
1924 change_attrs(zresult
, zwork
->zw_conf
);
1930 while (pidzip
< 0) {
1932 * The fork failed. If the failure was due to a temporary
1933 * problem, then wait a short time and try it again.
1936 warn("fork() for `%s %s'", pgm_name
, zwork
->zw_fname
);
1937 if (errsav
!= EAGAIN
|| fcount
> 5)
1938 errx(1, "Exiting...");
1944 /* The child process executes the compression command */
1945 execl(pgm_path
, pgm_path
, "-f", zwork
->zw_fname
, NULL
);
1946 err(1, "execl(`%s -f %s')", pgm_path
, zwork
->zw_fname
);
1949 wpid
= waitpid(pidzip
, &zstatus
, 0);
1951 /* XXX - should this be a fatal error? */
1952 warn("%s: waitpid(%d)", pgm_path
, pidzip
);
1955 if (!WIFEXITED(zstatus
)) {
1956 warnx("`%s -f %s' did not terminate normally", pgm_name
,
1960 if (WEXITSTATUS(zstatus
)) {
1961 warnx("`%s -f %s' terminated with a non-zero status (%d)",
1962 pgm_name
, zwork
->zw_fname
, WEXITSTATUS(zstatus
));
1966 /* Compression was successful, set file attributes on the result. */
1967 change_attrs(zresult
, zwork
->zw_conf
);
1971 * Save information on any process we need to signal. Any single
1972 * process may need to be sent different signal-values for different
1973 * log files, but usually a single signal-value will cause the process
1974 * to close and re-open all of it's log files.
1976 static struct sigwork_entry
*
1977 save_sigwork(const struct conf_entry
*ent
)
1979 struct sigwork_entry
*sprev
, *stmp
;
1985 SLIST_FOREACH(stmp
, &swhead
, sw_nextp
) {
1986 ndiff
= strcmp(ent
->pid_file
, stmp
->sw_fname
);
1990 if (ent
->sig
== stmp
->sw_signum
)
1992 if (ent
->sig
> stmp
->sw_signum
) {
1999 if (stmp
!= NULL
&& ndiff
== 0)
2002 tmpsiz
= sizeof(struct sigwork_entry
) + strlen(ent
->pid_file
) + 1;
2003 stmp
= malloc(tmpsiz
);
2004 set_swpid(stmp
, ent
);
2005 stmp
->sw_signum
= ent
->sig
;
2006 strcpy(stmp
->sw_fname
, ent
->pid_file
);
2008 SLIST_INSERT_HEAD(&swhead
, stmp
, sw_nextp
);
2010 SLIST_INSERT_AFTER(sprev
, stmp
, sw_nextp
);
2015 * Save information on any file we need to compress. We may see the same
2016 * file multiple times, so check the full list to avoid duplicates. The
2017 * list itself is sorted smallest-to-largest, because that's the order we
2018 * want to compress the files. If the partition is very low on disk space,
2019 * then the smallest files are the most likely to compress, and compressing
2020 * them first will free up more space for the larger files.
2022 static struct zipwork_entry
*
2023 save_zipwork(const struct conf_entry
*ent
, const struct sigwork_entry
*swork
,
2024 int zsize
, const char *zipfname
)
2026 struct zipwork_entry
*zprev
, *ztmp
;
2030 /* Compute the size if the caller did not know it. */
2032 zsize
= sizefile(zipfname
);
2036 SLIST_FOREACH(ztmp
, &zwhead
, zw_nextp
) {
2037 ndiff
= strcmp(zipfname
, ztmp
->zw_fname
);
2040 if (zsize
> ztmp
->zw_fsize
)
2043 if (ztmp
!= NULL
&& ndiff
== 0)
2046 tmpsiz
= sizeof(struct zipwork_entry
) + strlen(zipfname
) + 1;
2047 ztmp
= malloc(tmpsiz
);
2048 ztmp
->zw_conf
= ent
;
2049 ztmp
->zw_swork
= swork
;
2050 ztmp
->zw_fsize
= zsize
;
2051 strcpy(ztmp
->zw_fname
, zipfname
);
2053 SLIST_INSERT_HEAD(&zwhead
, ztmp
, zw_nextp
);
2055 SLIST_INSERT_AFTER(zprev
, ztmp
, zw_nextp
);
2059 /* Send a signal to the pid specified by pidfile */
2061 set_swpid(struct sigwork_entry
*swork
, const struct conf_entry
*ent
)
2064 long minok
, maxok
, rval
;
2065 char *endp
, *linep
, line
[BUFSIZ
];
2069 swork
->sw_pidok
= 0;
2071 swork
->sw_pidtype
= "daemon";
2072 if (ent
->flags
& CE_SIGNALGROUP
) {
2074 * If we are expected to signal a process-group when
2075 * rotating this logfile, then the value read in should
2076 * be the negative of a valid process ID.
2080 swork
->sw_pidtype
= "process-group";
2083 f
= fopen(ent
->pid_file
, "r");
2085 if (errno
== ENOENT
&& enforcepid
== 0) {
2087 * Warn if the PID file doesn't exist, but do
2088 * not consider it an error. Most likely it
2089 * means the process has been terminated,
2090 * so it should be safe to rotate any log
2091 * files that the process would have been using.
2093 swork
->sw_pidok
= 1;
2094 warnx("pid file doesn't exist: %s", ent
->pid_file
);
2096 warn("can't open pid file: %s", ent
->pid_file
);
2100 if (fgets(line
, BUFSIZ
, f
) == NULL
) {
2102 * Warn if the PID file is empty, but do not consider
2103 * it an error. Most likely it means the process has
2104 * has terminated, so it should be safe to rotate any
2105 * log files that the process would have been using.
2107 if (feof(f
) && enforcepid
== 0) {
2108 swork
->sw_pidok
= 1;
2109 warnx("pid file is empty: %s", ent
->pid_file
);
2111 warn("can't read from pid file: %s", ent
->pid_file
);
2119 while (*linep
== ' ')
2121 rval
= strtol(linep
, &endp
, 10);
2122 if (*endp
!= '\0' && !isspacech(*endp
)) {
2123 warnx("pid file does not start with a valid number: %s",
2125 } else if (rval
< minok
|| rval
> maxok
) {
2126 warnx("bad value '%ld' for process number in %s",
2127 rval
, ent
->pid_file
);
2129 warnx("\t(expecting value between %ld and %ld)",
2132 swork
->sw_pidok
= 1;
2133 swork
->sw_pid
= rval
;
2139 /* Log the fact that the logs were turned over */
2141 log_trim(const char *logname
, const struct conf_entry
*log_ent
)
2146 if ((f
= fopen(logname
, "a")) == NULL
)
2149 if (log_ent
->def_cfg
)
2150 xtra
= " using <default> rule";
2151 if (log_ent
->firstcreate
)
2152 fprintf(f
, "%s %s newsyslog[%d]: logfile first created%s\n",
2153 daytime
, hostname
, (int) getpid(), xtra
);
2154 else if (log_ent
->r_reason
!= NULL
)
2155 fprintf(f
, "%s %s newsyslog[%d]: logfile turned over%s%s\n",
2156 daytime
, hostname
, (int) getpid(), log_ent
->r_reason
, xtra
);
2158 fprintf(f
, "%s %s newsyslog[%d]: logfile turned over%s\n",
2159 daytime
, hostname
, (int) getpid(), xtra
);
2160 if (fclose(f
) == EOF
)
2161 err(1, "log_trim: fclose");
2165 /* Return size in kilobytes of a file */
2167 sizefile(const char *file
)
2171 if (stat(file
, &sb
) < 0)
2173 return (kbytes(dbtob(sb
.st_blocks
)));
2176 /* Return the age of old log file (file.0) */
2178 age_old_log(char *file
)
2181 const char *logfile_suffix
;
2182 char tmp
[MAXPATHLEN
+ sizeof(".0") + COMPRESS_SUFFIX_MAXLEN
+ 1];
2187 /* build name of archive directory into tmp */
2188 if (*archdirname
== '/') { /* absolute */
2189 strlcpy(tmp
, archdirname
, sizeof(tmp
));
2190 } else { /* relative */
2191 /* get directory part of logfile */
2192 strlcpy(tmp
, file
, sizeof(tmp
));
2193 if ((p
= strrchr(tmp
, '/')) == NULL
)
2197 strlcat(tmp
, archdirname
, sizeof(tmp
));
2200 strlcat(tmp
, "/", sizeof(tmp
));
2202 /* get filename part of logfile */
2203 if ((p
= strrchr(file
, '/')) == NULL
)
2204 strlcat(tmp
, file
, sizeof(tmp
));
2206 strlcat(tmp
, p
+ 1, sizeof(tmp
));
2208 strlcpy(tmp
, file
, sizeof(tmp
));
2211 strlcat(tmp
, ".0", sizeof(tmp
));
2212 logfile_suffix
= get_logfile_suffix(tmp
);
2213 if (logfile_suffix
== NULL
)
2215 strlcat(tmp
, logfile_suffix
, sizeof(tmp
));
2216 if (stat(tmp
, &sb
) < 0)
2218 return ((int)(ptimeget_secs(timenow
) - sb
.st_mtime
+ 1800) / 3600);
2221 /* Skip Over Blanks */
2225 while (p
&& *p
&& isspace(*p
))
2230 /* Skip Over Non-Blanks */
2234 while (p
&& *p
&& !isspace(*p
))
2239 /* Check if string is actually a number */
2241 isnumberstr(const char *string
)
2244 if (!isdigitch(*string
++))
2250 /* Check if string contains a glob */
2252 isglobstr(const char *string
)
2256 while ((chr
= *string
++)) {
2257 if (chr
== '*' || chr
== '?' || chr
== '[')
2264 * Save the active log file under a new name. A link to the new name
2265 * is the quick-and-easy way to do this. If that fails (which it will
2266 * if the destination is on another partition), then make a copy of
2267 * the file to the new location.
2270 savelog(char *from
, char *to
)
2275 res
= link(from
, to
);
2279 if ((src
= fopen(from
, "r")) == NULL
)
2280 err(1, "can't fopen %s for reading", from
);
2281 if ((dst
= fopen(to
, "w")) == NULL
)
2282 err(1, "can't fopen %s for writing", to
);
2284 while ((c
= getc(src
)) != EOF
) {
2285 if ((putc(c
, dst
)) == EOF
)
2286 err(1, "error writing to %s", to
);
2290 err(1, "error reading from %s", from
);
2291 if ((fclose(src
)) != 0)
2292 err(1, "can't fclose %s", to
);
2293 if ((fclose(dst
)) != 0)
2294 err(1, "can't fclose %s", from
);
2297 /* create one or more directory components of a path */
2299 createdir(const struct conf_entry
*ent
, char *dirpart
)
2303 char mkdirpath
[MAXPATHLEN
];
2311 if (*s
!= '/' && *s
!= '\0')
2314 res
= lstat(mkdirpath
, &st
);
2317 printf("\tmkdir %s\n", mkdirpath
);
2319 res
= mkdir(mkdirpath
, 0755);
2321 err(1, "Error on mkdir(\"%s\") for -a",
2329 if (ent
->firstcreate
)
2330 printf("Created directory '%s' for new %s\n",
2333 printf("Created directory '%s' for -a\n", dirpart
);
2338 * Create a new log file, destroying any currently-existing version
2339 * of the log file in the process. If the caller wants a backup copy
2340 * of the file to exist, they should call 'link(logfile,logbackup)'
2341 * before calling this routine.
2344 createlog(const struct conf_entry
*ent
)
2348 char *realfile
, *slash
, tempfile
[MAXPATHLEN
];
2351 realfile
= ent
->log
;
2354 * If this log file is being created for the first time (-C option),
2355 * then it may also be true that the parent directory does not exist
2356 * yet. Check, and create that directory if it is missing.
2358 if (ent
->firstcreate
) {
2359 strlcpy(tempfile
, realfile
, sizeof(tempfile
));
2360 slash
= strrchr(tempfile
, '/');
2361 if (slash
!= NULL
) {
2363 failed
= stat(tempfile
, &st
);
2364 if (failed
&& errno
!= ENOENT
)
2365 err(1, "Error on stat(%s)", tempfile
);
2367 createdir(ent
, tempfile
);
2368 else if (!S_ISDIR(st
.st_mode
))
2369 errx(1, "%s exists but is not a directory",
2375 * First create an unused filename, so it can be chown'ed and
2376 * chmod'ed before it is moved into the real location. mkstemp
2377 * will create the file mode=600 & owned by us. Note that all
2378 * temp files will have a suffix of '.z<something>'.
2380 strlcpy(tempfile
, realfile
, sizeof(tempfile
));
2381 strlcat(tempfile
, ".zXXXXXX", sizeof(tempfile
));
2383 printf("\tmktemp %s\n", tempfile
);
2385 fd
= mkstemp(tempfile
);
2387 err(1, "can't mkstemp logfile %s", tempfile
);
2390 * Add status message to what will become the new log file.
2392 if (!(ent
->flags
& CE_BINARY
)) {
2393 if (log_trim(tempfile
, ent
))
2394 err(1, "can't add status message to log");
2398 /* Change the owner/group, if we are supposed to */
2399 if (ent
->uid
!= (uid_t
)-1 || ent
->gid
!= (gid_t
)-1) {
2401 printf("\tchown %u:%u %s\n", ent
->uid
, ent
->gid
,
2404 failed
= fchown(fd
, ent
->uid
, ent
->gid
);
2406 err(1, "can't fchown temp file %s", tempfile
);
2410 /* Turn on NODUMP if it was requested in the config-file. */
2411 if (ent
->flags
& CE_NODUMP
) {
2413 printf("\tchflags nodump %s\n", tempfile
);
2415 failed
= fchflags(fd
, UF_NODUMP
);
2417 warn("log_trim: fchflags(NODUMP)");
2423 * Note that if the real logfile still exists, and if the call
2424 * to rename() fails, then "neither the old file nor the new
2425 * file shall be changed or created" (to quote the standard).
2426 * If the call succeeds, then the file will be replaced without
2427 * any window where some other process might find that the file
2429 * XXX - ? It may be that for some error conditions, we could
2430 * retry by first removing the realfile and then renaming.
2433 printf("\tchmod %o %s\n", ent
->permissions
, tempfile
);
2434 printf("\tmv %s %s\n", tempfile
, realfile
);
2436 failed
= fchmod(fd
, ent
->permissions
);
2438 err(1, "can't fchmod temp file '%s'", tempfile
);
2439 failed
= rename(tempfile
, realfile
);
2441 err(1, "can't mv %s to %s", tempfile
, realfile
);
2449 * Change the attributes of a given filename to what was specified in
2450 * the newsyslog.conf entry. This routine is only called for files
2451 * that newsyslog expects that it has created, and thus it is a fatal
2452 * error if this routine finds that the file does not exist.
2455 change_attrs(const char *fname
, const struct conf_entry
*ent
)
2460 printf("\tchmod %o %s\n", ent
->permissions
, fname
);
2462 if (ent
->uid
!= (uid_t
)-1 || ent
->gid
!= (gid_t
)-1)
2463 printf("\tchown %u:%u %s\n",
2464 ent
->uid
, ent
->gid
, fname
);
2466 if (ent
->flags
& CE_NODUMP
)
2467 printf("\tchflags nodump %s\n", fname
);
2471 failed
= chmod(fname
, ent
->permissions
);
2474 err(1, "chmod(%s) in change_attrs", fname
);
2475 warn("change_attrs couldn't chmod(%s)", fname
);
2478 if (ent
->uid
!= (uid_t
)-1 || ent
->gid
!= (gid_t
)-1) {
2479 failed
= chown(fname
, ent
->uid
, ent
->gid
);
2481 warn("can't chown %s", fname
);
2484 if (ent
->flags
& CE_NODUMP
) {
2485 failed
= chflags(fname
, UF_NODUMP
);
2487 warn("can't chflags %s NODUMP", fname
);