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