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: head/usr.sbin/newsyslog/newsyslog.c 327451 2017-12-31 22:01:36Z eadler $
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>
85 #include "pathnames.h"
89 * Compression suffixes
91 #ifndef COMPRESS_SUFFIX_GZ
92 #define COMPRESS_SUFFIX_GZ ".gz"
95 #ifndef COMPRESS_SUFFIX_BZ2
96 #define COMPRESS_SUFFIX_BZ2 ".bz2"
99 #ifndef COMPRESS_SUFFIX_XZ
100 #define COMPRESS_SUFFIX_XZ ".xz"
103 #ifndef COMPRESS_SUFFIX_ZST
104 #define COMPRESS_SUFFIX_ZST ".zst"
107 #define COMPRESS_SUFFIX_MAXLEN MAX(MAX(sizeof(COMPRESS_SUFFIX_GZ),sizeof(COMPRESS_SUFFIX_BZ2)),sizeof(COMPRESS_SUFFIX_XZ))
112 #define COMPRESS_TYPES 5 /* Number of supported compression types */
114 #define COMPRESS_NONE 0
115 #define COMPRESS_GZIP 1
116 #define COMPRESS_BZIP2 2
117 #define COMPRESS_XZ 3
118 #define COMPRESS_ZSTD 4
121 * Bit-values for the 'flags' parsed from a config-file entry.
123 #define CE_BINARY 0x0008 /* Logfile is in binary, do not add status */
124 /* messages to logfile(s) when rotating. */
125 #define CE_NOSIGNAL 0x0010 /* There is no process to signal when */
126 /* trimming this file. */
127 #define CE_TRIMAT 0x0020 /* trim file at a specific time. */
128 #define CE_GLOB 0x0040 /* name of the log is file name pattern. */
129 #define CE_SIGNALGROUP 0x0080 /* Signal a process-group instead of a single */
130 /* process when trimming this file. */
131 #define CE_CREATE 0x0100 /* Create the log file if it does not exist. */
132 #define CE_NODUMP 0x0200 /* Set 'nodump' on newly created log file. */
133 #define CE_PID2CMD 0x0400 /* Replace PID file with a shell command.*/
134 #define CE_PLAIN0 0x0800 /* Do not compress zero'th history file */
136 #define CE_RFC5424 0x0800 /* Use RFC5424 format rotation message */
138 #define MIN_PID 5 /* Don't touch pids lower than this */
139 #define MAX_PID PID_MAX /* was lower, see /usr/include/sys/proc.h */
141 #define kbytes(size) (((size) + 1023) >> 10)
143 #define DEFAULT_MARKER "<default>"
144 #define DEBUG_MARKER "<debug>"
145 #define INCLUDE_MARKER "<include>"
146 #define DEFAULT_TIMEFNAME_FMT "%Y%m%dT%H%M%S"
148 #define MAX_OLDLOGS 65536 /* Default maximum number of old logfiles */
150 struct compress_types
{
151 const char *flag
; /* Flag in configuration file */
152 const char *suffix
; /* Compression suffix */
153 const char *path
; /* Path to compression program */
154 char **args
; /* Compression program arguments */
157 static char f_arg
[] = "-f";
158 static char q_arg
[] = "-q";
159 static char rm_arg
[] = "--rm";
160 static char *gz_args
[] ={ NULL
, f_arg
, NULL
, NULL
};
161 #define bzip2_args gz_args
162 #define xz_args gz_args
163 static char *zstd_args
[] = { NULL
, q_arg
, rm_arg
, NULL
, NULL
};
166 static const struct compress_types compress_type
[COMPRESS_TYPES
] = {
167 { "", "", "", NULL
}, /* none */
168 { "Z", COMPRESS_SUFFIX_GZ
, _PATH_GZIP
, gz_args
}, /* gzip */
169 { "J", COMPRESS_SUFFIX_BZ2
, _PATH_BZIP2
, bzip2_args
}, /* bzip2 */
170 { "X", COMPRESS_SUFFIX_XZ
, _PATH_XZ
, xz_args
}, /* xz */
171 { "Y", COMPRESS_SUFFIX_ZST
, _PATH_ZSTD
, zstd_args
} /* zst */
175 STAILQ_ENTRY(conf_entry
) cf_nextp
;
176 char *log
; /* Name of the log */
177 char *pid_cmd_file
; /* PID or command file */
178 char *r_reason
; /* The reason this file is being rotated */
179 int firstcreate
; /* Creating log for the first time (-C). */
180 int rotate
; /* Non-zero if this file should be rotated */
181 int fsize
; /* size found for the log file */
182 uid_t uid
; /* Owner of log */
183 gid_t gid
; /* Group of log */
184 int numlogs
; /* Number of logs to keep */
185 int trsize
; /* Size cutoff to trigger trimming the log */
186 int hours
; /* Hours between log trimming */
187 struct ptime_data
*trim_at
; /* Specific time to do trimming */
188 unsigned int permissions
; /* File permissions on the log */
189 int flags
; /* CE_BINARY */
190 int compress
; /* Compression */
191 int sig
; /* Signal to send */
192 int def_cfg
; /* Using the <default> rule for this file */
195 struct sigwork_entry
{
196 SLIST_ENTRY(sigwork_entry
) sw_nextp
;
197 int sw_signum
; /* the signal to send */
198 int sw_pidok
; /* true if pid value is valid */
199 pid_t sw_pid
; /* the process id from the PID file */
200 const char *sw_pidtype
; /* "daemon" or "process group" */
201 int sw_runcmd
; /* run command or send PID to signal */
202 char sw_fname
[1]; /* file the PID was read from or shell cmd */
205 struct zipwork_entry
{
206 SLIST_ENTRY(zipwork_entry
) zw_nextp
;
207 const struct conf_entry
*zw_conf
; /* for chown/perm/flag info */
208 const struct sigwork_entry
*zw_swork
; /* to know success of signal */
209 int zw_fsize
; /* size of the file to compress */
210 char zw_fname
[1]; /* the file to compress */
213 struct include_entry
{
214 STAILQ_ENTRY(include_entry
) inc_nextp
;
215 const char *file
; /* Name of file to process */
218 struct oldlog_entry
{
219 char *fname
; /* Filename of the log file */
220 time_t t
; /* Parsed timestamp of the logfile */
227 STAILQ_HEAD(cflist
, conf_entry
);
228 static SLIST_HEAD(swlisthead
, sigwork_entry
) swhead
=
229 SLIST_HEAD_INITIALIZER(swhead
);
230 static SLIST_HEAD(zwlisthead
, zipwork_entry
) zwhead
=
231 SLIST_HEAD_INITIALIZER(zwhead
);
232 STAILQ_HEAD(ilist
, include_entry
);
234 int dbg_at_times
; /* -D Show details of 'trim_at' code */
236 static int archtodir
= 0; /* Archive old logfiles to other directory */
237 static int createlogs
; /* Create (non-GLOB) logfiles which do not */
238 /* already exist. 1=='for entries with */
239 /* C flag', 2=='for all entries'. */
240 int verbose
= 0; /* Print out what's going on */
241 static int needroot
= 1; /* Root privs are necessary */
242 int noaction
= 0; /* Don't do anything, just show it */
243 static int norotate
= 0; /* Don't rotate */
244 static int nosignal
; /* Do not send any signals */
245 static int enforcepid
= 0; /* If PID file does not exist or empty, do nothing */
246 static int force
= 0; /* Force the trim no matter what */
247 static int rotatereq
= 0; /* -R = Always rotate the file(s) as given */
248 /* on the command (this also requires */
249 /* that a list of files *are* given on */
250 /* the run command). */
251 static char *requestor
; /* The name given on a -R request */
252 static char *timefnamefmt
= NULL
; /* Use time based filenames instead of .0 */
253 static char *archdirname
; /* Directory path to old logfiles archive */
254 static char *destdir
= NULL
; /* Directory to treat at root for logs */
255 static const char *conf
; /* Configuration file to use */
257 struct ptime_data
*dbg_timenow
; /* A "timenow" value set via -D option */
258 static struct ptime_data
*timenow
; /* The time to use for checking at-fields */
260 #define DAYTIME_LEN 16
261 static char daytime
[DAYTIME_LEN
]; /* The current time in human readable form,
262 * used for rotation-tracking messages. */
264 /* Another buffer to hold the current time in RFC5424 format. Fractional
265 * seconds are allowed by the RFC, but are not included in the
266 * rotation-tracking messages written by newsyslog and so are not accounted for
267 * in the length below.
269 #define DAYTIME_RFC5424_LEN sizeof("YYYY-MM-DDTHH:MM:SS+00:00")
270 static char daytime_rfc5424
[DAYTIME_RFC5424_LEN
];
272 static char hostname
[MAXHOSTNAMELEN
]; /* hostname */
274 static const char *path_syslogpid
= _PATH_SYSLOGPID
;
276 static struct cflist
*get_worklist(char **files
);
277 static void parse_file(FILE *cf
, struct cflist
*work_p
, struct cflist
*glob_p
,
278 struct conf_entry
*defconf_p
, struct ilist
*inclist
);
279 static void add_to_queue(const char *fname
, struct ilist
*inclist
);
280 static char *sob(char *p
);
281 static char *son(char *p
);
282 static int isnumberstr(const char *);
283 static int isglobstr(const char *);
284 static char *missing_field(char *p
, char *errline
);
285 static void change_attrs(const char *, const struct conf_entry
*);
286 static const char *get_logfile_suffix(const char *logfile
);
287 static fk_entry
do_entry(struct conf_entry
*);
288 static fk_entry
do_rotate(const struct conf_entry
*);
289 static void do_sigwork(struct sigwork_entry
*);
290 static void do_zipwork(struct zipwork_entry
*);
291 static struct sigwork_entry
*
292 save_sigwork(const struct conf_entry
*);
293 static struct zipwork_entry
*
294 save_zipwork(const struct conf_entry
*, const struct
295 sigwork_entry
*, int, const char *);
296 static void set_swpid(struct sigwork_entry
*, const struct conf_entry
*);
297 static int sizefile(const char *);
298 static void expand_globs(struct cflist
*work_p
, struct cflist
*glob_p
);
299 static void free_clist(struct cflist
*list
);
300 static void free_entry(struct conf_entry
*ent
);
301 static struct conf_entry
*init_entry(const char *fname
,
302 struct conf_entry
*src_entry
);
303 static void parse_args(int argc
, char **argv
);
304 static int parse_doption(const char *doption
);
305 static void usage(void) __dead2
;
306 static int log_trim(const char *logname
, const struct conf_entry
*log_ent
);
307 static int age_old_log(const char *file
);
308 static void savelog(char *from
, char *to
);
309 static void createdir(const struct conf_entry
*ent
, char *dirpart
);
310 static void createlog(const struct conf_entry
*ent
);
311 static int parse_signal(const char *str
);
314 * All the following take a parameter of 'int', but expect values in the
315 * range of unsigned char. Define wrappers which take values of type 'char',
316 * whether signed or unsigned, and ensure they end up in the right range.
318 #define isdigitch(Anychar) isdigit((u_char)(Anychar))
319 #define isprintch(Anychar) isprint((u_char)(Anychar))
320 #define isspacech(Anychar) isspace((u_char)(Anychar))
321 #define tolowerch(Anychar) tolower((u_char)(Anychar))
324 main(int argc
, char **argv
)
326 struct cflist
*worklist
;
327 struct conf_entry
*p
;
328 struct sigwork_entry
*stmp
;
329 struct zipwork_entry
*ztmp
;
334 parse_args(argc
, argv
);
338 if (needroot
&& getuid() && geteuid())
339 errx(1, "must have root privs");
340 worklist
= get_worklist(argv
);
343 * Rotate all the files which need to be rotated. Note that
344 * some users have *hundreds* of entries in newsyslog.conf!
346 while (!STAILQ_EMPTY(worklist
)) {
347 p
= STAILQ_FIRST(worklist
);
348 STAILQ_REMOVE_HEAD(worklist
, cf_nextp
);
349 if (do_entry(p
) == FREE_ENT
)
354 * Send signals to any processes which need a signal to tell
355 * them to close and re-open the log file(s) we have rotated.
356 * Note that zipwork_entries include pointers to these
357 * sigwork_entry's, so we can not free the entries here.
359 if (!SLIST_EMPTY(&swhead
)) {
360 if (noaction
|| verbose
)
361 printf("Signal all daemon process(es)...\n");
362 SLIST_FOREACH(stmp
, &swhead
, sw_nextp
)
364 if (!(rotatereq
&& nosignal
)) {
366 printf("\tsleep 10\n");
369 printf("Pause 10 seconds to allow "
370 "daemon(s) to close log file(s)\n");
376 * Compress all files that we're expected to compress, now
377 * that all processes should have closed the files which
380 if (!SLIST_EMPTY(&zwhead
)) {
381 if (noaction
|| verbose
)
382 printf("Compress all rotated log file(s)...\n");
383 while (!SLIST_EMPTY(&zwhead
)) {
384 ztmp
= SLIST_FIRST(&zwhead
);
386 SLIST_REMOVE_HEAD(&zwhead
, zw_nextp
);
390 /* Now free all the sigwork entries. */
391 while (!SLIST_EMPTY(&swhead
)) {
392 stmp
= SLIST_FIRST(&swhead
);
393 SLIST_REMOVE_HEAD(&swhead
, sw_nextp
);
397 while (wait(NULL
) > 0 || errno
== EINTR
)
402 static struct conf_entry
*
403 init_entry(const char *fname
, struct conf_entry
*src_entry
)
405 struct conf_entry
*tempwork
;
408 printf("\t--> [creating entry for %s]\n", fname
);
410 tempwork
= malloc(sizeof(struct conf_entry
));
411 if (tempwork
== NULL
)
412 err(1, "malloc of conf_entry for %s", fname
);
414 if (destdir
== NULL
|| fname
[0] != '/')
415 tempwork
->log
= strdup(fname
);
417 asprintf(&tempwork
->log
, "%s%s", destdir
, fname
);
418 if (tempwork
->log
== NULL
)
419 err(1, "strdup for %s", fname
);
421 if (src_entry
!= NULL
) {
422 tempwork
->pid_cmd_file
= NULL
;
423 if (src_entry
->pid_cmd_file
)
424 tempwork
->pid_cmd_file
= strdup(src_entry
->pid_cmd_file
);
425 tempwork
->r_reason
= NULL
;
426 tempwork
->firstcreate
= 0;
427 tempwork
->rotate
= 0;
428 tempwork
->fsize
= -1;
429 tempwork
->uid
= src_entry
->uid
;
430 tempwork
->gid
= src_entry
->gid
;
431 tempwork
->numlogs
= src_entry
->numlogs
;
432 tempwork
->trsize
= src_entry
->trsize
;
433 tempwork
->hours
= src_entry
->hours
;
434 tempwork
->trim_at
= NULL
;
435 if (src_entry
->trim_at
!= NULL
)
436 tempwork
->trim_at
= ptime_init(src_entry
->trim_at
);
437 tempwork
->permissions
= src_entry
->permissions
;
438 tempwork
->flags
= src_entry
->flags
;
439 tempwork
->compress
= src_entry
->compress
;
440 tempwork
->sig
= src_entry
->sig
;
441 tempwork
->def_cfg
= src_entry
->def_cfg
;
443 /* Initialize as a "do-nothing" entry */
444 tempwork
->pid_cmd_file
= NULL
;
445 tempwork
->r_reason
= NULL
;
446 tempwork
->firstcreate
= 0;
447 tempwork
->rotate
= 0;
448 tempwork
->fsize
= -1;
449 tempwork
->uid
= (uid_t
)-1;
450 tempwork
->gid
= (gid_t
)-1;
451 tempwork
->numlogs
= 1;
452 tempwork
->trsize
= -1;
453 tempwork
->hours
= -1;
454 tempwork
->trim_at
= NULL
;
455 tempwork
->permissions
= 0;
457 tempwork
->compress
= COMPRESS_NONE
;
458 tempwork
->sig
= SIGHUP
;
459 tempwork
->def_cfg
= 0;
466 free_entry(struct conf_entry
*ent
)
472 if (ent
->log
!= NULL
) {
474 printf("\t--> [freeing entry for %s]\n", ent
->log
);
479 if (ent
->pid_cmd_file
!= NULL
) {
480 free(ent
->pid_cmd_file
);
481 ent
->pid_cmd_file
= NULL
;
484 if (ent
->r_reason
!= NULL
) {
486 ent
->r_reason
= NULL
;
489 if (ent
->trim_at
!= NULL
) {
490 ptime_free(ent
->trim_at
);
498 free_clist(struct cflist
*list
)
500 struct conf_entry
*ent
;
502 while (!STAILQ_EMPTY(list
)) {
503 ent
= STAILQ_FIRST(list
);
504 STAILQ_REMOVE_HEAD(list
, cf_nextp
);
513 do_entry(struct conf_entry
* ent
)
515 #define REASON_MAX 80
517 fk_entry free_or_keep
;
519 char temp_reason
[REASON_MAX
];
522 free_or_keep
= FREE_ENT
;
524 printf("%s <%d%s>: ", ent
->log
, ent
->numlogs
,
525 compress_type
[ent
->compress
].flag
);
526 ent
->fsize
= sizefile(ent
->log
);
527 oversized
= ((ent
->trsize
> 0) && (ent
->fsize
>= ent
->trsize
));
528 modtime
= age_old_log(ent
->log
);
530 ent
->firstcreate
= 0;
531 if (ent
->fsize
< 0) {
533 * If either the C flag or the -C option was specified,
534 * and if we won't be creating the file, then have the
535 * verbose message include a hint as to why the file
536 * will not be created.
538 temp_reason
[0] = '\0';
540 ent
->firstcreate
= 1;
541 else if ((ent
->flags
& CE_CREATE
) && createlogs
)
542 ent
->firstcreate
= 1;
543 else if (ent
->flags
& CE_CREATE
)
544 strlcpy(temp_reason
, " (no -C option)", REASON_MAX
);
546 strlcpy(temp_reason
, " (no C flag)", REASON_MAX
);
548 if (ent
->firstcreate
) {
550 printf("does not exist -> will create.\n");
552 } else if (verbose
) {
553 printf("does not exist, skipped%s.\n", temp_reason
);
556 if (ent
->flags
& CE_TRIMAT
&& !force
&& !rotatereq
&&
558 diffsecs
= ptimeget_diff(timenow
, ent
->trim_at
);
559 if (diffsecs
< 0.0) {
560 /* trim_at is some time in the future. */
562 ptime_adjust4dst(ent
->trim_at
,
564 printf("--> will trim at %s",
565 ptimeget_ctime(ent
->trim_at
));
567 return (free_or_keep
);
568 } else if (diffsecs
>= 3600.0) {
570 * trim_at is more than an hour in the past,
571 * so find the next valid trim_at time, and
572 * tell the user what that will be.
574 if (verbose
&& dbg_at_times
)
575 printf("\n\t--> prev trim at %s\t",
576 ptimeget_ctime(ent
->trim_at
));
578 ptimeset_nxtime(ent
->trim_at
);
579 printf("--> will trim at %s",
580 ptimeget_ctime(ent
->trim_at
));
582 return (free_or_keep
);
583 } else if (verbose
&& noaction
&& dbg_at_times
) {
585 * If we are just debugging at-times, then
586 * a detailed message is helpful. Also
587 * skip "doing" any commands, since they
588 * would all be turned off by no-action.
590 printf("\n\t--> timematch at %s",
591 ptimeget_ctime(ent
->trim_at
));
592 return (free_or_keep
);
593 } else if (verbose
&& ent
->hours
<= 0) {
594 printf("--> time is up\n");
597 if (verbose
&& (ent
->trsize
> 0))
598 printf("size (Kb): %d [%d] ", ent
->fsize
, ent
->trsize
);
599 if (verbose
&& (ent
->hours
> 0))
600 printf(" age (hr): %d [%d] ", modtime
, ent
->hours
);
603 * Figure out if this logfile needs to be rotated.
605 temp_reason
[0] = '\0';
608 snprintf(temp_reason
, REASON_MAX
, " due to -R from %s",
612 snprintf(temp_reason
, REASON_MAX
, " due to -F request");
613 } else if (oversized
) {
615 snprintf(temp_reason
, REASON_MAX
, " due to size>%dK",
617 } else if (ent
->hours
<= 0 && (ent
->flags
& CE_TRIMAT
)) {
619 } else if ((ent
->hours
> 0) && ((modtime
>= ent
->hours
) ||
625 * If the file needs to be rotated, then rotate it.
627 if (ent
->rotate
&& !norotate
) {
628 if (temp_reason
[0] != '\0')
629 ent
->r_reason
= strdup(temp_reason
);
631 printf("--> trimming log....\n");
632 if (noaction
&& !verbose
)
633 printf("%s <%d%s>: trimming\n", ent
->log
,
635 compress_type
[ent
->compress
].flag
);
636 free_or_keep
= do_rotate(ent
);
639 printf("--> skipping\n");
642 return (free_or_keep
);
647 parse_args(int argc
, char **argv
)
652 timenow
= ptime_init(NULL
);
653 ptimeset_time(timenow
, time(NULL
));
654 strlcpy(daytime
, ptimeget_ctime(timenow
) + 4, DAYTIME_LEN
);
655 ptimeget_ctime_rfc5424(timenow
, daytime_rfc5424
, DAYTIME_RFC5424_LEN
);
657 /* Let's get our hostname */
658 gethostname(hostname
, sizeof(hostname
));
660 /* Truncate domain */
661 if ((p
= strchr(hostname
, '.')) != NULL
)
664 /* Parse command line options. */
665 while ((ch
= getopt(argc
, argv
, "a:d:f:nrst:vCD:FNPR:S:")) != -1)
669 archdirname
= optarg
;
687 if (optarg
[0] == '\0' ||
688 strcmp(optarg
, "DEFAULT") == 0)
689 timefnamefmt
= strdup(DEFAULT_TIMEFNAME_FMT
);
691 timefnamefmt
= strdup(optarg
);
697 /* Useful for things like rc.diskless... */
702 * Set some debugging option. The specific option
703 * depends on the value of optarg. These options
704 * may come and go without notice or documentation.
706 if (parse_doption(optarg
))
721 requestor
= strdup(optarg
);
724 path_syslogpid
= optarg
;
726 case 'm': /* Used by OpenBSD for "monitor mode" */
732 if (force
&& norotate
) {
733 warnx("Only one of -F and -N may be specified.");
739 if (optind
== argc
) {
740 warnx("At least one filename must be given when -R is specified.");
744 /* Make sure "requestor" value is safe for a syslog message. */
745 for (p
= requestor
; *p
!= '\0'; p
++) {
746 if (!isprintch(*p
) && (*p
!= '\t'))
753 * Note that the 'daytime' variable is not changed.
754 * That is only used in messages that track when a
755 * logfile is rotated, and if a file *is* rotated,
756 * then it will still rotated at the "real now" time.
759 timenow
= dbg_timenow
;
760 fprintf(stderr
, "Debug: Running as if TimeNow is %s",
761 ptimeget_ctime(dbg_timenow
));
767 * These debugging options are mainly meant for developer use, such
768 * as writing regression-tests. They would not be needed by users
769 * during normal operation of newsyslog...
772 parse_doption(const char *doption
)
774 const char TN
[] = "TN=";
777 if (strncmp(doption
, TN
, sizeof(TN
) - 1) == 0) {
779 * The "TimeNow" debugging option. This might be off
780 * by an hour when crossing a timezone change.
782 dbg_timenow
= ptime_init(NULL
);
783 res
= ptime_relparse(dbg_timenow
, PTM_PARSE_ISO8601
,
784 time(NULL
), doption
+ sizeof(TN
) - 1);
786 warnx("Non-existent time specified on -D %s", doption
);
787 return (0); /* failure */
788 } else if (res
< 0) {
789 warnx("Malformed time given on -D %s", doption
);
790 return (0); /* failure */
792 return (1); /* successfully parsed */
796 if (strcmp(doption
, "ats") == 0) {
798 return (1); /* successfully parsed */
801 /* XXX - This check could probably be dropped. */
802 if ((strcmp(doption
, "neworder") == 0) || (strcmp(doption
, "oldorder")
804 warnx("NOTE: newsyslog always uses 'neworder'.");
805 return (1); /* successfully parsed */
808 warnx("Unknown -D (debug) option: '%s'", doption
);
809 return (0); /* failure */
817 "usage: newsyslog [-CFNPnrsv] [-a directory] [-d directory] [-f config_file]\n"
818 " [-S pidfile] [-t timefmt] [[-R tagname] file ...]\n");
823 * Parse a configuration file and return a linked list of all the logs
824 * which should be processed.
826 static struct cflist
*
827 get_worklist(char **files
)
831 struct cflist
*cmdlist
, *filelist
, *globlist
;
832 struct conf_entry
*defconf
, *dupent
, *ent
;
833 struct ilist inclist
;
834 struct include_entry
*inc
;
838 STAILQ_INIT(&inclist
);
840 filelist
= malloc(sizeof(struct cflist
));
841 if (filelist
== NULL
)
842 err(1, "malloc of filelist");
843 STAILQ_INIT(filelist
);
844 globlist
= malloc(sizeof(struct cflist
));
845 if (globlist
== NULL
)
846 err(1, "malloc of globlist");
847 STAILQ_INIT(globlist
);
849 inc
= malloc(sizeof(struct include_entry
));
851 err(1, "malloc of inc");
853 if (inc
->file
== NULL
)
854 inc
->file
= _PATH_CONF
;
855 STAILQ_INSERT_TAIL(&inclist
, inc
, inc_nextp
);
857 STAILQ_FOREACH(inc
, &inclist
, inc_nextp
) {
858 if (strcmp(inc
->file
, "-") != 0)
859 f
= fopen(inc
->file
, "r");
862 inc
->file
= "<stdin>";
865 err(1, "%s", inc
->file
);
868 printf("Processing %s\n", inc
->file
);
869 parse_file(f
, filelist
, globlist
, defconf
, &inclist
);
874 * All config-file information has been read in and turned into
875 * a filelist and a globlist. If there were no specific files
876 * given on the run command, then the only thing left to do is to
877 * call a routine which finds all files matched by the globlist
878 * and adds them to the filelist. Then return the worklist.
880 if (*files
== NULL
) {
881 expand_globs(filelist
, globlist
);
882 free_clist(globlist
);
890 * If newsyslog was given a specific list of files to process,
891 * it may be that some of those files were not listed in any
892 * config file. Those unlisted files should get the default
893 * rotation action. First, create the default-rotation action
894 * if none was found in a system config file.
896 if (defconf
== NULL
) {
897 defconf
= init_entry(DEFAULT_MARKER
, NULL
);
898 defconf
->numlogs
= 3;
899 defconf
->trsize
= 50;
900 defconf
->permissions
= S_IRUSR
|S_IWUSR
;
904 * If newsyslog was run with a list of specific filenames,
905 * then create a new worklist which has only those files in
906 * it, picking up the rotation-rules for those files from
907 * the original filelist.
909 * XXX - Note that this will copy multiple rules for a single
910 * logfile, if multiple entries are an exact match for
911 * that file. That matches the historic behavior, but do
912 * we want to continue to allow it? If so, it should
913 * probably be handled more intelligently.
915 cmdlist
= malloc(sizeof(struct cflist
));
917 err(1, "malloc of cmdlist");
918 STAILQ_INIT(cmdlist
);
920 for (given
= files
; *given
; ++given
) {
922 * First try to find exact-matches for this given file.
925 STAILQ_FOREACH(ent
, filelist
, cf_nextp
) {
926 if (strcmp(ent
->log
, *given
) == 0) {
928 dupent
= init_entry(*given
, ent
);
929 STAILQ_INSERT_TAIL(cmdlist
, dupent
, cf_nextp
);
934 printf("\t+ Matched entry %s\n", *given
);
939 * There was no exact-match for this given file, so look
940 * for a "glob" entry which does match.
943 if (verbose
> 2 && globlist
!= NULL
)
944 printf("\t+ Checking globs for %s\n", *given
);
945 STAILQ_FOREACH(ent
, globlist
, cf_nextp
) {
946 fnres
= fnmatch(ent
->log
, *given
, FNM_PATHNAME
);
948 printf("\t+ = %d for pattern %s\n", fnres
,
952 dupent
= init_entry(*given
, ent
);
953 /* This new entry is not a glob! */
954 dupent
->flags
&= ~CE_GLOB
;
955 STAILQ_INSERT_TAIL(cmdlist
, dupent
, cf_nextp
);
956 /* Only allow a match to one glob-entry */
962 printf("\t+ Matched %s via %s\n", *given
,
968 * This given file was not found in any config file, so
969 * add a worklist item based on the default entry.
972 printf("\t+ No entry matched %s (will use %s)\n",
973 *given
, DEFAULT_MARKER
);
974 dupent
= init_entry(*given
, defconf
);
975 /* Mark that it was *not* found in a config file */
977 STAILQ_INSERT_TAIL(cmdlist
, dupent
, cf_nextp
);
981 * Free all the entries in the original work list, the list of
982 * glob entries, and the default entry.
984 free_clist(filelist
);
985 free_clist(globlist
);
988 /* And finally, return a worklist which matches the given files. */
993 * Expand the list of entries with filename patterns, and add all files
994 * which match those glob-entries onto the worklist.
997 expand_globs(struct cflist
*work_p
, struct cflist
*glob_p
)
1002 struct conf_entry
*dupent
, *ent
, *globent
;
1007 * The worklist contains all fully-specified (non-GLOB) names.
1009 * Now expand the list of filename-pattern (GLOB) entries into
1010 * a second list, which (by definition) will only match files
1011 * that already exist. Do not add a glob-related entry for any
1012 * file which already exists in the fully-specified list.
1014 STAILQ_FOREACH(globent
, glob_p
, cf_nextp
) {
1015 gres
= glob(globent
->log
, GLOB_NOCHECK
, NULL
, &pglob
);
1017 warn("cannot expand pattern (%d): %s", gres
,
1023 printf("\t+ Expanding pattern %s\n", globent
->log
);
1024 for (i
= 0; i
< pglob
.gl_matchc
; i
++) {
1025 mfname
= pglob
.gl_pathv
[i
];
1027 /* See if this file already has a specific entry. */
1029 STAILQ_FOREACH(ent
, work_p
, cf_nextp
) {
1030 if (strcmp(mfname
, ent
->log
) == 0) {
1038 /* Make sure the named matched is a file. */
1039 gres
= lstat(mfname
, &st_fm
);
1041 /* Error on a file that glob() matched?!? */
1042 warn("Skipping %s - lstat() error", mfname
);
1045 if (!S_ISREG(st_fm
.st_mode
)) {
1046 /* We only rotate files! */
1048 printf("\t+ . skipping %s (!file)\n",
1054 printf("\t+ . add file %s\n", mfname
);
1055 dupent
= init_entry(mfname
, globent
);
1056 /* This new entry is not a glob! */
1057 dupent
->flags
&= ~CE_GLOB
;
1059 /* Add to the worklist. */
1060 STAILQ_INSERT_TAIL(work_p
, dupent
, cf_nextp
);
1064 printf("\t+ Done with pattern %s\n", globent
->log
);
1069 * Parse a configuration file and update a linked list of all the logs to
1073 parse_file(FILE *cf
, struct cflist
*work_p
, struct cflist
*glob_p
,
1074 struct conf_entry
*defconf_p
, struct ilist
*inclist
)
1076 char line
[BUFSIZ
], *parse
, *q
;
1077 char *cp
, *errline
, *group
;
1078 struct conf_entry
*working
;
1082 int eol
, ptm_opts
, res
, special
;
1086 while (fgets(line
, BUFSIZ
, cf
)) {
1087 if ((line
[0] == '\n') || (line
[0] == '#') ||
1088 (strlen(line
) == 0))
1090 if (errline
!= NULL
)
1092 errline
= strdup(line
);
1093 for (cp
= line
+ 1; *cp
!= '\0'; cp
++) {
1096 if (*(cp
- 1) == '\\') {
1105 q
= parse
= missing_field(sob(line
), errline
);
1108 errx(1, "malformed line (missing fields):\n%s",
1113 * Allow people to set debug options via the config file.
1114 * (NOTE: debug options are undocumented, and may disappear
1115 * at any time, etc).
1117 if (strcasecmp(DEBUG_MARKER
, q
) == 0) {
1118 q
= parse
= missing_field(sob(parse
+ 1), errline
);
1121 warnx("debug line specifies no option:\n%s",
1128 } else if (strcasecmp(INCLUDE_MARKER
, q
) == 0) {
1130 printf("Found: %s", errline
);
1131 q
= parse
= missing_field(sob(parse
+ 1), errline
);
1134 warnx("include line missing argument:\n%s",
1142 res
= glob(q
, GLOB_NOCHECK
, NULL
, &pglob
);
1144 warn("cannot expand pattern (%d): %s",
1150 printf("\t+ Expanding pattern %s\n", q
);
1152 for (i
= 0; i
< pglob
.gl_matchc
; i
++)
1153 add_to_queue(pglob
.gl_pathv
[i
],
1157 add_to_queue(q
, inclist
);
1162 working
= init_entry(q
, NULL
);
1163 if (strcasecmp(DEFAULT_MARKER
, q
) == 0) {
1165 if (defconf_p
!= NULL
) {
1166 warnx("Ignoring duplicate entry for %s!", q
);
1167 free_entry(working
);
1170 defconf_p
= working
;
1173 q
= parse
= missing_field(sob(parse
+ 1), errline
);
1176 errx(1, "malformed line (missing fields):\n%s",
1179 if ((group
= strchr(q
, ':')) != NULL
||
1180 (group
= strrchr(q
, '.')) != NULL
) {
1183 if (!(isnumberstr(q
))) {
1184 if ((pwd
= getpwnam(q
)) == NULL
)
1186 "error in config file; unknown user:\n%s",
1188 working
->uid
= pwd
->pw_uid
;
1190 working
->uid
= atoi(q
);
1192 working
->uid
= (uid_t
)-1;
1196 if (!(isnumberstr(q
))) {
1197 if ((grp
= getgrnam(q
)) == NULL
)
1199 "error in config file; unknown group:\n%s",
1201 working
->gid
= grp
->gr_gid
;
1203 working
->gid
= atoi(q
);
1205 working
->gid
= (gid_t
)-1;
1207 q
= parse
= missing_field(sob(parse
+ 1), errline
);
1210 errx(1, "malformed line (missing fields):\n%s",
1214 working
->uid
= (uid_t
)-1;
1215 working
->gid
= (gid_t
)-1;
1218 if (!sscanf(q
, "%o", &working
->permissions
))
1219 errx(1, "error in config file; bad permissions:\n%s",
1222 q
= parse
= missing_field(sob(parse
+ 1), errline
);
1225 errx(1, "malformed line (missing fields):\n%s",
1228 if (!sscanf(q
, "%d", &working
->numlogs
) || working
->numlogs
< 0)
1229 errx(1, "error in config file; bad value for count of logs to save:\n%s",
1232 q
= parse
= missing_field(sob(parse
+ 1), errline
);
1235 errx(1, "malformed line (missing fields):\n%s",
1239 working
->trsize
= atoi(q
);
1240 else if (strcmp(q
, "*") == 0)
1241 working
->trsize
= -1;
1243 warnx("Invalid value of '%s' for 'size' in line:\n%s",
1245 working
->trsize
= -1;
1249 working
->compress
= COMPRESS_NONE
;
1250 q
= parse
= missing_field(sob(parse
+ 1), errline
);
1258 ul
= strtoul(q
, &ep
, 10);
1261 else if (*ep
== '*')
1262 working
->hours
= -1;
1263 else if (ul
> INT_MAX
)
1264 errx(1, "interval is too large:\n%s", errline
);
1266 working
->hours
= ul
;
1268 if (*ep
== '\0' || strcmp(ep
, "*") == 0)
1270 if (*ep
!= '@' && *ep
!= '$')
1271 errx(1, "malformed interval/at:\n%s", errline
);
1273 working
->flags
|= CE_TRIMAT
;
1274 working
->trim_at
= ptime_init(NULL
);
1275 ptm_opts
= PTM_PARSE_ISO8601
;
1277 ptm_opts
= PTM_PARSE_DWM
;
1278 ptm_opts
|= PTM_PARSE_MATCHDOM
;
1279 res
= ptime_relparse(working
->trim_at
, ptm_opts
,
1280 ptimeget_secs(timenow
), ep
+ 1);
1282 errx(1, "nonexistent time for 'at' value:\n%s",
1285 errx(1, "malformed 'at' value:\n%s", errline
);
1292 q
= parse
= sob(parse
+ 1); /* Optional field */
1299 for (; q
&& *q
&& !isspacech(*q
); q
++) {
1300 switch (tolowerch(*q
)) {
1302 working
->flags
|= CE_BINARY
;
1305 working
->flags
|= CE_CREATE
;
1308 working
->flags
|= CE_NODUMP
;
1311 working
->flags
|= CE_GLOB
;
1314 working
->compress
= COMPRESS_BZIP2
;
1317 working
->flags
|= CE_NOSIGNAL
;
1320 working
->flags
|= CE_PLAIN0
;
1323 working
->flags
|= CE_PID2CMD
;
1326 working
->flags
|= CE_RFC5424
;
1329 working
->flags
|= CE_SIGNALGROUP
;
1332 /* Deprecated flag - keep for compatibility purposes */
1335 working
->compress
= COMPRESS_XZ
;
1339 working
->compress
= COMPRESS_ZSTD
;
1343 working
->compress
= COMPRESS_GZIP
;
1347 case 'f': /* Used by OpenBSD for "CE_FOLLOW" */
1348 case 'm': /* Used by OpenBSD for "CE_MONITOR" */
1350 errx(1, "illegal flag in config file -- %c",
1358 q
= parse
= sob(parse
+ 1); /* Optional field */
1365 working
->pid_cmd_file
= NULL
;
1368 working
->pid_cmd_file
= strdup(q
);
1369 else if (isalnum(*q
))
1373 "illegal pid file or signal in config file:\n%s",
1380 q
= parse
= sob(parse
+ 1); /* Optional field */
1381 *(parse
= son(parse
)) = '\0';
1384 working
->sig
= SIGHUP
;
1387 working
->sig
= parse_signal(q
);
1388 if (working
->sig
< 1 || working
->sig
>= sys_nsig
) {
1390 "illegal signal in config file:\n%s",
1396 * Finish figuring out what pid-file to use (if any) in
1397 * later processing if this logfile needs to be rotated.
1399 if ((working
->flags
& CE_NOSIGNAL
) == CE_NOSIGNAL
) {
1401 * This config-entry specified 'n' for nosignal,
1402 * see if it also specified an explicit pid_cmd_file.
1403 * This would be a pretty pointless combination.
1405 if (working
->pid_cmd_file
!= NULL
) {
1406 warnx("Ignoring '%s' because flag 'n' was specified in line:\n%s",
1407 working
->pid_cmd_file
, errline
);
1408 free(working
->pid_cmd_file
);
1409 working
->pid_cmd_file
= NULL
;
1411 } else if (working
->pid_cmd_file
== NULL
) {
1413 * This entry did not specify the 'n' flag, which
1414 * means it should signal syslogd unless it had
1415 * specified some other pid-file (and obviously the
1416 * syslog pid-file will not be for a process-group).
1417 * Also, we should only try to notify syslog if we
1420 if (working
->flags
& CE_SIGNALGROUP
) {
1421 warnx("Ignoring flag 'U' in line:\n%s",
1423 working
->flags
&= ~CE_SIGNALGROUP
;
1426 working
->pid_cmd_file
= strdup(path_syslogpid
);
1430 * Add this entry to the appropriate list of entries, unless
1431 * it was some kind of special entry (eg: <default>).
1434 ; /* Do not add to any list */
1435 } else if (working
->flags
& CE_GLOB
) {
1436 STAILQ_INSERT_TAIL(glob_p
, working
, cf_nextp
);
1438 STAILQ_INSERT_TAIL(work_p
, working
, cf_nextp
);
1441 if (errline
!= NULL
)
1446 missing_field(char *p
, char *errline
)
1450 errx(1, "missing field in config file:\n%s", errline
);
1455 * In our sort we return it in the reverse of what qsort normally
1456 * would do, as we want the newest files first. If we have two
1457 * entries with the same time we don't really care about order.
1459 * Support function for qsort() in delete_oldest_timelog().
1462 oldlog_entry_compare(const void *a
, const void *b
)
1464 const struct oldlog_entry
*ola
= a
, *olb
= b
;
1466 if (ola
->t
> olb
->t
)
1468 else if (ola
->t
< olb
->t
)
1475 * Check whether the file corresponding to dp is an archive of the logfile
1476 * logfname, based on the timefnamefmt format string. Return true and fill out
1477 * tm if this is the case; otherwise return false.
1480 validate_old_timelog(int fd
, const struct dirent
*dp
, const char *logfname
,
1484 size_t logfname_len
;
1488 logfname_len
= strlen(logfname
);
1490 if (dp
->d_type
!= DT_REG
) {
1492 * Some filesystems (e.g. NFS) don't fill out the d_type field
1493 * and leave it set to DT_UNKNOWN; in this case we must obtain
1494 * the file type ourselves.
1496 if (dp
->d_type
!= DT_UNKNOWN
||
1497 fstatat(fd
, dp
->d_name
, &sb
, AT_SYMLINK_NOFOLLOW
) != 0 ||
1498 !S_ISREG(sb
.st_mode
))
1501 /* Ignore everything but files with our logfile prefix. */
1502 if (strncmp(dp
->d_name
, logfname
, logfname_len
) != 0)
1504 /* Ignore the actual non-rotated logfile. */
1505 if (dp
->d_namlen
== logfname_len
)
1509 * Make sure we created have found a logfile, so the
1510 * postfix is valid, IE format is: '.<time>(.[bgx]z)?'.
1512 if (dp
->d_name
[logfname_len
] != '.') {
1514 printf("Ignoring %s which has unexpected "
1515 "extension '%s'\n", dp
->d_name
,
1516 &dp
->d_name
[logfname_len
]);
1519 memset(tm
, 0, sizeof(*tm
));
1520 if ((s
= strptime(&dp
->d_name
[logfname_len
+ 1],
1521 timefnamefmt
, tm
)) == NULL
) {
1523 * We could special case "old" sequentially named logfiles here,
1524 * but we do not as that would require special handling to
1525 * decide which one was the oldest compared to "new" time based
1529 printf("Ignoring %s which does not "
1530 "match time format\n", dp
->d_name
);
1534 for (c
= 0; c
< COMPRESS_TYPES
; c
++)
1535 if (strcmp(s
, compress_type
[c
].suffix
) == 0)
1540 printf("Ignoring %s which has unexpected extension '%s'\n",
1547 * Delete the oldest logfiles, when using time based filenames.
1550 delete_oldest_timelog(const struct conf_entry
*ent
, const char *archive_dir
)
1552 char *basebuf
, *dirbuf
, errbuf
[80];
1553 const char *base
, *dir
;
1554 int dir_fd
, i
, logcnt
, max_logcnt
;
1555 struct oldlog_entry
*oldlogs
;
1560 oldlogs
= malloc(MAX_OLDLOGS
* sizeof(struct oldlog_entry
));
1561 max_logcnt
= MAX_OLDLOGS
;
1564 if (archive_dir
!= NULL
&& archive_dir
[0] != '\0') {
1568 if ((dirbuf
= strdup(ent
->log
)) == NULL
)
1570 dir
= dirname(dirbuf
);
1573 if ((basebuf
= strdup(ent
->log
)) == NULL
)
1575 base
= basename(basebuf
);
1576 if (strcmp(base
, "/") == 0)
1577 errx(1, "Invalid log filename - became '/'");
1580 printf("Searching for old logs in %s\n", dir
);
1582 /* First we create a 'list' of all archived logfiles */
1583 if ((dirp
= opendir(dir
)) == NULL
)
1584 err(1, "Cannot open log directory '%s'", dir
);
1585 dir_fd
= dirfd(dirp
);
1586 while ((dp
= readdir(dirp
)) != NULL
) {
1587 if (validate_old_timelog(dir_fd
, dp
, base
, &tm
) == 0)
1591 * We should now have old an old rotated logfile, so
1592 * add it to the 'list'.
1594 if ((oldlogs
[logcnt
].t
= timegm(&tm
)) == -1)
1595 err(1, "Could not convert time string to time value");
1596 if ((oldlogs
[logcnt
].fname
= strdup(dp
->d_name
)) == NULL
)
1601 * It is very unlikely we ever run out of space in the
1602 * logfile array from the default size, but lets
1603 * handle it anyway...
1605 if (logcnt
>= max_logcnt
) {
1607 /* Detect integer overflow */
1608 if (max_logcnt
< logcnt
)
1609 errx(1, "Too many old logfiles found");
1610 oldlogs
= realloc(oldlogs
,
1611 max_logcnt
* sizeof(struct oldlog_entry
));
1612 if (oldlogs
== NULL
)
1613 err(1, "realloc()");
1617 /* Second, if needed we delete oldest archived logfiles */
1618 if (logcnt
> 0 && logcnt
>= ent
->numlogs
&& ent
->numlogs
> 1) {
1619 oldlogs
= realloc(oldlogs
, logcnt
*
1620 sizeof(struct oldlog_entry
));
1621 if (oldlogs
== NULL
)
1622 err(1, "realloc()");
1625 * We now sort the logs in the order of newest to
1626 * oldest. That way we can simply skip over the
1627 * number of records we want to keep.
1629 qsort(oldlogs
, logcnt
, sizeof(struct oldlog_entry
),
1630 oldlog_entry_compare
);
1631 for (i
= ent
->numlogs
- 1; i
< logcnt
; i
++) {
1633 printf("\trm -f %s/%s\n", dir
,
1635 else if (unlinkat(dir_fd
, oldlogs
[i
].fname
, 0) != 0) {
1636 snprintf(errbuf
, sizeof(errbuf
),
1637 "Could not delete old logfile '%s'",
1642 } else if (verbose
> 1)
1643 printf("No old logs to delete for logfile %s\n", ent
->log
);
1645 /* Third, cleanup */
1647 for (i
= 0; i
< logcnt
; i
++) {
1648 assert(oldlogs
[i
].fname
!= NULL
);
1649 free(oldlogs
[i
].fname
);
1657 * Generate a log filename, when using classic filenames.
1660 gen_classiclog_fname(char *fname
, size_t fname_sz
, const char *archive_dir
,
1661 const char *namepart
, int numlogs_c
)
1664 if (archive_dir
[0] != '\0')
1665 (void) snprintf(fname
, fname_sz
, "%s/%s.%d", archive_dir
,
1666 namepart
, numlogs_c
);
1668 (void) snprintf(fname
, fname_sz
, "%s.%d", namepart
, numlogs_c
);
1672 * Delete a rotated logfile, when using classic filenames.
1675 delete_classiclog(const char *archive_dir
, const char *namepart
, int numlog_c
)
1677 char file1
[MAXPATHLEN
], zfile1
[MAXPATHLEN
];
1680 gen_classiclog_fname(file1
, sizeof(file1
), archive_dir
, namepart
,
1683 for (c
= 0; c
< COMPRESS_TYPES
; c
++) {
1684 (void) snprintf(zfile1
, sizeof(zfile1
), "%s%s", file1
,
1685 compress_type
[c
].suffix
);
1687 printf("\trm -f %s\n", zfile1
);
1689 (void) unlink(zfile1
);
1694 * Only add to the queue if the file hasn't already been added. This is
1695 * done to prevent circular include loops.
1698 add_to_queue(const char *fname
, struct ilist
*inclist
)
1700 struct include_entry
*inc
;
1702 STAILQ_FOREACH(inc
, inclist
, inc_nextp
) {
1703 if (strcmp(fname
, inc
->file
) == 0) {
1704 warnx("duplicate include detected: %s", fname
);
1709 inc
= malloc(sizeof(struct include_entry
));
1711 err(1, "malloc of inc");
1712 inc
->file
= strdup(fname
);
1715 printf("\t+ Adding %s to the processing queue.\n", fname
);
1717 STAILQ_INSERT_TAIL(inclist
, inc
, inc_nextp
);
1721 * Search for logfile and return its compression suffix (if supported)
1722 * The suffix detection is first-match in the order of compress_types
1724 * Note: if logfile without suffix exists (uncompressed, COMPRESS_NONE)
1725 * a zero-length string is returned
1728 get_logfile_suffix(const char *logfile
)
1731 char zfile
[MAXPATHLEN
];
1734 for (c
= 0; c
< COMPRESS_TYPES
; c
++) {
1735 strlcpy(zfile
, logfile
, MAXPATHLEN
);
1736 strlcat(zfile
, compress_type
[c
].suffix
, MAXPATHLEN
);
1737 if (lstat(zfile
, &st
) == 0)
1738 return (compress_type
[c
].suffix
);
1744 do_rotate(const struct conf_entry
*ent
)
1746 char dirpart
[MAXPATHLEN
], namepart
[MAXPATHLEN
];
1747 char file1
[MAXPATHLEN
], file2
[MAXPATHLEN
];
1748 char zfile1
[MAXPATHLEN
], zfile2
[MAXPATHLEN
];
1749 const char *logfile_suffix
;
1750 char datetimestr
[30];
1751 int flags
, numlogs_c
;
1752 fk_entry free_or_keep
;
1753 struct sigwork_entry
*swork
;
1759 free_or_keep
= FREE_ENT
;
1764 /* build complete name of archive directory into dirpart */
1765 if (*archdirname
== '/') { /* absolute */
1766 strlcpy(dirpart
, archdirname
, sizeof(dirpart
));
1767 } else { /* relative */
1768 /* get directory part of logfile */
1769 strlcpy(dirpart
, ent
->log
, sizeof(dirpart
));
1770 if ((p
= strrchr(dirpart
, '/')) == NULL
)
1774 strlcat(dirpart
, archdirname
, sizeof(dirpart
));
1777 /* check if archive directory exists, if not, create it */
1778 if (lstat(dirpart
, &st
))
1779 createdir(ent
, dirpart
);
1781 /* get filename part of logfile */
1782 if ((p
= strrchr(ent
->log
, '/')) == NULL
)
1783 strlcpy(namepart
, ent
->log
, sizeof(namepart
));
1785 strlcpy(namepart
, p
+ 1, sizeof(namepart
));
1788 * Tell utility functions we are not using an archive
1792 strlcpy(namepart
, ent
->log
, sizeof(namepart
));
1795 /* Delete old logs */
1796 if (timefnamefmt
!= NULL
)
1797 delete_oldest_timelog(ent
, dirpart
);
1800 * Handle cleaning up after legacy newsyslog where we
1801 * kept ent->numlogs + 1 files. This code can go away
1802 * at some point in the future.
1804 delete_classiclog(dirpart
, namepart
, ent
->numlogs
);
1806 if (ent
->numlogs
> 0)
1807 delete_classiclog(dirpart
, namepart
, ent
->numlogs
- 1);
1810 if (timefnamefmt
!= NULL
) {
1811 /* If time functions fails we can't really do any sensible */
1812 if (time(&now
) == (time_t)-1 ||
1813 localtime_r(&now
, &tm
) == NULL
)
1814 bzero(&tm
, sizeof(tm
));
1816 strftime(datetimestr
, sizeof(datetimestr
), timefnamefmt
, &tm
);
1818 snprintf(file1
, sizeof(file1
), "%s/%s.%s",
1819 dirpart
, namepart
, datetimestr
);
1821 snprintf(file1
, sizeof(file1
), "%s.%s",
1822 ent
->log
, datetimestr
);
1825 /* Don't run the code to move down logs */
1828 gen_classiclog_fname(file1
, sizeof(file1
), dirpart
, namepart
,
1830 numlogs_c
= ent
->numlogs
- 2; /* copy for countdown */
1833 /* Move down log files */
1834 for (; numlogs_c
>= 0; numlogs_c
--) {
1835 strlcpy(file2
, file1
, sizeof(file2
));
1837 gen_classiclog_fname(file1
, sizeof(file1
), dirpart
, namepart
,
1840 logfile_suffix
= get_logfile_suffix(file1
);
1841 if (logfile_suffix
== NULL
)
1843 strlcpy(zfile1
, file1
, MAXPATHLEN
);
1844 strlcpy(zfile2
, file2
, MAXPATHLEN
);
1845 strlcat(zfile1
, logfile_suffix
, MAXPATHLEN
);
1846 strlcat(zfile2
, logfile_suffix
, MAXPATHLEN
);
1849 printf("\tmv %s %s\n", zfile1
, zfile2
);
1851 /* XXX - Ought to be checking for failure! */
1852 rename(zfile1
, zfile2
);
1853 change_attrs(zfile2
, ent
);
1854 if (ent
->compress
&& !strlen(logfile_suffix
)) {
1855 /* compress old rotation */
1856 struct zipwork_entry zwork
;
1858 memset(&zwork
, 0, sizeof(zwork
));
1859 zwork
.zw_conf
= ent
;
1860 zwork
.zw_fsize
= sizefile(zfile2
);
1861 strcpy(zwork
.zw_fname
, zfile2
);
1867 if (ent
->numlogs
> 0) {
1870 * Note that savelog() may succeed with using link()
1871 * for the archtodir case, but there is no good way
1872 * of knowing if it will when doing "noaction", so
1873 * here we claim that it will have to do a copy...
1876 printf("\tcp %s %s\n", ent
->log
, file1
);
1878 printf("\tln %s %s\n", ent
->log
, file1
);
1879 printf("\ttouch %s\t\t"
1880 "# Update mtime for 'when'-interval processing\n",
1883 if (!(flags
& CE_BINARY
)) {
1884 /* Report the trimming to the old log */
1885 log_trim(ent
->log
, ent
);
1887 savelog(ent
->log
, file1
);
1889 * Interval-based rotations are done using the mtime of
1890 * the most recently archived log, so make sure it gets
1891 * updated during a rotation.
1893 utimes(file1
, NULL
);
1895 change_attrs(file1
, ent
);
1898 /* Create the new log file and move it into place */
1900 printf("Start new log...\n");
1904 * Save all signalling and file-compression to be done after log
1905 * files from all entries have been rotated. This way any one
1906 * process will not be sent the same signal multiple times when
1907 * multiple log files had to be rotated.
1910 if (ent
->pid_cmd_file
!= NULL
)
1911 swork
= save_sigwork(ent
);
1912 if (ent
->numlogs
> 0 && ent
->compress
> COMPRESS_NONE
) {
1913 if (!(ent
->flags
& CE_PLAIN0
) ||
1914 strcmp(&file1
[strlen(file1
) - 2], ".0") != 0) {
1916 * The zipwork_entry will include a pointer to this
1917 * conf_entry, so the conf_entry should not be freed.
1919 free_or_keep
= KEEP_ENT
;
1920 save_zipwork(ent
, swork
, ent
->fsize
, file1
);
1924 return (free_or_keep
);
1928 do_sigwork(struct sigwork_entry
*swork
)
1930 struct sigwork_entry
*nextsig
;
1934 if (swork
->sw_runcmd
== 0 && (!(swork
->sw_pidok
) || swork
->sw_pid
== 0))
1935 return; /* no work to do... */
1938 * If nosignal (-s) was specified, then do not signal any process.
1939 * Note that a nosignal request triggers a warning message if the
1940 * rotated logfile needs to be compressed, *unless* -R was also
1941 * specified. We assume that an `-sR' request came from a process
1942 * which writes to the logfile, and as such, we assume that process
1943 * has already made sure the logfile is not presently in use. This
1944 * just sets swork->sw_pidok to a special value, and do_zipwork
1945 * will print any necessary warning(s).
1949 swork
->sw_pidok
= -1;
1954 * Compute the pause between consecutive signals. Use a longer
1955 * sleep time if we will be sending two signals to the same
1956 * daemon or process-group.
1959 nextsig
= SLIST_NEXT(swork
, sw_nextp
);
1960 if (nextsig
!= NULL
) {
1961 if (swork
->sw_pid
== nextsig
->sw_pid
)
1968 if (swork
->sw_runcmd
)
1969 printf("\tsh -c '%s %d'\n", swork
->sw_fname
,
1972 printf("\tkill -%d %d \t\t# %s\n", swork
->sw_signum
,
1973 (int)swork
->sw_pid
, swork
->sw_fname
);
1975 printf("\tsleep %d\n", secs
);
1980 if (swork
->sw_runcmd
) {
1981 asprintf(&tmp
, "%s %d", swork
->sw_fname
, swork
->sw_signum
);
1983 warn("can't allocate memory to run %s",
1988 printf("Run command: %s\n", tmp
);
1991 warnx("%s: returned non-zero exit code: %d",
1998 kres
= kill(swork
->sw_pid
, swork
->sw_signum
);
2001 * Assume that "no such process" (ESRCH) is something
2002 * to warn about, but is not an error. Presumably the
2003 * process which writes to the rotated log file(s) is
2004 * gone, in which case we should have no problem with
2005 * compressing the rotated log file(s).
2008 swork
->sw_pidok
= 0;
2009 warn("can't notify %s, pid %d = %s", swork
->sw_pidtype
,
2010 (int)swork
->sw_pid
, swork
->sw_fname
);
2013 printf("Notified %s pid %d = %s\n", swork
->sw_pidtype
,
2014 (int)swork
->sw_pid
, swork
->sw_fname
);
2017 printf("Pause %d second(s) between signals\n",
2025 do_zipwork(struct zipwork_entry
*zwork
)
2027 const char *pgm_name
, *pgm_path
;
2028 int errsav
, fcount
, zstatus
;
2030 char zresult
[MAXPATHLEN
];
2031 char command
[BUFSIZ
];
2035 assert(zwork
!= NULL
);
2037 strlcpy(zresult
, zwork
->zw_fname
, sizeof(zresult
));
2038 args
= calloc(ARGS_NUM
, sizeof(*args
));
2041 if (zwork
->zw_conf
!= NULL
&&
2042 zwork
->zw_conf
->compress
> COMPRESS_NONE
)
2043 for (c
= 1; c
< COMPRESS_TYPES
; c
++) {
2044 if (zwork
->zw_conf
->compress
== c
) {
2045 pgm_path
= compress_type
[c
].path
;
2047 compress_type
[c
].suffix
, sizeof(zresult
));
2048 /* the first argument is always NULL, skip it */
2049 for (c
= 1; c
< ARGS_NUM
; c
++) {
2050 if (compress_type
[c
].args
[c
] == NULL
)
2052 args
[c
] = compress_type
[c
].args
[c
];
2057 if (pgm_path
== NULL
) {
2058 warnx("invalid entry for %s in do_zipwork", zwork
->zw_fname
);
2061 pgm_name
= strrchr(pgm_path
, '/');
2062 if (pgm_name
== NULL
)
2063 pgm_name
= pgm_path
;
2067 args
[0] = strdup(pgm_name
);
2068 if (args
[0] == NULL
)
2070 for (c
= 0; args
[c
] != NULL
; c
++)
2072 args
[c
] = zwork
->zw_fname
;
2074 if (zwork
->zw_swork
!= NULL
&& zwork
->zw_swork
->sw_runcmd
== 0 &&
2075 zwork
->zw_swork
->sw_pidok
<= 0) {
2077 "log %s not compressed because daemon(s) not notified",
2079 change_attrs(zwork
->zw_fname
, zwork
->zw_conf
);
2083 strlcpy(command
, pgm_path
, sizeof(command
));
2084 for (c
= 1; args
[c
] != NULL
; c
++) {
2085 strlcat(command
, " ", sizeof(command
));
2086 strlcat(command
, args
[c
], sizeof(command
));
2089 printf("\t%s %s\n", pgm_name
, zwork
->zw_fname
);
2090 change_attrs(zresult
, zwork
->zw_conf
);
2095 printf("Executing: %s\n", command
);
2099 while (pidzip
< 0) {
2101 * The fork failed. If the failure was due to a temporary
2102 * problem, then wait a short time and try it again.
2105 warn("fork() for `%s %s'", pgm_name
, zwork
->zw_fname
);
2106 if (errsav
!= EAGAIN
|| fcount
> 5)
2107 errx(1, "Exiting...");
2113 /* The child process executes the compression command */
2114 execv(pgm_path
, (char *const*) args
);
2115 err(1, "execv(`%s')", command
);
2118 wpid
= waitpid(pidzip
, &zstatus
, 0);
2120 /* XXX - should this be a fatal error? */
2121 warn("%s: waitpid(%d)", pgm_path
, pidzip
);
2124 if (!WIFEXITED(zstatus
)) {
2125 warnx("`%s' did not terminate normally", command
);
2130 if (WEXITSTATUS(zstatus
)) {
2131 warnx("`%s' terminated with a non-zero status (%d)", command
,
2132 WEXITSTATUS(zstatus
));
2140 /* Compression was successful, set file attributes on the result. */
2141 change_attrs(zresult
, zwork
->zw_conf
);
2145 * Save information on any process we need to signal. Any single
2146 * process may need to be sent different signal-values for different
2147 * log files, but usually a single signal-value will cause the process
2148 * to close and re-open all of its log files.
2150 static struct sigwork_entry
*
2151 save_sigwork(const struct conf_entry
*ent
)
2153 struct sigwork_entry
*sprev
, *stmp
;
2159 SLIST_FOREACH(stmp
, &swhead
, sw_nextp
) {
2160 ndiff
= strcmp(ent
->pid_cmd_file
, stmp
->sw_fname
);
2164 if (ent
->sig
== stmp
->sw_signum
)
2166 if (ent
->sig
> stmp
->sw_signum
) {
2173 if (stmp
!= NULL
&& ndiff
== 0)
2176 tmpsiz
= sizeof(struct sigwork_entry
) + strlen(ent
->pid_cmd_file
) + 1;
2177 stmp
= malloc(tmpsiz
);
2179 stmp
->sw_runcmd
= 0;
2180 /* If this is a command to run we just set the flag and run command */
2181 if (ent
->flags
& CE_PID2CMD
) {
2184 stmp
->sw_runcmd
= 1;
2186 set_swpid(stmp
, ent
);
2188 stmp
->sw_signum
= ent
->sig
;
2189 strcpy(stmp
->sw_fname
, ent
->pid_cmd_file
);
2191 SLIST_INSERT_HEAD(&swhead
, stmp
, sw_nextp
);
2193 SLIST_INSERT_AFTER(sprev
, stmp
, sw_nextp
);
2198 * Save information on any file we need to compress. We may see the same
2199 * file multiple times, so check the full list to avoid duplicates. The
2200 * list itself is sorted smallest-to-largest, because that's the order we
2201 * want to compress the files. If the partition is very low on disk space,
2202 * then the smallest files are the most likely to compress, and compressing
2203 * them first will free up more space for the larger files.
2205 static struct zipwork_entry
*
2206 save_zipwork(const struct conf_entry
*ent
, const struct sigwork_entry
*swork
,
2207 int zsize
, const char *zipfname
)
2209 struct zipwork_entry
*zprev
, *ztmp
;
2213 /* Compute the size if the caller did not know it. */
2215 zsize
= sizefile(zipfname
);
2219 SLIST_FOREACH(ztmp
, &zwhead
, zw_nextp
) {
2220 ndiff
= strcmp(zipfname
, ztmp
->zw_fname
);
2223 if (zsize
> ztmp
->zw_fsize
)
2226 if (ztmp
!= NULL
&& ndiff
== 0)
2229 tmpsiz
= sizeof(struct zipwork_entry
) + strlen(zipfname
) + 1;
2230 ztmp
= malloc(tmpsiz
);
2231 ztmp
->zw_conf
= ent
;
2232 ztmp
->zw_swork
= swork
;
2233 ztmp
->zw_fsize
= zsize
;
2234 strcpy(ztmp
->zw_fname
, zipfname
);
2236 SLIST_INSERT_HEAD(&zwhead
, ztmp
, zw_nextp
);
2238 SLIST_INSERT_AFTER(zprev
, ztmp
, zw_nextp
);
2242 /* Send a signal to the pid specified by pidfile */
2244 set_swpid(struct sigwork_entry
*swork
, const struct conf_entry
*ent
)
2247 long minok
, maxok
, rval
;
2248 char *endp
, *linep
, line
[BUFSIZ
];
2252 swork
->sw_pidok
= 0;
2254 swork
->sw_pidtype
= "daemon";
2255 if (ent
->flags
& CE_SIGNALGROUP
) {
2257 * If we are expected to signal a process-group when
2258 * rotating this logfile, then the value read in should
2259 * be the negative of a valid process ID.
2263 swork
->sw_pidtype
= "process-group";
2266 f
= fopen(ent
->pid_cmd_file
, "r");
2268 if (errno
== ENOENT
&& enforcepid
== 0) {
2270 * Warn if the PID file doesn't exist, but do
2271 * not consider it an error. Most likely it
2272 * means the process has been terminated,
2273 * so it should be safe to rotate any log
2274 * files that the process would have been using.
2276 swork
->sw_pidok
= 1;
2277 warnx("pid file doesn't exist: %s", ent
->pid_cmd_file
);
2279 warn("can't open pid file: %s", ent
->pid_cmd_file
);
2283 if (fgets(line
, BUFSIZ
, f
) == NULL
) {
2285 * Warn if the PID file is empty, but do not consider
2286 * it an error. Most likely it means the process has
2287 * has terminated, so it should be safe to rotate any
2288 * log files that the process would have been using.
2290 if (feof(f
) && enforcepid
== 0) {
2291 swork
->sw_pidok
= 1;
2292 warnx("pid/cmd file is empty: %s", ent
->pid_cmd_file
);
2294 warn("can't read from pid file: %s", ent
->pid_cmd_file
);
2302 while (*linep
== ' ')
2304 rval
= strtol(linep
, &endp
, 10);
2305 if (*endp
!= '\0' && !isspacech(*endp
)) {
2306 warnx("pid file does not start with a valid number: %s",
2308 } else if (rval
< minok
|| rval
> maxok
) {
2309 warnx("bad value '%ld' for process number in %s",
2310 rval
, ent
->pid_cmd_file
);
2312 warnx("\t(expecting value between %ld and %ld)",
2315 swork
->sw_pidok
= 1;
2316 swork
->sw_pid
= rval
;
2322 /* Log the fact that the logs were turned over */
2324 log_trim(const char *logname
, const struct conf_entry
*log_ent
)
2329 if ((f
= fopen(logname
, "a")) == NULL
)
2332 if (log_ent
->def_cfg
)
2333 xtra
= " using <default> rule";
2334 if (log_ent
->flags
& CE_RFC5424
) {
2335 if (log_ent
->firstcreate
) {
2336 fprintf(f
, "<%d>1 %s %s newsyslog %d - - %s%s\n",
2337 LOG_MAKEPRI(LOG_USER
, LOG_INFO
),
2338 daytime_rfc5424
, hostname
, getpid(),
2339 "logfile first created", xtra
);
2340 } else if (log_ent
->r_reason
!= NULL
) {
2341 fprintf(f
, "<%d>1 %s %s newsyslog %d - - %s%s%s\n",
2342 LOG_MAKEPRI(LOG_USER
, LOG_INFO
),
2343 daytime_rfc5424
, hostname
, getpid(),
2344 "logfile turned over", log_ent
->r_reason
, xtra
);
2346 fprintf(f
, "<%d>1 %s %s newsyslog %d - - %s%s\n",
2347 LOG_MAKEPRI(LOG_USER
, LOG_INFO
),
2348 daytime_rfc5424
, hostname
, getpid(),
2349 "logfile turned over", xtra
);
2352 if (log_ent
->firstcreate
)
2353 fprintf(f
, "%s %s newsyslog[%d]: logfile first created%s\n",
2354 daytime
, hostname
, getpid(), xtra
);
2355 else if (log_ent
->r_reason
!= NULL
)
2356 fprintf(f
, "%s %s newsyslog[%d]: logfile turned over%s%s\n",
2357 daytime
, hostname
, getpid(), log_ent
->r_reason
, xtra
);
2359 fprintf(f
, "%s %s newsyslog[%d]: logfile turned over%s\n",
2360 daytime
, hostname
, getpid(), xtra
);
2362 if (fclose(f
) == EOF
)
2363 err(1, "log_trim: fclose");
2367 /* Return size in kilobytes of a file */
2369 sizefile(const char *file
)
2373 if (stat(file
, &sb
) < 0)
2375 return (kbytes(sb
.st_size
));
2379 * Return the mtime of the most recent archive of the logfile, using timestamp
2383 mtime_old_timelog(const char *file
)
2391 char *logfname
, *logfnamebuf
, *dir
, *dirbuf
;
2395 if ((dirbuf
= strdup(file
)) == NULL
) {
2396 warn("strdup() of '%s'", file
);
2399 dir
= dirname(dirbuf
);
2400 if ((logfnamebuf
= strdup(file
)) == NULL
) {
2401 warn("strdup() of '%s'", file
);
2405 logfname
= basename(logfnamebuf
);
2406 if (logfname
[0] == '/') {
2407 warnx("Invalid log filename '%s'", logfname
);
2411 if ((dirp
= opendir(dir
)) == NULL
) {
2412 warn("Cannot open log directory '%s'", dir
);
2415 dir_fd
= dirfd(dirp
);
2416 /* Open the archive dir and find the most recent archive of logfname. */
2417 while ((dp
= readdir(dirp
)) != NULL
) {
2418 if (validate_old_timelog(dir_fd
, dp
, logfname
, &tm
) == 0)
2421 if (fstatat(dir_fd
, dp
->d_name
, &sb
, AT_SYMLINK_NOFOLLOW
) == -1) {
2422 warn("Cannot stat '%s'", file
);
2425 if (t
< sb
.st_mtime
)
2436 /* Return the age in hours of the most recent archive of the logfile. */
2438 age_old_log(const char *file
)
2441 const char *logfile_suffix
;
2442 char tmp
[MAXPATHLEN
+ sizeof(".0") + COMPRESS_SUFFIX_MAXLEN
+ 1];
2448 /* build name of archive directory into tmp */
2449 if (*archdirname
== '/') { /* absolute */
2450 strlcpy(tmp
, archdirname
, sizeof(tmp
));
2451 } else { /* relative */
2452 /* get directory part of logfile */
2453 strlcpy(tmp
, file
, sizeof(tmp
));
2454 if ((p
= strrchr(tmp
, '/')) == NULL
)
2458 strlcat(tmp
, archdirname
, sizeof(tmp
));
2461 strlcat(tmp
, "/", sizeof(tmp
));
2463 /* get filename part of logfile */
2464 if ((p
= strrchr(file
, '/')) == NULL
)
2465 strlcat(tmp
, file
, sizeof(tmp
));
2467 strlcat(tmp
, p
+ 1, sizeof(tmp
));
2469 strlcpy(tmp
, file
, sizeof(tmp
));
2472 if (timefnamefmt
!= NULL
) {
2473 mtime
= mtime_old_timelog(tmp
);
2477 strlcat(tmp
, ".0", sizeof(tmp
));
2478 logfile_suffix
= get_logfile_suffix(tmp
);
2479 if (logfile_suffix
== NULL
)
2481 (void) strlcat(tmp
, logfile_suffix
, sizeof(tmp
));
2482 if (stat(tmp
, &sb
) < 0)
2484 mtime
= sb
.st_mtime
;
2487 return ((int)(ptimeget_secs(timenow
) - mtime
+ 1800) / 3600);
2490 /* Skip Over Blanks */
2494 while (p
&& *p
&& isspace(*p
))
2499 /* Skip Over Non-Blanks */
2503 while (p
&& *p
&& !isspace(*p
))
2508 /* Check if string is actually a number */
2510 isnumberstr(const char *string
)
2513 if (!isdigitch(*string
++))
2519 /* Check if string contains a glob */
2521 isglobstr(const char *string
)
2525 while ((chr
= *string
++)) {
2526 if (chr
== '*' || chr
== '?' || chr
== '[')
2533 * Save the active log file under a new name. A link to the new name
2534 * is the quick-and-easy way to do this. If that fails (which it will
2535 * if the destination is on another partition), then make a copy of
2536 * the file to the new location.
2539 savelog(char *from
, char *to
)
2544 res
= link(from
, to
);
2548 if ((src
= fopen(from
, "r")) == NULL
)
2549 err(1, "can't fopen %s for reading", from
);
2550 if ((dst
= fopen(to
, "w")) == NULL
)
2551 err(1, "can't fopen %s for writing", to
);
2553 while ((c
= getc(src
)) != EOF
) {
2554 if ((putc(c
, dst
)) == EOF
)
2555 err(1, "error writing to %s", to
);
2559 err(1, "error reading from %s", from
);
2560 if ((fclose(src
)) != 0)
2561 err(1, "can't fclose %s", to
);
2562 if ((fclose(dst
)) != 0)
2563 err(1, "can't fclose %s", from
);
2566 /* create one or more directory components of a path */
2568 createdir(const struct conf_entry
*ent
, char *dirpart
)
2572 char mkdirpath
[MAXPATHLEN
];
2580 if (*s
!= '/' && *s
!= '\0')
2583 res
= lstat(mkdirpath
, &st
);
2586 printf("\tmkdir %s\n", mkdirpath
);
2588 res
= mkdir(mkdirpath
, 0755);
2590 err(1, "Error on mkdir(\"%s\") for -a",
2598 if (ent
->firstcreate
)
2599 printf("Created directory '%s' for new %s\n",
2602 printf("Created directory '%s' for -a\n", dirpart
);
2607 * Create a new log file, destroying any currently-existing version
2608 * of the log file in the process. If the caller wants a backup copy
2609 * of the file to exist, they should call 'link(logfile,logbackup)'
2610 * before calling this routine.
2613 createlog(const struct conf_entry
*ent
)
2617 char *realfile
, *slash
, tempfile
[MAXPATHLEN
];
2620 realfile
= ent
->log
;
2623 * If this log file is being created for the first time (-C option),
2624 * then it may also be true that the parent directory does not exist
2625 * yet. Check, and create that directory if it is missing.
2627 if (ent
->firstcreate
) {
2628 strlcpy(tempfile
, realfile
, sizeof(tempfile
));
2629 slash
= strrchr(tempfile
, '/');
2630 if (slash
!= NULL
) {
2632 failed
= stat(tempfile
, &st
);
2633 if (failed
&& errno
!= ENOENT
)
2634 err(1, "Error on stat(%s)", tempfile
);
2636 createdir(ent
, tempfile
);
2637 else if (!S_ISDIR(st
.st_mode
))
2638 errx(1, "%s exists but is not a directory",
2644 * First create an unused filename, so it can be chown'ed and
2645 * chmod'ed before it is moved into the real location. mkstemp
2646 * will create the file mode=600 & owned by us. Note that all
2647 * temp files will have a suffix of '.z<something>'.
2649 strlcpy(tempfile
, realfile
, sizeof(tempfile
));
2650 strlcat(tempfile
, ".zXXXXXX", sizeof(tempfile
));
2652 printf("\tmktemp %s\n", tempfile
);
2654 fd
= mkstemp(tempfile
);
2656 err(1, "can't mkstemp logfile %s", tempfile
);
2659 * Add status message to what will become the new log file.
2661 if (!(ent
->flags
& CE_BINARY
)) {
2662 if (log_trim(tempfile
, ent
))
2663 err(1, "can't add status message to log");
2667 /* Change the owner/group, if we are supposed to */
2668 if (ent
->uid
!= (uid_t
)-1 || ent
->gid
!= (gid_t
)-1) {
2670 printf("\tchown %u:%u %s\n", ent
->uid
, ent
->gid
,
2673 failed
= fchown(fd
, ent
->uid
, ent
->gid
);
2675 err(1, "can't fchown temp file %s", tempfile
);
2679 /* Turn on NODUMP if it was requested in the config-file. */
2680 if (ent
->flags
& CE_NODUMP
) {
2682 printf("\tchflags nodump %s\n", tempfile
);
2684 failed
= fchflags(fd
, UF_NODUMP
);
2686 warn("log_trim: fchflags(NODUMP)");
2692 * Note that if the real logfile still exists, and if the call
2693 * to rename() fails, then "neither the old file nor the new
2694 * file shall be changed or created" (to quote the standard).
2695 * If the call succeeds, then the file will be replaced without
2696 * any window where some other process might find that the file
2698 * XXX - ? It may be that for some error conditions, we could
2699 * retry by first removing the realfile and then renaming.
2702 printf("\tchmod %o %s\n", ent
->permissions
, tempfile
);
2703 printf("\tmv %s %s\n", tempfile
, realfile
);
2705 failed
= fchmod(fd
, ent
->permissions
);
2707 err(1, "can't fchmod temp file '%s'", tempfile
);
2708 failed
= rename(tempfile
, realfile
);
2710 err(1, "can't mv %s to %s", tempfile
, realfile
);
2718 * Change the attributes of a given filename to what was specified in
2719 * the newsyslog.conf entry. This routine is only called for files
2720 * that newsyslog expects that it has created, and thus it is a fatal
2721 * error if this routine finds that the file does not exist.
2724 change_attrs(const char *fname
, const struct conf_entry
*ent
)
2729 printf("\tchmod %o %s\n", ent
->permissions
, fname
);
2731 if (ent
->uid
!= (uid_t
)-1 || ent
->gid
!= (gid_t
)-1)
2732 printf("\tchown %u:%u %s\n",
2733 ent
->uid
, ent
->gid
, fname
);
2735 if (ent
->flags
& CE_NODUMP
)
2736 printf("\tchflags nodump %s\n", fname
);
2740 failed
= chmod(fname
, ent
->permissions
);
2743 err(1, "chmod(%s) in change_attrs", fname
);
2744 warn("change_attrs couldn't chmod(%s)", fname
);
2747 if (ent
->uid
!= (uid_t
)-1 || ent
->gid
!= (gid_t
)-1) {
2748 failed
= chown(fname
, ent
->uid
, ent
->gid
);
2750 warn("can't chown %s", fname
);
2753 if (ent
->flags
& CE_NODUMP
) {
2754 failed
= chflags(fname
, UF_NODUMP
);
2756 warn("can't chflags %s NODUMP", fname
);
2761 * Parse a signal number or signal name. Returns the signal number parsed or -1
2765 parse_signal(const char *str
)
2770 sig
= strtonum(str
, 1, sys_nsig
- 1, &errstr
);
2774 if (strncasecmp(str
, "SIG", 3) == 0)
2777 for (i
= 1; i
< sys_nsig
; i
++) {
2778 if (strcasecmp(str
, sys_signame
[i
]) == 0)