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