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