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