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