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>
84 #include "pathnames.h"
88 * Compression suffixes
90 #ifndef COMPRESS_SUFFIX_GZ
91 #define COMPRESS_SUFFIX_GZ ".gz"
94 #ifndef COMPRESS_SUFFIX_BZ2
95 #define COMPRESS_SUFFIX_BZ2 ".bz2"
98 #ifndef COMPRESS_SUFFIX_XZ
99 #define COMPRESS_SUFFIX_XZ ".xz"
102 #define COMPRESS_SUFFIX_MAXLEN MAX(MAX(sizeof(COMPRESS_SUFFIX_GZ),sizeof(COMPRESS_SUFFIX_BZ2)),sizeof(COMPRESS_SUFFIX_XZ))
107 #define COMPRESS_TYPES 4 /* Number of supported compression types */
109 #define COMPRESS_NONE 0
110 #define COMPRESS_GZIP 1
111 #define COMPRESS_BZIP2 2
112 #define COMPRESS_XZ 3
115 * Bit-values for the 'flags' parsed from a config-file entry.
117 #define CE_BINARY 0x0008 /* Logfile is in binary, do not add status */
118 /* messages to logfile(s) when rotating. */
119 #define CE_NOSIGNAL 0x0010 /* There is no process to signal when */
120 /* trimming this file. */
121 #define CE_TRIMAT 0x0020 /* trim file at a specific time. */
122 #define CE_GLOB 0x0040 /* name of the log is file name pattern. */
123 #define CE_SIGNALGROUP 0x0080 /* Signal a process-group instead of a single */
124 /* process when trimming this file. */
125 #define CE_CREATE 0x0100 /* Create the log file if it does not exist. */
126 #define CE_NODUMP 0x0200 /* Set 'nodump' on newly created log file. */
128 #define MIN_PID 5 /* Don't touch pids lower than this */
129 #define MAX_PID PID_MAX /* was lower, see /usr/include/sys/proc.h */
131 #define kbytes(size) (((size) + 1023) >> 10)
133 #define DEFAULT_MARKER "<default>"
134 #define DEBUG_MARKER "<debug>"
135 #define INCLUDE_MARKER "<include>"
136 #define DEFAULT_TIMEFNAME_FMT "%Y%m%dT%H%M%S"
138 #define MAX_OLDLOGS 65536 /* Default maximum number of old logfiles */
140 struct compress_types
{
141 const char *flag
; /* Flag in configuration file */
142 const char *suffix
; /* Compression suffix */
143 const char *path
; /* Path to compression program */
146 const struct compress_types compress_type
[COMPRESS_TYPES
] = {
147 { "", "", "" }, /* no compression */
148 { "Z", COMPRESS_SUFFIX_GZ
, _PATH_GZIP
}, /* gzip compression */
149 { "J", COMPRESS_SUFFIX_BZ2
, _PATH_BZIP2
}, /* bzip2 compression */
150 { "X", COMPRESS_SUFFIX_XZ
, _PATH_XZ
} /* xz compression */
154 STAILQ_ENTRY(conf_entry
) cf_nextp
;
155 char *log
; /* Name of the log */
156 char *pid_file
; /* PID file */
157 char *r_reason
; /* The reason this file is being rotated */
158 int firstcreate
; /* Creating log for the first time (-C). */
159 int rotate
; /* Non-zero if this file should be rotated */
160 int fsize
; /* size found for the log file */
161 uid_t uid
; /* Owner of log */
162 gid_t gid
; /* Group of log */
163 int numlogs
; /* Number of logs to keep */
164 int trsize
; /* Size cutoff to trigger trimming the log */
165 int hours
; /* Hours between log trimming */
166 struct ptime_data
*trim_at
; /* Specific time to do trimming */
167 unsigned int permissions
; /* File permissions on the log */
168 int flags
; /* CE_BINARY */
169 int compress
; /* Compression */
170 int sig
; /* Signal to send */
171 int def_cfg
; /* Using the <default> rule for this file */
174 struct sigwork_entry
{
175 SLIST_ENTRY(sigwork_entry
) sw_nextp
;
176 int sw_signum
; /* the signal to send */
177 int sw_pidok
; /* true if pid value is valid */
178 pid_t sw_pid
; /* the process id from the PID file */
179 const char *sw_pidtype
; /* "daemon" or "process group" */
180 char sw_fname
[1]; /* file the PID was read from */
183 struct zipwork_entry
{
184 SLIST_ENTRY(zipwork_entry
) zw_nextp
;
185 const struct conf_entry
*zw_conf
; /* for chown/perm/flag info */
186 const struct sigwork_entry
*zw_swork
; /* to know success of signal */
187 int zw_fsize
; /* size of the file to compress */
188 char zw_fname
[1]; /* the file to compress */
191 struct include_entry
{
192 STAILQ_ENTRY(include_entry
) inc_nextp
;
193 const char *file
; /* Name of file to process */
196 struct oldlog_entry
{
197 char *fname
; /* Filename of the log file */
198 time_t t
; /* Parsed timestamp of the logfile */
205 STAILQ_HEAD(cflist
, conf_entry
);
206 SLIST_HEAD(swlisthead
, sigwork_entry
) swhead
= SLIST_HEAD_INITIALIZER(swhead
);
207 SLIST_HEAD(zwlisthead
, zipwork_entry
) zwhead
= SLIST_HEAD_INITIALIZER(zwhead
);
208 STAILQ_HEAD(ilist
, include_entry
);
210 int dbg_at_times
; /* -D Show details of 'trim_at' code */
212 int archtodir
= 0; /* Archive old logfiles to other directory */
213 int createlogs
; /* Create (non-GLOB) logfiles which do not */
214 /* already exist. 1=='for entries with */
215 /* C flag', 2=='for all entries'. */
216 int verbose
= 0; /* Print out what's going on */
217 int needroot
= 1; /* Root privs are necessary */
218 int noaction
= 0; /* Don't do anything, just show it */
219 int norotate
= 0; /* Don't rotate */
220 int nosignal
; /* Do not send any signals */
221 int enforcepid
= 0; /* If PID file does not exist or empty, do nothing */
222 int force
= 0; /* Force the trim no matter what */
223 int rotatereq
= 0; /* -R = Always rotate the file(s) as given */
224 /* on the command (this also requires */
225 /* that a list of files *are* given on */
226 /* the run command). */
227 char *requestor
; /* The name given on a -R request */
228 char *timefnamefmt
= NULL
; /* Use time based filenames instead of .0 etc */
229 char *archdirname
; /* Directory path to old logfiles archive */
230 char *destdir
= NULL
; /* Directory to treat at root for logs */
231 const char *conf
; /* Configuration file to use */
233 struct ptime_data
*dbg_timenow
; /* A "timenow" value set via -D option */
234 struct ptime_data
*timenow
; /* The time to use for checking at-fields */
236 #define DAYTIME_LEN 16
237 char daytime
[DAYTIME_LEN
]; /* The current time in human readable form,
238 * used for rotation-tracking messages. */
239 char hostname
[MAXHOSTNAMELEN
]; /* hostname */
241 const char *path_syslogpid
= _PATH_SYSLOGPID
;
243 static struct cflist
*get_worklist(char **files
);
244 static void parse_file(FILE *cf
, struct cflist
*work_p
, struct cflist
*glob_p
,
245 struct conf_entry
*defconf_p
, struct ilist
*inclist
);
246 static void add_to_queue(const char *fname
, struct ilist
*inclist
);
247 static char *sob(char *p
);
248 static char *son(char *p
);
249 static int isnumberstr(const char *);
250 static int isglobstr(const char *);
251 static char *missing_field(char *p
, char *errline
);
252 static void change_attrs(const char *, const struct conf_entry
*);
253 static const char *get_logfile_suffix(const char *logfile
);
254 static fk_entry
do_entry(struct conf_entry
*);
255 static fk_entry
do_rotate(const struct conf_entry
*);
256 static void do_sigwork(struct sigwork_entry
*);
257 static void do_zipwork(struct zipwork_entry
*);
258 static struct sigwork_entry
*
259 save_sigwork(const struct conf_entry
*);
260 static struct zipwork_entry
*
261 save_zipwork(const struct conf_entry
*, const struct
262 sigwork_entry
*, int, const char *);
263 static void set_swpid(struct sigwork_entry
*, const struct conf_entry
*);
264 static int sizefile(const char *);
265 static void expand_globs(struct cflist
*work_p
, struct cflist
*glob_p
);
266 static void free_clist(struct cflist
*list
);
267 static void free_entry(struct conf_entry
*ent
);
268 static struct conf_entry
*init_entry(const char *fname
,
269 struct conf_entry
*src_entry
);
270 static void parse_args(int argc
, char **argv
);
271 static int parse_doption(const char *doption
);
272 static void usage(void);
273 static int log_trim(const char *logname
, const struct conf_entry
*log_ent
);
274 static int age_old_log(char *file
);
275 static void savelog(char *from
, char *to
);
276 static void createdir(const struct conf_entry
*ent
, char *dirpart
);
277 static void createlog(const struct conf_entry
*ent
);
280 * All the following take a parameter of 'int', but expect values in the
281 * range of unsigned char. Define wrappers which take values of type 'char',
282 * whether signed or unsigned, and ensure they end up in the right range.
284 #define isdigitch(Anychar) isdigit((u_char)(Anychar))
285 #define isprintch(Anychar) isprint((u_char)(Anychar))
286 #define isspacech(Anychar) isspace((u_char)(Anychar))
287 #define tolowerch(Anychar) tolower((u_char)(Anychar))
290 main(int argc
, char **argv
)
292 struct cflist
*worklist
;
293 struct conf_entry
*p
;
294 struct sigwork_entry
*stmp
;
295 struct zipwork_entry
*ztmp
;
300 parse_args(argc
, argv
);
304 if (needroot
&& getuid() && geteuid())
305 errx(1, "must have root privs");
306 worklist
= get_worklist(argv
);
309 * Rotate all the files which need to be rotated. Note that
310 * some users have *hundreds* of entries in newsyslog.conf!
312 while (!STAILQ_EMPTY(worklist
)) {
313 p
= STAILQ_FIRST(worklist
);
314 STAILQ_REMOVE_HEAD(worklist
, cf_nextp
);
315 if (do_entry(p
) == FREE_ENT
)
320 * Send signals to any processes which need a signal to tell
321 * them to close and re-open the log file(s) we have rotated.
322 * Note that zipwork_entries include pointers to these
323 * sigwork_entry's, so we can not free the entries here.
325 if (!SLIST_EMPTY(&swhead
)) {
326 if (noaction
|| verbose
)
327 printf("Signal all daemon process(es)...\n");
328 SLIST_FOREACH(stmp
, &swhead
, sw_nextp
)
331 printf("\tsleep 10\n");
334 printf("Pause 10 seconds to allow daemon(s)"
335 " to close log file(s)\n");
340 * Compress all files that we're expected to compress, now
341 * that all processes should have closed the files which
344 if (!SLIST_EMPTY(&zwhead
)) {
345 if (noaction
|| verbose
)
346 printf("Compress all rotated log file(s)...\n");
347 while (!SLIST_EMPTY(&zwhead
)) {
348 ztmp
= SLIST_FIRST(&zwhead
);
350 SLIST_REMOVE_HEAD(&zwhead
, zw_nextp
);
354 /* Now free all the sigwork entries. */
355 while (!SLIST_EMPTY(&swhead
)) {
356 stmp
= SLIST_FIRST(&swhead
);
357 SLIST_REMOVE_HEAD(&swhead
, sw_nextp
);
361 while (wait(NULL
) > 0 || errno
== EINTR
)
366 static struct conf_entry
*
367 init_entry(const char *fname
, struct conf_entry
*src_entry
)
369 struct conf_entry
*tempwork
;
372 printf("\t--> [creating entry for %s]\n", fname
);
374 tempwork
= malloc(sizeof(struct conf_entry
));
375 if (tempwork
== NULL
)
376 err(1, "malloc of conf_entry for %s", fname
);
378 if (destdir
== NULL
|| fname
[0] != '/')
379 tempwork
->log
= strdup(fname
);
381 asprintf(&tempwork
->log
, "%s%s", destdir
, fname
);
382 if (tempwork
->log
== NULL
)
383 err(1, "strdup for %s", fname
);
385 if (src_entry
!= NULL
) {
386 tempwork
->pid_file
= NULL
;
387 if (src_entry
->pid_file
)
388 tempwork
->pid_file
= strdup(src_entry
->pid_file
);
389 tempwork
->r_reason
= NULL
;
390 tempwork
->firstcreate
= 0;
391 tempwork
->rotate
= 0;
392 tempwork
->fsize
= -1;
393 tempwork
->uid
= src_entry
->uid
;
394 tempwork
->gid
= src_entry
->gid
;
395 tempwork
->numlogs
= src_entry
->numlogs
;
396 tempwork
->trsize
= src_entry
->trsize
;
397 tempwork
->hours
= src_entry
->hours
;
398 tempwork
->trim_at
= NULL
;
399 if (src_entry
->trim_at
!= NULL
)
400 tempwork
->trim_at
= ptime_init(src_entry
->trim_at
);
401 tempwork
->permissions
= src_entry
->permissions
;
402 tempwork
->flags
= src_entry
->flags
;
403 tempwork
->compress
= src_entry
->compress
;
404 tempwork
->sig
= src_entry
->sig
;
405 tempwork
->def_cfg
= src_entry
->def_cfg
;
407 /* Initialize as a "do-nothing" entry */
408 tempwork
->pid_file
= NULL
;
409 tempwork
->r_reason
= NULL
;
410 tempwork
->firstcreate
= 0;
411 tempwork
->rotate
= 0;
412 tempwork
->fsize
= -1;
413 tempwork
->uid
= (uid_t
)-1;
414 tempwork
->gid
= (gid_t
)-1;
415 tempwork
->numlogs
= 1;
416 tempwork
->trsize
= -1;
417 tempwork
->hours
= -1;
418 tempwork
->trim_at
= NULL
;
419 tempwork
->permissions
= 0;
421 tempwork
->compress
= COMPRESS_NONE
;
422 tempwork
->sig
= SIGHUP
;
423 tempwork
->def_cfg
= 0;
430 free_entry(struct conf_entry
*ent
)
436 if (ent
->log
!= NULL
) {
438 printf("\t--> [freeing entry for %s]\n", ent
->log
);
443 if (ent
->pid_file
!= NULL
) {
445 ent
->pid_file
= NULL
;
448 if (ent
->r_reason
!= NULL
) {
450 ent
->r_reason
= NULL
;
453 if (ent
->trim_at
!= NULL
) {
454 ptime_free(ent
->trim_at
);
462 free_clist(struct cflist
*list
)
464 struct conf_entry
*ent
;
466 while (!STAILQ_EMPTY(list
)) {
467 ent
= STAILQ_FIRST(list
);
468 STAILQ_REMOVE_HEAD(list
, cf_nextp
);
477 do_entry(struct conf_entry
* ent
)
479 #define REASON_MAX 80
481 fk_entry free_or_keep
;
483 char temp_reason
[REASON_MAX
];
485 free_or_keep
= FREE_ENT
;
487 printf("%s <%d%s>: ", ent
->log
, ent
->numlogs
,
488 compress_type
[ent
->compress
].flag
);
489 ent
->fsize
= sizefile(ent
->log
);
490 modtime
= age_old_log(ent
->log
);
492 ent
->firstcreate
= 0;
493 if (ent
->fsize
< 0) {
495 * If either the C flag or the -C option was specified,
496 * and if we won't be creating the file, then have the
497 * verbose message include a hint as to why the file
498 * will not be created.
500 temp_reason
[0] = '\0';
502 ent
->firstcreate
= 1;
503 else if ((ent
->flags
& CE_CREATE
) && createlogs
)
504 ent
->firstcreate
= 1;
505 else if (ent
->flags
& CE_CREATE
)
506 strlcpy(temp_reason
, " (no -C option)", REASON_MAX
);
508 strlcpy(temp_reason
, " (no C flag)", REASON_MAX
);
510 if (ent
->firstcreate
) {
512 printf("does not exist -> will create.\n");
514 } else if (verbose
) {
515 printf("does not exist, skipped%s.\n", temp_reason
);
518 if (ent
->flags
& CE_TRIMAT
&& !force
&& !rotatereq
) {
519 diffsecs
= ptimeget_diff(timenow
, ent
->trim_at
);
520 if (diffsecs
< 0.0) {
521 /* trim_at is some time in the future. */
523 ptime_adjust4dst(ent
->trim_at
,
525 printf("--> will trim at %s",
526 ptimeget_ctime(ent
->trim_at
));
528 return (free_or_keep
);
529 } else if (diffsecs
>= 3600.0) {
531 * trim_at is more than an hour in the past,
532 * so find the next valid trim_at time, and
533 * tell the user what that will be.
535 if (verbose
&& dbg_at_times
)
536 printf("\n\t--> prev trim at %s\t",
537 ptimeget_ctime(ent
->trim_at
));
539 ptimeset_nxtime(ent
->trim_at
);
540 printf("--> will trim at %s",
541 ptimeget_ctime(ent
->trim_at
));
543 return (free_or_keep
);
544 } else if (verbose
&& noaction
&& dbg_at_times
) {
546 * If we are just debugging at-times, then
547 * a detailed message is helpful. Also
548 * skip "doing" any commands, since they
549 * would all be turned off by no-action.
551 printf("\n\t--> timematch at %s",
552 ptimeget_ctime(ent
->trim_at
));
553 return (free_or_keep
);
554 } else if (verbose
&& ent
->hours
<= 0) {
555 printf("--> time is up\n");
558 if (verbose
&& (ent
->trsize
> 0))
559 printf("size (Kb): %d [%d] ", ent
->fsize
, ent
->trsize
);
560 if (verbose
&& (ent
->hours
> 0))
561 printf(" age (hr): %d [%d] ", modtime
, ent
->hours
);
564 * Figure out if this logfile needs to be rotated.
566 temp_reason
[0] = '\0';
569 snprintf(temp_reason
, REASON_MAX
, " due to -R from %s",
573 snprintf(temp_reason
, REASON_MAX
, " due to -F request");
574 } else if ((ent
->trsize
> 0) && (ent
->fsize
>= ent
->trsize
)) {
576 snprintf(temp_reason
, REASON_MAX
, " due to size>%dK",
578 } else if (ent
->hours
<= 0 && (ent
->flags
& CE_TRIMAT
)) {
580 } else if ((ent
->hours
> 0) && ((modtime
>= ent
->hours
) ||
586 * If the file needs to be rotated, then rotate it.
588 if (ent
->rotate
&& !norotate
) {
589 if (temp_reason
[0] != '\0')
590 ent
->r_reason
= strdup(temp_reason
);
592 printf("--> trimming log....\n");
593 if (noaction
&& !verbose
)
594 printf("%s <%d%s>: trimming\n", ent
->log
,
596 compress_type
[ent
->compress
].flag
);
597 free_or_keep
= do_rotate(ent
);
600 printf("--> skipping\n");
603 return (free_or_keep
);
608 parse_args(int argc
, char **argv
)
613 timenow
= ptime_init(NULL
);
614 ptimeset_time(timenow
, time(NULL
));
615 strlcpy(daytime
, ptimeget_ctime(timenow
) + 4, DAYTIME_LEN
);
617 /* Let's get our hostname */
618 gethostname(hostname
, sizeof(hostname
));
620 /* Truncate domain */
621 if ((p
= strchr(hostname
, '.')) != NULL
)
624 /* Parse command line options. */
625 while ((ch
= getopt(argc
, argv
, "a:d:f:nrst:vCD:FNPR:S:")) != -1)
629 archdirname
= optarg
;
647 if (optarg
[0] == '\0' ||
648 strcmp(optarg
, "DEFAULT") == 0)
649 timefnamefmt
= strdup(DEFAULT_TIMEFNAME_FMT
);
651 timefnamefmt
= strdup(optarg
);
657 /* Useful for things like rc.diskless... */
662 * Set some debugging option. The specific option
663 * depends on the value of optarg. These options
664 * may come and go without notice or documentation.
666 if (parse_doption(optarg
))
681 requestor
= strdup(optarg
);
684 path_syslogpid
= optarg
;
686 case 'm': /* Used by OpenBSD for "monitor mode" */
692 if (force
&& norotate
) {
693 warnx("Only one of -F and -N may be specified.");
699 if (optind
== argc
) {
700 warnx("At least one filename must be given when -R is specified.");
704 /* Make sure "requestor" value is safe for a syslog message. */
705 for (p
= requestor
; *p
!= '\0'; p
++) {
706 if (!isprintch(*p
) && (*p
!= '\t'))
713 * Note that the 'daytime' variable is not changed.
714 * That is only used in messages that track when a
715 * logfile is rotated, and if a file *is* rotated,
716 * then it will still rotated at the "real now" time.
719 timenow
= dbg_timenow
;
720 fprintf(stderr
, "Debug: Running as if TimeNow is %s",
721 ptimeget_ctime(dbg_timenow
));
727 * These debugging options are mainly meant for developer use, such
728 * as writing regression-tests. They would not be needed by users
729 * during normal operation of newsyslog...
732 parse_doption(const char *doption
)
734 const char TN
[] = "TN=";
737 if (strncmp(doption
, TN
, sizeof(TN
) - 1) == 0) {
739 * The "TimeNow" debugging option. This might be off
740 * by an hour when crossing a timezone change.
742 dbg_timenow
= ptime_init(NULL
);
743 res
= ptime_relparse(dbg_timenow
, PTM_PARSE_ISO8601
,
744 time(NULL
), doption
+ sizeof(TN
) - 1);
746 warnx("Non-existent time specified on -D %s", doption
);
747 return (0); /* failure */
748 } else if (res
< 0) {
749 warnx("Malformed time given on -D %s", doption
);
750 return (0); /* failure */
752 return (1); /* successfully parsed */
756 if (strcmp(doption
, "ats") == 0) {
758 return (1); /* successfully parsed */
761 /* XXX - This check could probably be dropped. */
762 if ((strcmp(doption
, "neworder") == 0) || (strcmp(doption
, "oldorder")
764 warnx("NOTE: newsyslog always uses 'neworder'.");
765 return (1); /* successfully parsed */
768 warnx("Unknown -D (debug) option: '%s'", doption
);
769 return (0); /* failure */
777 "usage: newsyslog [-CFNnrsv] [-a directory] [-d directory] [-f config-file]\n"
778 " [-S pidfile] [-t timefmt ] [ [-R requestor] filename ... ]\n");
783 * Parse a configuration file and return a linked list of all the logs
784 * which should be processed.
786 static struct cflist
*
787 get_worklist(char **files
)
791 struct cflist
*cmdlist
, *filelist
, *globlist
;
792 struct conf_entry
*defconf
, *dupent
, *ent
;
793 struct ilist inclist
;
794 struct include_entry
*inc
;
798 STAILQ_INIT(&inclist
);
800 filelist
= malloc(sizeof(struct cflist
));
801 if (filelist
== NULL
)
802 err(1, "malloc of filelist");
803 STAILQ_INIT(filelist
);
804 globlist
= malloc(sizeof(struct cflist
));
805 if (globlist
== NULL
)
806 err(1, "malloc of globlist");
807 STAILQ_INIT(globlist
);
809 inc
= malloc(sizeof(struct include_entry
));
811 err(1, "malloc of inc");
813 if (inc
->file
== NULL
)
814 inc
->file
= _PATH_CONF
;
815 STAILQ_INSERT_TAIL(&inclist
, inc
, inc_nextp
);
817 STAILQ_FOREACH(inc
, &inclist
, inc_nextp
) {
818 if (strcmp(inc
->file
, "-") != 0)
819 f
= fopen(inc
->file
, "r");
822 inc
->file
= "<stdin>";
825 err(1, "%s", inc
->file
);
828 printf("Processing %s\n", inc
->file
);
829 parse_file(f
, filelist
, globlist
, defconf
, &inclist
);
834 * All config-file information has been read in and turned into
835 * a filelist and a globlist. If there were no specific files
836 * given on the run command, then the only thing left to do is to
837 * call a routine which finds all files matched by the globlist
838 * and adds them to the filelist. Then return the worklist.
840 if (*files
== NULL
) {
841 expand_globs(filelist
, globlist
);
842 free_clist(globlist
);
850 * If newsyslog was given a specific list of files to process,
851 * it may be that some of those files were not listed in any
852 * config file. Those unlisted files should get the default
853 * rotation action. First, create the default-rotation action
854 * if none was found in a system config file.
856 if (defconf
== NULL
) {
857 defconf
= init_entry(DEFAULT_MARKER
, NULL
);
858 defconf
->numlogs
= 3;
859 defconf
->trsize
= 50;
860 defconf
->permissions
= S_IRUSR
|S_IWUSR
;
864 * If newsyslog was run with a list of specific filenames,
865 * then create a new worklist which has only those files in
866 * it, picking up the rotation-rules for those files from
867 * the original filelist.
869 * XXX - Note that this will copy multiple rules for a single
870 * logfile, if multiple entries are an exact match for
871 * that file. That matches the historic behavior, but do
872 * we want to continue to allow it? If so, it should
873 * probably be handled more intelligently.
875 cmdlist
= malloc(sizeof(struct cflist
));
877 err(1, "malloc of cmdlist");
878 STAILQ_INIT(cmdlist
);
880 for (given
= files
; *given
; ++given
) {
882 * First try to find exact-matches for this given file.
885 STAILQ_FOREACH(ent
, filelist
, cf_nextp
) {
886 if (strcmp(ent
->log
, *given
) == 0) {
888 dupent
= init_entry(*given
, ent
);
889 STAILQ_INSERT_TAIL(cmdlist
, dupent
, cf_nextp
);
894 printf("\t+ Matched entry %s\n", *given
);
899 * There was no exact-match for this given file, so look
900 * for a "glob" entry which does match.
903 if (verbose
> 2 && globlist
!= NULL
)
904 printf("\t+ Checking globs for %s\n", *given
);
905 STAILQ_FOREACH(ent
, globlist
, cf_nextp
) {
906 fnres
= fnmatch(ent
->log
, *given
, FNM_PATHNAME
);
908 printf("\t+ = %d for pattern %s\n", fnres
,
912 dupent
= init_entry(*given
, ent
);
913 /* This new entry is not a glob! */
914 dupent
->flags
&= ~CE_GLOB
;
915 STAILQ_INSERT_TAIL(cmdlist
, dupent
, cf_nextp
);
916 /* Only allow a match to one glob-entry */
922 printf("\t+ Matched %s via %s\n", *given
,
928 * This given file was not found in any config file, so
929 * add a worklist item based on the default entry.
932 printf("\t+ No entry matched %s (will use %s)\n",
933 *given
, DEFAULT_MARKER
);
934 dupent
= init_entry(*given
, defconf
);
935 /* Mark that it was *not* found in a config file */
937 STAILQ_INSERT_TAIL(cmdlist
, dupent
, cf_nextp
);
941 * Free all the entries in the original work list, the list of
942 * glob entries, and the default entry.
944 free_clist(filelist
);
945 free_clist(globlist
);
948 /* And finally, return a worklist which matches the given files. */
953 * Expand the list of entries with filename patterns, and add all files
954 * which match those glob-entries onto the worklist.
957 expand_globs(struct cflist
*work_p
, struct cflist
*glob_p
)
962 struct conf_entry
*dupent
, *ent
, *globent
;
967 * The worklist contains all fully-specified (non-GLOB) names.
969 * Now expand the list of filename-pattern (GLOB) entries into
970 * a second list, which (by definition) will only match files
971 * that already exist. Do not add a glob-related entry for any
972 * file which already exists in the fully-specified list.
974 STAILQ_FOREACH(globent
, glob_p
, cf_nextp
) {
975 gres
= glob(globent
->log
, GLOB_NOCHECK
, NULL
, &pglob
);
977 warn("cannot expand pattern (%d): %s", gres
,
983 printf("\t+ Expanding pattern %s\n", globent
->log
);
984 for (i
= 0; i
< pglob
.gl_matchc
; i
++) {
985 mfname
= pglob
.gl_pathv
[i
];
987 /* See if this file already has a specific entry. */
989 STAILQ_FOREACH(ent
, work_p
, cf_nextp
) {
990 if (strcmp(mfname
, ent
->log
) == 0) {
998 /* Make sure the named matched is a file. */
999 gres
= lstat(mfname
, &st_fm
);
1001 /* Error on a file that glob() matched?!? */
1002 warn("Skipping %s - lstat() error", mfname
);
1005 if (!S_ISREG(st_fm
.st_mode
)) {
1006 /* We only rotate files! */
1008 printf("\t+ . skipping %s (!file)\n",
1014 printf("\t+ . add file %s\n", mfname
);
1015 dupent
= init_entry(mfname
, globent
);
1016 /* This new entry is not a glob! */
1017 dupent
->flags
&= ~CE_GLOB
;
1019 /* Add to the worklist. */
1020 STAILQ_INSERT_TAIL(work_p
, dupent
, cf_nextp
);
1024 printf("\t+ Done with pattern %s\n", globent
->log
);
1029 * Parse a configuration file and update a linked list of all the logs to
1033 parse_file(FILE *cf
, struct cflist
*work_p
, struct cflist
*glob_p
,
1034 struct conf_entry
*defconf_p
, struct ilist
*inclist
)
1036 char line
[BUFSIZ
], *parse
, *q
;
1037 char *cp
, *errline
, *group
;
1038 struct conf_entry
*working
;
1042 int eol
, ptm_opts
, res
, special
;
1046 while (fgets(line
, BUFSIZ
, cf
)) {
1047 if ((line
[0] == '\n') || (line
[0] == '#') ||
1048 (strlen(line
) == 0))
1050 if (errline
!= NULL
)
1052 errline
= strdup(line
);
1053 for (cp
= line
+ 1; *cp
!= '\0'; cp
++) {
1056 if (*(cp
- 1) == '\\') {
1065 q
= parse
= missing_field(sob(line
), errline
);
1068 errx(1, "malformed line (missing fields):\n%s",
1073 * Allow people to set debug options via the config file.
1074 * (NOTE: debug options are undocumented, and may disappear
1075 * at any time, etc).
1077 if (strcasecmp(DEBUG_MARKER
, q
) == 0) {
1078 q
= parse
= missing_field(sob(++parse
), errline
);
1081 warnx("debug line specifies no option:\n%s",
1088 } else if (strcasecmp(INCLUDE_MARKER
, q
) == 0) {
1090 printf("Found: %s", errline
);
1091 q
= parse
= missing_field(sob(++parse
), errline
);
1094 warnx("include line missing argument:\n%s",
1102 res
= glob(q
, GLOB_NOCHECK
, NULL
, &pglob
);
1104 warn("cannot expand pattern (%d): %s",
1110 printf("\t+ Expanding pattern %s\n", q
);
1112 for (i
= 0; i
< pglob
.gl_matchc
; i
++)
1113 add_to_queue(pglob
.gl_pathv
[i
],
1117 add_to_queue(q
, inclist
);
1122 working
= init_entry(q
, NULL
);
1123 if (strcasecmp(DEFAULT_MARKER
, q
) == 0) {
1125 if (defconf_p
!= NULL
) {
1126 warnx("Ignoring duplicate entry for %s!", q
);
1127 free_entry(working
);
1130 defconf_p
= working
;
1133 q
= parse
= missing_field(sob(++parse
), errline
);
1136 errx(1, "malformed line (missing fields):\n%s",
1139 if ((group
= strchr(q
, ':')) != NULL
||
1140 (group
= strrchr(q
, '.')) != NULL
) {
1143 if (!(isnumberstr(q
))) {
1144 if ((pwd
= getpwnam(q
)) == NULL
)
1146 "error in config file; unknown user:\n%s",
1148 working
->uid
= pwd
->pw_uid
;
1150 working
->uid
= atoi(q
);
1152 working
->uid
= (uid_t
)-1;
1156 if (!(isnumberstr(q
))) {
1157 if ((grp
= getgrnam(q
)) == NULL
)
1159 "error in config file; unknown group:\n%s",
1161 working
->gid
= grp
->gr_gid
;
1163 working
->gid
= atoi(q
);
1165 working
->gid
= (gid_t
)-1;
1167 q
= parse
= missing_field(sob(++parse
), errline
);
1170 errx(1, "malformed line (missing fields):\n%s",
1174 working
->uid
= (uid_t
)-1;
1175 working
->gid
= (gid_t
)-1;
1178 if (!sscanf(q
, "%o", &working
->permissions
))
1179 errx(1, "error in config file; bad permissions:\n%s",
1182 q
= parse
= missing_field(sob(++parse
), errline
);
1185 errx(1, "malformed line (missing fields):\n%s",
1188 if (!sscanf(q
, "%d", &working
->numlogs
) || working
->numlogs
< 0)
1189 errx(1, "error in config file; bad value for count of logs to save:\n%s",
1192 q
= parse
= missing_field(sob(++parse
), errline
);
1195 errx(1, "malformed line (missing fields):\n%s",
1199 working
->trsize
= atoi(q
);
1200 else if (strcmp(q
, "*") == 0)
1201 working
->trsize
= -1;
1203 warnx("Invalid value of '%s' for 'size' in line:\n%s",
1205 working
->trsize
= -1;
1209 working
->compress
= COMPRESS_NONE
;
1210 q
= parse
= missing_field(sob(++parse
), errline
);
1218 ul
= strtoul(q
, &ep
, 10);
1221 else if (*ep
== '*')
1222 working
->hours
= -1;
1223 else if (ul
> INT_MAX
)
1224 errx(1, "interval is too large:\n%s", errline
);
1226 working
->hours
= ul
;
1228 if (*ep
== '\0' || strcmp(ep
, "*") == 0)
1230 if (*ep
!= '@' && *ep
!= '$')
1231 errx(1, "malformed interval/at:\n%s", errline
);
1233 working
->flags
|= CE_TRIMAT
;
1234 working
->trim_at
= ptime_init(NULL
);
1235 ptm_opts
= PTM_PARSE_ISO8601
;
1237 ptm_opts
= PTM_PARSE_DWM
;
1238 ptm_opts
|= PTM_PARSE_MATCHDOM
;
1239 res
= ptime_relparse(working
->trim_at
, ptm_opts
,
1240 ptimeget_secs(timenow
), ep
+ 1);
1242 errx(1, "nonexistent time for 'at' value:\n%s",
1245 errx(1, "malformed 'at' value:\n%s", errline
);
1252 q
= parse
= sob(++parse
); /* Optional field */
1259 for (; q
&& *q
&& !isspacech(*q
); q
++) {
1260 switch (tolowerch(*q
)) {
1262 working
->flags
|= CE_BINARY
;
1266 * XXX - Ick! Ugly! Remove ASAP!
1267 * We want `c' and `C' for "create". But we
1268 * will temporarily treat `c' as `g', because
1269 * FreeBSD releases <= 4.8 have a typo of
1270 * checking ('G' || 'c') for CE_GLOB.
1273 warnx("Assuming 'g' for 'c' in flags for line:\n%s",
1275 warnx("The 'c' flag will eventually mean 'CREATE'");
1276 working
->flags
|= CE_GLOB
;
1279 working
->flags
|= CE_CREATE
;
1282 working
->flags
|= CE_NODUMP
;
1285 working
->flags
|= CE_GLOB
;
1288 working
->compress
= COMPRESS_BZIP2
;
1291 working
->flags
|= CE_NOSIGNAL
;
1294 working
->flags
|= CE_SIGNALGROUP
;
1297 /* Depreciated flag - keep for compatibility purposes */
1300 working
->compress
= COMPRESS_XZ
;
1303 working
->compress
= COMPRESS_GZIP
;
1307 case 'f': /* Used by OpenBSD for "CE_FOLLOW" */
1308 case 'm': /* Used by OpenBSD for "CE_MONITOR" */
1309 case 'p': /* Used by NetBSD for "CE_PLAIN0" */
1311 errx(1, "illegal flag in config file -- %c",
1319 q
= parse
= sob(++parse
); /* Optional field */
1326 working
->pid_file
= NULL
;
1329 working
->pid_file
= strdup(q
);
1330 else if (isdigit(*q
))
1334 "illegal pid file or signal number in config file:\n%s",
1340 q
= parse
= sob(++parse
); /* Optional field */
1341 *(parse
= son(parse
)) = '\0';
1344 working
->sig
= SIGHUP
;
1348 working
->sig
= atoi(q
);
1352 "illegal signal number in config file:\n%s",
1355 if (working
->sig
< 1 || working
->sig
>= NSIG
)
1360 * Finish figuring out what pid-file to use (if any) in
1361 * later processing if this logfile needs to be rotated.
1363 if ((working
->flags
& CE_NOSIGNAL
) == CE_NOSIGNAL
) {
1365 * This config-entry specified 'n' for nosignal,
1366 * see if it also specified an explicit pid_file.
1367 * This would be a pretty pointless combination.
1369 if (working
->pid_file
!= NULL
) {
1370 warnx("Ignoring '%s' because flag 'n' was specified in line:\n%s",
1371 working
->pid_file
, errline
);
1372 free(working
->pid_file
);
1373 working
->pid_file
= NULL
;
1375 } else if (working
->pid_file
== NULL
) {
1377 * This entry did not specify the 'n' flag, which
1378 * means it should signal syslogd unless it had
1379 * specified some other pid-file (and obviously the
1380 * syslog pid-file will not be for a process-group).
1381 * Also, we should only try to notify syslog if we
1384 if (working
->flags
& CE_SIGNALGROUP
) {
1385 warnx("Ignoring flag 'U' in line:\n%s",
1387 working
->flags
&= ~CE_SIGNALGROUP
;
1390 working
->pid_file
= strdup(path_syslogpid
);
1394 * Add this entry to the appropriate list of entries, unless
1395 * it was some kind of special entry (eg: <default>).
1398 ; /* Do not add to any list */
1399 } else if (working
->flags
& CE_GLOB
) {
1400 STAILQ_INSERT_TAIL(glob_p
, working
, cf_nextp
);
1402 STAILQ_INSERT_TAIL(work_p
, working
, cf_nextp
);
1405 if (errline
!= NULL
)
1410 missing_field(char *p
, char *errline
)
1414 errx(1, "missing field in config file:\n%s", errline
);
1419 * In our sort we return it in the reverse of what qsort normally
1420 * would do, as we want the newest files first. If we have two
1421 * entries with the same time we don't really care about order.
1423 * Support function for qsort() in delete_oldest_timelog().
1426 oldlog_entry_compare(const void *a
, const void *b
)
1428 const struct oldlog_entry
*ola
= a
, *olb
= b
;
1430 if (ola
->t
> olb
->t
)
1432 else if (ola
->t
< olb
->t
)
1439 * Delete the oldest logfiles, when using time based filenames.
1442 delete_oldest_timelog(const struct conf_entry
*ent
, const char *archive_dir
)
1444 char *logfname
, *s
, *dir
, errbuf
[80];
1445 int dir_fd
, i
, logcnt
, max_logcnt
, valid
;
1446 struct oldlog_entry
*oldlogs
;
1447 size_t logfname_len
;
1453 oldlogs
= malloc(MAX_OLDLOGS
* sizeof(struct oldlog_entry
));
1454 max_logcnt
= MAX_OLDLOGS
;
1457 if (archive_dir
!= NULL
&& archive_dir
[0] != '\0')
1460 if ((cdir
= dirname(ent
->log
)) == NULL
)
1461 err(1, "dirname()");
1462 if ((dir
= strdup(cdir
)) == NULL
)
1465 if ((s
= basename(ent
->log
)) == NULL
)
1466 err(1, "basename()");
1467 if ((logfname
= strdup(s
)) == NULL
)
1469 logfname_len
= strlen(logfname
);
1470 if (strcmp(logfname
, "/") == 0)
1471 errx(1, "Invalid log filename - became '/'");
1474 printf("Searching for old logs in %s\n", dir
);
1476 /* First we create a 'list' of all archived logfiles */
1477 if ((dirp
= opendir(dir
)) == NULL
)
1478 err(1, "Cannot open log directory '%s'", dir
);
1479 dir_fd
= dirfd(dirp
);
1480 while ((dp
= readdir(dirp
)) != NULL
) {
1481 if (dp
->d_type
!= DT_REG
)
1484 /* Ignore everything but files with our logfile prefix */
1485 if (strncmp(dp
->d_name
, logfname
, logfname_len
) != 0)
1487 /* Ignore the actual non-rotated logfile */
1488 if (dp
->d_namlen
== logfname_len
)
1491 * Make sure we created have found a logfile, so the
1492 * postfix is valid, IE format is: '.<time>(.[bg]z)?'.
1494 if (dp
->d_name
[logfname_len
] != '.') {
1496 printf("Ignoring %s which has unexpected "
1497 "extension '%s'\n", dp
->d_name
,
1498 &dp
->d_name
[logfname_len
]);
1501 if ((s
= strptime(&dp
->d_name
[logfname_len
+ 1],
1502 timefnamefmt
, &tm
)) == NULL
) {
1504 * We could special case "old" sequentially
1505 * named logfiles here, but we do not as that
1506 * would require special handling to decide
1507 * which one was the oldest compared to "new"
1508 * time based logfiles.
1511 printf("Ignoring %s which does not "
1512 "match time format\n", dp
->d_name
);
1516 for (int c
= 0; c
< COMPRESS_TYPES
; c
++)
1517 if (strcmp(s
, compress_type
[c
].suffix
) == 0)
1521 printf("Ignoring %s which has unexpected "
1522 "extension '%s'\n", dp
->d_name
, s
);
1527 * We should now have old an old rotated logfile, so
1528 * add it to the 'list'.
1530 if ((oldlogs
[logcnt
].t
= timegm(&tm
)) == -1)
1531 err(1, "Could not convert time string to time value");
1532 if ((oldlogs
[logcnt
].fname
= strdup(dp
->d_name
)) == NULL
)
1537 * It is very unlikely we ever run out of space in the
1538 * logfile array from the default size, but lets
1539 * handle it anyway...
1541 if (logcnt
>= max_logcnt
) {
1543 /* Detect integer overflow */
1544 if (max_logcnt
< logcnt
)
1545 errx(1, "Too many old logfiles found");
1546 oldlogs
= realloc(oldlogs
,
1547 max_logcnt
* sizeof(struct oldlog_entry
));
1548 if (oldlogs
== NULL
)
1549 err(1, "realloc()");
1553 /* Second, if needed we delete oldest archived logfiles */
1554 if (logcnt
> 0 && logcnt
>= ent
->numlogs
&& ent
->numlogs
> 1) {
1555 oldlogs
= realloc(oldlogs
, logcnt
*
1556 sizeof(struct oldlog_entry
));
1557 if (oldlogs
== NULL
)
1558 err(1, "realloc()");
1561 * We now sort the logs in the order of newest to
1562 * oldest. That way we can simply skip over the
1563 * number of records we want to keep.
1565 qsort(oldlogs
, logcnt
, sizeof(struct oldlog_entry
),
1566 oldlog_entry_compare
);
1567 for (i
= ent
->numlogs
- 1; i
< logcnt
; i
++) {
1569 printf("\trm -f %s/%s\n", dir
,
1571 else if (unlinkat(dir_fd
, oldlogs
[i
].fname
, 0) != 0) {
1572 snprintf(errbuf
, sizeof(errbuf
),
1573 "Could not delete old logfile '%s'",
1578 } else if (verbose
> 1)
1579 printf("No old logs to delete for logfile %s\n", ent
->log
);
1581 /* Third, cleanup */
1583 for (i
= 0; i
< logcnt
; i
++) {
1584 assert(oldlogs
[i
].fname
!= NULL
);
1585 free(oldlogs
[i
].fname
);
1593 * Only add to the queue if the file hasn't already been added. This is
1594 * done to prevent circular include loops.
1597 add_to_queue(const char *fname
, struct ilist
*inclist
)
1599 struct include_entry
*inc
;
1601 STAILQ_FOREACH(inc
, inclist
, inc_nextp
) {
1602 if (strcmp(fname
, inc
->file
) == 0) {
1603 warnx("duplicate include detected: %s", fname
);
1608 inc
= malloc(sizeof(struct include_entry
));
1610 err(1, "malloc of inc");
1611 inc
->file
= strdup(fname
);
1614 printf("\t+ Adding %s to the processing queue.\n", fname
);
1616 STAILQ_INSERT_TAIL(inclist
, inc
, inc_nextp
);
1620 * Search for logfile and return its compression suffix (if supported)
1621 * The suffix detection is first-match in the order of compress_types
1623 * Note: if logfile without suffix exists (uncompressed, COMPRESS_NONE)
1624 * a zero-length string is returned
1627 get_logfile_suffix(const char *logfile
)
1630 char zfile
[MAXPATHLEN
];
1632 for (int c
= 0; c
< COMPRESS_TYPES
; c
++) {
1633 strlcpy(zfile
, logfile
, MAXPATHLEN
);
1634 strlcat(zfile
, compress_type
[c
].suffix
, MAXPATHLEN
);
1635 if (lstat(zfile
, &st
) == 0)
1636 return (compress_type
[c
].suffix
);
1642 do_rotate(const struct conf_entry
*ent
)
1644 char dirpart
[MAXPATHLEN
], namepart
[MAXPATHLEN
];
1645 char file1
[MAXPATHLEN
], file2
[MAXPATHLEN
];
1646 char zfile1
[MAXPATHLEN
], zfile2
[MAXPATHLEN
];
1647 const char *logfile_suffix
;
1648 char datetimestr
[30];
1649 int flags
, numlogs_c
;
1650 fk_entry free_or_keep
;
1651 struct sigwork_entry
*swork
;
1657 free_or_keep
= FREE_ENT
;
1662 /* build complete name of archive directory into dirpart */
1663 if (*archdirname
== '/') { /* absolute */
1664 strlcpy(dirpart
, archdirname
, sizeof(dirpart
));
1665 } else { /* relative */
1666 /* get directory part of logfile */
1667 strlcpy(dirpart
, ent
->log
, sizeof(dirpart
));
1668 if ((p
= strrchr(dirpart
, '/')) == NULL
)
1672 strlcat(dirpart
, archdirname
, sizeof(dirpart
));
1675 /* check if archive directory exists, if not, create it */
1676 if (lstat(dirpart
, &st
))
1677 createdir(ent
, dirpart
);
1679 /* get filename part of logfile */
1680 if ((p
= strrchr(ent
->log
, '/')) == NULL
)
1681 strlcpy(namepart
, ent
->log
, sizeof(namepart
));
1683 strlcpy(namepart
, p
+ 1, sizeof(namepart
));
1685 /* name of oldest log */
1686 snprintf(file1
, sizeof(file1
), "%s/%s.%d", dirpart
,
1687 namepart
, ent
->numlogs
);
1690 * Tell delete_oldest_timelog() we are not using an
1695 /* name of oldest log */
1696 snprintf(file1
, sizeof(file1
), "%s.%d", ent
->log
,
1700 /* Delete old logs */
1701 if (timefnamefmt
!= NULL
)
1702 delete_oldest_timelog(ent
, dirpart
);
1704 /* name of oldest log */
1705 for (int c
= 0; c
< COMPRESS_TYPES
; c
++) {
1706 snprintf(zfile1
, sizeof(zfile1
), "%s%s", file1
,
1707 compress_type
[c
].suffix
);
1709 printf("\trm -f %s\n", zfile1
);
1715 if (timefnamefmt
!= NULL
) {
1716 /* If time functions fails we can't really do any sensible */
1717 if (time(&now
) == (time_t)-1 ||
1718 localtime_r(&now
, &tm
) == NULL
)
1719 bzero(&tm
, sizeof(tm
));
1721 strftime(datetimestr
, sizeof(datetimestr
), timefnamefmt
, &tm
);
1723 snprintf(file1
, sizeof(file1
), "%s/%s.%s",
1724 dirpart
, namepart
, datetimestr
);
1726 snprintf(file1
, sizeof(file1
), "%s.%s",
1727 ent
->log
, datetimestr
);
1730 /* Don't run the code to move down logs */
1733 numlogs_c
= ent
->numlogs
; /* copy for countdown */
1735 /* Move down log files */
1736 while (numlogs_c
--) {
1738 strlcpy(file2
, file1
, sizeof(file2
));
1741 snprintf(file1
, sizeof(file1
), "%s/%s.%d",
1742 dirpart
, namepart
, numlogs_c
);
1744 snprintf(file1
, sizeof(file1
), "%s.%d",
1745 ent
->log
, numlogs_c
);
1747 logfile_suffix
= get_logfile_suffix(file1
);
1748 if (logfile_suffix
== NULL
)
1750 strlcpy(zfile1
, file1
, MAXPATHLEN
);
1751 strlcpy(zfile2
, file2
, MAXPATHLEN
);
1752 strlcat(zfile1
, logfile_suffix
, MAXPATHLEN
);
1753 strlcat(zfile2
, logfile_suffix
, MAXPATHLEN
);
1756 printf("\tmv %s %s\n", zfile1
, zfile2
);
1758 /* XXX - Ought to be checking for failure! */
1759 rename(zfile1
, zfile2
);
1761 change_attrs(zfile2
, ent
);
1764 if (ent
->numlogs
> 0) {
1767 * Note that savelog() may succeed with using link()
1768 * for the archtodir case, but there is no good way
1769 * of knowing if it will when doing "noaction", so
1770 * here we claim that it will have to do a copy...
1773 printf("\tcp %s %s\n", ent
->log
, file1
);
1775 printf("\tln %s %s\n", ent
->log
, file1
);
1777 if (!(flags
& CE_BINARY
)) {
1778 /* Report the trimming to the old log */
1779 log_trim(ent
->log
, ent
);
1781 savelog(ent
->log
, file1
);
1783 change_attrs(file1
, ent
);
1786 /* Create the new log file and move it into place */
1788 printf("Start new log...\n");
1792 * Save all signalling and file-compression to be done after log
1793 * files from all entries have been rotated. This way any one
1794 * process will not be sent the same signal multiple times when
1795 * multiple log files had to be rotated.
1798 if (ent
->pid_file
!= NULL
)
1799 swork
= save_sigwork(ent
);
1800 if (ent
->numlogs
> 0 && ent
->compress
> COMPRESS_NONE
) {
1802 * The zipwork_entry will include a pointer to this
1803 * conf_entry, so the conf_entry should not be freed.
1805 free_or_keep
= KEEP_ENT
;
1806 save_zipwork(ent
, swork
, ent
->fsize
, file1
);
1809 return (free_or_keep
);
1813 do_sigwork(struct sigwork_entry
*swork
)
1815 struct sigwork_entry
*nextsig
;
1818 if (!(swork
->sw_pidok
) || swork
->sw_pid
== 0)
1819 return; /* no work to do... */
1822 * If nosignal (-s) was specified, then do not signal any process.
1823 * Note that a nosignal request triggers a warning message if the
1824 * rotated logfile needs to be compressed, *unless* -R was also
1825 * specified. We assume that an `-sR' request came from a process
1826 * which writes to the logfile, and as such, we assume that process
1827 * has already made sure the logfile is not presently in use. This
1828 * just sets swork->sw_pidok to a special value, and do_zipwork
1829 * will print any necessary warning(s).
1833 swork
->sw_pidok
= -1;
1838 * Compute the pause between consecutive signals. Use a longer
1839 * sleep time if we will be sending two signals to the same
1840 * deamon or process-group.
1843 nextsig
= SLIST_NEXT(swork
, sw_nextp
);
1844 if (nextsig
!= NULL
) {
1845 if (swork
->sw_pid
== nextsig
->sw_pid
)
1852 printf("\tkill -%d %d \t\t# %s\n", swork
->sw_signum
,
1853 (int)swork
->sw_pid
, swork
->sw_fname
);
1855 printf("\tsleep %d\n", secs
);
1859 kres
= kill(swork
->sw_pid
, swork
->sw_signum
);
1862 * Assume that "no such process" (ESRCH) is something
1863 * to warn about, but is not an error. Presumably the
1864 * process which writes to the rotated log file(s) is
1865 * gone, in which case we should have no problem with
1866 * compressing the rotated log file(s).
1869 swork
->sw_pidok
= 0;
1870 warn("can't notify %s, pid %d", swork
->sw_pidtype
,
1871 (int)swork
->sw_pid
);
1874 printf("Notified %s pid %d = %s\n", swork
->sw_pidtype
,
1875 (int)swork
->sw_pid
, swork
->sw_fname
);
1878 printf("Pause %d second(s) between signals\n",
1886 do_zipwork(struct zipwork_entry
*zwork
)
1888 const char *pgm_name
, *pgm_path
;
1889 int errsav
, fcount
, zstatus
;
1891 char zresult
[MAXPATHLEN
];
1893 assert(zwork
!= NULL
);
1895 strlcpy(zresult
, zwork
->zw_fname
, sizeof(zresult
));
1896 if (zwork
->zw_conf
!= NULL
&&
1897 zwork
->zw_conf
->compress
> COMPRESS_NONE
)
1898 for (int c
= 1; c
< COMPRESS_TYPES
; c
++) {
1899 if (zwork
->zw_conf
->compress
== c
) {
1900 pgm_path
= compress_type
[c
].path
;
1902 compress_type
[c
].suffix
, sizeof(zresult
));
1906 if (pgm_path
== NULL
) {
1907 warnx("invalid entry for %s in do_zipwork", zwork
->zw_fname
);
1910 pgm_name
= strrchr(pgm_path
, '/');
1911 if (pgm_name
== NULL
)
1912 pgm_name
= pgm_path
;
1916 if (zwork
->zw_swork
!= NULL
&& zwork
->zw_swork
->sw_pidok
<= 0) {
1918 "log %s not compressed because daemon(s) not notified",
1920 change_attrs(zwork
->zw_fname
, zwork
->zw_conf
);
1925 printf("\t%s %s\n", pgm_name
, zwork
->zw_fname
);
1926 change_attrs(zresult
, zwork
->zw_conf
);
1932 while (pidzip
< 0) {
1934 * The fork failed. If the failure was due to a temporary
1935 * problem, then wait a short time and try it again.
1938 warn("fork() for `%s %s'", pgm_name
, zwork
->zw_fname
);
1939 if (errsav
!= EAGAIN
|| fcount
> 5)
1940 errx(1, "Exiting...");
1946 /* The child process executes the compression command */
1947 execl(pgm_path
, pgm_path
, "-f", zwork
->zw_fname
, NULL
);
1948 err(1, "execl(`%s -f %s')", pgm_path
, zwork
->zw_fname
);
1951 wpid
= waitpid(pidzip
, &zstatus
, 0);
1953 /* XXX - should this be a fatal error? */
1954 warn("%s: waitpid(%d)", pgm_path
, pidzip
);
1957 if (!WIFEXITED(zstatus
)) {
1958 warnx("`%s -f %s' did not terminate normally", pgm_name
,
1962 if (WEXITSTATUS(zstatus
)) {
1963 warnx("`%s -f %s' terminated with a non-zero status (%d)",
1964 pgm_name
, zwork
->zw_fname
, WEXITSTATUS(zstatus
));
1968 /* Compression was successful, set file attributes on the result. */
1969 change_attrs(zresult
, zwork
->zw_conf
);
1973 * Save information on any process we need to signal. Any single
1974 * process may need to be sent different signal-values for different
1975 * log files, but usually a single signal-value will cause the process
1976 * to close and re-open all of it's log files.
1978 static struct sigwork_entry
*
1979 save_sigwork(const struct conf_entry
*ent
)
1981 struct sigwork_entry
*sprev
, *stmp
;
1987 SLIST_FOREACH(stmp
, &swhead
, sw_nextp
) {
1988 ndiff
= strcmp(ent
->pid_file
, stmp
->sw_fname
);
1992 if (ent
->sig
== stmp
->sw_signum
)
1994 if (ent
->sig
> stmp
->sw_signum
) {
2001 if (stmp
!= NULL
&& ndiff
== 0)
2004 tmpsiz
= sizeof(struct sigwork_entry
) + strlen(ent
->pid_file
) + 1;
2005 stmp
= malloc(tmpsiz
);
2006 set_swpid(stmp
, ent
);
2007 stmp
->sw_signum
= ent
->sig
;
2008 strcpy(stmp
->sw_fname
, ent
->pid_file
);
2010 SLIST_INSERT_HEAD(&swhead
, stmp
, sw_nextp
);
2012 SLIST_INSERT_AFTER(sprev
, stmp
, sw_nextp
);
2017 * Save information on any file we need to compress. We may see the same
2018 * file multiple times, so check the full list to avoid duplicates. The
2019 * list itself is sorted smallest-to-largest, because that's the order we
2020 * want to compress the files. If the partition is very low on disk space,
2021 * then the smallest files are the most likely to compress, and compressing
2022 * them first will free up more space for the larger files.
2024 static struct zipwork_entry
*
2025 save_zipwork(const struct conf_entry
*ent
, const struct sigwork_entry
*swork
,
2026 int zsize
, const char *zipfname
)
2028 struct zipwork_entry
*zprev
, *ztmp
;
2032 /* Compute the size if the caller did not know it. */
2034 zsize
= sizefile(zipfname
);
2038 SLIST_FOREACH(ztmp
, &zwhead
, zw_nextp
) {
2039 ndiff
= strcmp(zipfname
, ztmp
->zw_fname
);
2042 if (zsize
> ztmp
->zw_fsize
)
2045 if (ztmp
!= NULL
&& ndiff
== 0)
2048 tmpsiz
= sizeof(struct zipwork_entry
) + strlen(zipfname
) + 1;
2049 ztmp
= malloc(tmpsiz
);
2050 ztmp
->zw_conf
= ent
;
2051 ztmp
->zw_swork
= swork
;
2052 ztmp
->zw_fsize
= zsize
;
2053 strcpy(ztmp
->zw_fname
, zipfname
);
2055 SLIST_INSERT_HEAD(&zwhead
, ztmp
, zw_nextp
);
2057 SLIST_INSERT_AFTER(zprev
, ztmp
, zw_nextp
);
2061 /* Send a signal to the pid specified by pidfile */
2063 set_swpid(struct sigwork_entry
*swork
, const struct conf_entry
*ent
)
2066 long minok
, maxok
, rval
;
2067 char *endp
, *linep
, line
[BUFSIZ
];
2071 swork
->sw_pidok
= 0;
2073 swork
->sw_pidtype
= "daemon";
2074 if (ent
->flags
& CE_SIGNALGROUP
) {
2076 * If we are expected to signal a process-group when
2077 * rotating this logfile, then the value read in should
2078 * be the negative of a valid process ID.
2082 swork
->sw_pidtype
= "process-group";
2085 f
= fopen(ent
->pid_file
, "r");
2087 if (errno
== ENOENT
&& enforcepid
== 0) {
2089 * Warn if the PID file doesn't exist, but do
2090 * not consider it an error. Most likely it
2091 * means the process has been terminated,
2092 * so it should be safe to rotate any log
2093 * files that the process would have been using.
2095 swork
->sw_pidok
= 1;
2096 warnx("pid file doesn't exist: %s", ent
->pid_file
);
2098 warn("can't open pid file: %s", ent
->pid_file
);
2102 if (fgets(line
, BUFSIZ
, f
) == NULL
) {
2104 * Warn if the PID file is empty, but do not consider
2105 * it an error. Most likely it means the process has
2106 * has terminated, so it should be safe to rotate any
2107 * log files that the process would have been using.
2109 if (feof(f
) && enforcepid
== 0) {
2110 swork
->sw_pidok
= 1;
2111 warnx("pid file is empty: %s", ent
->pid_file
);
2113 warn("can't read from pid file: %s", ent
->pid_file
);
2121 while (*linep
== ' ')
2123 rval
= strtol(linep
, &endp
, 10);
2124 if (*endp
!= '\0' && !isspacech(*endp
)) {
2125 warnx("pid file does not start with a valid number: %s",
2127 } else if (rval
< minok
|| rval
> maxok
) {
2128 warnx("bad value '%ld' for process number in %s",
2129 rval
, ent
->pid_file
);
2131 warnx("\t(expecting value between %ld and %ld)",
2134 swork
->sw_pidok
= 1;
2135 swork
->sw_pid
= rval
;
2141 /* Log the fact that the logs were turned over */
2143 log_trim(const char *logname
, const struct conf_entry
*log_ent
)
2148 if ((f
= fopen(logname
, "a")) == NULL
)
2151 if (log_ent
->def_cfg
)
2152 xtra
= " using <default> rule";
2153 if (log_ent
->firstcreate
)
2154 fprintf(f
, "%s %s newsyslog[%d]: logfile first created%s\n",
2155 daytime
, hostname
, (int) getpid(), xtra
);
2156 else if (log_ent
->r_reason
!= NULL
)
2157 fprintf(f
, "%s %s newsyslog[%d]: logfile turned over%s%s\n",
2158 daytime
, hostname
, (int) getpid(), log_ent
->r_reason
, xtra
);
2160 fprintf(f
, "%s %s newsyslog[%d]: logfile turned over%s\n",
2161 daytime
, hostname
, (int) getpid(), xtra
);
2162 if (fclose(f
) == EOF
)
2163 err(1, "log_trim: fclose");
2167 /* Return size in kilobytes of a file */
2169 sizefile(const char *file
)
2173 if (stat(file
, &sb
) < 0)
2175 return (kbytes(sb
.st_size
));
2178 /* Return the age of old log file (file.0) */
2180 age_old_log(char *file
)
2183 const char *logfile_suffix
;
2184 char tmp
[MAXPATHLEN
+ sizeof(".0") + COMPRESS_SUFFIX_MAXLEN
+ 1];
2189 /* build name of archive directory into tmp */
2190 if (*archdirname
== '/') { /* absolute */
2191 strlcpy(tmp
, archdirname
, sizeof(tmp
));
2192 } else { /* relative */
2193 /* get directory part of logfile */
2194 strlcpy(tmp
, file
, sizeof(tmp
));
2195 if ((p
= strrchr(tmp
, '/')) == NULL
)
2199 strlcat(tmp
, archdirname
, sizeof(tmp
));
2202 strlcat(tmp
, "/", sizeof(tmp
));
2204 /* get filename part of logfile */
2205 if ((p
= strrchr(file
, '/')) == NULL
)
2206 strlcat(tmp
, file
, sizeof(tmp
));
2208 strlcat(tmp
, p
+ 1, sizeof(tmp
));
2210 strlcpy(tmp
, file
, sizeof(tmp
));
2213 strlcat(tmp
, ".0", sizeof(tmp
));
2214 logfile_suffix
= get_logfile_suffix(tmp
);
2215 if (logfile_suffix
== NULL
)
2217 strlcat(tmp
, logfile_suffix
, sizeof(tmp
));
2218 if (stat(tmp
, &sb
) < 0)
2220 return ((int)(ptimeget_secs(timenow
) - sb
.st_mtime
+ 1800) / 3600);
2223 /* Skip Over Blanks */
2227 while (p
&& *p
&& isspace(*p
))
2232 /* Skip Over Non-Blanks */
2236 while (p
&& *p
&& !isspace(*p
))
2241 /* Check if string is actually a number */
2243 isnumberstr(const char *string
)
2246 if (!isdigitch(*string
++))
2252 /* Check if string contains a glob */
2254 isglobstr(const char *string
)
2258 while ((chr
= *string
++)) {
2259 if (chr
== '*' || chr
== '?' || chr
== '[')
2266 * Save the active log file under a new name. A link to the new name
2267 * is the quick-and-easy way to do this. If that fails (which it will
2268 * if the destination is on another partition), then make a copy of
2269 * the file to the new location.
2272 savelog(char *from
, char *to
)
2277 res
= link(from
, to
);
2281 if ((src
= fopen(from
, "r")) == NULL
)
2282 err(1, "can't fopen %s for reading", from
);
2283 if ((dst
= fopen(to
, "w")) == NULL
)
2284 err(1, "can't fopen %s for writing", to
);
2286 while ((c
= getc(src
)) != EOF
) {
2287 if ((putc(c
, dst
)) == EOF
)
2288 err(1, "error writing to %s", to
);
2292 err(1, "error reading from %s", from
);
2293 if ((fclose(src
)) != 0)
2294 err(1, "can't fclose %s", to
);
2295 if ((fclose(dst
)) != 0)
2296 err(1, "can't fclose %s", from
);
2299 /* create one or more directory components of a path */
2301 createdir(const struct conf_entry
*ent
, char *dirpart
)
2305 char mkdirpath
[MAXPATHLEN
];
2313 if (*s
!= '/' && *s
!= '\0')
2316 res
= lstat(mkdirpath
, &st
);
2319 printf("\tmkdir %s\n", mkdirpath
);
2321 res
= mkdir(mkdirpath
, 0755);
2323 err(1, "Error on mkdir(\"%s\") for -a",
2331 if (ent
->firstcreate
)
2332 printf("Created directory '%s' for new %s\n",
2335 printf("Created directory '%s' for -a\n", dirpart
);
2340 * Create a new log file, destroying any currently-existing version
2341 * of the log file in the process. If the caller wants a backup copy
2342 * of the file to exist, they should call 'link(logfile,logbackup)'
2343 * before calling this routine.
2346 createlog(const struct conf_entry
*ent
)
2350 char *realfile
, *slash
, tempfile
[MAXPATHLEN
];
2353 realfile
= ent
->log
;
2356 * If this log file is being created for the first time (-C option),
2357 * then it may also be true that the parent directory does not exist
2358 * yet. Check, and create that directory if it is missing.
2360 if (ent
->firstcreate
) {
2361 strlcpy(tempfile
, realfile
, sizeof(tempfile
));
2362 slash
= strrchr(tempfile
, '/');
2363 if (slash
!= NULL
) {
2365 failed
= stat(tempfile
, &st
);
2366 if (failed
&& errno
!= ENOENT
)
2367 err(1, "Error on stat(%s)", tempfile
);
2369 createdir(ent
, tempfile
);
2370 else if (!S_ISDIR(st
.st_mode
))
2371 errx(1, "%s exists but is not a directory",
2377 * First create an unused filename, so it can be chown'ed and
2378 * chmod'ed before it is moved into the real location. mkstemp
2379 * will create the file mode=600 & owned by us. Note that all
2380 * temp files will have a suffix of '.z<something>'.
2382 strlcpy(tempfile
, realfile
, sizeof(tempfile
));
2383 strlcat(tempfile
, ".zXXXXXX", sizeof(tempfile
));
2385 printf("\tmktemp %s\n", tempfile
);
2387 fd
= mkstemp(tempfile
);
2389 err(1, "can't mkstemp logfile %s", tempfile
);
2392 * Add status message to what will become the new log file.
2394 if (!(ent
->flags
& CE_BINARY
)) {
2395 if (log_trim(tempfile
, ent
))
2396 err(1, "can't add status message to log");
2400 /* Change the owner/group, if we are supposed to */
2401 if (ent
->uid
!= (uid_t
)-1 || ent
->gid
!= (gid_t
)-1) {
2403 printf("\tchown %u:%u %s\n", ent
->uid
, ent
->gid
,
2406 failed
= fchown(fd
, ent
->uid
, ent
->gid
);
2408 err(1, "can't fchown temp file %s", tempfile
);
2412 /* Turn on NODUMP if it was requested in the config-file. */
2413 if (ent
->flags
& CE_NODUMP
) {
2415 printf("\tchflags nodump %s\n", tempfile
);
2417 failed
= fchflags(fd
, UF_NODUMP
);
2419 warn("log_trim: fchflags(NODUMP)");
2425 * Note that if the real logfile still exists, and if the call
2426 * to rename() fails, then "neither the old file nor the new
2427 * file shall be changed or created" (to quote the standard).
2428 * If the call succeeds, then the file will be replaced without
2429 * any window where some other process might find that the file
2431 * XXX - ? It may be that for some error conditions, we could
2432 * retry by first removing the realfile and then renaming.
2435 printf("\tchmod %o %s\n", ent
->permissions
, tempfile
);
2436 printf("\tmv %s %s\n", tempfile
, realfile
);
2438 failed
= fchmod(fd
, ent
->permissions
);
2440 err(1, "can't fchmod temp file '%s'", tempfile
);
2441 failed
= rename(tempfile
, realfile
);
2443 err(1, "can't mv %s to %s", tempfile
, realfile
);
2451 * Change the attributes of a given filename to what was specified in
2452 * the newsyslog.conf entry. This routine is only called for files
2453 * that newsyslog expects that it has created, and thus it is a fatal
2454 * error if this routine finds that the file does not exist.
2457 change_attrs(const char *fname
, const struct conf_entry
*ent
)
2462 printf("\tchmod %o %s\n", ent
->permissions
, fname
);
2464 if (ent
->uid
!= (uid_t
)-1 || ent
->gid
!= (gid_t
)-1)
2465 printf("\tchown %u:%u %s\n",
2466 ent
->uid
, ent
->gid
, fname
);
2468 if (ent
->flags
& CE_NODUMP
)
2469 printf("\tchflags nodump %s\n", fname
);
2473 failed
= chmod(fname
, ent
->permissions
);
2476 err(1, "chmod(%s) in change_attrs", fname
);
2477 warn("change_attrs couldn't chmod(%s)", fname
);
2480 if (ent
->uid
!= (uid_t
)-1 || ent
->gid
!= (gid_t
)-1) {
2481 failed
= chown(fname
, ent
->uid
, ent
->gid
);
2483 warn("can't chown %s", fname
);
2486 if (ent
->flags
& CE_NODUMP
) {
2487 failed
= chflags(fname
, UF_NODUMP
);
2489 warn("can't chflags %s NODUMP", fname
);