4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
22 * Copyright (c) 2001, 2010, Oracle and/or its affiliates. All rights reserved.
23 * Copyright (c) 2013, Joyent, Inc. All rights reserved.
25 * logadm/main.c -- main routines for logadm
27 * this program is 90% argument processing, 10% actions...
37 #include <sys/types.h>
40 #include <sys/filio.h>
41 #include <sys/sysmacros.h>
52 /* forward declarations for functions in this file */
53 static void usage(const char *msg
);
54 static void commajoin(const char *lhs
, void *rhs
, void *arg
);
55 static void doaftercmd(const char *lhs
, void *rhs
, void *arg
);
56 static void dologname(struct fn
*fnp
, struct opts
*clopts
);
57 static boolean_t
rotatelog(struct fn
*fnp
, struct opts
*opts
);
58 static void rotateto(struct fn
*fnp
, struct opts
*opts
, int n
,
59 struct fn
*recentlog
, boolean_t isgz
);
60 static void do_delayed_gzip(const char *lhs
, void *rhs
, void *arg
);
61 static void expirefiles(struct fn
*fnp
, struct opts
*opts
);
62 static void dorm(struct opts
*opts
, const char *msg
, struct fn
*fnp
);
63 static void docmd(struct opts
*opts
, const char *msg
, const char *cmd
,
64 const char *arg1
, const char *arg2
, const char *arg3
);
65 static void docopytruncate(struct opts
*opts
, const char *file
,
66 const char *file_copy
);
68 /* our configuration file, unless otherwise specified by -f */
69 static char *Default_conffile
= "/etc/logadm.conf";
70 /* our timestamps file, unless otherwise specified by -F */
71 static char *Default_timestamps
= "/var/logadm/timestamps";
73 /* default pathnames to the commands we invoke */
74 static char *Sh
= "/bin/sh";
75 static char *Mv
= "/bin/mv";
76 static char *Rm
= "/bin/rm";
77 static char *Touch
= "/bin/touch";
78 static char *Chmod
= "/bin/chmod";
79 static char *Chown
= "/bin/chown";
80 static char *Gzip
= "/bin/gzip";
81 static char *Mkdir
= "/bin/mkdir";
83 /* return from time(0), gathered early on to avoid slewed timestamps */
86 /* list of before commands that have been executed */
87 static struct lut
*Beforecmds
;
89 /* list of after commands to execute before exiting */
90 static struct lut
*Aftercmds
;
92 /* list of conffile entry names that are considered "done" */
93 static struct lut
*Donenames
;
95 /* A list of names of files to be gzipped */
96 static struct lut
*Gzipnames
= NULL
;
99 * only the "FfhnVv" options are allowed in the first form of this command,
100 * so this defines the list of options that are an error in they appear
101 * in the first form. In other words, it is not allowed to run logadm
102 * with any of these options unless at least one logname is also provided.
104 #define OPTIONS_NOT_FIRST_FORM "eNrwpPsabcglmoRtzACEST"
106 /* text that we spew with the -h flag */
108 "Usage: logadm [options]\n"\
109 " (processes all entries in /etc/logadm.conf or conffile given by -f)\n"\
110 " or: logadm [options] logname...\n"\
111 " (processes the given lognames)\n"\
113 "General options:\n"\
114 " -e mailaddr mail errors to given address\n"\
115 " -F timestamps use timestamps instead of /var/logadm/timestamps\n"\
116 " -f conffile use conffile instead of /etc/logadm.conf\n"\
117 " -h display help\n"\
118 " -N not an error if log file nonexistent\n"\
119 " -n show actions, don't perform them\n"\
120 " -r remove logname entry from conffile\n"\
121 " -V ensure conffile entries exist, correct\n"\
122 " -v print info about actions happening\n"\
123 " -w entryname write entry to config file\n"\
125 "Options which control when a logfile is rotated:\n"\
126 "(default is: -s1b -p1w if no -s or -p)\n"\
127 " -p period only rotate if period passed since last rotate\n"\
128 " -P timestamp used to store rotation date in conffile\n"\
129 " -s size only rotate if given size or greater\n"\
132 "Options which control how a logfile is rotated:\n"\
133 "(default is: -t '$file.$n', owner/group/mode taken from log file)\n"\
134 " -a cmd execute cmd after taking actions\n"\
135 " -b cmd execute cmd before taking actions\n"\
136 " -c copy & truncate logfile, don't rename\n"\
137 " -g group new empty log file group\n"\
138 " -l rotate log file with local time rather than UTC\n"\
139 " -m mode new empty log file mode\n"\
140 " -M cmd execute cmd to rotate the log file\n"\
141 " -o owner new empty log file owner\n"\
142 " -R cmd run cmd on file after rotate\n"\
143 " -t template template for naming old logs\n"\
144 " -z count gzip old logs except most recent count\n"\
146 "Options which control the expiration of old logfiles:\n"\
147 "(default is: -C10 if no -A, -C, or -S)\n"\
148 " -A age expire logs older than age\n"\
149 " -C count expire old logs until count remain\n"\
150 " -E cmd run cmd on file to expire\n"\
151 " -S size expire until space used is below size \n"\
152 " -T pattern pattern for finding old logs\n"
155 * main -- where it all begins
159 main(int argc
, char *argv
[])
161 struct opts
*clopts
; /* from parsing command line */
162 const char *conffile
; /* our configuration file */
163 const char *timestamps
; /* our timestamps file */
164 struct fn_list
*lognames
; /* list of lognames we're processing */
170 (void) setlocale(LC_ALL
, "");
172 #if !defined(TEXT_DOMAIN)
173 #define TEXT_DOMAIN "SYS_TEST" /* only used if Makefiles don't define it */
176 (void) textdomain(TEXT_DOMAIN
);
178 /* we only print times into the timestamps file, so make them uniform */
179 (void) setlocale(LC_TIME
, "C");
181 /* give our name to error routines & skip it for arg parsing */
183 (void) setlinebuf(stdout
);
185 if (putenv("PATH=/bin"))
186 err(EF_SYS
, "putenv PATH");
187 if (putenv("TZ=UTC"))
188 err(EF_SYS
, "putenv TZ");
195 /* check for (undocumented) debugging environment variables */
196 if (val
= getenv("_LOGADM_DEFAULT_CONFFILE"))
197 Default_conffile
= val
;
198 if (val
= getenv("_LOGADM_DEFAULT_TIMESTAMPS"))
199 Default_timestamps
= val
;
200 if (val
= getenv("_LOGADM_DEBUG"))
202 if (val
= getenv("_LOGADM_SH"))
204 if (val
= getenv("_LOGADM_MV"))
206 if (val
= getenv("_LOGADM_RM"))
208 if (val
= getenv("_LOGADM_TOUCH"))
210 if (val
= getenv("_LOGADM_CHMOD"))
212 if (val
= getenv("_LOGADM_CHOWN"))
214 if (val
= getenv("_LOGADM_GZIP"))
216 if (val
= getenv("_LOGADM_MKDIR"))
219 opts_init(Opttable
, Opttable_cnt
);
221 /* parse command line arguments */
223 usage("bailing out due to command line errors");
225 clopts
= opts_parse(NULL
, argv
, OPTF_CLI
);
228 (void) fprintf(stderr
, "command line opts:");
229 opts_print(clopts
, stderr
, NULL
);
230 (void) fprintf(stderr
, "\n");
234 * There are many moods of logadm:
236 * 1. "-h" for help was given. We spew a canned help
237 * message and exit, regardless of any other options given.
239 * 2. "-r" or "-w" asking us to write to the conffile. Lots
240 * of argument checking, then we make the change to conffile
241 * and exit. (-r processing actually happens in dologname().)
243 * 3. "-V" to search/verify the conffile was given. We do
244 * the appropriate run through the conffile and exit.
245 * (-V processing actually happens in dologname().)
247 * 4. No lognames were given, so we're being asked to go through
248 * every entry in conffile. We verify that only the options
249 * that make sense for this form of the command are present
250 * and fall into the main processing loop below.
252 * 5. lognames were given, so we fall into the main processing
253 * loop below to work our way through them.
255 * The last two cases are where the option processing gets more
256 * complex. Each time around the main processing loop, we're
257 * in one of these cases:
259 * A. No cmdargs were found (we're in case 4), the entry
260 * in conffile supplies no log file names, so the entry
261 * name itself is the logfile name (or names, if it globs
262 * to multiple file names).
264 * B. No cmdargs were found (we're in case 4), the entry
265 * in conffile gives log file names that we then loop
266 * through and rotate/expire. In this case, the entry
267 * name is specifically NOT one of the log file names.
269 * C. We're going through the cmdargs (we're in case 5),
270 * the entry in conffile either doesn't exist or it exists
271 * but supplies no log file names, so the cmdarg itself
272 * is the log file name.
274 * D. We're going through the cmdargs (we're in case 5),
275 * a matching entry in conffile supplies log file names
276 * that we then loop through and rotate/expire. In this
277 * case the entry name is specifically NOT one of the log
280 * As we're doing all this, any options given on the command line
281 * override any found in the conffile, and we apply the defaults
282 * for rotation conditions and expiration conditions, etc. at the
283 * last opportunity, when we're sure they haven't been overridden
284 * by an option somewhere along the way.
288 /* help option overrides anything else */
289 if (opts_count(clopts
, "h")) {
290 (void) fputs(HELP1
, stderr
);
291 (void) fputs(HELP2
, stderr
);
296 /* detect illegal option combinations */
297 if (opts_count(clopts
, "rwV") > 1)
298 usage("Only one of -r, -w, or -V may be used at a time.");
299 if (opts_count(clopts
, "cM") > 1)
300 usage("Only one of -c or -M may be used at a time.");
302 /* arrange for error output to be mailed if clopts includes -e */
303 if (opts_count(clopts
, "e"))
304 err_mailto(opts_optarg(clopts
, "e"));
306 /* this implements the default conffile and timestamps */
307 if ((conffile
= opts_optarg(clopts
, "f")) == NULL
)
308 conffile
= Default_conffile
;
309 if ((timestamps
= opts_optarg(clopts
, "F")) == NULL
)
310 timestamps
= Default_timestamps
;
311 if (opts_count(clopts
, "v"))
312 (void) out("# loading %s\n", conffile
);
313 status
= conf_open(conffile
, timestamps
, clopts
);
314 if (!status
&& opts_count(clopts
, "V"))
317 /* handle conffile write option */
318 if (opts_count(clopts
, "w")) {
320 (void) fprintf(stderr
,
321 "main: add/replace conffile entry: <%s>\n",
322 opts_optarg(clopts
, "w"));
323 conf_replace(opts_optarg(clopts
, "w"), clopts
);
330 * lognames is either a list supplied on the command line,
331 * or every entry in the conffile if none were supplied.
333 lognames
= opts_cmdargs(clopts
);
334 if (fn_list_empty(lognames
)) {
336 * being asked to do all entries in conffile
338 * check to see if any options were given that only
339 * make sense when lognames are given specifically
340 * on the command line.
342 if (opts_count(clopts
, OPTIONS_NOT_FIRST_FORM
))
343 usage("some options require logname argument");
345 (void) fprintf(stderr
,
346 "main: run all entries in conffile\n");
347 lognames
= conf_entries();
350 /* foreach logname... */
351 fn_list_rewind(lognames
);
352 while ((fnp
= fn_list_next(lognames
)) != NULL
) {
354 if (buf
!= NULL
&& lut_lookup(Donenames
, buf
) != NULL
) {
356 (void) fprintf(stderr
,
357 "main: logname already done: <%s>\n",
361 if (buf
!= NULL
&& SETJMP
)
362 err(EF_FILE
, "bailing out on logname \"%s\" "
363 "due to errors", buf
);
365 dologname(fnp
, clopts
);
368 /* execute any after commands */
369 lut_walk(Aftercmds
, doaftercmd
, clopts
);
371 /* execute any gzip commands */
372 lut_walk(Gzipnames
, do_delayed_gzip
, clopts
);
374 /* write out any conffile changes */
379 return (0); /* for lint's little mind */
382 /* spew a message, then a usage message, then exit */
384 usage(const char *msg
)
387 err(0, "%s\nUse \"logadm -h\" for help.", msg
);
389 err(EF_RAW
, "Use \"logadm -h\" for help.\n");
392 /* helper function used by doaftercmd() to join mail addrs with commas */
395 commajoin(const char *lhs
, void *rhs
, void *arg
)
397 struct fn
*fnp
= (struct fn
*)arg
;
401 if (buf
!= NULL
&& *buf
)
406 /* helper function used by main() to run "after" commands */
408 doaftercmd(const char *lhs
, void *rhs
, void *arg
)
410 struct opts
*opts
= (struct opts
*)arg
;
411 struct lut
*addrs
= (struct lut
*)rhs
;
414 struct fn
*fnp
= fn_new(NULL
);
417 * addrs contains list of email addrs that should get
418 * the error output when this after command is executed.
420 lut_walk(addrs
, commajoin
, fnp
);
421 err_mailto(fn_s(fnp
));
424 docmd(opts
, "-a cmd", Sh
, "-c", lhs
, NULL
);
427 /* perform delayed gzip */
430 do_delayed_gzip(const char *lhs
, void *rhs
, void *arg
)
432 struct opts
*opts
= (struct opts
*)arg
;
436 (void) fprintf(stderr
, "do_delayed_gzip: not gzipping "
437 "expired file <%s>\n", lhs
);
441 docmd(opts
, "compress old log (-z flag)", Gzip
, "-f", lhs
, NULL
);
445 /* main logname processing */
447 dologname(struct fn
*fnp
, struct opts
*clopts
)
449 const char *logname
= fn_s(fnp
);
451 struct opts
*allopts
;
452 struct fn_list
*logfiles
;
453 struct fn_list
*globbedfiles
;
456 /* look up options set by config file */
457 cfopts
= conf_opts(logname
);
459 if (opts_count(clopts
, "v"))
460 (void) out("# processing logname: %s\n", logname
);
464 (void) fprintf(stderr
, "dologname: logname <%s>\n",
466 (void) fprintf(stderr
, "conffile opts:");
467 opts_print(cfopts
, stderr
, NULL
);
468 (void) fprintf(stderr
, "\n");
471 /* handle conffile lookup option */
472 if (opts_count(clopts
, "V")) {
473 /* lookup an entry in conffile */
475 (void) fprintf(stderr
,
476 "dologname: lookup conffile entry\n");
477 if (conf_lookup(logname
)) {
478 opts_printword(logname
, stdout
);
479 opts_print(cfopts
, stdout
, NULL
);
486 /* handle conffile removal option */
487 if (opts_count(clopts
, "r")) {
489 (void) fprintf(stderr
,
490 "dologname: remove conffile entry\n");
491 if (conf_lookup(logname
))
492 conf_replace(logname
, NULL
);
498 /* generate combined options */
499 allopts
= opts_merge(cfopts
, clopts
);
501 /* arrange for error output to be mailed if allopts includes -e */
502 if (opts_count(allopts
, "e"))
503 err_mailto(opts_optarg(allopts
, "e"));
507 /* this implements the default rotation rules */
508 if (opts_count(allopts
, "sp") == 0) {
509 if (opts_count(clopts
, "v"))
511 "# using default rotate rules: -s1b -p1w\n");
512 (void) opts_set(allopts
, "s", "1b");
513 (void) opts_set(allopts
, "p", "1w");
516 /* this implements the default expiration rules */
517 if (opts_count(allopts
, "ACS") == 0) {
518 if (opts_count(clopts
, "v"))
519 (void) out("# using default expire rule: -C10\n");
520 (void) opts_set(allopts
, "C", "10");
523 /* this implements the default template */
524 if (opts_count(allopts
, "t") == 0) {
525 if (opts_count(clopts
, "v"))
526 (void) out("# using default template: $file.$n\n");
527 (void) opts_set(allopts
, "t", "$file.$n");
531 (void) fprintf(stderr
, "merged opts:");
532 opts_print(allopts
, stderr
, NULL
);
533 (void) fprintf(stderr
, "\n");
537 * if the conffile entry supplied log file names, then
538 * logname is NOT one of the log file names (it was just
539 * the entry name in conffile).
541 logfiles
= opts_cmdargs(cfopts
);
544 (void) fprintf(stderr
, "dologname: logfiles from cfopts:\n");
545 fn_list_rewind(logfiles
);
546 while ((nextfnp
= fn_list_next(logfiles
)) != NULL
) {
549 (void) fprintf(stderr
, " <%s>\n", buf
);
552 if (fn_list_empty(logfiles
))
553 globbedfiles
= glob_glob(fnp
);
555 globbedfiles
= glob_glob_list(logfiles
);
557 /* go through the list produced by glob expansion */
558 fn_list_rewind(globbedfiles
);
559 while ((nextfnp
= fn_list_next(globbedfiles
)) != NULL
)
560 if (rotatelog(nextfnp
, allopts
))
561 expirefiles(nextfnp
, allopts
);
563 fn_list_free(globbedfiles
);
568 /* absurdly long buffer lengths for holding user/group/mode strings */
569 #define TIMESTRMAX 100
572 /* rotate a log file if necessary, returns true if ok to go on to expire step */
574 rotatelog(struct fn
*fnp
, struct opts
*opts
)
576 char *fname
= fn_s(fnp
);
578 char nowstr
[TIMESTRMAX
];
579 struct fn
*recentlog
= fn_new(NULL
); /* for -R cmd */
580 char ownerbuf
[MAXATTR
];
581 char groupbuf
[MAXATTR
];
582 char modebuf
[MAXATTR
];
587 if (Debug
&& fname
!= NULL
)
588 (void) fprintf(stderr
, "rotatelog: fname <%s>\n", fname
);
590 if (opts_count(opts
, "p") && opts_optarg_int(opts
, "p") == OPTP_NEVER
)
591 return (B_TRUE
); /* "-p never" forced no rotate */
593 /* prepare the keywords */
596 (void) fprintf(stderr
, "rotatelog keywords:\n");
600 if (lstat(fname
, &stbuf
) < 0) {
601 if (opts_count(opts
, "N"))
603 err(EF_WARN
|EF_SYS
, "%s", fname
);
607 if ((stbuf
.st_mode
& S_IFMT
) == S_IFLNK
) {
608 err(EF_WARN
, "%s is a symlink", fname
);
612 if ((stbuf
.st_mode
& S_IFMT
) != S_IFREG
) {
613 err(EF_WARN
, "%s is not a regular file", fname
);
617 /* even if size condition is not met, this entry is "done" */
618 if (opts_count(opts
, "s") &&
619 stbuf
.st_size
< opts_optarg_int(opts
, "s")) {
620 Donenames
= lut_add(Donenames
, fname
, "1");
624 /* see if age condition is present, and return if not met */
625 if (opts_count(opts
, "p")) {
626 off_t when
= opts_optarg_int(opts
, "p");
629 /* unless rotate forced by "-p now", see if period has passed */
630 if (when
!= OPTP_NOW
) {
632 * "when" holds the number of seconds that must have
633 * passed since the last time this log was rotated.
634 * of course, running logadm can take a little time
635 * (typically a second or two, but longer if the
636 * conffile has lots of stuff in it) and that amount
637 * of time is variable, depending on system load, etc.
638 * so we want to allow a little "slop" in the value of
639 * "when". this way, if a log should be rotated every
640 * week, and the number of seconds passed is really a
641 * few seconds short of a week, we'll go ahead and
642 * rotate the log as expected.
649 * last rotation is recorded as argument to -P,
650 * but if logname isn't the same as log file name
651 * then the timestamp would be recorded on a
652 * separate line in the timestamp file. so if we
653 * haven't seen a -P already, we check to see if
654 * it is part of a specific entry for the log
655 * file name. this handles the case where the
656 * logname is "apache", it supplies a log file
657 * name like "/var/apache/logs/[a-z]*_log",
658 * which expands to multiple file names. if one
659 * of the file names is "/var/apache/logs/access_log"
660 * the the -P will be attached to a line with that
661 * logname in the timestamp file.
663 if (opts_count(opts
, "P")) {
664 off_t last
= opts_optarg_int(opts
, "P");
666 /* return if not enough time has passed */
667 if (Now
- last
< when
)
669 } else if ((cfopts
= conf_opts(fname
)) != NULL
&&
670 opts_count(cfopts
, "P")) {
671 off_t last
= opts_optarg_int(cfopts
, "P");
674 * just checking this means this entry
675 * is now "done" if we're going through
676 * the entire conffile
678 Donenames
= lut_add(Donenames
, fname
, "1");
680 /* return if not enough time has passed */
681 if (Now
- last
< when
)
688 (void) fprintf(stderr
, "rotatelog: conditions met\n");
689 if (opts_count(opts
, "l")) {
690 /* Change the time zone to local time zone */
692 err(EF_SYS
, "putenv TZ");
696 /* rename the log file */
697 rotateto(fnp
, opts
, 0, recentlog
, B_FALSE
);
699 /* Change the time zone to UTC */
700 if (putenv("TZ=UTC"))
701 err(EF_SYS
, "putenv TZ");
705 /* rename the log file */
706 rotateto(fnp
, opts
, 0, recentlog
, B_FALSE
);
709 /* determine owner, group, mode for empty log file */
710 if (opts_count(opts
, "o"))
711 (void) strlcpy(ownerbuf
, opts_optarg(opts
, "o"), MAXATTR
);
713 (void) snprintf(ownerbuf
, MAXATTR
, "%ld", stbuf
.st_uid
);
716 if (opts_count(opts
, "g"))
717 group
= opts_optarg(opts
, "g");
719 (void) snprintf(groupbuf
, MAXATTR
, "%ld", stbuf
.st_gid
);
722 (void) strlcat(ownerbuf
, ":", MAXATTR
- strlen(ownerbuf
));
723 (void) strlcat(ownerbuf
, group
, MAXATTR
- strlen(ownerbuf
));
724 if (opts_count(opts
, "m"))
725 mode
= opts_optarg(opts
, "m");
727 (void) snprintf(modebuf
, MAXATTR
,
728 "%03lo", stbuf
.st_mode
& 0777);
732 /* create the empty log file */
733 docmd(opts
, NULL
, Touch
, fname
, NULL
, NULL
);
734 docmd(opts
, NULL
, Chown
, owner
, fname
, NULL
);
735 docmd(opts
, NULL
, Chmod
, mode
, fname
, NULL
);
737 /* execute post-rotation command */
738 if (opts_count(opts
, "R")) {
739 struct fn
*rawcmd
= fn_new(opts_optarg(opts
, "R"));
740 struct fn
*cmd
= fn_new(NULL
);
742 kw_init(recentlog
, NULL
);
743 (void) kw_expand(rawcmd
, cmd
, 0, B_FALSE
);
744 docmd(opts
, "-R cmd", Sh
, "-c", fn_s(cmd
), NULL
);
751 * add "after" command to list of after commands. we also record
752 * the email address, if any, where the error output of the after
753 * command should be sent. if the after command is already on
754 * our list, add the email addr to the list the email addrs for
755 * that command (the after command will only be executed once,
756 * so the error output gets mailed to every address we've come
757 * across associated with this command).
759 if (opts_count(opts
, "a")) {
760 const char *cmd
= opts_optarg(opts
, "a");
761 struct lut
*addrs
= (struct lut
*)lut_lookup(Aftercmds
, cmd
);
762 if (opts_count(opts
, "e"))
763 addrs
= lut_add(addrs
, opts_optarg(opts
, "e"), NULL
);
764 Aftercmds
= lut_add(Aftercmds
, opts_optarg(opts
, "a"), addrs
);
767 /* record the rotation date */
768 (void) strftime(nowstr
, sizeof (nowstr
),
769 "%a %b %e %T %Y", gmtime(&Now
));
770 if (opts_count(opts
, "v") && fname
!= NULL
)
771 (void) out("# recording rotation date %s for %s\n",
773 conf_set(fname
, "P", STRDUP(nowstr
));
774 Donenames
= lut_add(Donenames
, fname
, "1");
778 /* rotate files "up" according to current template */
780 rotateto(struct fn
*fnp
, struct opts
*opts
, int n
, struct fn
*recentlog
,
783 struct fn
*template = fn_new(opts_optarg(opts
, "t"));
784 struct fn
*newfile
= fn_new(NULL
);
791 /* expand template to figure out new filename */
792 hasn
= kw_expand(template, newfile
, n
, isgz
);
795 buf2
= fn_s(newfile
);
798 if (buf1
!= NULL
&& buf2
!= NULL
) {
799 (void) fprintf(stderr
, "rotateto: %s -> %s (%d)\n",
802 /* if filename is there already, rotate "up" */
803 if (hasn
&& lstat(buf2
, &stbuf
) != -1)
804 rotateto(newfile
, opts
, n
+ 1, recentlog
, isgz
);
805 else if (hasn
&& opts_count(opts
, "z")) {
806 struct fn
*gzfnp
= fn_dup(newfile
);
808 * since we're compressing old files, see if we
809 * about to rotate into one.
811 fn_puts(gzfnp
, ".gz");
812 if (lstat(fn_s(gzfnp
), &stbuf
) != -1)
813 rotateto(gzfnp
, opts
, n
+ 1, recentlog
, B_TRUE
);
817 /* first time through run "before" cmd if not run already */
818 if (n
== 0 && opts_count(opts
, "b")) {
819 const char *cmd
= opts_optarg(opts
, "b");
821 if (lut_lookup(Beforecmds
, cmd
) == NULL
) {
822 docmd(opts
, "-b cmd", Sh
, "-c", cmd
, NULL
);
823 Beforecmds
= lut_add(Beforecmds
, cmd
, "1");
827 /* ensure destination directory exists */
828 dirname
= fn_dirname(newfile
);
829 docmd(opts
, "verify directory exists", Mkdir
, "-p",
830 fn_s(dirname
), NULL
);
834 if (n
== 0 && opts_count(opts
, "c") != 0) {
835 docopytruncate(opts
, fn_s(fnp
), fn_s(newfile
));
836 } else if (n
== 0 && opts_count(opts
, "M")) {
837 struct fn
*rawcmd
= fn_new(opts_optarg(opts
, "M"));
838 struct fn
*cmd
= fn_new(NULL
);
840 /* use specified command to mv the log file */
841 kw_init(fnp
, newfile
);
842 (void) kw_expand(rawcmd
, cmd
, 0, B_FALSE
);
843 docmd(opts
, "-M cmd", Sh
, "-c", fn_s(cmd
), NULL
);
847 /* common case: we call "mv" to handle the actual rename */
848 docmd(opts
, "rotate log file", Mv
, "-f",
849 fn_s(fnp
), fn_s(newfile
));
851 /* first time through, gather interesting info for caller */
853 fn_renew(recentlog
, fn_s(newfile
));
856 /* expire phase of logname processing */
858 expirefiles(struct fn
*fnp
, struct opts
*opts
)
860 char *fname
= fn_s(fnp
);
863 struct fn_list
*files
;
868 if (Debug
&& fname
!= NULL
)
869 (void) fprintf(stderr
, "expirefiles: fname <%s>\n", fname
);
871 /* return if no potential expire conditions */
872 if (opts_count(opts
, "zAS") == 0 && opts_optarg_int(opts
, "C") == 0)
877 (void) fprintf(stderr
, "expirefiles keywords:\n");
881 /* see if pattern was supplied by user */
882 if (opts_count(opts
, "T")) {
883 template = fn_new(opts_optarg(opts
, "T"));
884 pattern
= glob_to_reglob(template);
886 /* nope, generate pattern based on rotation template */
887 template = fn_new(opts_optarg(opts
, "t"));
888 pattern
= fn_new(NULL
);
889 (void) kw_expand(template, pattern
, -1,
890 opts_count(opts
, "z") != 0);
893 /* match all old log files (hopefully not any others as well!) */
894 files
= glob_reglob(pattern
);
901 (void) fprintf(stderr
, "expirefiles: pattern <%s>\n",
904 fn_list_rewind(files
);
905 while ((nextfnp
= fn_list_next(files
)) != NULL
) {
908 (void) fprintf(stderr
, " <%s>\n", buf
);
912 /* see if count causes expiration */
913 if ((count
= opts_optarg_int(opts
, "C")) > 0) {
914 int needexpire
= fn_list_count(files
) - count
;
917 (void) fprintf(stderr
, "expirefiles: needexpire %d\n",
920 while (needexpire
> 0 &&
921 ((nextfnp
= fn_list_popoldest(files
)) != NULL
)) {
922 dorm(opts
, "expire by count rule", nextfnp
);
928 /* see if total size causes expiration */
929 if (opts_count(opts
, "S") && (size
= opts_optarg_int(opts
, "S")) > 0) {
930 while (fn_list_totalsize(files
) > size
&&
931 ((nextfnp
= fn_list_popoldest(files
)) != NULL
)) {
932 dorm(opts
, "expire by size rule", nextfnp
);
937 /* see if age causes expiration */
938 if (opts_count(opts
, "A")) {
939 int mtime
= (int)time(0) - (int)opts_optarg_int(opts
, "A");
941 while ((nextfnp
= fn_list_popoldest(files
)) != NULL
) {
942 if (fn_getstat(nextfnp
)->st_mtime
< mtime
) {
943 dorm(opts
, "expire by age rule", nextfnp
);
946 fn_list_addfn(files
, nextfnp
);
952 /* record old log files to be gzip'ed according to -z count */
953 if (opts_count(opts
, "z")) {
954 int zcount
= (int)opts_optarg_int(opts
, "z");
955 int fcount
= fn_list_count(files
);
957 while (fcount
> zcount
&&
958 (nextfnp
= fn_list_popoldest(files
)) != NULL
) {
959 if (!fn_isgz(nextfnp
)) {
961 * Don't gzip the old log file yet -
962 * it takes too long. Just remember that we
966 (void) fprintf(stderr
,
967 "will compress %s count %d\n",
968 fn_s(nextfnp
), fcount
);
970 Gzipnames
= lut_add(Gzipnames
,
982 /* execute a command to remove an expired log file */
984 dorm(struct opts
*opts
, const char *msg
, struct fn
*fnp
)
986 if (opts_count(opts
, "E")) {
987 struct fn
*rawcmd
= fn_new(opts_optarg(opts
, "E"));
988 struct fn
*cmd
= fn_new(NULL
);
990 /* user supplied cmd, expand $file */
992 (void) kw_expand(rawcmd
, cmd
, 0, B_FALSE
);
993 docmd(opts
, msg
, Sh
, "-c", fn_s(cmd
), NULL
);
997 docmd(opts
, msg
, Rm
, "-f", fn_s(fnp
), NULL
);
998 Gzipnames
= lut_add(Gzipnames
, fn_s(fnp
), NULL
);
1001 /* execute a command, producing -n and -v output as necessary */
1003 docmd(struct opts
*opts
, const char *msg
, const char *cmd
,
1004 const char *arg1
, const char *arg2
, const char *arg3
)
1009 /* print info about command if necessary */
1010 if (opts_count(opts
, "vn")) {
1011 const char *simplecmd
;
1013 if ((simplecmd
= strrchr(cmd
, '/')) == NULL
)
1017 (void) out("%s", simplecmd
);
1019 (void) out(" %s", arg1
);
1021 (void) out(" %s", arg2
);
1023 (void) out(" %s", arg3
);
1025 (void) out(" # %s", msg
);
1029 if (opts_count(opts
, "n"))
1030 return; /* -n means don't really do it */
1033 * run the cmd and see if it failed. this function is *not* a
1034 * generic command runner -- we depend on some knowledge we
1035 * have about the commands we run. first of all, we expect
1036 * errors to spew something to stderr, and that something is
1037 * typically short enough to fit into a pipe so we can wait()
1038 * for the command to complete and then fetch the error text
1039 * from the pipe. we also expect the exit codes to make sense.
1040 * notice also that we only allow a command name which is an
1041 * absolute pathname, and two args must be supplied (the
1042 * second may be NULL, or they may both be NULL).
1044 if (pipe(errpipe
) < 0)
1045 err(EF_SYS
, "pipe");
1047 if ((pid
= fork()) < 0)
1048 err(EF_SYS
, "fork");
1054 (void) close(errpipe
[1]);
1055 if (waitpid(pid
, &wstat
, 0) < 0)
1056 err(EF_SYS
, "waitpid");
1058 /* check for stderr output */
1059 if (ioctl(errpipe
[0], FIONREAD
, &count
) >= 0 && count
) {
1060 err(EF_WARN
, "command failed: %s%s%s%s%s%s%s",
1067 (arg3
) ? arg3
: "");
1068 err_fromfd(errpipe
[0]);
1069 } else if (WIFSIGNALED(wstat
))
1071 "command died, signal %d: %s%s%s%s%s%s%s",
1079 (arg3
) ? arg3
: "");
1080 else if (WIFEXITED(wstat
) && WEXITSTATUS(wstat
))
1082 "command error, exit %d: %s%s%s%s%s%s%s",
1090 (arg3
) ? arg3
: "");
1092 (void) close(errpipe
[0]);
1095 (void) dup2(errpipe
[1], fileno(stderr
));
1096 (void) close(errpipe
[0]);
1097 (void) execl(cmd
, cmd
, arg1
, arg2
, arg3
, 0);
1103 /* do internal atomic file copy and truncation */
1105 docopytruncate(struct opts
*opts
, const char *file
, const char *file_copy
)
1108 char buf
[128 * 1024];
1110 struct utimbuf times
;
1111 off_t written
= 0, rem
, last
= 0, thresh
= 1024 * 1024;
1114 /* print info if necessary */
1115 if (opts_count(opts
, "vn") != 0) {
1116 (void) out("# log rotation via atomic copy and truncation"
1118 (void) out("# copy %s to %s\n", file
, file_copy
);
1119 (void) out("# truncate %s\n", file
);
1122 if (opts_count(opts
, "n"))
1123 return; /* -n means don't really do it */
1125 /* open log file to be rotated and remember its chmod mask */
1126 if ((fi
= open(file
, O_RDWR
)) < 0) {
1127 err(EF_SYS
, "cannot open file %s", file
);
1131 if (fstat(fi
, &s
) < 0) {
1132 err(EF_SYS
, "cannot access: %s", file
);
1137 /* create new file for copy destination with correct attributes */
1138 if ((fo
= open(file_copy
, O_CREAT
|O_TRUNC
|O_WRONLY
, s
.st_mode
)) < 0) {
1139 err(EF_SYS
, "cannot create file: %s", file_copy
);
1144 (void) fchown(fo
, s
.st_uid
, s
.st_gid
);
1147 * Now we'll loop, reading the log file and writing it to our copy
1148 * until the bytes remaining are beneath our atomicity threshold -- at
1149 * which point we'll lock the file and copy the remainder atomically.
1150 * The body of this loop is non-atomic with respect to writers, the
1151 * rationale being that total atomicity (that is, locking the file for
1152 * the entire duration of the copy) comes at too great a cost for a
1153 * large log file, as the writer (i.e., the daemon whose log is being
1154 * rolled) can be blocked for an unacceptable duration. (For one
1155 * particularly loquacious daemon, this period was observed to be
1156 * several minutes in length -- a time so long that it induced
1157 * additional failures in dependent components.) Note that this means
1158 * that if the log file is not always appended to -- if it is opened
1159 * without O_APPEND or otherwise truncated outside of logadm -- this
1160 * will result in our log snapshot being incorrect. But of course, in
1161 * either of these cases, the use of logadm at all is itself
1165 if (fstat(fi
, &s
) < 0) {
1166 err(EF_SYS
, "cannot stat: %s", file
);
1169 (void) remove(file_copy
);
1173 if ((rem
= s
.st_size
- written
) < thresh
) {
1178 * If the file became smaller, something fishy is going
1179 * on; we'll truncate our copy, reset our seek offset
1180 * and break into the atomic copy.
1182 (void) ftruncate(fo
, 0);
1183 (void) lseek(fo
, 0, SEEK_SET
);
1184 (void) lseek(fi
, 0, SEEK_SET
);
1188 if (written
!= 0 && rem
> last
) {
1190 * We're falling behind -- this file is getting bigger
1191 * faster than we're able to write it; break out and
1192 * lock the file to block the writer.
1200 if ((len
= read(fi
, buf
, MIN(sizeof (buf
), rem
))) <= 0)
1203 if (write(fo
, buf
, len
) == len
) {
1209 err(EF_SYS
, "cannot write into file %s", file_copy
);
1212 (void) remove(file_copy
);
1217 /* lock log file so that nobody can write into it before we are done */
1218 if (fchmod(fi
, s
.st_mode
|S_ISGID
) < 0)
1219 err(EF_SYS
, "cannot set mandatory lock bit for: %s", file
);
1221 if (lockf(fi
, F_LOCK
, 0) == -1)
1222 err(EF_SYS
, "cannot lock file %s", file
);
1224 /* do atomic copy and truncation */
1225 while ((len
= read(fi
, buf
, sizeof (buf
))) > 0)
1226 if (write(fo
, buf
, len
) != len
) {
1227 err(EF_SYS
, "cannot write into file %s", file_copy
);
1228 (void) lockf(fi
, F_ULOCK
, 0);
1229 (void) fchmod(fi
, s
.st_mode
);
1232 (void) remove(file_copy
);
1236 (void) ftruncate(fi
, 0);
1238 /* unlock log file */
1239 if (lockf(fi
, F_ULOCK
, 0) == -1)
1240 err(EF_SYS
, "cannot unlock file %s", file
);
1242 if (fchmod(fi
, s
.st_mode
) < 0)
1243 err(EF_SYS
, "cannot reset mandatory lock bit for: %s", file
);
1248 /* keep times from original file */
1249 times
.actime
= s
.st_atime
;
1250 times
.modtime
= s
.st_mtime
;
1251 (void) utime(file_copy
, ×
);