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