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