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