xref: /onnv-gate/usr/src/cmd/logadm/main.c (revision 12986:04c3fb904c79)
10Sstevel@tonic-gate /*
20Sstevel@tonic-gate  * CDDL HEADER START
30Sstevel@tonic-gate  *
40Sstevel@tonic-gate  * The contents of this file are subject to the terms of the
51913Sdg199075  * Common Development and Distribution License (the "License").
61913Sdg199075  * You may not use this file except in compliance with the License.
70Sstevel@tonic-gate  *
80Sstevel@tonic-gate  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
90Sstevel@tonic-gate  * or http://www.opensolaris.org/os/licensing.
100Sstevel@tonic-gate  * See the License for the specific language governing permissions
110Sstevel@tonic-gate  * and limitations under the License.
120Sstevel@tonic-gate  *
130Sstevel@tonic-gate  * When distributing Covered Code, include this CDDL HEADER in each
140Sstevel@tonic-gate  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
150Sstevel@tonic-gate  * If applicable, add the following below this CDDL HEADER, with the
160Sstevel@tonic-gate  * fields enclosed by brackets "[]" replaced with your own identifying
170Sstevel@tonic-gate  * information: Portions Copyright [yyyy] [name of copyright owner]
180Sstevel@tonic-gate  *
190Sstevel@tonic-gate  * CDDL HEADER END
200Sstevel@tonic-gate  */
210Sstevel@tonic-gate /*
22*12986SJohn.Zolnowsky@Sun.COM  * Copyright (c) 2001, 2010, Oracle and/or its affiliates. All rights reserved.
230Sstevel@tonic-gate  *
240Sstevel@tonic-gate  * logadm/main.c -- main routines for logadm
250Sstevel@tonic-gate  *
260Sstevel@tonic-gate  * this program is 90% argument processing, 10% actions...
270Sstevel@tonic-gate  */
280Sstevel@tonic-gate 
290Sstevel@tonic-gate #include <stdio.h>
300Sstevel@tonic-gate #include <stdlib.h>
310Sstevel@tonic-gate #include <unistd.h>
320Sstevel@tonic-gate #include <strings.h>
330Sstevel@tonic-gate #include <libintl.h>
340Sstevel@tonic-gate #include <locale.h>
3511829SPetr.Sumbera@Sun.COM #include <fcntl.h>
360Sstevel@tonic-gate #include <sys/types.h>
370Sstevel@tonic-gate #include <sys/stat.h>
380Sstevel@tonic-gate #include <sys/wait.h>
390Sstevel@tonic-gate #include <sys/filio.h>
400Sstevel@tonic-gate #include <time.h>
4111829SPetr.Sumbera@Sun.COM #include <utime.h>
420Sstevel@tonic-gate #include "err.h"
430Sstevel@tonic-gate #include "lut.h"
440Sstevel@tonic-gate #include "fn.h"
450Sstevel@tonic-gate #include "opts.h"
460Sstevel@tonic-gate #include "conf.h"
470Sstevel@tonic-gate #include "glob.h"
480Sstevel@tonic-gate #include "kw.h"
490Sstevel@tonic-gate 
500Sstevel@tonic-gate /* forward declarations for functions in this file */
510Sstevel@tonic-gate static void usage(const char *msg);
520Sstevel@tonic-gate static void commajoin(const char *lhs, void *rhs, void *arg);
530Sstevel@tonic-gate static void doaftercmd(const char *lhs, void *rhs, void *arg);
540Sstevel@tonic-gate static void dologname(struct fn *fnp, struct opts *clopts);
550Sstevel@tonic-gate static boolean_t rotatelog(struct fn *fnp, struct opts *opts);
560Sstevel@tonic-gate static void rotateto(struct fn *fnp, struct opts *opts, int n,
570Sstevel@tonic-gate     struct fn *recentlog, boolean_t isgz);
581913Sdg199075 static void do_delayed_gzip(const char *lhs, void *rhs, void *arg);
590Sstevel@tonic-gate static void expirefiles(struct fn *fnp, struct opts *opts);
600Sstevel@tonic-gate static void dorm(struct opts *opts, const char *msg, struct fn *fnp);
610Sstevel@tonic-gate static void docmd(struct opts *opts, const char *msg, const char *cmd,
620Sstevel@tonic-gate     const char *arg1, const char *arg2, const char *arg3);
6311829SPetr.Sumbera@Sun.COM static void docopytruncate(struct opts *opts, const char *file,
6411829SPetr.Sumbera@Sun.COM     const char *file_copy);
650Sstevel@tonic-gate 
660Sstevel@tonic-gate /* our configuration file, unless otherwise specified by -f */
670Sstevel@tonic-gate static char *Default_conffile = "/etc/logadm.conf";
68*12986SJohn.Zolnowsky@Sun.COM /* our timestamps file, unless otherwise specified by -F */
69*12986SJohn.Zolnowsky@Sun.COM static char *Default_timestamps = "/var/logadm/timestamps";
700Sstevel@tonic-gate 
710Sstevel@tonic-gate /* default pathnames to the commands we invoke */
720Sstevel@tonic-gate static char *Sh = "/bin/sh";
730Sstevel@tonic-gate static char *Mv = "/bin/mv";
740Sstevel@tonic-gate static char *Rm = "/bin/rm";
750Sstevel@tonic-gate static char *Touch = "/bin/touch";
760Sstevel@tonic-gate static char *Chmod = "/bin/chmod";
770Sstevel@tonic-gate static char *Chown = "/bin/chown";
780Sstevel@tonic-gate static char *Gzip = "/bin/gzip";
790Sstevel@tonic-gate static char *Mkdir = "/bin/mkdir";
800Sstevel@tonic-gate 
810Sstevel@tonic-gate /* return from time(0), gathered early on to avoid slewed timestamps */
820Sstevel@tonic-gate time_t Now;
830Sstevel@tonic-gate 
840Sstevel@tonic-gate /* list of before commands that have been executed */
850Sstevel@tonic-gate static struct lut *Beforecmds;
860Sstevel@tonic-gate 
870Sstevel@tonic-gate /* list of after commands to execute before exiting */
880Sstevel@tonic-gate static struct lut *Aftercmds;
890Sstevel@tonic-gate 
900Sstevel@tonic-gate /* list of conffile entry names that are considered "done" */
910Sstevel@tonic-gate static struct lut *Donenames;
920Sstevel@tonic-gate 
931913Sdg199075 /* A list of names of files to be gzipped */
941913Sdg199075 static struct lut *Gzipnames = NULL;
951913Sdg199075 
960Sstevel@tonic-gate /*
97*12986SJohn.Zolnowsky@Sun.COM  * only the "FfhnVv" options are allowed in the first form of this command,
980Sstevel@tonic-gate  * so this defines the list of options that are an error in they appear
990Sstevel@tonic-gate  * in the first form.  In other words, it is not allowed to run logadm
1000Sstevel@tonic-gate  * with any of these options unless at least one logname is also provided.
1010Sstevel@tonic-gate  */
102954Sgm149974 #define	OPTIONS_NOT_FIRST_FORM	"eNrwpPsabcglmoRtzACEST"
1030Sstevel@tonic-gate 
1040Sstevel@tonic-gate /* text that we spew with the -h flag */
1050Sstevel@tonic-gate #define	HELP1 \
1060Sstevel@tonic-gate "Usage: logadm [options]\n"\
1070Sstevel@tonic-gate "       (processes all entries in /etc/logadm.conf or conffile given by -f)\n"\
1080Sstevel@tonic-gate "   or: logadm [options] logname...\n"\
1090Sstevel@tonic-gate "       (processes the given lognames)\n"\
1100Sstevel@tonic-gate "\n"\
1110Sstevel@tonic-gate "General options:\n"\
1120Sstevel@tonic-gate "        -e mailaddr     mail errors to given address\n"\
113*12986SJohn.Zolnowsky@Sun.COM "        -F timestamps   use timestamps instead of /var/logadm/timestamps\n"\
1140Sstevel@tonic-gate "        -f conffile     use conffile instead of /etc/logadm.conf\n"\
1150Sstevel@tonic-gate "        -h              display help\n"\
1160Sstevel@tonic-gate "        -N              not an error if log file nonexistent\n"\
1170Sstevel@tonic-gate "        -n              show actions, don't perform them\n"\
1180Sstevel@tonic-gate "        -r              remove logname entry from conffile\n"\
1190Sstevel@tonic-gate "        -V              ensure conffile entries exist, correct\n"\
1200Sstevel@tonic-gate "        -v              print info about actions happening\n"\
1210Sstevel@tonic-gate "        -w entryname    write entry to config file\n"\
1220Sstevel@tonic-gate "\n"\
1230Sstevel@tonic-gate "Options which control when a logfile is rotated:\n"\
1240Sstevel@tonic-gate "(default is: -s1b -p1w if no -s or -p)\n"\
1250Sstevel@tonic-gate "        -p period       only rotate if period passed since last rotate\n"\
1260Sstevel@tonic-gate "        -P timestamp    used to store rotation date in conffile\n"\
1270Sstevel@tonic-gate "        -s size         only rotate if given size or greater\n"\
1280Sstevel@tonic-gate "\n"
1290Sstevel@tonic-gate #define	HELP2 \
1300Sstevel@tonic-gate "Options which control how a logfile is rotated:\n"\
1310Sstevel@tonic-gate "(default is: -t '$file.$n', owner/group/mode taken from log file)\n"\
1320Sstevel@tonic-gate "        -a cmd          execute cmd after taking actions\n"\
1330Sstevel@tonic-gate "        -b cmd          execute cmd before taking actions\n"\
1340Sstevel@tonic-gate "        -c              copy & truncate logfile, don't rename\n"\
1350Sstevel@tonic-gate "        -g group        new empty log file group\n"\
136954Sgm149974 "        -l              rotate log file with local time rather than UTC\n"\
1370Sstevel@tonic-gate "        -m mode         new empty log file mode\n"\
1380Sstevel@tonic-gate "        -M cmd          execute cmd to rotate the log file\n"\
1390Sstevel@tonic-gate "        -o owner        new empty log file owner\n"\
1400Sstevel@tonic-gate "        -R cmd          run cmd on file after rotate\n"\
1410Sstevel@tonic-gate "        -t template     template for naming old logs\n"\
1420Sstevel@tonic-gate "        -z count        gzip old logs except most recent count\n"\
1430Sstevel@tonic-gate "\n"\
1440Sstevel@tonic-gate "Options which control the expiration of old logfiles:\n"\
1450Sstevel@tonic-gate "(default is: -C10 if no -A, -C, or -S)\n"\
1460Sstevel@tonic-gate "        -A age          expire logs older than age\n"\
1470Sstevel@tonic-gate "        -C count        expire old logs until count remain\n"\
1480Sstevel@tonic-gate "        -E cmd          run cmd on file to expire\n"\
1490Sstevel@tonic-gate "        -S size         expire until space used is below size \n"\
1500Sstevel@tonic-gate "        -T pattern      pattern for finding old logs\n"
1510Sstevel@tonic-gate 
1520Sstevel@tonic-gate /*
1530Sstevel@tonic-gate  * main -- where it all begins
1540Sstevel@tonic-gate  */
1550Sstevel@tonic-gate /*ARGSUSED*/
1560Sstevel@tonic-gate int
main(int argc,char * argv[])1570Sstevel@tonic-gate main(int argc, char *argv[])
1580Sstevel@tonic-gate {
1590Sstevel@tonic-gate 	struct opts *clopts;		/* from parsing command line */
1600Sstevel@tonic-gate 	const char *conffile;		/* our configuration file */
161*12986SJohn.Zolnowsky@Sun.COM 	const char *timestamps;		/* our timestamps file */
1620Sstevel@tonic-gate 	struct fn_list *lognames;	/* list of lognames we're processing */
1630Sstevel@tonic-gate 	struct fn *fnp;
1640Sstevel@tonic-gate 	char *val;
1652397Sbasabi 	char *buf;
166*12986SJohn.Zolnowsky@Sun.COM 	int status;
1670Sstevel@tonic-gate 
1680Sstevel@tonic-gate 	(void) setlocale(LC_ALL, "");
1690Sstevel@tonic-gate 
1700Sstevel@tonic-gate #if !defined(TEXT_DOMAIN)
1710Sstevel@tonic-gate #define	TEXT_DOMAIN "SYS_TEST"	/* only used if Makefiles don't define it */
1720Sstevel@tonic-gate #endif
1730Sstevel@tonic-gate 
1740Sstevel@tonic-gate 	(void) textdomain(TEXT_DOMAIN);
1750Sstevel@tonic-gate 
176*12986SJohn.Zolnowsky@Sun.COM 	/* we only print times into the timestamps file, so make them uniform */
1770Sstevel@tonic-gate 	(void) setlocale(LC_TIME, "C");
1780Sstevel@tonic-gate 
1790Sstevel@tonic-gate 	/* give our name to error routines & skip it for arg parsing */
1800Sstevel@tonic-gate 	err_init(*argv++);
1810Sstevel@tonic-gate 	(void) setlinebuf(stdout);
1820Sstevel@tonic-gate 
1830Sstevel@tonic-gate 	if (putenv("PATH=/bin"))
1840Sstevel@tonic-gate 		err(EF_SYS, "putenv PATH");
1850Sstevel@tonic-gate 	if (putenv("TZ=UTC"))
1860Sstevel@tonic-gate 		err(EF_SYS, "putenv TZ");
1870Sstevel@tonic-gate 	tzset();
1880Sstevel@tonic-gate 
1890Sstevel@tonic-gate 	(void) umask(0);
1900Sstevel@tonic-gate 
1910Sstevel@tonic-gate 	Now = time(0);
1920Sstevel@tonic-gate 
1930Sstevel@tonic-gate 	/* check for (undocumented) debugging environment variables */
1940Sstevel@tonic-gate 	if (val = getenv("_LOGADM_DEFAULT_CONFFILE"))
1950Sstevel@tonic-gate 		Default_conffile = val;
196*12986SJohn.Zolnowsky@Sun.COM 	if (val = getenv("_LOGADM_DEFAULT_TIMESTAMPS"))
197*12986SJohn.Zolnowsky@Sun.COM 		Default_timestamps = val;
1980Sstevel@tonic-gate 	if (val = getenv("_LOGADM_DEBUG"))
1990Sstevel@tonic-gate 		Debug = atoi(val);
2000Sstevel@tonic-gate 	if (val = getenv("_LOGADM_SH"))
2010Sstevel@tonic-gate 		Sh = val;
2020Sstevel@tonic-gate 	if (val = getenv("_LOGADM_MV"))
2030Sstevel@tonic-gate 		Mv = val;
2040Sstevel@tonic-gate 	if (val = getenv("_LOGADM_RM"))
2050Sstevel@tonic-gate 		Rm = val;
2060Sstevel@tonic-gate 	if (val = getenv("_LOGADM_TOUCH"))
2070Sstevel@tonic-gate 		Touch = val;
2080Sstevel@tonic-gate 	if (val = getenv("_LOGADM_CHMOD"))
2090Sstevel@tonic-gate 		Chmod = val;
2100Sstevel@tonic-gate 	if (val = getenv("_LOGADM_CHOWN"))
2110Sstevel@tonic-gate 		Chown = val;
2120Sstevel@tonic-gate 	if (val = getenv("_LOGADM_GZIP"))
2130Sstevel@tonic-gate 		Gzip = val;
2140Sstevel@tonic-gate 	if (val = getenv("_LOGADM_MKDIR"))
2150Sstevel@tonic-gate 		Mkdir = val;
2160Sstevel@tonic-gate 
217*12986SJohn.Zolnowsky@Sun.COM 	opts_init(Opttable, Opttable_cnt);
2180Sstevel@tonic-gate 
2190Sstevel@tonic-gate 	/* parse command line arguments */
2200Sstevel@tonic-gate 	if (SETJMP)
2210Sstevel@tonic-gate 		usage("bailing out due to command line errors");
2220Sstevel@tonic-gate 	else
223*12986SJohn.Zolnowsky@Sun.COM 		clopts = opts_parse(NULL, argv, OPTF_CLI);
2240Sstevel@tonic-gate 
2250Sstevel@tonic-gate 	if (Debug) {
2260Sstevel@tonic-gate 		(void) fprintf(stderr, "command line opts:");
2270Sstevel@tonic-gate 		opts_print(clopts, stderr, NULL);
2280Sstevel@tonic-gate 		(void) fprintf(stderr, "\n");
2290Sstevel@tonic-gate 	}
2300Sstevel@tonic-gate 
2310Sstevel@tonic-gate 	/*
2320Sstevel@tonic-gate 	 * There are many moods of logadm:
2330Sstevel@tonic-gate 	 *
2340Sstevel@tonic-gate 	 *	1. "-h" for help was given.  We spew a canned help
2350Sstevel@tonic-gate 	 *	   message and exit, regardless of any other options given.
2360Sstevel@tonic-gate 	 *
2370Sstevel@tonic-gate 	 *	2. "-r" or "-w" asking us to write to the conffile.  Lots
2380Sstevel@tonic-gate 	 *	   of argument checking, then we make the change to conffile
2390Sstevel@tonic-gate 	 *	   and exit.  (-r processing actually happens in dologname().)
2400Sstevel@tonic-gate 	 *
2410Sstevel@tonic-gate 	 *	3. "-V" to search/verify the conffile was given.  We do
2420Sstevel@tonic-gate 	 *	   the appropriate run through the conffile and exit.
2430Sstevel@tonic-gate 	 *	   (-V processing actually happens in dologname().)
2440Sstevel@tonic-gate 	 *
2450Sstevel@tonic-gate 	 *	4. No lognames were given, so we're being asked to go through
2460Sstevel@tonic-gate 	 *	   every entry in conffile.  We verify that only the options
2470Sstevel@tonic-gate 	 *	   that make sense for this form of the command are present
2480Sstevel@tonic-gate 	 *	   and fall into the main processing loop below.
2490Sstevel@tonic-gate 	 *
2500Sstevel@tonic-gate 	 *	5. lognames were given, so we fall into the main processing
2510Sstevel@tonic-gate 	 *	   loop below to work our way through them.
2520Sstevel@tonic-gate 	 *
2530Sstevel@tonic-gate 	 * The last two cases are where the option processing gets more
2540Sstevel@tonic-gate 	 * complex.  Each time around the main processing loop, we're
2550Sstevel@tonic-gate 	 * in one of these cases:
2560Sstevel@tonic-gate 	 *
2570Sstevel@tonic-gate 	 *	A. No cmdargs were found (we're in case 4), the entry
2580Sstevel@tonic-gate 	 *	   in conffile supplies no log file names, so the entry
2590Sstevel@tonic-gate 	 *	   name itself is the logfile name (or names, if it globs
2600Sstevel@tonic-gate 	 *	   to multiple file names).
2610Sstevel@tonic-gate 	 *
2620Sstevel@tonic-gate 	 *	B. No cmdargs were found (we're in case 4), the entry
2630Sstevel@tonic-gate 	 *	   in conffile gives log file names that we then loop
2640Sstevel@tonic-gate 	 *	   through and rotate/expire.  In this case, the entry
2650Sstevel@tonic-gate 	 *	   name is specifically NOT one of the log file names.
2660Sstevel@tonic-gate 	 *
2670Sstevel@tonic-gate 	 *	C. We're going through the cmdargs (we're in case 5),
2680Sstevel@tonic-gate 	 *	   the entry in conffile either doesn't exist or it exists
2690Sstevel@tonic-gate 	 *	   but supplies no log file names, so the cmdarg itself
2700Sstevel@tonic-gate 	 *	   is the log file name.
2710Sstevel@tonic-gate 	 *
2720Sstevel@tonic-gate 	 *	D. We're going through the cmdargs (we're in case 5),
2730Sstevel@tonic-gate 	 *	   a matching entry in conffile supplies log file names
2740Sstevel@tonic-gate 	 *	   that we then loop through and rotate/expire.  In this
2750Sstevel@tonic-gate 	 *	   case the entry name is specifically NOT one of the log
2760Sstevel@tonic-gate 	 *	   file names.
2770Sstevel@tonic-gate 	 *
2780Sstevel@tonic-gate 	 * As we're doing all this, any options given on the command line
2790Sstevel@tonic-gate 	 * override any found in the conffile, and we apply the defaults
2800Sstevel@tonic-gate 	 * for rotation conditions and expiration conditions, etc. at the
2810Sstevel@tonic-gate 	 * last opportunity, when we're sure they haven't been overridden
2820Sstevel@tonic-gate 	 * by an option somewhere along the way.
2830Sstevel@tonic-gate 	 *
2840Sstevel@tonic-gate 	 */
2850Sstevel@tonic-gate 
2860Sstevel@tonic-gate 	/* help option overrides anything else */
2870Sstevel@tonic-gate 	if (opts_count(clopts, "h")) {
2880Sstevel@tonic-gate 		(void) fputs(HELP1, stderr);
2890Sstevel@tonic-gate 		(void) fputs(HELP2, stderr);
2900Sstevel@tonic-gate 		err_done(0);
2910Sstevel@tonic-gate 		/*NOTREACHED*/
2920Sstevel@tonic-gate 	}
2930Sstevel@tonic-gate 
2940Sstevel@tonic-gate 	/* detect illegal option combinations */
2950Sstevel@tonic-gate 	if (opts_count(clopts, "rwV") > 1)
2960Sstevel@tonic-gate 		usage("Only one of -r, -w, or -V may be used at a time.");
2970Sstevel@tonic-gate 	if (opts_count(clopts, "cM") > 1)
2980Sstevel@tonic-gate 		usage("Only one of -c or -M may be used at a time.");
2990Sstevel@tonic-gate 
3000Sstevel@tonic-gate 	/* arrange for error output to be mailed if clopts includes -e */
3010Sstevel@tonic-gate 	if (opts_count(clopts, "e"))
3020Sstevel@tonic-gate 		err_mailto(opts_optarg(clopts, "e"));
3030Sstevel@tonic-gate 
304*12986SJohn.Zolnowsky@Sun.COM 	/* this implements the default conffile and timestamps */
3050Sstevel@tonic-gate 	if ((conffile = opts_optarg(clopts, "f")) == NULL)
3060Sstevel@tonic-gate 		conffile = Default_conffile;
307*12986SJohn.Zolnowsky@Sun.COM 	if ((timestamps = opts_optarg(clopts, "F")) == NULL)
308*12986SJohn.Zolnowsky@Sun.COM 		timestamps = Default_timestamps;
3090Sstevel@tonic-gate 	if (opts_count(clopts, "v"))
3100Sstevel@tonic-gate 		(void) out("# loading %s\n", conffile);
311*12986SJohn.Zolnowsky@Sun.COM 	status = conf_open(conffile, timestamps, clopts);
312*12986SJohn.Zolnowsky@Sun.COM 	if (!status && opts_count(clopts, "V"))
313*12986SJohn.Zolnowsky@Sun.COM 		err_done(0);
3140Sstevel@tonic-gate 
3150Sstevel@tonic-gate 	/* handle conffile write option */
3160Sstevel@tonic-gate 	if (opts_count(clopts, "w")) {
3170Sstevel@tonic-gate 		if (Debug)
3180Sstevel@tonic-gate 			(void) fprintf(stderr,
3190Sstevel@tonic-gate 			    "main: add/replace conffile entry: <%s>\n",
3200Sstevel@tonic-gate 			    opts_optarg(clopts, "w"));
3210Sstevel@tonic-gate 		conf_replace(opts_optarg(clopts, "w"), clopts);
3220Sstevel@tonic-gate 		conf_close(clopts);
3230Sstevel@tonic-gate 		err_done(0);
3240Sstevel@tonic-gate 		/*NOTREACHED*/
3250Sstevel@tonic-gate 	}
3260Sstevel@tonic-gate 
3270Sstevel@tonic-gate 	/*
3280Sstevel@tonic-gate 	 * lognames is either a list supplied on the command line,
3290Sstevel@tonic-gate 	 * or every entry in the conffile if none were supplied.
3300Sstevel@tonic-gate 	 */
3310Sstevel@tonic-gate 	lognames = opts_cmdargs(clopts);
3320Sstevel@tonic-gate 	if (fn_list_empty(lognames)) {
3330Sstevel@tonic-gate 		/*
3340Sstevel@tonic-gate 		 * being asked to do all entries in conffile
3350Sstevel@tonic-gate 		 *
3360Sstevel@tonic-gate 		 * check to see if any options were given that only
3370Sstevel@tonic-gate 		 * make sense when lognames are given specifically
3380Sstevel@tonic-gate 		 * on the command line.
3390Sstevel@tonic-gate 		 */
3400Sstevel@tonic-gate 		if (opts_count(clopts, OPTIONS_NOT_FIRST_FORM))
3410Sstevel@tonic-gate 			usage("some options require logname argument");
3420Sstevel@tonic-gate 		if (Debug)
3430Sstevel@tonic-gate 			(void) fprintf(stderr,
3440Sstevel@tonic-gate 			    "main: run all entries in conffile\n");
3450Sstevel@tonic-gate 		lognames = conf_entries();
3460Sstevel@tonic-gate 	}
3470Sstevel@tonic-gate 
3480Sstevel@tonic-gate 	/* foreach logname... */
3490Sstevel@tonic-gate 	fn_list_rewind(lognames);
3500Sstevel@tonic-gate 	while ((fnp = fn_list_next(lognames)) != NULL) {
3512397Sbasabi 		buf = fn_s(fnp);
3522397Sbasabi 		if (buf != NULL && lut_lookup(Donenames, buf) != NULL) {
3530Sstevel@tonic-gate 			if (Debug)
3540Sstevel@tonic-gate 				(void) fprintf(stderr,
3550Sstevel@tonic-gate 				    "main: logname already done: <%s>\n",
3562397Sbasabi 				    buf);
3570Sstevel@tonic-gate 			continue;
3580Sstevel@tonic-gate 		}
3592397Sbasabi 		if (buf != NULL && SETJMP)
3600Sstevel@tonic-gate 			err(EF_FILE, "bailing out on logname \"%s\" "
3612397Sbasabi 			    "due to errors", buf);
3620Sstevel@tonic-gate 		else
3630Sstevel@tonic-gate 			dologname(fnp, clopts);
3640Sstevel@tonic-gate 	}
3650Sstevel@tonic-gate 
3660Sstevel@tonic-gate 	/* execute any after commands */
3670Sstevel@tonic-gate 	lut_walk(Aftercmds, doaftercmd, clopts);
3680Sstevel@tonic-gate 
3691913Sdg199075 	/* execute any gzip commands */
3701913Sdg199075 	lut_walk(Gzipnames, do_delayed_gzip, clopts);
3711913Sdg199075 
3720Sstevel@tonic-gate 	/* write out any conffile changes */
3730Sstevel@tonic-gate 	conf_close(clopts);
3740Sstevel@tonic-gate 
3750Sstevel@tonic-gate 	err_done(0);
3760Sstevel@tonic-gate 	/*NOTREACHED*/
3770Sstevel@tonic-gate 	return (0);	/* for lint's little mind */
3780Sstevel@tonic-gate }
3790Sstevel@tonic-gate 
3800Sstevel@tonic-gate /* spew a message, then a usage message, then exit */
3810Sstevel@tonic-gate static void
usage(const char * msg)3820Sstevel@tonic-gate usage(const char *msg)
3830Sstevel@tonic-gate {
3840Sstevel@tonic-gate 	if (msg)
3850Sstevel@tonic-gate 		err(0, "%s\nUse \"logadm -h\" for help.", msg);
3860Sstevel@tonic-gate 	else
3870Sstevel@tonic-gate 		err(EF_RAW, "Use \"logadm -h\" for help.\n");
3880Sstevel@tonic-gate }
3890Sstevel@tonic-gate 
3900Sstevel@tonic-gate /* helper function used by doaftercmd() to join mail addrs with commas */
3910Sstevel@tonic-gate /*ARGSUSED1*/
3920Sstevel@tonic-gate static void
commajoin(const char * lhs,void * rhs,void * arg)3930Sstevel@tonic-gate commajoin(const char *lhs, void *rhs, void *arg)
3940Sstevel@tonic-gate {
3950Sstevel@tonic-gate 	struct fn *fnp = (struct fn *)arg;
3962397Sbasabi 	char *buf;
3970Sstevel@tonic-gate 
3982397Sbasabi 	buf = fn_s(fnp);
3992397Sbasabi 	if (buf != NULL && *buf)
4000Sstevel@tonic-gate 		fn_putc(fnp, ',');
4010Sstevel@tonic-gate 	fn_puts(fnp, lhs);
4020Sstevel@tonic-gate }
4030Sstevel@tonic-gate 
4040Sstevel@tonic-gate /* helper function used by main() to run "after" commands */
4050Sstevel@tonic-gate static void
doaftercmd(const char * lhs,void * rhs,void * arg)4060Sstevel@tonic-gate doaftercmd(const char *lhs, void *rhs, void *arg)
4070Sstevel@tonic-gate {
4080Sstevel@tonic-gate 	struct opts *opts = (struct opts *)arg;
4090Sstevel@tonic-gate 	struct lut *addrs = (struct lut *)rhs;
4100Sstevel@tonic-gate 
4110Sstevel@tonic-gate 	if (addrs) {
4120Sstevel@tonic-gate 		struct fn *fnp = fn_new(NULL);
4130Sstevel@tonic-gate 
4140Sstevel@tonic-gate 		/*
4150Sstevel@tonic-gate 		 * addrs contains list of email addrs that should get
4160Sstevel@tonic-gate 		 * the error output when this after command is executed.
4170Sstevel@tonic-gate 		 */
4180Sstevel@tonic-gate 		lut_walk(addrs, commajoin, fnp);
4190Sstevel@tonic-gate 		err_mailto(fn_s(fnp));
4200Sstevel@tonic-gate 	}
4210Sstevel@tonic-gate 
4220Sstevel@tonic-gate 	docmd(opts, "-a cmd", Sh, "-c", lhs, NULL);
4230Sstevel@tonic-gate }
4240Sstevel@tonic-gate 
4251913Sdg199075 /* perform delayed gzip */
4261913Sdg199075 
4271913Sdg199075 static void
do_delayed_gzip(const char * lhs,void * rhs,void * arg)4281913Sdg199075 do_delayed_gzip(const char *lhs, void *rhs, void *arg)
4291913Sdg199075 {
4301913Sdg199075 	struct opts *opts = (struct opts *)arg;
4311913Sdg199075 
4321913Sdg199075 	if (rhs == NULL) {
4331913Sdg199075 		if (Debug) {
4341913Sdg199075 			(void) fprintf(stderr, "do_delayed_gzip: not gzipping "
43511829SPetr.Sumbera@Sun.COM 			    "expired file <%s>\n", lhs);
4361913Sdg199075 		}
4371913Sdg199075 		return;
4381913Sdg199075 	}
4391913Sdg199075 	docmd(opts, "compress old log (-z flag)", Gzip, "-f", lhs, NULL);
4401913Sdg199075 }
4411913Sdg199075 
4421913Sdg199075 
4430Sstevel@tonic-gate /* main logname processing */
4440Sstevel@tonic-gate static void
dologname(struct fn * fnp,struct opts * clopts)4450Sstevel@tonic-gate dologname(struct fn *fnp, struct opts *clopts)
4460Sstevel@tonic-gate {
4470Sstevel@tonic-gate 	const char *logname = fn_s(fnp);
4480Sstevel@tonic-gate 	struct opts *cfopts;
4490Sstevel@tonic-gate 	struct opts *allopts;
4500Sstevel@tonic-gate 	struct fn_list *logfiles;
4510Sstevel@tonic-gate 	struct fn_list *globbedfiles;
4520Sstevel@tonic-gate 	struct fn *nextfnp;
4530Sstevel@tonic-gate 
4540Sstevel@tonic-gate 	/* look up options set by config file */
4550Sstevel@tonic-gate 	cfopts = conf_opts(logname);
4560Sstevel@tonic-gate 
4570Sstevel@tonic-gate 	if (opts_count(clopts, "v"))
4580Sstevel@tonic-gate 		(void) out("# processing logname: %s\n", logname);
4590Sstevel@tonic-gate 
4600Sstevel@tonic-gate 	if (Debug) {
4612397Sbasabi 		if (logname != NULL)
4622397Sbasabi 			(void) fprintf(stderr, "dologname: logname <%s>\n",
4632397Sbasabi 			    logname);
4640Sstevel@tonic-gate 		(void) fprintf(stderr, "conffile opts:");
4650Sstevel@tonic-gate 		opts_print(cfopts, stderr, NULL);
4660Sstevel@tonic-gate 		(void) fprintf(stderr, "\n");
4670Sstevel@tonic-gate 	}
4680Sstevel@tonic-gate 
4690Sstevel@tonic-gate 	/* handle conffile lookup option */
4700Sstevel@tonic-gate 	if (opts_count(clopts, "V")) {
4710Sstevel@tonic-gate 		/* lookup an entry in conffile */
4720Sstevel@tonic-gate 		if (Debug)
4730Sstevel@tonic-gate 			(void) fprintf(stderr,
4740Sstevel@tonic-gate 			    "dologname: lookup conffile entry\n");
4750Sstevel@tonic-gate 		if (conf_lookup(logname)) {
4760Sstevel@tonic-gate 			opts_printword(logname, stdout);
4770Sstevel@tonic-gate 			opts_print(cfopts, stdout, NULL);
4780Sstevel@tonic-gate 			(void) out("\n");
4790Sstevel@tonic-gate 		} else
4800Sstevel@tonic-gate 			err_exitcode(1);
4810Sstevel@tonic-gate 		return;
4820Sstevel@tonic-gate 	}
4830Sstevel@tonic-gate 
4840Sstevel@tonic-gate 	/* handle conffile removal option */
4850Sstevel@tonic-gate 	if (opts_count(clopts, "r")) {
4860Sstevel@tonic-gate 		if (Debug)
4870Sstevel@tonic-gate 			(void) fprintf(stderr,
4880Sstevel@tonic-gate 			    "dologname: remove conffile entry\n");
4890Sstevel@tonic-gate 		if (conf_lookup(logname))
4900Sstevel@tonic-gate 			conf_replace(logname, NULL);
4910Sstevel@tonic-gate 		else
4920Sstevel@tonic-gate 			err_exitcode(1);
4930Sstevel@tonic-gate 		return;
4940Sstevel@tonic-gate 	}
4950Sstevel@tonic-gate 
4960Sstevel@tonic-gate 	/* generate combined options */
4970Sstevel@tonic-gate 	allopts = opts_merge(cfopts, clopts);
4980Sstevel@tonic-gate 
4990Sstevel@tonic-gate 	/* arrange for error output to be mailed if allopts includes -e */
5000Sstevel@tonic-gate 	if (opts_count(allopts, "e"))
5010Sstevel@tonic-gate 		err_mailto(opts_optarg(allopts, "e"));
5020Sstevel@tonic-gate 	else
5030Sstevel@tonic-gate 		err_mailto(NULL);
5040Sstevel@tonic-gate 
5050Sstevel@tonic-gate 	/* this implements the default rotation rules */
5060Sstevel@tonic-gate 	if (opts_count(allopts, "sp") == 0) {
5070Sstevel@tonic-gate 		if (opts_count(clopts, "v"))
5080Sstevel@tonic-gate 			(void) out(
5090Sstevel@tonic-gate 			    "#     using default rotate rules: -s1b -p1w\n");
5100Sstevel@tonic-gate 		(void) opts_set(allopts, "s", "1b");
5110Sstevel@tonic-gate 		(void) opts_set(allopts, "p", "1w");
5120Sstevel@tonic-gate 	}
5130Sstevel@tonic-gate 
5140Sstevel@tonic-gate 	/* this implements the default expiration rules */
5150Sstevel@tonic-gate 	if (opts_count(allopts, "ACS") == 0) {
5160Sstevel@tonic-gate 		if (opts_count(clopts, "v"))
5170Sstevel@tonic-gate 			(void) out("#     using default expire rule: -C10\n");
5180Sstevel@tonic-gate 		(void) opts_set(allopts, "C", "10");
5190Sstevel@tonic-gate 	}
5200Sstevel@tonic-gate 
5210Sstevel@tonic-gate 	/* this implements the default template */
5220Sstevel@tonic-gate 	if (opts_count(allopts, "t") == 0) {
5230Sstevel@tonic-gate 		if (opts_count(clopts, "v"))
5240Sstevel@tonic-gate 			(void) out("#     using default template: $file.$n\n");
5250Sstevel@tonic-gate 		(void) opts_set(allopts, "t", "$file.$n");
5260Sstevel@tonic-gate 	}
5270Sstevel@tonic-gate 
5280Sstevel@tonic-gate 	if (Debug) {
5290Sstevel@tonic-gate 		(void) fprintf(stderr, "merged opts:");
5300Sstevel@tonic-gate 		opts_print(allopts, stderr, NULL);
5310Sstevel@tonic-gate 		(void) fprintf(stderr, "\n");
5320Sstevel@tonic-gate 	}
5330Sstevel@tonic-gate 
5340Sstevel@tonic-gate 	/*
5350Sstevel@tonic-gate 	 * if the conffile entry supplied log file names, then
5360Sstevel@tonic-gate 	 * logname is NOT one of the log file names (it was just
5370Sstevel@tonic-gate 	 * the entry name in conffile).
5380Sstevel@tonic-gate 	 */
5390Sstevel@tonic-gate 	logfiles = opts_cmdargs(cfopts);
5400Sstevel@tonic-gate 	if (Debug) {
5412397Sbasabi 		char *buf;
5420Sstevel@tonic-gate 		(void) fprintf(stderr, "dologname: logfiles from cfopts:\n");
5430Sstevel@tonic-gate 		fn_list_rewind(logfiles);
5440Sstevel@tonic-gate 		while ((nextfnp = fn_list_next(logfiles)) != NULL)
5452397Sbasabi 			buf = fn_s(nextfnp);
5462397Sbasabi 			if (buf != NULL)
5472397Sbasabi 				(void) fprintf(stderr, "    <%s>\n", buf);
5480Sstevel@tonic-gate 	}
5490Sstevel@tonic-gate 	if (fn_list_empty(logfiles))
5500Sstevel@tonic-gate 		globbedfiles = glob_glob(fnp);
5510Sstevel@tonic-gate 	else
5520Sstevel@tonic-gate 		globbedfiles = glob_glob_list(logfiles);
5530Sstevel@tonic-gate 
5540Sstevel@tonic-gate 	/* go through the list produced by glob expansion */
5550Sstevel@tonic-gate 	fn_list_rewind(globbedfiles);
5560Sstevel@tonic-gate 	while ((nextfnp = fn_list_next(globbedfiles)) != NULL)
5570Sstevel@tonic-gate 		if (rotatelog(nextfnp, allopts))
5580Sstevel@tonic-gate 			expirefiles(nextfnp, allopts);
5590Sstevel@tonic-gate 
5600Sstevel@tonic-gate 	fn_list_free(globbedfiles);
5610Sstevel@tonic-gate 	opts_free(allopts);
5620Sstevel@tonic-gate }
5630Sstevel@tonic-gate 
5640Sstevel@tonic-gate 
5650Sstevel@tonic-gate /* absurdly long buffer lengths for holding user/group/mode strings */
5660Sstevel@tonic-gate #define	TIMESTRMAX	100
5670Sstevel@tonic-gate #define	MAXATTR		100
5680Sstevel@tonic-gate 
5690Sstevel@tonic-gate /* rotate a log file if necessary, returns true if ok to go on to expire step */
5700Sstevel@tonic-gate static boolean_t
rotatelog(struct fn * fnp,struct opts * opts)5710Sstevel@tonic-gate rotatelog(struct fn *fnp, struct opts *opts)
5720Sstevel@tonic-gate {
5730Sstevel@tonic-gate 	char *fname = fn_s(fnp);
5740Sstevel@tonic-gate 	struct stat stbuf;
5750Sstevel@tonic-gate 	char nowstr[TIMESTRMAX];
5760Sstevel@tonic-gate 	struct fn *recentlog = fn_new(NULL);	/* for -R cmd */
5770Sstevel@tonic-gate 	char ownerbuf[MAXATTR];
5780Sstevel@tonic-gate 	char groupbuf[MAXATTR];
5790Sstevel@tonic-gate 	char modebuf[MAXATTR];
5800Sstevel@tonic-gate 	const char *owner;
5810Sstevel@tonic-gate 	const char *group;
5820Sstevel@tonic-gate 	const char *mode;
5830Sstevel@tonic-gate 
5842397Sbasabi 	if (Debug && fname != NULL)
5850Sstevel@tonic-gate 		(void) fprintf(stderr, "rotatelog: fname <%s>\n", fname);
5860Sstevel@tonic-gate 
5870Sstevel@tonic-gate 	if (opts_count(opts, "p") && opts_optarg_int(opts, "p") == OPTP_NEVER)
5880Sstevel@tonic-gate 		return (B_TRUE);	/* "-p never" forced no rotate */
5890Sstevel@tonic-gate 
5900Sstevel@tonic-gate 	/* prepare the keywords */
5910Sstevel@tonic-gate 	kw_init(fnp, NULL);
5920Sstevel@tonic-gate 	if (Debug > 1) {
5930Sstevel@tonic-gate 		(void) fprintf(stderr, "rotatelog keywords:\n");
5940Sstevel@tonic-gate 		kw_print(stderr);
5950Sstevel@tonic-gate 	}
5960Sstevel@tonic-gate 
5970Sstevel@tonic-gate 	if (lstat(fname, &stbuf) < 0) {
5980Sstevel@tonic-gate 		if (opts_count(opts, "N"))
5990Sstevel@tonic-gate 			return (1);
6000Sstevel@tonic-gate 		err(EF_WARN|EF_SYS, "%s", fname);
6010Sstevel@tonic-gate 		return (B_FALSE);
6020Sstevel@tonic-gate 	}
6030Sstevel@tonic-gate 
6040Sstevel@tonic-gate 	if ((stbuf.st_mode & S_IFMT) == S_IFLNK) {
6050Sstevel@tonic-gate 		err(EF_WARN, "%s is a symlink", fname);
6060Sstevel@tonic-gate 		return (B_FALSE);
6070Sstevel@tonic-gate 	}
6080Sstevel@tonic-gate 
6090Sstevel@tonic-gate 	if ((stbuf.st_mode & S_IFMT) != S_IFREG) {
6100Sstevel@tonic-gate 		err(EF_WARN, "%s is not a regular file", fname);
6110Sstevel@tonic-gate 		return (B_FALSE);
6120Sstevel@tonic-gate 	}
6130Sstevel@tonic-gate 
6144412Smh138676 	/* even if size condition is not met, this entry is "done" */
6154412Smh138676 	if (opts_count(opts, "s") &&
6164412Smh138676 	    stbuf.st_size < opts_optarg_int(opts, "s")) {
6174412Smh138676 		Donenames = lut_add(Donenames, fname, "1");
6180Sstevel@tonic-gate 		return (B_TRUE);
6194412Smh138676 	}
6200Sstevel@tonic-gate 
6210Sstevel@tonic-gate 	/* see if age condition is present, and return if not met */
6220Sstevel@tonic-gate 	if (opts_count(opts, "p")) {
6232016Sbasabi 		off_t when = opts_optarg_int(opts, "p");
6240Sstevel@tonic-gate 		struct opts *cfopts;
6250Sstevel@tonic-gate 
6260Sstevel@tonic-gate 		/* unless rotate forced by "-p now", see if period has passed */
6270Sstevel@tonic-gate 		if (when != OPTP_NOW) {
6280Sstevel@tonic-gate 			/*
6290Sstevel@tonic-gate 			 * "when" holds the number of seconds that must have
6300Sstevel@tonic-gate 			 * passed since the last time this log was rotated.
6310Sstevel@tonic-gate 			 * of course, running logadm can take a little time
6320Sstevel@tonic-gate 			 * (typically a second or two, but longer if the
6330Sstevel@tonic-gate 			 * conffile has lots of stuff in it) and that amount
6340Sstevel@tonic-gate 			 * of time is variable, depending on system load, etc.
6350Sstevel@tonic-gate 			 * so we want to allow a little "slop" in the value of
6360Sstevel@tonic-gate 			 * "when".  this way, if a log should be rotated every
6370Sstevel@tonic-gate 			 * week, and the number of seconds passed is really a
6380Sstevel@tonic-gate 			 * few seconds short of a week, we'll go ahead and
6390Sstevel@tonic-gate 			 * rotate the log as expected.
6400Sstevel@tonic-gate 			 *
6410Sstevel@tonic-gate 			 */
6420Sstevel@tonic-gate 			if (when >= 60 * 60)
6430Sstevel@tonic-gate 				when -= 59;
6440Sstevel@tonic-gate 
6450Sstevel@tonic-gate 			/*
6460Sstevel@tonic-gate 			 * last rotation is recorded as argument to -P,
6470Sstevel@tonic-gate 			 * but if logname isn't the same as log file name
6480Sstevel@tonic-gate 			 * then the timestamp would be recorded on a
6490Sstevel@tonic-gate 			 * separate line in the conf file.  so if we
6500Sstevel@tonic-gate 			 * haven't seen a -P already, we check to see if
6510Sstevel@tonic-gate 			 * it is part of a specific entry for the log
6520Sstevel@tonic-gate 			 * file name.  this handles the case where the
6530Sstevel@tonic-gate 			 * logname is "apache", it supplies a log file
6540Sstevel@tonic-gate 			 * name like "/var/apache/logs/[a-z]*_log",
6550Sstevel@tonic-gate 			 * which expands to multiple file names.  if one
6560Sstevel@tonic-gate 			 * of the file names is "/var/apache/logs/access_log"
6570Sstevel@tonic-gate 			 * the the -P will be attached to a line with that
6580Sstevel@tonic-gate 			 * logname in the conf file.
6590Sstevel@tonic-gate 			 */
6600Sstevel@tonic-gate 			if (opts_count(opts, "P")) {
6612016Sbasabi 				off_t last = opts_optarg_int(opts, "P");
6620Sstevel@tonic-gate 
6630Sstevel@tonic-gate 				/* return if not enough time has passed */
6640Sstevel@tonic-gate 				if (Now - last < when)
6650Sstevel@tonic-gate 					return (B_TRUE);
6660Sstevel@tonic-gate 			} else if ((cfopts = conf_opts(fname)) != NULL &&
6670Sstevel@tonic-gate 			    opts_count(cfopts, "P")) {
6682016Sbasabi 				off_t last = opts_optarg_int(cfopts, "P");
6690Sstevel@tonic-gate 
6700Sstevel@tonic-gate 				/*
6710Sstevel@tonic-gate 				 * just checking this means this entry
6720Sstevel@tonic-gate 				 * is now "done" if we're going through
6730Sstevel@tonic-gate 				 * the entire conffile
6740Sstevel@tonic-gate 				 */
6750Sstevel@tonic-gate 				Donenames = lut_add(Donenames, fname, "1");
6760Sstevel@tonic-gate 
6770Sstevel@tonic-gate 				/* return if not enough time has passed */
6780Sstevel@tonic-gate 				if (Now - last < when)
6790Sstevel@tonic-gate 					return (B_TRUE);
6800Sstevel@tonic-gate 			}
6810Sstevel@tonic-gate 		}
6820Sstevel@tonic-gate 	}
6830Sstevel@tonic-gate 
6840Sstevel@tonic-gate 	if (Debug)
6850Sstevel@tonic-gate 		(void) fprintf(stderr, "rotatelog: conditions met\n");
686954Sgm149974 	if (opts_count(opts, "l")) {
687954Sgm149974 		/* Change the time zone to local time zone */
688954Sgm149974 		if (putenv("TZ="))
689954Sgm149974 			err(EF_SYS, "putenv TZ");
690954Sgm149974 		tzset();
691954Sgm149974 		Now = time(0);
6920Sstevel@tonic-gate 
693954Sgm149974 		/* rename the log file */
694954Sgm149974 		rotateto(fnp, opts, 0, recentlog, B_FALSE);
695954Sgm149974 
696954Sgm149974 		/* Change the time zone to UTC */
697954Sgm149974 		if (putenv("TZ=UTC"))
698954Sgm149974 			err(EF_SYS, "putenv TZ");
699954Sgm149974 		tzset();
700954Sgm149974 		Now = time(0);
701954Sgm149974 	} else {
702954Sgm149974 		/* rename the log file */
703954Sgm149974 		rotateto(fnp, opts, 0, recentlog, B_FALSE);
704954Sgm149974 	}
7050Sstevel@tonic-gate 
7060Sstevel@tonic-gate 	/* determine owner, group, mode for empty log file */
7070Sstevel@tonic-gate 	if (opts_count(opts, "o"))
7080Sstevel@tonic-gate 		(void) strlcpy(ownerbuf, opts_optarg(opts, "o"), MAXATTR);
7090Sstevel@tonic-gate 	else {
7100Sstevel@tonic-gate 		(void) snprintf(ownerbuf, MAXATTR, "%ld", stbuf.st_uid);
7110Sstevel@tonic-gate 	}
7120Sstevel@tonic-gate 	owner = ownerbuf;
7130Sstevel@tonic-gate 	if (opts_count(opts, "g"))
7140Sstevel@tonic-gate 		group = opts_optarg(opts, "g");
7150Sstevel@tonic-gate 	else {
7160Sstevel@tonic-gate 		(void) snprintf(groupbuf, MAXATTR, "%ld", stbuf.st_gid);
7170Sstevel@tonic-gate 		group = groupbuf;
7180Sstevel@tonic-gate 	}
7190Sstevel@tonic-gate 	(void) strlcat(ownerbuf, ":", MAXATTR - strlen(ownerbuf));
7200Sstevel@tonic-gate 	(void) strlcat(ownerbuf, group, MAXATTR - strlen(ownerbuf));
7210Sstevel@tonic-gate 	if (opts_count(opts, "m"))
7220Sstevel@tonic-gate 		mode = opts_optarg(opts, "m");
7230Sstevel@tonic-gate 	else {
7240Sstevel@tonic-gate 		(void) snprintf(modebuf, MAXATTR,
7250Sstevel@tonic-gate 		    "%03lo", stbuf.st_mode & 0777);
7260Sstevel@tonic-gate 		mode = modebuf;
7270Sstevel@tonic-gate 	}
7280Sstevel@tonic-gate 
7290Sstevel@tonic-gate 	/* create the empty log file */
7300Sstevel@tonic-gate 	docmd(opts, NULL, Touch, fname, NULL, NULL);
7310Sstevel@tonic-gate 	docmd(opts, NULL, Chown, owner, fname, NULL);
7320Sstevel@tonic-gate 	docmd(opts, NULL, Chmod, mode, fname, NULL);
7330Sstevel@tonic-gate 
7340Sstevel@tonic-gate 	/* execute post-rotation command */
7350Sstevel@tonic-gate 	if (opts_count(opts, "R")) {
7360Sstevel@tonic-gate 		struct fn *rawcmd = fn_new(opts_optarg(opts, "R"));
7370Sstevel@tonic-gate 		struct fn *cmd = fn_new(NULL);
7380Sstevel@tonic-gate 
7390Sstevel@tonic-gate 		kw_init(recentlog, NULL);
7400Sstevel@tonic-gate 		(void) kw_expand(rawcmd, cmd, 0, B_FALSE);
7410Sstevel@tonic-gate 		docmd(opts, "-R cmd", Sh, "-c", fn_s(cmd), NULL);
7420Sstevel@tonic-gate 		fn_free(rawcmd);
7430Sstevel@tonic-gate 		fn_free(cmd);
7440Sstevel@tonic-gate 	}
7450Sstevel@tonic-gate 	fn_free(recentlog);
7460Sstevel@tonic-gate 
7470Sstevel@tonic-gate 	/*
7480Sstevel@tonic-gate 	 * add "after" command to list of after commands.  we also record
7490Sstevel@tonic-gate 	 * the email address, if any, where the error output of the after
7500Sstevel@tonic-gate 	 * command should be sent.  if the after command is already on
7510Sstevel@tonic-gate 	 * our list, add the email addr to the list the email addrs for
7520Sstevel@tonic-gate 	 * that command (the after command will only be executed once,
7530Sstevel@tonic-gate 	 * so the error output gets mailed to every address we've come
7540Sstevel@tonic-gate 	 * across associated with this command).
7550Sstevel@tonic-gate 	 */
7560Sstevel@tonic-gate 	if (opts_count(opts, "a")) {
7570Sstevel@tonic-gate 		const char *cmd = opts_optarg(opts, "a");
7580Sstevel@tonic-gate 		struct lut *addrs = (struct lut *)lut_lookup(Aftercmds, cmd);
7590Sstevel@tonic-gate 		if (opts_count(opts, "e"))
7600Sstevel@tonic-gate 			addrs = lut_add(addrs, opts_optarg(opts, "e"), NULL);
7610Sstevel@tonic-gate 		Aftercmds = lut_add(Aftercmds, opts_optarg(opts, "a"), addrs);
7620Sstevel@tonic-gate 	}
7630Sstevel@tonic-gate 
7640Sstevel@tonic-gate 	/* record the rotation date */
7650Sstevel@tonic-gate 	(void) strftime(nowstr, sizeof (nowstr),
7660Sstevel@tonic-gate 	    "%a %b %e %T %Y", gmtime(&Now));
7672397Sbasabi 	if (opts_count(opts, "v") && fname != NULL)
7680Sstevel@tonic-gate 		(void) out("#     recording rotation date %s for %s\n",
7690Sstevel@tonic-gate 		    nowstr, fname);
7700Sstevel@tonic-gate 	conf_set(fname, "P", STRDUP(nowstr));
7710Sstevel@tonic-gate 	Donenames = lut_add(Donenames, fname, "1");
7720Sstevel@tonic-gate 	return (B_TRUE);
7730Sstevel@tonic-gate }
7740Sstevel@tonic-gate 
7750Sstevel@tonic-gate /* rotate files "up" according to current template */
7760Sstevel@tonic-gate static void
rotateto(struct fn * fnp,struct opts * opts,int n,struct fn * recentlog,boolean_t isgz)7770Sstevel@tonic-gate rotateto(struct fn *fnp, struct opts *opts, int n, struct fn *recentlog,
7780Sstevel@tonic-gate     boolean_t isgz)
7790Sstevel@tonic-gate {
7800Sstevel@tonic-gate 	struct fn *template = fn_new(opts_optarg(opts, "t"));
7810Sstevel@tonic-gate 	struct fn *newfile = fn_new(NULL);
7820Sstevel@tonic-gate 	struct fn *dirname;
7830Sstevel@tonic-gate 	int hasn;
7840Sstevel@tonic-gate 	struct stat stbuf;
7852397Sbasabi 	char *buf1;
7862397Sbasabi 	char *buf2;
7870Sstevel@tonic-gate 
7880Sstevel@tonic-gate 	/* expand template to figure out new filename */
7890Sstevel@tonic-gate 	hasn = kw_expand(template, newfile, n, isgz);
7900Sstevel@tonic-gate 
7912397Sbasabi 	buf1 = fn_s(fnp);
7922397Sbasabi 	buf2 = fn_s(newfile);
7932397Sbasabi 
7940Sstevel@tonic-gate 	if (Debug)
7952397Sbasabi 		if (buf1 != NULL && buf2 != NULL) {
7962397Sbasabi 			(void) fprintf(stderr, "rotateto: %s -> %s (%d)\n",
7972397Sbasabi 			    buf1, buf2, n);
7982397Sbasabi 		}
7990Sstevel@tonic-gate 	/* if filename is there already, rotate "up" */
8002397Sbasabi 	if (hasn && lstat(buf2, &stbuf) != -1)
8010Sstevel@tonic-gate 		rotateto(newfile, opts, n + 1, recentlog, isgz);
8020Sstevel@tonic-gate 	else if (hasn && opts_count(opts, "z")) {
8030Sstevel@tonic-gate 		struct fn *gzfnp = fn_dup(newfile);
8040Sstevel@tonic-gate 		/*
8050Sstevel@tonic-gate 		 * since we're compressing old files, see if we
8060Sstevel@tonic-gate 		 * about to rotate into one.
8070Sstevel@tonic-gate 		 */
8080Sstevel@tonic-gate 		fn_puts(gzfnp, ".gz");
8090Sstevel@tonic-gate 		if (lstat(fn_s(gzfnp), &stbuf) != -1)
8100Sstevel@tonic-gate 			rotateto(gzfnp, opts, n + 1, recentlog, B_TRUE);
8110Sstevel@tonic-gate 		fn_free(gzfnp);
8120Sstevel@tonic-gate 	}
8130Sstevel@tonic-gate 
8140Sstevel@tonic-gate 	/* first time through run "before" cmd if not run already */
8150Sstevel@tonic-gate 	if (n == 0 && opts_count(opts, "b")) {
8160Sstevel@tonic-gate 		const char *cmd = opts_optarg(opts, "b");
8170Sstevel@tonic-gate 
8180Sstevel@tonic-gate 		if (lut_lookup(Beforecmds, cmd) == NULL) {
8190Sstevel@tonic-gate 			docmd(opts, "-b cmd", Sh, "-c", cmd, NULL);
8200Sstevel@tonic-gate 			Beforecmds = lut_add(Beforecmds, cmd, "1");
8210Sstevel@tonic-gate 		}
8220Sstevel@tonic-gate 	}
8230Sstevel@tonic-gate 
8240Sstevel@tonic-gate 	/* ensure destination directory exists */
8250Sstevel@tonic-gate 	dirname = fn_dirname(newfile);
8260Sstevel@tonic-gate 	docmd(opts, "verify directory exists", Mkdir, "-p",
8270Sstevel@tonic-gate 	    fn_s(dirname), NULL);
8280Sstevel@tonic-gate 	fn_free(dirname);
8290Sstevel@tonic-gate 
8300Sstevel@tonic-gate 	/* do the rename */
83111829SPetr.Sumbera@Sun.COM 	if (opts_count(opts, "c") != NULL) {
83211829SPetr.Sumbera@Sun.COM 		docopytruncate(opts, fn_s(fnp), fn_s(newfile));
8330Sstevel@tonic-gate 	} else if (n == 0 && opts_count(opts, "M")) {
8340Sstevel@tonic-gate 		struct fn *rawcmd = fn_new(opts_optarg(opts, "M"));
8350Sstevel@tonic-gate 		struct fn *cmd = fn_new(NULL);
8360Sstevel@tonic-gate 
8370Sstevel@tonic-gate 		/* use specified command to mv the log file */
8380Sstevel@tonic-gate 		kw_init(fnp, newfile);
8390Sstevel@tonic-gate 		(void) kw_expand(rawcmd, cmd, 0, B_FALSE);
8400Sstevel@tonic-gate 		docmd(opts, "-M cmd", Sh, "-c", fn_s(cmd), NULL);
8410Sstevel@tonic-gate 		fn_free(rawcmd);
8420Sstevel@tonic-gate 		fn_free(cmd);
8430Sstevel@tonic-gate 	} else
8440Sstevel@tonic-gate 		/* common case: we call "mv" to handle the actual rename */
8450Sstevel@tonic-gate 		docmd(opts, "rotate log file", Mv, "-f",
8460Sstevel@tonic-gate 		    fn_s(fnp), fn_s(newfile));
8470Sstevel@tonic-gate 
8480Sstevel@tonic-gate 	/* first time through, gather interesting info for caller */
8490Sstevel@tonic-gate 	if (n == 0)
8500Sstevel@tonic-gate 		fn_renew(recentlog, fn_s(newfile));
8510Sstevel@tonic-gate }
8520Sstevel@tonic-gate 
8530Sstevel@tonic-gate /* expire phase of logname processing */
8540Sstevel@tonic-gate static void
expirefiles(struct fn * fnp,struct opts * opts)8550Sstevel@tonic-gate expirefiles(struct fn *fnp, struct opts *opts)
8560Sstevel@tonic-gate {
8570Sstevel@tonic-gate 	char *fname = fn_s(fnp);
8580Sstevel@tonic-gate 	struct fn *template;
8590Sstevel@tonic-gate 	struct fn *pattern;
8600Sstevel@tonic-gate 	struct fn_list *files;
8610Sstevel@tonic-gate 	struct fn *nextfnp;
8622016Sbasabi 	off_t count;
8632016Sbasabi 	off_t size;
8640Sstevel@tonic-gate 
8652397Sbasabi 	if (Debug && fname != NULL)
8660Sstevel@tonic-gate 		(void) fprintf(stderr, "expirefiles: fname <%s>\n", fname);
8670Sstevel@tonic-gate 
8680Sstevel@tonic-gate 	/* return if no potential expire conditions */
8692876Snakanon 	if (opts_count(opts, "zAS") == 0 && opts_optarg_int(opts, "C") == 0)
8700Sstevel@tonic-gate 		return;
8710Sstevel@tonic-gate 
8720Sstevel@tonic-gate 	kw_init(fnp, NULL);
8730Sstevel@tonic-gate 	if (Debug > 1) {
8740Sstevel@tonic-gate 		(void) fprintf(stderr, "expirefiles keywords:\n");
8750Sstevel@tonic-gate 		kw_print(stderr);
8760Sstevel@tonic-gate 	}
8770Sstevel@tonic-gate 
8780Sstevel@tonic-gate 	/* see if pattern was supplied by user */
8790Sstevel@tonic-gate 	if (opts_count(opts, "T")) {
8800Sstevel@tonic-gate 		template = fn_new(opts_optarg(opts, "T"));
8810Sstevel@tonic-gate 		pattern = glob_to_reglob(template);
8820Sstevel@tonic-gate 	} else {
8830Sstevel@tonic-gate 		/* nope, generate pattern based on rotation template */
8840Sstevel@tonic-gate 		template = fn_new(opts_optarg(opts, "t"));
8850Sstevel@tonic-gate 		pattern = fn_new(NULL);
8860Sstevel@tonic-gate 		(void) kw_expand(template, pattern, -1,
8870Sstevel@tonic-gate 		    opts_count(opts, "z") != 0);
8880Sstevel@tonic-gate 	}
8890Sstevel@tonic-gate 
8900Sstevel@tonic-gate 	/* match all old log files (hopefully not any others as well!) */
8910Sstevel@tonic-gate 	files = glob_reglob(pattern);
8920Sstevel@tonic-gate 
8930Sstevel@tonic-gate 	if (Debug) {
8942397Sbasabi 		char *buf;
8952397Sbasabi 
8962397Sbasabi 		buf = fn_s(pattern);
8972397Sbasabi 		if (buf != NULL) {
8982397Sbasabi 			(void) fprintf(stderr, "expirefiles: pattern <%s>\n",
8992397Sbasabi 			    buf);
9002397Sbasabi 		}
9010Sstevel@tonic-gate 		fn_list_rewind(files);
9020Sstevel@tonic-gate 		while ((nextfnp = fn_list_next(files)) != NULL)
9032397Sbasabi 			buf = fn_s(nextfnp);
9042397Sbasabi 			if (buf != NULL)
9052397Sbasabi 				(void) fprintf(stderr, "    <%s>\n", buf);
9060Sstevel@tonic-gate 	}
9070Sstevel@tonic-gate 
9080Sstevel@tonic-gate 	/* see if count causes expiration */
9090Sstevel@tonic-gate 	if ((count = opts_optarg_int(opts, "C")) > 0) {
9100Sstevel@tonic-gate 		int needexpire = fn_list_count(files) - count;
9110Sstevel@tonic-gate 
9120Sstevel@tonic-gate 		if (Debug)
9130Sstevel@tonic-gate 			(void) fprintf(stderr, "expirefiles: needexpire %d\n",
9140Sstevel@tonic-gate 			    needexpire);
9150Sstevel@tonic-gate 
9160Sstevel@tonic-gate 		while (needexpire > 0 &&
9170Sstevel@tonic-gate 		    ((nextfnp = fn_list_popoldest(files)) != NULL)) {
9180Sstevel@tonic-gate 			dorm(opts, "expire by count rule", nextfnp);
9190Sstevel@tonic-gate 			fn_free(nextfnp);
9200Sstevel@tonic-gate 			needexpire--;
9210Sstevel@tonic-gate 		}
9220Sstevel@tonic-gate 	}
9230Sstevel@tonic-gate 
9240Sstevel@tonic-gate 	/* see if total size causes expiration */
9250Sstevel@tonic-gate 	if (opts_count(opts, "S") && (size = opts_optarg_int(opts, "S")) > 0) {
9260Sstevel@tonic-gate 		while (fn_list_totalsize(files) > size &&
9270Sstevel@tonic-gate 		    ((nextfnp = fn_list_popoldest(files)) != NULL)) {
9282876Snakanon 			dorm(opts, "expire by size rule", nextfnp);
9292876Snakanon 			fn_free(nextfnp);
9300Sstevel@tonic-gate 		}
9310Sstevel@tonic-gate 	}
9320Sstevel@tonic-gate 
9330Sstevel@tonic-gate 	/* see if age causes expiration */
9340Sstevel@tonic-gate 	if (opts_count(opts, "A")) {
9352016Sbasabi 		int mtime = (int)time(0) - (int)opts_optarg_int(opts, "A");
9360Sstevel@tonic-gate 
9372876Snakanon 		while ((nextfnp = fn_list_popoldest(files)) != NULL) {
9380Sstevel@tonic-gate 			if (fn_getstat(nextfnp)->st_mtime < mtime) {
9390Sstevel@tonic-gate 				dorm(opts, "expire by age rule", nextfnp);
9400Sstevel@tonic-gate 				fn_free(nextfnp);
9410Sstevel@tonic-gate 			} else {
9423404Snakanon 				fn_list_addfn(files, nextfnp);
9430Sstevel@tonic-gate 				break;
9440Sstevel@tonic-gate 			}
9452876Snakanon 		}
9462876Snakanon 	}
9472876Snakanon 
9482876Snakanon 	/* record old log files to be gzip'ed according to -z count */
9492876Snakanon 	if (opts_count(opts, "z")) {
9502876Snakanon 		int zcount = (int)opts_optarg_int(opts, "z");
9512876Snakanon 		int fcount = fn_list_count(files);
9522876Snakanon 
9532876Snakanon 		while (fcount > zcount &&
9542876Snakanon 		    (nextfnp = fn_list_popoldest(files)) != NULL) {
9552876Snakanon 			if (!fn_isgz(nextfnp)) {
9562876Snakanon 				/*
9572876Snakanon 				 * Don't gzip the old log file yet -
9582876Snakanon 				 * it takes too long. Just remember that we
9592876Snakanon 				 * need to gzip.
9602876Snakanon 				 */
9612876Snakanon 				if (Debug) {
9622876Snakanon 					(void) fprintf(stderr,
9632876Snakanon 					    "will compress %s count %d\n",
9642876Snakanon 					    fn_s(nextfnp), fcount);
9652876Snakanon 				}
9662876Snakanon 				Gzipnames = lut_add(Gzipnames,
96711829SPetr.Sumbera@Sun.COM 				    fn_s(nextfnp), "1");
9682876Snakanon 			}
9692876Snakanon 			fn_free(nextfnp);
9702876Snakanon 			fcount--;
9712876Snakanon 		}
9720Sstevel@tonic-gate 	}
9730Sstevel@tonic-gate 
9740Sstevel@tonic-gate 	fn_free(template);
9750Sstevel@tonic-gate 	fn_list_free(files);
9760Sstevel@tonic-gate }
9770Sstevel@tonic-gate 
9780Sstevel@tonic-gate /* execute a command to remove an expired log file */
9790Sstevel@tonic-gate static void
dorm(struct opts * opts,const char * msg,struct fn * fnp)9800Sstevel@tonic-gate dorm(struct opts *opts, const char *msg, struct fn *fnp)
9810Sstevel@tonic-gate {
9820Sstevel@tonic-gate 	if (opts_count(opts, "E")) {
9830Sstevel@tonic-gate 		struct fn *rawcmd = fn_new(opts_optarg(opts, "E"));
9840Sstevel@tonic-gate 		struct fn *cmd = fn_new(NULL);
9850Sstevel@tonic-gate 
9860Sstevel@tonic-gate 		/* user supplied cmd, expand $file */
9870Sstevel@tonic-gate 		kw_init(fnp, NULL);
9880Sstevel@tonic-gate 		(void) kw_expand(rawcmd, cmd, 0, B_FALSE);
9890Sstevel@tonic-gate 		docmd(opts, msg, Sh, "-c", fn_s(cmd), NULL);
9900Sstevel@tonic-gate 		fn_free(rawcmd);
9910Sstevel@tonic-gate 		fn_free(cmd);
9920Sstevel@tonic-gate 	} else
9930Sstevel@tonic-gate 		docmd(opts, msg, Rm, "-f", fn_s(fnp), NULL);
9941913Sdg199075 	Gzipnames = lut_add(Gzipnames, fn_s(fnp), NULL);
9950Sstevel@tonic-gate }
9960Sstevel@tonic-gate 
9970Sstevel@tonic-gate /* execute a command, producing -n and -v output as necessary */
9980Sstevel@tonic-gate static void
docmd(struct opts * opts,const char * msg,const char * cmd,const char * arg1,const char * arg2,const char * arg3)9990Sstevel@tonic-gate docmd(struct opts *opts, const char *msg, const char *cmd,
10000Sstevel@tonic-gate     const char *arg1, const char *arg2, const char *arg3)
10010Sstevel@tonic-gate {
10020Sstevel@tonic-gate 	int pid;
10030Sstevel@tonic-gate 	int errpipe[2];
10040Sstevel@tonic-gate 
10050Sstevel@tonic-gate 	/* print info about command if necessary */
10060Sstevel@tonic-gate 	if (opts_count(opts, "vn")) {
10070Sstevel@tonic-gate 		const char *simplecmd;
10080Sstevel@tonic-gate 
10090Sstevel@tonic-gate 		if ((simplecmd = strrchr(cmd, '/')) == NULL)
10100Sstevel@tonic-gate 			simplecmd = cmd;
10110Sstevel@tonic-gate 		else
10120Sstevel@tonic-gate 			simplecmd++;
10130Sstevel@tonic-gate 		(void) out("%s", simplecmd);
10140Sstevel@tonic-gate 		if (arg1)
10150Sstevel@tonic-gate 			(void) out(" %s", arg1);
10160Sstevel@tonic-gate 		if (arg2)
10170Sstevel@tonic-gate 			(void) out(" %s", arg2);
10180Sstevel@tonic-gate 		if (arg3)
10190Sstevel@tonic-gate 			(void) out(" %s", arg3);
10200Sstevel@tonic-gate 		if (msg)
10210Sstevel@tonic-gate 			(void) out(" # %s", msg);
10220Sstevel@tonic-gate 		(void) out("\n");
10230Sstevel@tonic-gate 	}
10240Sstevel@tonic-gate 
10250Sstevel@tonic-gate 	if (opts_count(opts, "n"))
10260Sstevel@tonic-gate 		return;		/* -n means don't really do it */
10270Sstevel@tonic-gate 
10280Sstevel@tonic-gate 	/*
10290Sstevel@tonic-gate 	 * run the cmd and see if it failed.  this function is *not* a
10300Sstevel@tonic-gate 	 * generic command runner -- we depend on some knowledge we
10310Sstevel@tonic-gate 	 * have about the commands we run.  first of all, we expect
10320Sstevel@tonic-gate 	 * errors to spew something to stderr, and that something is
10330Sstevel@tonic-gate 	 * typically short enough to fit into a pipe so we can wait()
10340Sstevel@tonic-gate 	 * for the command to complete and then fetch the error text
10350Sstevel@tonic-gate 	 * from the pipe.  we also expect the exit codes to make sense.
10360Sstevel@tonic-gate 	 * notice also that we only allow a command name which is an
10370Sstevel@tonic-gate 	 * absolute pathname, and two args must be supplied (the
10380Sstevel@tonic-gate 	 * second may be NULL, or they may both be NULL).
10390Sstevel@tonic-gate 	 */
10400Sstevel@tonic-gate 	if (pipe(errpipe) < 0)
10410Sstevel@tonic-gate 		err(EF_SYS, "pipe");
10420Sstevel@tonic-gate 
10430Sstevel@tonic-gate 	if ((pid = fork()) < 0)
10440Sstevel@tonic-gate 		err(EF_SYS, "fork");
10450Sstevel@tonic-gate 	else if (pid) {
10460Sstevel@tonic-gate 		int wstat;
10470Sstevel@tonic-gate 		int count;
10480Sstevel@tonic-gate 
10490Sstevel@tonic-gate 		/* parent */
10500Sstevel@tonic-gate 		(void) close(errpipe[1]);
10510Sstevel@tonic-gate 		if (waitpid(pid, &wstat, 0) < 0)
10520Sstevel@tonic-gate 			err(EF_SYS, "waitpid");
10530Sstevel@tonic-gate 
10540Sstevel@tonic-gate 		/* check for stderr output */
10550Sstevel@tonic-gate 		if (ioctl(errpipe[0], FIONREAD, &count) >= 0 && count) {
10560Sstevel@tonic-gate 			err(EF_WARN, "command failed: %s%s%s%s%s%s%s",
105711829SPetr.Sumbera@Sun.COM 			    cmd,
105811829SPetr.Sumbera@Sun.COM 			    (arg1) ? " " : "",
105911829SPetr.Sumbera@Sun.COM 			    (arg1) ? arg1 : "",
106011829SPetr.Sumbera@Sun.COM 			    (arg2) ? " " : "",
106111829SPetr.Sumbera@Sun.COM 			    (arg2) ? arg2 : "",
106211829SPetr.Sumbera@Sun.COM 			    (arg3) ? " " : "",
106311829SPetr.Sumbera@Sun.COM 			    (arg3) ? arg3 : "");
10640Sstevel@tonic-gate 			err_fromfd(errpipe[0]);
10650Sstevel@tonic-gate 		} else if (WIFSIGNALED(wstat))
10660Sstevel@tonic-gate 			err(EF_WARN,
10670Sstevel@tonic-gate 			    "command died, signal %d: %s%s%s%s%s%s%s",
106811829SPetr.Sumbera@Sun.COM 			    WTERMSIG(wstat),
106911829SPetr.Sumbera@Sun.COM 			    cmd,
107011829SPetr.Sumbera@Sun.COM 			    (arg1) ? " " : "",
107111829SPetr.Sumbera@Sun.COM 			    (arg1) ? arg1 : "",
107211829SPetr.Sumbera@Sun.COM 			    (arg2) ? " " : "",
107311829SPetr.Sumbera@Sun.COM 			    (arg2) ? arg2 : "",
107411829SPetr.Sumbera@Sun.COM 			    (arg3) ? " " : "",
107511829SPetr.Sumbera@Sun.COM 			    (arg3) ? arg3 : "");
10760Sstevel@tonic-gate 		else if (WIFEXITED(wstat) && WEXITSTATUS(wstat))
10770Sstevel@tonic-gate 			err(EF_WARN,
10780Sstevel@tonic-gate 			    "command error, exit %d: %s%s%s%s%s%s%s",
107911829SPetr.Sumbera@Sun.COM 			    WEXITSTATUS(wstat),
108011829SPetr.Sumbera@Sun.COM 			    cmd,
108111829SPetr.Sumbera@Sun.COM 			    (arg1) ? " " : "",
108211829SPetr.Sumbera@Sun.COM 			    (arg1) ? arg1 : "",
108311829SPetr.Sumbera@Sun.COM 			    (arg2) ? " " : "",
108411829SPetr.Sumbera@Sun.COM 			    (arg2) ? arg2 : "",
108511829SPetr.Sumbera@Sun.COM 			    (arg3) ? " " : "",
108611829SPetr.Sumbera@Sun.COM 			    (arg3) ? arg3 : "");
10870Sstevel@tonic-gate 
10880Sstevel@tonic-gate 		(void) close(errpipe[0]);
10890Sstevel@tonic-gate 	} else {
10900Sstevel@tonic-gate 		/* child */
10910Sstevel@tonic-gate 		(void) dup2(errpipe[1], fileno(stderr));
10920Sstevel@tonic-gate 		(void) close(errpipe[0]);
10930Sstevel@tonic-gate 		(void) execl(cmd, cmd, arg1, arg2, arg3, 0);
10940Sstevel@tonic-gate 		perror(cmd);
10950Sstevel@tonic-gate 		_exit(1);
10960Sstevel@tonic-gate 	}
10970Sstevel@tonic-gate }
109811829SPetr.Sumbera@Sun.COM 
109911829SPetr.Sumbera@Sun.COM /* do internal atomic file copy and truncation */
110011829SPetr.Sumbera@Sun.COM static void
docopytruncate(struct opts * opts,const char * file,const char * file_copy)110111829SPetr.Sumbera@Sun.COM docopytruncate(struct opts *opts, const char *file, const char *file_copy)
110211829SPetr.Sumbera@Sun.COM {
110311829SPetr.Sumbera@Sun.COM 	int fi, fo, len;
110411829SPetr.Sumbera@Sun.COM 	char buf[4096];
110511829SPetr.Sumbera@Sun.COM 	struct stat s;
110611829SPetr.Sumbera@Sun.COM 	struct utimbuf times;
110711829SPetr.Sumbera@Sun.COM 
110811829SPetr.Sumbera@Sun.COM 	/* print info if necessary */
110911829SPetr.Sumbera@Sun.COM 	if (opts_count(opts, "vn") != NULL) {
111011829SPetr.Sumbera@Sun.COM 		(void) out("# log rotation via atomic copy and truncation"
111111829SPetr.Sumbera@Sun.COM 		    " (-c flag):\n");
111211829SPetr.Sumbera@Sun.COM 		(void) out("# copy %s to %s\n", file, file_copy);
111311829SPetr.Sumbera@Sun.COM 		(void) out("# truncate %s\n", file);
111411829SPetr.Sumbera@Sun.COM 	}
111511829SPetr.Sumbera@Sun.COM 
111611829SPetr.Sumbera@Sun.COM 	if (opts_count(opts, "n"))
111711829SPetr.Sumbera@Sun.COM 		return;		/* -n means don't really do it */
111811829SPetr.Sumbera@Sun.COM 
111911829SPetr.Sumbera@Sun.COM 	/* open log file to be rotated and remember its chmod mask */
112011829SPetr.Sumbera@Sun.COM 	if ((fi = open(file, O_RDWR)) < 0) {
112111829SPetr.Sumbera@Sun.COM 		err(EF_SYS, "cannot open file %s", file);
112211829SPetr.Sumbera@Sun.COM 		return;
112311829SPetr.Sumbera@Sun.COM 	}
112411829SPetr.Sumbera@Sun.COM 
112511829SPetr.Sumbera@Sun.COM 	if (fstat(fi, &s) < 0) {
112611829SPetr.Sumbera@Sun.COM 		err(EF_SYS, "cannot access: %s", file);
112711829SPetr.Sumbera@Sun.COM 		(void) close(fi);
112811829SPetr.Sumbera@Sun.COM 		return;
112911829SPetr.Sumbera@Sun.COM 	}
113011829SPetr.Sumbera@Sun.COM 
113111829SPetr.Sumbera@Sun.COM 	/* create new file for copy destination with correct attributes */
113211829SPetr.Sumbera@Sun.COM 	if ((fo = open(file_copy, O_CREAT|O_APPEND|O_WRONLY, s.st_mode)) < 0) {
113311829SPetr.Sumbera@Sun.COM 		err(EF_SYS, "cannot create file: %s", file_copy);
113411829SPetr.Sumbera@Sun.COM 		(void) close(fi);
113511829SPetr.Sumbera@Sun.COM 		return;
113611829SPetr.Sumbera@Sun.COM 	}
113711829SPetr.Sumbera@Sun.COM 
113811829SPetr.Sumbera@Sun.COM 	(void) fchown(fo, s.st_uid, s.st_gid);
113911829SPetr.Sumbera@Sun.COM 
114011829SPetr.Sumbera@Sun.COM 	/* lock log file so that nobody can write into it before we are done */
114111829SPetr.Sumbera@Sun.COM 	if (fchmod(fi, s.st_mode|S_ISGID) < 0)
114211829SPetr.Sumbera@Sun.COM 		err(EF_SYS, "cannot set mandatory lock bit for: %s", file);
114311829SPetr.Sumbera@Sun.COM 
114411829SPetr.Sumbera@Sun.COM 	if (lockf(fi, F_LOCK, 0) == -1)
114511829SPetr.Sumbera@Sun.COM 		err(EF_SYS, "cannot lock file %s", file);
114611829SPetr.Sumbera@Sun.COM 
114711829SPetr.Sumbera@Sun.COM 	/* do atomic copy and truncation */
114811829SPetr.Sumbera@Sun.COM 	while ((len = read(fi, buf, sizeof (buf))) > 0)
114911829SPetr.Sumbera@Sun.COM 		if (write(fo, buf, len) != len) {
115011829SPetr.Sumbera@Sun.COM 			err(EF_SYS, "cannot write into file %s", file_copy);
115111829SPetr.Sumbera@Sun.COM 			(void) lockf(fi, F_ULOCK, 0);
115211829SPetr.Sumbera@Sun.COM 			(void) fchmod(fi, s.st_mode);
115311829SPetr.Sumbera@Sun.COM 			(void) close(fi);
115411829SPetr.Sumbera@Sun.COM 			(void) close(fo);
115511829SPetr.Sumbera@Sun.COM 			(void) remove(file_copy);
115611829SPetr.Sumbera@Sun.COM 			return;
115711829SPetr.Sumbera@Sun.COM 		}
115811829SPetr.Sumbera@Sun.COM 
115911829SPetr.Sumbera@Sun.COM 	(void) ftruncate(fi, 0);
116011829SPetr.Sumbera@Sun.COM 
116111829SPetr.Sumbera@Sun.COM 	/* unlock log file */
116211829SPetr.Sumbera@Sun.COM 	if (lockf(fi, F_ULOCK, 0) == -1)
116311829SPetr.Sumbera@Sun.COM 		err(EF_SYS, "cannot unlock file %s", file);
116411829SPetr.Sumbera@Sun.COM 
116511829SPetr.Sumbera@Sun.COM 	if (fchmod(fi, s.st_mode) < 0)
116611829SPetr.Sumbera@Sun.COM 		err(EF_SYS, "cannot reset mandatory lock bit for: %s", file);
116711829SPetr.Sumbera@Sun.COM 
116811829SPetr.Sumbera@Sun.COM 	(void) close(fi);
116911829SPetr.Sumbera@Sun.COM 	(void) close(fo);
117011829SPetr.Sumbera@Sun.COM 
117111829SPetr.Sumbera@Sun.COM 	/* keep times from original file */
117211829SPetr.Sumbera@Sun.COM 	times.actime = s.st_atime;
117311829SPetr.Sumbera@Sun.COM 	times.modtime = s.st_mtime;
117411829SPetr.Sumbera@Sun.COM 	(void) utime(file_copy, &times);
117511829SPetr.Sumbera@Sun.COM }
1176