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