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