newsyslog.c revision 111768
1/* 2 * This file contains changes from the Open Software Foundation. 3 */ 4 5/* 6 * Copyright 1988, 1989 by the Massachusetts Institute of Technology 7 * 8 * Permission to use, copy, modify, and distribute this software and its 9 * documentation for any purpose and without fee is hereby granted, provided 10 * that the above copyright notice appear in all copies and that both that 11 * copyright notice and this permission notice appear in supporting 12 * documentation, and that the names of M.I.T. and the M.I.T. S.I.P.B. not be 13 * used in advertising or publicity pertaining to distribution of the 14 * software without specific, written prior permission. M.I.T. and the M.I.T. 15 * S.I.P.B. make no representations about the suitability of this software 16 * for any purpose. It is provided "as is" without express or implied 17 * warranty. 18 * 19 */ 20 21/* 22 * newsyslog - roll over selected logs at the appropriate time, keeping the a 23 * specified number of backup files around. 24 */ 25 26#ifndef lint 27static const char rcsid[] = 28"$FreeBSD: head/usr.sbin/newsyslog/newsyslog.c 111768 2003-03-02 22:05:17Z gad $"; 29#endif /* not lint */ 30 31#define OSF 32#ifndef COMPRESS_POSTFIX 33#define COMPRESS_POSTFIX ".gz" 34#endif 35#ifndef BZCOMPRESS_POSTFIX 36#define BZCOMPRESS_POSTFIX ".bz2" 37#endif 38 39#include <sys/param.h> 40#include <sys/stat.h> 41#include <sys/wait.h> 42 43#include <ctype.h> 44#include <err.h> 45#include <errno.h> 46#include <fcntl.h> 47#include <glob.h> 48#include <grp.h> 49#include <paths.h> 50#include <pwd.h> 51#include <signal.h> 52#include <stdio.h> 53#include <stdlib.h> 54#include <string.h> 55#include <time.h> 56#include <unistd.h> 57 58#include "pathnames.h" 59 60#define kbytes(size) (((size) + 1023) >> 10) 61 62#ifdef _IBMR2 63/* Calculates (db * DEV_BSIZE) */ 64#define dbtob(db) ((unsigned)(db) << UBSHIFT) 65#endif 66 67/* 68 * Bit-values for the 'flags' parsed from a config-file entry. 69 */ 70#define CE_COMPACT 0x0001 /* Compact the achived log files with gzip. */ 71#define CE_BZCOMPACT 0x0002 /* Compact the achived log files with bzip2. */ 72#define CE_COMPACTWAIT 0x0004 /* wait until compressing one file finishes */ 73 /* before starting the next step. */ 74#define CE_BINARY 0x0008 /* Logfile is in binary, do not add status */ 75 /* messages to logfile(s) when rotating. */ 76#define CE_NOSIGNAL 0x0010 /* There is no process to signal when */ 77 /* trimming this file. */ 78#define CE_TRIMAT 0x0020 /* trim file at a specific time. */ 79#define CE_GLOB 0x0040 /* name of the log is file name pattern. */ 80 81#define NONE -1 82 83struct conf_entry { 84 char *log; /* Name of the log */ 85 char *pid_file; /* PID file */ 86 int uid; /* Owner of log */ 87 int gid; /* Group of log */ 88 int numlogs; /* Number of logs to keep */ 89 int size; /* Size cutoff to trigger trimming the log */ 90 int hours; /* Hours between log trimming */ 91 time_t trim_at; /* Specific time to do trimming */ 92 int permissions; /* File permissions on the log */ 93 int flags; /* CE_COMPACT, CE_BZCOMPACT, CE_BINARY */ 94 int sig; /* Signal to send */ 95 int def_cfg; /* Using the <default> rule for this file */ 96 struct conf_entry *next;/* Linked list pointer */ 97}; 98 99#define DEFAULT_MARKER "<default>" 100 101int archtodir = 0; /* Archive old logfiles to other directory */ 102int verbose = 0; /* Print out what's going on */ 103int needroot = 1; /* Root privs are necessary */ 104int noaction = 0; /* Don't do anything, just show it */ 105int nosignal; /* Do not send any signals */ 106int force = 0; /* Force the trim no matter what */ 107char *archdirname; /* Directory path to old logfiles archive */ 108const char *conf = _PATH_CONF; /* Configuration file to use */ 109time_t timenow; 110 111#define MIN_PID 5 112#define MAX_PID 99999 /* was lower, see /usr/include/sys/proc.h */ 113char hostname[MAXHOSTNAMELEN]; /* hostname */ 114char daytime[16]; /* timenow in human readable form */ 115 116static struct conf_entry *parse_file(char **files); 117static char *sob(char *p); 118static char *son(char *p); 119static char *missing_field(char *p, char *errline); 120static void do_entry(struct conf_entry * ent); 121static void free_entry(struct conf_entry *ent); 122static struct conf_entry *init_entry(const char *fname, 123 struct conf_entry *src_entry); 124static void PRS(int argc, char **argv); 125static void usage(void); 126static void dotrim(const struct conf_entry *trim_ent, char *log, 127 int numdays, int flags, int perm, int owner_uid, 128 int group_gid, int sig); 129static int log_trim(const char *log, const struct conf_entry *log_ent); 130static void compress_log(char *log, int dowait); 131static void bzcompress_log(char *log, int dowait); 132static int sizefile(char *file); 133static int age_old_log(char *file); 134static pid_t get_pid(const char *pid_file); 135static time_t parse8601(char *s, char *errline); 136static void movefile(char *from, char *to, int perm, int owner_uid, 137 int group_gid); 138static void createdir(char *dirpart); 139static time_t parseDWM(char *s, char *errline); 140 141/* 142 * All the following are defined to work on an 'int', in the 143 * range 0 to 255, plus EOF. Define wrappers which can take 144 * values of type 'char', either signed or unsigned. 145 */ 146#define isspacech(Anychar) isspace(((int) Anychar) & 255) 147#define tolowerch(Anychar) tolower(((int) Anychar) & 255) 148 149int 150main(int argc, char **argv) 151{ 152 struct conf_entry *p, *q; 153 char *savglob; 154 glob_t pglob; 155 int i; 156 157 PRS(argc, argv); 158 if (needroot && getuid() && geteuid()) 159 errx(1, "must have root privs"); 160 p = q = parse_file(argv + optind); 161 162 while (p) { 163 if ((p->flags & CE_GLOB) == 0) { 164 do_entry(p); 165 } else { 166 if (glob(p->log, GLOB_NOCHECK, NULL, &pglob) != 0) { 167 warn("can't expand pattern: %s", p->log); 168 } else { 169 savglob = p->log; 170 for (i = 0; i < pglob.gl_matchc; i++) { 171 p->log = pglob.gl_pathv[i]; 172 do_entry(p); 173 } 174 globfree(&pglob); 175 p->log = savglob; 176 } 177 } 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->uid = src_entry->uid; 208 tempwork->gid = src_entry->gid; 209 tempwork->numlogs = src_entry->numlogs; 210 tempwork->size = src_entry->size; 211 tempwork->hours = src_entry->hours; 212 tempwork->trim_at = src_entry->trim_at; 213 tempwork->permissions = src_entry->permissions; 214 tempwork->flags = src_entry->flags; 215 tempwork->sig = src_entry->sig; 216 tempwork->def_cfg = src_entry->def_cfg; 217 } else { 218 /* Initialize as a "do-nothing" entry */ 219 tempwork->pid_file = NULL; 220 tempwork->uid = NONE; 221 tempwork->gid = NONE; 222 tempwork->numlogs = 1; 223 tempwork->size = -1; 224 tempwork->hours = -1; 225 tempwork->trim_at = (time_t)0; 226 tempwork->permissions = 0; 227 tempwork->flags = 0; 228 tempwork->sig = SIGHUP; 229 tempwork->def_cfg = 0; 230 } 231 tempwork->next = NULL; 232 233 return (tempwork); 234} 235 236static void 237free_entry(struct conf_entry *ent) 238{ 239 240 if (ent == NULL) 241 return; 242 243 if (ent->log != NULL) { 244 if (verbose > 4) 245 printf("\t--> [freeing entry for %s]\n", ent->log); 246 free(ent->log); 247 ent->log = NULL; 248 } 249 250 if (ent->pid_file != NULL) { 251 free(ent->pid_file); 252 ent->pid_file = NULL; 253 } 254 255 free(ent); 256} 257 258static void 259do_entry(struct conf_entry * ent) 260{ 261 int size, modtime; 262 263 if (verbose) { 264 if (ent->flags & CE_COMPACT) 265 printf("%s <%dZ>: ", ent->log, ent->numlogs); 266 else if (ent->flags & CE_BZCOMPACT) 267 printf("%s <%dJ>: ", ent->log, ent->numlogs); 268 else 269 printf("%s <%d>: ", ent->log, ent->numlogs); 270 } 271 size = sizefile(ent->log); 272 modtime = age_old_log(ent->log); 273 if (size < 0) { 274 if (verbose) 275 printf("does not exist.\n"); 276 } else { 277 if (ent->flags & CE_TRIMAT && !force) { 278 if (timenow < ent->trim_at 279 || difftime(timenow, ent->trim_at) >= 60 * 60) { 280 if (verbose) 281 printf("--> will trim at %s", 282 ctime(&ent->trim_at)); 283 return; 284 } else if (verbose && ent->hours <= 0) { 285 printf("--> time is up\n"); 286 } 287 } 288 if (verbose && (ent->size > 0)) 289 printf("size (Kb): %d [%d] ", size, ent->size); 290 if (verbose && (ent->hours > 0)) 291 printf(" age (hr): %d [%d] ", modtime, ent->hours); 292 if (force || ((ent->size > 0) && (size >= ent->size)) || 293 (ent->hours <= 0 && (ent->flags & CE_TRIMAT)) || 294 ((ent->hours > 0) && ((modtime >= ent->hours) 295 || (modtime < 0)))) { 296 if (verbose) 297 printf("--> trimming log....\n"); 298 if (noaction && !verbose) { 299 if (ent->flags & CE_COMPACT) 300 printf("%s <%dZ>: trimming\n", 301 ent->log, ent->numlogs); 302 else if (ent->flags & CE_BZCOMPACT) 303 printf("%s <%dJ>: trimming\n", 304 ent->log, ent->numlogs); 305 else 306 printf("%s <%d>: trimming\n", 307 ent->log, ent->numlogs); 308 } 309 dotrim(ent, ent->log, ent->numlogs, ent->flags, 310 ent->permissions, ent->uid, ent->gid, ent->sig); 311 } else { 312 if (verbose) 313 printf("--> skipping\n"); 314 } 315 } 316} 317 318static void 319PRS(int argc, char **argv) 320{ 321 int c; 322 char *p; 323 324 timenow = time((time_t *) 0); 325 (void)strncpy(daytime, ctime(&timenow) + 4, 15); 326 daytime[15] = '\0'; 327 328 /* Let's get our hostname */ 329 (void) gethostname(hostname, sizeof(hostname)); 330 331 /* Truncate domain */ 332 if ((p = strchr(hostname, '.'))) { 333 *p = '\0'; 334 } 335 336 /* Parse command line options. */ 337 while ((c = getopt(argc, argv, "a:f:nrsvF")) != -1) 338 switch (c) { 339 case 'a': 340 archtodir++; 341 archdirname = optarg; 342 break; 343 case 'f': 344 conf = optarg; 345 break; 346 case 'n': 347 noaction++; 348 break; 349 case 'r': 350 needroot = 0; 351 break; 352 case 's': 353 nosignal = 1; 354 break; 355 case 'v': 356 verbose++; 357 break; 358 case 'F': 359 force++; 360 break; 361 default: 362 usage(); 363 /* NOTREACHED */ 364 } 365} 366 367static void 368usage(void) 369{ 370 371 fprintf(stderr, 372 "usage: newsyslog [-Fnrsv] [-f config-file] [-a directory] [ filename ... ]\n"); 373 exit(1); 374} 375 376/* 377 * Parse a configuration file and return a linked list of all the logs to 378 * process 379 */ 380static struct conf_entry * 381parse_file(char **files) 382{ 383 FILE *f; 384 char line[BUFSIZ], *parse, *q; 385 char *cp, *errline, *group; 386 char **given; 387 struct conf_entry *defconf, *first, *working, *worklist; 388 struct passwd *pass; 389 struct group *grp; 390 int eol; 391 392 defconf = first = working = worklist = NULL; 393 394 if (strcmp(conf, "-")) 395 f = fopen(conf, "r"); 396 else 397 f = stdin; 398 if (!f) 399 err(1, "%s", conf); 400 while (fgets(line, BUFSIZ, f)) { 401 if ((line[0] == '\n') || (line[0] == '#') || 402 (strlen(line) == 0)) 403 continue; 404 errline = strdup(line); 405 for (cp = line + 1; *cp != '\0'; cp++) { 406 if (*cp != '#') 407 continue; 408 if (*(cp - 1) == '\\') { 409 strcpy(cp - 1, cp); 410 cp--; 411 continue; 412 } 413 *cp = '\0'; 414 break; 415 } 416 417 q = parse = missing_field(sob(line), errline); 418 parse = son(line); 419 if (!*parse) 420 errx(1, "malformed line (missing fields):\n%s", 421 errline); 422 *parse = '\0'; 423 424 /* 425 * If newsyslog was run with a list of specific filenames, 426 * then this line of the config file should be skipped if 427 * it is NOT one of those given files (except that we do 428 * want any line that defines the <default> action). 429 * 430 * XXX - note that CE_GLOB processing is *NOT* done when 431 * trying to match a filename given on the command! 432 */ 433 if (*files) { 434 if (strcasecmp(DEFAULT_MARKER, q) != 0) { 435 for (given = files; *given; ++given) { 436 if (strcmp(*given, q) == 0) 437 break; 438 } 439 if (!*given) 440 continue; 441 } 442 if (verbose > 2) 443 printf("\t+ Matched entry %s\n", q); 444 } else { 445 /* 446 * If no files were specified on the command line, 447 * then we can skip any line which defines the 448 * default action. 449 */ 450 if (strcasecmp(DEFAULT_MARKER, q) == 0) { 451 if (verbose > 2) 452 printf("\t+ Ignoring entry for %s\n", 453 q); 454 continue; 455 } 456 } 457 458 working = init_entry(q, NULL); 459 if (strcasecmp(DEFAULT_MARKER, q) == 0) { 460 if (defconf != NULL) { 461 warnx("Ignoring duplicate entry for %s!", q); 462 free_entry(working); 463 continue; 464 } 465 defconf = working; 466 } else { 467 if (!first) 468 first = working; 469 else 470 worklist->next = working; 471 worklist = working; 472 } 473 474 q = parse = missing_field(sob(++parse), errline); 475 parse = son(parse); 476 if (!*parse) 477 errx(1, "malformed line (missing fields):\n%s", 478 errline); 479 *parse = '\0'; 480 if ((group = strchr(q, ':')) != NULL || 481 (group = strrchr(q, '.')) != NULL) { 482 *group++ = '\0'; 483 if (*q) { 484 if (!(isnumber(*q))) { 485 if ((pass = getpwnam(q)) == NULL) 486 errx(1, 487 "error in config file; unknown user:\n%s", 488 errline); 489 working->uid = pass->pw_uid; 490 } else 491 working->uid = atoi(q); 492 } else 493 working->uid = NONE; 494 495 q = group; 496 if (*q) { 497 if (!(isnumber(*q))) { 498 if ((grp = getgrnam(q)) == NULL) 499 errx(1, 500 "error in config file; unknown group:\n%s", 501 errline); 502 working->gid = grp->gr_gid; 503 } else 504 working->gid = atoi(q); 505 } else 506 working->gid = NONE; 507 508 q = parse = missing_field(sob(++parse), errline); 509 parse = son(parse); 510 if (!*parse) 511 errx(1, "malformed line (missing fields):\n%s", 512 errline); 513 *parse = '\0'; 514 } else 515 working->uid = working->gid = NONE; 516 517 if (!sscanf(q, "%o", &working->permissions)) 518 errx(1, "error in config file; bad permissions:\n%s", 519 errline); 520 521 q = parse = missing_field(sob(++parse), errline); 522 parse = son(parse); 523 if (!*parse) 524 errx(1, "malformed line (missing fields):\n%s", 525 errline); 526 *parse = '\0'; 527 if (!sscanf(q, "%d", &working->numlogs) || working->numlogs < 0) 528 errx(1, "error in config file; bad value for count of logs to save:\n%s", 529 errline); 530 531 q = parse = missing_field(sob(++parse), errline); 532 parse = son(parse); 533 if (!*parse) 534 errx(1, "malformed line (missing fields):\n%s", 535 errline); 536 *parse = '\0'; 537 if (isdigit(*q)) 538 working->size = atoi(q); 539 else 540 working->size = -1; 541 542 working->flags = 0; 543 q = parse = missing_field(sob(++parse), errline); 544 parse = son(parse); 545 eol = !*parse; 546 *parse = '\0'; 547 { 548 char *ep; 549 u_long ul; 550 551 ul = strtoul(q, &ep, 10); 552 if (ep == q) 553 working->hours = 0; 554 else if (*ep == '*') 555 working->hours = -1; 556 else if (ul > INT_MAX) 557 errx(1, "interval is too large:\n%s", errline); 558 else 559 working->hours = ul; 560 561 if (*ep != '\0' && *ep != '@' && *ep != '*' && 562 *ep != '$') 563 errx(1, "malformed interval/at:\n%s", errline); 564 if (*ep == '@') { 565 if ((working->trim_at = parse8601(ep + 1, errline)) 566 == (time_t) - 1) 567 errx(1, "malformed at:\n%s", errline); 568 working->flags |= CE_TRIMAT; 569 } else if (*ep == '$') { 570 if ((working->trim_at = parseDWM(ep + 1, errline)) 571 == (time_t) - 1) 572 errx(1, "malformed at:\n%s", errline); 573 working->flags |= CE_TRIMAT; 574 } 575 } 576 577 if (eol) 578 q = NULL; 579 else { 580 q = parse = sob(++parse); /* Optional field */ 581 parse = son(parse); 582 if (!*parse) 583 eol = 1; 584 *parse = '\0'; 585 } 586 587 for (; q && *q && !isspacech(*q); q++) { 588 switch (tolowerch(*q)) { 589 case 'b': 590 working->flags |= CE_BINARY; 591 break; 592 case 'c': 593 /* 594 * netbsd uses 'c' for "create". We will 595 * temporarily accept it for 'g', because 596 * earlier freebsd versions had a typo 597 * of ('G' || 'c')... 598 */ 599 warnx("Assuming 'g' for 'c' in flags for line:\n%s", 600 errline); 601 /* FALLTHROUGH */ 602 case 'g': 603 working->flags |= CE_GLOB; 604 break; 605 case 'j': 606 working->flags |= CE_BZCOMPACT; 607 break; 608 case 'n': 609 working->flags |= CE_NOSIGNAL; 610 break; 611 case 'w': 612 working->flags |= CE_COMPACTWAIT; 613 break; 614 case 'z': 615 working->flags |= CE_COMPACT; 616 break; 617 case '-': 618 break; 619 default: 620 errx(1, "illegal flag in config file -- %c", 621 *q); 622 } 623 } 624 625 if (eol) 626 q = NULL; 627 else { 628 q = parse = sob(++parse); /* Optional field */ 629 parse = son(parse); 630 if (!*parse) 631 eol = 1; 632 *parse = '\0'; 633 } 634 635 working->pid_file = NULL; 636 if (q && *q) { 637 if (*q == '/') 638 working->pid_file = strdup(q); 639 else if (isdigit(*q)) 640 goto got_sig; 641 else 642 errx(1, 643 "illegal pid file or signal number in config file:\n%s", 644 errline); 645 } 646 if (eol) 647 q = NULL; 648 else { 649 q = parse = sob(++parse); /* Optional field */ 650 *(parse = son(parse)) = '\0'; 651 } 652 653 working->sig = SIGHUP; 654 if (q && *q) { 655 if (isdigit(*q)) { 656 got_sig: 657 working->sig = atoi(q); 658 } else { 659 err_sig: 660 errx(1, 661 "illegal signal number in config file:\n%s", 662 errline); 663 } 664 if (working->sig < 1 || working->sig >= NSIG) 665 goto err_sig; 666 } 667 668 /* 669 * Finish figuring out what pid-file to use (if any) in 670 * later processing if this logfile needs to be rotated. 671 */ 672 if ((working->flags & CE_NOSIGNAL) == CE_NOSIGNAL) { 673 /* 674 * This config-entry specified 'n' for nosignal, 675 * see if it also specified an explicit pid_file. 676 * This would be a pretty pointless combination. 677 */ 678 if (working->pid_file != NULL) { 679 warnx("Ignoring '%s' because flag 'n' was specified in line:\n%s", 680 working->pid_file, errline); 681 free(working->pid_file); 682 working->pid_file = NULL; 683 } 684 } else if (nosignal) { 685 /* 686 * While this entry might usually signal some 687 * process via the pid-file, newsyslog was run 688 * with '-s', so quietly ignore the pid-file. 689 */ 690 if (working->pid_file != NULL) { 691 free(working->pid_file); 692 working->pid_file = NULL; 693 } 694 } else if (working->pid_file == NULL) { 695 /* 696 * This entry did not specify the 'n' flag, which 697 * means it should signal syslogd unless it had 698 * specified some other pid-file. But we only 699 * try to notify syslog if we are root 700 */ 701 if (needroot) 702 working->pid_file = strdup(_PATH_SYSLOGPID); 703 } 704 705 free(errline); 706 errline = NULL; 707 } 708 (void) fclose(f); 709 710 /* 711 * The entire config file has been processed. If there were 712 * no specific files given on the run command, then the work 713 * of this routine is done. 714 */ 715 if (*files == NULL) 716 return (first); 717 718 /* 719 * If the program was given a specific list of files to process, 720 * it may be that some of those files were not listed in the 721 * config file. Those unlisted files should get the default 722 * rotation action. First, create the default-rotation action 723 * if none was found in the config file. 724 */ 725 if (defconf == NULL) { 726 working = init_entry(DEFAULT_MARKER, NULL); 727 working->numlogs = 3; 728 working->size = 50; 729 working->permissions = S_IRUSR|S_IWUSR; 730 defconf = working; 731 } 732 733 for (given = files; *given; ++given) { 734 for (working = first; working; working = working->next) { 735 if (strcmp(*given, working->log) == 0) 736 break; 737 } 738 if (working != NULL) 739 continue; 740 if (verbose > 2) 741 printf("\t+ No entry for %s (will use %s)\n", 742 *given, DEFAULT_MARKER); 743 /* 744 * This given file was not found in the config file. 745 * Add another item on to our work list, based on the 746 * default entry. 747 */ 748 working = init_entry(*given, defconf); 749 if (!first) 750 first = working; 751 else 752 worklist->next = working; 753 /* This is a file that was *not* found in config file */ 754 working->def_cfg = 1; 755 worklist = working; 756 } 757 758 free_entry(defconf); 759 return (first); 760} 761 762static char * 763missing_field(char *p, char *errline) 764{ 765 766 if (!p || !*p) 767 errx(1, "missing field in config file:\n%s", errline); 768 return (p); 769} 770 771static void 772dotrim(const struct conf_entry *trim_ent, char *log, int numdays, int flags, 773 int perm, int owner_uid, int group_gid, int sig) 774{ 775 char dirpart[MAXPATHLEN], namepart[MAXPATHLEN]; 776 char file1[MAXPATHLEN], file2[MAXPATHLEN]; 777 char zfile1[MAXPATHLEN], zfile2[MAXPATHLEN]; 778 char jfile1[MAXPATHLEN]; 779 char tfile[MAXPATHLEN]; 780 int notified, need_notification, fd, _numdays; 781 struct stat st; 782 pid_t pid; 783 784#ifdef _IBMR2 785 /* 786 * AIX 3.1 has a broken fchown- if the owner_uid is -1, it will 787 * actually change it to be owned by uid -1, instead of leaving it 788 * as is, as it is supposed to. 789 */ 790 if (owner_uid == -1) 791 owner_uid = geteuid(); 792#endif 793 794 if (archtodir) { 795 char *p; 796 797 /* build complete name of archive directory into dirpart */ 798 if (*archdirname == '/') { /* absolute */ 799 strlcpy(dirpart, archdirname, sizeof(dirpart)); 800 } else { /* relative */ 801 /* get directory part of logfile */ 802 strlcpy(dirpart, log, sizeof(dirpart)); 803 if ((p = rindex(dirpart, '/')) == NULL) 804 dirpart[0] = '\0'; 805 else 806 *(p + 1) = '\0'; 807 strlcat(dirpart, archdirname, sizeof(dirpart)); 808 } 809 810 /* check if archive directory exists, if not, create it */ 811 if (lstat(dirpart, &st)) 812 createdir(dirpart); 813 814 /* get filename part of logfile */ 815 if ((p = rindex(log, '/')) == NULL) 816 strlcpy(namepart, log, sizeof(namepart)); 817 else 818 strlcpy(namepart, p + 1, sizeof(namepart)); 819 820 /* name of oldest log */ 821 (void) snprintf(file1, sizeof(file1), "%s/%s.%d", dirpart, 822 namepart, numdays); 823 (void) snprintf(zfile1, sizeof(zfile1), "%s%s", file1, 824 COMPRESS_POSTFIX); 825 snprintf(jfile1, sizeof(jfile1), "%s%s", file1, 826 BZCOMPRESS_POSTFIX); 827 } else { 828 /* name of oldest log */ 829 (void) snprintf(file1, sizeof(file1), "%s.%d", log, numdays); 830 (void) snprintf(zfile1, sizeof(zfile1), "%s%s", file1, 831 COMPRESS_POSTFIX); 832 snprintf(jfile1, sizeof(jfile1), "%s%s", file1, 833 BZCOMPRESS_POSTFIX); 834 } 835 836 if (noaction) { 837 printf("rm -f %s\n", file1); 838 printf("rm -f %s\n", zfile1); 839 printf("rm -f %s\n", jfile1); 840 } else { 841 (void) unlink(file1); 842 (void) unlink(zfile1); 843 (void) unlink(jfile1); 844 } 845 846 /* Move down log files */ 847 _numdays = numdays; /* preserve */ 848 while (numdays--) { 849 850 (void) strlcpy(file2, file1, sizeof(file2)); 851 852 if (archtodir) 853 (void) snprintf(file1, sizeof(file1), "%s/%s.%d", 854 dirpart, namepart, numdays); 855 else 856 (void) snprintf(file1, sizeof(file1), "%s.%d", log, 857 numdays); 858 859 (void) strlcpy(zfile1, file1, sizeof(zfile1)); 860 (void) strlcpy(zfile2, file2, sizeof(zfile2)); 861 if (lstat(file1, &st)) { 862 (void) strlcat(zfile1, COMPRESS_POSTFIX, 863 sizeof(zfile1)); 864 (void) strlcat(zfile2, COMPRESS_POSTFIX, 865 sizeof(zfile2)); 866 if (lstat(zfile1, &st)) { 867 strlcpy(zfile1, file1, sizeof(zfile1)); 868 strlcpy(zfile2, file2, sizeof(zfile2)); 869 strlcat(zfile1, BZCOMPRESS_POSTFIX, 870 sizeof(zfile1)); 871 strlcat(zfile2, BZCOMPRESS_POSTFIX, 872 sizeof(zfile2)); 873 if (lstat(zfile1, &st)) 874 continue; 875 } 876 } 877 if (noaction) { 878 printf("mv %s %s\n", zfile1, zfile2); 879 printf("chmod %o %s\n", perm, zfile2); 880 printf("chown %d:%d %s\n", 881 owner_uid, group_gid, zfile2); 882 } else { 883 (void) rename(zfile1, zfile2); 884 (void) chmod(zfile2, perm); 885 (void) chown(zfile2, owner_uid, group_gid); 886 } 887 } 888 if (!noaction && !(flags & CE_BINARY)) { 889 /* Report the trimming to the old log */ 890 (void) log_trim(log, trim_ent); 891 } 892 893 if (!_numdays) { 894 if (noaction) 895 printf("rm %s\n", log); 896 else 897 (void) unlink(log); 898 } else { 899 if (noaction) 900 printf("mv %s to %s\n", log, file1); 901 else { 902 if (archtodir) 903 movefile(log, file1, perm, owner_uid, 904 group_gid); 905 else 906 (void) rename(log, file1); 907 } 908 } 909 910 if (noaction) 911 printf("Start new log..."); 912 else { 913 strlcpy(tfile, log, sizeof(tfile)); 914 strlcat(tfile, ".XXXXXX", sizeof(tfile)); 915 mkstemp(tfile); 916 fd = creat(tfile, perm); 917 if (fd < 0) 918 err(1, "can't start new log"); 919 if (fchown(fd, owner_uid, group_gid)) 920 err(1, "can't chmod new log file"); 921 (void) close(fd); 922 if (!(flags & CE_BINARY)) { 923 /* Add status message to new log file */ 924 if (log_trim(tfile, trim_ent)) 925 err(1, "can't add status message to log"); 926 } 927 } 928 if (noaction) 929 printf("chmod %o %s...\n", perm, log); 930 else { 931 (void) chmod(tfile, perm); 932 if (rename(tfile, log) < 0) { 933 err(1, "can't start new log"); 934 (void) unlink(tfile); 935 } 936 } 937 938 pid = 0; 939 need_notification = notified = 0; 940 if (trim_ent->pid_file != NULL) { 941 need_notification = 1; 942 pid = get_pid(trim_ent->pid_file); 943 } 944 if (pid) { 945 if (noaction) { 946 notified = 1; 947 printf("kill -%d %d\n", sig, (int) pid); 948 } else if (kill(pid, sig)) 949 warn("can't notify daemon, pid %d", (int) pid); 950 else { 951 notified = 1; 952 if (verbose) 953 printf("daemon pid %d notified\n", (int) pid); 954 } 955 } 956 if ((flags & CE_COMPACT) || (flags & CE_BZCOMPACT)) { 957 if (need_notification && !notified) 958 warnx( 959 "log %s not compressed because daemon not notified", 960 log); 961 else if (noaction) 962 printf("Compress %s.0\n", log); 963 else { 964 if (notified) { 965 if (verbose) 966 printf("small pause to allow daemon to close log\n"); 967 sleep(10); 968 } 969 if (archtodir) { 970 (void) snprintf(file1, sizeof(file1), "%s/%s", 971 dirpart, namepart); 972 if (flags & CE_COMPACT) 973 compress_log(file1, 974 flags & CE_COMPACTWAIT); 975 else if (flags & CE_BZCOMPACT) 976 bzcompress_log(file1, 977 flags & CE_COMPACTWAIT); 978 } else { 979 if (flags & CE_COMPACT) 980 compress_log(log, 981 flags & CE_COMPACTWAIT); 982 else if (flags & CE_BZCOMPACT) 983 bzcompress_log(log, 984 flags & CE_COMPACTWAIT); 985 } 986 } 987 } 988} 989 990/* Log the fact that the logs were turned over */ 991static int 992log_trim(const char *log, const struct conf_entry *log_ent) 993{ 994 FILE *f; 995 const char *xtra; 996 997 if ((f = fopen(log, "a")) == NULL) 998 return (-1); 999 xtra = ""; 1000 if (log_ent->def_cfg) 1001 xtra = " using <default> rule"; 1002 fprintf(f, "%s %s newsyslog[%d]: logfile turned over%s\n", 1003 daytime, hostname, (int) getpid(), xtra); 1004 if (fclose(f) == EOF) 1005 err(1, "log_trim: fclose:"); 1006 return (0); 1007} 1008 1009/* Fork of gzip to compress the old log file */ 1010static void 1011compress_log(char *log, int dowait) 1012{ 1013 pid_t pid; 1014 char tmp[MAXPATHLEN]; 1015 1016 while (dowait && (wait(NULL) > 0 || errno == EINTR)) 1017 ; 1018 (void) snprintf(tmp, sizeof(tmp), "%s.0", log); 1019 pid = fork(); 1020 if (pid < 0) 1021 err(1, "gzip fork"); 1022 else if (!pid) { 1023 (void) execl(_PATH_GZIP, _PATH_GZIP, "-f", tmp, (char *)0); 1024 err(1, _PATH_GZIP); 1025 } 1026} 1027 1028/* Fork of bzip2 to compress the old log file */ 1029static void 1030bzcompress_log(char *log, int dowait) 1031{ 1032 pid_t pid; 1033 char tmp[MAXPATHLEN]; 1034 1035 while (dowait && (wait(NULL) > 0 || errno == EINTR)) 1036 ; 1037 snprintf(tmp, sizeof(tmp), "%s.0", log); 1038 pid = fork(); 1039 if (pid < 0) 1040 err(1, "bzip2 fork"); 1041 else if (!pid) { 1042 execl(_PATH_BZIP2, _PATH_BZIP2, "-f", tmp, (char *)0); 1043 err(1, _PATH_BZIP2); 1044 } 1045} 1046 1047/* Return size in kilobytes of a file */ 1048static int 1049sizefile(char *file) 1050{ 1051 struct stat sb; 1052 1053 if (stat(file, &sb) < 0) 1054 return (-1); 1055 return (kbytes(dbtob(sb.st_blocks))); 1056} 1057 1058/* Return the age of old log file (file.0) */ 1059static int 1060age_old_log(char *file) 1061{ 1062 struct stat sb; 1063 char tmp[MAXPATHLEN + sizeof(".0") + sizeof(COMPRESS_POSTFIX) + 1]; 1064 1065 if (archtodir) { 1066 char *p; 1067 1068 /* build name of archive directory into tmp */ 1069 if (*archdirname == '/') { /* absolute */ 1070 strlcpy(tmp, archdirname, sizeof(tmp)); 1071 } else { /* relative */ 1072 /* get directory part of logfile */ 1073 strlcpy(tmp, file, sizeof(tmp)); 1074 if ((p = rindex(tmp, '/')) == NULL) 1075 tmp[0] = '\0'; 1076 else 1077 *(p + 1) = '\0'; 1078 strlcat(tmp, archdirname, sizeof(tmp)); 1079 } 1080 1081 strlcat(tmp, "/", sizeof(tmp)); 1082 1083 /* get filename part of logfile */ 1084 if ((p = rindex(file, '/')) == NULL) 1085 strlcat(tmp, file, sizeof(tmp)); 1086 else 1087 strlcat(tmp, p + 1, sizeof(tmp)); 1088 } else { 1089 (void) strlcpy(tmp, file, sizeof(tmp)); 1090 } 1091 1092 if (stat(strcat(tmp, ".0"), &sb) < 0) 1093 if (stat(strcat(tmp, COMPRESS_POSTFIX), &sb) < 0) 1094 return (-1); 1095 return ((int) (timenow - sb.st_mtime + 1800) / 3600); 1096} 1097 1098static pid_t 1099get_pid(const char *pid_file) 1100{ 1101 FILE *f; 1102 char line[BUFSIZ]; 1103 pid_t pid = 0; 1104 1105 if ((f = fopen(pid_file, "r")) == NULL) 1106 warn("can't open %s pid file to restart a daemon", 1107 pid_file); 1108 else { 1109 if (fgets(line, BUFSIZ, f)) { 1110 pid = atol(line); 1111 if (pid < MIN_PID || pid > MAX_PID) { 1112 warnx("preposterous process number: %d", 1113 (int)pid); 1114 pid = 0; 1115 } 1116 } else 1117 warn("can't read %s pid file to restart a daemon", 1118 pid_file); 1119 (void) fclose(f); 1120 } 1121 return (pid); 1122} 1123 1124/* Skip Over Blanks */ 1125char * 1126sob(char *p) 1127{ 1128 while (p && *p && isspace(*p)) 1129 p++; 1130 return (p); 1131} 1132 1133/* Skip Over Non-Blanks */ 1134char * 1135son(char *p) 1136{ 1137 while (p && *p && !isspace(*p)) 1138 p++; 1139 return (p); 1140} 1141 1142/* 1143 * Parse a limited subset of ISO 8601. The specific format is as follows: 1144 * 1145 * [CC[YY[MM[DD]]]][THH[MM[SS]]] (where `T' is the literal letter) 1146 * 1147 * We don't accept a timezone specification; missing fields (including timezone) 1148 * are defaulted to the current date but time zero. 1149 */ 1150static time_t 1151parse8601(char *s, char *errline) 1152{ 1153 char *t; 1154 time_t tsecs; 1155 struct tm tm, *tmp; 1156 u_long ul; 1157 1158 tmp = localtime(&timenow); 1159 tm = *tmp; 1160 1161 tm.tm_hour = tm.tm_min = tm.tm_sec = 0; 1162 1163 ul = strtoul(s, &t, 10); 1164 if (*t != '\0' && *t != 'T') 1165 return (-1); 1166 1167 /* 1168 * Now t points either to the end of the string (if no time was 1169 * provided) or to the letter `T' which separates date and time in 1170 * ISO 8601. The pointer arithmetic is the same for either case. 1171 */ 1172 switch (t - s) { 1173 case 8: 1174 tm.tm_year = ((ul / 1000000) - 19) * 100; 1175 ul = ul % 1000000; 1176 case 6: 1177 tm.tm_year -= tm.tm_year % 100; 1178 tm.tm_year += ul / 10000; 1179 ul = ul % 10000; 1180 case 4: 1181 tm.tm_mon = (ul / 100) - 1; 1182 ul = ul % 100; 1183 case 2: 1184 tm.tm_mday = ul; 1185 case 0: 1186 break; 1187 default: 1188 return (-1); 1189 } 1190 1191 /* sanity check */ 1192 if (tm.tm_year < 70 || tm.tm_mon < 0 || tm.tm_mon > 12 1193 || tm.tm_mday < 1 || tm.tm_mday > 31) 1194 return (-1); 1195 1196 if (*t != '\0') { 1197 s = ++t; 1198 ul = strtoul(s, &t, 10); 1199 if (*t != '\0' && !isspace(*t)) 1200 return (-1); 1201 1202 switch (t - s) { 1203 case 6: 1204 tm.tm_sec = ul % 100; 1205 ul /= 100; 1206 case 4: 1207 tm.tm_min = ul % 100; 1208 ul /= 100; 1209 case 2: 1210 tm.tm_hour = ul; 1211 case 0: 1212 break; 1213 default: 1214 return (-1); 1215 } 1216 1217 /* sanity check */ 1218 if (tm.tm_sec < 0 || tm.tm_sec > 60 || tm.tm_min < 0 1219 || tm.tm_min > 59 || tm.tm_hour < 0 || tm.tm_hour > 23) 1220 return (-1); 1221 } 1222 if ((tsecs = mktime(&tm)) == -1) 1223 errx(1, "nonexistent time:\n%s", errline); 1224 return (tsecs); 1225} 1226 1227/* physically move file */ 1228static void 1229movefile(char *from, char *to, int perm, int owner_uid, int group_gid) 1230{ 1231 FILE *src, *dst; 1232 int c; 1233 1234 if ((src = fopen(from, "r")) == NULL) 1235 err(1, "can't fopen %s for reading", from); 1236 if ((dst = fopen(to, "w")) == NULL) 1237 err(1, "can't fopen %s for writing", to); 1238 if (fchown(fileno(dst), owner_uid, group_gid)) 1239 err(1, "can't fchown %s", to); 1240 if (fchmod(fileno(dst), perm)) 1241 err(1, "can't fchmod %s", to); 1242 1243 while ((c = getc(src)) != EOF) { 1244 if ((putc(c, dst)) == EOF) 1245 err(1, "error writing to %s", to); 1246 } 1247 1248 if (ferror(src)) 1249 err(1, "error reading from %s", from); 1250 if ((fclose(src)) != 0) 1251 err(1, "can't fclose %s", to); 1252 if ((fclose(dst)) != 0) 1253 err(1, "can't fclose %s", from); 1254 if ((unlink(from)) != 0) 1255 err(1, "can't unlink %s", from); 1256} 1257 1258/* create one or more directory components of a path */ 1259static void 1260createdir(char *dirpart) 1261{ 1262 int res; 1263 char *s, *d; 1264 char mkdirpath[MAXPATHLEN]; 1265 struct stat st; 1266 1267 s = dirpart; 1268 d = mkdirpath; 1269 1270 for (;;) { 1271 *d++ = *s++; 1272 if (*s != '/' && *s != '\0') 1273 continue; 1274 *d = '\0'; 1275 res = lstat(mkdirpath, &st); 1276 if (res != 0) { 1277 if (noaction) { 1278 printf("mkdir %s\n", mkdirpath); 1279 } else { 1280 res = mkdir(mkdirpath, 0755); 1281 if (res != 0) 1282 err(1, "Error on mkdir(\"%s\") for -a", 1283 mkdirpath); 1284 } 1285 } 1286 if (*s == '\0') 1287 break; 1288 } 1289 if (verbose) 1290 printf("created directory '%s' for -a\n", dirpart); 1291} 1292 1293/*- 1294 * Parse a cyclic time specification, the format is as follows: 1295 * 1296 * [Dhh] or [Wd[Dhh]] or [Mdd[Dhh]] 1297 * 1298 * to rotate a logfile cyclic at 1299 * 1300 * - every day (D) within a specific hour (hh) (hh = 0...23) 1301 * - once a week (W) at a specific day (d) OR (d = 0..6, 0 = Sunday) 1302 * - once a month (M) at a specific day (d) (d = 1..31,l|L) 1303 * 1304 * We don't accept a timezone specification; missing fields 1305 * are defaulted to the current date but time zero. 1306 */ 1307static time_t 1308parseDWM(char *s, char *errline) 1309{ 1310 char *t; 1311 time_t tsecs; 1312 struct tm tm, *tmp; 1313 long l; 1314 int nd; 1315 static int mtab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; 1316 int WMseen = 0; 1317 int Dseen = 0; 1318 1319 tmp = localtime(&timenow); 1320 tm = *tmp; 1321 1322 /* set no. of days per month */ 1323 1324 nd = mtab[tm.tm_mon]; 1325 1326 if (tm.tm_mon == 1) { 1327 if (((tm.tm_year + 1900) % 4 == 0) && 1328 ((tm.tm_year + 1900) % 100 != 0) && 1329 ((tm.tm_year + 1900) % 400 == 0)) { 1330 nd++; /* leap year, 29 days in february */ 1331 } 1332 } 1333 tm.tm_hour = tm.tm_min = tm.tm_sec = 0; 1334 1335 for (;;) { 1336 switch (*s) { 1337 case 'D': 1338 if (Dseen) 1339 return (-1); 1340 Dseen++; 1341 s++; 1342 l = strtol(s, &t, 10); 1343 if (l < 0 || l > 23) 1344 return (-1); 1345 tm.tm_hour = l; 1346 break; 1347 1348 case 'W': 1349 if (WMseen) 1350 return (-1); 1351 WMseen++; 1352 s++; 1353 l = strtol(s, &t, 10); 1354 if (l < 0 || l > 6) 1355 return (-1); 1356 if (l != tm.tm_wday) { 1357 int save; 1358 1359 if (l < tm.tm_wday) { 1360 save = 6 - tm.tm_wday; 1361 save += (l + 1); 1362 } else { 1363 save = l - tm.tm_wday; 1364 } 1365 1366 tm.tm_mday += save; 1367 1368 if (tm.tm_mday > nd) { 1369 tm.tm_mon++; 1370 tm.tm_mday = tm.tm_mday - nd; 1371 } 1372 } 1373 break; 1374 1375 case 'M': 1376 if (WMseen) 1377 return (-1); 1378 WMseen++; 1379 s++; 1380 if (tolower(*s) == 'l') { 1381 tm.tm_mday = nd; 1382 s++; 1383 t = s; 1384 } else { 1385 l = strtol(s, &t, 10); 1386 if (l < 1 || l > 31) 1387 return (-1); 1388 1389 if (l > nd) 1390 return (-1); 1391 tm.tm_mday = l; 1392 } 1393 break; 1394 1395 default: 1396 return (-1); 1397 break; 1398 } 1399 1400 if (*t == '\0' || isspace(*t)) 1401 break; 1402 else 1403 s = t; 1404 } 1405 if ((tsecs = mktime(&tm)) == -1) 1406 errx(1, "nonexistent time:\n%s", errline); 1407 return (tsecs); 1408} 1409