newsyslog.c revision 93659
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 93659 2002-04-02 12:03:16Z cjc $"; 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 <ctype.h> 40#include <err.h> 41#include <fcntl.h> 42#include <grp.h> 43#include <paths.h> 44#include <pwd.h> 45#include <signal.h> 46#include <stdio.h> 47#include <stdlib.h> 48#include <string.h> 49#include <time.h> 50#include <unistd.h> 51#include <sys/types.h> 52#include <sys/stat.h> 53#include <sys/param.h> 54#include <sys/wait.h> 55 56#include "pathnames.h" 57 58#define kbytes(size) (((size) + 1023) >> 10) 59 60#ifdef _IBMR2 61/* Calculates (db * DEV_BSIZE) */ 62#define dbtob(db) ((unsigned)(db) << UBSHIFT) 63#endif 64 65#define CE_COMPACT 1 /* Compact the achived log files */ 66#define CE_BINARY 2 /* Logfile is in binary, don't add */ 67#define CE_BZCOMPACT 8 /* Compact the achived log files with bzip2 */ 68 /* status messages */ 69#define CE_TRIMAT 4 /* trim at a specific time */ 70 71#define NONE -1 72 73struct conf_entry { 74 char *log; /* Name of the log */ 75 char *pid_file; /* PID file */ 76 int uid; /* Owner of log */ 77 int gid; /* Group of log */ 78 int numlogs; /* Number of logs to keep */ 79 int size; /* Size cutoff to trigger trimming the log */ 80 int hours; /* Hours between log trimming */ 81 time_t trim_at; /* Specific time to do trimming */ 82 int permissions; /* File permissions on the log */ 83 int flags; /* CE_COMPACT, CE_BZCOMPACT, CE_BINARY */ 84 int sig; /* Signal to send */ 85 struct conf_entry *next;/* Linked list pointer */ 86}; 87 88int archtodir = 0; /* Archive old logfiles to other directory */ 89int verbose = 0; /* Print out what's going on */ 90int needroot = 1; /* Root privs are necessary */ 91int noaction = 0; /* Don't do anything, just show it */ 92int force = 0; /* Force the trim no matter what */ 93char *archdirname; /* Directory path to old logfiles archive */ 94const char *conf = _PATH_CONF; /* Configuration file to use */ 95time_t timenow; 96 97#define MIN_PID 5 98#define MAX_PID 99999 /* was lower, see /usr/include/sys/proc.h */ 99char hostname[MAXHOSTNAMELEN]; /* hostname */ 100char *daytime; /* timenow in human readable form */ 101 102static struct conf_entry *parse_file(char **files); 103static char *sob(char *p); 104static char *son(char *p); 105static char *missing_field(char *p, char *errline); 106static void do_entry(struct conf_entry * ent); 107static void PRS(int argc, char **argv); 108static void usage(void); 109static void dotrim(char *log, const char *pid_file, int numdays, int falgs, 110 int perm, int owner_uid, int group_gid, int sig); 111static int log_trim(char *log); 112static void compress_log(char *log); 113static void bzcompress_log(char *log); 114static int sizefile(char *file); 115static int age_old_log(char *file); 116static pid_t get_pid(const char *pid_file); 117static time_t parse8601(char *s, char *errline); 118static void movefile(char *from, char *to, int perm, int owner_uid, 119 int group_gid); 120static void createdir(char *dirpart); 121static time_t parseDWM(char *s, char *errline); 122 123int 124main(int argc, char **argv) 125{ 126 struct conf_entry *p, *q; 127 128 PRS(argc, argv); 129 if (needroot && getuid() && geteuid()) 130 errx(1, "must have root privs"); 131 p = q = parse_file(argv + optind); 132 133 while (p) { 134 do_entry(p); 135 p = p->next; 136 free((char *) q); 137 q = p; 138 } 139 return (0); 140} 141 142static void 143do_entry(struct conf_entry * ent) 144{ 145 int size, modtime; 146 const char *pid_file; 147 148 if (verbose) { 149 if (ent->flags & CE_COMPACT) 150 printf("%s <%dZ>: ", ent->log, ent->numlogs); 151 else if (ent->flags & CE_BZCOMPACT) 152 printf("%s <%dJ>: ", ent->log, ent->numlogs); 153 else 154 printf("%s <%d>: ", ent->log, ent->numlogs); 155 } 156 size = sizefile(ent->log); 157 modtime = age_old_log(ent->log); 158 if (size < 0) { 159 if (verbose) 160 printf("does not exist.\n"); 161 } else { 162 if (ent->flags & CE_TRIMAT && !force) { 163 if (timenow < ent->trim_at 164 || difftime(timenow, ent->trim_at) >= 60 * 60) { 165 if (verbose) 166 printf("--> will trim at %s", 167 ctime(&ent->trim_at)); 168 return; 169 } else if (verbose && ent->hours <= 0) { 170 printf("--> time is up\n"); 171 } 172 } 173 if (verbose && (ent->size > 0)) 174 printf("size (Kb): %d [%d] ", size, ent->size); 175 if (verbose && (ent->hours > 0)) 176 printf(" age (hr): %d [%d] ", modtime, ent->hours); 177 if (force || ((ent->size > 0) && (size >= ent->size)) || 178 (ent->hours <= 0 && (ent->flags & CE_TRIMAT)) || 179 ((ent->hours > 0) && ((modtime >= ent->hours) 180 || (modtime < 0)))) { 181 if (verbose) 182 printf("--> trimming log....\n"); 183 if (noaction && !verbose) { 184 if (ent->flags & CE_COMPACT) 185 printf("%s <%dZ>: trimming\n", 186 ent->log, ent->numlogs); 187 else if (ent->flags & CE_BZCOMPACT) 188 printf("%s <%dJ>: trimming\n", 189 ent->log, ent->numlogs); 190 else 191 printf("%s <%d>: trimming\n", 192 ent->log, ent->numlogs); 193 } 194 if (ent->pid_file) { 195 pid_file = ent->pid_file; 196 } else { 197 /* Only try to notify syslog if we are root */ 198 if (needroot) 199 pid_file = _PATH_SYSLOGPID; 200 else 201 pid_file = NULL; 202 } 203 dotrim(ent->log, pid_file, ent->numlogs, 204 ent->flags, ent->permissions, ent->uid, ent->gid, 205 ent->sig); 206 } else { 207 if (verbose) 208 printf("--> skipping\n"); 209 } 210 } 211} 212 213static void 214PRS(int argc, char **argv) 215{ 216 int c; 217 char *p; 218 219 timenow = time((time_t *) 0); 220 daytime = ctime(&timenow) + 4; 221 daytime[15] = '\0'; 222 223 /* Let's get our hostname */ 224 (void) gethostname(hostname, sizeof(hostname)); 225 226 /* Truncate domain */ 227 if ((p = strchr(hostname, '.'))) { 228 *p = '\0'; 229 } 230 while ((c = getopt(argc, argv, "nrvFf:a:t:")) != -1) 231 switch (c) { 232 case 'n': 233 noaction++; 234 break; 235 case 'a': 236 archtodir++; 237 archdirname = optarg; 238 break; 239 case 'r': 240 needroot = 0; 241 break; 242 case 'v': 243 verbose++; 244 break; 245 case 'f': 246 conf = optarg; 247 break; 248 case 'F': 249 force++; 250 break; 251 default: 252 usage(); 253 } 254} 255 256static void 257usage(void) 258{ 259 260 fprintf(stderr, 261 "usage: newsyslog [-Fnrv] [-f config-file] [-a directory]\n"); 262 exit(1); 263} 264 265/* 266 * Parse a configuration file and return a linked list of all the logs to 267 * process 268 */ 269static struct conf_entry * 270parse_file(char **files) 271{ 272 FILE *f; 273 char line[BUFSIZ], *parse, *q; 274 char *errline, *group; 275 char **p; 276 struct conf_entry *first, *working; 277 struct passwd *pass; 278 struct group *grp; 279 int eol; 280 281 first = working = NULL; 282 283 if (strcmp(conf, "-")) 284 f = fopen(conf, "r"); 285 else 286 f = stdin; 287 if (!f) 288 err(1, "%s", conf); 289 while (fgets(line, BUFSIZ, f)) { 290 if ((line[0] == '\n') || (line[0] == '#')) 291 continue; 292 errline = strdup(line); 293 294 q = parse = missing_field(sob(line), errline); 295 parse = son(line); 296 if (!*parse) 297 errx(1, "malformed line (missing fields):\n%s", 298 errline); 299 *parse = '\0'; 300 301 if (*files) { 302 for (p = files; *p; ++p) 303 if (strcmp(*p, q) == 0) 304 break; 305 if (!*p) 306 continue; 307 } 308 309 if (!first) { 310 if ((working = malloc(sizeof(struct conf_entry))) == 311 NULL) 312 err(1, "malloc"); 313 first = working; 314 } else { 315 if ((working->next = malloc(sizeof(struct conf_entry))) 316 == NULL) 317 err(1, "malloc"); 318 working = working->next; 319 } 320 if ((working->log = strdup(q)) == NULL) 321 err(1, "strdup"); 322 323 q = parse = missing_field(sob(++parse), errline); 324 parse = son(parse); 325 if (!*parse) 326 errx(1, "malformed line (missing fields):\n%s", 327 errline); 328 *parse = '\0'; 329 if ((group = strchr(q, ':')) != NULL || 330 (group = strrchr(q, '.')) != NULL) { 331 *group++ = '\0'; 332 if (*q) { 333 if (!(isnumber(*q))) { 334 if ((pass = getpwnam(q)) == NULL) 335 errx(1, 336 "error in config file; unknown user:\n%s", 337 errline); 338 working->uid = pass->pw_uid; 339 } else 340 working->uid = atoi(q); 341 } else 342 working->uid = NONE; 343 344 q = group; 345 if (*q) { 346 if (!(isnumber(*q))) { 347 if ((grp = getgrnam(q)) == NULL) 348 errx(1, 349 "error in config file; unknown group:\n%s", 350 errline); 351 working->gid = grp->gr_gid; 352 } else 353 working->gid = atoi(q); 354 } else 355 working->gid = NONE; 356 357 q = parse = missing_field(sob(++parse), errline); 358 parse = son(parse); 359 if (!*parse) 360 errx(1, "malformed line (missing fields):\n%s", 361 errline); 362 *parse = '\0'; 363 } else 364 working->uid = working->gid = NONE; 365 366 if (!sscanf(q, "%o", &working->permissions)) 367 errx(1, "error in config file; bad permissions:\n%s", 368 errline); 369 370 q = parse = missing_field(sob(++parse), errline); 371 parse = son(parse); 372 if (!*parse) 373 errx(1, "malformed line (missing fields):\n%s", 374 errline); 375 *parse = '\0'; 376 if (!sscanf(q, "%d", &working->numlogs)) 377 errx(1, "error in config file; bad number:\n%s", 378 errline); 379 380 q = parse = missing_field(sob(++parse), errline); 381 parse = son(parse); 382 if (!*parse) 383 errx(1, "malformed line (missing fields):\n%s", 384 errline); 385 *parse = '\0'; 386 if (isdigit(*q)) 387 working->size = atoi(q); 388 else 389 working->size = -1; 390 391 working->flags = 0; 392 q = parse = missing_field(sob(++parse), errline); 393 parse = son(parse); 394 eol = !*parse; 395 *parse = '\0'; 396 { 397 char *ep; 398 u_long ul; 399 400 ul = strtoul(q, &ep, 10); 401 if (ep == q) 402 working->hours = 0; 403 else if (*ep == '*') 404 working->hours = -1; 405 else if (ul > INT_MAX) 406 errx(1, "interval is too large:\n%s", errline); 407 else 408 working->hours = ul; 409 410 if (*ep != '\0' && *ep != '@' && *ep != '*' && 411 *ep != '$') 412 errx(1, "malformed interval/at:\n%s", errline); 413 if (*ep == '@') { 414 if ((working->trim_at = parse8601(ep + 1, errline)) 415 == (time_t) - 1) 416 errx(1, "malformed at:\n%s", errline); 417 working->flags |= CE_TRIMAT; 418 } else if (*ep == '$') { 419 if ((working->trim_at = parseDWM(ep + 1, errline)) 420 == (time_t) - 1) 421 errx(1, "malformed at:\n%s", errline); 422 working->flags |= CE_TRIMAT; 423 } 424 } 425 426 if (eol) 427 q = NULL; 428 else { 429 q = parse = sob(++parse); /* Optional field */ 430 parse = son(parse); 431 if (!*parse) 432 eol = 1; 433 *parse = '\0'; 434 } 435 436 while (q && *q && !isspace(*q)) { 437 if ((*q == 'Z') || (*q == 'z')) 438 working->flags |= CE_COMPACT; 439 else if ((*q == 'J') || (*q == 'j')) 440 working->flags |= CE_BZCOMPACT; 441 else if ((*q == 'B') || (*q == 'b')) 442 working->flags |= CE_BINARY; 443 else if (*q != '-') 444 errx(1, "illegal flag in config file -- %c", 445 *q); 446 q++; 447 } 448 449 if (eol) 450 q = NULL; 451 else { 452 q = parse = sob(++parse); /* Optional field */ 453 parse = son(parse); 454 if (!*parse) 455 eol = 1; 456 *parse = '\0'; 457 } 458 459 working->pid_file = NULL; 460 if (q && *q) { 461 if (*q == '/') 462 working->pid_file = strdup(q); 463 else if (isdigit(*q)) 464 goto got_sig; 465 else 466 errx(1, 467 "illegal pid file or signal number in config file:\n%s", 468 errline); 469 } 470 if (eol) 471 q = NULL; 472 else { 473 q = parse = sob(++parse); /* Optional field */ 474 *(parse = son(parse)) = '\0'; 475 } 476 477 working->sig = SIGHUP; 478 if (q && *q) { 479 if (isdigit(*q)) { 480 got_sig: 481 working->sig = atoi(q); 482 } else { 483 err_sig: 484 errx(1, 485 "illegal signal number in config file:\n%s", 486 errline); 487 } 488 if (working->sig < 1 || working->sig >= NSIG) 489 goto err_sig; 490 } 491 free(errline); 492 } 493 if (working) 494 working->next = (struct conf_entry *) NULL; 495 (void) fclose(f); 496 return (first); 497} 498 499static char * 500missing_field(char *p, char *errline) 501{ 502 503 if (!p || !*p) 504 errx(1, "missing field in config file:\n%s", errline); 505 return (p); 506} 507 508static void 509dotrim(char *log, const char *pid_file, int numdays, int flags, int perm, 510 int owner_uid, int group_gid, int sig) 511{ 512 char dirpart[MAXPATHLEN], namepart[MAXPATHLEN]; 513 char file1[MAXPATHLEN], file2[MAXPATHLEN]; 514 char zfile1[MAXPATHLEN], zfile2[MAXPATHLEN]; 515 char jfile1[MAXPATHLEN]; 516 int notified, need_notification, fd, _numdays; 517 struct stat st; 518 pid_t pid; 519 520#ifdef _IBMR2 521 /* 522 * AIX 3.1 has a broken fchown- if the owner_uid is -1, it will 523 * actually change it to be owned by uid -1, instead of leaving it 524 * as is, as it is supposed to. 525 */ 526 if (owner_uid == -1) 527 owner_uid = geteuid(); 528#endif 529 530 if (archtodir) { 531 char *p; 532 533 /* build complete name of archive directory into dirpart */ 534 if (*archdirname == '/') { /* absolute */ 535 strlcpy(dirpart, archdirname, sizeof(dirpart)); 536 } else { /* relative */ 537 /* get directory part of logfile */ 538 strlcpy(dirpart, log, sizeof(dirpart)); 539 if ((p = rindex(dirpart, '/')) == NULL) 540 dirpart[0] = '\0'; 541 else 542 *(p + 1) = '\0'; 543 strlcat(dirpart, archdirname, sizeof(dirpart)); 544 } 545 546 /* check if archive directory exists, if not, create it */ 547 if (lstat(dirpart, &st)) 548 createdir(dirpart); 549 550 /* get filename part of logfile */ 551 if ((p = rindex(log, '/')) == NULL) 552 strlcpy(namepart, log, sizeof(namepart)); 553 else 554 strlcpy(namepart, p + 1, sizeof(namepart)); 555 556 /* name of oldest log */ 557 (void) snprintf(file1, sizeof(file1), "%s/%s.%d", dirpart, 558 namepart, numdays); 559 (void) snprintf(zfile1, sizeof(zfile1), "%s%s", file1, 560 COMPRESS_POSTFIX); 561 snprintf(jfile1, sizeof(jfile1), "%s%s", file1, 562 BZCOMPRESS_POSTFIX); 563 } else { 564 /* name of oldest log */ 565 (void) snprintf(file1, sizeof(file1), "%s.%d", log, numdays); 566 (void) snprintf(zfile1, sizeof(zfile1), "%s%s", file1, 567 COMPRESS_POSTFIX); 568 snprintf(jfile1, sizeof(jfile1), "%s%s", file1, 569 BZCOMPRESS_POSTFIX); 570 } 571 572 if (noaction) { 573 printf("rm -f %s\n", file1); 574 printf("rm -f %s\n", zfile1); 575 printf("rm -f %s\n", jfile1); 576 } else { 577 (void) unlink(file1); 578 (void) unlink(zfile1); 579 (void) unlink(jfile1); 580 } 581 582 /* Move down log files */ 583 _numdays = numdays; /* preserve */ 584 while (numdays--) { 585 586 (void) strlcpy(file2, file1, sizeof(file2)); 587 588 if (archtodir) 589 (void) snprintf(file1, sizeof(file1), "%s/%s.%d", 590 dirpart, namepart, numdays); 591 else 592 (void) snprintf(file1, sizeof(file1), "%s.%d", log, 593 numdays); 594 595 (void) strlcpy(zfile1, file1, sizeof(zfile1)); 596 (void) strlcpy(zfile2, file2, sizeof(zfile2)); 597 if (lstat(file1, &st)) { 598 (void) strlcat(zfile1, COMPRESS_POSTFIX, 599 sizeof(zfile1)); 600 (void) strlcat(zfile2, COMPRESS_POSTFIX, 601 sizeof(zfile2)); 602 if (lstat(zfile1, &st)) { 603 strlcpy(zfile1, file1, sizeof(zfile1)); 604 strlcpy(zfile2, file2, sizeof(zfile2)); 605 strlcat(zfile1, BZCOMPRESS_POSTFIX, 606 sizeof(zfile1)); 607 strlcat(zfile2, BZCOMPRESS_POSTFIX, 608 sizeof(zfile2)); 609 if (lstat(zfile1, &st)) 610 continue; 611 } 612 } 613 if (noaction) { 614 printf("mv %s %s\n", zfile1, zfile2); 615 printf("chmod %o %s\n", perm, zfile2); 616 printf("chown %d:%d %s\n", 617 owner_uid, group_gid, zfile2); 618 } else { 619 (void) rename(zfile1, zfile2); 620 (void) chmod(zfile2, perm); 621 (void) chown(zfile2, owner_uid, group_gid); 622 } 623 } 624 if (!noaction && !(flags & CE_BINARY)) 625 (void) log_trim(log); /* Report the trimming to the old log */ 626 627 if (!_numdays) { 628 if (noaction) 629 printf("rm %s\n", log); 630 else 631 (void) unlink(log); 632 } else { 633 if (noaction) 634 printf("mv %s to %s\n", log, file1); 635 else { 636 if (archtodir) 637 movefile(log, file1, perm, owner_uid, 638 group_gid); 639 else 640 (void) rename(log, file1); 641 } 642 } 643 644 if (noaction) 645 printf("Start new log..."); 646 else { 647 fd = creat(log, perm); 648 if (fd < 0) 649 err(1, "can't start new log"); 650 if (fchown(fd, owner_uid, group_gid)) 651 err(1, "can't chmod new log file"); 652 (void) close(fd); 653 if (!(flags & CE_BINARY)) 654 if (log_trim(log)) /* Add status message */ 655 err(1, "can't add status message to log"); 656 } 657 if (noaction) 658 printf("chmod %o %s...\n", perm, log); 659 else 660 (void) chmod(log, perm); 661 662 pid = 0; 663 need_notification = notified = 0; 664 if (pid_file != NULL) { 665 need_notification = 1; 666 pid = get_pid(pid_file); 667 } 668 if (pid) { 669 if (noaction) { 670 notified = 1; 671 printf("kill -%d %d\n", sig, (int) pid); 672 } else if (kill(pid, sig)) 673 warn("can't notify daemon, pid %d", (int) pid); 674 else { 675 notified = 1; 676 if (verbose) 677 printf("daemon pid %d notified\n", (int) pid); 678 } 679 } 680 if ((flags & CE_COMPACT) || (flags & CE_BZCOMPACT)) { 681 if (need_notification && !notified) 682 warnx( 683 "log %s not compressed because daemon not notified", 684 log); 685 else if (noaction) 686 printf("Compress %s.0\n", log); 687 else { 688 if (notified) { 689 if (verbose) 690 printf("small pause to allow daemon to close log\n"); 691 sleep(10); 692 } 693 if (archtodir) { 694 (void) snprintf(file1, sizeof(file1), "%s/%s", 695 dirpart, namepart); 696 if (flags & CE_COMPACT) 697 compress_log(file1); 698 else if (flags & CE_BZCOMPACT) 699 bzcompress_log(file1); 700 } else { 701 if (flags & CE_COMPACT) 702 compress_log(log); 703 else if (flags & CE_BZCOMPACT) 704 bzcompress_log(log); 705 } 706 } 707 } 708} 709 710/* Log the fact that the logs were turned over */ 711static int 712log_trim(char *log) 713{ 714 FILE *f; 715 716 if ((f = fopen(log, "a")) == NULL) 717 return (-1); 718 fprintf(f, "%s %s newsyslog[%d]: logfile turned over\n", 719 daytime, hostname, (int) getpid()); 720 if (fclose(f) == EOF) 721 err(1, "log_trim: fclose:"); 722 return (0); 723} 724 725/* Fork of gzip to compress the old log file */ 726static void 727compress_log(char *log) 728{ 729 pid_t pid; 730 char tmp[MAXPATHLEN]; 731 732 (void) snprintf(tmp, sizeof(tmp), "%s.0", log); 733 pid = fork(); 734 if (pid < 0) 735 err(1, "gzip fork"); 736 else if (!pid) { 737 (void) execl(_PATH_GZIP, _PATH_GZIP, "-f", tmp, (char *)0); 738 err(1, _PATH_GZIP); 739 } 740} 741 742/* Fork of bzip2 to compress the old log file */ 743static void 744bzcompress_log(char *log) 745{ 746 pid_t pid; 747 char tmp[MAXPATHLEN]; 748 749 snprintf(tmp, sizeof(tmp), "%s.0", log); 750 pid = fork(); 751 if (pid < 0) 752 err(1, "bzip2 fork"); 753 else if (!pid) { 754 execl(_PATH_BZIP2, _PATH_BZIP2, "-f", tmp, (char *)0); 755 err(1, _PATH_BZIP2); 756 } 757} 758 759/* Return size in kilobytes of a file */ 760static int 761sizefile(char *file) 762{ 763 struct stat sb; 764 765 if (stat(file, &sb) < 0) 766 return (-1); 767 return (kbytes(dbtob(sb.st_blocks))); 768} 769 770/* Return the age of old log file (file.0) */ 771static int 772age_old_log(char *file) 773{ 774 struct stat sb; 775 char tmp[MAXPATHLEN + sizeof(".0") + sizeof(COMPRESS_POSTFIX) + 1]; 776 777 if (archtodir) { 778 char *p; 779 780 /* build name of archive directory into tmp */ 781 if (*archdirname == '/') { /* absolute */ 782 strlcpy(tmp, archdirname, sizeof(tmp)); 783 } else { /* relative */ 784 /* get directory part of logfile */ 785 strlcpy(tmp, file, sizeof(tmp)); 786 if ((p = rindex(tmp, '/')) == NULL) 787 tmp[0] = '\0'; 788 else 789 *(p + 1) = '\0'; 790 strlcat(tmp, archdirname, sizeof(tmp)); 791 } 792 793 strlcat(tmp, "/", sizeof(tmp)); 794 795 /* get filename part of logfile */ 796 if ((p = rindex(file, '/')) == NULL) 797 strlcat(tmp, file, sizeof(tmp)); 798 else 799 strlcat(tmp, p + 1, sizeof(tmp)); 800 } else { 801 (void) strlcpy(tmp, file, sizeof(tmp)); 802 } 803 804 if (stat(strcat(tmp, ".0"), &sb) < 0) 805 if (stat(strcat(tmp, COMPRESS_POSTFIX), &sb) < 0) 806 return (-1); 807 return ((int) (timenow - sb.st_mtime + 1800) / 3600); 808} 809 810static pid_t 811get_pid(const char *pid_file) 812{ 813 FILE *f; 814 char line[BUFSIZ]; 815 pid_t pid = 0; 816 817 if ((f = fopen(pid_file, "r")) == NULL) 818 warn("can't open %s pid file to restart a daemon", 819 pid_file); 820 else { 821 if (fgets(line, BUFSIZ, f)) { 822 pid = atol(line); 823 if (pid < MIN_PID || pid > MAX_PID) { 824 warnx("preposterous process number: %d", 825 (int)pid); 826 pid = 0; 827 } 828 } else 829 warn("can't read %s pid file to restart a daemon", 830 pid_file); 831 (void) fclose(f); 832 } 833 return pid; 834} 835 836/* Skip Over Blanks */ 837char * 838sob(char *p) 839{ 840 while (p && *p && isspace(*p)) 841 p++; 842 return (p); 843} 844 845/* Skip Over Non-Blanks */ 846char * 847son(char *p) 848{ 849 while (p && *p && !isspace(*p)) 850 p++; 851 return (p); 852} 853 854/* 855 * Parse a limited subset of ISO 8601. The specific format is as follows: 856 * 857 * [CC[YY[MM[DD]]]][THH[MM[SS]]] (where `T' is the literal letter) 858 * 859 * We don't accept a timezone specification; missing fields (including timezone) 860 * are defaulted to the current date but time zero. 861 */ 862static time_t 863parse8601(char *s, char *errline) 864{ 865 char *t; 866 time_t tsecs; 867 struct tm tm, *tmp; 868 u_long ul; 869 870 tmp = localtime(&timenow); 871 tm = *tmp; 872 873 tm.tm_hour = tm.tm_min = tm.tm_sec = 0; 874 875 ul = strtoul(s, &t, 10); 876 if (*t != '\0' && *t != 'T') 877 return -1; 878 879 /* 880 * Now t points either to the end of the string (if no time was 881 * provided) or to the letter `T' which separates date and time in 882 * ISO 8601. The pointer arithmetic is the same for either case. 883 */ 884 switch (t - s) { 885 case 8: 886 tm.tm_year = ((ul / 1000000) - 19) * 100; 887 ul = ul % 1000000; 888 case 6: 889 tm.tm_year -= tm.tm_year % 100; 890 tm.tm_year += ul / 10000; 891 ul = ul % 10000; 892 case 4: 893 tm.tm_mon = (ul / 100) - 1; 894 ul = ul % 100; 895 case 2: 896 tm.tm_mday = ul; 897 case 0: 898 break; 899 default: 900 return -1; 901 } 902 903 /* sanity check */ 904 if (tm.tm_year < 70 || tm.tm_mon < 0 || tm.tm_mon > 12 905 || tm.tm_mday < 1 || tm.tm_mday > 31) 906 return -1; 907 908 if (*t != '\0') { 909 s = ++t; 910 ul = strtoul(s, &t, 10); 911 if (*t != '\0' && !isspace(*t)) 912 return -1; 913 914 switch (t - s) { 915 case 6: 916 tm.tm_sec = ul % 100; 917 ul /= 100; 918 case 4: 919 tm.tm_min = ul % 100; 920 ul /= 100; 921 case 2: 922 tm.tm_hour = ul; 923 case 0: 924 break; 925 default: 926 return -1; 927 } 928 929 /* sanity check */ 930 if (tm.tm_sec < 0 || tm.tm_sec > 60 || tm.tm_min < 0 931 || tm.tm_min > 59 || tm.tm_hour < 0 || tm.tm_hour > 23) 932 return -1; 933 } 934 if ((tsecs = mktime(&tm)) == -1) 935 errx(1, "nonexistent time:\n%s", errline); 936 return tsecs; 937} 938 939/* physically move file */ 940static void 941movefile(char *from, char *to, int perm, int owner_uid, int group_gid) 942{ 943 FILE *src, *dst; 944 int c; 945 946 if ((src = fopen(from, "r")) == NULL) 947 err(1, "can't fopen %s for reading", from); 948 if ((dst = fopen(to, "w")) == NULL) 949 err(1, "can't fopen %s for writing", to); 950 if (fchown(fileno(dst), owner_uid, group_gid)) 951 err(1, "can't fchown %s", to); 952 if (fchmod(fileno(dst), perm)) 953 err(1, "can't fchmod %s", to); 954 955 while ((c = getc(src)) != EOF) { 956 if ((putc(c, dst)) == EOF) 957 err(1, "error writing to %s", to); 958 } 959 960 if (ferror(src)) 961 err(1, "error reading from %s", from); 962 if ((fclose(src)) != 0) 963 err(1, "can't fclose %s", to); 964 if ((fclose(dst)) != 0) 965 err(1, "can't fclose %s", from); 966 if ((unlink(from)) != 0) 967 err(1, "can't unlink %s", from); 968} 969 970/* create one or more directory components of a path */ 971static void 972createdir(char *dirpart) 973{ 974 char *s, *d; 975 char mkdirpath[MAXPATHLEN]; 976 struct stat st; 977 978 s = dirpart; 979 d = mkdirpath; 980 981 for (;;) { 982 *d++ = *s++; 983 if (*s == '/' || *s == '\0') { 984 *d = '\0'; 985 if (lstat(mkdirpath, &st)) 986 mkdir(mkdirpath, 0755); 987 } 988 if (*s == '\0') 989 break; 990 } 991} 992 993/*- 994 * Parse a cyclic time specification, the format is as follows: 995 * 996 * [Dhh] or [Wd[Dhh]] or [Mdd[Dhh]] 997 * 998 * to rotate a logfile cyclic at 999 * 1000 * - every day (D) within a specific hour (hh) (hh = 0...23) 1001 * - once a week (W) at a specific day (d) OR (d = 0..6, 0 = Sunday) 1002 * - once a month (M) at a specific day (d) (d = 1..31,l|L) 1003 * 1004 * We don't accept a timezone specification; missing fields 1005 * are defaulted to the current date but time zero. 1006 */ 1007static time_t 1008parseDWM(char *s, char *errline) 1009{ 1010 char *t; 1011 time_t tsecs; 1012 struct tm tm, *tmp; 1013 long l; 1014 int nd; 1015 static int mtab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; 1016 int WMseen = 0; 1017 int Dseen = 0; 1018 1019 tmp = localtime(&timenow); 1020 tm = *tmp; 1021 1022 /* set no. of days per month */ 1023 1024 nd = mtab[tm.tm_mon]; 1025 1026 if (tm.tm_mon == 1) { 1027 if (((tm.tm_year + 1900) % 4 == 0) && 1028 ((tm.tm_year + 1900) % 100 != 0) && 1029 ((tm.tm_year + 1900) % 400 == 0)) { 1030 nd++; /* leap year, 29 days in february */ 1031 } 1032 } 1033 tm.tm_hour = tm.tm_min = tm.tm_sec = 0; 1034 1035 for (;;) { 1036 switch (*s) { 1037 case 'D': 1038 if (Dseen) 1039 return -1; 1040 Dseen++; 1041 s++; 1042 l = strtol(s, &t, 10); 1043 if (l < 0 || l > 23) 1044 return -1; 1045 tm.tm_hour = l; 1046 break; 1047 1048 case 'W': 1049 if (WMseen) 1050 return -1; 1051 WMseen++; 1052 s++; 1053 l = strtol(s, &t, 10); 1054 if (l < 0 || l > 6) 1055 return -1; 1056 if (l != tm.tm_wday) { 1057 int save; 1058 1059 if (l < tm.tm_wday) { 1060 save = 6 - tm.tm_wday; 1061 save += (l + 1); 1062 } else { 1063 save = l - tm.tm_wday; 1064 } 1065 1066 tm.tm_mday += save; 1067 1068 if (tm.tm_mday > nd) { 1069 tm.tm_mon++; 1070 tm.tm_mday = tm.tm_mday - nd; 1071 } 1072 } 1073 break; 1074 1075 case 'M': 1076 if (WMseen) 1077 return -1; 1078 WMseen++; 1079 s++; 1080 if (tolower(*s) == 'l') { 1081 tm.tm_mday = nd; 1082 s++; 1083 t = s; 1084 } else { 1085 l = strtol(s, &t, 10); 1086 if (l < 1 || l > 31) 1087 return -1; 1088 1089 if (l > nd) 1090 return -1; 1091 tm.tm_mday = l; 1092 } 1093 break; 1094 1095 default: 1096 return (-1); 1097 break; 1098 } 1099 1100 if (*t == '\0' || isspace(*t)) 1101 break; 1102 else 1103 s = t; 1104 } 1105 if ((tsecs = mktime(&tm)) == -1) 1106 errx(1, "nonexistent time:\n%s", errline); 1107 return tsecs; 1108} 1109