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