newsyslog.c revision 111772
11844Swollman/* 250476Speter * This file contains changes from the Open Software Foundation. 31844Swollman */ 41638Srgrimes 594940Sru/* 61638Srgrimes * Copyright 1988, 1989 by the Massachusetts Institute of Technology 742915Sjdp * 842915Sjdp * Permission to use, copy, modify, and distribute this software and its 942915Sjdp * documentation for any purpose and without fee is hereby granted, provided 1042915Sjdp * that the above copyright notice appear in all copies and that both that 11139106Sru * copyright notice and this permission notice appear in supporting 1242915Sjdp * documentation, and that the names of M.I.T. and the M.I.T. S.I.P.B. not be 1342915Sjdp * used in advertising or publicity pertaining to distribution of the 1442915Sjdp * software without specific, written prior permission. M.I.T. and the M.I.T. 15129024Sdes * S.I.P.B. make no representations about the suitability of this software 16129024Sdes * for any purpose. It is provided "as is" without express or implied 1729141Speter * warranty. 18129024Sdes * 19129024Sdes */ 20129024Sdes 21125119Sru/* 22100332Sru * newsyslog - roll over selected logs at the appropriate time, keeping the a 23100332Sru * specified number of backup files around. 2442915Sjdp */ 2542915Sjdp 2629141Speter#ifndef lint 27119607Srustatic const char rcsid[] = 28117034Sgordon"$FreeBSD: head/usr.sbin/newsyslog/newsyslog.c 111772 2003-03-02 23:23:11Z gad $"; 29119607Sru#endif /* not lint */ 30117034Sgordon 312827Sjkh#define OSF 322827Sjkh#ifndef COMPRESS_POSTFIX 332827Sjkh#define COMPRESS_POSTFIX ".gz" 342827Sjkh#endif 352827Sjkh#ifndef BZCOMPRESS_POSTFIX 361638Srgrimes#define BZCOMPRESS_POSTFIX ".bz2" 372827Sjkh#endif 381638Srgrimes 3918529Sbde#include <sys/param.h> 4018529Sbde#include <sys/stat.h> 411638Srgrimes#include <sys/wait.h> 4242450Sjdp 431638Srgrimes#include <ctype.h> 44117173Sru#include <err.h> 451638Srgrimes#include <errno.h> 4696512Sru#include <fcntl.h> 4796512Sru#include <glob.h> 4896512Sru#include <grp.h> 4996512Sru#include <paths.h> 5096512Sru#include <pwd.h> 5196512Sru#include <signal.h> 5296512Sru#include <stdio.h> 5396512Sru#include <stdlib.h> 54126890Strhodes#include <string.h> 55126890Strhodes#include <time.h> 56126890Strhodes#include <unistd.h> 57126890Strhodes 58126890Strhodes#include "pathnames.h" 59126890Strhodes 601638Srgrimes#define kbytes(size) (((size) + 1023) >> 10) 61126890Strhodes 621638Srgrimes#ifdef _IBMR2 6342450Sjdp/* Calculates (db * DEV_BSIZE) */ 641844Swollman#define dbtob(db) ((unsigned)(db) << UBSHIFT) 651844Swollman#endif 6636673Sdt 67126890Strhodes/* 681844Swollman * Bit-values for the 'flags' parsed from a config-file entry. 6942450Sjdp */ 701844Swollman#define CE_COMPACT 0x0001 /* Compact the achived log files with gzip. */ 711844Swollman#define CE_BZCOMPACT 0x0002 /* Compact the achived log files with bzip2. */ 721844Swollman#define CE_COMPACTWAIT 0x0004 /* wait until compressing one file finishes */ 73127027Strhodes /* before starting the next step. */ 741844Swollman#define CE_BINARY 0x0008 /* Logfile is in binary, do not add status */ 7542450Sjdp /* messages to logfile(s) when rotating. */ 761844Swollman#define CE_NOSIGNAL 0x0010 /* There is no process to signal when */ 771844Swollman /* trimming this file. */ 7836054Sbde#define CE_TRIMAT 0x0020 /* trim file at a specific time. */ 7936054Sbde#define CE_GLOB 0x0040 /* name of the log is file name pattern. */ 8036054Sbde 8142450Sjdp#define NONE -1 8236054Sbde 8336054Sbdestruct conf_entry { 84117173Sru char *log; /* Name of the log */ 85117159Sru char *pid_file; /* PID file */ 861638Srgrimes char *r_reason; /* The reason this file is being rotated */ 87117173Sru int rotate; /* Non-zero if this file should be rotated */ 88117173Sru int uid; /* Owner of log */ 89117173Sru int gid; /* Group of log */ 90117173Sru int numlogs; /* Number of logs to keep */ 91117173Sru int size; /* Size cutoff to trigger trimming the log */ 92117173Sru int hours; /* Hours between log trimming */ 93117173Sru time_t trim_at; /* Specific time to do trimming */ 941844Swollman int permissions; /* File permissions on the log */ 95117122Sru int flags; /* CE_COMPACT, CE_BZCOMPACT, CE_BINARY */ 961844Swollman int sig; /* Signal to send */ 9742450Sjdp int def_cfg; /* Using the <default> rule for this file */ 98117122Sru struct conf_entry *next;/* Linked list pointer */ 991844Swollman}; 10096512Sru 1011638Srgrimes#define DEFAULT_MARKER "<default>" 102156772Sdeischen 103156772Sdeischenint archtodir = 0; /* Archive old logfiles to other directory */ 104156772Sdeischenint verbose = 0; /* Print out what's going on */ 105156772Sdeischenint needroot = 1; /* Root privs are necessary */ 106156772Sdeischenint noaction = 0; /* Don't do anything, just show it */ 107156772Sdeischenint nosignal; /* Do not send any signals */ 108156772Sdeischenint force = 0; /* Force the trim no matter what */ 109156772Sdeischenint rotatereq = 0; /* -R = Always rotate the file(s) as given */ 110156772Sdeischen /* on the command (this also requires */ 111156772Sdeischen /* that a list of files *are* given on */ 112156772Sdeischen /* the run command). */ 113156772Sdeischenchar *requestor; /* The name given on a -R request */ 114156772Sdeischenchar *archdirname; /* Directory path to old logfiles archive */ 115156772Sdeischenconst char *conf = _PATH_CONF; /* Configuration file to use */ 116156772Sdeischentime_t timenow; 117156772Sdeischen 118156772Sdeischen#define MIN_PID 5 119156772Sdeischen#define MAX_PID 99999 /* was lower, see /usr/include/sys/proc.h */ 120156772Sdeischenchar hostname[MAXHOSTNAMELEN]; /* hostname */ 121156772Sdeischenchar daytime[16]; /* timenow in human readable form */ 122156772Sdeischen 123156772Sdeischenstatic struct conf_entry *parse_file(char **files); 124156772Sdeischenstatic char *sob(char *p); 125156772Sdeischenstatic char *son(char *p); 126156772Sdeischenstatic char *missing_field(char *p, char *errline); 127156772Sdeischenstatic void do_entry(struct conf_entry * ent); 128156772Sdeischenstatic void free_entry(struct conf_entry *ent); 129156772Sdeischenstatic struct conf_entry *init_entry(const char *fname, 130156772Sdeischen struct conf_entry *src_entry); 131156772Sdeischenstatic void PRS(int argc, char **argv); 132156772Sdeischenstatic void usage(void); 133156772Sdeischenstatic void dotrim(const struct conf_entry *trim_ent, char *log, 134156772Sdeischen int numdays, int flags, int perm, int owner_uid, 135156772Sdeischen int group_gid, int sig); 136156772Sdeischenstatic int log_trim(const char *log, const struct conf_entry *log_ent); 137156772Sdeischenstatic void compress_log(char *log, int dowait); 138156772Sdeischenstatic void bzcompress_log(char *log, int dowait); 139156772Sdeischenstatic int sizefile(char *file); 140156772Sdeischenstatic int age_old_log(char *file); 141156772Sdeischenstatic pid_t get_pid(const char *pid_file); 14299362Srustatic time_t parse8601(char *s, char *errline); 14399362Srustatic void movefile(char *from, char *to, int perm, int owner_uid, 14499362Sru int group_gid); 14599362Srustatic void createdir(char *dirpart); 14696512Srustatic time_t parseDWM(char *s, char *errline); 14796512Sru 1481638Srgrimes/* 14996512Sru * All the following are defined to work on an 'int', in the 15096512Sru * range 0 to 255, plus EOF. Define wrappers which can take 15196512Sru * values of type 'char', either signed or unsigned. 15296512Sru */ 15396512Sru#define isprintch(Anychar) isprint(((int) Anychar) & 255) 15499362Sru#define isspacech(Anychar) isspace(((int) Anychar) & 255) 1551638Srgrimes#define tolowerch(Anychar) tolower(((int) Anychar) & 255) 15696512Sru 15795114Sobrienint 158156854Srumain(int argc, char **argv) 15996512Sru{ 16096512Sru struct conf_entry *p, *q; 16195306Sru char *savglob; 16296512Sru glob_t pglob; 16396512Sru int i; 16496512Sru 16596512Sru PRS(argc, argv); 16696512Sru if (needroot && getuid() && geteuid()) 16774805Sru errx(1, "must have root privs"); 1681844Swollman p = q = parse_file(argv + optind); 16999362Sru 17099362Sru while (p) { 17196512Sru if ((p->flags & CE_GLOB) == 0) { 17299362Sru do_entry(p); 1731844Swollman } else { 17496512Sru if (glob(p->log, GLOB_NOCHECK, NULL, &pglob) != 0) { 17596512Sru warn("can't expand pattern: %s", p->log); 1761638Srgrimes } else { 17742915Sjdp savglob = p->log; 17842915Sjdp for (i = 0; i < pglob.gl_matchc; i++) { 17996512Sru p->log = pglob.gl_pathv[i]; 18042915Sjdp do_entry(p); 18196512Sru } 18242915Sjdp globfree(&pglob); 18396343Sobrien p->log = savglob; 18496512Sru } 18591011Sru } 18628945Speter p = p->next; 1871844Swollman free_entry(q); 188156813Sru q = p; 18996512Sru } 19096512Sru while (wait(NULL) > 0 || errno == EINTR) 19196512Sru ; 1922353Sbde return (0); 19396512Sru} 19496512Sru 19596512Srustatic struct conf_entry * 1963859Sbdeinit_entry(const char *fname, struct conf_entry *src_entry) 1971844Swollman{ 198139106Sru struct conf_entry *tempwork; 19996512Sru 20096512Sru if (verbose > 4) 20196512Sru printf("\t--> [creating entry for %s]\n", fname); 20296512Sru 20392491Smarkm tempwork = malloc(sizeof(struct conf_entry)); 20496512Sru if (tempwork == NULL) 20596512Sru err(1, "malloc of conf_entry for %s", fname); 20692491Smarkm 20792491Smarkm tempwork->log = strdup(fname); 2081638Srgrimes if (tempwork->log == NULL) 209144893Sharti err(1, "strdup for %s", fname); 21096512Sru 21196512Sru if (src_entry != NULL) { 21296512Sru tempwork->pid_file = NULL; 213156813Sru if (src_entry->pid_file) 21496512Sru tempwork->pid_file = strdup(src_entry->pid_file); 2151638Srgrimes tempwork->r_reason = NULL; 2161638Srgrimes tempwork->rotate = 0; 21734179Sbde tempwork->uid = src_entry->uid; 21824750Sbde tempwork->gid = src_entry->gid; 21942450Sjdp tempwork->numlogs = src_entry->numlogs; 22024750Sbde tempwork->size = src_entry->size; 22124750Sbde tempwork->hours = src_entry->hours; 222139107Sru tempwork->trim_at = src_entry->trim_at; 22331809Sbde tempwork->permissions = src_entry->permissions; 22442915Sjdp tempwork->flags = src_entry->flags; 22527910Sasami tempwork->sig = src_entry->sig; 22628945Speter tempwork->def_cfg = src_entry->def_cfg; 2271638Srgrimes } else { 2281638Srgrimes /* Initialize as a "do-nothing" entry */ 2291638Srgrimes tempwork->pid_file = NULL; 230136019Sru tempwork->r_reason = NULL; 231139111Sru tempwork->rotate = 0; 2322298Swollman tempwork->uid = NONE; 2332298Swollman tempwork->gid = NONE; 234136019Sru tempwork->numlogs = 1; 235136019Sru tempwork->size = -1; 2362298Swollman tempwork->hours = -1; 23749328Shoek tempwork->trim_at = (time_t)0; 23849328Shoek tempwork->permissions = 0; 23949328Shoek tempwork->flags = 0; 24049328Shoek tempwork->sig = SIGHUP; 24156971Sru tempwork->def_cfg = 0; 24249328Shoek } 24349328Shoek tempwork->next = NULL; 24449328Shoek 24549328Shoek return (tempwork); 24699362Sru} 24795306Sru 24899343Srustatic void 24995306Srufree_entry(struct conf_entry *ent) 250139110Sru{ 25192980Sdes 25249328Shoek if (ent == NULL) 25396512Sru return; 254156854Sru 25592980Sdes if (ent->log != NULL) { 25649328Shoek if (verbose > 4) 2571638Srgrimes printf("\t--> [freeing entry for %s]\n", ent->log); 258116144Sobrien free(ent->log); 259100872Sru ent->log = NULL; 26049328Shoek } 26142915Sjdp 26242915Sjdp if (ent->pid_file != NULL) { 263119846Sru free(ent->pid_file); 264119846Sru ent->pid_file = NULL; 265119846Sru } 266119846Sru 267119730Speter if (ent->r_reason != NULL) { 268119846Sru free(ent->r_reason); 269119846Sru ent->r_reason = NULL; 270119846Sru } 2711844Swollman 27228945Speter free(ent); 273119730Speter} 274119846Sru 275156813Srustatic void 276100872Srudo_entry(struct conf_entry * ent) 27749328Shoek{ 2781844Swollman#define REASON_MAX 80 279139106Sru int size, modtime; 280100872Sru char temp_reason[REASON_MAX]; 28196462Sru 28296462Sru if (verbose) { 283144893Sharti if (ent->flags & CE_COMPACT) 28496462Sru printf("%s <%dZ>: ", ent->log, ent->numlogs); 285141503Sphantom else if (ent->flags & CE_BZCOMPACT) 28697769Sru printf("%s <%dJ>: ", ent->log, ent->numlogs); 28796668Sru else 28899256Sru printf("%s <%d>: ", ent->log, ent->numlogs); 28996462Sru } 290156813Sru size = sizefile(ent->log); 29196164Sru modtime = age_old_log(ent->log); 29299343Sru ent->rotate = 0; 29396162Sru if (size < 0) { 29496162Sru if (verbose) 2951638Srgrimes printf("does not exist.\n"); 2961638Srgrimes } else { 2971638Srgrimes if (ent->flags & CE_TRIMAT && !force && !rotatereq) { 29895306Sru if (timenow < ent->trim_at 299103713Smarkm || difftime(timenow, ent->trim_at) >= 60 * 60) { 3001638Srgrimes if (verbose) 3011638Srgrimes printf("--> will trim at %s", 302156813Sru ctime(&ent->trim_at)); 3031638Srgrimes return; 30474842Sru } else if (verbose && ent->hours <= 0) { 3051844Swollman printf("--> time is up\n"); 3061844Swollman } 30734092Sbde } 30899362Sru if (verbose && (ent->size > 0)) 30996512Sru printf("size (Kb): %d [%d] ", size, ent->size); 31099362Sru if (verbose && (ent->hours > 0)) 311124637Sru printf(" age (hr): %d [%d] ", modtime, ent->hours); 312124637Sru 313124637Sru /* 31434092Sbde * Figure out if this logfile needs to be rotated. 31599362Sru */ 31699362Sru temp_reason[0] = '\0'; 31799362Sru if (rotatereq) { 318124637Sru ent->rotate = 1; 319124637Sru snprintf(temp_reason, REASON_MAX, " due to -R from %s", 320124637Sru requestor); 32196512Sru } else if (force) { 32299362Sru ent->rotate = 1; 32334092Sbde snprintf(temp_reason, REASON_MAX, " due to -F request"); 324100457Sru } else if ((ent->size > 0) && (size >= ent->size)) { 325100457Sru ent->rotate = 1; 326100457Sru snprintf(temp_reason, REASON_MAX, " due to size>%dK", 327100457Sru ent->size); 328100457Sru } else if (ent->hours <= 0 && (ent->flags & CE_TRIMAT)) { 329100457Sru ent->rotate = 1; 330100457Sru } else if ((ent->hours > 0) && ((modtime >= ent->hours) || 331100457Sru (modtime < 0))) { 332100457Sru ent->rotate = 1; 333156854Sru } 334100457Sru 335100457Sru /* 336100457Sru * If the file needs to be rotated, then rotate it. 337100457Sru */ 338100457Sru if (ent->rotate) { 339100457Sru if (temp_reason[0] != '\0') 340100457Sru ent->r_reason = strdup(temp_reason); 341100457Sru if (verbose) 342100457Sru printf("--> trimming log....\n"); 343100457Sru if (noaction && !verbose) { 344100457Sru if (ent->flags & CE_COMPACT) 345100457Sru printf("%s <%dZ>: trimming\n", 346100457Sru ent->log, ent->numlogs); 347100457Sru else if (ent->flags & CE_BZCOMPACT) 348100457Sru printf("%s <%dJ>: trimming\n", 349100457Sru ent->log, ent->numlogs); 350100457Sru else 351144893Sharti printf("%s <%d>: trimming\n", 352100457Sru ent->log, ent->numlogs); 353100457Sru } 354100457Sru dotrim(ent, ent->log, ent->numlogs, ent->flags, 355100457Sru ent->permissions, ent->uid, ent->gid, ent->sig); 356100457Sru } else { 357100457Sru if (verbose) 358100457Sru printf("--> skipping\n"); 359100457Sru } 36016663Sjkh } 36176861Skris#undef REASON_MAX 36276861Skris} 363 364static void 365PRS(int argc, char **argv) 366{ 367 int c; 368 char *p; 369 370 timenow = time((time_t *) 0); 371 (void)strncpy(daytime, ctime(&timenow) + 4, 15); 372 daytime[15] = '\0'; 373 374 /* Let's get our hostname */ 375 (void) gethostname(hostname, sizeof(hostname)); 376 377 /* Truncate domain */ 378 if ((p = strchr(hostname, '.'))) { 379 *p = '\0'; 380 } 381 382 /* Parse command line options. */ 383 while ((c = getopt(argc, argv, "a:f:nrsvFR:")) != -1) 384 switch (c) { 385 case 'a': 386 archtodir++; 387 archdirname = optarg; 388 break; 389 case 'f': 390 conf = optarg; 391 break; 392 case 'n': 393 noaction++; 394 break; 395 case 'r': 396 needroot = 0; 397 break; 398 case 's': 399 nosignal = 1; 400 break; 401 case 'v': 402 verbose++; 403 break; 404 case 'F': 405 force++; 406 break; 407 case 'R': 408 rotatereq++; 409 requestor = strdup(optarg); 410 break; 411 default: 412 usage(); 413 /* NOTREACHED */ 414 } 415 416 if (rotatereq) { 417 if (optind == argc) { 418 warnx("At least one filename must be given when -R is specified."); 419 usage(); 420 /* NOTREACHED */ 421 } 422 /* Make sure "requestor" value is safe for a syslog message. */ 423 for (p = requestor; *p != '\0'; p++) { 424 if (!isprintch(*p) && (*p != '\t')) 425 *p = '.'; 426 } 427 } 428} 429 430static void 431usage(void) 432{ 433 434 fprintf(stderr, 435 "usage: newsyslog [-Fnrsv] [-a directory] [-f config-file]\n" 436 " [ [-R requestor] filename ... ]\n"); 437 exit(1); 438} 439 440/* 441 * Parse a configuration file and return a linked list of all the logs to 442 * process 443 */ 444static struct conf_entry * 445parse_file(char **files) 446{ 447 FILE *f; 448 char line[BUFSIZ], *parse, *q; 449 char *cp, *errline, *group; 450 char **given; 451 struct conf_entry *defconf, *first, *working, *worklist; 452 struct passwd *pass; 453 struct group *grp; 454 int eol; 455 456 defconf = first = working = worklist = NULL; 457 458 if (strcmp(conf, "-")) 459 f = fopen(conf, "r"); 460 else 461 f = stdin; 462 if (!f) 463 err(1, "%s", conf); 464 while (fgets(line, BUFSIZ, f)) { 465 if ((line[0] == '\n') || (line[0] == '#') || 466 (strlen(line) == 0)) 467 continue; 468 errline = strdup(line); 469 for (cp = line + 1; *cp != '\0'; cp++) { 470 if (*cp != '#') 471 continue; 472 if (*(cp - 1) == '\\') { 473 strcpy(cp - 1, cp); 474 cp--; 475 continue; 476 } 477 *cp = '\0'; 478 break; 479 } 480 481 q = parse = missing_field(sob(line), errline); 482 parse = son(line); 483 if (!*parse) 484 errx(1, "malformed line (missing fields):\n%s", 485 errline); 486 *parse = '\0'; 487 488 /* 489 * If newsyslog was run with a list of specific filenames, 490 * then this line of the config file should be skipped if 491 * it is NOT one of those given files (except that we do 492 * want any line that defines the <default> action). 493 * 494 * XXX - note that CE_GLOB processing is *NOT* done when 495 * trying to match a filename given on the command! 496 */ 497 if (*files) { 498 if (strcasecmp(DEFAULT_MARKER, q) != 0) { 499 for (given = files; *given; ++given) { 500 if (strcmp(*given, q) == 0) 501 break; 502 } 503 if (!*given) 504 continue; 505 } 506 if (verbose > 2) 507 printf("\t+ Matched entry %s\n", q); 508 } else { 509 /* 510 * If no files were specified on the command line, 511 * then we can skip any line which defines the 512 * default action. 513 */ 514 if (strcasecmp(DEFAULT_MARKER, q) == 0) { 515 if (verbose > 2) 516 printf("\t+ Ignoring entry for %s\n", 517 q); 518 continue; 519 } 520 } 521 522 working = init_entry(q, NULL); 523 if (strcasecmp(DEFAULT_MARKER, q) == 0) { 524 if (defconf != NULL) { 525 warnx("Ignoring duplicate entry for %s!", q); 526 free_entry(working); 527 continue; 528 } 529 defconf = working; 530 } else { 531 if (!first) 532 first = working; 533 else 534 worklist->next = working; 535 worklist = working; 536 } 537 538 q = parse = missing_field(sob(++parse), errline); 539 parse = son(parse); 540 if (!*parse) 541 errx(1, "malformed line (missing fields):\n%s", 542 errline); 543 *parse = '\0'; 544 if ((group = strchr(q, ':')) != NULL || 545 (group = strrchr(q, '.')) != NULL) { 546 *group++ = '\0'; 547 if (*q) { 548 if (!(isnumber(*q))) { 549 if ((pass = getpwnam(q)) == NULL) 550 errx(1, 551 "error in config file; unknown user:\n%s", 552 errline); 553 working->uid = pass->pw_uid; 554 } else 555 working->uid = atoi(q); 556 } else 557 working->uid = NONE; 558 559 q = group; 560 if (*q) { 561 if (!(isnumber(*q))) { 562 if ((grp = getgrnam(q)) == NULL) 563 errx(1, 564 "error in config file; unknown group:\n%s", 565 errline); 566 working->gid = grp->gr_gid; 567 } else 568 working->gid = atoi(q); 569 } else 570 working->gid = NONE; 571 572 q = parse = missing_field(sob(++parse), errline); 573 parse = son(parse); 574 if (!*parse) 575 errx(1, "malformed line (missing fields):\n%s", 576 errline); 577 *parse = '\0'; 578 } else 579 working->uid = working->gid = NONE; 580 581 if (!sscanf(q, "%o", &working->permissions)) 582 errx(1, "error in config file; bad permissions:\n%s", 583 errline); 584 585 q = parse = missing_field(sob(++parse), errline); 586 parse = son(parse); 587 if (!*parse) 588 errx(1, "malformed line (missing fields):\n%s", 589 errline); 590 *parse = '\0'; 591 if (!sscanf(q, "%d", &working->numlogs) || working->numlogs < 0) 592 errx(1, "error in config file; bad value for count of logs to save:\n%s", 593 errline); 594 595 q = parse = missing_field(sob(++parse), errline); 596 parse = son(parse); 597 if (!*parse) 598 errx(1, "malformed line (missing fields):\n%s", 599 errline); 600 *parse = '\0'; 601 if (isdigit(*q)) 602 working->size = atoi(q); 603 else 604 working->size = -1; 605 606 working->flags = 0; 607 q = parse = missing_field(sob(++parse), errline); 608 parse = son(parse); 609 eol = !*parse; 610 *parse = '\0'; 611 { 612 char *ep; 613 u_long ul; 614 615 ul = strtoul(q, &ep, 10); 616 if (ep == q) 617 working->hours = 0; 618 else if (*ep == '*') 619 working->hours = -1; 620 else if (ul > INT_MAX) 621 errx(1, "interval is too large:\n%s", errline); 622 else 623 working->hours = ul; 624 625 if (*ep != '\0' && *ep != '@' && *ep != '*' && 626 *ep != '$') 627 errx(1, "malformed interval/at:\n%s", errline); 628 if (*ep == '@') { 629 if ((working->trim_at = parse8601(ep + 1, errline)) 630 == (time_t) - 1) 631 errx(1, "malformed at:\n%s", errline); 632 working->flags |= CE_TRIMAT; 633 } else if (*ep == '$') { 634 if ((working->trim_at = parseDWM(ep + 1, errline)) 635 == (time_t) - 1) 636 errx(1, "malformed at:\n%s", errline); 637 working->flags |= CE_TRIMAT; 638 } 639 } 640 641 if (eol) 642 q = NULL; 643 else { 644 q = parse = sob(++parse); /* Optional field */ 645 parse = son(parse); 646 if (!*parse) 647 eol = 1; 648 *parse = '\0'; 649 } 650 651 for (; q && *q && !isspacech(*q); q++) { 652 switch (tolowerch(*q)) { 653 case 'b': 654 working->flags |= CE_BINARY; 655 break; 656 case 'c': 657 /* 658 * netbsd uses 'c' for "create". We will 659 * temporarily accept it for 'g', because 660 * earlier freebsd versions had a typo 661 * of ('G' || 'c')... 662 */ 663 warnx("Assuming 'g' for 'c' in flags for line:\n%s", 664 errline); 665 /* FALLTHROUGH */ 666 case 'g': 667 working->flags |= CE_GLOB; 668 break; 669 case 'j': 670 working->flags |= CE_BZCOMPACT; 671 break; 672 case 'n': 673 working->flags |= CE_NOSIGNAL; 674 break; 675 case 'w': 676 working->flags |= CE_COMPACTWAIT; 677 break; 678 case 'z': 679 working->flags |= CE_COMPACT; 680 break; 681 case '-': 682 break; 683 default: 684 errx(1, "illegal flag in config file -- %c", 685 *q); 686 } 687 } 688 689 if (eol) 690 q = NULL; 691 else { 692 q = parse = sob(++parse); /* Optional field */ 693 parse = son(parse); 694 if (!*parse) 695 eol = 1; 696 *parse = '\0'; 697 } 698 699 working->pid_file = NULL; 700 if (q && *q) { 701 if (*q == '/') 702 working->pid_file = strdup(q); 703 else if (isdigit(*q)) 704 goto got_sig; 705 else 706 errx(1, 707 "illegal pid file or signal number in config file:\n%s", 708 errline); 709 } 710 if (eol) 711 q = NULL; 712 else { 713 q = parse = sob(++parse); /* Optional field */ 714 *(parse = son(parse)) = '\0'; 715 } 716 717 working->sig = SIGHUP; 718 if (q && *q) { 719 if (isdigit(*q)) { 720 got_sig: 721 working->sig = atoi(q); 722 } else { 723 err_sig: 724 errx(1, 725 "illegal signal number in config file:\n%s", 726 errline); 727 } 728 if (working->sig < 1 || working->sig >= NSIG) 729 goto err_sig; 730 } 731 732 /* 733 * Finish figuring out what pid-file to use (if any) in 734 * later processing if this logfile needs to be rotated. 735 */ 736 if ((working->flags & CE_NOSIGNAL) == CE_NOSIGNAL) { 737 /* 738 * This config-entry specified 'n' for nosignal, 739 * see if it also specified an explicit pid_file. 740 * This would be a pretty pointless combination. 741 */ 742 if (working->pid_file != NULL) { 743 warnx("Ignoring '%s' because flag 'n' was specified in line:\n%s", 744 working->pid_file, errline); 745 free(working->pid_file); 746 working->pid_file = NULL; 747 } 748 } else if (nosignal) { 749 /* 750 * While this entry might usually signal some 751 * process via the pid-file, newsyslog was run 752 * with '-s', so quietly ignore the pid-file. 753 */ 754 if (working->pid_file != NULL) { 755 free(working->pid_file); 756 working->pid_file = NULL; 757 } 758 } else if (working->pid_file == NULL) { 759 /* 760 * This entry did not specify the 'n' flag, which 761 * means it should signal syslogd unless it had 762 * specified some other pid-file. But we only 763 * try to notify syslog if we are root 764 */ 765 if (needroot) 766 working->pid_file = strdup(_PATH_SYSLOGPID); 767 } 768 769 free(errline); 770 errline = NULL; 771 } 772 (void) fclose(f); 773 774 /* 775 * The entire config file has been processed. If there were 776 * no specific files given on the run command, then the work 777 * of this routine is done. 778 */ 779 if (*files == NULL) 780 return (first); 781 782 /* 783 * If the program was given a specific list of files to process, 784 * it may be that some of those files were not listed in the 785 * config file. Those unlisted files should get the default 786 * rotation action. First, create the default-rotation action 787 * if none was found in the config file. 788 */ 789 if (defconf == NULL) { 790 working = init_entry(DEFAULT_MARKER, NULL); 791 working->numlogs = 3; 792 working->size = 50; 793 working->permissions = S_IRUSR|S_IWUSR; 794 defconf = working; 795 } 796 797 for (given = files; *given; ++given) { 798 for (working = first; working; working = working->next) { 799 if (strcmp(*given, working->log) == 0) 800 break; 801 } 802 if (working != NULL) 803 continue; 804 if (verbose > 2) 805 printf("\t+ No entry for %s (will use %s)\n", 806 *given, DEFAULT_MARKER); 807 /* 808 * This given file was not found in the config file. 809 * Add another item on to our work list, based on the 810 * default entry. 811 */ 812 working = init_entry(*given, defconf); 813 if (!first) 814 first = working; 815 else 816 worklist->next = working; 817 /* This is a file that was *not* found in config file */ 818 working->def_cfg = 1; 819 worklist = working; 820 } 821 822 free_entry(defconf); 823 return (first); 824} 825 826static char * 827missing_field(char *p, char *errline) 828{ 829 830 if (!p || !*p) 831 errx(1, "missing field in config file:\n%s", errline); 832 return (p); 833} 834 835static void 836dotrim(const struct conf_entry *trim_ent, char *log, int numdays, int flags, 837 int perm, int owner_uid, int group_gid, int sig) 838{ 839 char dirpart[MAXPATHLEN], namepart[MAXPATHLEN]; 840 char file1[MAXPATHLEN], file2[MAXPATHLEN]; 841 char zfile1[MAXPATHLEN], zfile2[MAXPATHLEN]; 842 char jfile1[MAXPATHLEN]; 843 char tfile[MAXPATHLEN]; 844 int notified, need_notification, fd, _numdays; 845 struct stat st; 846 pid_t pid; 847 848#ifdef _IBMR2 849 /* 850 * AIX 3.1 has a broken fchown- if the owner_uid is -1, it will 851 * actually change it to be owned by uid -1, instead of leaving it 852 * as is, as it is supposed to. 853 */ 854 if (owner_uid == -1) 855 owner_uid = geteuid(); 856#endif 857 858 if (archtodir) { 859 char *p; 860 861 /* build complete name of archive directory into dirpart */ 862 if (*archdirname == '/') { /* absolute */ 863 strlcpy(dirpart, archdirname, sizeof(dirpart)); 864 } else { /* relative */ 865 /* get directory part of logfile */ 866 strlcpy(dirpart, log, sizeof(dirpart)); 867 if ((p = rindex(dirpart, '/')) == NULL) 868 dirpart[0] = '\0'; 869 else 870 *(p + 1) = '\0'; 871 strlcat(dirpart, archdirname, sizeof(dirpart)); 872 } 873 874 /* check if archive directory exists, if not, create it */ 875 if (lstat(dirpart, &st)) 876 createdir(dirpart); 877 878 /* get filename part of logfile */ 879 if ((p = rindex(log, '/')) == NULL) 880 strlcpy(namepart, log, sizeof(namepart)); 881 else 882 strlcpy(namepart, p + 1, sizeof(namepart)); 883 884 /* name of oldest log */ 885 (void) snprintf(file1, sizeof(file1), "%s/%s.%d", dirpart, 886 namepart, numdays); 887 (void) snprintf(zfile1, sizeof(zfile1), "%s%s", file1, 888 COMPRESS_POSTFIX); 889 snprintf(jfile1, sizeof(jfile1), "%s%s", file1, 890 BZCOMPRESS_POSTFIX); 891 } else { 892 /* name of oldest log */ 893 (void) snprintf(file1, sizeof(file1), "%s.%d", log, numdays); 894 (void) snprintf(zfile1, sizeof(zfile1), "%s%s", file1, 895 COMPRESS_POSTFIX); 896 snprintf(jfile1, sizeof(jfile1), "%s%s", file1, 897 BZCOMPRESS_POSTFIX); 898 } 899 900 if (noaction) { 901 printf("rm -f %s\n", file1); 902 printf("rm -f %s\n", zfile1); 903 printf("rm -f %s\n", jfile1); 904 } else { 905 (void) unlink(file1); 906 (void) unlink(zfile1); 907 (void) unlink(jfile1); 908 } 909 910 /* Move down log files */ 911 _numdays = numdays; /* preserve */ 912 while (numdays--) { 913 914 (void) strlcpy(file2, file1, sizeof(file2)); 915 916 if (archtodir) 917 (void) snprintf(file1, sizeof(file1), "%s/%s.%d", 918 dirpart, namepart, numdays); 919 else 920 (void) snprintf(file1, sizeof(file1), "%s.%d", log, 921 numdays); 922 923 (void) strlcpy(zfile1, file1, sizeof(zfile1)); 924 (void) strlcpy(zfile2, file2, sizeof(zfile2)); 925 if (lstat(file1, &st)) { 926 (void) strlcat(zfile1, COMPRESS_POSTFIX, 927 sizeof(zfile1)); 928 (void) strlcat(zfile2, COMPRESS_POSTFIX, 929 sizeof(zfile2)); 930 if (lstat(zfile1, &st)) { 931 strlcpy(zfile1, file1, sizeof(zfile1)); 932 strlcpy(zfile2, file2, sizeof(zfile2)); 933 strlcat(zfile1, BZCOMPRESS_POSTFIX, 934 sizeof(zfile1)); 935 strlcat(zfile2, BZCOMPRESS_POSTFIX, 936 sizeof(zfile2)); 937 if (lstat(zfile1, &st)) 938 continue; 939 } 940 } 941 if (noaction) { 942 printf("mv %s %s\n", zfile1, zfile2); 943 printf("chmod %o %s\n", perm, zfile2); 944 printf("chown %d:%d %s\n", 945 owner_uid, group_gid, zfile2); 946 } else { 947 (void) rename(zfile1, zfile2); 948 (void) chmod(zfile2, perm); 949 (void) chown(zfile2, owner_uid, group_gid); 950 } 951 } 952 if (!noaction && !(flags & CE_BINARY)) { 953 /* Report the trimming to the old log */ 954 (void) log_trim(log, trim_ent); 955 } 956 957 if (!_numdays) { 958 if (noaction) 959 printf("rm %s\n", log); 960 else 961 (void) unlink(log); 962 } else { 963 if (noaction) 964 printf("mv %s to %s\n", log, file1); 965 else { 966 if (archtodir) 967 movefile(log, file1, perm, owner_uid, 968 group_gid); 969 else 970 (void) rename(log, file1); 971 } 972 } 973 974 if (noaction) 975 printf("Start new log..."); 976 else { 977 strlcpy(tfile, log, sizeof(tfile)); 978 strlcat(tfile, ".XXXXXX", sizeof(tfile)); 979 mkstemp(tfile); 980 fd = creat(tfile, perm); 981 if (fd < 0) 982 err(1, "can't start new log"); 983 if (fchown(fd, owner_uid, group_gid)) 984 err(1, "can't chmod new log file"); 985 (void) close(fd); 986 if (!(flags & CE_BINARY)) { 987 /* Add status message to new log file */ 988 if (log_trim(tfile, trim_ent)) 989 err(1, "can't add status message to log"); 990 } 991 } 992 if (noaction) 993 printf("chmod %o %s...\n", perm, log); 994 else { 995 (void) chmod(tfile, perm); 996 if (rename(tfile, log) < 0) { 997 err(1, "can't start new log"); 998 (void) unlink(tfile); 999 } 1000 } 1001 1002 pid = 0; 1003 need_notification = notified = 0; 1004 if (trim_ent->pid_file != NULL) { 1005 need_notification = 1; 1006 pid = get_pid(trim_ent->pid_file); 1007 } 1008 if (pid) { 1009 if (noaction) { 1010 notified = 1; 1011 printf("kill -%d %d\n", sig, (int) pid); 1012 } else if (kill(pid, sig)) 1013 warn("can't notify daemon, pid %d", (int) pid); 1014 else { 1015 notified = 1; 1016 if (verbose) 1017 printf("daemon pid %d notified\n", (int) pid); 1018 } 1019 } 1020 if ((flags & CE_COMPACT) || (flags & CE_BZCOMPACT)) { 1021 if (need_notification && !notified) 1022 warnx( 1023 "log %s not compressed because daemon not notified", 1024 log); 1025 else if (noaction) 1026 printf("Compress %s.0\n", log); 1027 else { 1028 if (notified) { 1029 if (verbose) 1030 printf("small pause to allow daemon to close log\n"); 1031 sleep(10); 1032 } 1033 if (archtodir) { 1034 (void) snprintf(file1, sizeof(file1), "%s/%s", 1035 dirpart, namepart); 1036 if (flags & CE_COMPACT) 1037 compress_log(file1, 1038 flags & CE_COMPACTWAIT); 1039 else if (flags & CE_BZCOMPACT) 1040 bzcompress_log(file1, 1041 flags & CE_COMPACTWAIT); 1042 } else { 1043 if (flags & CE_COMPACT) 1044 compress_log(log, 1045 flags & CE_COMPACTWAIT); 1046 else if (flags & CE_BZCOMPACT) 1047 bzcompress_log(log, 1048 flags & CE_COMPACTWAIT); 1049 } 1050 } 1051 } 1052} 1053 1054/* Log the fact that the logs were turned over */ 1055static int 1056log_trim(const char *log, const struct conf_entry *log_ent) 1057{ 1058 FILE *f; 1059 const char *xtra; 1060 1061 if ((f = fopen(log, "a")) == NULL) 1062 return (-1); 1063 xtra = ""; 1064 if (log_ent->def_cfg) 1065 xtra = " using <default> rule"; 1066 if (log_ent->r_reason != NULL) 1067 fprintf(f, "%s %s newsyslog[%d]: logfile turned over%s%s\n", 1068 daytime, hostname, (int) getpid(), log_ent->r_reason, xtra); 1069 else 1070 fprintf(f, "%s %s newsyslog[%d]: logfile turned over%s\n", 1071 daytime, hostname, (int) getpid(), xtra); 1072 if (fclose(f) == EOF) 1073 err(1, "log_trim: fclose:"); 1074 return (0); 1075} 1076 1077/* Fork of gzip to compress the old log file */ 1078static void 1079compress_log(char *log, int dowait) 1080{ 1081 pid_t pid; 1082 char tmp[MAXPATHLEN]; 1083 1084 while (dowait && (wait(NULL) > 0 || errno == EINTR)) 1085 ; 1086 (void) snprintf(tmp, sizeof(tmp), "%s.0", log); 1087 pid = fork(); 1088 if (pid < 0) 1089 err(1, "gzip fork"); 1090 else if (!pid) { 1091 (void) execl(_PATH_GZIP, _PATH_GZIP, "-f", tmp, (char *)0); 1092 err(1, _PATH_GZIP); 1093 } 1094} 1095 1096/* Fork of bzip2 to compress the old log file */ 1097static void 1098bzcompress_log(char *log, int dowait) 1099{ 1100 pid_t pid; 1101 char tmp[MAXPATHLEN]; 1102 1103 while (dowait && (wait(NULL) > 0 || errno == EINTR)) 1104 ; 1105 snprintf(tmp, sizeof(tmp), "%s.0", log); 1106 pid = fork(); 1107 if (pid < 0) 1108 err(1, "bzip2 fork"); 1109 else if (!pid) { 1110 execl(_PATH_BZIP2, _PATH_BZIP2, "-f", tmp, (char *)0); 1111 err(1, _PATH_BZIP2); 1112 } 1113} 1114 1115/* Return size in kilobytes of a file */ 1116static int 1117sizefile(char *file) 1118{ 1119 struct stat sb; 1120 1121 if (stat(file, &sb) < 0) 1122 return (-1); 1123 return (kbytes(dbtob(sb.st_blocks))); 1124} 1125 1126/* Return the age of old log file (file.0) */ 1127static int 1128age_old_log(char *file) 1129{ 1130 struct stat sb; 1131 char tmp[MAXPATHLEN + sizeof(".0") + sizeof(COMPRESS_POSTFIX) + 1]; 1132 1133 if (archtodir) { 1134 char *p; 1135 1136 /* build name of archive directory into tmp */ 1137 if (*archdirname == '/') { /* absolute */ 1138 strlcpy(tmp, archdirname, sizeof(tmp)); 1139 } else { /* relative */ 1140 /* get directory part of logfile */ 1141 strlcpy(tmp, file, sizeof(tmp)); 1142 if ((p = rindex(tmp, '/')) == NULL) 1143 tmp[0] = '\0'; 1144 else 1145 *(p + 1) = '\0'; 1146 strlcat(tmp, archdirname, sizeof(tmp)); 1147 } 1148 1149 strlcat(tmp, "/", sizeof(tmp)); 1150 1151 /* get filename part of logfile */ 1152 if ((p = rindex(file, '/')) == NULL) 1153 strlcat(tmp, file, sizeof(tmp)); 1154 else 1155 strlcat(tmp, p + 1, sizeof(tmp)); 1156 } else { 1157 (void) strlcpy(tmp, file, sizeof(tmp)); 1158 } 1159 1160 if (stat(strcat(tmp, ".0"), &sb) < 0) 1161 if (stat(strcat(tmp, COMPRESS_POSTFIX), &sb) < 0) 1162 return (-1); 1163 return ((int) (timenow - sb.st_mtime + 1800) / 3600); 1164} 1165 1166static pid_t 1167get_pid(const char *pid_file) 1168{ 1169 FILE *f; 1170 char line[BUFSIZ]; 1171 pid_t pid = 0; 1172 1173 if ((f = fopen(pid_file, "r")) == NULL) 1174 warn("can't open %s pid file to restart a daemon", 1175 pid_file); 1176 else { 1177 if (fgets(line, BUFSIZ, f)) { 1178 pid = atol(line); 1179 if (pid < MIN_PID || pid > MAX_PID) { 1180 warnx("preposterous process number: %d", 1181 (int)pid); 1182 pid = 0; 1183 } 1184 } else 1185 warn("can't read %s pid file to restart a daemon", 1186 pid_file); 1187 (void) fclose(f); 1188 } 1189 return (pid); 1190} 1191 1192/* Skip Over Blanks */ 1193char * 1194sob(char *p) 1195{ 1196 while (p && *p && isspace(*p)) 1197 p++; 1198 return (p); 1199} 1200 1201/* Skip Over Non-Blanks */ 1202char * 1203son(char *p) 1204{ 1205 while (p && *p && !isspace(*p)) 1206 p++; 1207 return (p); 1208} 1209 1210/* 1211 * Parse a limited subset of ISO 8601. The specific format is as follows: 1212 * 1213 * [CC[YY[MM[DD]]]][THH[MM[SS]]] (where `T' is the literal letter) 1214 * 1215 * We don't accept a timezone specification; missing fields (including timezone) 1216 * are defaulted to the current date but time zero. 1217 */ 1218static time_t 1219parse8601(char *s, char *errline) 1220{ 1221 char *t; 1222 time_t tsecs; 1223 struct tm tm, *tmp; 1224 u_long ul; 1225 1226 tmp = localtime(&timenow); 1227 tm = *tmp; 1228 1229 tm.tm_hour = tm.tm_min = tm.tm_sec = 0; 1230 1231 ul = strtoul(s, &t, 10); 1232 if (*t != '\0' && *t != 'T') 1233 return (-1); 1234 1235 /* 1236 * Now t points either to the end of the string (if no time was 1237 * provided) or to the letter `T' which separates date and time in 1238 * ISO 8601. The pointer arithmetic is the same for either case. 1239 */ 1240 switch (t - s) { 1241 case 8: 1242 tm.tm_year = ((ul / 1000000) - 19) * 100; 1243 ul = ul % 1000000; 1244 case 6: 1245 tm.tm_year -= tm.tm_year % 100; 1246 tm.tm_year += ul / 10000; 1247 ul = ul % 10000; 1248 case 4: 1249 tm.tm_mon = (ul / 100) - 1; 1250 ul = ul % 100; 1251 case 2: 1252 tm.tm_mday = ul; 1253 case 0: 1254 break; 1255 default: 1256 return (-1); 1257 } 1258 1259 /* sanity check */ 1260 if (tm.tm_year < 70 || tm.tm_mon < 0 || tm.tm_mon > 12 1261 || tm.tm_mday < 1 || tm.tm_mday > 31) 1262 return (-1); 1263 1264 if (*t != '\0') { 1265 s = ++t; 1266 ul = strtoul(s, &t, 10); 1267 if (*t != '\0' && !isspace(*t)) 1268 return (-1); 1269 1270 switch (t - s) { 1271 case 6: 1272 tm.tm_sec = ul % 100; 1273 ul /= 100; 1274 case 4: 1275 tm.tm_min = ul % 100; 1276 ul /= 100; 1277 case 2: 1278 tm.tm_hour = ul; 1279 case 0: 1280 break; 1281 default: 1282 return (-1); 1283 } 1284 1285 /* sanity check */ 1286 if (tm.tm_sec < 0 || tm.tm_sec > 60 || tm.tm_min < 0 1287 || tm.tm_min > 59 || tm.tm_hour < 0 || tm.tm_hour > 23) 1288 return (-1); 1289 } 1290 if ((tsecs = mktime(&tm)) == -1) 1291 errx(1, "nonexistent time:\n%s", errline); 1292 return (tsecs); 1293} 1294 1295/* physically move file */ 1296static void 1297movefile(char *from, char *to, int perm, int owner_uid, int group_gid) 1298{ 1299 FILE *src, *dst; 1300 int c; 1301 1302 if ((src = fopen(from, "r")) == NULL) 1303 err(1, "can't fopen %s for reading", from); 1304 if ((dst = fopen(to, "w")) == NULL) 1305 err(1, "can't fopen %s for writing", to); 1306 if (fchown(fileno(dst), owner_uid, group_gid)) 1307 err(1, "can't fchown %s", to); 1308 if (fchmod(fileno(dst), perm)) 1309 err(1, "can't fchmod %s", to); 1310 1311 while ((c = getc(src)) != EOF) { 1312 if ((putc(c, dst)) == EOF) 1313 err(1, "error writing to %s", to); 1314 } 1315 1316 if (ferror(src)) 1317 err(1, "error reading from %s", from); 1318 if ((fclose(src)) != 0) 1319 err(1, "can't fclose %s", to); 1320 if ((fclose(dst)) != 0) 1321 err(1, "can't fclose %s", from); 1322 if ((unlink(from)) != 0) 1323 err(1, "can't unlink %s", from); 1324} 1325 1326/* create one or more directory components of a path */ 1327static void 1328createdir(char *dirpart) 1329{ 1330 int res; 1331 char *s, *d; 1332 char mkdirpath[MAXPATHLEN]; 1333 struct stat st; 1334 1335 s = dirpart; 1336 d = mkdirpath; 1337 1338 for (;;) { 1339 *d++ = *s++; 1340 if (*s != '/' && *s != '\0') 1341 continue; 1342 *d = '\0'; 1343 res = lstat(mkdirpath, &st); 1344 if (res != 0) { 1345 if (noaction) { 1346 printf("mkdir %s\n", mkdirpath); 1347 } else { 1348 res = mkdir(mkdirpath, 0755); 1349 if (res != 0) 1350 err(1, "Error on mkdir(\"%s\") for -a", 1351 mkdirpath); 1352 } 1353 } 1354 if (*s == '\0') 1355 break; 1356 } 1357 if (verbose) 1358 printf("created directory '%s' for -a\n", dirpart); 1359} 1360 1361/*- 1362 * Parse a cyclic time specification, the format is as follows: 1363 * 1364 * [Dhh] or [Wd[Dhh]] or [Mdd[Dhh]] 1365 * 1366 * to rotate a logfile cyclic at 1367 * 1368 * - every day (D) within a specific hour (hh) (hh = 0...23) 1369 * - once a week (W) at a specific day (d) OR (d = 0..6, 0 = Sunday) 1370 * - once a month (M) at a specific day (d) (d = 1..31,l|L) 1371 * 1372 * We don't accept a timezone specification; missing fields 1373 * are defaulted to the current date but time zero. 1374 */ 1375static time_t 1376parseDWM(char *s, char *errline) 1377{ 1378 char *t; 1379 time_t tsecs; 1380 struct tm tm, *tmp; 1381 long l; 1382 int nd; 1383 static int mtab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; 1384 int WMseen = 0; 1385 int Dseen = 0; 1386 1387 tmp = localtime(&timenow); 1388 tm = *tmp; 1389 1390 /* set no. of days per month */ 1391 1392 nd = mtab[tm.tm_mon]; 1393 1394 if (tm.tm_mon == 1) { 1395 if (((tm.tm_year + 1900) % 4 == 0) && 1396 ((tm.tm_year + 1900) % 100 != 0) && 1397 ((tm.tm_year + 1900) % 400 == 0)) { 1398 nd++; /* leap year, 29 days in february */ 1399 } 1400 } 1401 tm.tm_hour = tm.tm_min = tm.tm_sec = 0; 1402 1403 for (;;) { 1404 switch (*s) { 1405 case 'D': 1406 if (Dseen) 1407 return (-1); 1408 Dseen++; 1409 s++; 1410 l = strtol(s, &t, 10); 1411 if (l < 0 || l > 23) 1412 return (-1); 1413 tm.tm_hour = l; 1414 break; 1415 1416 case 'W': 1417 if (WMseen) 1418 return (-1); 1419 WMseen++; 1420 s++; 1421 l = strtol(s, &t, 10); 1422 if (l < 0 || l > 6) 1423 return (-1); 1424 if (l != tm.tm_wday) { 1425 int save; 1426 1427 if (l < tm.tm_wday) { 1428 save = 6 - tm.tm_wday; 1429 save += (l + 1); 1430 } else { 1431 save = l - tm.tm_wday; 1432 } 1433 1434 tm.tm_mday += save; 1435 1436 if (tm.tm_mday > nd) { 1437 tm.tm_mon++; 1438 tm.tm_mday = tm.tm_mday - nd; 1439 } 1440 } 1441 break; 1442 1443 case 'M': 1444 if (WMseen) 1445 return (-1); 1446 WMseen++; 1447 s++; 1448 if (tolower(*s) == 'l') { 1449 tm.tm_mday = nd; 1450 s++; 1451 t = s; 1452 } else { 1453 l = strtol(s, &t, 10); 1454 if (l < 1 || l > 31) 1455 return (-1); 1456 1457 if (l > nd) 1458 return (-1); 1459 tm.tm_mday = l; 1460 } 1461 break; 1462 1463 default: 1464 return (-1); 1465 break; 1466 } 1467 1468 if (*t == '\0' || isspace(*t)) 1469 break; 1470 else 1471 s = t; 1472 } 1473 if ((tsecs = mktime(&tm)) == -1) 1474 errx(1, "nonexistent time:\n%s", errline); 1475 return (tsecs); 1476} 1477