xref: /onnv-gate/usr/src/cmd/logadm/main.c (revision 0)
1*0Sstevel@tonic-gate /*
2*0Sstevel@tonic-gate  * CDDL HEADER START
3*0Sstevel@tonic-gate  *
4*0Sstevel@tonic-gate  * The contents of this file are subject to the terms of the
5*0Sstevel@tonic-gate  * Common Development and Distribution License, Version 1.0 only
6*0Sstevel@tonic-gate  * (the "License").  You may not use this file except in compliance
7*0Sstevel@tonic-gate  * with the License.
8*0Sstevel@tonic-gate  *
9*0Sstevel@tonic-gate  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10*0Sstevel@tonic-gate  * or http://www.opensolaris.org/os/licensing.
11*0Sstevel@tonic-gate  * See the License for the specific language governing permissions
12*0Sstevel@tonic-gate  * and limitations under the License.
13*0Sstevel@tonic-gate  *
14*0Sstevel@tonic-gate  * When distributing Covered Code, include this CDDL HEADER in each
15*0Sstevel@tonic-gate  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16*0Sstevel@tonic-gate  * If applicable, add the following below this CDDL HEADER, with the
17*0Sstevel@tonic-gate  * fields enclosed by brackets "[]" replaced with your own identifying
18*0Sstevel@tonic-gate  * information: Portions Copyright [yyyy] [name of copyright owner]
19*0Sstevel@tonic-gate  *
20*0Sstevel@tonic-gate  * CDDL HEADER END
21*0Sstevel@tonic-gate  */
22*0Sstevel@tonic-gate /*
23*0Sstevel@tonic-gate  * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
24*0Sstevel@tonic-gate  * Use is subject to license terms.
25*0Sstevel@tonic-gate  *
26*0Sstevel@tonic-gate  * logadm/main.c -- main routines for logadm
27*0Sstevel@tonic-gate  *
28*0Sstevel@tonic-gate  * this program is 90% argument processing, 10% actions...
29*0Sstevel@tonic-gate  */
30*0Sstevel@tonic-gate 
31*0Sstevel@tonic-gate #pragma ident	"%Z%%M%	%I%	%E% SMI"
32*0Sstevel@tonic-gate 
33*0Sstevel@tonic-gate #include <stdio.h>
34*0Sstevel@tonic-gate #include <stdlib.h>
35*0Sstevel@tonic-gate #include <unistd.h>
36*0Sstevel@tonic-gate #include <strings.h>
37*0Sstevel@tonic-gate #include <libintl.h>
38*0Sstevel@tonic-gate #include <locale.h>
39*0Sstevel@tonic-gate #include <sys/types.h>
40*0Sstevel@tonic-gate #include <sys/stat.h>
41*0Sstevel@tonic-gate #include <sys/wait.h>
42*0Sstevel@tonic-gate #include <sys/filio.h>
43*0Sstevel@tonic-gate #include <time.h>
44*0Sstevel@tonic-gate #include "err.h"
45*0Sstevel@tonic-gate #include "lut.h"
46*0Sstevel@tonic-gate #include "fn.h"
47*0Sstevel@tonic-gate #include "opts.h"
48*0Sstevel@tonic-gate #include "conf.h"
49*0Sstevel@tonic-gate #include "glob.h"
50*0Sstevel@tonic-gate #include "kw.h"
51*0Sstevel@tonic-gate 
52*0Sstevel@tonic-gate /* forward declarations for functions in this file */
53*0Sstevel@tonic-gate static void usage(const char *msg);
54*0Sstevel@tonic-gate static void commajoin(const char *lhs, void *rhs, void *arg);
55*0Sstevel@tonic-gate static void doaftercmd(const char *lhs, void *rhs, void *arg);
56*0Sstevel@tonic-gate static void dologname(struct fn *fnp, struct opts *clopts);
57*0Sstevel@tonic-gate static boolean_t rotatelog(struct fn *fnp, struct opts *opts);
58*0Sstevel@tonic-gate static void rotateto(struct fn *fnp, struct opts *opts, int n,
59*0Sstevel@tonic-gate     struct fn *recentlog, boolean_t isgz);
60*0Sstevel@tonic-gate static void expirefiles(struct fn *fnp, struct opts *opts);
61*0Sstevel@tonic-gate static void dorm(struct opts *opts, const char *msg, struct fn *fnp);
62*0Sstevel@tonic-gate static void docmd(struct opts *opts, const char *msg, const char *cmd,
63*0Sstevel@tonic-gate     const char *arg1, const char *arg2, const char *arg3);
64*0Sstevel@tonic-gate 
65*0Sstevel@tonic-gate /* our configuration file, unless otherwise specified by -f */
66*0Sstevel@tonic-gate static char *Default_conffile = "/etc/logadm.conf";
67*0Sstevel@tonic-gate 
68*0Sstevel@tonic-gate /* default pathnames to the commands we invoke */
69*0Sstevel@tonic-gate static char *Sh = "/bin/sh";
70*0Sstevel@tonic-gate static char *Mv = "/bin/mv";
71*0Sstevel@tonic-gate static char *Cp = "/bin/cp";
72*0Sstevel@tonic-gate static char *Rm = "/bin/rm";
73*0Sstevel@tonic-gate static char *Touch = "/bin/touch";
74*0Sstevel@tonic-gate static char *Chmod = "/bin/chmod";
75*0Sstevel@tonic-gate static char *Chown = "/bin/chown";
76*0Sstevel@tonic-gate static char *Gzip = "/bin/gzip";
77*0Sstevel@tonic-gate static char *Mkdir = "/bin/mkdir";
78*0Sstevel@tonic-gate 
79*0Sstevel@tonic-gate /* return from time(0), gathered early on to avoid slewed timestamps */
80*0Sstevel@tonic-gate time_t Now;
81*0Sstevel@tonic-gate 
82*0Sstevel@tonic-gate /* list of before commands that have been executed */
83*0Sstevel@tonic-gate static struct lut *Beforecmds;
84*0Sstevel@tonic-gate 
85*0Sstevel@tonic-gate /* list of after commands to execute before exiting */
86*0Sstevel@tonic-gate static struct lut *Aftercmds;
87*0Sstevel@tonic-gate 
88*0Sstevel@tonic-gate /* list of conffile entry names that are considered "done" */
89*0Sstevel@tonic-gate static struct lut *Donenames;
90*0Sstevel@tonic-gate 
91*0Sstevel@tonic-gate /* table that drives argument parsing */
92*0Sstevel@tonic-gate static struct optinfo Opttable[] = {
93*0Sstevel@tonic-gate 	{ "e", OPTTYPE_STRING,	NULL,			OPTF_CLI|OPTF_CONF },
94*0Sstevel@tonic-gate 	{ "f", OPTTYPE_STRING,	NULL,			OPTF_CLI },
95*0Sstevel@tonic-gate 	{ "h", OPTTYPE_BOOLEAN,	NULL,			OPTF_CLI },
96*0Sstevel@tonic-gate 	{ "N", OPTTYPE_BOOLEAN,	NULL,			OPTF_CLI|OPTF_CONF },
97*0Sstevel@tonic-gate 	{ "n", OPTTYPE_BOOLEAN,	NULL,			OPTF_CLI },
98*0Sstevel@tonic-gate 	{ "r", OPTTYPE_BOOLEAN,	NULL,			OPTF_CLI },
99*0Sstevel@tonic-gate 	{ "V", OPTTYPE_BOOLEAN,	NULL,			OPTF_CLI },
100*0Sstevel@tonic-gate 	{ "v", OPTTYPE_BOOLEAN,	NULL,			OPTF_CLI },
101*0Sstevel@tonic-gate 	{ "w", OPTTYPE_STRING,	NULL,			OPTF_CLI },
102*0Sstevel@tonic-gate 	{ "p", OPTTYPE_INT,	opts_parse_seconds,	OPTF_CLI|OPTF_CONF },
103*0Sstevel@tonic-gate 	{ "P", OPTTYPE_INT,	opts_parse_ctime,	OPTF_CLI|OPTF_CONF },
104*0Sstevel@tonic-gate 	{ "s", OPTTYPE_INT,	opts_parse_bytes,	OPTF_CLI|OPTF_CONF },
105*0Sstevel@tonic-gate 	{ "a", OPTTYPE_STRING,	NULL,			OPTF_CLI|OPTF_CONF },
106*0Sstevel@tonic-gate 	{ "b", OPTTYPE_STRING,	NULL,			OPTF_CLI|OPTF_CONF },
107*0Sstevel@tonic-gate 	{ "c", OPTTYPE_BOOLEAN, NULL,			OPTF_CLI|OPTF_CONF },
108*0Sstevel@tonic-gate 	{ "g", OPTTYPE_STRING,	NULL,			OPTF_CLI|OPTF_CONF },
109*0Sstevel@tonic-gate 	{ "m", OPTTYPE_INT,	opts_parse_atopi,	OPTF_CLI|OPTF_CONF },
110*0Sstevel@tonic-gate 	{ "M", OPTTYPE_STRING,	NULL,			OPTF_CLI|OPTF_CONF },
111*0Sstevel@tonic-gate 	{ "o", OPTTYPE_STRING,	NULL,			OPTF_CLI|OPTF_CONF },
112*0Sstevel@tonic-gate 	{ "R", OPTTYPE_STRING,	NULL,			OPTF_CLI|OPTF_CONF },
113*0Sstevel@tonic-gate 	{ "t", OPTTYPE_STRING,	NULL,			OPTF_CLI|OPTF_CONF },
114*0Sstevel@tonic-gate 	{ "z", OPTTYPE_INT,	opts_parse_atopi,	OPTF_CLI|OPTF_CONF },
115*0Sstevel@tonic-gate 	{ "A", OPTTYPE_INT,	opts_parse_seconds,	OPTF_CLI|OPTF_CONF },
116*0Sstevel@tonic-gate 	{ "C", OPTTYPE_INT,	opts_parse_atopi,	OPTF_CLI|OPTF_CONF },
117*0Sstevel@tonic-gate 	{ "E", OPTTYPE_STRING,	NULL,			OPTF_CLI|OPTF_CONF },
118*0Sstevel@tonic-gate 	{ "S", OPTTYPE_INT,	opts_parse_bytes,	OPTF_CLI|OPTF_CONF },
119*0Sstevel@tonic-gate 	{ "T", OPTTYPE_STRING,	NULL,			OPTF_CLI|OPTF_CONF },
120*0Sstevel@tonic-gate };
121*0Sstevel@tonic-gate 
122*0Sstevel@tonic-gate /*
123*0Sstevel@tonic-gate  * only the "fhnVv" options are allowed in the first form of this command,
124*0Sstevel@tonic-gate  * so this defines the list of options that are an error in they appear
125*0Sstevel@tonic-gate  * in the first form.  In other words, it is not allowed to run logadm
126*0Sstevel@tonic-gate  * with any of these options unless at least one logname is also provided.
127*0Sstevel@tonic-gate  */
128*0Sstevel@tonic-gate #define	OPTIONS_NOT_FIRST_FORM	"eNrwpPsabcgmoRtzACEST"
129*0Sstevel@tonic-gate 
130*0Sstevel@tonic-gate /* text that we spew with the -h flag */
131*0Sstevel@tonic-gate #define	HELP1 \
132*0Sstevel@tonic-gate "Usage: logadm [options]\n"\
133*0Sstevel@tonic-gate "       (processes all entries in /etc/logadm.conf or conffile given by -f)\n"\
134*0Sstevel@tonic-gate "   or: logadm [options] logname...\n"\
135*0Sstevel@tonic-gate "       (processes the given lognames)\n"\
136*0Sstevel@tonic-gate "\n"\
137*0Sstevel@tonic-gate "General options:\n"\
138*0Sstevel@tonic-gate "        -e mailaddr     mail errors to given address\n"\
139*0Sstevel@tonic-gate "        -f conffile     use conffile instead of /etc/logadm.conf\n"\
140*0Sstevel@tonic-gate "        -h              display help\n"\
141*0Sstevel@tonic-gate "        -N              not an error if log file nonexistent\n"\
142*0Sstevel@tonic-gate "        -n              show actions, don't perform them\n"\
143*0Sstevel@tonic-gate "        -r              remove logname entry from conffile\n"\
144*0Sstevel@tonic-gate "        -V              ensure conffile entries exist, correct\n"\
145*0Sstevel@tonic-gate "        -v              print info about actions happening\n"\
146*0Sstevel@tonic-gate "        -w entryname    write entry to config file\n"\
147*0Sstevel@tonic-gate "\n"\
148*0Sstevel@tonic-gate "Options which control when a logfile is rotated:\n"\
149*0Sstevel@tonic-gate "(default is: -s1b -p1w if no -s or -p)\n"\
150*0Sstevel@tonic-gate "        -p period       only rotate if period passed since last rotate\n"\
151*0Sstevel@tonic-gate "        -P timestamp    used to store rotation date in conffile\n"\
152*0Sstevel@tonic-gate "        -s size         only rotate if given size or greater\n"\
153*0Sstevel@tonic-gate "\n"
154*0Sstevel@tonic-gate #define	HELP2 \
155*0Sstevel@tonic-gate "Options which control how a logfile is rotated:\n"\
156*0Sstevel@tonic-gate "(default is: -t '$file.$n', owner/group/mode taken from log file)\n"\
157*0Sstevel@tonic-gate "        -a cmd          execute cmd after taking actions\n"\
158*0Sstevel@tonic-gate "        -b cmd          execute cmd before taking actions\n"\
159*0Sstevel@tonic-gate "        -c              copy & truncate logfile, don't rename\n"\
160*0Sstevel@tonic-gate "        -g group        new empty log file group\n"\
161*0Sstevel@tonic-gate "        -m mode         new empty log file mode\n"\
162*0Sstevel@tonic-gate "        -M cmd          execute cmd to rotate the log file\n"\
163*0Sstevel@tonic-gate "        -o owner        new empty log file owner\n"\
164*0Sstevel@tonic-gate "        -R cmd          run cmd on file after rotate\n"\
165*0Sstevel@tonic-gate "        -t template     template for naming old logs\n"\
166*0Sstevel@tonic-gate "        -z count        gzip old logs except most recent count\n"\
167*0Sstevel@tonic-gate "\n"\
168*0Sstevel@tonic-gate "Options which control the expiration of old logfiles:\n"\
169*0Sstevel@tonic-gate "(default is: -C10 if no -A, -C, or -S)\n"\
170*0Sstevel@tonic-gate "        -A age          expire logs older than age\n"\
171*0Sstevel@tonic-gate "        -C count        expire old logs until count remain\n"\
172*0Sstevel@tonic-gate "        -E cmd          run cmd on file to expire\n"\
173*0Sstevel@tonic-gate "        -S size         expire until space used is below size \n"\
174*0Sstevel@tonic-gate "        -T pattern      pattern for finding old logs\n"
175*0Sstevel@tonic-gate 
176*0Sstevel@tonic-gate /*
177*0Sstevel@tonic-gate  * main -- where it all begins
178*0Sstevel@tonic-gate  */
179*0Sstevel@tonic-gate /*ARGSUSED*/
180*0Sstevel@tonic-gate int
181*0Sstevel@tonic-gate main(int argc, char *argv[])
182*0Sstevel@tonic-gate {
183*0Sstevel@tonic-gate 	struct opts *clopts;		/* from parsing command line */
184*0Sstevel@tonic-gate 	const char *conffile;		/* our configuration file */
185*0Sstevel@tonic-gate 	struct fn_list *lognames;	/* list of lognames we're processing */
186*0Sstevel@tonic-gate 	struct fn *fnp;
187*0Sstevel@tonic-gate 	char *val;
188*0Sstevel@tonic-gate 
189*0Sstevel@tonic-gate 	(void) setlocale(LC_ALL, "");
190*0Sstevel@tonic-gate 
191*0Sstevel@tonic-gate #if !defined(TEXT_DOMAIN)
192*0Sstevel@tonic-gate #define	TEXT_DOMAIN "SYS_TEST"	/* only used if Makefiles don't define it */
193*0Sstevel@tonic-gate #endif
194*0Sstevel@tonic-gate 
195*0Sstevel@tonic-gate 	(void) textdomain(TEXT_DOMAIN);
196*0Sstevel@tonic-gate 
197*0Sstevel@tonic-gate 	/* we only print times into the conffile, so make them uniform */
198*0Sstevel@tonic-gate 	(void) setlocale(LC_TIME, "C");
199*0Sstevel@tonic-gate 
200*0Sstevel@tonic-gate 	/* give our name to error routines & skip it for arg parsing */
201*0Sstevel@tonic-gate 	err_init(*argv++);
202*0Sstevel@tonic-gate 	(void) setlinebuf(stdout);
203*0Sstevel@tonic-gate 
204*0Sstevel@tonic-gate 	if (putenv("PATH=/bin"))
205*0Sstevel@tonic-gate 		err(EF_SYS, "putenv PATH");
206*0Sstevel@tonic-gate 	if (putenv("TZ=UTC"))
207*0Sstevel@tonic-gate 		err(EF_SYS, "putenv TZ");
208*0Sstevel@tonic-gate 	tzset();
209*0Sstevel@tonic-gate 
210*0Sstevel@tonic-gate 	(void) umask(0);
211*0Sstevel@tonic-gate 
212*0Sstevel@tonic-gate 	Now = time(0);
213*0Sstevel@tonic-gate 
214*0Sstevel@tonic-gate 	/* check for (undocumented) debugging environment variables */
215*0Sstevel@tonic-gate 	if (val = getenv("_LOGADM_DEFAULT_CONFFILE"))
216*0Sstevel@tonic-gate 		Default_conffile = val;
217*0Sstevel@tonic-gate 	if (val = getenv("_LOGADM_DEBUG"))
218*0Sstevel@tonic-gate 		Debug = atoi(val);
219*0Sstevel@tonic-gate 	if (val = getenv("_LOGADM_SH"))
220*0Sstevel@tonic-gate 		Sh = val;
221*0Sstevel@tonic-gate 	if (val = getenv("_LOGADM_MV"))
222*0Sstevel@tonic-gate 		Mv = val;
223*0Sstevel@tonic-gate 	if (val = getenv("_LOGADM_CP"))
224*0Sstevel@tonic-gate 		Cp = val;
225*0Sstevel@tonic-gate 	if (val = getenv("_LOGADM_RM"))
226*0Sstevel@tonic-gate 		Rm = val;
227*0Sstevel@tonic-gate 	if (val = getenv("_LOGADM_TOUCH"))
228*0Sstevel@tonic-gate 		Touch = val;
229*0Sstevel@tonic-gate 	if (val = getenv("_LOGADM_CHMOD"))
230*0Sstevel@tonic-gate 		Chmod = val;
231*0Sstevel@tonic-gate 	if (val = getenv("_LOGADM_CHOWN"))
232*0Sstevel@tonic-gate 		Chown = val;
233*0Sstevel@tonic-gate 	if (val = getenv("_LOGADM_GZIP"))
234*0Sstevel@tonic-gate 		Gzip = val;
235*0Sstevel@tonic-gate 	if (val = getenv("_LOGADM_MKDIR"))
236*0Sstevel@tonic-gate 		Mkdir = val;
237*0Sstevel@tonic-gate 
238*0Sstevel@tonic-gate 	opts_init(Opttable, sizeof (Opttable) / sizeof (struct optinfo));
239*0Sstevel@tonic-gate 
240*0Sstevel@tonic-gate 	/* parse command line arguments */
241*0Sstevel@tonic-gate 	if (SETJMP)
242*0Sstevel@tonic-gate 		usage("bailing out due to command line errors");
243*0Sstevel@tonic-gate 	else
244*0Sstevel@tonic-gate 		clopts = opts_parse(argv, OPTF_CLI);
245*0Sstevel@tonic-gate 
246*0Sstevel@tonic-gate 	if (Debug) {
247*0Sstevel@tonic-gate 		(void) fprintf(stderr, "command line opts:");
248*0Sstevel@tonic-gate 		opts_print(clopts, stderr, NULL);
249*0Sstevel@tonic-gate 		(void) fprintf(stderr, "\n");
250*0Sstevel@tonic-gate 	}
251*0Sstevel@tonic-gate 
252*0Sstevel@tonic-gate 	/*
253*0Sstevel@tonic-gate 	 * There are many moods of logadm:
254*0Sstevel@tonic-gate 	 *
255*0Sstevel@tonic-gate 	 *	1. "-h" for help was given.  We spew a canned help
256*0Sstevel@tonic-gate 	 *	   message and exit, regardless of any other options given.
257*0Sstevel@tonic-gate 	 *
258*0Sstevel@tonic-gate 	 *	2. "-r" or "-w" asking us to write to the conffile.  Lots
259*0Sstevel@tonic-gate 	 *	   of argument checking, then we make the change to conffile
260*0Sstevel@tonic-gate 	 *	   and exit.  (-r processing actually happens in dologname().)
261*0Sstevel@tonic-gate 	 *
262*0Sstevel@tonic-gate 	 *	3. "-V" to search/verify the conffile was given.  We do
263*0Sstevel@tonic-gate 	 *	   the appropriate run through the conffile and exit.
264*0Sstevel@tonic-gate 	 *	   (-V processing actually happens in dologname().)
265*0Sstevel@tonic-gate 	 *
266*0Sstevel@tonic-gate 	 *	4. No lognames were given, so we're being asked to go through
267*0Sstevel@tonic-gate 	 *	   every entry in conffile.  We verify that only the options
268*0Sstevel@tonic-gate 	 *	   that make sense for this form of the command are present
269*0Sstevel@tonic-gate 	 *	   and fall into the main processing loop below.
270*0Sstevel@tonic-gate 	 *
271*0Sstevel@tonic-gate 	 *	5. lognames were given, so we fall into the main processing
272*0Sstevel@tonic-gate 	 *	   loop below to work our way through them.
273*0Sstevel@tonic-gate 	 *
274*0Sstevel@tonic-gate 	 * The last two cases are where the option processing gets more
275*0Sstevel@tonic-gate 	 * complex.  Each time around the main processing loop, we're
276*0Sstevel@tonic-gate 	 * in one of these cases:
277*0Sstevel@tonic-gate 	 *
278*0Sstevel@tonic-gate 	 *	A. No cmdargs were found (we're in case 4), the entry
279*0Sstevel@tonic-gate 	 *	   in conffile supplies no log file names, so the entry
280*0Sstevel@tonic-gate 	 *	   name itself is the logfile name (or names, if it globs
281*0Sstevel@tonic-gate 	 *	   to multiple file names).
282*0Sstevel@tonic-gate 	 *
283*0Sstevel@tonic-gate 	 *	B. No cmdargs were found (we're in case 4), the entry
284*0Sstevel@tonic-gate 	 *	   in conffile gives log file names that we then loop
285*0Sstevel@tonic-gate 	 *	   through and rotate/expire.  In this case, the entry
286*0Sstevel@tonic-gate 	 *	   name is specifically NOT one of the log file names.
287*0Sstevel@tonic-gate 	 *
288*0Sstevel@tonic-gate 	 *	C. We're going through the cmdargs (we're in case 5),
289*0Sstevel@tonic-gate 	 *	   the entry in conffile either doesn't exist or it exists
290*0Sstevel@tonic-gate 	 *	   but supplies no log file names, so the cmdarg itself
291*0Sstevel@tonic-gate 	 *	   is the log file name.
292*0Sstevel@tonic-gate 	 *
293*0Sstevel@tonic-gate 	 *	D. We're going through the cmdargs (we're in case 5),
294*0Sstevel@tonic-gate 	 *	   a matching entry in conffile supplies log file names
295*0Sstevel@tonic-gate 	 *	   that we then loop through and rotate/expire.  In this
296*0Sstevel@tonic-gate 	 *	   case the entry name is specifically NOT one of the log
297*0Sstevel@tonic-gate 	 *	   file names.
298*0Sstevel@tonic-gate 	 *
299*0Sstevel@tonic-gate 	 * As we're doing all this, any options given on the command line
300*0Sstevel@tonic-gate 	 * override any found in the conffile, and we apply the defaults
301*0Sstevel@tonic-gate 	 * for rotation conditions and expiration conditions, etc. at the
302*0Sstevel@tonic-gate 	 * last opportunity, when we're sure they haven't been overridden
303*0Sstevel@tonic-gate 	 * by an option somewhere along the way.
304*0Sstevel@tonic-gate 	 *
305*0Sstevel@tonic-gate 	 */
306*0Sstevel@tonic-gate 
307*0Sstevel@tonic-gate 	/* help option overrides anything else */
308*0Sstevel@tonic-gate 	if (opts_count(clopts, "h")) {
309*0Sstevel@tonic-gate 		(void) fputs(HELP1, stderr);
310*0Sstevel@tonic-gate 		(void) fputs(HELP2, stderr);
311*0Sstevel@tonic-gate 		err_done(0);
312*0Sstevel@tonic-gate 		/*NOTREACHED*/
313*0Sstevel@tonic-gate 	}
314*0Sstevel@tonic-gate 
315*0Sstevel@tonic-gate 	/* detect illegal option combinations */
316*0Sstevel@tonic-gate 	if (opts_count(clopts, "rwV") > 1)
317*0Sstevel@tonic-gate 		usage("Only one of -r, -w, or -V may be used at a time.");
318*0Sstevel@tonic-gate 	if (opts_count(clopts, "cM") > 1)
319*0Sstevel@tonic-gate 		usage("Only one of -c or -M may be used at a time.");
320*0Sstevel@tonic-gate 
321*0Sstevel@tonic-gate 	/* arrange for error output to be mailed if clopts includes -e */
322*0Sstevel@tonic-gate 	if (opts_count(clopts, "e"))
323*0Sstevel@tonic-gate 		err_mailto(opts_optarg(clopts, "e"));
324*0Sstevel@tonic-gate 
325*0Sstevel@tonic-gate 	/* this implements the default conffile */
326*0Sstevel@tonic-gate 	if ((conffile = opts_optarg(clopts, "f")) == NULL)
327*0Sstevel@tonic-gate 		conffile = Default_conffile;
328*0Sstevel@tonic-gate 	if (opts_count(clopts, "v"))
329*0Sstevel@tonic-gate 		(void) out("# loading %s\n", conffile);
330*0Sstevel@tonic-gate 	conf_open(conffile, opts_count(clopts, "Vn") == 0);
331*0Sstevel@tonic-gate 
332*0Sstevel@tonic-gate 	/* handle conffile write option */
333*0Sstevel@tonic-gate 	if (opts_count(clopts, "w")) {
334*0Sstevel@tonic-gate 		if (Debug)
335*0Sstevel@tonic-gate 			(void) fprintf(stderr,
336*0Sstevel@tonic-gate 			    "main: add/replace conffile entry: <%s>\n",
337*0Sstevel@tonic-gate 			    opts_optarg(clopts, "w"));
338*0Sstevel@tonic-gate 		conf_replace(opts_optarg(clopts, "w"), clopts);
339*0Sstevel@tonic-gate 		conf_close(clopts);
340*0Sstevel@tonic-gate 		err_done(0);
341*0Sstevel@tonic-gate 		/*NOTREACHED*/
342*0Sstevel@tonic-gate 	}
343*0Sstevel@tonic-gate 
344*0Sstevel@tonic-gate 	/*
345*0Sstevel@tonic-gate 	 * lognames is either a list supplied on the command line,
346*0Sstevel@tonic-gate 	 * or every entry in the conffile if none were supplied.
347*0Sstevel@tonic-gate 	 */
348*0Sstevel@tonic-gate 	lognames = opts_cmdargs(clopts);
349*0Sstevel@tonic-gate 	if (fn_list_empty(lognames)) {
350*0Sstevel@tonic-gate 		/*
351*0Sstevel@tonic-gate 		 * being asked to do all entries in conffile
352*0Sstevel@tonic-gate 		 *
353*0Sstevel@tonic-gate 		 * check to see if any options were given that only
354*0Sstevel@tonic-gate 		 * make sense when lognames are given specifically
355*0Sstevel@tonic-gate 		 * on the command line.
356*0Sstevel@tonic-gate 		 */
357*0Sstevel@tonic-gate 		if (opts_count(clopts, OPTIONS_NOT_FIRST_FORM))
358*0Sstevel@tonic-gate 			usage("some options require logname argument");
359*0Sstevel@tonic-gate 		if (Debug)
360*0Sstevel@tonic-gate 			(void) fprintf(stderr,
361*0Sstevel@tonic-gate 			    "main: run all entries in conffile\n");
362*0Sstevel@tonic-gate 		lognames = conf_entries();
363*0Sstevel@tonic-gate 	}
364*0Sstevel@tonic-gate 
365*0Sstevel@tonic-gate 	/* foreach logname... */
366*0Sstevel@tonic-gate 	fn_list_rewind(lognames);
367*0Sstevel@tonic-gate 	while ((fnp = fn_list_next(lognames)) != NULL) {
368*0Sstevel@tonic-gate 		if (lut_lookup(Donenames, fn_s(fnp)) != NULL) {
369*0Sstevel@tonic-gate 			if (Debug)
370*0Sstevel@tonic-gate 				(void) fprintf(stderr,
371*0Sstevel@tonic-gate 				    "main: logname already done: <%s>\n",
372*0Sstevel@tonic-gate 				    fn_s(fnp));
373*0Sstevel@tonic-gate 			continue;
374*0Sstevel@tonic-gate 		}
375*0Sstevel@tonic-gate 		if (SETJMP)
376*0Sstevel@tonic-gate 			err(EF_FILE, "bailing out on logname \"%s\" "
377*0Sstevel@tonic-gate 			    "due to errors", fn_s(fnp));
378*0Sstevel@tonic-gate 		else
379*0Sstevel@tonic-gate 			dologname(fnp, clopts);
380*0Sstevel@tonic-gate 	}
381*0Sstevel@tonic-gate 
382*0Sstevel@tonic-gate 	/* execute any after commands */
383*0Sstevel@tonic-gate 	lut_walk(Aftercmds, doaftercmd, clopts);
384*0Sstevel@tonic-gate 
385*0Sstevel@tonic-gate 	/* write out any conffile changes */
386*0Sstevel@tonic-gate 	conf_close(clopts);
387*0Sstevel@tonic-gate 
388*0Sstevel@tonic-gate 	err_done(0);
389*0Sstevel@tonic-gate 	/*NOTREACHED*/
390*0Sstevel@tonic-gate 	return (0);	/* for lint's little mind */
391*0Sstevel@tonic-gate }
392*0Sstevel@tonic-gate 
393*0Sstevel@tonic-gate /* spew a message, then a usage message, then exit */
394*0Sstevel@tonic-gate static void
395*0Sstevel@tonic-gate usage(const char *msg)
396*0Sstevel@tonic-gate {
397*0Sstevel@tonic-gate 	if (msg)
398*0Sstevel@tonic-gate 		err(0, "%s\nUse \"logadm -h\" for help.", msg);
399*0Sstevel@tonic-gate 	else
400*0Sstevel@tonic-gate 		err(EF_RAW, "Use \"logadm -h\" for help.\n");
401*0Sstevel@tonic-gate }
402*0Sstevel@tonic-gate 
403*0Sstevel@tonic-gate /* helper function used by doaftercmd() to join mail addrs with commas */
404*0Sstevel@tonic-gate /*ARGSUSED1*/
405*0Sstevel@tonic-gate static void
406*0Sstevel@tonic-gate commajoin(const char *lhs, void *rhs, void *arg)
407*0Sstevel@tonic-gate {
408*0Sstevel@tonic-gate 	struct fn *fnp = (struct fn *)arg;
409*0Sstevel@tonic-gate 
410*0Sstevel@tonic-gate 	if (*fn_s(fnp))
411*0Sstevel@tonic-gate 		fn_putc(fnp, ',');
412*0Sstevel@tonic-gate 	fn_puts(fnp, lhs);
413*0Sstevel@tonic-gate }
414*0Sstevel@tonic-gate 
415*0Sstevel@tonic-gate /* helper function used by main() to run "after" commands */
416*0Sstevel@tonic-gate static void
417*0Sstevel@tonic-gate doaftercmd(const char *lhs, void *rhs, void *arg)
418*0Sstevel@tonic-gate {
419*0Sstevel@tonic-gate 	struct opts *opts = (struct opts *)arg;
420*0Sstevel@tonic-gate 	struct lut *addrs = (struct lut *)rhs;
421*0Sstevel@tonic-gate 
422*0Sstevel@tonic-gate 	if (addrs) {
423*0Sstevel@tonic-gate 		struct fn *fnp = fn_new(NULL);
424*0Sstevel@tonic-gate 
425*0Sstevel@tonic-gate 		/*
426*0Sstevel@tonic-gate 		 * addrs contains list of email addrs that should get
427*0Sstevel@tonic-gate 		 * the error output when this after command is executed.
428*0Sstevel@tonic-gate 		 */
429*0Sstevel@tonic-gate 		lut_walk(addrs, commajoin, fnp);
430*0Sstevel@tonic-gate 		err_mailto(fn_s(fnp));
431*0Sstevel@tonic-gate 	}
432*0Sstevel@tonic-gate 
433*0Sstevel@tonic-gate 	docmd(opts, "-a cmd", Sh, "-c", lhs, NULL);
434*0Sstevel@tonic-gate }
435*0Sstevel@tonic-gate 
436*0Sstevel@tonic-gate /* main logname processing */
437*0Sstevel@tonic-gate static void
438*0Sstevel@tonic-gate dologname(struct fn *fnp, struct opts *clopts)
439*0Sstevel@tonic-gate {
440*0Sstevel@tonic-gate 	const char *logname = fn_s(fnp);
441*0Sstevel@tonic-gate 	struct opts *cfopts;
442*0Sstevel@tonic-gate 	struct opts *allopts;
443*0Sstevel@tonic-gate 	struct fn_list *logfiles;
444*0Sstevel@tonic-gate 	struct fn_list *globbedfiles;
445*0Sstevel@tonic-gate 	struct fn *nextfnp;
446*0Sstevel@tonic-gate 
447*0Sstevel@tonic-gate 	/* look up options set by config file */
448*0Sstevel@tonic-gate 	cfopts = conf_opts(logname);
449*0Sstevel@tonic-gate 
450*0Sstevel@tonic-gate 	if (opts_count(clopts, "v"))
451*0Sstevel@tonic-gate 		(void) out("# processing logname: %s\n", logname);
452*0Sstevel@tonic-gate 
453*0Sstevel@tonic-gate 	if (Debug) {
454*0Sstevel@tonic-gate 		(void) fprintf(stderr, "dologname: logname <%s>\n", logname);
455*0Sstevel@tonic-gate 		(void) fprintf(stderr, "conffile opts:");
456*0Sstevel@tonic-gate 		opts_print(cfopts, stderr, NULL);
457*0Sstevel@tonic-gate 		(void) fprintf(stderr, "\n");
458*0Sstevel@tonic-gate 	}
459*0Sstevel@tonic-gate 
460*0Sstevel@tonic-gate 	/* handle conffile lookup option */
461*0Sstevel@tonic-gate 	if (opts_count(clopts, "V")) {
462*0Sstevel@tonic-gate 		/* lookup an entry in conffile */
463*0Sstevel@tonic-gate 		if (Debug)
464*0Sstevel@tonic-gate 			(void) fprintf(stderr,
465*0Sstevel@tonic-gate 			    "dologname: lookup conffile entry\n");
466*0Sstevel@tonic-gate 		if (conf_lookup(logname)) {
467*0Sstevel@tonic-gate 			opts_printword(logname, stdout);
468*0Sstevel@tonic-gate 			opts_print(cfopts, stdout, NULL);
469*0Sstevel@tonic-gate 			(void) out("\n");
470*0Sstevel@tonic-gate 		} else
471*0Sstevel@tonic-gate 			err_exitcode(1);
472*0Sstevel@tonic-gate 		return;
473*0Sstevel@tonic-gate 	}
474*0Sstevel@tonic-gate 
475*0Sstevel@tonic-gate 	/* handle conffile removal option */
476*0Sstevel@tonic-gate 	if (opts_count(clopts, "r")) {
477*0Sstevel@tonic-gate 		if (Debug)
478*0Sstevel@tonic-gate 			(void) fprintf(stderr,
479*0Sstevel@tonic-gate 			    "dologname: remove conffile entry\n");
480*0Sstevel@tonic-gate 		if (conf_lookup(logname))
481*0Sstevel@tonic-gate 			conf_replace(logname, NULL);
482*0Sstevel@tonic-gate 		else
483*0Sstevel@tonic-gate 			err_exitcode(1);
484*0Sstevel@tonic-gate 		return;
485*0Sstevel@tonic-gate 	}
486*0Sstevel@tonic-gate 
487*0Sstevel@tonic-gate 	/* generate combined options */
488*0Sstevel@tonic-gate 	allopts = opts_merge(cfopts, clopts);
489*0Sstevel@tonic-gate 
490*0Sstevel@tonic-gate 	/* arrange for error output to be mailed if allopts includes -e */
491*0Sstevel@tonic-gate 	if (opts_count(allopts, "e"))
492*0Sstevel@tonic-gate 		err_mailto(opts_optarg(allopts, "e"));
493*0Sstevel@tonic-gate 	else
494*0Sstevel@tonic-gate 		err_mailto(NULL);
495*0Sstevel@tonic-gate 
496*0Sstevel@tonic-gate 	/* this implements the default rotation rules */
497*0Sstevel@tonic-gate 	if (opts_count(allopts, "sp") == 0) {
498*0Sstevel@tonic-gate 		if (opts_count(clopts, "v"))
499*0Sstevel@tonic-gate 			(void) out(
500*0Sstevel@tonic-gate 			    "#     using default rotate rules: -s1b -p1w\n");
501*0Sstevel@tonic-gate 		(void) opts_set(allopts, "s", "1b");
502*0Sstevel@tonic-gate 		(void) opts_set(allopts, "p", "1w");
503*0Sstevel@tonic-gate 	}
504*0Sstevel@tonic-gate 
505*0Sstevel@tonic-gate 	/* this implements the default expiration rules */
506*0Sstevel@tonic-gate 	if (opts_count(allopts, "ACS") == 0) {
507*0Sstevel@tonic-gate 		if (opts_count(clopts, "v"))
508*0Sstevel@tonic-gate 			(void) out("#     using default expire rule: -C10\n");
509*0Sstevel@tonic-gate 		(void) opts_set(allopts, "C", "10");
510*0Sstevel@tonic-gate 	}
511*0Sstevel@tonic-gate 
512*0Sstevel@tonic-gate 	/* this implements the default template */
513*0Sstevel@tonic-gate 	if (opts_count(allopts, "t") == 0) {
514*0Sstevel@tonic-gate 		if (opts_count(clopts, "v"))
515*0Sstevel@tonic-gate 			(void) out("#     using default template: $file.$n\n");
516*0Sstevel@tonic-gate 		(void) opts_set(allopts, "t", "$file.$n");
517*0Sstevel@tonic-gate 	}
518*0Sstevel@tonic-gate 
519*0Sstevel@tonic-gate 	if (Debug) {
520*0Sstevel@tonic-gate 		(void) fprintf(stderr, "merged opts:");
521*0Sstevel@tonic-gate 		opts_print(allopts, stderr, NULL);
522*0Sstevel@tonic-gate 		(void) fprintf(stderr, "\n");
523*0Sstevel@tonic-gate 	}
524*0Sstevel@tonic-gate 
525*0Sstevel@tonic-gate 	/*
526*0Sstevel@tonic-gate 	 * if the conffile entry supplied log file names, then
527*0Sstevel@tonic-gate 	 * logname is NOT one of the log file names (it was just
528*0Sstevel@tonic-gate 	 * the entry name in conffile).
529*0Sstevel@tonic-gate 	 */
530*0Sstevel@tonic-gate 	logfiles = opts_cmdargs(cfopts);
531*0Sstevel@tonic-gate 	if (Debug) {
532*0Sstevel@tonic-gate 		(void) fprintf(stderr, "dologname: logfiles from cfopts:\n");
533*0Sstevel@tonic-gate 		fn_list_rewind(logfiles);
534*0Sstevel@tonic-gate 		while ((nextfnp = fn_list_next(logfiles)) != NULL)
535*0Sstevel@tonic-gate 			(void) fprintf(stderr, "    <%s>\n", fn_s(nextfnp));
536*0Sstevel@tonic-gate 	}
537*0Sstevel@tonic-gate 	if (fn_list_empty(logfiles))
538*0Sstevel@tonic-gate 		globbedfiles = glob_glob(fnp);
539*0Sstevel@tonic-gate 	else
540*0Sstevel@tonic-gate 		globbedfiles = glob_glob_list(logfiles);
541*0Sstevel@tonic-gate 
542*0Sstevel@tonic-gate 	/* go through the list produced by glob expansion */
543*0Sstevel@tonic-gate 	fn_list_rewind(globbedfiles);
544*0Sstevel@tonic-gate 	while ((nextfnp = fn_list_next(globbedfiles)) != NULL)
545*0Sstevel@tonic-gate 		if (rotatelog(nextfnp, allopts))
546*0Sstevel@tonic-gate 			expirefiles(nextfnp, allopts);
547*0Sstevel@tonic-gate 
548*0Sstevel@tonic-gate 	fn_list_free(globbedfiles);
549*0Sstevel@tonic-gate 	opts_free(allopts);
550*0Sstevel@tonic-gate }
551*0Sstevel@tonic-gate 
552*0Sstevel@tonic-gate 
553*0Sstevel@tonic-gate /* absurdly long buffer lengths for holding user/group/mode strings */
554*0Sstevel@tonic-gate #define	TIMESTRMAX	100
555*0Sstevel@tonic-gate #define	MAXATTR		100
556*0Sstevel@tonic-gate 
557*0Sstevel@tonic-gate /* rotate a log file if necessary, returns true if ok to go on to expire step */
558*0Sstevel@tonic-gate static boolean_t
559*0Sstevel@tonic-gate rotatelog(struct fn *fnp, struct opts *opts)
560*0Sstevel@tonic-gate {
561*0Sstevel@tonic-gate 	char *fname = fn_s(fnp);
562*0Sstevel@tonic-gate 	struct stat stbuf;
563*0Sstevel@tonic-gate 	char nowstr[TIMESTRMAX];
564*0Sstevel@tonic-gate 	struct fn *recentlog = fn_new(NULL);	/* for -R cmd */
565*0Sstevel@tonic-gate 	char ownerbuf[MAXATTR];
566*0Sstevel@tonic-gate 	char groupbuf[MAXATTR];
567*0Sstevel@tonic-gate 	char modebuf[MAXATTR];
568*0Sstevel@tonic-gate 	const char *owner;
569*0Sstevel@tonic-gate 	const char *group;
570*0Sstevel@tonic-gate 	const char *mode;
571*0Sstevel@tonic-gate 
572*0Sstevel@tonic-gate 	if (Debug)
573*0Sstevel@tonic-gate 		(void) fprintf(stderr, "rotatelog: fname <%s>\n", fname);
574*0Sstevel@tonic-gate 
575*0Sstevel@tonic-gate 	if (opts_count(opts, "p") && opts_optarg_int(opts, "p") == OPTP_NEVER)
576*0Sstevel@tonic-gate 		return (B_TRUE);	/* "-p never" forced no rotate */
577*0Sstevel@tonic-gate 
578*0Sstevel@tonic-gate 	/* prepare the keywords */
579*0Sstevel@tonic-gate 	kw_init(fnp, NULL);
580*0Sstevel@tonic-gate 	if (Debug > 1) {
581*0Sstevel@tonic-gate 		(void) fprintf(stderr, "rotatelog keywords:\n");
582*0Sstevel@tonic-gate 		kw_print(stderr);
583*0Sstevel@tonic-gate 	}
584*0Sstevel@tonic-gate 
585*0Sstevel@tonic-gate 	if (lstat(fname, &stbuf) < 0) {
586*0Sstevel@tonic-gate 		if (opts_count(opts, "N"))
587*0Sstevel@tonic-gate 			return (1);
588*0Sstevel@tonic-gate 		err(EF_WARN|EF_SYS, "%s", fname);
589*0Sstevel@tonic-gate 		return (B_FALSE);
590*0Sstevel@tonic-gate 	}
591*0Sstevel@tonic-gate 
592*0Sstevel@tonic-gate 	if ((stbuf.st_mode & S_IFMT) == S_IFLNK) {
593*0Sstevel@tonic-gate 		err(EF_WARN, "%s is a symlink", fname);
594*0Sstevel@tonic-gate 		return (B_FALSE);
595*0Sstevel@tonic-gate 	}
596*0Sstevel@tonic-gate 
597*0Sstevel@tonic-gate 	if ((stbuf.st_mode & S_IFMT) != S_IFREG) {
598*0Sstevel@tonic-gate 		err(EF_WARN, "%s is not a regular file", fname);
599*0Sstevel@tonic-gate 		return (B_FALSE);
600*0Sstevel@tonic-gate 	}
601*0Sstevel@tonic-gate 
602*0Sstevel@tonic-gate 	/* see if size condition is present, and return if not met */
603*0Sstevel@tonic-gate 	if (opts_count(opts, "s") && stbuf.st_size < opts_optarg_int(opts, "s"))
604*0Sstevel@tonic-gate 		return (B_TRUE);
605*0Sstevel@tonic-gate 
606*0Sstevel@tonic-gate 	/* see if age condition is present, and return if not met */
607*0Sstevel@tonic-gate 	if (opts_count(opts, "p")) {
608*0Sstevel@tonic-gate 		int when = opts_optarg_int(opts, "p");
609*0Sstevel@tonic-gate 		struct opts *cfopts;
610*0Sstevel@tonic-gate 
611*0Sstevel@tonic-gate 		/* unless rotate forced by "-p now", see if period has passed */
612*0Sstevel@tonic-gate 		if (when != OPTP_NOW) {
613*0Sstevel@tonic-gate 			/*
614*0Sstevel@tonic-gate 			 * "when" holds the number of seconds that must have
615*0Sstevel@tonic-gate 			 * passed since the last time this log was rotated.
616*0Sstevel@tonic-gate 			 * of course, running logadm can take a little time
617*0Sstevel@tonic-gate 			 * (typically a second or two, but longer if the
618*0Sstevel@tonic-gate 			 * conffile has lots of stuff in it) and that amount
619*0Sstevel@tonic-gate 			 * of time is variable, depending on system load, etc.
620*0Sstevel@tonic-gate 			 * so we want to allow a little "slop" in the value of
621*0Sstevel@tonic-gate 			 * "when".  this way, if a log should be rotated every
622*0Sstevel@tonic-gate 			 * week, and the number of seconds passed is really a
623*0Sstevel@tonic-gate 			 * few seconds short of a week, we'll go ahead and
624*0Sstevel@tonic-gate 			 * rotate the log as expected.
625*0Sstevel@tonic-gate 			 *
626*0Sstevel@tonic-gate 			 */
627*0Sstevel@tonic-gate 			if (when >= 60 * 60)
628*0Sstevel@tonic-gate 				when -= 59;
629*0Sstevel@tonic-gate 
630*0Sstevel@tonic-gate 			/*
631*0Sstevel@tonic-gate 			 * last rotation is recorded as argument to -P,
632*0Sstevel@tonic-gate 			 * but if logname isn't the same as log file name
633*0Sstevel@tonic-gate 			 * then the timestamp would be recorded on a
634*0Sstevel@tonic-gate 			 * separate line in the conf file.  so if we
635*0Sstevel@tonic-gate 			 * haven't seen a -P already, we check to see if
636*0Sstevel@tonic-gate 			 * it is part of a specific entry for the log
637*0Sstevel@tonic-gate 			 * file name.  this handles the case where the
638*0Sstevel@tonic-gate 			 * logname is "apache", it supplies a log file
639*0Sstevel@tonic-gate 			 * name like "/var/apache/logs/[a-z]*_log",
640*0Sstevel@tonic-gate 			 * which expands to multiple file names.  if one
641*0Sstevel@tonic-gate 			 * of the file names is "/var/apache/logs/access_log"
642*0Sstevel@tonic-gate 			 * the the -P will be attached to a line with that
643*0Sstevel@tonic-gate 			 * logname in the conf file.
644*0Sstevel@tonic-gate 			 */
645*0Sstevel@tonic-gate 			if (opts_count(opts, "P")) {
646*0Sstevel@tonic-gate 				int last = opts_optarg_int(opts, "P");
647*0Sstevel@tonic-gate 
648*0Sstevel@tonic-gate 				/* return if not enough time has passed */
649*0Sstevel@tonic-gate 				if (Now - last < when)
650*0Sstevel@tonic-gate 					return (B_TRUE);
651*0Sstevel@tonic-gate 			} else if ((cfopts = conf_opts(fname)) != NULL &&
652*0Sstevel@tonic-gate 			    opts_count(cfopts, "P")) {
653*0Sstevel@tonic-gate 				int last = opts_optarg_int(cfopts, "P");
654*0Sstevel@tonic-gate 
655*0Sstevel@tonic-gate 				/*
656*0Sstevel@tonic-gate 				 * just checking this means this entry
657*0Sstevel@tonic-gate 				 * is now "done" if we're going through
658*0Sstevel@tonic-gate 				 * the entire conffile
659*0Sstevel@tonic-gate 				 */
660*0Sstevel@tonic-gate 				Donenames = lut_add(Donenames, fname, "1");
661*0Sstevel@tonic-gate 
662*0Sstevel@tonic-gate 				/* return if not enough time has passed */
663*0Sstevel@tonic-gate 				if (Now - last < when)
664*0Sstevel@tonic-gate 					return (B_TRUE);
665*0Sstevel@tonic-gate 			}
666*0Sstevel@tonic-gate 		}
667*0Sstevel@tonic-gate 	}
668*0Sstevel@tonic-gate 
669*0Sstevel@tonic-gate 	if (Debug)
670*0Sstevel@tonic-gate 		(void) fprintf(stderr, "rotatelog: conditions met\n");
671*0Sstevel@tonic-gate 
672*0Sstevel@tonic-gate 	/* rename the log file */
673*0Sstevel@tonic-gate 	rotateto(fnp, opts, 0, recentlog, B_FALSE);
674*0Sstevel@tonic-gate 
675*0Sstevel@tonic-gate 	/* determine owner, group, mode for empty log file */
676*0Sstevel@tonic-gate 	if (opts_count(opts, "o"))
677*0Sstevel@tonic-gate 		(void) strlcpy(ownerbuf, opts_optarg(opts, "o"), MAXATTR);
678*0Sstevel@tonic-gate 	else {
679*0Sstevel@tonic-gate 		(void) snprintf(ownerbuf, MAXATTR, "%ld", stbuf.st_uid);
680*0Sstevel@tonic-gate 	}
681*0Sstevel@tonic-gate 	owner = ownerbuf;
682*0Sstevel@tonic-gate 	if (opts_count(opts, "g"))
683*0Sstevel@tonic-gate 		group = opts_optarg(opts, "g");
684*0Sstevel@tonic-gate 	else {
685*0Sstevel@tonic-gate 		(void) snprintf(groupbuf, MAXATTR, "%ld", stbuf.st_gid);
686*0Sstevel@tonic-gate 		group = groupbuf;
687*0Sstevel@tonic-gate 	}
688*0Sstevel@tonic-gate 	(void) strlcat(ownerbuf, ":", MAXATTR - strlen(ownerbuf));
689*0Sstevel@tonic-gate 	(void) strlcat(ownerbuf, group, MAXATTR - strlen(ownerbuf));
690*0Sstevel@tonic-gate 	if (opts_count(opts, "m"))
691*0Sstevel@tonic-gate 		mode = opts_optarg(opts, "m");
692*0Sstevel@tonic-gate 	else {
693*0Sstevel@tonic-gate 		(void) snprintf(modebuf, MAXATTR,
694*0Sstevel@tonic-gate 		    "%03lo", stbuf.st_mode & 0777);
695*0Sstevel@tonic-gate 		mode = modebuf;
696*0Sstevel@tonic-gate 	}
697*0Sstevel@tonic-gate 
698*0Sstevel@tonic-gate 	/* create the empty log file */
699*0Sstevel@tonic-gate 	docmd(opts, NULL, Touch, fname, NULL, NULL);
700*0Sstevel@tonic-gate 	docmd(opts, NULL, Chown, owner, fname, NULL);
701*0Sstevel@tonic-gate 	docmd(opts, NULL, Chmod, mode, fname, NULL);
702*0Sstevel@tonic-gate 
703*0Sstevel@tonic-gate 	/* execute post-rotation command */
704*0Sstevel@tonic-gate 	if (opts_count(opts, "R")) {
705*0Sstevel@tonic-gate 		struct fn *rawcmd = fn_new(opts_optarg(opts, "R"));
706*0Sstevel@tonic-gate 		struct fn *cmd = fn_new(NULL);
707*0Sstevel@tonic-gate 
708*0Sstevel@tonic-gate 		kw_init(recentlog, NULL);
709*0Sstevel@tonic-gate 		(void) kw_expand(rawcmd, cmd, 0, B_FALSE);
710*0Sstevel@tonic-gate 		docmd(opts, "-R cmd", Sh, "-c", fn_s(cmd), NULL);
711*0Sstevel@tonic-gate 		fn_free(rawcmd);
712*0Sstevel@tonic-gate 		fn_free(cmd);
713*0Sstevel@tonic-gate 	}
714*0Sstevel@tonic-gate 	fn_free(recentlog);
715*0Sstevel@tonic-gate 
716*0Sstevel@tonic-gate 	/*
717*0Sstevel@tonic-gate 	 * add "after" command to list of after commands.  we also record
718*0Sstevel@tonic-gate 	 * the email address, if any, where the error output of the after
719*0Sstevel@tonic-gate 	 * command should be sent.  if the after command is already on
720*0Sstevel@tonic-gate 	 * our list, add the email addr to the list the email addrs for
721*0Sstevel@tonic-gate 	 * that command (the after command will only be executed once,
722*0Sstevel@tonic-gate 	 * so the error output gets mailed to every address we've come
723*0Sstevel@tonic-gate 	 * across associated with this command).
724*0Sstevel@tonic-gate 	 */
725*0Sstevel@tonic-gate 	if (opts_count(opts, "a")) {
726*0Sstevel@tonic-gate 		const char *cmd = opts_optarg(opts, "a");
727*0Sstevel@tonic-gate 		struct lut *addrs = (struct lut *)lut_lookup(Aftercmds, cmd);
728*0Sstevel@tonic-gate 		if (opts_count(opts, "e"))
729*0Sstevel@tonic-gate 			addrs = lut_add(addrs, opts_optarg(opts, "e"), NULL);
730*0Sstevel@tonic-gate 		Aftercmds = lut_add(Aftercmds, opts_optarg(opts, "a"), addrs);
731*0Sstevel@tonic-gate 	}
732*0Sstevel@tonic-gate 
733*0Sstevel@tonic-gate 	/* record the rotation date */
734*0Sstevel@tonic-gate 	(void) strftime(nowstr, sizeof (nowstr),
735*0Sstevel@tonic-gate 	    "%a %b %e %T %Y", gmtime(&Now));
736*0Sstevel@tonic-gate 	if (opts_count(opts, "v"))
737*0Sstevel@tonic-gate 		(void) out("#     recording rotation date %s for %s\n",
738*0Sstevel@tonic-gate 		    nowstr, fname);
739*0Sstevel@tonic-gate 	conf_set(fname, "P", STRDUP(nowstr));
740*0Sstevel@tonic-gate 	Donenames = lut_add(Donenames, fname, "1");
741*0Sstevel@tonic-gate 	return (B_TRUE);
742*0Sstevel@tonic-gate }
743*0Sstevel@tonic-gate 
744*0Sstevel@tonic-gate /* rotate files "up" according to current template */
745*0Sstevel@tonic-gate static void
746*0Sstevel@tonic-gate rotateto(struct fn *fnp, struct opts *opts, int n, struct fn *recentlog,
747*0Sstevel@tonic-gate     boolean_t isgz)
748*0Sstevel@tonic-gate {
749*0Sstevel@tonic-gate 	struct fn *template = fn_new(opts_optarg(opts, "t"));
750*0Sstevel@tonic-gate 	struct fn *newfile = fn_new(NULL);
751*0Sstevel@tonic-gate 	struct fn *dirname;
752*0Sstevel@tonic-gate 	int hasn;
753*0Sstevel@tonic-gate 	struct stat stbuf;
754*0Sstevel@tonic-gate 
755*0Sstevel@tonic-gate 	/* expand template to figure out new filename */
756*0Sstevel@tonic-gate 	hasn = kw_expand(template, newfile, n, isgz);
757*0Sstevel@tonic-gate 
758*0Sstevel@tonic-gate 	if (Debug)
759*0Sstevel@tonic-gate 		(void) fprintf(stderr, "rotateto: %s -> %s (%d)\n", fn_s(fnp),
760*0Sstevel@tonic-gate 		    fn_s(newfile), n);
761*0Sstevel@tonic-gate 
762*0Sstevel@tonic-gate 	/* if filename is there already, rotate "up" */
763*0Sstevel@tonic-gate 	if (hasn && lstat(fn_s(newfile), &stbuf) != -1)
764*0Sstevel@tonic-gate 		rotateto(newfile, opts, n + 1, recentlog, isgz);
765*0Sstevel@tonic-gate 	else if (hasn && opts_count(opts, "z")) {
766*0Sstevel@tonic-gate 		struct fn *gzfnp = fn_dup(newfile);
767*0Sstevel@tonic-gate 		/*
768*0Sstevel@tonic-gate 		 * since we're compressing old files, see if we
769*0Sstevel@tonic-gate 		 * about to rotate into one.
770*0Sstevel@tonic-gate 		 */
771*0Sstevel@tonic-gate 		fn_puts(gzfnp, ".gz");
772*0Sstevel@tonic-gate 		if (lstat(fn_s(gzfnp), &stbuf) != -1)
773*0Sstevel@tonic-gate 			rotateto(gzfnp, opts, n + 1, recentlog, B_TRUE);
774*0Sstevel@tonic-gate 		fn_free(gzfnp);
775*0Sstevel@tonic-gate 	}
776*0Sstevel@tonic-gate 
777*0Sstevel@tonic-gate 	/* first time through run "before" cmd if not run already */
778*0Sstevel@tonic-gate 	if (n == 0 && opts_count(opts, "b")) {
779*0Sstevel@tonic-gate 		const char *cmd = opts_optarg(opts, "b");
780*0Sstevel@tonic-gate 
781*0Sstevel@tonic-gate 		if (lut_lookup(Beforecmds, cmd) == NULL) {
782*0Sstevel@tonic-gate 			docmd(opts, "-b cmd", Sh, "-c", cmd, NULL);
783*0Sstevel@tonic-gate 			Beforecmds = lut_add(Beforecmds, cmd, "1");
784*0Sstevel@tonic-gate 		}
785*0Sstevel@tonic-gate 	}
786*0Sstevel@tonic-gate 
787*0Sstevel@tonic-gate 	/* ensure destination directory exists */
788*0Sstevel@tonic-gate 	dirname = fn_dirname(newfile);
789*0Sstevel@tonic-gate 	docmd(opts, "verify directory exists", Mkdir, "-p",
790*0Sstevel@tonic-gate 	    fn_s(dirname), NULL);
791*0Sstevel@tonic-gate 	fn_free(dirname);
792*0Sstevel@tonic-gate 
793*0Sstevel@tonic-gate 	/* do the rename */
794*0Sstevel@tonic-gate 	if (opts_count(opts, "c")) {
795*0Sstevel@tonic-gate 		docmd(opts, "rotate log file via copy (-c flag)",
796*0Sstevel@tonic-gate 		    Cp, "-fp", fn_s(fnp), fn_s(newfile));
797*0Sstevel@tonic-gate 		docmd(opts, "truncate log file (-c flag)",
798*0Sstevel@tonic-gate 		    Cp, "-f", "/dev/null", fn_s(fnp));
799*0Sstevel@tonic-gate 	} else if (n == 0 && opts_count(opts, "M")) {
800*0Sstevel@tonic-gate 		struct fn *rawcmd = fn_new(opts_optarg(opts, "M"));
801*0Sstevel@tonic-gate 		struct fn *cmd = fn_new(NULL);
802*0Sstevel@tonic-gate 
803*0Sstevel@tonic-gate 		/* use specified command to mv the log file */
804*0Sstevel@tonic-gate 		kw_init(fnp, newfile);
805*0Sstevel@tonic-gate 		(void) kw_expand(rawcmd, cmd, 0, B_FALSE);
806*0Sstevel@tonic-gate 		docmd(opts, "-M cmd", Sh, "-c", fn_s(cmd), NULL);
807*0Sstevel@tonic-gate 		fn_free(rawcmd);
808*0Sstevel@tonic-gate 		fn_free(cmd);
809*0Sstevel@tonic-gate 	} else
810*0Sstevel@tonic-gate 		/* common case: we call "mv" to handle the actual rename */
811*0Sstevel@tonic-gate 		docmd(opts, "rotate log file", Mv, "-f",
812*0Sstevel@tonic-gate 		    fn_s(fnp), fn_s(newfile));
813*0Sstevel@tonic-gate 
814*0Sstevel@tonic-gate 	/* gzip the old log file  according to -z count */
815*0Sstevel@tonic-gate 	if (!isgz && opts_count(opts, "z")) {
816*0Sstevel@tonic-gate 		int count = opts_optarg_int(opts, "z");
817*0Sstevel@tonic-gate 
818*0Sstevel@tonic-gate 		if (Debug)
819*0Sstevel@tonic-gate 			(void) fprintf(stderr, "rotateto z count %d\n", count);
820*0Sstevel@tonic-gate 
821*0Sstevel@tonic-gate 		if (count <= n) {
822*0Sstevel@tonic-gate 			docmd(opts, "compress old log (-z flag)", Gzip,
823*0Sstevel@tonic-gate 			    "-f", fn_s(newfile), NULL);
824*0Sstevel@tonic-gate 			fn_puts(newfile, ".gz");
825*0Sstevel@tonic-gate 		}
826*0Sstevel@tonic-gate 	}
827*0Sstevel@tonic-gate 
828*0Sstevel@tonic-gate 	/* first time through, gather interesting info for caller */
829*0Sstevel@tonic-gate 	if (n == 0)
830*0Sstevel@tonic-gate 		fn_renew(recentlog, fn_s(newfile));
831*0Sstevel@tonic-gate }
832*0Sstevel@tonic-gate 
833*0Sstevel@tonic-gate /* expire phase of logname processing */
834*0Sstevel@tonic-gate static void
835*0Sstevel@tonic-gate expirefiles(struct fn *fnp, struct opts *opts)
836*0Sstevel@tonic-gate {
837*0Sstevel@tonic-gate 	char *fname = fn_s(fnp);
838*0Sstevel@tonic-gate 	struct fn *template;
839*0Sstevel@tonic-gate 	struct fn *pattern;
840*0Sstevel@tonic-gate 	struct fn_list *files;
841*0Sstevel@tonic-gate 	struct fn *nextfnp;
842*0Sstevel@tonic-gate 	int count;
843*0Sstevel@tonic-gate 	size_t size;
844*0Sstevel@tonic-gate 
845*0Sstevel@tonic-gate 	if (Debug)
846*0Sstevel@tonic-gate 		(void) fprintf(stderr, "expirefiles: fname <%s>\n", fname);
847*0Sstevel@tonic-gate 
848*0Sstevel@tonic-gate 	/* return if no potential expire conditions */
849*0Sstevel@tonic-gate 	if (opts_count(opts, "AS") == 0 && opts_optarg_int(opts, "C") == 0)
850*0Sstevel@tonic-gate 		return;
851*0Sstevel@tonic-gate 
852*0Sstevel@tonic-gate 	kw_init(fnp, NULL);
853*0Sstevel@tonic-gate 	if (Debug > 1) {
854*0Sstevel@tonic-gate 		(void) fprintf(stderr, "expirefiles keywords:\n");
855*0Sstevel@tonic-gate 		kw_print(stderr);
856*0Sstevel@tonic-gate 	}
857*0Sstevel@tonic-gate 
858*0Sstevel@tonic-gate 	/* see if pattern was supplied by user */
859*0Sstevel@tonic-gate 	if (opts_count(opts, "T")) {
860*0Sstevel@tonic-gate 		template = fn_new(opts_optarg(opts, "T"));
861*0Sstevel@tonic-gate 		pattern = glob_to_reglob(template);
862*0Sstevel@tonic-gate 	} else {
863*0Sstevel@tonic-gate 		/* nope, generate pattern based on rotation template */
864*0Sstevel@tonic-gate 		template = fn_new(opts_optarg(opts, "t"));
865*0Sstevel@tonic-gate 		pattern = fn_new(NULL);
866*0Sstevel@tonic-gate 		(void) kw_expand(template, pattern, -1,
867*0Sstevel@tonic-gate 		    opts_count(opts, "z") != 0);
868*0Sstevel@tonic-gate 	}
869*0Sstevel@tonic-gate 
870*0Sstevel@tonic-gate 	/* match all old log files (hopefully not any others as well!) */
871*0Sstevel@tonic-gate 	files = glob_reglob(pattern);
872*0Sstevel@tonic-gate 
873*0Sstevel@tonic-gate 	if (Debug) {
874*0Sstevel@tonic-gate 		(void) fprintf(stderr, "expirefiles: pattern <%s>\n",
875*0Sstevel@tonic-gate 		    fn_s(pattern));
876*0Sstevel@tonic-gate 		fn_list_rewind(files);
877*0Sstevel@tonic-gate 		while ((nextfnp = fn_list_next(files)) != NULL)
878*0Sstevel@tonic-gate 			(void) fprintf(stderr, "    <%s>\n", fn_s(nextfnp));
879*0Sstevel@tonic-gate 	}
880*0Sstevel@tonic-gate 
881*0Sstevel@tonic-gate 	/* see if count causes expiration */
882*0Sstevel@tonic-gate 	if ((count = opts_optarg_int(opts, "C")) > 0) {
883*0Sstevel@tonic-gate 		int needexpire = fn_list_count(files) - count;
884*0Sstevel@tonic-gate 
885*0Sstevel@tonic-gate 		if (Debug)
886*0Sstevel@tonic-gate 			(void) fprintf(stderr, "expirefiles: needexpire %d\n",
887*0Sstevel@tonic-gate 			    needexpire);
888*0Sstevel@tonic-gate 
889*0Sstevel@tonic-gate 		while (needexpire > 0 &&
890*0Sstevel@tonic-gate 		    ((nextfnp = fn_list_popoldest(files)) != NULL)) {
891*0Sstevel@tonic-gate 			dorm(opts, "expire by count rule", nextfnp);
892*0Sstevel@tonic-gate 			fn_free(nextfnp);
893*0Sstevel@tonic-gate 			needexpire--;
894*0Sstevel@tonic-gate 		}
895*0Sstevel@tonic-gate 	}
896*0Sstevel@tonic-gate 
897*0Sstevel@tonic-gate 	/* see if total size causes expiration */
898*0Sstevel@tonic-gate 	if (opts_count(opts, "S") && (size = opts_optarg_int(opts, "S")) > 0) {
899*0Sstevel@tonic-gate 		while (fn_list_totalsize(files) > size &&
900*0Sstevel@tonic-gate 		    ((nextfnp = fn_list_popoldest(files)) != NULL)) {
901*0Sstevel@tonic-gate 				dorm(opts, "expire by size rule", nextfnp);
902*0Sstevel@tonic-gate 				fn_free(nextfnp);
903*0Sstevel@tonic-gate 		}
904*0Sstevel@tonic-gate 	}
905*0Sstevel@tonic-gate 
906*0Sstevel@tonic-gate 	/* see if age causes expiration */
907*0Sstevel@tonic-gate 	if (opts_count(opts, "A")) {
908*0Sstevel@tonic-gate 		int mtime = (int)time(0) - opts_optarg_int(opts, "A");
909*0Sstevel@tonic-gate 
910*0Sstevel@tonic-gate 		while ((nextfnp = fn_list_popoldest(files)) != NULL)
911*0Sstevel@tonic-gate 			if (fn_getstat(nextfnp)->st_mtime < mtime) {
912*0Sstevel@tonic-gate 				dorm(opts, "expire by age rule", nextfnp);
913*0Sstevel@tonic-gate 				fn_free(nextfnp);
914*0Sstevel@tonic-gate 			} else {
915*0Sstevel@tonic-gate 				fn_free(nextfnp);
916*0Sstevel@tonic-gate 				break;
917*0Sstevel@tonic-gate 			}
918*0Sstevel@tonic-gate 	}
919*0Sstevel@tonic-gate 
920*0Sstevel@tonic-gate 	fn_free(template);
921*0Sstevel@tonic-gate 	fn_list_free(files);
922*0Sstevel@tonic-gate }
923*0Sstevel@tonic-gate 
924*0Sstevel@tonic-gate /* execute a command to remove an expired log file */
925*0Sstevel@tonic-gate static void
926*0Sstevel@tonic-gate dorm(struct opts *opts, const char *msg, struct fn *fnp)
927*0Sstevel@tonic-gate {
928*0Sstevel@tonic-gate 	if (opts_count(opts, "E")) {
929*0Sstevel@tonic-gate 		struct fn *rawcmd = fn_new(opts_optarg(opts, "E"));
930*0Sstevel@tonic-gate 		struct fn *cmd = fn_new(NULL);
931*0Sstevel@tonic-gate 
932*0Sstevel@tonic-gate 		/* user supplied cmd, expand $file */
933*0Sstevel@tonic-gate 		kw_init(fnp, NULL);
934*0Sstevel@tonic-gate 		(void) kw_expand(rawcmd, cmd, 0, B_FALSE);
935*0Sstevel@tonic-gate 		docmd(opts, msg, Sh, "-c", fn_s(cmd), NULL);
936*0Sstevel@tonic-gate 		fn_free(rawcmd);
937*0Sstevel@tonic-gate 		fn_free(cmd);
938*0Sstevel@tonic-gate 	} else
939*0Sstevel@tonic-gate 		docmd(opts, msg, Rm, "-f", fn_s(fnp), NULL);
940*0Sstevel@tonic-gate }
941*0Sstevel@tonic-gate 
942*0Sstevel@tonic-gate /* execute a command, producing -n and -v output as necessary */
943*0Sstevel@tonic-gate static void
944*0Sstevel@tonic-gate docmd(struct opts *opts, const char *msg, const char *cmd,
945*0Sstevel@tonic-gate     const char *arg1, const char *arg2, const char *arg3)
946*0Sstevel@tonic-gate {
947*0Sstevel@tonic-gate 	int pid;
948*0Sstevel@tonic-gate 	int errpipe[2];
949*0Sstevel@tonic-gate 
950*0Sstevel@tonic-gate 	/* print info about command if necessary */
951*0Sstevel@tonic-gate 	if (opts_count(opts, "vn")) {
952*0Sstevel@tonic-gate 		const char *simplecmd;
953*0Sstevel@tonic-gate 
954*0Sstevel@tonic-gate 		if ((simplecmd = strrchr(cmd, '/')) == NULL)
955*0Sstevel@tonic-gate 			simplecmd = cmd;
956*0Sstevel@tonic-gate 		else
957*0Sstevel@tonic-gate 			simplecmd++;
958*0Sstevel@tonic-gate 		(void) out("%s", simplecmd);
959*0Sstevel@tonic-gate 		if (arg1)
960*0Sstevel@tonic-gate 			(void) out(" %s", arg1);
961*0Sstevel@tonic-gate 		if (arg2)
962*0Sstevel@tonic-gate 			(void) out(" %s", arg2);
963*0Sstevel@tonic-gate 		if (arg3)
964*0Sstevel@tonic-gate 			(void) out(" %s", arg3);
965*0Sstevel@tonic-gate 		if (msg)
966*0Sstevel@tonic-gate 			(void) out(" # %s", msg);
967*0Sstevel@tonic-gate 		(void) out("\n");
968*0Sstevel@tonic-gate 	}
969*0Sstevel@tonic-gate 
970*0Sstevel@tonic-gate 	if (opts_count(opts, "n"))
971*0Sstevel@tonic-gate 		return;		/* -n means don't really do it */
972*0Sstevel@tonic-gate 
973*0Sstevel@tonic-gate 	/*
974*0Sstevel@tonic-gate 	 * run the cmd and see if it failed.  this function is *not* a
975*0Sstevel@tonic-gate 	 * generic command runner -- we depend on some knowledge we
976*0Sstevel@tonic-gate 	 * have about the commands we run.  first of all, we expect
977*0Sstevel@tonic-gate 	 * errors to spew something to stderr, and that something is
978*0Sstevel@tonic-gate 	 * typically short enough to fit into a pipe so we can wait()
979*0Sstevel@tonic-gate 	 * for the command to complete and then fetch the error text
980*0Sstevel@tonic-gate 	 * from the pipe.  we also expect the exit codes to make sense.
981*0Sstevel@tonic-gate 	 * notice also that we only allow a command name which is an
982*0Sstevel@tonic-gate 	 * absolute pathname, and two args must be supplied (the
983*0Sstevel@tonic-gate 	 * second may be NULL, or they may both be NULL).
984*0Sstevel@tonic-gate 	 */
985*0Sstevel@tonic-gate 	if (pipe(errpipe) < 0)
986*0Sstevel@tonic-gate 		err(EF_SYS, "pipe");
987*0Sstevel@tonic-gate 
988*0Sstevel@tonic-gate 	if ((pid = fork()) < 0)
989*0Sstevel@tonic-gate 		err(EF_SYS, "fork");
990*0Sstevel@tonic-gate 	else if (pid) {
991*0Sstevel@tonic-gate 		int wstat;
992*0Sstevel@tonic-gate 		int count;
993*0Sstevel@tonic-gate 
994*0Sstevel@tonic-gate 		/* parent */
995*0Sstevel@tonic-gate 		(void) close(errpipe[1]);
996*0Sstevel@tonic-gate 		if (waitpid(pid, &wstat, 0) < 0)
997*0Sstevel@tonic-gate 			err(EF_SYS, "waitpid");
998*0Sstevel@tonic-gate 
999*0Sstevel@tonic-gate 		/* check for stderr output */
1000*0Sstevel@tonic-gate 		if (ioctl(errpipe[0], FIONREAD, &count) >= 0 && count) {
1001*0Sstevel@tonic-gate 			err(EF_WARN, "command failed: %s%s%s%s%s%s%s",
1002*0Sstevel@tonic-gate 				cmd,
1003*0Sstevel@tonic-gate 				(arg1) ? " " : "",
1004*0Sstevel@tonic-gate 				(arg1) ? arg1 : "",
1005*0Sstevel@tonic-gate 				(arg2) ? " " : "",
1006*0Sstevel@tonic-gate 				(arg2) ? arg2 : "",
1007*0Sstevel@tonic-gate 				(arg3) ? " " : "",
1008*0Sstevel@tonic-gate 				(arg3) ? arg3 : "");
1009*0Sstevel@tonic-gate 			err_fromfd(errpipe[0]);
1010*0Sstevel@tonic-gate 		} else if (WIFSIGNALED(wstat))
1011*0Sstevel@tonic-gate 			err(EF_WARN,
1012*0Sstevel@tonic-gate 			    "command died, signal %d: %s%s%s%s%s%s%s",
1013*0Sstevel@tonic-gate 				WTERMSIG(wstat),
1014*0Sstevel@tonic-gate 				cmd,
1015*0Sstevel@tonic-gate 				(arg1) ? " " : "",
1016*0Sstevel@tonic-gate 				(arg1) ? arg1 : "",
1017*0Sstevel@tonic-gate 				(arg2) ? " " : "",
1018*0Sstevel@tonic-gate 				(arg2) ? arg2 : "",
1019*0Sstevel@tonic-gate 				(arg3) ? " " : "",
1020*0Sstevel@tonic-gate 				(arg3) ? arg3 : "");
1021*0Sstevel@tonic-gate 		else if (WIFEXITED(wstat) && WEXITSTATUS(wstat))
1022*0Sstevel@tonic-gate 			err(EF_WARN,
1023*0Sstevel@tonic-gate 			    "command error, exit %d: %s%s%s%s%s%s%s",
1024*0Sstevel@tonic-gate 				WEXITSTATUS(wstat),
1025*0Sstevel@tonic-gate 				cmd,
1026*0Sstevel@tonic-gate 				(arg1) ? " " : "",
1027*0Sstevel@tonic-gate 				(arg1) ? arg1 : "",
1028*0Sstevel@tonic-gate 				(arg2) ? " " : "",
1029*0Sstevel@tonic-gate 				(arg2) ? arg2 : "",
1030*0Sstevel@tonic-gate 				(arg3) ? " " : "",
1031*0Sstevel@tonic-gate 				(arg3) ? arg3 : "");
1032*0Sstevel@tonic-gate 
1033*0Sstevel@tonic-gate 		(void) close(errpipe[0]);
1034*0Sstevel@tonic-gate 	} else {
1035*0Sstevel@tonic-gate 		/* child */
1036*0Sstevel@tonic-gate 		(void) dup2(errpipe[1], fileno(stderr));
1037*0Sstevel@tonic-gate 		(void) close(errpipe[0]);
1038*0Sstevel@tonic-gate 		(void) execl(cmd, cmd, arg1, arg2, arg3, 0);
1039*0Sstevel@tonic-gate 		perror(cmd);
1040*0Sstevel@tonic-gate 		_exit(1);
1041*0Sstevel@tonic-gate 	}
1042*0Sstevel@tonic-gate }
1043