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