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