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 if (fn_list_empty(logfiles)) 552 globbedfiles = glob_glob(fnp); 553 else 554 globbedfiles = glob_glob_list(logfiles); 555 556 /* go through the list produced by glob expansion */ 557 fn_list_rewind(globbedfiles); 558 while ((nextfnp = fn_list_next(globbedfiles)) != NULL) 559 if (rotatelog(nextfnp, allopts)) 560 expirefiles(nextfnp, allopts); 561 562 fn_list_free(globbedfiles); 563 opts_free(allopts); 564 } 565 566 567 /* absurdly long buffer lengths for holding user/group/mode strings */ 568 #define TIMESTRMAX 100 569 #define MAXATTR 100 570 571 /* rotate a log file if necessary, returns true if ok to go on to expire step */ 572 static boolean_t 573 rotatelog(struct fn *fnp, struct opts *opts) 574 { 575 char *fname = fn_s(fnp); 576 struct stat stbuf; 577 char nowstr[TIMESTRMAX]; 578 struct fn *recentlog = fn_new(NULL); /* for -R cmd */ 579 char ownerbuf[MAXATTR]; 580 char groupbuf[MAXATTR]; 581 char modebuf[MAXATTR]; 582 const char *owner; 583 const char *group; 584 const char *mode; 585 586 if (Debug && fname != NULL) 587 (void) fprintf(stderr, "rotatelog: fname <%s>\n", fname); 588 589 if (opts_count(opts, "p") && opts_optarg_int(opts, "p") == OPTP_NEVER) 590 return (B_TRUE); /* "-p never" forced no rotate */ 591 592 /* prepare the keywords */ 593 kw_init(fnp, NULL); 594 if (Debug > 1) { 595 (void) fprintf(stderr, "rotatelog keywords:\n"); 596 kw_print(stderr); 597 } 598 599 if (lstat(fname, &stbuf) < 0) { 600 if (opts_count(opts, "N")) 601 return (1); 602 err(EF_WARN|EF_SYS, "%s", fname); 603 return (B_FALSE); 604 } 605 606 if ((stbuf.st_mode & S_IFMT) == S_IFLNK) { 607 err(EF_WARN, "%s is a symlink", fname); 608 return (B_FALSE); 609 } 610 611 if ((stbuf.st_mode & S_IFMT) != S_IFREG) { 612 err(EF_WARN, "%s is not a regular file", fname); 613 return (B_FALSE); 614 } 615 616 /* even if size condition is not met, this entry is "done" */ 617 if (opts_count(opts, "s") && 618 stbuf.st_size < opts_optarg_int(opts, "s")) { 619 Donenames = lut_add(Donenames, fname, "1"); 620 return (B_TRUE); 621 } 622 623 /* see if age condition is present, and return if not met */ 624 if (opts_count(opts, "p")) { 625 off_t when = opts_optarg_int(opts, "p"); 626 struct opts *cfopts; 627 628 /* unless rotate forced by "-p now", see if period has passed */ 629 if (when != OPTP_NOW) { 630 /* 631 * "when" holds the number of seconds that must have 632 * passed since the last time this log was rotated. 633 * of course, running logadm can take a little time 634 * (typically a second or two, but longer if the 635 * conffile has lots of stuff in it) and that amount 636 * of time is variable, depending on system load, etc. 637 * so we want to allow a little "slop" in the value of 638 * "when". this way, if a log should be rotated every 639 * week, and the number of seconds passed is really a 640 * few seconds short of a week, we'll go ahead and 641 * rotate the log as expected. 642 * 643 */ 644 if (when >= 60 * 60) 645 when -= 59; 646 647 /* 648 * last rotation is recorded as argument to -P, 649 * but if logname isn't the same as log file name 650 * then the timestamp would be recorded on a 651 * separate line in the conf file. so if we 652 * haven't seen a -P already, we check to see if 653 * it is part of a specific entry for the log 654 * file name. this handles the case where the 655 * logname is "apache", it supplies a log file 656 * name like "/var/apache/logs/[a-z]*_log", 657 * which expands to multiple file names. if one 658 * of the file names is "/var/apache/logs/access_log" 659 * the the -P will be attached to a line with that 660 * logname in the conf file. 661 */ 662 if (opts_count(opts, "P")) { 663 off_t last = opts_optarg_int(opts, "P"); 664 665 /* return if not enough time has passed */ 666 if (Now - last < when) 667 return (B_TRUE); 668 } else if ((cfopts = conf_opts(fname)) != NULL && 669 opts_count(cfopts, "P")) { 670 off_t last = opts_optarg_int(cfopts, "P"); 671 672 /* 673 * just checking this means this entry 674 * is now "done" if we're going through 675 * the entire conffile 676 */ 677 Donenames = lut_add(Donenames, fname, "1"); 678 679 /* return if not enough time has passed */ 680 if (Now - last < when) 681 return (B_TRUE); 682 } 683 } 684 } 685 686 if (Debug) 687 (void) fprintf(stderr, "rotatelog: conditions met\n"); 688 if (opts_count(opts, "l")) { 689 /* Change the time zone to local time zone */ 690 if (putenv("TZ=")) 691 err(EF_SYS, "putenv TZ"); 692 tzset(); 693 Now = time(0); 694 695 /* rename the log file */ 696 rotateto(fnp, opts, 0, recentlog, B_FALSE); 697 698 /* Change the time zone to UTC */ 699 if (putenv("TZ=UTC")) 700 err(EF_SYS, "putenv TZ"); 701 tzset(); 702 Now = time(0); 703 } else { 704 /* rename the log file */ 705 rotateto(fnp, opts, 0, recentlog, B_FALSE); 706 } 707 708 /* determine owner, group, mode for empty log file */ 709 if (opts_count(opts, "o")) 710 (void) strlcpy(ownerbuf, opts_optarg(opts, "o"), MAXATTR); 711 else { 712 (void) snprintf(ownerbuf, MAXATTR, "%ld", stbuf.st_uid); 713 } 714 owner = ownerbuf; 715 if (opts_count(opts, "g")) 716 group = opts_optarg(opts, "g"); 717 else { 718 (void) snprintf(groupbuf, MAXATTR, "%ld", stbuf.st_gid); 719 group = groupbuf; 720 } 721 (void) strlcat(ownerbuf, ":", MAXATTR - strlen(ownerbuf)); 722 (void) strlcat(ownerbuf, group, MAXATTR - strlen(ownerbuf)); 723 if (opts_count(opts, "m")) 724 mode = opts_optarg(opts, "m"); 725 else { 726 (void) snprintf(modebuf, MAXATTR, 727 "%03lo", stbuf.st_mode & 0777); 728 mode = modebuf; 729 } 730 731 /* create the empty log file */ 732 docmd(opts, NULL, Touch, fname, NULL, NULL); 733 docmd(opts, NULL, Chown, owner, fname, NULL); 734 docmd(opts, NULL, Chmod, mode, fname, NULL); 735 736 /* execute post-rotation command */ 737 if (opts_count(opts, "R")) { 738 struct fn *rawcmd = fn_new(opts_optarg(opts, "R")); 739 struct fn *cmd = fn_new(NULL); 740 741 kw_init(recentlog, NULL); 742 (void) kw_expand(rawcmd, cmd, 0, B_FALSE); 743 docmd(opts, "-R cmd", Sh, "-c", fn_s(cmd), NULL); 744 fn_free(rawcmd); 745 fn_free(cmd); 746 } 747 fn_free(recentlog); 748 749 /* 750 * add "after" command to list of after commands. we also record 751 * the email address, if any, where the error output of the after 752 * command should be sent. if the after command is already on 753 * our list, add the email addr to the list the email addrs for 754 * that command (the after command will only be executed once, 755 * so the error output gets mailed to every address we've come 756 * across associated with this command). 757 */ 758 if (opts_count(opts, "a")) { 759 const char *cmd = opts_optarg(opts, "a"); 760 struct lut *addrs = (struct lut *)lut_lookup(Aftercmds, cmd); 761 if (opts_count(opts, "e")) 762 addrs = lut_add(addrs, opts_optarg(opts, "e"), NULL); 763 Aftercmds = lut_add(Aftercmds, opts_optarg(opts, "a"), addrs); 764 } 765 766 /* record the rotation date */ 767 (void) strftime(nowstr, sizeof (nowstr), 768 "%a %b %e %T %Y", gmtime(&Now)); 769 if (opts_count(opts, "v") && fname != NULL) 770 (void) out("# recording rotation date %s for %s\n", 771 nowstr, fname); 772 conf_set(fname, "P", STRDUP(nowstr)); 773 Donenames = lut_add(Donenames, fname, "1"); 774 return (B_TRUE); 775 } 776 777 /* rotate files "up" according to current template */ 778 static void 779 rotateto(struct fn *fnp, struct opts *opts, int n, struct fn *recentlog, 780 boolean_t isgz) 781 { 782 struct fn *template = fn_new(opts_optarg(opts, "t")); 783 struct fn *newfile = fn_new(NULL); 784 struct fn *dirname; 785 int hasn; 786 struct stat stbuf; 787 char *buf1; 788 char *buf2; 789 790 /* expand template to figure out new filename */ 791 hasn = kw_expand(template, newfile, n, isgz); 792 793 buf1 = fn_s(fnp); 794 buf2 = fn_s(newfile); 795 796 if (Debug) 797 if (buf1 != NULL && buf2 != NULL) { 798 (void) fprintf(stderr, "rotateto: %s -> %s (%d)\n", 799 buf1, buf2, n); 800 } 801 /* if filename is there already, rotate "up" */ 802 if (hasn && lstat(buf2, &stbuf) != -1) 803 rotateto(newfile, opts, n + 1, recentlog, isgz); 804 else if (hasn && opts_count(opts, "z")) { 805 struct fn *gzfnp = fn_dup(newfile); 806 /* 807 * since we're compressing old files, see if we 808 * about to rotate into one. 809 */ 810 fn_puts(gzfnp, ".gz"); 811 if (lstat(fn_s(gzfnp), &stbuf) != -1) 812 rotateto(gzfnp, opts, n + 1, recentlog, B_TRUE); 813 fn_free(gzfnp); 814 } 815 816 /* first time through run "before" cmd if not run already */ 817 if (n == 0 && opts_count(opts, "b")) { 818 const char *cmd = opts_optarg(opts, "b"); 819 820 if (lut_lookup(Beforecmds, cmd) == NULL) { 821 docmd(opts, "-b cmd", Sh, "-c", cmd, NULL); 822 Beforecmds = lut_add(Beforecmds, cmd, "1"); 823 } 824 } 825 826 /* ensure destination directory exists */ 827 dirname = fn_dirname(newfile); 828 docmd(opts, "verify directory exists", Mkdir, "-p", 829 fn_s(dirname), NULL); 830 fn_free(dirname); 831 832 /* do the rename */ 833 if (n == 0 && opts_count(opts, "c") != NULL) { 834 docopytruncate(opts, fn_s(fnp), fn_s(newfile)); 835 } else if (n == 0 && opts_count(opts, "M")) { 836 struct fn *rawcmd = fn_new(opts_optarg(opts, "M")); 837 struct fn *cmd = fn_new(NULL); 838 839 /* use specified command to mv the log file */ 840 kw_init(fnp, newfile); 841 (void) kw_expand(rawcmd, cmd, 0, B_FALSE); 842 docmd(opts, "-M cmd", Sh, "-c", fn_s(cmd), NULL); 843 fn_free(rawcmd); 844 fn_free(cmd); 845 } else 846 /* common case: we call "mv" to handle the actual rename */ 847 docmd(opts, "rotate log file", Mv, "-f", 848 fn_s(fnp), fn_s(newfile)); 849 850 /* first time through, gather interesting info for caller */ 851 if (n == 0) 852 fn_renew(recentlog, fn_s(newfile)); 853 } 854 855 /* expire phase of logname processing */ 856 static void 857 expirefiles(struct fn *fnp, struct opts *opts) 858 { 859 char *fname = fn_s(fnp); 860 struct fn *template; 861 struct fn *pattern; 862 struct fn_list *files; 863 struct fn *nextfnp; 864 off_t count; 865 off_t size; 866 867 if (Debug && fname != NULL) 868 (void) fprintf(stderr, "expirefiles: fname <%s>\n", fname); 869 870 /* return if no potential expire conditions */ 871 if (opts_count(opts, "zAS") == 0 && opts_optarg_int(opts, "C") == 0) 872 return; 873 874 kw_init(fnp, NULL); 875 if (Debug > 1) { 876 (void) fprintf(stderr, "expirefiles keywords:\n"); 877 kw_print(stderr); 878 } 879 880 /* see if pattern was supplied by user */ 881 if (opts_count(opts, "T")) { 882 template = fn_new(opts_optarg(opts, "T")); 883 pattern = glob_to_reglob(template); 884 } else { 885 /* nope, generate pattern based on rotation template */ 886 template = fn_new(opts_optarg(opts, "t")); 887 pattern = fn_new(NULL); 888 (void) kw_expand(template, pattern, -1, 889 opts_count(opts, "z") != 0); 890 } 891 892 /* match all old log files (hopefully not any others as well!) */ 893 files = glob_reglob(pattern); 894 895 if (Debug) { 896 char *buf; 897 898 buf = fn_s(pattern); 899 if (buf != NULL) { 900 (void) fprintf(stderr, "expirefiles: pattern <%s>\n", 901 buf); 902 } 903 fn_list_rewind(files); 904 while ((nextfnp = fn_list_next(files)) != NULL) 905 buf = fn_s(nextfnp); 906 if (buf != NULL) 907 (void) fprintf(stderr, " <%s>\n", buf); 908 } 909 910 /* see if count causes expiration */ 911 if ((count = opts_optarg_int(opts, "C")) > 0) { 912 int needexpire = fn_list_count(files) - count; 913 914 if (Debug) 915 (void) fprintf(stderr, "expirefiles: needexpire %d\n", 916 needexpire); 917 918 while (needexpire > 0 && 919 ((nextfnp = fn_list_popoldest(files)) != NULL)) { 920 dorm(opts, "expire by count rule", nextfnp); 921 fn_free(nextfnp); 922 needexpire--; 923 } 924 } 925 926 /* see if total size causes expiration */ 927 if (opts_count(opts, "S") && (size = opts_optarg_int(opts, "S")) > 0) { 928 while (fn_list_totalsize(files) > size && 929 ((nextfnp = fn_list_popoldest(files)) != NULL)) { 930 dorm(opts, "expire by size rule", nextfnp); 931 fn_free(nextfnp); 932 } 933 } 934 935 /* see if age causes expiration */ 936 if (opts_count(opts, "A")) { 937 int mtime = (int)time(0) - (int)opts_optarg_int(opts, "A"); 938 939 while ((nextfnp = fn_list_popoldest(files)) != NULL) { 940 if (fn_getstat(nextfnp)->st_mtime < mtime) { 941 dorm(opts, "expire by age rule", nextfnp); 942 fn_free(nextfnp); 943 } else { 944 fn_list_addfn(files, nextfnp); 945 break; 946 } 947 } 948 } 949 950 /* record old log files to be gzip'ed according to -z count */ 951 if (opts_count(opts, "z")) { 952 int zcount = (int)opts_optarg_int(opts, "z"); 953 int fcount = fn_list_count(files); 954 955 while (fcount > zcount && 956 (nextfnp = fn_list_popoldest(files)) != NULL) { 957 if (!fn_isgz(nextfnp)) { 958 /* 959 * Don't gzip the old log file yet - 960 * it takes too long. Just remember that we 961 * need to gzip. 962 */ 963 if (Debug) { 964 (void) fprintf(stderr, 965 "will compress %s count %d\n", 966 fn_s(nextfnp), fcount); 967 } 968 Gzipnames = lut_add(Gzipnames, 969 fn_s(nextfnp), "1"); 970 } 971 fn_free(nextfnp); 972 fcount--; 973 } 974 } 975 976 fn_free(template); 977 fn_list_free(files); 978 } 979 980 /* execute a command to remove an expired log file */ 981 static void 982 dorm(struct opts *opts, const char *msg, struct fn *fnp) 983 { 984 if (opts_count(opts, "E")) { 985 struct fn *rawcmd = fn_new(opts_optarg(opts, "E")); 986 struct fn *cmd = fn_new(NULL); 987 988 /* user supplied cmd, expand $file */ 989 kw_init(fnp, NULL); 990 (void) kw_expand(rawcmd, cmd, 0, B_FALSE); 991 docmd(opts, msg, Sh, "-c", fn_s(cmd), NULL); 992 fn_free(rawcmd); 993 fn_free(cmd); 994 } else 995 docmd(opts, msg, Rm, "-f", fn_s(fnp), NULL); 996 Gzipnames = lut_add(Gzipnames, fn_s(fnp), NULL); 997 } 998 999 /* execute a command, producing -n and -v output as necessary */ 1000 static void 1001 docmd(struct opts *opts, const char *msg, const char *cmd, 1002 const char *arg1, const char *arg2, const char *arg3) 1003 { 1004 int pid; 1005 int errpipe[2]; 1006 1007 /* print info about command if necessary */ 1008 if (opts_count(opts, "vn")) { 1009 const char *simplecmd; 1010 1011 if ((simplecmd = strrchr(cmd, '/')) == NULL) 1012 simplecmd = cmd; 1013 else 1014 simplecmd++; 1015 (void) out("%s", simplecmd); 1016 if (arg1) 1017 (void) out(" %s", arg1); 1018 if (arg2) 1019 (void) out(" %s", arg2); 1020 if (arg3) 1021 (void) out(" %s", arg3); 1022 if (msg) 1023 (void) out(" # %s", msg); 1024 (void) out("\n"); 1025 } 1026 1027 if (opts_count(opts, "n")) 1028 return; /* -n means don't really do it */ 1029 1030 /* 1031 * run the cmd and see if it failed. this function is *not* a 1032 * generic command runner -- we depend on some knowledge we 1033 * have about the commands we run. first of all, we expect 1034 * errors to spew something to stderr, and that something is 1035 * typically short enough to fit into a pipe so we can wait() 1036 * for the command to complete and then fetch the error text 1037 * from the pipe. we also expect the exit codes to make sense. 1038 * notice also that we only allow a command name which is an 1039 * absolute pathname, and two args must be supplied (the 1040 * second may be NULL, or they may both be NULL). 1041 */ 1042 if (pipe(errpipe) < 0) 1043 err(EF_SYS, "pipe"); 1044 1045 if ((pid = fork()) < 0) 1046 err(EF_SYS, "fork"); 1047 else if (pid) { 1048 int wstat; 1049 int count; 1050 1051 /* parent */ 1052 (void) close(errpipe[1]); 1053 if (waitpid(pid, &wstat, 0) < 0) 1054 err(EF_SYS, "waitpid"); 1055 1056 /* check for stderr output */ 1057 if (ioctl(errpipe[0], FIONREAD, &count) >= 0 && count) { 1058 err(EF_WARN, "command failed: %s%s%s%s%s%s%s", 1059 cmd, 1060 (arg1) ? " " : "", 1061 (arg1) ? arg1 : "", 1062 (arg2) ? " " : "", 1063 (arg2) ? arg2 : "", 1064 (arg3) ? " " : "", 1065 (arg3) ? arg3 : ""); 1066 err_fromfd(errpipe[0]); 1067 } else if (WIFSIGNALED(wstat)) 1068 err(EF_WARN, 1069 "command died, signal %d: %s%s%s%s%s%s%s", 1070 WTERMSIG(wstat), 1071 cmd, 1072 (arg1) ? " " : "", 1073 (arg1) ? arg1 : "", 1074 (arg2) ? " " : "", 1075 (arg2) ? arg2 : "", 1076 (arg3) ? " " : "", 1077 (arg3) ? arg3 : ""); 1078 else if (WIFEXITED(wstat) && WEXITSTATUS(wstat)) 1079 err(EF_WARN, 1080 "command error, exit %d: %s%s%s%s%s%s%s", 1081 WEXITSTATUS(wstat), 1082 cmd, 1083 (arg1) ? " " : "", 1084 (arg1) ? arg1 : "", 1085 (arg2) ? " " : "", 1086 (arg2) ? arg2 : "", 1087 (arg3) ? " " : "", 1088 (arg3) ? arg3 : ""); 1089 1090 (void) close(errpipe[0]); 1091 } else { 1092 /* child */ 1093 (void) dup2(errpipe[1], fileno(stderr)); 1094 (void) close(errpipe[0]); 1095 (void) execl(cmd, cmd, arg1, arg2, arg3, 0); 1096 perror(cmd); 1097 _exit(1); 1098 } 1099 } 1100 1101 /* do internal atomic file copy and truncation */ 1102 static void 1103 docopytruncate(struct opts *opts, const char *file, const char *file_copy) 1104 { 1105 int fi, fo; 1106 char buf[128 * 1024]; 1107 struct stat s; 1108 struct utimbuf times; 1109 off_t written = 0, rem, last = 0, thresh = 1024 * 1024; 1110 ssize_t len; 1111 1112 /* print info if necessary */ 1113 if (opts_count(opts, "vn") != NULL) { 1114 (void) out("# log rotation via atomic copy and truncation" 1115 " (-c flag):\n"); 1116 (void) out("# copy %s to %s\n", file, file_copy); 1117 (void) out("# truncate %s\n", file); 1118 } 1119 1120 if (opts_count(opts, "n")) 1121 return; /* -n means don't really do it */ 1122 1123 /* open log file to be rotated and remember its chmod mask */ 1124 if ((fi = open(file, O_RDWR)) < 0) { 1125 err(EF_SYS, "cannot open file %s", file); 1126 return; 1127 } 1128 1129 if (fstat(fi, &s) < 0) { 1130 err(EF_SYS, "cannot access: %s", file); 1131 (void) close(fi); 1132 return; 1133 } 1134 1135 /* create new file for copy destination with correct attributes */ 1136 if ((fo = open(file_copy, O_CREAT|O_TRUNC|O_WRONLY, s.st_mode)) < 0) { 1137 err(EF_SYS, "cannot create file: %s", file_copy); 1138 (void) close(fi); 1139 return; 1140 } 1141 1142 (void) fchown(fo, s.st_uid, s.st_gid); 1143 1144 /* 1145 * Now we'll loop, reading the log file and writing it to our copy 1146 * until the bytes remaining are beneath our atomicity threshold -- at 1147 * which point we'll lock the file and copy the remainder atomically. 1148 * The body of this loop is non-atomic with respect to writers, the 1149 * rationale being that total atomicity (that is, locking the file for 1150 * the entire duration of the copy) comes at too great a cost for a 1151 * large log file, as the writer (i.e., the daemon whose log is being 1152 * rolled) can be blocked for an unacceptable duration. (For one 1153 * particularly loquacious daemon, this period was observed to be 1154 * several minutes in length -- a time so long that it induced 1155 * additional failures in dependent components.) Note that this means 1156 * that if the log file is not always appended to -- if it is opened 1157 * without O_APPEND or otherwise truncated outside of logadm -- this 1158 * will result in our log snapshot being incorrect. But of course, in 1159 * either of these cases, the use of logadm at all is itself 1160 * suspect... 1161 */ 1162 do { 1163 if (fstat(fi, &s) < 0) { 1164 err(EF_SYS, "cannot stat: %s", file); 1165 (void) close(fi); 1166 (void) close(fo); 1167 (void) remove(file_copy); 1168 return; 1169 } 1170 1171 if ((rem = s.st_size - written) < thresh) { 1172 if (rem >= 0) 1173 break; 1174 1175 /* 1176 * If the file became smaller, something fishy is going 1177 * on; we'll truncate our copy, reset our seek offset 1178 * and break into the atomic copy. 1179 */ 1180 (void) ftruncate(fo, 0); 1181 (void) lseek(fo, 0, SEEK_SET); 1182 (void) lseek(fi, 0, SEEK_SET); 1183 break; 1184 } 1185 1186 if (written != 0 && rem > last) { 1187 /* 1188 * We're falling behind -- this file is getting bigger 1189 * faster than we're able to write it; break out and 1190 * lock the file to block the writer. 1191 */ 1192 break; 1193 } 1194 1195 last = rem; 1196 1197 while (rem > 0) { 1198 if ((len = read(fi, buf, MIN(sizeof (buf), rem))) <= 0) 1199 break; 1200 1201 if (write(fo, buf, len) == len) { 1202 rem -= len; 1203 written += len; 1204 continue; 1205 } 1206 1207 err(EF_SYS, "cannot write into file %s", file_copy); 1208 (void) close(fi); 1209 (void) close(fo); 1210 (void) remove(file_copy); 1211 return; 1212 } 1213 } while (len >= 0); 1214 1215 /* lock log file so that nobody can write into it before we are done */ 1216 if (fchmod(fi, s.st_mode|S_ISGID) < 0) 1217 err(EF_SYS, "cannot set mandatory lock bit for: %s", file); 1218 1219 if (lockf(fi, F_LOCK, 0) == -1) 1220 err(EF_SYS, "cannot lock file %s", file); 1221 1222 /* do atomic copy and truncation */ 1223 while ((len = read(fi, buf, sizeof (buf))) > 0) 1224 if (write(fo, buf, len) != len) { 1225 err(EF_SYS, "cannot write into file %s", file_copy); 1226 (void) lockf(fi, F_ULOCK, 0); 1227 (void) fchmod(fi, s.st_mode); 1228 (void) close(fi); 1229 (void) close(fo); 1230 (void) remove(file_copy); 1231 return; 1232 } 1233 1234 (void) ftruncate(fi, 0); 1235 1236 /* unlock log file */ 1237 if (lockf(fi, F_ULOCK, 0) == -1) 1238 err(EF_SYS, "cannot unlock file %s", file); 1239 1240 if (fchmod(fi, s.st_mode) < 0) 1241 err(EF_SYS, "cannot reset mandatory lock bit for: %s", file); 1242 1243 (void) close(fi); 1244 (void) close(fo); 1245 1246 /* keep times from original file */ 1247 times.actime = s.st_atime; 1248 times.modtime = s.st_mtime; 1249 (void) utime(file_copy, ×); 1250 } 1251