newsyslog.c revision 119926
1/* 2 * This file contains changes from the Open Software Foundation. 3 */ 4 5/* 6 * Copyright 1988, 1989 by the Massachusetts Institute of Technology 7 * 8 * Permission to use, copy, modify, and distribute this software and its 9 * documentation for any purpose and without fee is hereby granted, provided 10 * that the above copyright notice appear in all copies and that both that 11 * copyright notice and this permission notice appear in supporting 12 * documentation, and that the names of M.I.T. and the M.I.T. S.I.P.B. not be 13 * used in advertising or publicity pertaining to distribution of the 14 * software without specific, written prior permission. M.I.T. and the M.I.T. 15 * S.I.P.B. make no representations about the suitability of this software 16 * for any purpose. It is provided "as is" without express or implied 17 * warranty. 18 * 19 */ 20 21/* 22 * newsyslog - roll over selected logs at the appropriate time, keeping the a 23 * specified number of backup files around. 24 */ 25 26#include <sys/cdefs.h> 27__FBSDID("$FreeBSD: head/usr.sbin/newsyslog/newsyslog.c 119926 2003-09-09 20:29:26Z gad $"); 28 29#define OSF 30#ifndef COMPRESS_POSTFIX 31#define COMPRESS_POSTFIX ".gz" 32#endif 33#ifndef BZCOMPRESS_POSTFIX 34#define BZCOMPRESS_POSTFIX ".bz2" 35#endif 36 37#include <sys/param.h> 38#include <sys/stat.h> 39#include <sys/wait.h> 40 41#include <ctype.h> 42#include <err.h> 43#include <errno.h> 44#include <fcntl.h> 45#include <fnmatch.h> 46#include <glob.h> 47#include <grp.h> 48#include <paths.h> 49#include <pwd.h> 50#include <signal.h> 51#include <stdio.h> 52#include <stdlib.h> 53#include <string.h> 54#include <time.h> 55#include <unistd.h> 56 57#include "pathnames.h" 58 59/* 60 * Bit-values for the 'flags' parsed from a config-file entry. 61 */ 62#define CE_COMPACT 0x0001 /* Compact the achived log files with gzip. */ 63#define CE_BZCOMPACT 0x0002 /* Compact the achived log files with bzip2. */ 64#define CE_COMPACTWAIT 0x0004 /* wait until compressing one file finishes */ 65 /* before starting the next step. */ 66#define CE_BINARY 0x0008 /* Logfile is in binary, do not add status */ 67 /* messages to logfile(s) when rotating. */ 68#define CE_NOSIGNAL 0x0010 /* There is no process to signal when */ 69 /* trimming this file. */ 70#define CE_TRIMAT 0x0020 /* trim file at a specific time. */ 71#define CE_GLOB 0x0040 /* name of the log is file name pattern. */ 72#define CE_SIGNALGROUP 0x0080 /* Signal a process-group instead of a single */ 73 /* process when trimming this file. */ 74#define CE_CREATE 0x0100 /* Create the log file if it does not exist. */ 75 76#define MIN_PID 5 /* Don't touch pids lower than this */ 77#define MAX_PID 99999 /* was lower, see /usr/include/sys/proc.h */ 78 79#define kbytes(size) (((size) + 1023) >> 10) 80 81struct conf_entry { 82 char *log; /* Name of the log */ 83 char *pid_file; /* PID file */ 84 char *r_reason; /* The reason this file is being rotated */ 85 int firstcreate; /* Creating log for the first time (-C). */ 86 int rotate; /* Non-zero if this file should be rotated */ 87 uid_t uid; /* Owner of log */ 88 gid_t gid; /* Group of log */ 89 int numlogs; /* Number of logs to keep */ 90 int size; /* Size cutoff to trigger trimming the log */ 91 int hours; /* Hours between log trimming */ 92 time_t trim_at; /* Specific time to do trimming */ 93 int permissions; /* File permissions on the log */ 94 int flags; /* CE_COMPACT, CE_BZCOMPACT, CE_BINARY */ 95 int sig; /* Signal to send */ 96 int def_cfg; /* Using the <default> rule for this file */ 97 struct conf_entry *next;/* Linked list pointer */ 98}; 99 100#define DEFAULT_MARKER "<default>" 101 102int archtodir = 0; /* Archive old logfiles to other directory */ 103int createlogs; /* Create (non-GLOB) logfiles which do not */ 104 /* already exist. 1=='for entries with */ 105 /* C flag', 2=='for all entries'. */ 106int verbose = 0; /* Print out what's going on */ 107int needroot = 1; /* Root privs are necessary */ 108int noaction = 0; /* Don't do anything, just show it */ 109int nosignal; /* Do not send any signals */ 110int force = 0; /* Force the trim no matter what */ 111int rotatereq = 0; /* -R = Always rotate the file(s) as given */ 112 /* on the command (this also requires */ 113 /* that a list of files *are* given on */ 114 /* the run command). */ 115char *requestor; /* The name given on a -R request */ 116char *archdirname; /* Directory path to old logfiles archive */ 117const char *conf; /* Configuration file to use */ 118time_t dbg_timenow; /* A "timenow" value set via -D option */ 119time_t timenow; 120 121char hostname[MAXHOSTNAMELEN]; /* hostname */ 122char daytime[16]; /* timenow in human readable form */ 123 124static struct conf_entry *get_worklist(char **files); 125static void parse_file(FILE *cf, const char *cfname, struct conf_entry **work_p, 126 struct conf_entry **glob_p, struct conf_entry **defconf_p); 127static char *sob(char *p); 128static char *son(char *p); 129static int isnumberstr(const char *); 130static char *missing_field(char *p, char *errline); 131static void do_entry(struct conf_entry * ent); 132static void expand_globs(struct conf_entry **work_p, 133 struct conf_entry **glob_p); 134static void free_clist(struct conf_entry **firstent); 135static void free_entry(struct conf_entry *ent); 136static struct conf_entry *init_entry(const char *fname, 137 struct conf_entry *src_entry); 138static void parse_args(int argc, char **argv); 139static int parse_doption(const char *doption); 140static void usage(void); 141static void dotrim(const struct conf_entry *ent, char *log, 142 int numdays, int flags); 143static int log_trim(const char *logname, const struct conf_entry *log_ent); 144static void compress_log(char *logname, int dowait); 145static void bzcompress_log(char *logname, int dowait); 146static int sizefile(char *file); 147static int age_old_log(char *file); 148static int send_signal(const struct conf_entry *ent); 149static void movefile(char *from, char *to, int perm, uid_t owner_uid, 150 gid_t group_gid); 151static void createdir(const struct conf_entry *ent, char *dirpart); 152static void createlog(const struct conf_entry *ent); 153static time_t parse8601(const char *s); 154static time_t parseDWM(char *s); 155 156/* 157 * All the following are defined to work on an 'int', in the 158 * range 0 to 255, plus EOF. Define wrappers which can take 159 * values of type 'char', either signed or unsigned. 160 */ 161#define isdigitch(Anychar) isdigit(((int) Anychar) & 255) 162#define isprintch(Anychar) isprint(((int) Anychar) & 255) 163#define isspacech(Anychar) isspace(((int) Anychar) & 255) 164#define tolowerch(Anychar) tolower(((int) Anychar) & 255) 165 166int 167main(int argc, char **argv) 168{ 169 struct conf_entry *p, *q; 170 171 parse_args(argc, argv); 172 argc -= optind; 173 argv += optind; 174 175 if (needroot && getuid() && geteuid()) 176 errx(1, "must have root privs"); 177 p = q = get_worklist(argv); 178 179 while (p) { 180 do_entry(p); 181 p = p->next; 182 free_entry(q); 183 q = p; 184 } 185 while (wait(NULL) > 0 || errno == EINTR) 186 ; 187 return (0); 188} 189 190static struct conf_entry * 191init_entry(const char *fname, struct conf_entry *src_entry) 192{ 193 struct conf_entry *tempwork; 194 195 if (verbose > 4) 196 printf("\t--> [creating entry for %s]\n", fname); 197 198 tempwork = malloc(sizeof(struct conf_entry)); 199 if (tempwork == NULL) 200 err(1, "malloc of conf_entry for %s", fname); 201 202 tempwork->log = strdup(fname); 203 if (tempwork->log == NULL) 204 err(1, "strdup for %s", fname); 205 206 if (src_entry != NULL) { 207 tempwork->pid_file = NULL; 208 if (src_entry->pid_file) 209 tempwork->pid_file = strdup(src_entry->pid_file); 210 tempwork->r_reason = NULL; 211 tempwork->firstcreate = 0; 212 tempwork->rotate = 0; 213 tempwork->uid = src_entry->uid; 214 tempwork->gid = src_entry->gid; 215 tempwork->numlogs = src_entry->numlogs; 216 tempwork->size = src_entry->size; 217 tempwork->hours = src_entry->hours; 218 tempwork->trim_at = src_entry->trim_at; 219 tempwork->permissions = src_entry->permissions; 220 tempwork->flags = src_entry->flags; 221 tempwork->sig = src_entry->sig; 222 tempwork->def_cfg = src_entry->def_cfg; 223 } else { 224 /* Initialize as a "do-nothing" entry */ 225 tempwork->pid_file = NULL; 226 tempwork->r_reason = NULL; 227 tempwork->firstcreate = 0; 228 tempwork->rotate = 0; 229 tempwork->uid = (uid_t)-1; 230 tempwork->gid = (gid_t)-1; 231 tempwork->numlogs = 1; 232 tempwork->size = -1; 233 tempwork->hours = -1; 234 tempwork->trim_at = (time_t)0; 235 tempwork->permissions = 0; 236 tempwork->flags = 0; 237 tempwork->sig = SIGHUP; 238 tempwork->def_cfg = 0; 239 } 240 tempwork->next = NULL; 241 242 return (tempwork); 243} 244 245static void 246free_entry(struct conf_entry *ent) 247{ 248 249 if (ent == NULL) 250 return; 251 252 if (ent->log != NULL) { 253 if (verbose > 4) 254 printf("\t--> [freeing entry for %s]\n", ent->log); 255 free(ent->log); 256 ent->log = NULL; 257 } 258 259 if (ent->pid_file != NULL) { 260 free(ent->pid_file); 261 ent->pid_file = NULL; 262 } 263 264 if (ent->r_reason != NULL) { 265 free(ent->r_reason); 266 ent->r_reason = NULL; 267 } 268 269 free(ent); 270} 271 272static void 273free_clist(struct conf_entry **firstent) 274{ 275 struct conf_entry *ent, *nextent; 276 277 if (firstent == NULL) 278 return; /* There is nothing to do. */ 279 280 ent = *firstent; 281 firstent = NULL; 282 283 while (ent) { 284 nextent = ent->next; 285 free_entry(ent); 286 ent = nextent; 287 } 288} 289 290static void 291do_entry(struct conf_entry * ent) 292{ 293#define REASON_MAX 80 294 int size, modtime; 295 char temp_reason[REASON_MAX]; 296 297 if (verbose) { 298 if (ent->flags & CE_COMPACT) 299 printf("%s <%dZ>: ", ent->log, ent->numlogs); 300 else if (ent->flags & CE_BZCOMPACT) 301 printf("%s <%dJ>: ", ent->log, ent->numlogs); 302 else 303 printf("%s <%d>: ", ent->log, ent->numlogs); 304 } 305 size = sizefile(ent->log); 306 modtime = age_old_log(ent->log); 307 ent->rotate = 0; 308 ent->firstcreate = 0; 309 if (size < 0) { 310 /* 311 * If either the C flag or the -C option was specified, 312 * and if we won't be creating the file, then have the 313 * verbose message include a hint as to why the file 314 * will not be created. 315 */ 316 temp_reason[0] = '\0'; 317 if (createlogs > 1) 318 ent->firstcreate = 1; 319 else if ((ent->flags & CE_CREATE) && createlogs) 320 ent->firstcreate = 1; 321 else if (ent->flags & CE_CREATE) 322 strncpy(temp_reason, " (no -C option)", REASON_MAX); 323 else if (createlogs) 324 strncpy(temp_reason, " (no C flag)", REASON_MAX); 325 326 if (ent->firstcreate) { 327 if (verbose) 328 printf("does not exist -> will create.\n"); 329 createlog(ent); 330 } else if (verbose) { 331 printf("does not exist, skipped%s.\n", temp_reason); 332 } 333 } else { 334 if (ent->flags & CE_TRIMAT && !force && !rotatereq) { 335 if (timenow < ent->trim_at 336 || difftime(timenow, ent->trim_at) >= 60 * 60) { 337 if (verbose) 338 printf("--> will trim at %s", 339 ctime(&ent->trim_at)); 340 return; 341 } else if (verbose && ent->hours <= 0) { 342 printf("--> time is up\n"); 343 } 344 } 345 if (verbose && (ent->size > 0)) 346 printf("size (Kb): %d [%d] ", size, ent->size); 347 if (verbose && (ent->hours > 0)) 348 printf(" age (hr): %d [%d] ", modtime, ent->hours); 349 350 /* 351 * Figure out if this logfile needs to be rotated. 352 */ 353 temp_reason[0] = '\0'; 354 if (rotatereq) { 355 ent->rotate = 1; 356 snprintf(temp_reason, REASON_MAX, " due to -R from %s", 357 requestor); 358 } else if (force) { 359 ent->rotate = 1; 360 snprintf(temp_reason, REASON_MAX, " due to -F request"); 361 } else if ((ent->size > 0) && (size >= ent->size)) { 362 ent->rotate = 1; 363 snprintf(temp_reason, REASON_MAX, " due to size>%dK", 364 ent->size); 365 } else if (ent->hours <= 0 && (ent->flags & CE_TRIMAT)) { 366 ent->rotate = 1; 367 } else if ((ent->hours > 0) && ((modtime >= ent->hours) || 368 (modtime < 0))) { 369 ent->rotate = 1; 370 } 371 372 /* 373 * If the file needs to be rotated, then rotate it. 374 */ 375 if (ent->rotate) { 376 if (temp_reason[0] != '\0') 377 ent->r_reason = strdup(temp_reason); 378 if (verbose) 379 printf("--> trimming log....\n"); 380 if (noaction && !verbose) { 381 if (ent->flags & CE_COMPACT) 382 printf("%s <%dZ>: trimming\n", 383 ent->log, ent->numlogs); 384 else if (ent->flags & CE_BZCOMPACT) 385 printf("%s <%dJ>: trimming\n", 386 ent->log, ent->numlogs); 387 else 388 printf("%s <%d>: trimming\n", 389 ent->log, ent->numlogs); 390 } 391 dotrim(ent, ent->log, ent->numlogs, ent->flags); 392 } else { 393 if (verbose) 394 printf("--> skipping\n"); 395 } 396 } 397#undef REASON_MAX 398} 399 400/* Send a signal to the pid specified by pidfile */ 401static int 402send_signal(const struct conf_entry *ent) 403{ 404 pid_t target_pid; 405 int did_notify; 406 FILE *f; 407 long minok, maxok, rval; 408 const char *target_name; 409 char *endp, *linep, line[BUFSIZ]; 410 411 did_notify = 0; 412 f = fopen(ent->pid_file, "r"); 413 if (f == NULL) { 414 warn("can't open pid file: %s", ent->pid_file); 415 return (did_notify); 416 /* NOTREACHED */ 417 } 418 419 if (fgets(line, BUFSIZ, f) == NULL) { 420 /* 421 * XXX - If the pid file is empty, is that really a 422 * problem? Wouldn't that mean that the process 423 * has shut down? In that case there would be no 424 * problem with compressing the rotated log file. 425 */ 426 if (feof(f)) 427 warnx("pid file is empty: %s", ent->pid_file); 428 else 429 warn("can't read from pid file: %s", ent->pid_file); 430 (void) fclose(f); 431 return (did_notify); 432 /* NOTREACHED */ 433 } 434 (void) fclose(f); 435 436 target_name = "daemon"; 437 minok = MIN_PID; 438 maxok = MAX_PID; 439 if (ent->flags & CE_SIGNALGROUP) { 440 /* 441 * If we are expected to signal a process-group when 442 * rotating this logfile, then the value read in should 443 * be the negative of a valid process ID. 444 */ 445 target_name = "process-group"; 446 minok = -MAX_PID; 447 maxok = -MIN_PID; 448 } 449 450 errno = 0; 451 linep = line; 452 while (*linep == ' ') 453 linep++; 454 rval = strtol(linep, &endp, 10); 455 if (*endp != '\0' && !isspacech(*endp)) { 456 warnx("pid file does not start with a valid number: %s", 457 ent->pid_file); 458 rval = 0; 459 } else if (rval < minok || rval > maxok) { 460 warnx("bad value '%ld' for process number in %s", 461 rval, ent->pid_file); 462 if (verbose) 463 warnx("\t(expecting value between %ld and %ld)", 464 minok, maxok); 465 rval = 0; 466 } 467 if (rval == 0) { 468 return (did_notify); 469 /* NOTREACHED */ 470 } 471 472 target_pid = rval; 473 474 if (noaction) { 475 did_notify = 1; 476 printf("\tkill -%d %d\n", ent->sig, (int) target_pid); 477 } else if (kill(target_pid, ent->sig)) { 478 /* 479 * XXX - Iff the error was "no such process", should that 480 * really be an error for us? Perhaps the process 481 * is already gone, in which case there would be no 482 * problem with compressing the rotated log file. 483 */ 484 warn("can't notify %s, pid %d", target_name, 485 (int) target_pid); 486 } else { 487 did_notify = 1; 488 if (verbose) 489 printf("%s pid %d notified\n", target_name, 490 (int) target_pid); 491 } 492 493 return (did_notify); 494} 495 496static void 497parse_args(int argc, char **argv) 498{ 499 int ch; 500 char *p; 501 char debugtime[32]; 502 503 timenow = time(NULL); 504 (void)strncpy(daytime, ctime(&timenow) + 4, 15); 505 daytime[15] = '\0'; 506 507 /* Let's get our hostname */ 508 (void)gethostname(hostname, sizeof(hostname)); 509 510 /* Truncate domain */ 511 if ((p = strchr(hostname, '.')) != NULL) 512 *p = '\0'; 513 514 /* Parse command line options. */ 515 while ((ch = getopt(argc, argv, "a:f:nrsvCD:FR:")) != -1) 516 switch (ch) { 517 case 'a': 518 archtodir++; 519 archdirname = optarg; 520 break; 521 case 'f': 522 conf = optarg; 523 break; 524 case 'n': 525 noaction++; 526 break; 527 case 'r': 528 needroot = 0; 529 break; 530 case 's': 531 nosignal = 1; 532 break; 533 case 'v': 534 verbose++; 535 break; 536 case 'C': 537 /* Useful for things like rc.diskless... */ 538 createlogs++; 539 break; 540 case 'D': 541 /* 542 * Set some debugging option. The specific option 543 * depends on the value of optarg. These options 544 * may come and go without notice or documentation. 545 */ 546 if (parse_doption(optarg)) 547 break; 548 usage(); 549 /* NOTREACHED */ 550 case 'F': 551 force++; 552 break; 553 case 'R': 554 rotatereq++; 555 requestor = strdup(optarg); 556 break; 557 case 'm': /* Used by OpenBSD for "monitor mode" */ 558 default: 559 usage(); 560 /* NOTREACHED */ 561 } 562 563 if (rotatereq) { 564 if (optind == argc) { 565 warnx("At least one filename must be given when -R is specified."); 566 usage(); 567 /* NOTREACHED */ 568 } 569 /* Make sure "requestor" value is safe for a syslog message. */ 570 for (p = requestor; *p != '\0'; p++) { 571 if (!isprintch(*p) && (*p != '\t')) 572 *p = '.'; 573 } 574 } 575 576 if (dbg_timenow) { 577 /* 578 * Note that the 'daytime' variable is not changed. 579 * That is only used in messages that track when a 580 * logfile is rotated, and if a file *is* rotated, 581 * then it will still rotated at the "real now" time. 582 */ 583 timenow = dbg_timenow; 584 strlcpy(debugtime, ctime(&timenow), sizeof(debugtime)); 585 fprintf(stderr, "Debug: Running as if TimeNow is %s", 586 debugtime); 587 } 588 589} 590 591static int 592parse_doption(const char *doption) 593{ 594 const char TN[] = "TN="; 595 596 if (strncmp(doption, TN, sizeof(TN) - 1) == 0) { 597 /* 598 * The "TimeNow" debugging option. This probably will 599 * be off by an hour when crossing a timezone change. 600 */ 601 dbg_timenow = parse8601(doption + sizeof(TN) - 1); 602 if (dbg_timenow == (time_t)-1) { 603 warnx("Malformed time given on -D %s", doption); 604 return (0); /* failure */ 605 } 606 if (dbg_timenow == (time_t)-2) { 607 warnx("Non-existent time specified on -D %s", doption); 608 return (0); /* failure */ 609 } 610 return (1); /* successfully parsed */ 611 612 } 613 614 warnx("Unknown -D (debug) option: %s", doption); 615 return (0); /* failure */ 616} 617 618static void 619usage(void) 620{ 621 622 fprintf(stderr, 623 "usage: newsyslog [-CFnrsv] [-a directory] [-f config-file]\n" 624 " [ [-R requestor] filename ... ]\n"); 625 exit(1); 626} 627 628/* 629 * Parse a configuration file and return a linked list of all the logs 630 * which should be processed. 631 */ 632static struct conf_entry * 633get_worklist(char **files) 634{ 635 FILE *f; 636 const char *fname; 637 char **given; 638 struct conf_entry *defconf, *dupent, *ent, *firstnew; 639 struct conf_entry *globlist, *lastnew, *worklist; 640 int gmatch, fnres; 641 642 defconf = globlist = worklist = NULL; 643 644 fname = conf; 645 if (fname == NULL) 646 fname = _PATH_CONF; 647 648 if (strcmp(fname, "-") != 0) 649 f = fopen(fname, "r"); 650 else { 651 f = stdin; 652 fname = "<stdin>"; 653 } 654 if (!f) 655 err(1, "%s", conf); 656 657 parse_file(f, fname, &worklist, &globlist, &defconf); 658 (void) fclose(f); 659 660 /* 661 * All config-file information has been read in and turned into 662 * a worklist and a globlist. If there were no specific files 663 * given on the run command, then the only thing left to do is to 664 * call a routine which finds all files matched by the globlist 665 * and adds them to the worklist. Then return the worklist. 666 */ 667 if (*files == NULL) { 668 expand_globs(&worklist, &globlist); 669 free_clist(&globlist); 670 if (defconf != NULL) 671 free_entry(defconf); 672 return (worklist); 673 /* NOTREACHED */ 674 } 675 676 /* 677 * If newsyslog was given a specific list of files to process, 678 * it may be that some of those files were not listed in any 679 * config file. Those unlisted files should get the default 680 * rotation action. First, create the default-rotation action 681 * if none was found in a system config file. 682 */ 683 if (defconf == NULL) { 684 defconf = init_entry(DEFAULT_MARKER, NULL); 685 defconf->numlogs = 3; 686 defconf->size = 50; 687 defconf->permissions = S_IRUSR|S_IWUSR; 688 } 689 690 /* 691 * If newsyslog was run with a list of specific filenames, 692 * then create a new worklist which has only those files in 693 * it, picking up the rotation-rules for those files from 694 * the original worklist. 695 * 696 * XXX - Note that this will copy multiple rules for a single 697 * logfile, if multiple entries are an exact match for 698 * that file. That matches the historic behavior, but do 699 * we want to continue to allow it? If so, it should 700 * probably be handled more intelligently. 701 */ 702 firstnew = lastnew = NULL; 703 for (given = files; *given; ++given) { 704 /* 705 * First try to find exact-matches for this given file. 706 */ 707 gmatch = 0; 708 for (ent = worklist; ent; ent = ent->next) { 709 if (strcmp(ent->log, *given) == 0) { 710 gmatch++; 711 dupent = init_entry(*given, ent); 712 if (!firstnew) 713 firstnew = dupent; 714 else 715 lastnew->next = dupent; 716 lastnew = dupent; 717 } 718 } 719 if (gmatch) { 720 if (verbose > 2) 721 printf("\t+ Matched entry %s\n", *given); 722 continue; 723 } 724 725 /* 726 * There was no exact-match for this given file, so look 727 * for a "glob" entry which does match. 728 */ 729 gmatch = 0; 730 if (verbose > 2 && globlist != NULL) 731 printf("\t+ Checking globs for %s\n", *given); 732 for (ent = globlist; ent; ent = ent->next) { 733 fnres = fnmatch(ent->log, *given, FNM_PATHNAME); 734 if (verbose > 2) 735 printf("\t+ = %d for pattern %s\n", fnres, 736 ent->log); 737 if (fnres == 0) { 738 gmatch++; 739 dupent = init_entry(*given, ent); 740 if (!firstnew) 741 firstnew = dupent; 742 else 743 lastnew->next = dupent; 744 lastnew = dupent; 745 /* This new entry is not a glob! */ 746 dupent->flags &= ~CE_GLOB; 747 /* Only allow a match to one glob-entry */ 748 break; 749 } 750 } 751 if (gmatch) { 752 if (verbose > 2) 753 printf("\t+ Matched %s via %s\n", *given, 754 ent->log); 755 continue; 756 } 757 758 /* 759 * This given file was not found in any config file, so 760 * add a worklist item based on the default entry. 761 */ 762 if (verbose > 2) 763 printf("\t+ No entry matched %s (will use %s)\n", 764 *given, DEFAULT_MARKER); 765 dupent = init_entry(*given, defconf); 766 if (!firstnew) 767 firstnew = dupent; 768 else 769 lastnew->next = dupent; 770 /* Mark that it was *not* found in a config file */ 771 dupent->def_cfg = 1; 772 lastnew = dupent; 773 } 774 775 /* 776 * Free all the entries in the original work list, the list of 777 * glob entries, and the default entry. 778 */ 779 free_clist(&worklist); 780 free_clist(&globlist); 781 free_entry(defconf); 782 783 /* And finally, return a worklist which matches the given files. */ 784 return (firstnew); 785} 786 787/* 788 * Expand the list of entries with filename patterns, and add all files 789 * which match those glob-entries onto the worklist. 790 */ 791static void 792expand_globs(struct conf_entry **work_p, struct conf_entry **glob_p) 793{ 794 int gmatch, gres, i; 795 char *mfname; 796 struct conf_entry *dupent, *ent, *firstmatch, *globent; 797 struct conf_entry *lastmatch; 798 glob_t pglob; 799 struct stat st_fm; 800 801 if ((glob_p == NULL) || (*glob_p == NULL)) 802 return; /* There is nothing to do. */ 803 804 /* 805 * The worklist contains all fully-specified (non-GLOB) names. 806 * 807 * Now expand the list of filename-pattern (GLOB) entries into 808 * a second list, which (by definition) will only match files 809 * that already exist. Do not add a glob-related entry for any 810 * file which already exists in the fully-specified list. 811 */ 812 firstmatch = lastmatch = NULL; 813 for (globent = *glob_p; globent; globent = globent->next) { 814 815 gres = glob(globent->log, GLOB_NOCHECK, NULL, &pglob); 816 if (gres != 0) { 817 warn("cannot expand pattern (%d): %s", gres, 818 globent->log); 819 continue; 820 } 821 822 if (verbose > 2) 823 printf("\t+ Expanding pattern %s\n", globent->log); 824 for (i = 0; i < pglob.gl_matchc; i++) { 825 mfname = pglob.gl_pathv[i]; 826 827 /* See if this file already has a specific entry. */ 828 gmatch = 0; 829 for (ent = *work_p; ent; ent = ent->next) { 830 if (strcmp(mfname, ent->log) == 0) { 831 gmatch++; 832 break; 833 } 834 } 835 if (gmatch) 836 continue; 837 838 /* Make sure the named matched is a file. */ 839 gres = lstat(mfname, &st_fm); 840 if (gres != 0) { 841 /* Error on a file that glob() matched?!? */ 842 warn("Skipping %s - lstat() error", mfname); 843 continue; 844 } 845 if (!S_ISREG(st_fm.st_mode)) { 846 /* We only rotate files! */ 847 if (verbose > 2) 848 printf("\t+ . skipping %s (!file)\n", 849 mfname); 850 continue; 851 } 852 853 if (verbose > 2) 854 printf("\t+ . add file %s\n", mfname); 855 dupent = init_entry(mfname, globent); 856 if (!firstmatch) 857 firstmatch = dupent; 858 else 859 lastmatch->next = dupent; 860 lastmatch = dupent; 861 /* This new entry is not a glob! */ 862 dupent->flags &= ~CE_GLOB; 863 } 864 globfree(&pglob); 865 if (verbose > 2) 866 printf("\t+ Done with pattern %s\n", globent->log); 867 } 868 869 /* Add the list of matched files to the end of the worklist. */ 870 if (!*work_p) 871 *work_p = firstmatch; 872 else { 873 ent = *work_p; 874 while (ent->next) 875 ent = ent->next; 876 ent->next = firstmatch; 877 } 878 879} 880 881/* 882 * Parse a configuration file and update a linked list of all the logs to 883 * process. 884 */ 885static void 886parse_file(FILE *cf, const char *cfname, struct conf_entry **work_p, 887 struct conf_entry **glob_p, struct conf_entry **defconf_p) 888{ 889 char line[BUFSIZ], *parse, *q; 890 char *cp, *errline, *group; 891 struct conf_entry *lastglob, *lastwork, *working; 892 struct passwd *pwd; 893 struct group *grp; 894 int eol, special; 895 896 /* 897 * XXX - for now, assume that only one config file will be read, 898 * ie, this routine is only called one time. 899 */ 900 lastglob = lastwork = NULL; 901 902 while (fgets(line, BUFSIZ, cf)) { 903 if ((line[0] == '\n') || (line[0] == '#') || 904 (strlen(line) == 0)) 905 continue; 906 errline = strdup(line); 907 for (cp = line + 1; *cp != '\0'; cp++) { 908 if (*cp != '#') 909 continue; 910 if (*(cp - 1) == '\\') { 911 strcpy(cp - 1, cp); 912 cp--; 913 continue; 914 } 915 *cp = '\0'; 916 break; 917 } 918 919 q = parse = missing_field(sob(line), errline); 920 parse = son(line); 921 if (!*parse) 922 errx(1, "malformed line (missing fields):\n%s", 923 errline); 924 *parse = '\0'; 925 926 special = 0; 927 working = init_entry(q, NULL); 928 if (strcasecmp(DEFAULT_MARKER, q) == 0) { 929 special = 1; 930 if (defconf_p == NULL) { 931 warnx("Ignoring entry for %s in %s!", q, 932 cfname); 933 free_entry(working); 934 continue; 935 } else if (*defconf_p != NULL) { 936 warnx("Ignoring duplicate entry for %s!", q); 937 free_entry(working); 938 continue; 939 } 940 *defconf_p = working; 941 } 942 943 q = parse = missing_field(sob(++parse), errline); 944 parse = son(parse); 945 if (!*parse) 946 errx(1, "malformed line (missing fields):\n%s", 947 errline); 948 *parse = '\0'; 949 if ((group = strchr(q, ':')) != NULL || 950 (group = strrchr(q, '.')) != NULL) { 951 *group++ = '\0'; 952 if (*q) { 953 if (!(isnumberstr(q))) { 954 if ((pwd = getpwnam(q)) == NULL) 955 errx(1, 956 "error in config file; unknown user:\n%s", 957 errline); 958 working->uid = pwd->pw_uid; 959 } else 960 working->uid = atoi(q); 961 } else 962 working->uid = (uid_t)-1; 963 964 q = group; 965 if (*q) { 966 if (!(isnumberstr(q))) { 967 if ((grp = getgrnam(q)) == NULL) 968 errx(1, 969 "error in config file; unknown group:\n%s", 970 errline); 971 working->gid = grp->gr_gid; 972 } else 973 working->gid = atoi(q); 974 } else 975 working->gid = (gid_t)-1; 976 977 q = parse = missing_field(sob(++parse), errline); 978 parse = son(parse); 979 if (!*parse) 980 errx(1, "malformed line (missing fields):\n%s", 981 errline); 982 *parse = '\0'; 983 } else { 984 working->uid = (uid_t)-1; 985 working->gid = (gid_t)-1; 986 } 987 988 if (!sscanf(q, "%o", &working->permissions)) 989 errx(1, "error in config file; bad permissions:\n%s", 990 errline); 991 992 q = parse = missing_field(sob(++parse), errline); 993 parse = son(parse); 994 if (!*parse) 995 errx(1, "malformed line (missing fields):\n%s", 996 errline); 997 *parse = '\0'; 998 if (!sscanf(q, "%d", &working->numlogs) || working->numlogs < 0) 999 errx(1, "error in config file; bad value for count of logs to save:\n%s", 1000 errline); 1001 1002 q = parse = missing_field(sob(++parse), errline); 1003 parse = son(parse); 1004 if (!*parse) 1005 errx(1, "malformed line (missing fields):\n%s", 1006 errline); 1007 *parse = '\0'; 1008 if (isdigitch(*q)) 1009 working->size = atoi(q); 1010 else if (strcmp(q,"*") == 0) 1011 working->size = -1; 1012 else { 1013 warnx("Invalid value of '%s' for 'size' in line:\n%s", 1014 q, errline); 1015 working->size = -1; 1016 } 1017 1018 working->flags = 0; 1019 q = parse = missing_field(sob(++parse), errline); 1020 parse = son(parse); 1021 eol = !*parse; 1022 *parse = '\0'; 1023 { 1024 char *ep; 1025 u_long ul; 1026 1027 ul = strtoul(q, &ep, 10); 1028 if (ep == q) 1029 working->hours = 0; 1030 else if (*ep == '*') 1031 working->hours = -1; 1032 else if (ul > INT_MAX) 1033 errx(1, "interval is too large:\n%s", errline); 1034 else 1035 working->hours = ul; 1036 1037 if (*ep != '\0' && *ep != '@' && *ep != '*' && 1038 *ep != '$') 1039 errx(1, "malformed interval/at:\n%s", errline); 1040 if (*ep == '@') { 1041 working->trim_at = parse8601(ep + 1); 1042 working->flags |= CE_TRIMAT; 1043 } else if (*ep == '$') { 1044 working->trim_at = parseDWM(ep + 1); 1045 working->flags |= CE_TRIMAT; 1046 } 1047 if (working->flags & CE_TRIMAT) { 1048 if (working->trim_at == (time_t)-1) 1049 errx(1, "malformed at:\n%s", errline); 1050 if (working->trim_at == (time_t)-2) 1051 errx(1, "nonexistent time:\n%s", 1052 errline); 1053 } 1054 } 1055 1056 if (eol) 1057 q = NULL; 1058 else { 1059 q = parse = sob(++parse); /* Optional field */ 1060 parse = son(parse); 1061 if (!*parse) 1062 eol = 1; 1063 *parse = '\0'; 1064 } 1065 1066 for (; q && *q && !isspacech(*q); q++) { 1067 switch (tolowerch(*q)) { 1068 case 'b': 1069 working->flags |= CE_BINARY; 1070 break; 1071 case 'c': 1072 /* 1073 * XXX - Ick! Ugly! Remove ASAP! 1074 * We want `c' and `C' for "create". But we 1075 * will temporarily treat `c' as `g', because 1076 * FreeBSD releases <= 4.8 have a typo of 1077 * checking ('G' || 'c') for CE_GLOB. 1078 */ 1079 if (*q == 'c') { 1080 warnx("Assuming 'g' for 'c' in flags for line:\n%s", 1081 errline); 1082 warnx("The 'c' flag will eventually mean 'CREATE'"); 1083 working->flags |= CE_GLOB; 1084 break; 1085 } 1086 working->flags |= CE_CREATE; 1087 break; 1088 case 'g': 1089 working->flags |= CE_GLOB; 1090 break; 1091 case 'j': 1092 working->flags |= CE_BZCOMPACT; 1093 break; 1094 case 'n': 1095 working->flags |= CE_NOSIGNAL; 1096 break; 1097 case 'u': 1098 working->flags |= CE_SIGNALGROUP; 1099 break; 1100 case 'w': 1101 working->flags |= CE_COMPACTWAIT; 1102 break; 1103 case 'z': 1104 working->flags |= CE_COMPACT; 1105 break; 1106 case '-': 1107 break; 1108 case 'f': /* Used by OpenBSD for "CE_FOLLOW" */ 1109 case 'm': /* Used by OpenBSD for "CE_MONITOR" */ 1110 case 'p': /* Used by NetBSD for "CE_PLAIN0" */ 1111 default: 1112 errx(1, "illegal flag in config file -- %c", 1113 *q); 1114 } 1115 } 1116 1117 if (eol) 1118 q = NULL; 1119 else { 1120 q = parse = sob(++parse); /* Optional field */ 1121 parse = son(parse); 1122 if (!*parse) 1123 eol = 1; 1124 *parse = '\0'; 1125 } 1126 1127 working->pid_file = NULL; 1128 if (q && *q) { 1129 if (*q == '/') 1130 working->pid_file = strdup(q); 1131 else if (isdigit(*q)) 1132 goto got_sig; 1133 else 1134 errx(1, 1135 "illegal pid file or signal number in config file:\n%s", 1136 errline); 1137 } 1138 if (eol) 1139 q = NULL; 1140 else { 1141 q = parse = sob(++parse); /* Optional field */ 1142 *(parse = son(parse)) = '\0'; 1143 } 1144 1145 working->sig = SIGHUP; 1146 if (q && *q) { 1147 if (isdigit(*q)) { 1148 got_sig: 1149 working->sig = atoi(q); 1150 } else { 1151 err_sig: 1152 errx(1, 1153 "illegal signal number in config file:\n%s", 1154 errline); 1155 } 1156 if (working->sig < 1 || working->sig >= NSIG) 1157 goto err_sig; 1158 } 1159 1160 /* 1161 * Finish figuring out what pid-file to use (if any) in 1162 * later processing if this logfile needs to be rotated. 1163 */ 1164 if ((working->flags & CE_NOSIGNAL) == CE_NOSIGNAL) { 1165 /* 1166 * This config-entry specified 'n' for nosignal, 1167 * see if it also specified an explicit pid_file. 1168 * This would be a pretty pointless combination. 1169 */ 1170 if (working->pid_file != NULL) { 1171 warnx("Ignoring '%s' because flag 'n' was specified in line:\n%s", 1172 working->pid_file, errline); 1173 free(working->pid_file); 1174 working->pid_file = NULL; 1175 } 1176 } else if (working->pid_file == NULL) { 1177 /* 1178 * This entry did not specify the 'n' flag, which 1179 * means it should signal syslogd unless it had 1180 * specified some other pid-file (and obviously the 1181 * syslog pid-file will not be for a process-group). 1182 * Also, we should only try to notify syslog if we 1183 * are root. 1184 */ 1185 if (working->flags & CE_SIGNALGROUP) { 1186 warnx("Ignoring flag 'U' in line:\n%s", 1187 errline); 1188 working->flags &= ~CE_SIGNALGROUP; 1189 } 1190 if (needroot) 1191 working->pid_file = strdup(_PATH_SYSLOGPID); 1192 } 1193 1194 /* 1195 * Add this entry to the appropriate list of entries, unless 1196 * it was some kind of special entry (eg: <default>). 1197 */ 1198 if (special) { 1199 ; /* Do not add to any list */ 1200 } else if (working->flags & CE_GLOB) { 1201 if (!*glob_p) 1202 *glob_p = working; 1203 else 1204 lastglob->next = working; 1205 lastglob = working; 1206 } else { 1207 if (!*work_p) 1208 *work_p = working; 1209 else 1210 lastwork->next = working; 1211 lastwork = working; 1212 } 1213 1214 free(errline); 1215 errline = NULL; 1216 } 1217} 1218 1219static char * 1220missing_field(char *p, char *errline) 1221{ 1222 1223 if (!p || !*p) 1224 errx(1, "missing field in config file:\n%s", errline); 1225 return (p); 1226} 1227 1228static void 1229dotrim(const struct conf_entry *ent, char *log, int numdays, int flags) 1230{ 1231 char dirpart[MAXPATHLEN], namepart[MAXPATHLEN]; 1232 char file1[MAXPATHLEN], file2[MAXPATHLEN]; 1233 char zfile1[MAXPATHLEN], zfile2[MAXPATHLEN]; 1234 char jfile1[MAXPATHLEN]; 1235 char tfile[MAXPATHLEN]; 1236 int notified, need_notification, fd, _numdays; 1237 struct stat st; 1238 1239 if (archtodir) { 1240 char *p; 1241 1242 /* build complete name of archive directory into dirpart */ 1243 if (*archdirname == '/') { /* absolute */ 1244 strlcpy(dirpart, archdirname, sizeof(dirpart)); 1245 } else { /* relative */ 1246 /* get directory part of logfile */ 1247 strlcpy(dirpart, log, sizeof(dirpart)); 1248 if ((p = rindex(dirpart, '/')) == NULL) 1249 dirpart[0] = '\0'; 1250 else 1251 *(p + 1) = '\0'; 1252 strlcat(dirpart, archdirname, sizeof(dirpart)); 1253 } 1254 1255 /* check if archive directory exists, if not, create it */ 1256 if (lstat(dirpart, &st)) 1257 createdir(ent, dirpart); 1258 1259 /* get filename part of logfile */ 1260 if ((p = rindex(log, '/')) == NULL) 1261 strlcpy(namepart, log, sizeof(namepart)); 1262 else 1263 strlcpy(namepart, p + 1, sizeof(namepart)); 1264 1265 /* name of oldest log */ 1266 (void) snprintf(file1, sizeof(file1), "%s/%s.%d", dirpart, 1267 namepart, numdays); 1268 (void) snprintf(zfile1, sizeof(zfile1), "%s%s", file1, 1269 COMPRESS_POSTFIX); 1270 snprintf(jfile1, sizeof(jfile1), "%s%s", file1, 1271 BZCOMPRESS_POSTFIX); 1272 } else { 1273 /* name of oldest log */ 1274 (void) snprintf(file1, sizeof(file1), "%s.%d", log, numdays); 1275 (void) snprintf(zfile1, sizeof(zfile1), "%s%s", file1, 1276 COMPRESS_POSTFIX); 1277 snprintf(jfile1, sizeof(jfile1), "%s%s", file1, 1278 BZCOMPRESS_POSTFIX); 1279 } 1280 1281 if (noaction) { 1282 printf("\trm -f %s\n", file1); 1283 printf("\trm -f %s\n", zfile1); 1284 printf("\trm -f %s\n", jfile1); 1285 } else { 1286 (void) unlink(file1); 1287 (void) unlink(zfile1); 1288 (void) unlink(jfile1); 1289 } 1290 1291 /* Move down log files */ 1292 _numdays = numdays; /* preserve */ 1293 while (numdays--) { 1294 1295 (void) strlcpy(file2, file1, sizeof(file2)); 1296 1297 if (archtodir) 1298 (void) snprintf(file1, sizeof(file1), "%s/%s.%d", 1299 dirpart, namepart, numdays); 1300 else 1301 (void) snprintf(file1, sizeof(file1), "%s.%d", log, 1302 numdays); 1303 1304 (void) strlcpy(zfile1, file1, sizeof(zfile1)); 1305 (void) strlcpy(zfile2, file2, sizeof(zfile2)); 1306 if (lstat(file1, &st)) { 1307 (void) strlcat(zfile1, COMPRESS_POSTFIX, 1308 sizeof(zfile1)); 1309 (void) strlcat(zfile2, COMPRESS_POSTFIX, 1310 sizeof(zfile2)); 1311 if (lstat(zfile1, &st)) { 1312 strlcpy(zfile1, file1, sizeof(zfile1)); 1313 strlcpy(zfile2, file2, sizeof(zfile2)); 1314 strlcat(zfile1, BZCOMPRESS_POSTFIX, 1315 sizeof(zfile1)); 1316 strlcat(zfile2, BZCOMPRESS_POSTFIX, 1317 sizeof(zfile2)); 1318 if (lstat(zfile1, &st)) 1319 continue; 1320 } 1321 } 1322 if (noaction) { 1323 printf("\tmv %s %s\n", zfile1, zfile2); 1324 printf("\tchmod %o %s\n", ent->permissions, zfile2); 1325 if (ent->uid != (uid_t)-1 || ent->gid != (gid_t)-1) 1326 printf("\tchown %u:%u %s\n", 1327 ent->uid, ent->gid, zfile2); 1328 } else { 1329 (void) rename(zfile1, zfile2); 1330 if (chmod(zfile2, ent->permissions)) 1331 warn("can't chmod %s", file2); 1332 if (ent->uid != (uid_t)-1 || ent->gid != (gid_t)-1) 1333 if (chown(zfile2, ent->uid, ent->gid)) 1334 warn("can't chown %s", zfile2); 1335 } 1336 } 1337 if (!noaction && !(flags & CE_BINARY)) { 1338 /* Report the trimming to the old log */ 1339 (void) log_trim(log, ent); 1340 } 1341 1342 if (!_numdays) { 1343 if (noaction) 1344 printf("\trm %s\n", log); 1345 else 1346 (void) unlink(log); 1347 } else { 1348 if (noaction) 1349 printf("\tmv %s to %s\n", log, file1); 1350 else { 1351 if (archtodir) 1352 movefile(log, file1, ent->permissions, ent->uid, 1353 ent->gid); 1354 else 1355 (void) rename(log, file1); 1356 } 1357 } 1358 1359 /* Now move the new log file into place */ 1360 /* XXX - We should replace the above 'rename' with 'link(log, file1)' 1361 * then replace the following with 'createfile(ent)' */ 1362 strlcpy(tfile, log, sizeof(tfile)); 1363 strlcat(tfile, ".XXXXXX", sizeof(tfile)); 1364 if (noaction) { 1365 printf("Start new log...\n"); 1366 printf("\tmktemp %s\n", tfile); 1367 } else { 1368 mkstemp(tfile); 1369 fd = creat(tfile, ent->permissions); 1370 if (fd < 0) 1371 err(1, "can't start new log"); 1372 if (ent->uid != (uid_t)-1 || ent->gid != (gid_t)-1) 1373 if (fchown(fd, ent->uid, ent->gid)) 1374 err(1, "can't chown new log file"); 1375 (void) close(fd); 1376 if (!(flags & CE_BINARY)) { 1377 /* Add status message to new log file */ 1378 if (log_trim(tfile, ent)) 1379 err(1, "can't add status message to log"); 1380 } 1381 } 1382 if (noaction) { 1383 printf("\tchmod %o %s\n", ent->permissions, tfile); 1384 printf("\tmv %s %s\n", tfile, log); 1385 } else { 1386 (void) chmod(tfile, ent->permissions); 1387 if (rename(tfile, log) < 0) { 1388 err(1, "can't start new log"); 1389 (void) unlink(tfile); 1390 } 1391 } 1392 1393 /* 1394 * Find out if there is a process to signal. If nosignal (-s) was 1395 * specified, then do not signal any process. Note that nosignal 1396 * will trigger a warning message if the rotated logfile needs to 1397 * be compressed, *unless* -R was specified. This is because there 1398 * presumably still are process(es) writing to the old logfile, but 1399 * we assume that a -sR request comes from a process which writes 1400 * to the logfile, and as such, that process has already made sure 1401 * that the logfile is not presently in use. 1402 */ 1403 need_notification = notified = 0; 1404 if (ent->pid_file != NULL) { 1405 need_notification = 1; 1406 if (!nosignal) 1407 notified = send_signal(ent); /* the normal case! */ 1408 else if (rotatereq) 1409 need_notification = 0; 1410 } 1411 1412 if ((flags & CE_COMPACT) || (flags & CE_BZCOMPACT)) { 1413 if (need_notification && !notified) 1414 warnx( 1415 "log %s.0 not compressed because daemon(s) not notified", 1416 log); 1417 else if (noaction) 1418 if (flags & CE_COMPACT) 1419 printf("\tgzip %s.0\n", log); 1420 else 1421 printf("\tbzip2 %s.0\n", log); 1422 else { 1423 if (notified) { 1424 if (verbose) 1425 printf("small pause to allow daemon(s) to close log\n"); 1426 sleep(10); 1427 } 1428 if (archtodir) { 1429 (void) snprintf(file1, sizeof(file1), "%s/%s", 1430 dirpart, namepart); 1431 if (flags & CE_COMPACT) 1432 compress_log(file1, 1433 flags & CE_COMPACTWAIT); 1434 else if (flags & CE_BZCOMPACT) 1435 bzcompress_log(file1, 1436 flags & CE_COMPACTWAIT); 1437 } else { 1438 if (flags & CE_COMPACT) 1439 compress_log(log, 1440 flags & CE_COMPACTWAIT); 1441 else if (flags & CE_BZCOMPACT) 1442 bzcompress_log(log, 1443 flags & CE_COMPACTWAIT); 1444 } 1445 } 1446 } 1447} 1448 1449/* Log the fact that the logs were turned over */ 1450static int 1451log_trim(const char *logname, const struct conf_entry *log_ent) 1452{ 1453 FILE *f; 1454 const char *xtra; 1455 1456 if ((f = fopen(logname, "a")) == NULL) 1457 return (-1); 1458 xtra = ""; 1459 if (log_ent->def_cfg) 1460 xtra = " using <default> rule"; 1461 if (log_ent->firstcreate) 1462 fprintf(f, "%s %s newsyslog[%d]: logfile first created%s\n", 1463 daytime, hostname, (int) getpid(), xtra); 1464 else if (log_ent->r_reason != NULL) 1465 fprintf(f, "%s %s newsyslog[%d]: logfile turned over%s%s\n", 1466 daytime, hostname, (int) getpid(), log_ent->r_reason, xtra); 1467 else 1468 fprintf(f, "%s %s newsyslog[%d]: logfile turned over%s\n", 1469 daytime, hostname, (int) getpid(), xtra); 1470 if (fclose(f) == EOF) 1471 err(1, "log_trim: fclose:"); 1472 return (0); 1473} 1474 1475/* Fork of gzip to compress the old log file */ 1476static void 1477compress_log(char *logname, int dowait) 1478{ 1479 pid_t pid; 1480 char tmp[MAXPATHLEN]; 1481 1482 while (dowait && (wait(NULL) > 0 || errno == EINTR)) 1483 ; 1484 (void) snprintf(tmp, sizeof(tmp), "%s.0", logname); 1485 pid = fork(); 1486 if (pid < 0) 1487 err(1, "gzip fork"); 1488 else if (!pid) { 1489 (void) execl(_PATH_GZIP, _PATH_GZIP, "-f", tmp, (char *)0); 1490 err(1, _PATH_GZIP); 1491 } 1492} 1493 1494/* Fork of bzip2 to compress the old log file */ 1495static void 1496bzcompress_log(char *logname, int dowait) 1497{ 1498 pid_t pid; 1499 char tmp[MAXPATHLEN]; 1500 1501 while (dowait && (wait(NULL) > 0 || errno == EINTR)) 1502 ; 1503 snprintf(tmp, sizeof(tmp), "%s.0", logname); 1504 pid = fork(); 1505 if (pid < 0) 1506 err(1, "bzip2 fork"); 1507 else if (!pid) { 1508 execl(_PATH_BZIP2, _PATH_BZIP2, "-f", tmp, (char *)0); 1509 err(1, _PATH_BZIP2); 1510 } 1511} 1512 1513/* Return size in kilobytes of a file */ 1514static int 1515sizefile(char *file) 1516{ 1517 struct stat sb; 1518 1519 if (stat(file, &sb) < 0) 1520 return (-1); 1521 return (kbytes(dbtob(sb.st_blocks))); 1522} 1523 1524/* Return the age of old log file (file.0) */ 1525static int 1526age_old_log(char *file) 1527{ 1528 struct stat sb; 1529 char *endp; 1530 char tmp[MAXPATHLEN + sizeof(".0") + sizeof(COMPRESS_POSTFIX) + 1531 sizeof(BZCOMPRESS_POSTFIX) + 1]; 1532 1533 if (archtodir) { 1534 char *p; 1535 1536 /* build name of archive directory into tmp */ 1537 if (*archdirname == '/') { /* absolute */ 1538 strlcpy(tmp, archdirname, sizeof(tmp)); 1539 } else { /* relative */ 1540 /* get directory part of logfile */ 1541 strlcpy(tmp, file, sizeof(tmp)); 1542 if ((p = rindex(tmp, '/')) == NULL) 1543 tmp[0] = '\0'; 1544 else 1545 *(p + 1) = '\0'; 1546 strlcat(tmp, archdirname, sizeof(tmp)); 1547 } 1548 1549 strlcat(tmp, "/", sizeof(tmp)); 1550 1551 /* get filename part of logfile */ 1552 if ((p = rindex(file, '/')) == NULL) 1553 strlcat(tmp, file, sizeof(tmp)); 1554 else 1555 strlcat(tmp, p + 1, sizeof(tmp)); 1556 } else { 1557 (void) strlcpy(tmp, file, sizeof(tmp)); 1558 } 1559 1560 strlcat(tmp, ".0", sizeof(tmp)); 1561 if (stat(tmp, &sb) < 0) { 1562 /* 1563 * A plain '.0' file does not exist. Try again, first 1564 * with the added suffix of '.gz', then with an added 1565 * suffix of '.bz2' instead of '.gz'. 1566 */ 1567 endp = strchr(tmp, '\0'); 1568 strlcat(tmp, COMPRESS_POSTFIX, sizeof(tmp)); 1569 if (stat(tmp, &sb) < 0) { 1570 *endp = '\0'; /* Remove .gz */ 1571 strlcat(tmp, BZCOMPRESS_POSTFIX, sizeof(tmp)); 1572 if (stat(tmp, &sb) < 0) 1573 return (-1); 1574 } 1575 } 1576 return ((int)(timenow - sb.st_mtime + 1800) / 3600); 1577} 1578 1579/* Skip Over Blanks */ 1580static char * 1581sob(char *p) 1582{ 1583 while (p && *p && isspace(*p)) 1584 p++; 1585 return (p); 1586} 1587 1588/* Skip Over Non-Blanks */ 1589static char * 1590son(char *p) 1591{ 1592 while (p && *p && !isspace(*p)) 1593 p++; 1594 return (p); 1595} 1596 1597/* Check if string is actually a number */ 1598static int 1599isnumberstr(const char *string) 1600{ 1601 while (*string) { 1602 if (!isdigitch(*string++)) 1603 return (0); 1604 } 1605 return (1); 1606} 1607 1608/* physically move file */ 1609static void 1610movefile(char *from, char *to, int perm, uid_t owner_uid, gid_t group_gid) 1611{ 1612 FILE *src, *dst; 1613 int c; 1614 1615 if ((src = fopen(from, "r")) == NULL) 1616 err(1, "can't fopen %s for reading", from); 1617 if ((dst = fopen(to, "w")) == NULL) 1618 err(1, "can't fopen %s for writing", to); 1619 if (owner_uid != (uid_t)-1 || group_gid != (gid_t)-1) { 1620 if (fchown(fileno(dst), owner_uid, group_gid)) 1621 err(1, "can't fchown %s", to); 1622 } 1623 if (fchmod(fileno(dst), perm)) 1624 err(1, "can't fchmod %s", to); 1625 1626 while ((c = getc(src)) != EOF) { 1627 if ((putc(c, dst)) == EOF) 1628 err(1, "error writing to %s", to); 1629 } 1630 1631 if (ferror(src)) 1632 err(1, "error reading from %s", from); 1633 if ((fclose(src)) != 0) 1634 err(1, "can't fclose %s", to); 1635 if ((fclose(dst)) != 0) 1636 err(1, "can't fclose %s", from); 1637 if ((unlink(from)) != 0) 1638 err(1, "can't unlink %s", from); 1639} 1640 1641/* create one or more directory components of a path */ 1642static void 1643createdir(const struct conf_entry *ent, char *dirpart) 1644{ 1645 int res; 1646 char *s, *d; 1647 char mkdirpath[MAXPATHLEN]; 1648 struct stat st; 1649 1650 s = dirpart; 1651 d = mkdirpath; 1652 1653 for (;;) { 1654 *d++ = *s++; 1655 if (*s != '/' && *s != '\0') 1656 continue; 1657 *d = '\0'; 1658 res = lstat(mkdirpath, &st); 1659 if (res != 0) { 1660 if (noaction) { 1661 printf("\tmkdir %s\n", mkdirpath); 1662 } else { 1663 res = mkdir(mkdirpath, 0755); 1664 if (res != 0) 1665 err(1, "Error on mkdir(\"%s\") for -a", 1666 mkdirpath); 1667 } 1668 } 1669 if (*s == '\0') 1670 break; 1671 } 1672 if (verbose) { 1673 if (ent->firstcreate) 1674 printf("Created directory '%s' for new %s\n", 1675 dirpart, ent->log); 1676 else 1677 printf("Created directory '%s' for -a\n", dirpart); 1678 } 1679} 1680 1681/* 1682 * Create a new log file, destroying any currently-existing version 1683 * of the log file in the process. If the caller wants a backup copy 1684 * of the file to exist, they should call 'link(logfile,logbackup)' 1685 * before calling this routine. 1686 */ 1687void 1688createlog(const struct conf_entry *ent) 1689{ 1690 int fd, failed; 1691 struct stat st; 1692 char *realfile, *slash, tempfile[MAXPATHLEN]; 1693 1694 fd = -1; 1695 realfile = ent->log; 1696 1697 /* 1698 * If this log file is being created for the first time (-C option), 1699 * then it may also be true that the parent directory does not exist 1700 * yet. Check, and create that directory if it is missing. 1701 */ 1702 if (ent->firstcreate) { 1703 strlcpy(tempfile, realfile, sizeof(tempfile)); 1704 slash = strrchr(tempfile, '/'); 1705 if (slash != NULL) { 1706 *slash = '\0'; 1707 failed = lstat(tempfile, &st); 1708 if (failed && errno != ENOENT) 1709 err(1, "Error on lstat(%s)", tempfile); 1710 if (failed) 1711 createdir(ent, tempfile); 1712 else if (!S_ISDIR(st.st_mode)) 1713 errx(1, "%s exists but is not a directory", 1714 tempfile); 1715 } 1716 } 1717 1718 /* 1719 * First create an unused filename, so it can be chown'ed and 1720 * chmod'ed before it is moved into the real location. mkstemp 1721 * will create the file mode=600 & owned by us. Note that all 1722 * temp files will have a suffix of '.z<something>'. 1723 */ 1724 strlcpy(tempfile, realfile, sizeof(tempfile)); 1725 strlcat(tempfile, ".zXXXXXX", sizeof(tempfile)); 1726 if (noaction) 1727 printf("\tmktemp %s\n", tempfile); 1728 else { 1729 fd = mkstemp(tempfile); 1730 if (fd < 0) 1731 err(1, "can't mkstemp logfile %s", tempfile); 1732 1733 /* 1734 * Add status message to what will become the new log file. 1735 */ 1736 if (!(ent->flags & CE_BINARY)) { 1737 if (log_trim(tempfile, ent)) 1738 err(1, "can't add status message to log"); 1739 } 1740 } 1741 1742 /* Change the owner/group, if we are supposed to */ 1743 if (ent->uid != (uid_t)-1 || ent->gid != (gid_t)-1) { 1744 if (noaction) 1745 printf("\tchown %u:%u %s\n", ent->uid, ent->gid, 1746 tempfile); 1747 else { 1748 failed = fchown(fd, ent->uid, ent->gid); 1749 if (failed) 1750 err(1, "can't fchown temp file %s", tempfile); 1751 } 1752 } 1753 1754 /* 1755 * Note that if the real logfile still exists, and if the call 1756 * to rename() fails, then "neither the old file nor the new 1757 * file shall be changed or created" (to quote the standard). 1758 * If the call succeeds, then the file will be replaced without 1759 * any window where some other process might find that the file 1760 * did not exist. 1761 * XXX - ? It may be that for some error conditions, we could 1762 * retry by first removing the realfile and then renaming. 1763 */ 1764 if (noaction) { 1765 printf("\tchmod %o %s\n", ent->permissions, tempfile); 1766 printf("\tmv %s %s\n", tempfile, realfile); 1767 } else { 1768 failed = fchmod(fd, ent->permissions); 1769 if (failed) 1770 err(1, "can't fchmod temp file '%s'", tempfile); 1771 failed = rename(tempfile, realfile); 1772 if (failed) 1773 err(1, "can't mv %s to %s", tempfile, realfile); 1774 } 1775 1776 if (fd >= 0) 1777 close(fd); 1778} 1779 1780/*- 1781 * Parse a limited subset of ISO 8601. The specific format is as follows: 1782 * 1783 * [CC[YY[MM[DD]]]][THH[MM[SS]]] (where `T' is the literal letter) 1784 * 1785 * We don't accept a timezone specification; missing fields (including timezone) 1786 * are defaulted to the current date but time zero. 1787 */ 1788static time_t 1789parse8601(const char *s) 1790{ 1791 char *t; 1792 time_t tsecs; 1793 struct tm tm, *tmp; 1794 long l; 1795 1796 tmp = localtime(&timenow); 1797 tm = *tmp; 1798 1799 tm.tm_hour = tm.tm_min = tm.tm_sec = 0; 1800 1801 l = strtol(s, &t, 10); 1802 if (l < 0 || l >= INT_MAX || (*t != '\0' && *t != 'T')) 1803 return (-1); 1804 1805 /* 1806 * Now t points either to the end of the string (if no time was 1807 * provided) or to the letter `T' which separates date and time in 1808 * ISO 8601. The pointer arithmetic is the same for either case. 1809 */ 1810 switch (t - s) { 1811 case 8: 1812 tm.tm_year = ((l / 1000000) - 19) * 100; 1813 l = l % 1000000; 1814 case 6: 1815 tm.tm_year -= tm.tm_year % 100; 1816 tm.tm_year += l / 10000; 1817 l = l % 10000; 1818 case 4: 1819 tm.tm_mon = (l / 100) - 1; 1820 l = l % 100; 1821 case 2: 1822 tm.tm_mday = l; 1823 case 0: 1824 break; 1825 default: 1826 return (-1); 1827 } 1828 1829 /* sanity check */ 1830 if (tm.tm_year < 70 || tm.tm_mon < 0 || tm.tm_mon > 12 1831 || tm.tm_mday < 1 || tm.tm_mday > 31) 1832 return (-1); 1833 1834 if (*t != '\0') { 1835 s = ++t; 1836 l = strtol(s, &t, 10); 1837 if (l < 0 || l >= INT_MAX || (*t != '\0' && !isspace(*t))) 1838 return (-1); 1839 1840 switch (t - s) { 1841 case 6: 1842 tm.tm_sec = l % 100; 1843 l /= 100; 1844 case 4: 1845 tm.tm_min = l % 100; 1846 l /= 100; 1847 case 2: 1848 tm.tm_hour = l; 1849 case 0: 1850 break; 1851 default: 1852 return (-1); 1853 } 1854 1855 /* sanity check */ 1856 if (tm.tm_sec < 0 || tm.tm_sec > 60 || tm.tm_min < 0 1857 || tm.tm_min > 59 || tm.tm_hour < 0 || tm.tm_hour > 23) 1858 return (-1); 1859 } 1860 1861 tsecs = mktime(&tm); 1862 /* 1863 * Check for invalid times, including things like the missing 1864 * hour when switching from "standard time" to "daylight saving". 1865 */ 1866 if (tsecs == (time_t)-1) 1867 tsecs = (time_t)-2; 1868 return (tsecs); 1869} 1870 1871/*- 1872 * Parse a cyclic time specification, the format is as follows: 1873 * 1874 * [Dhh] or [Wd[Dhh]] or [Mdd[Dhh]] 1875 * 1876 * to rotate a logfile cyclic at 1877 * 1878 * - every day (D) within a specific hour (hh) (hh = 0...23) 1879 * - once a week (W) at a specific day (d) OR (d = 0..6, 0 = Sunday) 1880 * - once a month (M) at a specific day (d) (d = 1..31,l|L) 1881 * 1882 * We don't accept a timezone specification; missing fields 1883 * are defaulted to the current date but time zero. 1884 */ 1885static time_t 1886parseDWM(char *s) 1887{ 1888 char *t; 1889 time_t tsecs; 1890 struct tm tm, *tmp; 1891 long l; 1892 int nd; 1893 static int mtab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; 1894 int WMseen = 0; 1895 int Dseen = 0; 1896 1897 tmp = localtime(&timenow); 1898 tm = *tmp; 1899 1900 /* set no. of days per month */ 1901 1902 nd = mtab[tm.tm_mon]; 1903 1904 if (tm.tm_mon == 1) { 1905 if (((tm.tm_year + 1900) % 4 == 0) && 1906 ((tm.tm_year + 1900) % 100 != 0) && 1907 ((tm.tm_year + 1900) % 400 == 0)) { 1908 nd++; /* leap year, 29 days in february */ 1909 } 1910 } 1911 tm.tm_hour = tm.tm_min = tm.tm_sec = 0; 1912 1913 for (;;) { 1914 switch (*s) { 1915 case 'D': 1916 if (Dseen) 1917 return (-1); 1918 Dseen++; 1919 s++; 1920 l = strtol(s, &t, 10); 1921 if (l < 0 || l > 23) 1922 return (-1); 1923 tm.tm_hour = l; 1924 break; 1925 1926 case 'W': 1927 if (WMseen) 1928 return (-1); 1929 WMseen++; 1930 s++; 1931 l = strtol(s, &t, 10); 1932 if (l < 0 || l > 6) 1933 return (-1); 1934 if (l != tm.tm_wday) { 1935 int save; 1936 1937 if (l < tm.tm_wday) { 1938 save = 6 - tm.tm_wday; 1939 save += (l + 1); 1940 } else { 1941 save = l - tm.tm_wday; 1942 } 1943 1944 tm.tm_mday += save; 1945 1946 if (tm.tm_mday > nd) { 1947 tm.tm_mon++; 1948 tm.tm_mday = tm.tm_mday - nd; 1949 } 1950 } 1951 break; 1952 1953 case 'M': 1954 if (WMseen) 1955 return (-1); 1956 WMseen++; 1957 s++; 1958 if (tolower(*s) == 'l') { 1959 tm.tm_mday = nd; 1960 s++; 1961 t = s; 1962 } else { 1963 l = strtol(s, &t, 10); 1964 if (l < 1 || l > 31) 1965 return (-1); 1966 1967 if (l > nd) 1968 return (-1); 1969 tm.tm_mday = l; 1970 } 1971 break; 1972 1973 default: 1974 return (-1); 1975 break; 1976 } 1977 1978 if (*t == '\0' || isspace(*t)) 1979 break; 1980 else 1981 s = t; 1982 } 1983 1984 tsecs = mktime(&tm); 1985 /* 1986 * Check for invalid times, including things like the missing 1987 * hour when switching from "standard time" to "daylight saving". 1988 */ 1989 if (tsecs == (time_t)-1) 1990 tsecs = (time_t)-2; 1991 return (tsecs); 1992} 1993