2 * This file contains changes from the Open Software Foundation.
6 * Copyright 1988, 1989 by the Massachusetts Institute of Technology
8 * Permission to use, copy, modify, and distribute this software and its
9 * documentation for any purpose and without fee is hereby granted, provided
10 * that the above copyright notice appear in all copies and that both that
11 * copyright notice and this permission notice appear in supporting
12 * documentation, and that the names of M.I.T. and the M.I.T. S.I.P.B. not be
13 * used in advertising or publicity pertaining to distribution of the
14 * software without specific, written prior permission. M.I.T. and the M.I.T.
15 * S.I.P.B. make no representations about the suitability of this software
16 * for any purpose. It is provided "as is" without express or implied
19 * $FreeBSD: src/usr.sbin/newsyslog/newsyslog.c,v 1.25.2.21 2003/05/12 23:41:29 gad Exp $
20 * $DragonFly: src/usr.sbin/newsyslog/newsyslog.c,v 1.5 2005/03/02 06:08:29 joerg Exp $
24 * newsyslog - roll over selected logs at the appropriate time, keeping the a
25 * specified number of backup files around.
29 #ifndef COMPRESS_POSTFIX
30 #define COMPRESS_POSTFIX ".gz"
32 #ifndef BZCOMPRESS_POSTFIX
33 #define BZCOMPRESS_POSTFIX ".bz2"
36 #include <sys/param.h>
56 #include "pathnames.h"
59 * Bit-values for the 'flags' parsed from a config-file entry.
61 #define CE_COMPACT 0x0001 /* Compact the achived log files with gzip. */
62 #define CE_BZCOMPACT 0x0002 /* Compact the achived log files with bzip2. */
63 #define CE_COMPACTWAIT 0x0004 /* wait until compressing one file finishes */
64 /* before starting the next step. */
65 #define CE_BINARY 0x0008 /* Logfile is in binary, do not add status */
66 /* messages to logfile(s) when rotating. */
67 #define CE_NOSIGNAL 0x0010 /* There is no process to signal when */
68 /* trimming this file. */
69 #define CE_TRIMAT 0x0020 /* trim file at a specific time. */
70 #define CE_GLOB 0x0040 /* name of the log is file name pattern. */
71 #define CE_SIGNALGROUP 0x0080 /* Signal a process-group instead of a single */
72 /* process when trimming this file. */
73 #define CE_CREATE 0x0100 /* Create the log file if it does not exist. */
75 #define MIN_PID 5 /* Don't touch pids lower than this */
76 #define MAX_PID 99999 /* was lower, see /usr/include/sys/proc.h */
78 #define kbytes(size) (((size) + 1023) >> 10)
81 char *log
; /* Name of the log */
82 char *pid_file
; /* PID file */
83 char *r_reason
; /* The reason this file is being rotated */
84 int firstcreate
; /* Creating log for the first time (-C). */
85 int rotate
; /* Non-zero if this file should be rotated */
86 uid_t uid
; /* Owner of log */
87 gid_t gid
; /* Group of log */
88 int numlogs
; /* Number of logs to keep */
89 int size
; /* Size cutoff to trigger trimming the log */
90 int hours
; /* Hours between log trimming */
91 time_t trim_at
; /* Specific time to do trimming */
92 int permissions
; /* File permissions on the log */
93 int flags
; /* CE_COMPACT, CE_BZCOMPACT, CE_BINARY */
94 int sig
; /* Signal to send */
95 int def_cfg
; /* Using the <default> rule for this file */
96 struct conf_entry
*next
;/* Linked list pointer */
99 #define DEFAULT_MARKER "<default>"
101 int archtodir
= 0; /* Archive old logfiles to other directory */
102 int createlogs
; /* Create (non-GLOB) logfiles which do not */
103 /* already exist. 1=='for entries with */
104 /* C flag', 2=='for all entries'. */
105 int verbose
= 0; /* Print out what's going on */
106 int needroot
= 1; /* Root privs are necessary */
107 int noaction
= 0; /* Don't do anything, just show it */
108 int nosignal
; /* Do not send any signals */
109 int force
= 0; /* Force the trim no matter what */
110 int rotatereq
= 0; /* -R = Always rotate the file(s) as given */
111 /* on the command (this also requires */
112 /* that a list of files *are* given on */
113 /* the run command). */
114 char *requestor
; /* The name given on a -R request */
115 char *archdirname
; /* Directory path to old logfiles archive */
116 const char *conf
; /* Configuration file to use */
119 char hostname
[MAXHOSTNAMELEN
]; /* hostname */
120 char daytime
[16]; /* timenow in human readable form */
122 static struct conf_entry
*get_worklist(char **files
);
123 static void parse_file(FILE *cf
, const char *cfname
, struct conf_entry
**work_p
,
124 struct conf_entry
**glob_p
, struct conf_entry
**defconf_p
);
125 static char *sob(char *p
);
126 static char *son(char *p
);
127 static char *missing_field(char *p
, char *errline
);
128 static void do_entry(struct conf_entry
* ent
);
129 static void expand_globs(struct conf_entry
**work_p
,
130 struct conf_entry
**glob_p
);
131 static void free_clist(struct conf_entry
**firstent
);
132 static void free_entry(struct conf_entry
*ent
);
133 static struct conf_entry
*init_entry(const char *fname
,
134 struct conf_entry
*src_entry
);
135 static void parse_args(int argc
, char **argv
);
136 static void usage(void);
137 static void dotrim(const struct conf_entry
*ent
, char *log
,
138 int numdays
, int flags
);
139 static int log_trim(const char *log
, const struct conf_entry
*log_ent
);
140 static void compress_log(char *log
, int dowait
);
141 static void bzcompress_log(char *log
, int dowait
);
142 static int sizefile(char *file
);
143 static int age_old_log(char *file
);
144 static int send_signal(const struct conf_entry
*ent
);
145 static time_t parse8601(char *s
, char *errline
);
146 static void movefile(char *from
, char *to
, int perm
, uid_t owner_uid
,
148 static void createdir(const struct conf_entry
*ent
, char *dirpart
);
149 static void createlog(const struct conf_entry
*ent
);
150 static time_t parseDWM(char *s
, char *errline
);
153 * All the following are defined to work on an 'int', in the
154 * range 0 to 255, plus EOF. Define wrappers which can take
155 * values of type 'char', either signed or unsigned.
157 #define isdigitch(Anychar) isdigit(((int) Anychar) & 255)
158 #define isprintch(Anychar) isprint(((int) Anychar) & 255)
159 #define isspacech(Anychar) isspace(((int) Anychar) & 255)
160 #define tolowerch(Anychar) tolower(((int) Anychar) & 255)
163 main(int argc
, char **argv
)
165 struct conf_entry
*p
, *q
;
167 parse_args(argc
, argv
);
171 if (needroot
&& getuid() && geteuid())
172 errx(1, "must have root privs");
173 p
= q
= get_worklist(argv
);
181 while (wait(NULL
) > 0 || errno
== EINTR
)
186 static struct conf_entry
*
187 init_entry(const char *fname
, struct conf_entry
*src_entry
)
189 struct conf_entry
*tempwork
;
192 printf("\t--> [creating entry for %s]\n", fname
);
194 tempwork
= malloc(sizeof(struct conf_entry
));
195 if (tempwork
== NULL
)
196 err(1, "malloc of conf_entry for %s", fname
);
198 tempwork
->log
= strdup(fname
);
199 if (tempwork
->log
== NULL
)
200 err(1, "strdup for %s", fname
);
202 if (src_entry
!= NULL
) {
203 tempwork
->pid_file
= NULL
;
204 if (src_entry
->pid_file
)
205 tempwork
->pid_file
= strdup(src_entry
->pid_file
);
206 tempwork
->r_reason
= NULL
;
207 tempwork
->firstcreate
= 0;
208 tempwork
->rotate
= 0;
209 tempwork
->uid
= src_entry
->uid
;
210 tempwork
->gid
= src_entry
->gid
;
211 tempwork
->numlogs
= src_entry
->numlogs
;
212 tempwork
->size
= src_entry
->size
;
213 tempwork
->hours
= src_entry
->hours
;
214 tempwork
->trim_at
= src_entry
->trim_at
;
215 tempwork
->permissions
= src_entry
->permissions
;
216 tempwork
->flags
= src_entry
->flags
;
217 tempwork
->sig
= src_entry
->sig
;
218 tempwork
->def_cfg
= src_entry
->def_cfg
;
220 /* Initialize as a "do-nothing" entry */
221 tempwork
->pid_file
= NULL
;
222 tempwork
->r_reason
= NULL
;
223 tempwork
->firstcreate
= 0;
224 tempwork
->rotate
= 0;
225 tempwork
->uid
= (uid_t
)-1;
226 tempwork
->gid
= (gid_t
)-1;
227 tempwork
->numlogs
= 1;
229 tempwork
->hours
= -1;
230 tempwork
->trim_at
= (time_t)0;
231 tempwork
->permissions
= 0;
233 tempwork
->sig
= SIGHUP
;
234 tempwork
->def_cfg
= 0;
236 tempwork
->next
= NULL
;
242 free_entry(struct conf_entry
*ent
)
248 if (ent
->log
!= NULL
) {
250 printf("\t--> [freeing entry for %s]\n", ent
->log
);
255 if (ent
->pid_file
!= NULL
) {
257 ent
->pid_file
= NULL
;
260 if (ent
->r_reason
!= NULL
) {
262 ent
->r_reason
= NULL
;
269 free_clist(struct conf_entry
**firstent
)
271 struct conf_entry
*ent
, *nextent
;
273 if (firstent
== NULL
)
274 return; /* There is nothing to do. */
287 do_entry(struct conf_entry
* ent
)
289 #define REASON_MAX 80
291 char temp_reason
[REASON_MAX
];
294 if (ent
->flags
& CE_COMPACT
)
295 printf("%s <%dZ>: ", ent
->log
, ent
->numlogs
);
296 else if (ent
->flags
& CE_BZCOMPACT
)
297 printf("%s <%dJ>: ", ent
->log
, ent
->numlogs
);
299 printf("%s <%d>: ", ent
->log
, ent
->numlogs
);
301 size
= sizefile(ent
->log
);
302 modtime
= age_old_log(ent
->log
);
304 ent
->firstcreate
= 0;
307 * If either the C flag or the -C option was specified,
308 * and if we won't be creating the file, then have the
309 * verbose message include a hint as to why the file
310 * will not be created.
312 temp_reason
[0] = '\0';
314 ent
->firstcreate
= 1;
315 else if ((ent
->flags
& CE_CREATE
) && createlogs
)
316 ent
->firstcreate
= 1;
317 else if (ent
->flags
& CE_CREATE
)
318 strncpy(temp_reason
, " (no -C option)", REASON_MAX
);
320 strncpy(temp_reason
, " (no C flag)", REASON_MAX
);
322 if (ent
->firstcreate
) {
324 printf("does not exist -> will create.\n");
326 } else if (verbose
) {
327 printf("does not exist, skipped%s.\n", temp_reason
);
330 if (ent
->flags
& CE_TRIMAT
&& !force
&& !rotatereq
) {
331 if (timenow
< ent
->trim_at
332 || difftime(timenow
, ent
->trim_at
) >= 60 * 60) {
334 printf("--> will trim at %s",
335 ctime(&ent
->trim_at
));
337 } else if (verbose
&& ent
->hours
<= 0) {
338 printf("--> time is up\n");
341 if (verbose
&& (ent
->size
> 0))
342 printf("size (Kb): %d [%d] ", size
, ent
->size
);
343 if (verbose
&& (ent
->hours
> 0))
344 printf(" age (hr): %d [%d] ", modtime
, ent
->hours
);
347 * Figure out if this logfile needs to be rotated.
349 temp_reason
[0] = '\0';
352 snprintf(temp_reason
, REASON_MAX
, " due to -R from %s",
356 snprintf(temp_reason
, REASON_MAX
, " due to -F request");
357 } else if ((ent
->size
> 0) && (size
>= ent
->size
)) {
359 snprintf(temp_reason
, REASON_MAX
, " due to size>%dK",
361 } else if (ent
->hours
<= 0 && (ent
->flags
& CE_TRIMAT
)) {
363 } else if ((ent
->hours
> 0) && ((modtime
>= ent
->hours
) ||
369 * If the file needs to be rotated, then rotate it.
372 if (temp_reason
[0] != '\0')
373 ent
->r_reason
= strdup(temp_reason
);
375 printf("--> trimming log....\n");
376 if (noaction
&& !verbose
) {
377 if (ent
->flags
& CE_COMPACT
)
378 printf("%s <%dZ>: trimming\n",
379 ent
->log
, ent
->numlogs
);
380 else if (ent
->flags
& CE_BZCOMPACT
)
381 printf("%s <%dJ>: trimming\n",
382 ent
->log
, ent
->numlogs
);
384 printf("%s <%d>: trimming\n",
385 ent
->log
, ent
->numlogs
);
387 dotrim(ent
, ent
->log
, ent
->numlogs
, ent
->flags
);
390 printf("--> skipping\n");
396 /* Send a signal to the pid specified by pidfile */
398 send_signal(const struct conf_entry
*ent
)
403 long minok
, maxok
, rval
;
404 const char *target_name
;
405 char *endp
, *linep
, line
[BUFSIZ
];
408 f
= fopen(ent
->pid_file
, "r");
410 warn("can't open pid file: %s", ent
->pid_file
);
415 if (fgets(line
, BUFSIZ
, f
) == NULL
) {
417 * XXX - If the pid file is empty, is that really a
418 * problem? Wouldn't that mean that the process
419 * has shut down? In that case there would be no
420 * problem with compressing the rotated log file.
423 warnx("pid file is empty: %s", ent
->pid_file
);
425 warn("can't read from pid file: %s", ent
->pid_file
);
432 target_name
= "daemon";
435 if (ent
->flags
& CE_SIGNALGROUP
) {
437 * If we are expected to signal a process-group when
438 * rotating this logfile, then the value read in should
439 * be the negative of a valid process ID.
441 target_name
= "process-group";
448 while (*linep
== ' ')
450 rval
= strtol(linep
, &endp
, 10);
451 if (*endp
!= '\0' && !isspacech(*endp
)) {
452 warnx("pid file does not start with a valid number: %s",
455 } else if (rval
< minok
|| rval
> maxok
) {
456 warnx("bad value '%ld' for process number in %s",
457 rval
, ent
->pid_file
);
459 warnx("\t(expecting value between %ld and %ld)",
472 printf("\tkill -%d %d\n", ent
->sig
, (int) target_pid
);
473 } else if (kill(target_pid
, ent
->sig
)) {
475 * XXX - Iff the error was "no such process", should that
476 * really be an error for us? Perhaps the process
477 * is already gone, in which case there would be no
478 * problem with compressing the rotated log file.
480 warn("can't notify %s, pid %d", target_name
,
485 printf("%s pid %d notified\n", target_name
,
493 parse_args(int argc
, char **argv
)
498 timenow
= time(NULL
);
499 strncpy(daytime
, ctime(&timenow
) + 4, 15);
502 /* Let's get our hostname */
503 gethostname(hostname
, sizeof(hostname
));
505 /* Truncate domain */
506 if ((p
= strchr(hostname
, '.')) != NULL
)
509 /* Parse command line options. */
510 while ((ch
= getopt(argc
, argv
, "a:f:nrsvCFR:")) != -1)
514 archdirname
= optarg
;
532 /* Useful for things like rc.diskless... */
540 requestor
= strdup(optarg
);
542 case 'm': /* Used by OpenBSD for "monitor mode" */
549 if (optind
== argc
) {
550 warnx("At least one filename must be given when -R is specified.");
554 /* Make sure "requestor" value is safe for a syslog message. */
555 for (p
= requestor
; *p
!= '\0'; p
++) {
556 if (!isprintch(*p
) && (*p
!= '\t'))
567 "usage: newsyslog [-CFnrsv] [-a directory] [-f config-file]\n"
568 " [ [-R requestor] filename ... ]\n");
573 * Parse a configuration file and return a linked list of all the logs
574 * which should be processed.
576 static struct conf_entry
*
577 get_worklist(char **files
)
582 struct conf_entry
*defconf
, *dupent
, *ent
, *firstnew
;
583 struct conf_entry
*globlist
, *lastnew
, *worklist
;
586 defconf
= globlist
= worklist
= NULL
;
592 if (strcmp(fname
, "-") != 0)
593 f
= fopen(fname
, "r");
601 parse_file(f
, fname
, &worklist
, &globlist
, &defconf
);
605 * All config-file information has been read in and turned into
606 * a worklist and a globlist. If there were no specific files
607 * given on the run command, then the only thing left to do is to
608 * call a routine which finds all files matched by the globlist
609 * and adds them to the worklist. Then return the worklist.
611 if (*files
== NULL
) {
612 expand_globs(&worklist
, &globlist
);
613 free_clist(&globlist
);
621 * If newsyslog was given a specific list of files to process,
622 * it may be that some of those files were not listed in any
623 * config file. Those unlisted files should get the default
624 * rotation action. First, create the default-rotation action
625 * if none was found in a system config file.
627 if (defconf
== NULL
) {
628 defconf
= init_entry(DEFAULT_MARKER
, NULL
);
629 defconf
->numlogs
= 3;
631 defconf
->permissions
= S_IRUSR
|S_IWUSR
;
635 * If newsyslog was run with a list of specific filenames,
636 * then create a new worklist which has only those files in
637 * it, picking up the rotation-rules for those files from
638 * the original worklist.
640 * XXX - Note that this will copy multiple rules for a single
641 * logfile, if multiple entries are an exact match for
642 * that file. That matches the historic behavior, but do
643 * we want to continue to allow it? If so, it should
644 * probably be handled more intelligently.
646 firstnew
= lastnew
= NULL
;
647 for (given
= files
; *given
; ++given
) {
649 * First try to find exact-matches for this given file.
652 for (ent
= worklist
; ent
; ent
= ent
->next
) {
653 if (strcmp(ent
->log
, *given
) == 0) {
655 dupent
= init_entry(*given
, ent
);
659 lastnew
->next
= dupent
;
665 printf("\t+ Matched entry %s\n", *given
);
670 * There was no exact-match for this given file, so look
671 * for a "glob" entry which does match.
674 if (verbose
> 2 && globlist
!= NULL
)
675 printf("\t+ Checking globs for %s\n", *given
);
676 for (ent
= globlist
; ent
; ent
= ent
->next
) {
677 fnres
= fnmatch(ent
->log
, *given
, FNM_PATHNAME
);
679 printf("\t+ = %d for pattern %s\n", fnres
,
683 dupent
= init_entry(*given
, ent
);
687 lastnew
->next
= dupent
;
689 /* This new entry is not a glob! */
690 dupent
->flags
&= ~CE_GLOB
;
691 /* Only allow a match to one glob-entry */
697 printf("\t+ Matched %s via %s\n", *given
,
703 * This given file was not found in any config file, so
704 * add a worklist item based on the default entry.
707 printf("\t+ No entry matched %s (will use %s)\n",
708 *given
, DEFAULT_MARKER
);
709 dupent
= init_entry(*given
, defconf
);
713 lastnew
->next
= dupent
;
714 /* Mark that it was *not* found in a config file */
720 * Free all the entries in the original work list, the list of
721 * glob entries, and the default entry.
723 free_clist(&worklist
);
724 free_clist(&globlist
);
727 /* And finally, return a worklist which matches the given files. */
732 * Expand the list of entries with filename patterns, and add all files
733 * which match those glob-entries onto the worklist.
736 expand_globs(struct conf_entry
**work_p
, struct conf_entry
**glob_p
)
740 struct conf_entry
*dupent
, *ent
, *firstmatch
, *globent
;
741 struct conf_entry
*lastmatch
;
745 if ((glob_p
== NULL
) || (*glob_p
== NULL
))
746 return; /* There is nothing to do. */
749 * The worklist contains all fully-specified (non-GLOB) names.
751 * Now expand the list of filename-pattern (GLOB) entries into
752 * a second list, which (by definition) will only match files
753 * that already exist. Do not add a glob-related entry for any
754 * file which already exists in the fully-specified list.
756 firstmatch
= lastmatch
= NULL
;
757 for (globent
= *glob_p
; globent
; globent
= globent
->next
) {
759 gres
= glob(globent
->log
, GLOB_NOCHECK
, NULL
, &pglob
);
761 warn("cannot expand pattern (%d): %s", gres
,
767 printf("\t+ Expanding pattern %s\n", globent
->log
);
768 for (i
= 0; i
< pglob
.gl_matchc
; i
++) {
769 mfname
= pglob
.gl_pathv
[i
];
771 /* See if this file already has a specific entry. */
773 for (ent
= *work_p
; ent
; ent
= ent
->next
) {
774 if (strcmp(mfname
, ent
->log
) == 0) {
782 /* Make sure the named matched is a file. */
783 gres
= lstat(mfname
, &st_fm
);
785 /* Error on a file that glob() matched?!? */
786 warn("Skipping %s - lstat() error", mfname
);
789 if (!S_ISREG(st_fm
.st_mode
)) {
790 /* We only rotate files! */
792 printf("\t+ . skipping %s (!file)\n",
798 printf("\t+ . add file %s\n", mfname
);
799 dupent
= init_entry(mfname
, globent
);
803 lastmatch
->next
= dupent
;
805 /* This new entry is not a glob! */
806 dupent
->flags
&= ~CE_GLOB
;
810 printf("\t+ Done with pattern %s\n", globent
->log
);
813 /* Add the list of matched files to the end of the worklist. */
815 *work_p
= firstmatch
;
820 ent
->next
= firstmatch
;
826 * Parse a configuration file and update a linked list of all the logs to
830 parse_file(FILE *cf
, const char *cfname
, struct conf_entry
**work_p
,
831 struct conf_entry
**glob_p
, struct conf_entry
**defconf_p
)
833 char line
[BUFSIZ
], *parse
, *q
;
834 char *cp
, *errline
, *group
;
835 struct conf_entry
*lastglob
, *lastwork
, *working
;
841 * XXX - for now, assume that only one config file will be read,
842 * ie, this routine is only called one time.
844 lastglob
= lastwork
= NULL
;
846 while (fgets(line
, BUFSIZ
, cf
)) {
847 if ((line
[0] == '\n') || (line
[0] == '#') ||
850 errline
= strdup(line
);
851 for (cp
= line
+ 1; *cp
!= '\0'; cp
++) {
854 if (*(cp
- 1) == '\\') {
863 q
= parse
= missing_field(sob(line
), errline
);
866 errx(1, "malformed line (missing fields):\n%s",
871 working
= init_entry(q
, NULL
);
872 if (strcasecmp(DEFAULT_MARKER
, q
) == 0) {
874 if (defconf_p
== NULL
) {
875 warnx("Ignoring entry for %s in %s!", q
,
879 } else if (*defconf_p
!= NULL
) {
880 warnx("Ignoring duplicate entry for %s!", q
);
884 *defconf_p
= working
;
887 q
= parse
= missing_field(sob(++parse
), errline
);
890 errx(1, "malformed line (missing fields):\n%s",
893 if ((group
= strchr(q
, ':')) != NULL
||
894 (group
= strrchr(q
, '.')) != NULL
) {
898 if ((pwd
= getpwnam(q
)) == NULL
)
900 "error in config file; unknown user:\n%s",
902 working
->uid
= pwd
->pw_uid
;
904 working
->uid
= atoi(q
);
906 working
->uid
= (uid_t
)-1;
911 if ((grp
= getgrnam(q
)) == NULL
)
913 "error in config file; unknown group:\n%s",
915 working
->gid
= grp
->gr_gid
;
917 working
->gid
= atoi(q
);
919 working
->gid
= (gid_t
)-1;
921 q
= parse
= missing_field(sob(++parse
), errline
);
924 errx(1, "malformed line (missing fields):\n%s",
928 working
->uid
= (uid_t
)-1;
929 working
->gid
= (gid_t
)-1;
932 if (!sscanf(q
, "%o", &working
->permissions
))
933 errx(1, "error in config file; bad permissions:\n%s",
936 q
= parse
= missing_field(sob(++parse
), errline
);
939 errx(1, "malformed line (missing fields):\n%s",
942 if (!sscanf(q
, "%d", &working
->numlogs
) || working
->numlogs
< 0)
943 errx(1, "error in config file; bad value for count of logs to save:\n%s",
946 q
= parse
= missing_field(sob(++parse
), errline
);
949 errx(1, "malformed line (missing fields):\n%s",
953 working
->size
= atoi(q
);
954 else if (strcmp(q
,"*") == 0)
957 warnx("Invalid value of '%s' for 'size' in line:\n%s",
963 q
= parse
= missing_field(sob(++parse
), errline
);
971 ul
= strtoul(q
, &ep
, 10);
976 else if (ul
> INT_MAX
)
977 errx(1, "interval is too large:\n%s", errline
);
981 if (*ep
!= '\0' && *ep
!= '@' && *ep
!= '*' &&
983 errx(1, "malformed interval/at:\n%s", errline
);
985 if ((working
->trim_at
= parse8601(ep
+ 1, errline
))
987 errx(1, "malformed at:\n%s", errline
);
988 working
->flags
|= CE_TRIMAT
;
989 } else if (*ep
== '$') {
990 if ((working
->trim_at
= parseDWM(ep
+ 1, errline
))
992 errx(1, "malformed at:\n%s", errline
);
993 working
->flags
|= CE_TRIMAT
;
1000 q
= parse
= sob(++parse
); /* Optional field */
1007 for (; q
&& *q
&& !isspacech(*q
); q
++) {
1008 switch (tolowerch(*q
)) {
1010 working
->flags
|= CE_BINARY
;
1014 * XXX - Ick! Ugly! Remove ASAP!
1015 * We want `c' and `C' for "create". But we
1016 * will temporarily treat `c' as `g', because
1017 * FreeBSD releases <= 4.8 have a typo of
1018 * checking ('G' || 'c') for CE_GLOB.
1021 warnx("Assuming 'g' for 'c' in flags for line:\n%s",
1023 warnx("The 'c' flag will eventually mean 'CREATE'");
1024 working
->flags
|= CE_GLOB
;
1027 working
->flags
|= CE_CREATE
;
1030 working
->flags
|= CE_GLOB
;
1033 working
->flags
|= CE_BZCOMPACT
;
1036 working
->flags
|= CE_NOSIGNAL
;
1039 working
->flags
|= CE_SIGNALGROUP
;
1042 working
->flags
|= CE_COMPACTWAIT
;
1045 working
->flags
|= CE_COMPACT
;
1049 case 'f': /* Used by OpenBSD for "CE_FOLLOW" */
1050 case 'm': /* Used by OpenBSD for "CE_MONITOR" */
1051 case 'p': /* Used by NetBSD for "CE_PLAIN0" */
1053 errx(1, "illegal flag in config file -- %c",
1061 q
= parse
= sob(++parse
); /* Optional field */
1068 working
->pid_file
= NULL
;
1071 working
->pid_file
= strdup(q
);
1072 else if (isdigit(*q
))
1076 "illegal pid file or signal number in config file:\n%s",
1082 q
= parse
= sob(++parse
); /* Optional field */
1083 *(parse
= son(parse
)) = '\0';
1086 working
->sig
= SIGHUP
;
1090 working
->sig
= atoi(q
);
1094 "illegal signal number in config file:\n%s",
1097 if (working
->sig
< 1 || working
->sig
>= NSIG
)
1102 * Finish figuring out what pid-file to use (if any) in
1103 * later processing if this logfile needs to be rotated.
1105 if ((working
->flags
& CE_NOSIGNAL
) == CE_NOSIGNAL
) {
1107 * This config-entry specified 'n' for nosignal,
1108 * see if it also specified an explicit pid_file.
1109 * This would be a pretty pointless combination.
1111 if (working
->pid_file
!= NULL
) {
1112 warnx("Ignoring '%s' because flag 'n' was specified in line:\n%s",
1113 working
->pid_file
, errline
);
1114 free(working
->pid_file
);
1115 working
->pid_file
= NULL
;
1117 } else if (working
->pid_file
== NULL
) {
1119 * This entry did not specify the 'n' flag, which
1120 * means it should signal syslogd unless it had
1121 * specified some other pid-file (and obviously the
1122 * syslog pid-file will not be for a process-group).
1123 * Also, we should only try to notify syslog if we
1126 if (working
->flags
& CE_SIGNALGROUP
) {
1127 warnx("Ignoring flag 'U' in line:\n%s",
1129 working
->flags
&= ~CE_SIGNALGROUP
;
1132 working
->pid_file
= strdup(_PATH_SYSLOGPID
);
1136 * Add this entry to the appropriate list of entries, unless
1137 * it was some kind of special entry (eg: <default>).
1140 ; /* Do not add to any list */
1141 } else if (working
->flags
& CE_GLOB
) {
1145 lastglob
->next
= working
;
1151 lastwork
->next
= working
;
1161 missing_field(char *p
, char *errline
)
1165 errx(1, "missing field in config file:\n%s", errline
);
1170 dotrim(const struct conf_entry
*ent
, char *log
, int numdays
, int flags
)
1172 char dirpart
[MAXPATHLEN
], namepart
[MAXPATHLEN
];
1173 char file1
[MAXPATHLEN
], file2
[MAXPATHLEN
];
1174 char zfile1
[MAXPATHLEN
], zfile2
[MAXPATHLEN
];
1175 char jfile1
[MAXPATHLEN
];
1176 char tfile
[MAXPATHLEN
];
1177 int notified
, need_notification
, fd
, _numdays
;
1183 /* build complete name of archive directory into dirpart */
1184 if (*archdirname
== '/') { /* absolute */
1185 strlcpy(dirpart
, archdirname
, sizeof(dirpart
));
1186 } else { /* relative */
1187 /* get directory part of logfile */
1188 strlcpy(dirpart
, log
, sizeof(dirpart
));
1189 if ((p
= strrchr(dirpart
, '/')) == NULL
)
1193 strlcat(dirpart
, archdirname
, sizeof(dirpart
));
1196 /* check if archive directory exists, if not, create it */
1197 if (lstat(dirpart
, &st
))
1198 createdir(ent
, dirpart
);
1200 /* get filename part of logfile */
1201 if ((p
= strrchr(log
, '/')) == NULL
)
1202 strlcpy(namepart
, log
, sizeof(namepart
));
1204 strlcpy(namepart
, p
+ 1, sizeof(namepart
));
1206 /* name of oldest log */
1207 snprintf(file1
, sizeof(file1
), "%s/%s.%d", dirpart
,
1209 snprintf(zfile1
, sizeof(zfile1
), "%s%s", file1
,
1211 snprintf(jfile1
, sizeof(jfile1
), "%s%s", file1
,
1212 BZCOMPRESS_POSTFIX
);
1214 /* name of oldest log */
1215 snprintf(file1
, sizeof(file1
), "%s.%d", log
, numdays
);
1216 snprintf(zfile1
, sizeof(zfile1
), "%s%s", file1
,
1218 snprintf(jfile1
, sizeof(jfile1
), "%s%s", file1
,
1219 BZCOMPRESS_POSTFIX
);
1223 printf("\trm -f %s\n", file1
);
1224 printf("\trm -f %s\n", zfile1
);
1225 printf("\trm -f %s\n", jfile1
);
1232 /* Move down log files */
1233 _numdays
= numdays
; /* preserve */
1236 strlcpy(file2
, file1
, sizeof(file2
));
1239 snprintf(file1
, sizeof(file1
), "%s/%s.%d",
1240 dirpart
, namepart
, numdays
);
1242 snprintf(file1
, sizeof(file1
), "%s.%d", log
,
1245 strlcpy(zfile1
, file1
, sizeof(zfile1
));
1246 strlcpy(zfile2
, file2
, sizeof(zfile2
));
1247 if (lstat(file1
, &st
)) {
1248 strlcat(zfile1
, COMPRESS_POSTFIX
,
1250 strlcat(zfile2
, COMPRESS_POSTFIX
,
1252 if (lstat(zfile1
, &st
)) {
1253 strlcpy(zfile1
, file1
, sizeof(zfile1
));
1254 strlcpy(zfile2
, file2
, sizeof(zfile2
));
1255 strlcat(zfile1
, BZCOMPRESS_POSTFIX
,
1257 strlcat(zfile2
, BZCOMPRESS_POSTFIX
,
1259 if (lstat(zfile1
, &st
))
1264 printf("\tmv %s %s\n", zfile1
, zfile2
);
1265 printf("\tchmod %o %s\n", ent
->permissions
, zfile2
);
1266 if (ent
->uid
!= (uid_t
)-1 || ent
->gid
!= (gid_t
)-1)
1267 printf("\tchown %u:%u %s\n",
1268 ent
->uid
, ent
->gid
, zfile2
);
1270 rename(zfile1
, zfile2
);
1271 if (chmod(zfile2
, ent
->permissions
))
1272 warn("can't chmod %s", file2
);
1273 if (ent
->uid
!= (uid_t
)-1 || ent
->gid
!= (gid_t
)-1)
1274 if (chown(zfile2
, ent
->uid
, ent
->gid
))
1275 warn("can't chown %s", zfile2
);
1278 if (!noaction
&& !(flags
& CE_BINARY
)) {
1279 /* Report the trimming to the old log */
1285 printf("\trm %s\n", log
);
1290 printf("\tmv %s to %s\n", log
, file1
);
1293 movefile(log
, file1
, ent
->permissions
, ent
->uid
,
1300 /* Now move the new log file into place */
1301 /* XXX - We should replace the above 'rename' with 'link(log, file1)'
1302 * then replace the following with 'createfile(ent)' */
1303 strlcpy(tfile
, log
, sizeof(tfile
));
1304 strlcat(tfile
, ".XXXXXX", sizeof(tfile
));
1306 printf("Start new log...\n");
1307 printf("\tmktemp %s\n", tfile
);
1310 fd
= creat(tfile
, ent
->permissions
);
1312 err(1, "can't start new log");
1313 if (ent
->uid
!= (uid_t
)-1 || ent
->gid
!= (gid_t
)-1)
1314 if (fchown(fd
, ent
->uid
, ent
->gid
))
1315 err(1, "can't chown new log file");
1317 if (!(flags
& CE_BINARY
)) {
1318 /* Add status message to new log file */
1319 if (log_trim(tfile
, ent
))
1320 err(1, "can't add status message to log");
1324 printf("\tchmod %o %s\n", ent
->permissions
, tfile
);
1325 printf("\tmv %s %s\n", tfile
, log
);
1327 chmod(tfile
, ent
->permissions
);
1328 if (rename(tfile
, log
) < 0) {
1329 err(1, "can't start new log");
1335 * Find out if there is a process to signal. If nosignal (-s) was
1336 * specified, then do not signal any process. Note that nosignal
1337 * will trigger a warning message if the rotated logfile needs to
1338 * be compressed, *unless* -R was specified. This is because there
1339 * presumably still are process(es) writing to the old logfile, but
1340 * we assume that a -sR request comes from a process which writes
1341 * to the logfile, and as such, that process has already made sure
1342 * that the logfile is not presently in use.
1344 need_notification
= notified
= 0;
1345 if (ent
->pid_file
!= NULL
) {
1346 need_notification
= 1;
1348 notified
= send_signal(ent
); /* the normal case! */
1350 need_notification
= 0;
1353 if ((flags
& CE_COMPACT
) || (flags
& CE_BZCOMPACT
)) {
1354 if (need_notification
&& !notified
)
1356 "log %s.0 not compressed because daemon(s) not notified",
1359 if (flags
& CE_COMPACT
)
1360 printf("\tgzip %s.0\n", log
);
1362 printf("\tbzip2 %s.0\n", log
);
1366 printf("small pause to allow daemon(s) to close log\n");
1370 snprintf(file1
, sizeof(file1
), "%s/%s",
1372 if (flags
& CE_COMPACT
)
1374 flags
& CE_COMPACTWAIT
);
1375 else if (flags
& CE_BZCOMPACT
)
1376 bzcompress_log(file1
,
1377 flags
& CE_COMPACTWAIT
);
1379 if (flags
& CE_COMPACT
)
1381 flags
& CE_COMPACTWAIT
);
1382 else if (flags
& CE_BZCOMPACT
)
1384 flags
& CE_COMPACTWAIT
);
1390 /* Log the fact that the logs were turned over */
1392 log_trim(const char *log
, const struct conf_entry
*log_ent
)
1397 if ((f
= fopen(log
, "a")) == NULL
)
1400 if (log_ent
->def_cfg
)
1401 xtra
= " using <default> rule";
1402 if (log_ent
->firstcreate
)
1403 fprintf(f
, "%s %s newsyslog[%d]: logfile first created%s\n",
1404 daytime
, hostname
, (int) getpid(), xtra
);
1405 else if (log_ent
->r_reason
!= NULL
)
1406 fprintf(f
, "%s %s newsyslog[%d]: logfile turned over%s%s\n",
1407 daytime
, hostname
, (int) getpid(), log_ent
->r_reason
, xtra
);
1409 fprintf(f
, "%s %s newsyslog[%d]: logfile turned over%s\n",
1410 daytime
, hostname
, (int) getpid(), xtra
);
1411 if (fclose(f
) == EOF
)
1412 err(1, "log_trim: fclose:");
1416 /* Fork of gzip to compress the old log file */
1418 compress_log(char *log
, int dowait
)
1421 char tmp
[MAXPATHLEN
];
1423 while (dowait
&& (wait(NULL
) > 0 || errno
== EINTR
))
1425 snprintf(tmp
, sizeof(tmp
), "%s.0", log
);
1428 err(1, "gzip fork");
1430 execl(_PATH_GZIP
, _PATH_GZIP
, "-f", tmp
, (char *)0);
1435 /* Fork of bzip2 to compress the old log file */
1437 bzcompress_log(char *log
, int dowait
)
1440 char tmp
[MAXPATHLEN
];
1442 while (dowait
&& (wait(NULL
) > 0 || errno
== EINTR
))
1444 snprintf(tmp
, sizeof(tmp
), "%s.0", log
);
1447 err(1, "bzip2 fork");
1449 execl(_PATH_BZIP2
, _PATH_BZIP2
, "-f", tmp
, (char *)0);
1450 err(1, _PATH_BZIP2
);
1454 /* Return size in kilobytes of a file */
1456 sizefile(char *file
)
1460 if (stat(file
, &sb
) < 0)
1462 return (kbytes(dbtob(sb
.st_blocks
)));
1465 /* Return the age of old log file (file.0) */
1467 age_old_log(char *file
)
1471 char tmp
[MAXPATHLEN
+ sizeof(".0") + sizeof(COMPRESS_POSTFIX
) +
1472 sizeof(BZCOMPRESS_POSTFIX
) + 1];
1477 /* build name of archive directory into tmp */
1478 if (*archdirname
== '/') { /* absolute */
1479 strlcpy(tmp
, archdirname
, sizeof(tmp
));
1480 } else { /* relative */
1481 /* get directory part of logfile */
1482 strlcpy(tmp
, file
, sizeof(tmp
));
1483 if ((p
= strrchr(tmp
, '/')) == NULL
)
1487 strlcat(tmp
, archdirname
, sizeof(tmp
));
1490 strlcat(tmp
, "/", sizeof(tmp
));
1492 /* get filename part of logfile */
1493 if ((p
= strrchr(file
, '/')) == NULL
)
1494 strlcat(tmp
, file
, sizeof(tmp
));
1496 strlcat(tmp
, p
+ 1, sizeof(tmp
));
1498 strlcpy(tmp
, file
, sizeof(tmp
));
1501 strlcat(tmp
, ".0", sizeof(tmp
));
1502 if (stat(tmp
, &sb
) < 0) {
1504 * A plain '.0' file does not exist. Try again, first
1505 * with the added suffix of '.gz', then with an added
1506 * suffix of '.bz2' instead of '.gz'.
1508 endp
= strchr(tmp
, '\0');
1509 strlcat(tmp
, COMPRESS_POSTFIX
, sizeof(tmp
));
1510 if (stat(tmp
, &sb
) < 0) {
1511 *endp
= '\0'; /* Remove .gz */
1512 strlcat(tmp
, BZCOMPRESS_POSTFIX
, sizeof(tmp
));
1513 if (stat(tmp
, &sb
) < 0)
1517 return ((int)(timenow
- sb
.st_mtime
+ 1800) / 3600);
1520 /* Skip Over Blanks */
1524 while (p
&& *p
&& isspace(*p
))
1529 /* Skip Over Non-Blanks */
1533 while (p
&& *p
&& !isspace(*p
))
1539 * Parse a limited subset of ISO 8601. The specific format is as follows:
1541 * [CC[YY[MM[DD]]]][THH[MM[SS]]] (where `T' is the literal letter)
1543 * We don't accept a timezone specification; missing fields (including timezone)
1544 * are defaulted to the current date but time zero.
1547 parse8601(char *s
, char *errline
)
1554 tmp
= localtime(&timenow
);
1557 tm
.tm_hour
= tm
.tm_min
= tm
.tm_sec
= 0;
1559 ul
= strtoul(s
, &t
, 10);
1560 if (*t
!= '\0' && *t
!= 'T')
1564 * Now t points either to the end of the string (if no time was
1565 * provided) or to the letter `T' which separates date and time in
1566 * ISO 8601. The pointer arithmetic is the same for either case.
1570 tm
.tm_year
= ((ul
/ 1000000) - 19) * 100;
1573 tm
.tm_year
-= tm
.tm_year
% 100;
1574 tm
.tm_year
+= ul
/ 10000;
1577 tm
.tm_mon
= (ul
/ 100) - 1;
1588 if (tm
.tm_year
< 70 || tm
.tm_mon
< 0 || tm
.tm_mon
> 12
1589 || tm
.tm_mday
< 1 || tm
.tm_mday
> 31)
1594 ul
= strtoul(s
, &t
, 10);
1595 if (*t
!= '\0' && !isspace(*t
))
1600 tm
.tm_sec
= ul
% 100;
1603 tm
.tm_min
= ul
% 100;
1614 if (tm
.tm_sec
< 0 || tm
.tm_sec
> 60 || tm
.tm_min
< 0
1615 || tm
.tm_min
> 59 || tm
.tm_hour
< 0 || tm
.tm_hour
> 23)
1618 if ((tsecs
= mktime(&tm
)) == -1)
1619 errx(1, "nonexistent time:\n%s", errline
);
1623 /* physically move file */
1625 movefile(char *from
, char *to
, int perm
, uid_t owner_uid
, gid_t group_gid
)
1630 if ((src
= fopen(from
, "r")) == NULL
)
1631 err(1, "can't fopen %s for reading", from
);
1632 if ((dst
= fopen(to
, "w")) == NULL
)
1633 err(1, "can't fopen %s for writing", to
);
1634 if (owner_uid
!= (uid_t
)-1 || group_gid
!= (gid_t
)-1) {
1635 if (fchown(fileno(dst
), owner_uid
, group_gid
))
1636 err(1, "can't fchown %s", to
);
1638 if (fchmod(fileno(dst
), perm
))
1639 err(1, "can't fchmod %s", to
);
1641 while ((c
= getc(src
)) != EOF
) {
1642 if ((putc(c
, dst
)) == EOF
)
1643 err(1, "error writing to %s", to
);
1647 err(1, "error reading from %s", from
);
1648 if ((fclose(src
)) != 0)
1649 err(1, "can't fclose %s", to
);
1650 if ((fclose(dst
)) != 0)
1651 err(1, "can't fclose %s", from
);
1652 if ((unlink(from
)) != 0)
1653 err(1, "can't unlink %s", from
);
1656 /* create one or more directory components of a path */
1658 createdir(const struct conf_entry
*ent
, char *dirpart
)
1662 char mkdirpath
[MAXPATHLEN
];
1670 if (*s
!= '/' && *s
!= '\0')
1673 res
= lstat(mkdirpath
, &st
);
1676 printf("\tmkdir %s\n", mkdirpath
);
1678 res
= mkdir(mkdirpath
, 0755);
1680 err(1, "Error on mkdir(\"%s\") for -a",
1688 if (ent
->firstcreate
)
1689 printf("Created directory '%s' for new %s\n",
1692 printf("Created directory '%s' for -a\n", dirpart
);
1697 * Create a new log file, destroying any currently-existing version
1698 * of the log file in the process. If the caller wants a backup copy
1699 * of the file to exist, they should call 'link(logfile,logbackup)'
1700 * before calling this routine.
1703 createlog(const struct conf_entry
*ent
)
1707 char *realfile
, *slash
, tempfile
[MAXPATHLEN
];
1710 realfile
= ent
->log
;
1713 * If this log file is being created for the first time (-C option),
1714 * then it may also be true that the parent directory does not exist
1715 * yet. Check, and create that directory if it is missing.
1717 if (ent
->firstcreate
) {
1718 strlcpy(tempfile
, realfile
, sizeof(tempfile
));
1719 slash
= strrchr(tempfile
, '/');
1720 if (slash
!= NULL
) {
1722 failed
= lstat(tempfile
, &st
);
1723 if (failed
&& errno
!= ENOENT
)
1724 err(1, "Error on lstat(%s)", tempfile
);
1726 createdir(ent
, tempfile
);
1727 else if (!S_ISDIR(st
.st_mode
))
1728 errx(1, "%s exists but is not a directory",
1734 * First create an unused filename, so it can be chown'ed and
1735 * chmod'ed before it is moved into the real location. mkstemp
1736 * will create the file mode=600 & owned by us. Note that all
1737 * temp files will have a suffix of '.z<something>'.
1739 strlcpy(tempfile
, realfile
, sizeof(tempfile
));
1740 strlcat(tempfile
, ".zXXXXXX", sizeof(tempfile
));
1742 printf("\tmktemp %s\n", tempfile
);
1744 fd
= mkstemp(tempfile
);
1746 err(1, "can't mkstemp logfile %s", tempfile
);
1749 * Add status message to what will become the new log file.
1751 if (!(ent
->flags
& CE_BINARY
)) {
1752 if (log_trim(tempfile
, ent
))
1753 err(1, "can't add status message to log");
1757 /* Change the owner/group, if we are supposed to */
1758 if (ent
->uid
!= (uid_t
)-1 || ent
->gid
!= (gid_t
)-1) {
1760 printf("\tchown %u:%u %s\n", ent
->uid
, ent
->gid
,
1763 failed
= fchown(fd
, ent
->uid
, ent
->gid
);
1765 err(1, "can't fchown temp file %s", tempfile
);
1771 * Note that if the real logfile still exists, and if the call
1772 * to rename() fails, then "neither the old file nor the new
1773 * file shall be changed or created" (to quote the standard).
1774 * If the call succeeds, then the file will be replaced without
1775 * any window where some other process might find that the file
1777 * XXX - ? It may be that for some error conditions, we could
1778 * retry by first removing the realfile and then renaming.
1781 printf("\tchmod %o %s\n", ent
->permissions
, tempfile
);
1782 printf("\tmv %s %s\n", tempfile
, realfile
);
1784 failed
= fchmod(fd
, ent
->permissions
);
1786 err(1, "can't fchmod temp file '%s'", tempfile
);
1787 failed
= rename(tempfile
, realfile
);
1789 err(1, "can't mv %s to %s", tempfile
, realfile
);
1797 * Parse a cyclic time specification, the format is as follows:
1799 * [Dhh] or [Wd[Dhh]] or [Mdd[Dhh]]
1801 * to rotate a logfile cyclic at
1803 * - every day (D) within a specific hour (hh) (hh = 0...23)
1804 * - once a week (W) at a specific day (d) OR (d = 0..6, 0 = Sunday)
1805 * - once a month (M) at a specific day (d) (d = 1..31,l|L)
1807 * We don't accept a timezone specification; missing fields
1808 * are defaulted to the current date but time zero.
1811 parseDWM(char *s
, char *errline
)
1818 static int mtab
[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
1822 tmp
= localtime(&timenow
);
1825 /* set no. of days per month */
1827 nd
= mtab
[tm
.tm_mon
];
1829 if (tm
.tm_mon
== 1) {
1830 if (((tm
.tm_year
+ 1900) % 4 == 0) &&
1831 ((tm
.tm_year
+ 1900) % 100 != 0) &&
1832 ((tm
.tm_year
+ 1900) % 400 == 0)) {
1833 nd
++; /* leap year, 29 days in february */
1836 tm
.tm_hour
= tm
.tm_min
= tm
.tm_sec
= 0;
1845 l
= strtol(s
, &t
, 10);
1846 if (l
< 0 || l
> 23)
1856 l
= strtol(s
, &t
, 10);
1859 if (l
!= tm
.tm_wday
) {
1862 if (l
< tm
.tm_wday
) {
1863 save
= 6 - tm
.tm_wday
;
1866 save
= l
- tm
.tm_wday
;
1871 if (tm
.tm_mday
> nd
) {
1873 tm
.tm_mday
= tm
.tm_mday
- nd
;
1883 if (tolower(*s
) == 'l') {
1888 l
= strtol(s
, &t
, 10);
1889 if (l
< 1 || l
> 31)
1903 if (*t
== '\0' || isspace(*t
))
1908 if ((tsecs
= mktime(&tm
)) == -1)
1909 errx(1, "nonexistent time:\n%s", errline
);