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