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