newsyslog.c revision 111966
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 27static const char rcsid[] = 28"$FreeBSD: head/usr.sbin/newsyslog/newsyslog.c 111966 2003-03-07 21:06:32Z gad $"; 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 <sys/param.h> 40#include <sys/stat.h> 41#include <sys/wait.h> 42 43#include <ctype.h> 44#include <err.h> 45#include <errno.h> 46#include <fcntl.h> 47#include <fnmatch.h> 48#include <glob.h> 49#include <grp.h> 50#include <paths.h> 51#include <pwd.h> 52#include <signal.h> 53#include <stdio.h> 54#include <stdlib.h> 55#include <string.h> 56#include <time.h> 57#include <unistd.h> 58 59#include "pathnames.h" 60 61/* 62 * Bit-values for the 'flags' parsed from a config-file entry. 63 */ 64#define CE_COMPACT 0x0001 /* Compact the achived log files with gzip. */ 65#define CE_BZCOMPACT 0x0002 /* Compact the achived log files with bzip2. */ 66#define CE_COMPACTWAIT 0x0004 /* wait until compressing one file finishes */ 67 /* before starting the next step. */ 68#define CE_BINARY 0x0008 /* Logfile is in binary, do not add status */ 69 /* messages to logfile(s) when rotating. */ 70#define CE_NOSIGNAL 0x0010 /* There is no process to signal when */ 71 /* trimming this file. */ 72#define CE_TRIMAT 0x0020 /* trim file at a specific time. */ 73#define CE_GLOB 0x0040 /* name of the log is file name pattern. */ 74 75#define MIN_PID 5 /* Don't touch pids lower than this */ 76#define MAX_PID 99999 /* was lower, see /usr/include/sys/proc.h */ 77 78#define kbytes(size) (((size) + 1023) >> 10) 79 80struct conf_entry { 81 char *log; /* Name of the log */ 82 char *pid_file; /* PID file */ 83 char *r_reason; /* The reason this file is being rotated */ 84 int rotate; /* Non-zero if this file should be rotated */ 85 uid_t uid; /* Owner of log */ 86 gid_t gid; /* Group of log */ 87 int numlogs; /* Number of logs to keep */ 88 int size; /* Size cutoff to trigger trimming the log */ 89 int hours; /* Hours between log trimming */ 90 time_t trim_at; /* Specific time to do trimming */ 91 int permissions; /* File permissions on the log */ 92 int flags; /* CE_COMPACT, CE_BZCOMPACT, CE_BINARY */ 93 int sig; /* Signal to send */ 94 int def_cfg; /* Using the <default> rule for this file */ 95 struct conf_entry *next;/* Linked list pointer */ 96}; 97 98#define DEFAULT_MARKER "<default>" 99 100int archtodir = 0; /* Archive old logfiles to other directory */ 101int verbose = 0; /* Print out what's going on */ 102int needroot = 1; /* Root privs are necessary */ 103int noaction = 0; /* Don't do anything, just show it */ 104int nosignal; /* Do not send any signals */ 105int force = 0; /* Force the trim no matter what */ 106int rotatereq = 0; /* -R = Always rotate the file(s) as given */ 107 /* on the command (this also requires */ 108 /* that a list of files *are* given on */ 109 /* the run command). */ 110char *requestor; /* The name given on a -R request */ 111char *archdirname; /* Directory path to old logfiles archive */ 112const char *conf; /* Configuration file to use */ 113time_t timenow; 114 115char hostname[MAXHOSTNAMELEN]; /* hostname */ 116char daytime[16]; /* timenow in human readable form */ 117 118static struct conf_entry *get_worklist(char **files); 119static void parse_file(FILE *cf, const char *cfname, struct conf_entry **work_p, 120 struct conf_entry **defconf_p); 121static char *sob(char *p); 122static char *son(char *p); 123static char *missing_field(char *p, char *errline); 124static void do_entry(struct conf_entry * ent); 125static void free_entry(struct conf_entry *ent); 126static struct conf_entry *init_entry(const char *fname, 127 struct conf_entry *src_entry); 128static void parse_args(int argc, char **argv); 129static void usage(void); 130static void dotrim(const struct conf_entry *ent, char *log, 131 int numdays, int flags); 132static int log_trim(const char *log, const struct conf_entry *log_ent); 133static void compress_log(char *log, int dowait); 134static void bzcompress_log(char *log, int dowait); 135static int sizefile(char *file); 136static int age_old_log(char *file); 137static pid_t get_pid(const char *pid_file); 138static time_t parse8601(char *s, char *errline); 139static void movefile(char *from, char *to, int perm, uid_t owner_uid, 140 gid_t group_gid); 141static void createdir(char *dirpart); 142static time_t parseDWM(char *s, char *errline); 143 144/* 145 * All the following are defined to work on an 'int', in the 146 * range 0 to 255, plus EOF. Define wrappers which can take 147 * values of type 'char', either signed or unsigned. 148 */ 149#define isprintch(Anychar) isprint(((int) Anychar) & 255) 150#define isspacech(Anychar) isspace(((int) Anychar) & 255) 151#define tolowerch(Anychar) tolower(((int) Anychar) & 255) 152 153int 154main(int argc, char **argv) 155{ 156 struct conf_entry *p, *q; 157 char *savglob; 158 glob_t pglob; 159 int i; 160 161 parse_args(argc, argv); 162 argc -= optind; 163 argv += optind; 164 165 if (needroot && getuid() && geteuid()) 166 errx(1, "must have root privs"); 167 p = q = get_worklist(argv); 168 169 while (p) { 170 if ((p->flags & CE_GLOB) == 0) { 171 do_entry(p); 172 } else { 173 if (verbose > 2) 174 printf("\t+ Processing pattern %s\n", p->log); 175 if (glob(p->log, GLOB_NOCHECK, NULL, &pglob) != 0) { 176 warn("can't expand pattern: %s", p->log); 177 } else { 178 savglob = p->log; 179 for (i = 0; i < pglob.gl_matchc; i++) { 180 p->log = pglob.gl_pathv[i]; 181 do_entry(p); 182 } 183 globfree(&pglob); 184 p->log = savglob; 185 if (verbose > 2) 186 printf("\t+ Done with pattern\n"); 187 } 188 } 189 p = p->next; 190 free_entry(q); 191 q = p; 192 } 193 while (wait(NULL) > 0 || errno == EINTR) 194 ; 195 return (0); 196} 197 198static struct conf_entry * 199init_entry(const char *fname, struct conf_entry *src_entry) 200{ 201 struct conf_entry *tempwork; 202 203 if (verbose > 4) 204 printf("\t--> [creating entry for %s]\n", fname); 205 206 tempwork = malloc(sizeof(struct conf_entry)); 207 if (tempwork == NULL) 208 err(1, "malloc of conf_entry for %s", fname); 209 210 tempwork->log = strdup(fname); 211 if (tempwork->log == NULL) 212 err(1, "strdup for %s", fname); 213 214 if (src_entry != NULL) { 215 tempwork->pid_file = NULL; 216 if (src_entry->pid_file) 217 tempwork->pid_file = strdup(src_entry->pid_file); 218 tempwork->r_reason = NULL; 219 tempwork->rotate = 0; 220 tempwork->uid = src_entry->uid; 221 tempwork->gid = src_entry->gid; 222 tempwork->numlogs = src_entry->numlogs; 223 tempwork->size = src_entry->size; 224 tempwork->hours = src_entry->hours; 225 tempwork->trim_at = src_entry->trim_at; 226 tempwork->permissions = src_entry->permissions; 227 tempwork->flags = src_entry->flags; 228 tempwork->sig = src_entry->sig; 229 tempwork->def_cfg = src_entry->def_cfg; 230 } else { 231 /* Initialize as a "do-nothing" entry */ 232 tempwork->pid_file = NULL; 233 tempwork->r_reason = NULL; 234 tempwork->rotate = 0; 235 tempwork->uid = (uid_t)-1; 236 tempwork->gid = (gid_t)-1; 237 tempwork->numlogs = 1; 238 tempwork->size = -1; 239 tempwork->hours = -1; 240 tempwork->trim_at = (time_t)0; 241 tempwork->permissions = 0; 242 tempwork->flags = 0; 243 tempwork->sig = SIGHUP; 244 tempwork->def_cfg = 0; 245 } 246 tempwork->next = NULL; 247 248 return (tempwork); 249} 250 251static void 252free_entry(struct conf_entry *ent) 253{ 254 255 if (ent == NULL) 256 return; 257 258 if (ent->log != NULL) { 259 if (verbose > 4) 260 printf("\t--> [freeing entry for %s]\n", ent->log); 261 free(ent->log); 262 ent->log = NULL; 263 } 264 265 if (ent->pid_file != NULL) { 266 free(ent->pid_file); 267 ent->pid_file = NULL; 268 } 269 270 if (ent->r_reason != NULL) { 271 free(ent->r_reason); 272 ent->r_reason = NULL; 273 } 274 275 free(ent); 276} 277 278static void 279do_entry(struct conf_entry * ent) 280{ 281#define REASON_MAX 80 282 int size, modtime; 283 char temp_reason[REASON_MAX]; 284 285 if (verbose) { 286 if (ent->flags & CE_COMPACT) 287 printf("%s <%dZ>: ", ent->log, ent->numlogs); 288 else if (ent->flags & CE_BZCOMPACT) 289 printf("%s <%dJ>: ", ent->log, ent->numlogs); 290 else 291 printf("%s <%d>: ", ent->log, ent->numlogs); 292 } 293 size = sizefile(ent->log); 294 modtime = age_old_log(ent->log); 295 ent->rotate = 0; 296 if (size < 0) { 297 if (verbose) 298 printf("does not exist.\n"); 299 } else { 300 if (ent->flags & CE_TRIMAT && !force && !rotatereq) { 301 if (timenow < ent->trim_at 302 || difftime(timenow, ent->trim_at) >= 60 * 60) { 303 if (verbose) 304 printf("--> will trim at %s", 305 ctime(&ent->trim_at)); 306 return; 307 } else if (verbose && ent->hours <= 0) { 308 printf("--> time is up\n"); 309 } 310 } 311 if (verbose && (ent->size > 0)) 312 printf("size (Kb): %d [%d] ", size, ent->size); 313 if (verbose && (ent->hours > 0)) 314 printf(" age (hr): %d [%d] ", modtime, ent->hours); 315 316 /* 317 * Figure out if this logfile needs to be rotated. 318 */ 319 temp_reason[0] = '\0'; 320 if (rotatereq) { 321 ent->rotate = 1; 322 snprintf(temp_reason, REASON_MAX, " due to -R from %s", 323 requestor); 324 } else if (force) { 325 ent->rotate = 1; 326 snprintf(temp_reason, REASON_MAX, " due to -F request"); 327 } else if ((ent->size > 0) && (size >= ent->size)) { 328 ent->rotate = 1; 329 snprintf(temp_reason, REASON_MAX, " due to size>%dK", 330 ent->size); 331 } else if (ent->hours <= 0 && (ent->flags & CE_TRIMAT)) { 332 ent->rotate = 1; 333 } else if ((ent->hours > 0) && ((modtime >= ent->hours) || 334 (modtime < 0))) { 335 ent->rotate = 1; 336 } 337 338 /* 339 * If the file needs to be rotated, then rotate it. 340 */ 341 if (ent->rotate) { 342 if (temp_reason[0] != '\0') 343 ent->r_reason = strdup(temp_reason); 344 if (verbose) 345 printf("--> trimming log....\n"); 346 if (noaction && !verbose) { 347 if (ent->flags & CE_COMPACT) 348 printf("%s <%dZ>: trimming\n", 349 ent->log, ent->numlogs); 350 else if (ent->flags & CE_BZCOMPACT) 351 printf("%s <%dJ>: trimming\n", 352 ent->log, ent->numlogs); 353 else 354 printf("%s <%d>: trimming\n", 355 ent->log, ent->numlogs); 356 } 357 dotrim(ent, ent->log, ent->numlogs, ent->flags); 358 } else { 359 if (verbose) 360 printf("--> skipping\n"); 361 } 362 } 363#undef REASON_MAX 364} 365 366static void 367parse_args(int argc, char **argv) 368{ 369 int ch; 370 char *p; 371 372 timenow = time(NULL); 373 (void)strncpy(daytime, ctime(&timenow) + 4, 15); 374 daytime[15] = '\0'; 375 376 /* Let's get our hostname */ 377 (void)gethostname(hostname, sizeof(hostname)); 378 379 /* Truncate domain */ 380 if ((p = strchr(hostname, '.')) != NULL) 381 *p = '\0'; 382 383 /* Parse command line options. */ 384 while ((ch = getopt(argc, argv, "a:f:nrsvFR:")) != -1) 385 switch (ch) { 386 case 'a': 387 archtodir++; 388 archdirname = optarg; 389 break; 390 case 'f': 391 conf = optarg; 392 break; 393 case 'n': 394 noaction++; 395 break; 396 case 'r': 397 needroot = 0; 398 break; 399 case 's': 400 nosignal = 1; 401 break; 402 case 'v': 403 verbose++; 404 break; 405 case 'F': 406 force++; 407 break; 408 case 'R': 409 rotatereq++; 410 requestor = strdup(optarg); 411 break; 412 case 'm': /* Used by OpenBSD for "monitor mode" */ 413 default: 414 usage(); 415 /* NOTREACHED */ 416 } 417 418 if (rotatereq) { 419 if (optind == argc) { 420 warnx("At least one filename must be given when -R is specified."); 421 usage(); 422 /* NOTREACHED */ 423 } 424 /* Make sure "requestor" value is safe for a syslog message. */ 425 for (p = requestor; *p != '\0'; p++) { 426 if (!isprintch(*p) && (*p != '\t')) 427 *p = '.'; 428 } 429 } 430} 431 432static void 433usage(void) 434{ 435 436 fprintf(stderr, 437 "usage: newsyslog [-Fnrsv] [-a directory] [-f config-file]\n" 438 " [ [-R requestor] filename ... ]\n"); 439 exit(1); 440} 441 442/* 443 * Parse a configuration file and return a linked list of all the logs 444 * which should be processed. 445 */ 446static struct conf_entry * 447get_worklist(char **files) 448{ 449 FILE *f; 450 const char *fname; 451 char **given; 452 struct conf_entry *defconf, *dupent, *ent, *firstnew; 453 struct conf_entry *newlist, *worklist; 454 int gmatch; 455 456 defconf = worklist = NULL; 457 458 fname = conf; 459 if (fname == NULL) 460 fname = _PATH_CONF; 461 462 if (strcmp(fname, "-") != 0) 463 f = fopen(fname, "r"); 464 else { 465 f = stdin; 466 fname = "<stdin>"; 467 } 468 if (!f) 469 err(1, "%s", conf); 470 471 parse_file(f, fname, &worklist, &defconf); 472 (void) fclose(f); 473 474 /* 475 * All config-file information has been read in and turned into 476 * a worklist. If there were no specific files given on the run 477 * command, then the work of this routine is done. 478 */ 479 if (*files == NULL) { 480 if (defconf != NULL) 481 free_entry(defconf); 482 return (worklist); 483 /* NOTREACHED */ 484 } 485 486 /* 487 * If newsyslog was given a specific list of files to process, 488 * it may be that some of those files were not listed in any 489 * config file. Those unlisted files should get the default 490 * rotation action. First, create the default-rotation action 491 * if none was found in a system config file. 492 */ 493 if (defconf == NULL) { 494 defconf = init_entry(DEFAULT_MARKER, NULL); 495 defconf->numlogs = 3; 496 defconf->size = 50; 497 defconf->permissions = S_IRUSR|S_IWUSR; 498 } 499 500 /* 501 * If newsyslog was run with a list of specific filenames, 502 * then create a new worklist which has only those files in 503 * it, picking up the rotation-rules for those files from 504 * the original worklist. 505 * 506 * XXX - Note that this will copy multiple rules for a single 507 * logfile, if multiple entries are an exact match for 508 * that file. That matches the historic behavior, but do 509 * we want to continue to allow it? If so, it should 510 * probably be handled more intelligently. 511 */ 512 firstnew = newlist = NULL; 513 for (given = files; *given; ++given) { 514 gmatch = 0; 515 /* 516 * First try to find exact-matches for this given file. 517 */ 518 for (ent = worklist; ent; ent = ent->next) { 519 if ((ent->flags & CE_GLOB) != 0) 520 continue; 521 if (strcmp(ent->log, *given) == 0) { 522 gmatch++; 523 dupent = init_entry(*given, ent); 524 if (!firstnew) 525 firstnew = dupent; 526 else 527 newlist->next = dupent; 528 newlist = dupent; 529 } 530 } 531 if (gmatch) { 532 if (verbose > 2) 533 printf("\t+ Matched entry %s\n", *given); 534 continue; 535 } 536 537 /* 538 * There was no exact-match for this given file, so look 539 * for a "glob" entry which does match. 540 */ 541 for (ent = worklist; ent; ent = ent->next) { 542 if ((ent->flags & CE_GLOB) == 0) 543 continue; 544 if (fnmatch(ent->log, *given, FNM_PATHNAME) == 0) { 545 gmatch++; 546 dupent = init_entry(*given, ent); 547 if (!firstnew) 548 firstnew = dupent; 549 else 550 newlist->next = dupent; 551 newlist = dupent; 552 /* This work entry is *not* a glob! */ 553 dupent->flags &= ~CE_GLOB; 554 /* Only allow a match to one glob-entry */ 555 break; 556 } 557 } 558 if (gmatch) { 559 if (verbose > 2) 560 printf("\t+ Matched %s via %s\n", *given, 561 ent->log); 562 continue; 563 } 564 565 /* 566 * This given file was not found in any config file, so 567 * add a worklist item based on the default entry. 568 */ 569 if (verbose > 2) 570 printf("\t+ No entry matched %s (will use %s)\n", 571 *given, DEFAULT_MARKER); 572 dupent = init_entry(*given, defconf); 573 if (!firstnew) 574 firstnew = dupent; 575 else 576 newlist->next = dupent; 577 /* Mark that it was *not* found in a config file */ 578 dupent->def_cfg = 1; 579 newlist = dupent; 580 } 581 582 /* 583 * Free all the entries in the original work list, and then 584 * return the new work list. 585 */ 586 while (worklist) { 587 ent = worklist->next; 588 free_entry(worklist); 589 worklist = ent; 590 } 591 592 free_entry(defconf); 593 return (newlist); 594} 595 596/* 597 * Parse a configuration file and update a linked list of all the logs to 598 * process. 599 */ 600static void 601parse_file(FILE *cf, const char *cfname, struct conf_entry **work_p, 602 struct conf_entry **defconf_p) 603{ 604 char line[BUFSIZ], *parse, *q; 605 char *cp, *errline, *group; 606 struct conf_entry *working, *worklist; 607 struct passwd *pwd; 608 struct group *grp; 609 int eol; 610 611 /* 612 * XXX - for now, assume that only one config file will be read, 613 * ie, this routine is only called one time. 614 */ 615 worklist = NULL; 616 617 while (fgets(line, BUFSIZ, cf)) { 618 if ((line[0] == '\n') || (line[0] == '#') || 619 (strlen(line) == 0)) 620 continue; 621 errline = strdup(line); 622 for (cp = line + 1; *cp != '\0'; cp++) { 623 if (*cp != '#') 624 continue; 625 if (*(cp - 1) == '\\') { 626 strcpy(cp - 1, cp); 627 cp--; 628 continue; 629 } 630 *cp = '\0'; 631 break; 632 } 633 634 q = parse = missing_field(sob(line), errline); 635 parse = son(line); 636 if (!*parse) 637 errx(1, "malformed line (missing fields):\n%s", 638 errline); 639 *parse = '\0'; 640 641 working = init_entry(q, NULL); 642 if (strcasecmp(DEFAULT_MARKER, q) == 0) { 643 if (defconf_p == NULL) { 644 warnx("Ignoring entry for %s in %s!", q, 645 cfname); 646 free_entry(working); 647 continue; 648 } else if (*defconf_p != NULL) { 649 warnx("Ignoring duplicate entry for %s!", q); 650 free_entry(working); 651 continue; 652 } 653 *defconf_p = working; 654 } else { 655 if (!*work_p) 656 *work_p = working; 657 else 658 worklist->next = working; 659 worklist = working; 660 } 661 662 q = parse = missing_field(sob(++parse), errline); 663 parse = son(parse); 664 if (!*parse) 665 errx(1, "malformed line (missing fields):\n%s", 666 errline); 667 *parse = '\0'; 668 if ((group = strchr(q, ':')) != NULL || 669 (group = strrchr(q, '.')) != NULL) { 670 *group++ = '\0'; 671 if (*q) { 672 if (!(isnumber(*q))) { 673 if ((pwd = getpwnam(q)) == NULL) 674 errx(1, 675 "error in config file; unknown user:\n%s", 676 errline); 677 working->uid = pwd->pw_uid; 678 } else 679 working->uid = atoi(q); 680 } else 681 working->uid = (uid_t)-1; 682 683 q = group; 684 if (*q) { 685 if (!(isnumber(*q))) { 686 if ((grp = getgrnam(q)) == NULL) 687 errx(1, 688 "error in config file; unknown group:\n%s", 689 errline); 690 working->gid = grp->gr_gid; 691 } else 692 working->gid = atoi(q); 693 } else 694 working->gid = (gid_t)-1; 695 696 q = parse = missing_field(sob(++parse), errline); 697 parse = son(parse); 698 if (!*parse) 699 errx(1, "malformed line (missing fields):\n%s", 700 errline); 701 *parse = '\0'; 702 } else { 703 working->uid = (uid_t)-1; 704 working->gid = (gid_t)-1; 705 } 706 707 if (!sscanf(q, "%o", &working->permissions)) 708 errx(1, "error in config file; bad permissions:\n%s", 709 errline); 710 711 q = parse = missing_field(sob(++parse), errline); 712 parse = son(parse); 713 if (!*parse) 714 errx(1, "malformed line (missing fields):\n%s", 715 errline); 716 *parse = '\0'; 717 if (!sscanf(q, "%d", &working->numlogs) || working->numlogs < 0) 718 errx(1, "error in config file; bad value for count of logs to save:\n%s", 719 errline); 720 721 q = parse = missing_field(sob(++parse), errline); 722 parse = son(parse); 723 if (!*parse) 724 errx(1, "malformed line (missing fields):\n%s", 725 errline); 726 *parse = '\0'; 727 if (isdigit(*q)) 728 working->size = atoi(q); 729 else 730 working->size = -1; 731 732 working->flags = 0; 733 q = parse = missing_field(sob(++parse), errline); 734 parse = son(parse); 735 eol = !*parse; 736 *parse = '\0'; 737 { 738 char *ep; 739 u_long ul; 740 741 ul = strtoul(q, &ep, 10); 742 if (ep == q) 743 working->hours = 0; 744 else if (*ep == '*') 745 working->hours = -1; 746 else if (ul > INT_MAX) 747 errx(1, "interval is too large:\n%s", errline); 748 else 749 working->hours = ul; 750 751 if (*ep != '\0' && *ep != '@' && *ep != '*' && 752 *ep != '$') 753 errx(1, "malformed interval/at:\n%s", errline); 754 if (*ep == '@') { 755 if ((working->trim_at = parse8601(ep + 1, errline)) 756 == (time_t) - 1) 757 errx(1, "malformed at:\n%s", errline); 758 working->flags |= CE_TRIMAT; 759 } else if (*ep == '$') { 760 if ((working->trim_at = parseDWM(ep + 1, errline)) 761 == (time_t) - 1) 762 errx(1, "malformed at:\n%s", errline); 763 working->flags |= CE_TRIMAT; 764 } 765 } 766 767 if (eol) 768 q = NULL; 769 else { 770 q = parse = sob(++parse); /* Optional field */ 771 parse = son(parse); 772 if (!*parse) 773 eol = 1; 774 *parse = '\0'; 775 } 776 777 for (; q && *q && !isspacech(*q); q++) { 778 switch (tolowerch(*q)) { 779 case 'b': 780 working->flags |= CE_BINARY; 781 break; 782 case 'c': /* Used by NetBSD for "CE_CREATE" */ 783 /* 784 * netbsd uses 'c' for "create". We will 785 * temporarily accept it for 'g', because 786 * earlier freebsd versions had a typo 787 * of ('G' || 'c')... 788 */ 789 warnx("Assuming 'g' for 'c' in flags for line:\n%s", 790 errline); 791 /* FALLTHROUGH */ 792 case 'g': 793 working->flags |= CE_GLOB; 794 break; 795 case 'j': 796 working->flags |= CE_BZCOMPACT; 797 break; 798 case 'n': 799 working->flags |= CE_NOSIGNAL; 800 break; 801 case 'w': 802 working->flags |= CE_COMPACTWAIT; 803 break; 804 case 'z': 805 working->flags |= CE_COMPACT; 806 break; 807 case '-': 808 break; 809 case 'f': /* Used by OpenBSD for "CE_FOLLOW" */ 810 case 'm': /* Used by OpenBSD for "CE_MONITOR" */ 811 case 'p': /* Used by NetBSD for "CE_PLAIN0" */ 812 default: 813 errx(1, "illegal flag in config file -- %c", 814 *q); 815 } 816 } 817 818 if (eol) 819 q = NULL; 820 else { 821 q = parse = sob(++parse); /* Optional field */ 822 parse = son(parse); 823 if (!*parse) 824 eol = 1; 825 *parse = '\0'; 826 } 827 828 working->pid_file = NULL; 829 if (q && *q) { 830 if (*q == '/') 831 working->pid_file = strdup(q); 832 else if (isdigit(*q)) 833 goto got_sig; 834 else 835 errx(1, 836 "illegal pid file or signal number in config file:\n%s", 837 errline); 838 } 839 if (eol) 840 q = NULL; 841 else { 842 q = parse = sob(++parse); /* Optional field */ 843 *(parse = son(parse)) = '\0'; 844 } 845 846 working->sig = SIGHUP; 847 if (q && *q) { 848 if (isdigit(*q)) { 849 got_sig: 850 working->sig = atoi(q); 851 } else { 852 err_sig: 853 errx(1, 854 "illegal signal number in config file:\n%s", 855 errline); 856 } 857 if (working->sig < 1 || working->sig >= NSIG) 858 goto err_sig; 859 } 860 861 /* 862 * Finish figuring out what pid-file to use (if any) in 863 * later processing if this logfile needs to be rotated. 864 */ 865 if ((working->flags & CE_NOSIGNAL) == CE_NOSIGNAL) { 866 /* 867 * This config-entry specified 'n' for nosignal, 868 * see if it also specified an explicit pid_file. 869 * This would be a pretty pointless combination. 870 */ 871 if (working->pid_file != NULL) { 872 warnx("Ignoring '%s' because flag 'n' was specified in line:\n%s", 873 working->pid_file, errline); 874 free(working->pid_file); 875 working->pid_file = NULL; 876 } 877 } else if (working->pid_file == NULL) { 878 /* 879 * This entry did not specify the 'n' flag, which 880 * means it should signal syslogd unless it had 881 * specified some other pid-file. But we only 882 * try to notify syslog if we are root 883 */ 884 if (needroot) 885 working->pid_file = strdup(_PATH_SYSLOGPID); 886 } 887 888 free(errline); 889 errline = NULL; 890 } 891} 892 893static char * 894missing_field(char *p, char *errline) 895{ 896 897 if (!p || !*p) 898 errx(1, "missing field in config file:\n%s", errline); 899 return (p); 900} 901 902static void 903dotrim(const struct conf_entry *ent, char *log, int numdays, int flags) 904{ 905 char dirpart[MAXPATHLEN], namepart[MAXPATHLEN]; 906 char file1[MAXPATHLEN], file2[MAXPATHLEN]; 907 char zfile1[MAXPATHLEN], zfile2[MAXPATHLEN]; 908 char jfile1[MAXPATHLEN]; 909 char tfile[MAXPATHLEN]; 910 int notified, need_notification, fd, _numdays; 911 struct stat st; 912 pid_t pid; 913 914 if (archtodir) { 915 char *p; 916 917 /* build complete name of archive directory into dirpart */ 918 if (*archdirname == '/') { /* absolute */ 919 strlcpy(dirpart, archdirname, sizeof(dirpart)); 920 } else { /* relative */ 921 /* get directory part of logfile */ 922 strlcpy(dirpart, log, sizeof(dirpart)); 923 if ((p = rindex(dirpart, '/')) == NULL) 924 dirpart[0] = '\0'; 925 else 926 *(p + 1) = '\0'; 927 strlcat(dirpart, archdirname, sizeof(dirpart)); 928 } 929 930 /* check if archive directory exists, if not, create it */ 931 if (lstat(dirpart, &st)) 932 createdir(dirpart); 933 934 /* get filename part of logfile */ 935 if ((p = rindex(log, '/')) == NULL) 936 strlcpy(namepart, log, sizeof(namepart)); 937 else 938 strlcpy(namepart, p + 1, sizeof(namepart)); 939 940 /* name of oldest log */ 941 (void) snprintf(file1, sizeof(file1), "%s/%s.%d", dirpart, 942 namepart, numdays); 943 (void) snprintf(zfile1, sizeof(zfile1), "%s%s", file1, 944 COMPRESS_POSTFIX); 945 snprintf(jfile1, sizeof(jfile1), "%s%s", file1, 946 BZCOMPRESS_POSTFIX); 947 } else { 948 /* name of oldest log */ 949 (void) snprintf(file1, sizeof(file1), "%s.%d", log, numdays); 950 (void) snprintf(zfile1, sizeof(zfile1), "%s%s", file1, 951 COMPRESS_POSTFIX); 952 snprintf(jfile1, sizeof(jfile1), "%s%s", file1, 953 BZCOMPRESS_POSTFIX); 954 } 955 956 if (noaction) { 957 printf("rm -f %s\n", file1); 958 printf("rm -f %s\n", zfile1); 959 printf("rm -f %s\n", jfile1); 960 } else { 961 (void) unlink(file1); 962 (void) unlink(zfile1); 963 (void) unlink(jfile1); 964 } 965 966 /* Move down log files */ 967 _numdays = numdays; /* preserve */ 968 while (numdays--) { 969 970 (void) strlcpy(file2, file1, sizeof(file2)); 971 972 if (archtodir) 973 (void) snprintf(file1, sizeof(file1), "%s/%s.%d", 974 dirpart, namepart, numdays); 975 else 976 (void) snprintf(file1, sizeof(file1), "%s.%d", log, 977 numdays); 978 979 (void) strlcpy(zfile1, file1, sizeof(zfile1)); 980 (void) strlcpy(zfile2, file2, sizeof(zfile2)); 981 if (lstat(file1, &st)) { 982 (void) strlcat(zfile1, COMPRESS_POSTFIX, 983 sizeof(zfile1)); 984 (void) strlcat(zfile2, COMPRESS_POSTFIX, 985 sizeof(zfile2)); 986 if (lstat(zfile1, &st)) { 987 strlcpy(zfile1, file1, sizeof(zfile1)); 988 strlcpy(zfile2, file2, sizeof(zfile2)); 989 strlcat(zfile1, BZCOMPRESS_POSTFIX, 990 sizeof(zfile1)); 991 strlcat(zfile2, BZCOMPRESS_POSTFIX, 992 sizeof(zfile2)); 993 if (lstat(zfile1, &st)) 994 continue; 995 } 996 } 997 if (noaction) { 998 printf("mv %s %s\n", zfile1, zfile2); 999 printf("chmod %o %s\n", ent->permissions, zfile2); 1000 if (ent->uid != (uid_t)-1 || ent->gid != (gid_t)-1) 1001 printf("chown %u:%u %s\n", 1002 ent->uid, ent->gid, zfile2); 1003 } else { 1004 (void) rename(zfile1, zfile2); 1005 if (chmod(zfile2, ent->permissions)) 1006 warn("can't chmod %s", file2); 1007 if (ent->uid != (uid_t)-1 || ent->gid != (gid_t)-1) 1008 if (chown(zfile2, ent->uid, ent->gid)) 1009 warn("can't chown %s", zfile2); 1010 } 1011 } 1012 if (!noaction && !(flags & CE_BINARY)) { 1013 /* Report the trimming to the old log */ 1014 (void) log_trim(log, ent); 1015 } 1016 1017 if (!_numdays) { 1018 if (noaction) 1019 printf("rm %s\n", log); 1020 else 1021 (void) unlink(log); 1022 } else { 1023 if (noaction) 1024 printf("mv %s to %s\n", log, file1); 1025 else { 1026 if (archtodir) 1027 movefile(log, file1, ent->permissions, ent->uid, 1028 ent->gid); 1029 else 1030 (void) rename(log, file1); 1031 } 1032 } 1033 1034 /* Now move the new log file into place */ 1035 if (noaction) 1036 printf("Start new log...\n"); 1037 else { 1038 strlcpy(tfile, log, sizeof(tfile)); 1039 strlcat(tfile, ".XXXXXX", sizeof(tfile)); 1040 mkstemp(tfile); 1041 fd = creat(tfile, ent->permissions); 1042 if (fd < 0) 1043 err(1, "can't start new log"); 1044 if (ent->uid != (uid_t)-1 || ent->gid != (gid_t)-1) 1045 if (fchown(fd, ent->uid, ent->gid)) 1046 err(1, "can't chown new log file"); 1047 (void) close(fd); 1048 if (!(flags & CE_BINARY)) { 1049 /* Add status message to new log file */ 1050 if (log_trim(tfile, ent)) 1051 err(1, "can't add status message to log"); 1052 } 1053 } 1054 if (noaction) 1055 printf("chmod %o %s...\n", ent->permissions, log); 1056 else { 1057 (void) chmod(tfile, ent->permissions); 1058 if (rename(tfile, log) < 0) { 1059 err(1, "can't start new log"); 1060 (void) unlink(tfile); 1061 } 1062 } 1063 1064 /* 1065 * Find out if there is a process to signal. If nosignal (-s) was 1066 * specified, then do not signal any process. Note that nosignal 1067 * will trigger a warning message if the rotated logfile needs to 1068 * be compressed, *unless* -R was specified. This is because there 1069 * presumably still are process(es) writing to the old logfile, but 1070 * we assume that a -sR request comes from a process which writes 1071 * to the logfile, and as such, that process has already made sure 1072 * that the logfile is not presently in use. 1073 */ 1074 pid = 0; 1075 need_notification = notified = 0; 1076 if (ent->pid_file != NULL) { 1077 need_notification = 1; 1078 if (!nosignal) 1079 pid = get_pid(ent->pid_file); /* the normal case! */ 1080 else if (rotatereq) 1081 need_notification = 0; 1082 } 1083 if (pid) { 1084 if (noaction) { 1085 notified = 1; 1086 printf("kill -%d %d\n", ent->sig, (int) pid); 1087 } else if (kill(pid, ent->sig)) 1088 warn("can't notify daemon, pid %d", (int) pid); 1089 else { 1090 notified = 1; 1091 if (verbose) 1092 printf("daemon pid %d notified\n", (int) pid); 1093 } 1094 } 1095 if ((flags & CE_COMPACT) || (flags & CE_BZCOMPACT)) { 1096 if (need_notification && !notified) 1097 warnx( 1098 "log %s not compressed because daemon not notified", 1099 log); 1100 else if (noaction) 1101 printf("Compress %s.0\n", log); 1102 else { 1103 if (notified) { 1104 if (verbose) 1105 printf("small pause to allow daemon to close log\n"); 1106 sleep(10); 1107 } 1108 if (archtodir) { 1109 (void) snprintf(file1, sizeof(file1), "%s/%s", 1110 dirpart, namepart); 1111 if (flags & CE_COMPACT) 1112 compress_log(file1, 1113 flags & CE_COMPACTWAIT); 1114 else if (flags & CE_BZCOMPACT) 1115 bzcompress_log(file1, 1116 flags & CE_COMPACTWAIT); 1117 } else { 1118 if (flags & CE_COMPACT) 1119 compress_log(log, 1120 flags & CE_COMPACTWAIT); 1121 else if (flags & CE_BZCOMPACT) 1122 bzcompress_log(log, 1123 flags & CE_COMPACTWAIT); 1124 } 1125 } 1126 } 1127} 1128 1129/* Log the fact that the logs were turned over */ 1130static int 1131log_trim(const char *log, const struct conf_entry *log_ent) 1132{ 1133 FILE *f; 1134 const char *xtra; 1135 1136 if ((f = fopen(log, "a")) == NULL) 1137 return (-1); 1138 xtra = ""; 1139 if (log_ent->def_cfg) 1140 xtra = " using <default> rule"; 1141 if (log_ent->r_reason != NULL) 1142 fprintf(f, "%s %s newsyslog[%d]: logfile turned over%s%s\n", 1143 daytime, hostname, (int) getpid(), log_ent->r_reason, xtra); 1144 else 1145 fprintf(f, "%s %s newsyslog[%d]: logfile turned over%s\n", 1146 daytime, hostname, (int) getpid(), xtra); 1147 if (fclose(f) == EOF) 1148 err(1, "log_trim: fclose:"); 1149 return (0); 1150} 1151 1152/* Fork of gzip to compress the old log file */ 1153static void 1154compress_log(char *log, int dowait) 1155{ 1156 pid_t pid; 1157 char tmp[MAXPATHLEN]; 1158 1159 while (dowait && (wait(NULL) > 0 || errno == EINTR)) 1160 ; 1161 (void) snprintf(tmp, sizeof(tmp), "%s.0", log); 1162 pid = fork(); 1163 if (pid < 0) 1164 err(1, "gzip fork"); 1165 else if (!pid) { 1166 (void) execl(_PATH_GZIP, _PATH_GZIP, "-f", tmp, (char *)0); 1167 err(1, _PATH_GZIP); 1168 } 1169} 1170 1171/* Fork of bzip2 to compress the old log file */ 1172static void 1173bzcompress_log(char *log, int dowait) 1174{ 1175 pid_t pid; 1176 char tmp[MAXPATHLEN]; 1177 1178 while (dowait && (wait(NULL) > 0 || errno == EINTR)) 1179 ; 1180 snprintf(tmp, sizeof(tmp), "%s.0", log); 1181 pid = fork(); 1182 if (pid < 0) 1183 err(1, "bzip2 fork"); 1184 else if (!pid) { 1185 execl(_PATH_BZIP2, _PATH_BZIP2, "-f", tmp, (char *)0); 1186 err(1, _PATH_BZIP2); 1187 } 1188} 1189 1190/* Return size in kilobytes of a file */ 1191static int 1192sizefile(char *file) 1193{ 1194 struct stat sb; 1195 1196 if (stat(file, &sb) < 0) 1197 return (-1); 1198 return (kbytes(dbtob(sb.st_blocks))); 1199} 1200 1201/* Return the age of old log file (file.0) */ 1202static int 1203age_old_log(char *file) 1204{ 1205 struct stat sb; 1206 char tmp[MAXPATHLEN + sizeof(".0") + sizeof(COMPRESS_POSTFIX) + 1]; 1207 1208 if (archtodir) { 1209 char *p; 1210 1211 /* build name of archive directory into tmp */ 1212 if (*archdirname == '/') { /* absolute */ 1213 strlcpy(tmp, archdirname, sizeof(tmp)); 1214 } else { /* relative */ 1215 /* get directory part of logfile */ 1216 strlcpy(tmp, file, sizeof(tmp)); 1217 if ((p = rindex(tmp, '/')) == NULL) 1218 tmp[0] = '\0'; 1219 else 1220 *(p + 1) = '\0'; 1221 strlcat(tmp, archdirname, sizeof(tmp)); 1222 } 1223 1224 strlcat(tmp, "/", sizeof(tmp)); 1225 1226 /* get filename part of logfile */ 1227 if ((p = rindex(file, '/')) == NULL) 1228 strlcat(tmp, file, sizeof(tmp)); 1229 else 1230 strlcat(tmp, p + 1, sizeof(tmp)); 1231 } else { 1232 (void) strlcpy(tmp, file, sizeof(tmp)); 1233 } 1234 1235 if (stat(strcat(tmp, ".0"), &sb) < 0) 1236 if (stat(strcat(tmp, COMPRESS_POSTFIX), &sb) < 0) 1237 return (-1); 1238 return ((int)(timenow - sb.st_mtime + 1800) / 3600); 1239} 1240 1241static pid_t 1242get_pid(const char *pid_file) 1243{ 1244 FILE *f; 1245 char line[BUFSIZ]; 1246 pid_t pid = 0; 1247 1248 if ((f = fopen(pid_file, "r")) == NULL) 1249 warn("can't open %s pid file to restart a daemon", 1250 pid_file); 1251 else { 1252 if (fgets(line, BUFSIZ, f)) { 1253 pid = atol(line); 1254 if (pid < MIN_PID || pid > MAX_PID) { 1255 warnx("preposterous process number: %d", 1256 (int)pid); 1257 pid = 0; 1258 } 1259 } else 1260 warn("can't read %s pid file to restart a daemon", 1261 pid_file); 1262 (void) fclose(f); 1263 } 1264 return (pid); 1265} 1266 1267/* Skip Over Blanks */ 1268static char * 1269sob(char *p) 1270{ 1271 while (p && *p && isspace(*p)) 1272 p++; 1273 return (p); 1274} 1275 1276/* Skip Over Non-Blanks */ 1277static char * 1278son(char *p) 1279{ 1280 while (p && *p && !isspace(*p)) 1281 p++; 1282 return (p); 1283} 1284 1285/* 1286 * Parse a limited subset of ISO 8601. The specific format is as follows: 1287 * 1288 * [CC[YY[MM[DD]]]][THH[MM[SS]]] (where `T' is the literal letter) 1289 * 1290 * We don't accept a timezone specification; missing fields (including timezone) 1291 * are defaulted to the current date but time zero. 1292 */ 1293static time_t 1294parse8601(char *s, char *errline) 1295{ 1296 char *t; 1297 time_t tsecs; 1298 struct tm tm, *tmp; 1299 u_long ul; 1300 1301 tmp = localtime(&timenow); 1302 tm = *tmp; 1303 1304 tm.tm_hour = tm.tm_min = tm.tm_sec = 0; 1305 1306 ul = strtoul(s, &t, 10); 1307 if (*t != '\0' && *t != 'T') 1308 return (-1); 1309 1310 /* 1311 * Now t points either to the end of the string (if no time was 1312 * provided) or to the letter `T' which separates date and time in 1313 * ISO 8601. The pointer arithmetic is the same for either case. 1314 */ 1315 switch (t - s) { 1316 case 8: 1317 tm.tm_year = ((ul / 1000000) - 19) * 100; 1318 ul = ul % 1000000; 1319 case 6: 1320 tm.tm_year -= tm.tm_year % 100; 1321 tm.tm_year += ul / 10000; 1322 ul = ul % 10000; 1323 case 4: 1324 tm.tm_mon = (ul / 100) - 1; 1325 ul = ul % 100; 1326 case 2: 1327 tm.tm_mday = ul; 1328 case 0: 1329 break; 1330 default: 1331 return (-1); 1332 } 1333 1334 /* sanity check */ 1335 if (tm.tm_year < 70 || tm.tm_mon < 0 || tm.tm_mon > 12 1336 || tm.tm_mday < 1 || tm.tm_mday > 31) 1337 return (-1); 1338 1339 if (*t != '\0') { 1340 s = ++t; 1341 ul = strtoul(s, &t, 10); 1342 if (*t != '\0' && !isspace(*t)) 1343 return (-1); 1344 1345 switch (t - s) { 1346 case 6: 1347 tm.tm_sec = ul % 100; 1348 ul /= 100; 1349 case 4: 1350 tm.tm_min = ul % 100; 1351 ul /= 100; 1352 case 2: 1353 tm.tm_hour = ul; 1354 case 0: 1355 break; 1356 default: 1357 return (-1); 1358 } 1359 1360 /* sanity check */ 1361 if (tm.tm_sec < 0 || tm.tm_sec > 60 || tm.tm_min < 0 1362 || tm.tm_min > 59 || tm.tm_hour < 0 || tm.tm_hour > 23) 1363 return (-1); 1364 } 1365 if ((tsecs = mktime(&tm)) == -1) 1366 errx(1, "nonexistent time:\n%s", errline); 1367 return (tsecs); 1368} 1369 1370/* physically move file */ 1371static void 1372movefile(char *from, char *to, int perm, uid_t owner_uid, gid_t group_gid) 1373{ 1374 FILE *src, *dst; 1375 int c; 1376 1377 if ((src = fopen(from, "r")) == NULL) 1378 err(1, "can't fopen %s for reading", from); 1379 if ((dst = fopen(to, "w")) == NULL) 1380 err(1, "can't fopen %s for writing", to); 1381 if (owner_uid != (uid_t)-1 || group_gid != (gid_t)-1) { 1382 if (fchown(fileno(dst), owner_uid, group_gid)) 1383 err(1, "can't fchown %s", to); 1384 } 1385 if (fchmod(fileno(dst), perm)) 1386 err(1, "can't fchmod %s", to); 1387 1388 while ((c = getc(src)) != EOF) { 1389 if ((putc(c, dst)) == EOF) 1390 err(1, "error writing to %s", to); 1391 } 1392 1393 if (ferror(src)) 1394 err(1, "error reading from %s", from); 1395 if ((fclose(src)) != 0) 1396 err(1, "can't fclose %s", to); 1397 if ((fclose(dst)) != 0) 1398 err(1, "can't fclose %s", from); 1399 if ((unlink(from)) != 0) 1400 err(1, "can't unlink %s", from); 1401} 1402 1403/* create one or more directory components of a path */ 1404static void 1405createdir(char *dirpart) 1406{ 1407 int res; 1408 char *s, *d; 1409 char mkdirpath[MAXPATHLEN]; 1410 struct stat st; 1411 1412 s = dirpart; 1413 d = mkdirpath; 1414 1415 for (;;) { 1416 *d++ = *s++; 1417 if (*s != '/' && *s != '\0') 1418 continue; 1419 *d = '\0'; 1420 res = lstat(mkdirpath, &st); 1421 if (res != 0) { 1422 if (noaction) { 1423 printf("mkdir %s\n", mkdirpath); 1424 } else { 1425 res = mkdir(mkdirpath, 0755); 1426 if (res != 0) 1427 err(1, "Error on mkdir(\"%s\") for -a", 1428 mkdirpath); 1429 } 1430 } 1431 if (*s == '\0') 1432 break; 1433 } 1434 if (verbose) 1435 printf("created directory '%s' for -a\n", dirpart); 1436} 1437 1438/*- 1439 * Parse a cyclic time specification, the format is as follows: 1440 * 1441 * [Dhh] or [Wd[Dhh]] or [Mdd[Dhh]] 1442 * 1443 * to rotate a logfile cyclic at 1444 * 1445 * - every day (D) within a specific hour (hh) (hh = 0...23) 1446 * - once a week (W) at a specific day (d) OR (d = 0..6, 0 = Sunday) 1447 * - once a month (M) at a specific day (d) (d = 1..31,l|L) 1448 * 1449 * We don't accept a timezone specification; missing fields 1450 * are defaulted to the current date but time zero. 1451 */ 1452static time_t 1453parseDWM(char *s, char *errline) 1454{ 1455 char *t; 1456 time_t tsecs; 1457 struct tm tm, *tmp; 1458 long l; 1459 int nd; 1460 static int mtab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; 1461 int WMseen = 0; 1462 int Dseen = 0; 1463 1464 tmp = localtime(&timenow); 1465 tm = *tmp; 1466 1467 /* set no. of days per month */ 1468 1469 nd = mtab[tm.tm_mon]; 1470 1471 if (tm.tm_mon == 1) { 1472 if (((tm.tm_year + 1900) % 4 == 0) && 1473 ((tm.tm_year + 1900) % 100 != 0) && 1474 ((tm.tm_year + 1900) % 400 == 0)) { 1475 nd++; /* leap year, 29 days in february */ 1476 } 1477 } 1478 tm.tm_hour = tm.tm_min = tm.tm_sec = 0; 1479 1480 for (;;) { 1481 switch (*s) { 1482 case 'D': 1483 if (Dseen) 1484 return (-1); 1485 Dseen++; 1486 s++; 1487 l = strtol(s, &t, 10); 1488 if (l < 0 || l > 23) 1489 return (-1); 1490 tm.tm_hour = l; 1491 break; 1492 1493 case 'W': 1494 if (WMseen) 1495 return (-1); 1496 WMseen++; 1497 s++; 1498 l = strtol(s, &t, 10); 1499 if (l < 0 || l > 6) 1500 return (-1); 1501 if (l != tm.tm_wday) { 1502 int save; 1503 1504 if (l < tm.tm_wday) { 1505 save = 6 - tm.tm_wday; 1506 save += (l + 1); 1507 } else { 1508 save = l - tm.tm_wday; 1509 } 1510 1511 tm.tm_mday += save; 1512 1513 if (tm.tm_mday > nd) { 1514 tm.tm_mon++; 1515 tm.tm_mday = tm.tm_mday - nd; 1516 } 1517 } 1518 break; 1519 1520 case 'M': 1521 if (WMseen) 1522 return (-1); 1523 WMseen++; 1524 s++; 1525 if (tolower(*s) == 'l') { 1526 tm.tm_mday = nd; 1527 s++; 1528 t = s; 1529 } else { 1530 l = strtol(s, &t, 10); 1531 if (l < 1 || l > 31) 1532 return (-1); 1533 1534 if (l > nd) 1535 return (-1); 1536 tm.tm_mday = l; 1537 } 1538 break; 1539 1540 default: 1541 return (-1); 1542 break; 1543 } 1544 1545 if (*t == '\0' || isspace(*t)) 1546 break; 1547 else 1548 s = t; 1549 } 1550 if ((tsecs = mktime(&tm)) == -1) 1551 errx(1, "nonexistent time:\n%s", errline); 1552 return (tsecs); 1553} 1554