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