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*3404Snakanon * Copyright 2007 Sun Microsystems, Inc. All rights reserved. 230Sstevel@tonic-gate * Use is subject to license terms. 240Sstevel@tonic-gate * 250Sstevel@tonic-gate * logadm/main.c -- main routines for logadm 260Sstevel@tonic-gate * 270Sstevel@tonic-gate * this program is 90% argument processing, 10% actions... 280Sstevel@tonic-gate */ 290Sstevel@tonic-gate 300Sstevel@tonic-gate #pragma ident "%Z%%M% %I% %E% SMI" 310Sstevel@tonic-gate 320Sstevel@tonic-gate #include <stdio.h> 330Sstevel@tonic-gate #include <stdlib.h> 340Sstevel@tonic-gate #include <unistd.h> 350Sstevel@tonic-gate #include <strings.h> 360Sstevel@tonic-gate #include <libintl.h> 370Sstevel@tonic-gate #include <locale.h> 380Sstevel@tonic-gate #include <sys/types.h> 390Sstevel@tonic-gate #include <sys/stat.h> 400Sstevel@tonic-gate #include <sys/wait.h> 410Sstevel@tonic-gate #include <sys/filio.h> 420Sstevel@tonic-gate #include <time.h> 430Sstevel@tonic-gate #include "err.h" 440Sstevel@tonic-gate #include "lut.h" 450Sstevel@tonic-gate #include "fn.h" 460Sstevel@tonic-gate #include "opts.h" 470Sstevel@tonic-gate #include "conf.h" 480Sstevel@tonic-gate #include "glob.h" 490Sstevel@tonic-gate #include "kw.h" 500Sstevel@tonic-gate 510Sstevel@tonic-gate /* forward declarations for functions in this file */ 520Sstevel@tonic-gate static void usage(const char *msg); 530Sstevel@tonic-gate static void commajoin(const char *lhs, void *rhs, void *arg); 540Sstevel@tonic-gate static void doaftercmd(const char *lhs, void *rhs, void *arg); 550Sstevel@tonic-gate static void dologname(struct fn *fnp, struct opts *clopts); 560Sstevel@tonic-gate static boolean_t rotatelog(struct fn *fnp, struct opts *opts); 570Sstevel@tonic-gate static void rotateto(struct fn *fnp, struct opts *opts, int n, 580Sstevel@tonic-gate struct fn *recentlog, boolean_t isgz); 591913Sdg199075 static void do_delayed_gzip(const char *lhs, void *rhs, void *arg); 600Sstevel@tonic-gate static void expirefiles(struct fn *fnp, struct opts *opts); 610Sstevel@tonic-gate static void dorm(struct opts *opts, const char *msg, struct fn *fnp); 620Sstevel@tonic-gate static void docmd(struct opts *opts, const char *msg, const char *cmd, 630Sstevel@tonic-gate const char *arg1, const char *arg2, const char *arg3); 640Sstevel@tonic-gate 650Sstevel@tonic-gate /* our configuration file, unless otherwise specified by -f */ 660Sstevel@tonic-gate static char *Default_conffile = "/etc/logadm.conf"; 670Sstevel@tonic-gate 680Sstevel@tonic-gate /* default pathnames to the commands we invoke */ 690Sstevel@tonic-gate static char *Sh = "/bin/sh"; 700Sstevel@tonic-gate static char *Mv = "/bin/mv"; 710Sstevel@tonic-gate static char *Cp = "/bin/cp"; 720Sstevel@tonic-gate static char *Rm = "/bin/rm"; 730Sstevel@tonic-gate static char *Touch = "/bin/touch"; 740Sstevel@tonic-gate static char *Chmod = "/bin/chmod"; 750Sstevel@tonic-gate static char *Chown = "/bin/chown"; 760Sstevel@tonic-gate static char *Gzip = "/bin/gzip"; 770Sstevel@tonic-gate static char *Mkdir = "/bin/mkdir"; 780Sstevel@tonic-gate 790Sstevel@tonic-gate /* return from time(0), gathered early on to avoid slewed timestamps */ 800Sstevel@tonic-gate time_t Now; 810Sstevel@tonic-gate 820Sstevel@tonic-gate /* list of before commands that have been executed */ 830Sstevel@tonic-gate static struct lut *Beforecmds; 840Sstevel@tonic-gate 850Sstevel@tonic-gate /* list of after commands to execute before exiting */ 860Sstevel@tonic-gate static struct lut *Aftercmds; 870Sstevel@tonic-gate 880Sstevel@tonic-gate /* list of conffile entry names that are considered "done" */ 890Sstevel@tonic-gate static struct lut *Donenames; 900Sstevel@tonic-gate 911913Sdg199075 /* A list of names of files to be gzipped */ 921913Sdg199075 static struct lut *Gzipnames = NULL; 931913Sdg199075 940Sstevel@tonic-gate /* table that drives argument parsing */ 950Sstevel@tonic-gate static struct optinfo Opttable[] = { 960Sstevel@tonic-gate { "e", OPTTYPE_STRING, NULL, OPTF_CLI|OPTF_CONF }, 970Sstevel@tonic-gate { "f", OPTTYPE_STRING, NULL, OPTF_CLI }, 980Sstevel@tonic-gate { "h", OPTTYPE_BOOLEAN, NULL, OPTF_CLI }, 992602Sisamu { "l", OPTTYPE_BOOLEAN, NULL, OPTF_CLI|OPTF_CONF }, 1000Sstevel@tonic-gate { "N", OPTTYPE_BOOLEAN, NULL, OPTF_CLI|OPTF_CONF }, 1010Sstevel@tonic-gate { "n", OPTTYPE_BOOLEAN, NULL, OPTF_CLI }, 1020Sstevel@tonic-gate { "r", OPTTYPE_BOOLEAN, NULL, OPTF_CLI }, 1030Sstevel@tonic-gate { "V", OPTTYPE_BOOLEAN, NULL, OPTF_CLI }, 1040Sstevel@tonic-gate { "v", OPTTYPE_BOOLEAN, NULL, OPTF_CLI }, 1050Sstevel@tonic-gate { "w", OPTTYPE_STRING, NULL, OPTF_CLI }, 1060Sstevel@tonic-gate { "p", OPTTYPE_INT, opts_parse_seconds, OPTF_CLI|OPTF_CONF }, 1070Sstevel@tonic-gate { "P", OPTTYPE_INT, opts_parse_ctime, OPTF_CLI|OPTF_CONF }, 1080Sstevel@tonic-gate { "s", OPTTYPE_INT, opts_parse_bytes, OPTF_CLI|OPTF_CONF }, 1090Sstevel@tonic-gate { "a", OPTTYPE_STRING, NULL, OPTF_CLI|OPTF_CONF }, 1100Sstevel@tonic-gate { "b", OPTTYPE_STRING, NULL, OPTF_CLI|OPTF_CONF }, 1110Sstevel@tonic-gate { "c", OPTTYPE_BOOLEAN, NULL, OPTF_CLI|OPTF_CONF }, 1120Sstevel@tonic-gate { "g", OPTTYPE_STRING, NULL, OPTF_CLI|OPTF_CONF }, 1130Sstevel@tonic-gate { "m", OPTTYPE_INT, opts_parse_atopi, OPTF_CLI|OPTF_CONF }, 1140Sstevel@tonic-gate { "M", OPTTYPE_STRING, NULL, OPTF_CLI|OPTF_CONF }, 1150Sstevel@tonic-gate { "o", OPTTYPE_STRING, NULL, OPTF_CLI|OPTF_CONF }, 1160Sstevel@tonic-gate { "R", OPTTYPE_STRING, NULL, OPTF_CLI|OPTF_CONF }, 1170Sstevel@tonic-gate { "t", OPTTYPE_STRING, NULL, OPTF_CLI|OPTF_CONF }, 1180Sstevel@tonic-gate { "z", OPTTYPE_INT, opts_parse_atopi, OPTF_CLI|OPTF_CONF }, 1190Sstevel@tonic-gate { "A", OPTTYPE_INT, opts_parse_seconds, OPTF_CLI|OPTF_CONF }, 1200Sstevel@tonic-gate { "C", OPTTYPE_INT, opts_parse_atopi, OPTF_CLI|OPTF_CONF }, 1210Sstevel@tonic-gate { "E", OPTTYPE_STRING, NULL, OPTF_CLI|OPTF_CONF }, 1220Sstevel@tonic-gate { "S", OPTTYPE_INT, opts_parse_bytes, OPTF_CLI|OPTF_CONF }, 1230Sstevel@tonic-gate { "T", OPTTYPE_STRING, NULL, OPTF_CLI|OPTF_CONF }, 1240Sstevel@tonic-gate }; 1250Sstevel@tonic-gate 1260Sstevel@tonic-gate /* 1270Sstevel@tonic-gate * only the "fhnVv" options are allowed in the first form of this command, 1280Sstevel@tonic-gate * so this defines the list of options that are an error in they appear 1290Sstevel@tonic-gate * in the first form. In other words, it is not allowed to run logadm 1300Sstevel@tonic-gate * with any of these options unless at least one logname is also provided. 1310Sstevel@tonic-gate */ 132954Sgm149974 #define OPTIONS_NOT_FIRST_FORM "eNrwpPsabcglmoRtzACEST" 1330Sstevel@tonic-gate 1340Sstevel@tonic-gate /* text that we spew with the -h flag */ 1350Sstevel@tonic-gate #define HELP1 \ 1360Sstevel@tonic-gate "Usage: logadm [options]\n"\ 1370Sstevel@tonic-gate " (processes all entries in /etc/logadm.conf or conffile given by -f)\n"\ 1380Sstevel@tonic-gate " or: logadm [options] logname...\n"\ 1390Sstevel@tonic-gate " (processes the given lognames)\n"\ 1400Sstevel@tonic-gate "\n"\ 1410Sstevel@tonic-gate "General options:\n"\ 1420Sstevel@tonic-gate " -e mailaddr mail errors to given address\n"\ 1430Sstevel@tonic-gate " -f conffile use conffile instead of /etc/logadm.conf\n"\ 1440Sstevel@tonic-gate " -h display help\n"\ 1450Sstevel@tonic-gate " -N not an error if log file nonexistent\n"\ 1460Sstevel@tonic-gate " -n show actions, don't perform them\n"\ 1470Sstevel@tonic-gate " -r remove logname entry from conffile\n"\ 1480Sstevel@tonic-gate " -V ensure conffile entries exist, correct\n"\ 1490Sstevel@tonic-gate " -v print info about actions happening\n"\ 1500Sstevel@tonic-gate " -w entryname write entry to config file\n"\ 1510Sstevel@tonic-gate "\n"\ 1520Sstevel@tonic-gate "Options which control when a logfile is rotated:\n"\ 1530Sstevel@tonic-gate "(default is: -s1b -p1w if no -s or -p)\n"\ 1540Sstevel@tonic-gate " -p period only rotate if period passed since last rotate\n"\ 1550Sstevel@tonic-gate " -P timestamp used to store rotation date in conffile\n"\ 1560Sstevel@tonic-gate " -s size only rotate if given size or greater\n"\ 1570Sstevel@tonic-gate "\n" 1580Sstevel@tonic-gate #define HELP2 \ 1590Sstevel@tonic-gate "Options which control how a logfile is rotated:\n"\ 1600Sstevel@tonic-gate "(default is: -t '$file.$n', owner/group/mode taken from log file)\n"\ 1610Sstevel@tonic-gate " -a cmd execute cmd after taking actions\n"\ 1620Sstevel@tonic-gate " -b cmd execute cmd before taking actions\n"\ 1630Sstevel@tonic-gate " -c copy & truncate logfile, don't rename\n"\ 1640Sstevel@tonic-gate " -g group new empty log file group\n"\ 165954Sgm149974 " -l rotate log file with local time rather than UTC\n"\ 1660Sstevel@tonic-gate " -m mode new empty log file mode\n"\ 1670Sstevel@tonic-gate " -M cmd execute cmd to rotate the log file\n"\ 1680Sstevel@tonic-gate " -o owner new empty log file owner\n"\ 1690Sstevel@tonic-gate " -R cmd run cmd on file after rotate\n"\ 1700Sstevel@tonic-gate " -t template template for naming old logs\n"\ 1710Sstevel@tonic-gate " -z count gzip old logs except most recent count\n"\ 1720Sstevel@tonic-gate "\n"\ 1730Sstevel@tonic-gate "Options which control the expiration of old logfiles:\n"\ 1740Sstevel@tonic-gate "(default is: -C10 if no -A, -C, or -S)\n"\ 1750Sstevel@tonic-gate " -A age expire logs older than age\n"\ 1760Sstevel@tonic-gate " -C count expire old logs until count remain\n"\ 1770Sstevel@tonic-gate " -E cmd run cmd on file to expire\n"\ 1780Sstevel@tonic-gate " -S size expire until space used is below size \n"\ 1790Sstevel@tonic-gate " -T pattern pattern for finding old logs\n" 1800Sstevel@tonic-gate 1810Sstevel@tonic-gate /* 1820Sstevel@tonic-gate * main -- where it all begins 1830Sstevel@tonic-gate */ 1840Sstevel@tonic-gate /*ARGSUSED*/ 1850Sstevel@tonic-gate int 1860Sstevel@tonic-gate main(int argc, char *argv[]) 1870Sstevel@tonic-gate { 1880Sstevel@tonic-gate struct opts *clopts; /* from parsing command line */ 1890Sstevel@tonic-gate const char *conffile; /* our configuration file */ 1900Sstevel@tonic-gate struct fn_list *lognames; /* list of lognames we're processing */ 1910Sstevel@tonic-gate struct fn *fnp; 1920Sstevel@tonic-gate char *val; 1932397Sbasabi char *buf; 1940Sstevel@tonic-gate 1950Sstevel@tonic-gate (void) setlocale(LC_ALL, ""); 1960Sstevel@tonic-gate 1970Sstevel@tonic-gate #if !defined(TEXT_DOMAIN) 1980Sstevel@tonic-gate #define TEXT_DOMAIN "SYS_TEST" /* only used if Makefiles don't define it */ 1990Sstevel@tonic-gate #endif 2000Sstevel@tonic-gate 2010Sstevel@tonic-gate (void) textdomain(TEXT_DOMAIN); 2020Sstevel@tonic-gate 2030Sstevel@tonic-gate /* we only print times into the conffile, so make them uniform */ 2040Sstevel@tonic-gate (void) setlocale(LC_TIME, "C"); 2050Sstevel@tonic-gate 2060Sstevel@tonic-gate /* give our name to error routines & skip it for arg parsing */ 2070Sstevel@tonic-gate err_init(*argv++); 2080Sstevel@tonic-gate (void) setlinebuf(stdout); 2090Sstevel@tonic-gate 2100Sstevel@tonic-gate if (putenv("PATH=/bin")) 2110Sstevel@tonic-gate err(EF_SYS, "putenv PATH"); 2120Sstevel@tonic-gate if (putenv("TZ=UTC")) 2130Sstevel@tonic-gate err(EF_SYS, "putenv TZ"); 2140Sstevel@tonic-gate tzset(); 2150Sstevel@tonic-gate 2160Sstevel@tonic-gate (void) umask(0); 2170Sstevel@tonic-gate 2180Sstevel@tonic-gate Now = time(0); 2190Sstevel@tonic-gate 2200Sstevel@tonic-gate /* check for (undocumented) debugging environment variables */ 2210Sstevel@tonic-gate if (val = getenv("_LOGADM_DEFAULT_CONFFILE")) 2220Sstevel@tonic-gate Default_conffile = val; 2230Sstevel@tonic-gate if (val = getenv("_LOGADM_DEBUG")) 2240Sstevel@tonic-gate Debug = atoi(val); 2250Sstevel@tonic-gate if (val = getenv("_LOGADM_SH")) 2260Sstevel@tonic-gate Sh = val; 2270Sstevel@tonic-gate if (val = getenv("_LOGADM_MV")) 2280Sstevel@tonic-gate Mv = val; 2290Sstevel@tonic-gate if (val = getenv("_LOGADM_CP")) 2300Sstevel@tonic-gate Cp = val; 2310Sstevel@tonic-gate if (val = getenv("_LOGADM_RM")) 2320Sstevel@tonic-gate Rm = val; 2330Sstevel@tonic-gate if (val = getenv("_LOGADM_TOUCH")) 2340Sstevel@tonic-gate Touch = val; 2350Sstevel@tonic-gate if (val = getenv("_LOGADM_CHMOD")) 2360Sstevel@tonic-gate Chmod = val; 2370Sstevel@tonic-gate if (val = getenv("_LOGADM_CHOWN")) 2380Sstevel@tonic-gate Chown = val; 2390Sstevel@tonic-gate if (val = getenv("_LOGADM_GZIP")) 2400Sstevel@tonic-gate Gzip = val; 2410Sstevel@tonic-gate if (val = getenv("_LOGADM_MKDIR")) 2420Sstevel@tonic-gate Mkdir = val; 2430Sstevel@tonic-gate 2440Sstevel@tonic-gate opts_init(Opttable, sizeof (Opttable) / sizeof (struct optinfo)); 2450Sstevel@tonic-gate 2460Sstevel@tonic-gate /* parse command line arguments */ 2470Sstevel@tonic-gate if (SETJMP) 2480Sstevel@tonic-gate usage("bailing out due to command line errors"); 2490Sstevel@tonic-gate else 2500Sstevel@tonic-gate clopts = opts_parse(argv, OPTF_CLI); 2510Sstevel@tonic-gate 2520Sstevel@tonic-gate if (Debug) { 2530Sstevel@tonic-gate (void) fprintf(stderr, "command line opts:"); 2540Sstevel@tonic-gate opts_print(clopts, stderr, NULL); 2550Sstevel@tonic-gate (void) fprintf(stderr, "\n"); 2560Sstevel@tonic-gate } 2570Sstevel@tonic-gate 2580Sstevel@tonic-gate /* 2590Sstevel@tonic-gate * There are many moods of logadm: 2600Sstevel@tonic-gate * 2610Sstevel@tonic-gate * 1. "-h" for help was given. We spew a canned help 2620Sstevel@tonic-gate * message and exit, regardless of any other options given. 2630Sstevel@tonic-gate * 2640Sstevel@tonic-gate * 2. "-r" or "-w" asking us to write to the conffile. Lots 2650Sstevel@tonic-gate * of argument checking, then we make the change to conffile 2660Sstevel@tonic-gate * and exit. (-r processing actually happens in dologname().) 2670Sstevel@tonic-gate * 2680Sstevel@tonic-gate * 3. "-V" to search/verify the conffile was given. We do 2690Sstevel@tonic-gate * the appropriate run through the conffile and exit. 2700Sstevel@tonic-gate * (-V processing actually happens in dologname().) 2710Sstevel@tonic-gate * 2720Sstevel@tonic-gate * 4. No lognames were given, so we're being asked to go through 2730Sstevel@tonic-gate * every entry in conffile. We verify that only the options 2740Sstevel@tonic-gate * that make sense for this form of the command are present 2750Sstevel@tonic-gate * and fall into the main processing loop below. 2760Sstevel@tonic-gate * 2770Sstevel@tonic-gate * 5. lognames were given, so we fall into the main processing 2780Sstevel@tonic-gate * loop below to work our way through them. 2790Sstevel@tonic-gate * 2800Sstevel@tonic-gate * The last two cases are where the option processing gets more 2810Sstevel@tonic-gate * complex. Each time around the main processing loop, we're 2820Sstevel@tonic-gate * in one of these cases: 2830Sstevel@tonic-gate * 2840Sstevel@tonic-gate * A. No cmdargs were found (we're in case 4), the entry 2850Sstevel@tonic-gate * in conffile supplies no log file names, so the entry 2860Sstevel@tonic-gate * name itself is the logfile name (or names, if it globs 2870Sstevel@tonic-gate * to multiple file names). 2880Sstevel@tonic-gate * 2890Sstevel@tonic-gate * B. No cmdargs were found (we're in case 4), the entry 2900Sstevel@tonic-gate * in conffile gives log file names that we then loop 2910Sstevel@tonic-gate * through and rotate/expire. In this case, the entry 2920Sstevel@tonic-gate * name is specifically NOT one of the log file names. 2930Sstevel@tonic-gate * 2940Sstevel@tonic-gate * C. We're going through the cmdargs (we're in case 5), 2950Sstevel@tonic-gate * the entry in conffile either doesn't exist or it exists 2960Sstevel@tonic-gate * but supplies no log file names, so the cmdarg itself 2970Sstevel@tonic-gate * is the log file name. 2980Sstevel@tonic-gate * 2990Sstevel@tonic-gate * D. We're going through the cmdargs (we're in case 5), 3000Sstevel@tonic-gate * a matching entry in conffile supplies log file names 3010Sstevel@tonic-gate * that we then loop through and rotate/expire. In this 3020Sstevel@tonic-gate * case the entry name is specifically NOT one of the log 3030Sstevel@tonic-gate * file names. 3040Sstevel@tonic-gate * 3050Sstevel@tonic-gate * As we're doing all this, any options given on the command line 3060Sstevel@tonic-gate * override any found in the conffile, and we apply the defaults 3070Sstevel@tonic-gate * for rotation conditions and expiration conditions, etc. at the 3080Sstevel@tonic-gate * last opportunity, when we're sure they haven't been overridden 3090Sstevel@tonic-gate * by an option somewhere along the way. 3100Sstevel@tonic-gate * 3110Sstevel@tonic-gate */ 3120Sstevel@tonic-gate 3130Sstevel@tonic-gate /* help option overrides anything else */ 3140Sstevel@tonic-gate if (opts_count(clopts, "h")) { 3150Sstevel@tonic-gate (void) fputs(HELP1, stderr); 3160Sstevel@tonic-gate (void) fputs(HELP2, stderr); 3170Sstevel@tonic-gate err_done(0); 3180Sstevel@tonic-gate /*NOTREACHED*/ 3190Sstevel@tonic-gate } 3200Sstevel@tonic-gate 3210Sstevel@tonic-gate /* detect illegal option combinations */ 3220Sstevel@tonic-gate if (opts_count(clopts, "rwV") > 1) 3230Sstevel@tonic-gate usage("Only one of -r, -w, or -V may be used at a time."); 3240Sstevel@tonic-gate if (opts_count(clopts, "cM") > 1) 3250Sstevel@tonic-gate usage("Only one of -c or -M may be used at a time."); 3260Sstevel@tonic-gate 3270Sstevel@tonic-gate /* arrange for error output to be mailed if clopts includes -e */ 3280Sstevel@tonic-gate if (opts_count(clopts, "e")) 3290Sstevel@tonic-gate err_mailto(opts_optarg(clopts, "e")); 3300Sstevel@tonic-gate 3310Sstevel@tonic-gate /* this implements the default conffile */ 3320Sstevel@tonic-gate if ((conffile = opts_optarg(clopts, "f")) == NULL) 3330Sstevel@tonic-gate conffile = Default_conffile; 3340Sstevel@tonic-gate if (opts_count(clopts, "v")) 3350Sstevel@tonic-gate (void) out("# loading %s\n", conffile); 3360Sstevel@tonic-gate conf_open(conffile, opts_count(clopts, "Vn") == 0); 3370Sstevel@tonic-gate 3380Sstevel@tonic-gate /* handle conffile write option */ 3390Sstevel@tonic-gate if (opts_count(clopts, "w")) { 3400Sstevel@tonic-gate if (Debug) 3410Sstevel@tonic-gate (void) fprintf(stderr, 3420Sstevel@tonic-gate "main: add/replace conffile entry: <%s>\n", 3430Sstevel@tonic-gate opts_optarg(clopts, "w")); 3440Sstevel@tonic-gate conf_replace(opts_optarg(clopts, "w"), clopts); 3450Sstevel@tonic-gate conf_close(clopts); 3460Sstevel@tonic-gate err_done(0); 3470Sstevel@tonic-gate /*NOTREACHED*/ 3480Sstevel@tonic-gate } 3490Sstevel@tonic-gate 3500Sstevel@tonic-gate /* 3510Sstevel@tonic-gate * lognames is either a list supplied on the command line, 3520Sstevel@tonic-gate * or every entry in the conffile if none were supplied. 3530Sstevel@tonic-gate */ 3540Sstevel@tonic-gate lognames = opts_cmdargs(clopts); 3550Sstevel@tonic-gate if (fn_list_empty(lognames)) { 3560Sstevel@tonic-gate /* 3570Sstevel@tonic-gate * being asked to do all entries in conffile 3580Sstevel@tonic-gate * 3590Sstevel@tonic-gate * check to see if any options were given that only 3600Sstevel@tonic-gate * make sense when lognames are given specifically 3610Sstevel@tonic-gate * on the command line. 3620Sstevel@tonic-gate */ 3630Sstevel@tonic-gate if (opts_count(clopts, OPTIONS_NOT_FIRST_FORM)) 3640Sstevel@tonic-gate usage("some options require logname argument"); 3650Sstevel@tonic-gate if (Debug) 3660Sstevel@tonic-gate (void) fprintf(stderr, 3670Sstevel@tonic-gate "main: run all entries in conffile\n"); 3680Sstevel@tonic-gate lognames = conf_entries(); 3690Sstevel@tonic-gate } 3700Sstevel@tonic-gate 3710Sstevel@tonic-gate /* foreach logname... */ 3720Sstevel@tonic-gate fn_list_rewind(lognames); 3730Sstevel@tonic-gate while ((fnp = fn_list_next(lognames)) != NULL) { 3742397Sbasabi buf = fn_s(fnp); 3752397Sbasabi if (buf != NULL && lut_lookup(Donenames, buf) != NULL) { 3760Sstevel@tonic-gate if (Debug) 3770Sstevel@tonic-gate (void) fprintf(stderr, 3780Sstevel@tonic-gate "main: logname already done: <%s>\n", 3792397Sbasabi buf); 3800Sstevel@tonic-gate continue; 3810Sstevel@tonic-gate } 3822397Sbasabi if (buf != NULL && SETJMP) 3830Sstevel@tonic-gate err(EF_FILE, "bailing out on logname \"%s\" " 3842397Sbasabi "due to errors", buf); 3850Sstevel@tonic-gate else 3860Sstevel@tonic-gate dologname(fnp, clopts); 3870Sstevel@tonic-gate } 3880Sstevel@tonic-gate 3890Sstevel@tonic-gate /* execute any after commands */ 3900Sstevel@tonic-gate lut_walk(Aftercmds, doaftercmd, clopts); 3910Sstevel@tonic-gate 3921913Sdg199075 /* execute any gzip commands */ 3931913Sdg199075 lut_walk(Gzipnames, do_delayed_gzip, clopts); 3941913Sdg199075 3950Sstevel@tonic-gate /* write out any conffile changes */ 3960Sstevel@tonic-gate conf_close(clopts); 3970Sstevel@tonic-gate 3980Sstevel@tonic-gate err_done(0); 3990Sstevel@tonic-gate /*NOTREACHED*/ 4000Sstevel@tonic-gate return (0); /* for lint's little mind */ 4010Sstevel@tonic-gate } 4020Sstevel@tonic-gate 4030Sstevel@tonic-gate /* spew a message, then a usage message, then exit */ 4040Sstevel@tonic-gate static void 4050Sstevel@tonic-gate usage(const char *msg) 4060Sstevel@tonic-gate { 4070Sstevel@tonic-gate if (msg) 4080Sstevel@tonic-gate err(0, "%s\nUse \"logadm -h\" for help.", msg); 4090Sstevel@tonic-gate else 4100Sstevel@tonic-gate err(EF_RAW, "Use \"logadm -h\" for help.\n"); 4110Sstevel@tonic-gate } 4120Sstevel@tonic-gate 4130Sstevel@tonic-gate /* helper function used by doaftercmd() to join mail addrs with commas */ 4140Sstevel@tonic-gate /*ARGSUSED1*/ 4150Sstevel@tonic-gate static void 4160Sstevel@tonic-gate commajoin(const char *lhs, void *rhs, void *arg) 4170Sstevel@tonic-gate { 4180Sstevel@tonic-gate struct fn *fnp = (struct fn *)arg; 4192397Sbasabi char *buf; 4200Sstevel@tonic-gate 4212397Sbasabi buf = fn_s(fnp); 4222397Sbasabi if (buf != NULL && *buf) 4230Sstevel@tonic-gate fn_putc(fnp, ','); 4240Sstevel@tonic-gate fn_puts(fnp, lhs); 4250Sstevel@tonic-gate } 4260Sstevel@tonic-gate 4270Sstevel@tonic-gate /* helper function used by main() to run "after" commands */ 4280Sstevel@tonic-gate static void 4290Sstevel@tonic-gate doaftercmd(const char *lhs, void *rhs, void *arg) 4300Sstevel@tonic-gate { 4310Sstevel@tonic-gate struct opts *opts = (struct opts *)arg; 4320Sstevel@tonic-gate struct lut *addrs = (struct lut *)rhs; 4330Sstevel@tonic-gate 4340Sstevel@tonic-gate if (addrs) { 4350Sstevel@tonic-gate struct fn *fnp = fn_new(NULL); 4360Sstevel@tonic-gate 4370Sstevel@tonic-gate /* 4380Sstevel@tonic-gate * addrs contains list of email addrs that should get 4390Sstevel@tonic-gate * the error output when this after command is executed. 4400Sstevel@tonic-gate */ 4410Sstevel@tonic-gate lut_walk(addrs, commajoin, fnp); 4420Sstevel@tonic-gate err_mailto(fn_s(fnp)); 4430Sstevel@tonic-gate } 4440Sstevel@tonic-gate 4450Sstevel@tonic-gate docmd(opts, "-a cmd", Sh, "-c", lhs, NULL); 4460Sstevel@tonic-gate } 4470Sstevel@tonic-gate 4481913Sdg199075 /* perform delayed gzip */ 4491913Sdg199075 4501913Sdg199075 static void 4511913Sdg199075 do_delayed_gzip(const char *lhs, void *rhs, void *arg) 4521913Sdg199075 { 4531913Sdg199075 struct opts *opts = (struct opts *)arg; 4541913Sdg199075 4551913Sdg199075 if (rhs == NULL) { 4561913Sdg199075 if (Debug) { 4571913Sdg199075 (void) fprintf(stderr, "do_delayed_gzip: not gzipping " 4581913Sdg199075 "expired file <%s>\n", lhs); 4591913Sdg199075 } 4601913Sdg199075 return; 4611913Sdg199075 } 4621913Sdg199075 docmd(opts, "compress old log (-z flag)", Gzip, "-f", lhs, NULL); 4631913Sdg199075 } 4641913Sdg199075 4651913Sdg199075 4660Sstevel@tonic-gate /* main logname processing */ 4670Sstevel@tonic-gate static void 4680Sstevel@tonic-gate dologname(struct fn *fnp, struct opts *clopts) 4690Sstevel@tonic-gate { 4700Sstevel@tonic-gate const char *logname = fn_s(fnp); 4710Sstevel@tonic-gate struct opts *cfopts; 4720Sstevel@tonic-gate struct opts *allopts; 4730Sstevel@tonic-gate struct fn_list *logfiles; 4740Sstevel@tonic-gate struct fn_list *globbedfiles; 4750Sstevel@tonic-gate struct fn *nextfnp; 4760Sstevel@tonic-gate 4770Sstevel@tonic-gate /* look up options set by config file */ 4780Sstevel@tonic-gate cfopts = conf_opts(logname); 4790Sstevel@tonic-gate 4800Sstevel@tonic-gate if (opts_count(clopts, "v")) 4810Sstevel@tonic-gate (void) out("# processing logname: %s\n", logname); 4820Sstevel@tonic-gate 4830Sstevel@tonic-gate if (Debug) { 4842397Sbasabi if (logname != NULL) 4852397Sbasabi (void) fprintf(stderr, "dologname: logname <%s>\n", 4862397Sbasabi logname); 4870Sstevel@tonic-gate (void) fprintf(stderr, "conffile opts:"); 4880Sstevel@tonic-gate opts_print(cfopts, stderr, NULL); 4890Sstevel@tonic-gate (void) fprintf(stderr, "\n"); 4900Sstevel@tonic-gate } 4910Sstevel@tonic-gate 4920Sstevel@tonic-gate /* handle conffile lookup option */ 4930Sstevel@tonic-gate if (opts_count(clopts, "V")) { 4940Sstevel@tonic-gate /* lookup an entry in conffile */ 4950Sstevel@tonic-gate if (Debug) 4960Sstevel@tonic-gate (void) fprintf(stderr, 4970Sstevel@tonic-gate "dologname: lookup conffile entry\n"); 4980Sstevel@tonic-gate if (conf_lookup(logname)) { 4990Sstevel@tonic-gate opts_printword(logname, stdout); 5000Sstevel@tonic-gate opts_print(cfopts, stdout, NULL); 5010Sstevel@tonic-gate (void) out("\n"); 5020Sstevel@tonic-gate } else 5030Sstevel@tonic-gate err_exitcode(1); 5040Sstevel@tonic-gate return; 5050Sstevel@tonic-gate } 5060Sstevel@tonic-gate 5070Sstevel@tonic-gate /* handle conffile removal option */ 5080Sstevel@tonic-gate if (opts_count(clopts, "r")) { 5090Sstevel@tonic-gate if (Debug) 5100Sstevel@tonic-gate (void) fprintf(stderr, 5110Sstevel@tonic-gate "dologname: remove conffile entry\n"); 5120Sstevel@tonic-gate if (conf_lookup(logname)) 5130Sstevel@tonic-gate conf_replace(logname, NULL); 5140Sstevel@tonic-gate else 5150Sstevel@tonic-gate err_exitcode(1); 5160Sstevel@tonic-gate return; 5170Sstevel@tonic-gate } 5180Sstevel@tonic-gate 5190Sstevel@tonic-gate /* generate combined options */ 5200Sstevel@tonic-gate allopts = opts_merge(cfopts, clopts); 5210Sstevel@tonic-gate 5220Sstevel@tonic-gate /* arrange for error output to be mailed if allopts includes -e */ 5230Sstevel@tonic-gate if (opts_count(allopts, "e")) 5240Sstevel@tonic-gate err_mailto(opts_optarg(allopts, "e")); 5250Sstevel@tonic-gate else 5260Sstevel@tonic-gate err_mailto(NULL); 5270Sstevel@tonic-gate 5280Sstevel@tonic-gate /* this implements the default rotation rules */ 5290Sstevel@tonic-gate if (opts_count(allopts, "sp") == 0) { 5300Sstevel@tonic-gate if (opts_count(clopts, "v")) 5310Sstevel@tonic-gate (void) out( 5320Sstevel@tonic-gate "# using default rotate rules: -s1b -p1w\n"); 5330Sstevel@tonic-gate (void) opts_set(allopts, "s", "1b"); 5340Sstevel@tonic-gate (void) opts_set(allopts, "p", "1w"); 5350Sstevel@tonic-gate } 5360Sstevel@tonic-gate 5370Sstevel@tonic-gate /* this implements the default expiration rules */ 5380Sstevel@tonic-gate if (opts_count(allopts, "ACS") == 0) { 5390Sstevel@tonic-gate if (opts_count(clopts, "v")) 5400Sstevel@tonic-gate (void) out("# using default expire rule: -C10\n"); 5410Sstevel@tonic-gate (void) opts_set(allopts, "C", "10"); 5420Sstevel@tonic-gate } 5430Sstevel@tonic-gate 5440Sstevel@tonic-gate /* this implements the default template */ 5450Sstevel@tonic-gate if (opts_count(allopts, "t") == 0) { 5460Sstevel@tonic-gate if (opts_count(clopts, "v")) 5470Sstevel@tonic-gate (void) out("# using default template: $file.$n\n"); 5480Sstevel@tonic-gate (void) opts_set(allopts, "t", "$file.$n"); 5490Sstevel@tonic-gate } 5500Sstevel@tonic-gate 5510Sstevel@tonic-gate if (Debug) { 5520Sstevel@tonic-gate (void) fprintf(stderr, "merged opts:"); 5530Sstevel@tonic-gate opts_print(allopts, stderr, NULL); 5540Sstevel@tonic-gate (void) fprintf(stderr, "\n"); 5550Sstevel@tonic-gate } 5560Sstevel@tonic-gate 5570Sstevel@tonic-gate /* 5580Sstevel@tonic-gate * if the conffile entry supplied log file names, then 5590Sstevel@tonic-gate * logname is NOT one of the log file names (it was just 5600Sstevel@tonic-gate * the entry name in conffile). 5610Sstevel@tonic-gate */ 5620Sstevel@tonic-gate logfiles = opts_cmdargs(cfopts); 5630Sstevel@tonic-gate if (Debug) { 5642397Sbasabi char *buf; 5650Sstevel@tonic-gate (void) fprintf(stderr, "dologname: logfiles from cfopts:\n"); 5660Sstevel@tonic-gate fn_list_rewind(logfiles); 5670Sstevel@tonic-gate while ((nextfnp = fn_list_next(logfiles)) != NULL) 5682397Sbasabi buf = fn_s(nextfnp); 5692397Sbasabi if (buf != NULL) 5702397Sbasabi (void) fprintf(stderr, " <%s>\n", buf); 5710Sstevel@tonic-gate } 5720Sstevel@tonic-gate if (fn_list_empty(logfiles)) 5730Sstevel@tonic-gate globbedfiles = glob_glob(fnp); 5740Sstevel@tonic-gate else 5750Sstevel@tonic-gate globbedfiles = glob_glob_list(logfiles); 5760Sstevel@tonic-gate 5770Sstevel@tonic-gate /* go through the list produced by glob expansion */ 5780Sstevel@tonic-gate fn_list_rewind(globbedfiles); 5790Sstevel@tonic-gate while ((nextfnp = fn_list_next(globbedfiles)) != NULL) 5800Sstevel@tonic-gate if (rotatelog(nextfnp, allopts)) 5810Sstevel@tonic-gate expirefiles(nextfnp, allopts); 5820Sstevel@tonic-gate 5830Sstevel@tonic-gate fn_list_free(globbedfiles); 5840Sstevel@tonic-gate opts_free(allopts); 5850Sstevel@tonic-gate } 5860Sstevel@tonic-gate 5870Sstevel@tonic-gate 5880Sstevel@tonic-gate /* absurdly long buffer lengths for holding user/group/mode strings */ 5890Sstevel@tonic-gate #define TIMESTRMAX 100 5900Sstevel@tonic-gate #define MAXATTR 100 5910Sstevel@tonic-gate 5920Sstevel@tonic-gate /* rotate a log file if necessary, returns true if ok to go on to expire step */ 5930Sstevel@tonic-gate static boolean_t 5940Sstevel@tonic-gate rotatelog(struct fn *fnp, struct opts *opts) 5950Sstevel@tonic-gate { 5960Sstevel@tonic-gate char *fname = fn_s(fnp); 5970Sstevel@tonic-gate struct stat stbuf; 5980Sstevel@tonic-gate char nowstr[TIMESTRMAX]; 5990Sstevel@tonic-gate struct fn *recentlog = fn_new(NULL); /* for -R cmd */ 6000Sstevel@tonic-gate char ownerbuf[MAXATTR]; 6010Sstevel@tonic-gate char groupbuf[MAXATTR]; 6020Sstevel@tonic-gate char modebuf[MAXATTR]; 6030Sstevel@tonic-gate const char *owner; 6040Sstevel@tonic-gate const char *group; 6050Sstevel@tonic-gate const char *mode; 6060Sstevel@tonic-gate 6072397Sbasabi if (Debug && fname != NULL) 6080Sstevel@tonic-gate (void) fprintf(stderr, "rotatelog: fname <%s>\n", fname); 6090Sstevel@tonic-gate 6100Sstevel@tonic-gate if (opts_count(opts, "p") && opts_optarg_int(opts, "p") == OPTP_NEVER) 6110Sstevel@tonic-gate return (B_TRUE); /* "-p never" forced no rotate */ 6120Sstevel@tonic-gate 6130Sstevel@tonic-gate /* prepare the keywords */ 6140Sstevel@tonic-gate kw_init(fnp, NULL); 6150Sstevel@tonic-gate if (Debug > 1) { 6160Sstevel@tonic-gate (void) fprintf(stderr, "rotatelog keywords:\n"); 6170Sstevel@tonic-gate kw_print(stderr); 6180Sstevel@tonic-gate } 6190Sstevel@tonic-gate 6200Sstevel@tonic-gate if (lstat(fname, &stbuf) < 0) { 6210Sstevel@tonic-gate if (opts_count(opts, "N")) 6220Sstevel@tonic-gate return (1); 6230Sstevel@tonic-gate err(EF_WARN|EF_SYS, "%s", fname); 6240Sstevel@tonic-gate return (B_FALSE); 6250Sstevel@tonic-gate } 6260Sstevel@tonic-gate 6270Sstevel@tonic-gate if ((stbuf.st_mode & S_IFMT) == S_IFLNK) { 6280Sstevel@tonic-gate err(EF_WARN, "%s is a symlink", fname); 6290Sstevel@tonic-gate return (B_FALSE); 6300Sstevel@tonic-gate } 6310Sstevel@tonic-gate 6320Sstevel@tonic-gate if ((stbuf.st_mode & S_IFMT) != S_IFREG) { 6330Sstevel@tonic-gate err(EF_WARN, "%s is not a regular file", fname); 6340Sstevel@tonic-gate return (B_FALSE); 6350Sstevel@tonic-gate } 6360Sstevel@tonic-gate 6370Sstevel@tonic-gate /* see if size condition is present, and return if not met */ 6380Sstevel@tonic-gate if (opts_count(opts, "s") && stbuf.st_size < opts_optarg_int(opts, "s")) 6390Sstevel@tonic-gate return (B_TRUE); 6400Sstevel@tonic-gate 6410Sstevel@tonic-gate /* see if age condition is present, and return if not met */ 6420Sstevel@tonic-gate if (opts_count(opts, "p")) { 6432016Sbasabi off_t when = opts_optarg_int(opts, "p"); 6440Sstevel@tonic-gate struct opts *cfopts; 6450Sstevel@tonic-gate 6460Sstevel@tonic-gate /* unless rotate forced by "-p now", see if period has passed */ 6470Sstevel@tonic-gate if (when != OPTP_NOW) { 6480Sstevel@tonic-gate /* 6490Sstevel@tonic-gate * "when" holds the number of seconds that must have 6500Sstevel@tonic-gate * passed since the last time this log was rotated. 6510Sstevel@tonic-gate * of course, running logadm can take a little time 6520Sstevel@tonic-gate * (typically a second or two, but longer if the 6530Sstevel@tonic-gate * conffile has lots of stuff in it) and that amount 6540Sstevel@tonic-gate * of time is variable, depending on system load, etc. 6550Sstevel@tonic-gate * so we want to allow a little "slop" in the value of 6560Sstevel@tonic-gate * "when". this way, if a log should be rotated every 6570Sstevel@tonic-gate * week, and the number of seconds passed is really a 6580Sstevel@tonic-gate * few seconds short of a week, we'll go ahead and 6590Sstevel@tonic-gate * rotate the log as expected. 6600Sstevel@tonic-gate * 6610Sstevel@tonic-gate */ 6620Sstevel@tonic-gate if (when >= 60 * 60) 6630Sstevel@tonic-gate when -= 59; 6640Sstevel@tonic-gate 6650Sstevel@tonic-gate /* 6660Sstevel@tonic-gate * last rotation is recorded as argument to -P, 6670Sstevel@tonic-gate * but if logname isn't the same as log file name 6680Sstevel@tonic-gate * then the timestamp would be recorded on a 6690Sstevel@tonic-gate * separate line in the conf file. so if we 6700Sstevel@tonic-gate * haven't seen a -P already, we check to see if 6710Sstevel@tonic-gate * it is part of a specific entry for the log 6720Sstevel@tonic-gate * file name. this handles the case where the 6730Sstevel@tonic-gate * logname is "apache", it supplies a log file 6740Sstevel@tonic-gate * name like "/var/apache/logs/[a-z]*_log", 6750Sstevel@tonic-gate * which expands to multiple file names. if one 6760Sstevel@tonic-gate * of the file names is "/var/apache/logs/access_log" 6770Sstevel@tonic-gate * the the -P will be attached to a line with that 6780Sstevel@tonic-gate * logname in the conf file. 6790Sstevel@tonic-gate */ 6800Sstevel@tonic-gate if (opts_count(opts, "P")) { 6812016Sbasabi off_t last = opts_optarg_int(opts, "P"); 6820Sstevel@tonic-gate 6830Sstevel@tonic-gate /* return if not enough time has passed */ 6840Sstevel@tonic-gate if (Now - last < when) 6850Sstevel@tonic-gate return (B_TRUE); 6860Sstevel@tonic-gate } else if ((cfopts = conf_opts(fname)) != NULL && 6870Sstevel@tonic-gate opts_count(cfopts, "P")) { 6882016Sbasabi off_t last = opts_optarg_int(cfopts, "P"); 6890Sstevel@tonic-gate 6900Sstevel@tonic-gate /* 6910Sstevel@tonic-gate * just checking this means this entry 6920Sstevel@tonic-gate * is now "done" if we're going through 6930Sstevel@tonic-gate * the entire conffile 6940Sstevel@tonic-gate */ 6950Sstevel@tonic-gate Donenames = lut_add(Donenames, fname, "1"); 6960Sstevel@tonic-gate 6970Sstevel@tonic-gate /* return if not enough time has passed */ 6980Sstevel@tonic-gate if (Now - last < when) 6990Sstevel@tonic-gate return (B_TRUE); 7000Sstevel@tonic-gate } 7010Sstevel@tonic-gate } 7020Sstevel@tonic-gate } 7030Sstevel@tonic-gate 7040Sstevel@tonic-gate if (Debug) 7050Sstevel@tonic-gate (void) fprintf(stderr, "rotatelog: conditions met\n"); 706954Sgm149974 if (opts_count(opts, "l")) { 707954Sgm149974 /* Change the time zone to local time zone */ 708954Sgm149974 if (putenv("TZ=")) 709954Sgm149974 err(EF_SYS, "putenv TZ"); 710954Sgm149974 tzset(); 711954Sgm149974 Now = time(0); 7120Sstevel@tonic-gate 713954Sgm149974 /* rename the log file */ 714954Sgm149974 rotateto(fnp, opts, 0, recentlog, B_FALSE); 715954Sgm149974 716954Sgm149974 /* Change the time zone to UTC */ 717954Sgm149974 if (putenv("TZ=UTC")) 718954Sgm149974 err(EF_SYS, "putenv TZ"); 719954Sgm149974 tzset(); 720954Sgm149974 Now = time(0); 721954Sgm149974 } else { 722954Sgm149974 /* rename the log file */ 723954Sgm149974 rotateto(fnp, opts, 0, recentlog, B_FALSE); 724954Sgm149974 } 7250Sstevel@tonic-gate 7260Sstevel@tonic-gate /* determine owner, group, mode for empty log file */ 7270Sstevel@tonic-gate if (opts_count(opts, "o")) 7280Sstevel@tonic-gate (void) strlcpy(ownerbuf, opts_optarg(opts, "o"), MAXATTR); 7290Sstevel@tonic-gate else { 7300Sstevel@tonic-gate (void) snprintf(ownerbuf, MAXATTR, "%ld", stbuf.st_uid); 7310Sstevel@tonic-gate } 7320Sstevel@tonic-gate owner = ownerbuf; 7330Sstevel@tonic-gate if (opts_count(opts, "g")) 7340Sstevel@tonic-gate group = opts_optarg(opts, "g"); 7350Sstevel@tonic-gate else { 7360Sstevel@tonic-gate (void) snprintf(groupbuf, MAXATTR, "%ld", stbuf.st_gid); 7370Sstevel@tonic-gate group = groupbuf; 7380Sstevel@tonic-gate } 7390Sstevel@tonic-gate (void) strlcat(ownerbuf, ":", MAXATTR - strlen(ownerbuf)); 7400Sstevel@tonic-gate (void) strlcat(ownerbuf, group, MAXATTR - strlen(ownerbuf)); 7410Sstevel@tonic-gate if (opts_count(opts, "m")) 7420Sstevel@tonic-gate mode = opts_optarg(opts, "m"); 7430Sstevel@tonic-gate else { 7440Sstevel@tonic-gate (void) snprintf(modebuf, MAXATTR, 7450Sstevel@tonic-gate "%03lo", stbuf.st_mode & 0777); 7460Sstevel@tonic-gate mode = modebuf; 7470Sstevel@tonic-gate } 7480Sstevel@tonic-gate 7490Sstevel@tonic-gate /* create the empty log file */ 7500Sstevel@tonic-gate docmd(opts, NULL, Touch, fname, NULL, NULL); 7510Sstevel@tonic-gate docmd(opts, NULL, Chown, owner, fname, NULL); 7520Sstevel@tonic-gate docmd(opts, NULL, Chmod, mode, fname, NULL); 7530Sstevel@tonic-gate 7540Sstevel@tonic-gate /* execute post-rotation command */ 7550Sstevel@tonic-gate if (opts_count(opts, "R")) { 7560Sstevel@tonic-gate struct fn *rawcmd = fn_new(opts_optarg(opts, "R")); 7570Sstevel@tonic-gate struct fn *cmd = fn_new(NULL); 7580Sstevel@tonic-gate 7590Sstevel@tonic-gate kw_init(recentlog, NULL); 7600Sstevel@tonic-gate (void) kw_expand(rawcmd, cmd, 0, B_FALSE); 7610Sstevel@tonic-gate docmd(opts, "-R cmd", Sh, "-c", fn_s(cmd), NULL); 7620Sstevel@tonic-gate fn_free(rawcmd); 7630Sstevel@tonic-gate fn_free(cmd); 7640Sstevel@tonic-gate } 7650Sstevel@tonic-gate fn_free(recentlog); 7660Sstevel@tonic-gate 7670Sstevel@tonic-gate /* 7680Sstevel@tonic-gate * add "after" command to list of after commands. we also record 7690Sstevel@tonic-gate * the email address, if any, where the error output of the after 7700Sstevel@tonic-gate * command should be sent. if the after command is already on 7710Sstevel@tonic-gate * our list, add the email addr to the list the email addrs for 7720Sstevel@tonic-gate * that command (the after command will only be executed once, 7730Sstevel@tonic-gate * so the error output gets mailed to every address we've come 7740Sstevel@tonic-gate * across associated with this command). 7750Sstevel@tonic-gate */ 7760Sstevel@tonic-gate if (opts_count(opts, "a")) { 7770Sstevel@tonic-gate const char *cmd = opts_optarg(opts, "a"); 7780Sstevel@tonic-gate struct lut *addrs = (struct lut *)lut_lookup(Aftercmds, cmd); 7790Sstevel@tonic-gate if (opts_count(opts, "e")) 7800Sstevel@tonic-gate addrs = lut_add(addrs, opts_optarg(opts, "e"), NULL); 7810Sstevel@tonic-gate Aftercmds = lut_add(Aftercmds, opts_optarg(opts, "a"), addrs); 7820Sstevel@tonic-gate } 7830Sstevel@tonic-gate 7840Sstevel@tonic-gate /* record the rotation date */ 7850Sstevel@tonic-gate (void) strftime(nowstr, sizeof (nowstr), 7860Sstevel@tonic-gate "%a %b %e %T %Y", gmtime(&Now)); 7872397Sbasabi if (opts_count(opts, "v") && fname != NULL) 7880Sstevel@tonic-gate (void) out("# recording rotation date %s for %s\n", 7890Sstevel@tonic-gate nowstr, fname); 7900Sstevel@tonic-gate conf_set(fname, "P", STRDUP(nowstr)); 7910Sstevel@tonic-gate Donenames = lut_add(Donenames, fname, "1"); 7920Sstevel@tonic-gate return (B_TRUE); 7930Sstevel@tonic-gate } 7940Sstevel@tonic-gate 7950Sstevel@tonic-gate /* rotate files "up" according to current template */ 7960Sstevel@tonic-gate static void 7970Sstevel@tonic-gate rotateto(struct fn *fnp, struct opts *opts, int n, struct fn *recentlog, 7980Sstevel@tonic-gate boolean_t isgz) 7990Sstevel@tonic-gate { 8000Sstevel@tonic-gate struct fn *template = fn_new(opts_optarg(opts, "t")); 8010Sstevel@tonic-gate struct fn *newfile = fn_new(NULL); 8020Sstevel@tonic-gate struct fn *dirname; 8030Sstevel@tonic-gate int hasn; 8040Sstevel@tonic-gate struct stat stbuf; 8052397Sbasabi char *buf1; 8062397Sbasabi char *buf2; 8070Sstevel@tonic-gate 8080Sstevel@tonic-gate /* expand template to figure out new filename */ 8090Sstevel@tonic-gate hasn = kw_expand(template, newfile, n, isgz); 8100Sstevel@tonic-gate 8112397Sbasabi buf1 = fn_s(fnp); 8122397Sbasabi buf2 = fn_s(newfile); 8132397Sbasabi 8140Sstevel@tonic-gate if (Debug) 8152397Sbasabi if (buf1 != NULL && buf2 != NULL) { 8162397Sbasabi (void) fprintf(stderr, "rotateto: %s -> %s (%d)\n", 8172397Sbasabi buf1, buf2, n); 8182397Sbasabi } 8190Sstevel@tonic-gate /* if filename is there already, rotate "up" */ 8202397Sbasabi if (hasn && lstat(buf2, &stbuf) != -1) 8210Sstevel@tonic-gate rotateto(newfile, opts, n + 1, recentlog, isgz); 8220Sstevel@tonic-gate else if (hasn && opts_count(opts, "z")) { 8230Sstevel@tonic-gate struct fn *gzfnp = fn_dup(newfile); 8240Sstevel@tonic-gate /* 8250Sstevel@tonic-gate * since we're compressing old files, see if we 8260Sstevel@tonic-gate * about to rotate into one. 8270Sstevel@tonic-gate */ 8280Sstevel@tonic-gate fn_puts(gzfnp, ".gz"); 8290Sstevel@tonic-gate if (lstat(fn_s(gzfnp), &stbuf) != -1) 8300Sstevel@tonic-gate rotateto(gzfnp, opts, n + 1, recentlog, B_TRUE); 8310Sstevel@tonic-gate fn_free(gzfnp); 8320Sstevel@tonic-gate } 8330Sstevel@tonic-gate 8340Sstevel@tonic-gate /* first time through run "before" cmd if not run already */ 8350Sstevel@tonic-gate if (n == 0 && opts_count(opts, "b")) { 8360Sstevel@tonic-gate const char *cmd = opts_optarg(opts, "b"); 8370Sstevel@tonic-gate 8380Sstevel@tonic-gate if (lut_lookup(Beforecmds, cmd) == NULL) { 8390Sstevel@tonic-gate docmd(opts, "-b cmd", Sh, "-c", cmd, NULL); 8400Sstevel@tonic-gate Beforecmds = lut_add(Beforecmds, cmd, "1"); 8410Sstevel@tonic-gate } 8420Sstevel@tonic-gate } 8430Sstevel@tonic-gate 8440Sstevel@tonic-gate /* ensure destination directory exists */ 8450Sstevel@tonic-gate dirname = fn_dirname(newfile); 8460Sstevel@tonic-gate docmd(opts, "verify directory exists", Mkdir, "-p", 8470Sstevel@tonic-gate fn_s(dirname), NULL); 8480Sstevel@tonic-gate fn_free(dirname); 8490Sstevel@tonic-gate 8500Sstevel@tonic-gate /* do the rename */ 8510Sstevel@tonic-gate if (opts_count(opts, "c")) { 8520Sstevel@tonic-gate docmd(opts, "rotate log file via copy (-c flag)", 8530Sstevel@tonic-gate Cp, "-fp", fn_s(fnp), fn_s(newfile)); 8540Sstevel@tonic-gate docmd(opts, "truncate log file (-c flag)", 8550Sstevel@tonic-gate Cp, "-f", "/dev/null", fn_s(fnp)); 8560Sstevel@tonic-gate } else if (n == 0 && opts_count(opts, "M")) { 8570Sstevel@tonic-gate struct fn *rawcmd = fn_new(opts_optarg(opts, "M")); 8580Sstevel@tonic-gate struct fn *cmd = fn_new(NULL); 8590Sstevel@tonic-gate 8600Sstevel@tonic-gate /* use specified command to mv the log file */ 8610Sstevel@tonic-gate kw_init(fnp, newfile); 8620Sstevel@tonic-gate (void) kw_expand(rawcmd, cmd, 0, B_FALSE); 8630Sstevel@tonic-gate docmd(opts, "-M cmd", Sh, "-c", fn_s(cmd), NULL); 8640Sstevel@tonic-gate fn_free(rawcmd); 8650Sstevel@tonic-gate fn_free(cmd); 8660Sstevel@tonic-gate } else 8670Sstevel@tonic-gate /* common case: we call "mv" to handle the actual rename */ 8680Sstevel@tonic-gate docmd(opts, "rotate log file", Mv, "-f", 8690Sstevel@tonic-gate fn_s(fnp), fn_s(newfile)); 8700Sstevel@tonic-gate 8710Sstevel@tonic-gate /* first time through, gather interesting info for caller */ 8720Sstevel@tonic-gate if (n == 0) 8730Sstevel@tonic-gate fn_renew(recentlog, fn_s(newfile)); 8740Sstevel@tonic-gate } 8750Sstevel@tonic-gate 8760Sstevel@tonic-gate /* expire phase of logname processing */ 8770Sstevel@tonic-gate static void 8780Sstevel@tonic-gate expirefiles(struct fn *fnp, struct opts *opts) 8790Sstevel@tonic-gate { 8800Sstevel@tonic-gate char *fname = fn_s(fnp); 8810Sstevel@tonic-gate struct fn *template; 8820Sstevel@tonic-gate struct fn *pattern; 8830Sstevel@tonic-gate struct fn_list *files; 8840Sstevel@tonic-gate struct fn *nextfnp; 8852016Sbasabi off_t count; 8862016Sbasabi off_t size; 8870Sstevel@tonic-gate 8882397Sbasabi if (Debug && fname != NULL) 8890Sstevel@tonic-gate (void) fprintf(stderr, "expirefiles: fname <%s>\n", fname); 8900Sstevel@tonic-gate 8910Sstevel@tonic-gate /* return if no potential expire conditions */ 8922876Snakanon if (opts_count(opts, "zAS") == 0 && opts_optarg_int(opts, "C") == 0) 8930Sstevel@tonic-gate return; 8940Sstevel@tonic-gate 8950Sstevel@tonic-gate kw_init(fnp, NULL); 8960Sstevel@tonic-gate if (Debug > 1) { 8970Sstevel@tonic-gate (void) fprintf(stderr, "expirefiles keywords:\n"); 8980Sstevel@tonic-gate kw_print(stderr); 8990Sstevel@tonic-gate } 9000Sstevel@tonic-gate 9010Sstevel@tonic-gate /* see if pattern was supplied by user */ 9020Sstevel@tonic-gate if (opts_count(opts, "T")) { 9030Sstevel@tonic-gate template = fn_new(opts_optarg(opts, "T")); 9040Sstevel@tonic-gate pattern = glob_to_reglob(template); 9050Sstevel@tonic-gate } else { 9060Sstevel@tonic-gate /* nope, generate pattern based on rotation template */ 9070Sstevel@tonic-gate template = fn_new(opts_optarg(opts, "t")); 9080Sstevel@tonic-gate pattern = fn_new(NULL); 9090Sstevel@tonic-gate (void) kw_expand(template, pattern, -1, 9100Sstevel@tonic-gate opts_count(opts, "z") != 0); 9110Sstevel@tonic-gate } 9120Sstevel@tonic-gate 9130Sstevel@tonic-gate /* match all old log files (hopefully not any others as well!) */ 9140Sstevel@tonic-gate files = glob_reglob(pattern); 9150Sstevel@tonic-gate 9160Sstevel@tonic-gate if (Debug) { 9172397Sbasabi char *buf; 9182397Sbasabi 9192397Sbasabi buf = fn_s(pattern); 9202397Sbasabi if (buf != NULL) { 9212397Sbasabi (void) fprintf(stderr, "expirefiles: pattern <%s>\n", 9222397Sbasabi buf); 9232397Sbasabi } 9240Sstevel@tonic-gate fn_list_rewind(files); 9250Sstevel@tonic-gate while ((nextfnp = fn_list_next(files)) != NULL) 9262397Sbasabi buf = fn_s(nextfnp); 9272397Sbasabi if (buf != NULL) 9282397Sbasabi (void) fprintf(stderr, " <%s>\n", buf); 9290Sstevel@tonic-gate } 9300Sstevel@tonic-gate 9310Sstevel@tonic-gate /* see if count causes expiration */ 9320Sstevel@tonic-gate if ((count = opts_optarg_int(opts, "C")) > 0) { 9330Sstevel@tonic-gate int needexpire = fn_list_count(files) - count; 9340Sstevel@tonic-gate 9350Sstevel@tonic-gate if (Debug) 9360Sstevel@tonic-gate (void) fprintf(stderr, "expirefiles: needexpire %d\n", 9370Sstevel@tonic-gate needexpire); 9380Sstevel@tonic-gate 9390Sstevel@tonic-gate while (needexpire > 0 && 9400Sstevel@tonic-gate ((nextfnp = fn_list_popoldest(files)) != NULL)) { 9410Sstevel@tonic-gate dorm(opts, "expire by count rule", nextfnp); 9420Sstevel@tonic-gate fn_free(nextfnp); 9430Sstevel@tonic-gate needexpire--; 9440Sstevel@tonic-gate } 9450Sstevel@tonic-gate } 9460Sstevel@tonic-gate 9470Sstevel@tonic-gate /* see if total size causes expiration */ 9480Sstevel@tonic-gate if (opts_count(opts, "S") && (size = opts_optarg_int(opts, "S")) > 0) { 9490Sstevel@tonic-gate while (fn_list_totalsize(files) > size && 9500Sstevel@tonic-gate ((nextfnp = fn_list_popoldest(files)) != NULL)) { 9512876Snakanon dorm(opts, "expire by size rule", nextfnp); 9522876Snakanon fn_free(nextfnp); 9530Sstevel@tonic-gate } 9540Sstevel@tonic-gate } 9550Sstevel@tonic-gate 9560Sstevel@tonic-gate /* see if age causes expiration */ 9570Sstevel@tonic-gate if (opts_count(opts, "A")) { 9582016Sbasabi int mtime = (int)time(0) - (int)opts_optarg_int(opts, "A"); 9590Sstevel@tonic-gate 9602876Snakanon while ((nextfnp = fn_list_popoldest(files)) != NULL) { 9610Sstevel@tonic-gate if (fn_getstat(nextfnp)->st_mtime < mtime) { 9620Sstevel@tonic-gate dorm(opts, "expire by age rule", nextfnp); 9630Sstevel@tonic-gate fn_free(nextfnp); 9640Sstevel@tonic-gate } else { 965*3404Snakanon fn_list_addfn(files, nextfnp); 9660Sstevel@tonic-gate break; 9670Sstevel@tonic-gate } 9682876Snakanon } 9692876Snakanon } 9702876Snakanon 9712876Snakanon /* record old log files to be gzip'ed according to -z count */ 9722876Snakanon if (opts_count(opts, "z")) { 9732876Snakanon int zcount = (int)opts_optarg_int(opts, "z"); 9742876Snakanon int fcount = fn_list_count(files); 9752876Snakanon 9762876Snakanon while (fcount > zcount && 9772876Snakanon (nextfnp = fn_list_popoldest(files)) != NULL) { 9782876Snakanon if (!fn_isgz(nextfnp)) { 9792876Snakanon /* 9802876Snakanon * Don't gzip the old log file yet - 9812876Snakanon * it takes too long. Just remember that we 9822876Snakanon * need to gzip. 9832876Snakanon */ 9842876Snakanon if (Debug) { 9852876Snakanon (void) fprintf(stderr, 9862876Snakanon "will compress %s count %d\n", 9872876Snakanon fn_s(nextfnp), fcount); 9882876Snakanon } 9892876Snakanon Gzipnames = lut_add(Gzipnames, 9902876Snakanon fn_s(nextfnp), "1"); 9912876Snakanon } 9922876Snakanon fn_free(nextfnp); 9932876Snakanon fcount--; 9942876Snakanon } 9950Sstevel@tonic-gate } 9960Sstevel@tonic-gate 9970Sstevel@tonic-gate fn_free(template); 9980Sstevel@tonic-gate fn_list_free(files); 9990Sstevel@tonic-gate } 10000Sstevel@tonic-gate 10010Sstevel@tonic-gate /* execute a command to remove an expired log file */ 10020Sstevel@tonic-gate static void 10030Sstevel@tonic-gate dorm(struct opts *opts, const char *msg, struct fn *fnp) 10040Sstevel@tonic-gate { 10050Sstevel@tonic-gate if (opts_count(opts, "E")) { 10060Sstevel@tonic-gate struct fn *rawcmd = fn_new(opts_optarg(opts, "E")); 10070Sstevel@tonic-gate struct fn *cmd = fn_new(NULL); 10080Sstevel@tonic-gate 10090Sstevel@tonic-gate /* user supplied cmd, expand $file */ 10100Sstevel@tonic-gate kw_init(fnp, NULL); 10110Sstevel@tonic-gate (void) kw_expand(rawcmd, cmd, 0, B_FALSE); 10120Sstevel@tonic-gate docmd(opts, msg, Sh, "-c", fn_s(cmd), NULL); 10130Sstevel@tonic-gate fn_free(rawcmd); 10140Sstevel@tonic-gate fn_free(cmd); 10150Sstevel@tonic-gate } else 10160Sstevel@tonic-gate docmd(opts, msg, Rm, "-f", fn_s(fnp), NULL); 10171913Sdg199075 Gzipnames = lut_add(Gzipnames, fn_s(fnp), NULL); 10180Sstevel@tonic-gate } 10190Sstevel@tonic-gate 10200Sstevel@tonic-gate /* execute a command, producing -n and -v output as necessary */ 10210Sstevel@tonic-gate static void 10220Sstevel@tonic-gate docmd(struct opts *opts, const char *msg, const char *cmd, 10230Sstevel@tonic-gate const char *arg1, const char *arg2, const char *arg3) 10240Sstevel@tonic-gate { 10250Sstevel@tonic-gate int pid; 10260Sstevel@tonic-gate int errpipe[2]; 10270Sstevel@tonic-gate 10280Sstevel@tonic-gate /* print info about command if necessary */ 10290Sstevel@tonic-gate if (opts_count(opts, "vn")) { 10300Sstevel@tonic-gate const char *simplecmd; 10310Sstevel@tonic-gate 10320Sstevel@tonic-gate if ((simplecmd = strrchr(cmd, '/')) == NULL) 10330Sstevel@tonic-gate simplecmd = cmd; 10340Sstevel@tonic-gate else 10350Sstevel@tonic-gate simplecmd++; 10360Sstevel@tonic-gate (void) out("%s", simplecmd); 10370Sstevel@tonic-gate if (arg1) 10380Sstevel@tonic-gate (void) out(" %s", arg1); 10390Sstevel@tonic-gate if (arg2) 10400Sstevel@tonic-gate (void) out(" %s", arg2); 10410Sstevel@tonic-gate if (arg3) 10420Sstevel@tonic-gate (void) out(" %s", arg3); 10430Sstevel@tonic-gate if (msg) 10440Sstevel@tonic-gate (void) out(" # %s", msg); 10450Sstevel@tonic-gate (void) out("\n"); 10460Sstevel@tonic-gate } 10470Sstevel@tonic-gate 10480Sstevel@tonic-gate if (opts_count(opts, "n")) 10490Sstevel@tonic-gate return; /* -n means don't really do it */ 10500Sstevel@tonic-gate 10510Sstevel@tonic-gate /* 10520Sstevel@tonic-gate * run the cmd and see if it failed. this function is *not* a 10530Sstevel@tonic-gate * generic command runner -- we depend on some knowledge we 10540Sstevel@tonic-gate * have about the commands we run. first of all, we expect 10550Sstevel@tonic-gate * errors to spew something to stderr, and that something is 10560Sstevel@tonic-gate * typically short enough to fit into a pipe so we can wait() 10570Sstevel@tonic-gate * for the command to complete and then fetch the error text 10580Sstevel@tonic-gate * from the pipe. we also expect the exit codes to make sense. 10590Sstevel@tonic-gate * notice also that we only allow a command name which is an 10600Sstevel@tonic-gate * absolute pathname, and two args must be supplied (the 10610Sstevel@tonic-gate * second may be NULL, or they may both be NULL). 10620Sstevel@tonic-gate */ 10630Sstevel@tonic-gate if (pipe(errpipe) < 0) 10640Sstevel@tonic-gate err(EF_SYS, "pipe"); 10650Sstevel@tonic-gate 10660Sstevel@tonic-gate if ((pid = fork()) < 0) 10670Sstevel@tonic-gate err(EF_SYS, "fork"); 10680Sstevel@tonic-gate else if (pid) { 10690Sstevel@tonic-gate int wstat; 10700Sstevel@tonic-gate int count; 10710Sstevel@tonic-gate 10720Sstevel@tonic-gate /* parent */ 10730Sstevel@tonic-gate (void) close(errpipe[1]); 10740Sstevel@tonic-gate if (waitpid(pid, &wstat, 0) < 0) 10750Sstevel@tonic-gate err(EF_SYS, "waitpid"); 10760Sstevel@tonic-gate 10770Sstevel@tonic-gate /* check for stderr output */ 10780Sstevel@tonic-gate if (ioctl(errpipe[0], FIONREAD, &count) >= 0 && count) { 10790Sstevel@tonic-gate err(EF_WARN, "command failed: %s%s%s%s%s%s%s", 10800Sstevel@tonic-gate cmd, 10810Sstevel@tonic-gate (arg1) ? " " : "", 10820Sstevel@tonic-gate (arg1) ? arg1 : "", 10830Sstevel@tonic-gate (arg2) ? " " : "", 10840Sstevel@tonic-gate (arg2) ? arg2 : "", 10850Sstevel@tonic-gate (arg3) ? " " : "", 10860Sstevel@tonic-gate (arg3) ? arg3 : ""); 10870Sstevel@tonic-gate err_fromfd(errpipe[0]); 10880Sstevel@tonic-gate } else if (WIFSIGNALED(wstat)) 10890Sstevel@tonic-gate err(EF_WARN, 10900Sstevel@tonic-gate "command died, signal %d: %s%s%s%s%s%s%s", 10910Sstevel@tonic-gate WTERMSIG(wstat), 10920Sstevel@tonic-gate cmd, 10930Sstevel@tonic-gate (arg1) ? " " : "", 10940Sstevel@tonic-gate (arg1) ? arg1 : "", 10950Sstevel@tonic-gate (arg2) ? " " : "", 10960Sstevel@tonic-gate (arg2) ? arg2 : "", 10970Sstevel@tonic-gate (arg3) ? " " : "", 10980Sstevel@tonic-gate (arg3) ? arg3 : ""); 10990Sstevel@tonic-gate else if (WIFEXITED(wstat) && WEXITSTATUS(wstat)) 11000Sstevel@tonic-gate err(EF_WARN, 11010Sstevel@tonic-gate "command error, exit %d: %s%s%s%s%s%s%s", 11020Sstevel@tonic-gate WEXITSTATUS(wstat), 11030Sstevel@tonic-gate cmd, 11040Sstevel@tonic-gate (arg1) ? " " : "", 11050Sstevel@tonic-gate (arg1) ? arg1 : "", 11060Sstevel@tonic-gate (arg2) ? " " : "", 11070Sstevel@tonic-gate (arg2) ? arg2 : "", 11080Sstevel@tonic-gate (arg3) ? " " : "", 11090Sstevel@tonic-gate (arg3) ? arg3 : ""); 11100Sstevel@tonic-gate 11110Sstevel@tonic-gate (void) close(errpipe[0]); 11120Sstevel@tonic-gate } else { 11130Sstevel@tonic-gate /* child */ 11140Sstevel@tonic-gate (void) dup2(errpipe[1], fileno(stderr)); 11150Sstevel@tonic-gate (void) close(errpipe[0]); 11160Sstevel@tonic-gate (void) execl(cmd, cmd, arg1, arg2, arg3, 0); 11170Sstevel@tonic-gate perror(cmd); 11180Sstevel@tonic-gate _exit(1); 11190Sstevel@tonic-gate } 11200Sstevel@tonic-gate } 1121