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