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