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