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