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