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