newsyslog.c revision 107737
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 107737 2002-12-11 01:19:56Z 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 *cp, *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 (strlen(line) == 0)) 311 continue; 312 errline = strdup(line); 313 for (cp = line + 1; *cp != '\0'; cp++) { 314 if (*cp != '#') 315 continue; 316 if (*(cp - 1) == '\\') { 317 strcpy(cp - 1, cp); 318 cp--; 319 continue; 320 } 321 *cp = '\0'; 322 break; 323 } 324 325 q = parse = missing_field(sob(line), errline); 326 parse = son(line); 327 if (!*parse) 328 errx(1, "malformed line (missing fields):\n%s", 329 errline); 330 *parse = '\0'; 331 332 if (*files) { 333 for (p = files; *p; ++p) 334 if (strcmp(*p, q) == 0) 335 break; 336 if (!*p) 337 continue; 338 } 339 340 if (!first) { 341 if ((working = malloc(sizeof(struct conf_entry))) == 342 NULL) 343 err(1, "malloc"); 344 first = working; 345 } else { 346 if ((working->next = malloc(sizeof(struct conf_entry))) 347 == NULL) 348 err(1, "malloc"); 349 working = working->next; 350 } 351 if ((working->log = strdup(q)) == NULL) 352 err(1, "strdup"); 353 354 q = parse = missing_field(sob(++parse), errline); 355 parse = son(parse); 356 if (!*parse) 357 errx(1, "malformed line (missing fields):\n%s", 358 errline); 359 *parse = '\0'; 360 if ((group = strchr(q, ':')) != NULL || 361 (group = strrchr(q, '.')) != NULL) { 362 *group++ = '\0'; 363 if (*q) { 364 if (!(isnumber(*q))) { 365 if ((pass = getpwnam(q)) == NULL) 366 errx(1, 367 "error in config file; unknown user:\n%s", 368 errline); 369 working->uid = pass->pw_uid; 370 } else 371 working->uid = atoi(q); 372 } else 373 working->uid = NONE; 374 375 q = group; 376 if (*q) { 377 if (!(isnumber(*q))) { 378 if ((grp = getgrnam(q)) == NULL) 379 errx(1, 380 "error in config file; unknown group:\n%s", 381 errline); 382 working->gid = grp->gr_gid; 383 } else 384 working->gid = atoi(q); 385 } else 386 working->gid = NONE; 387 388 q = parse = missing_field(sob(++parse), errline); 389 parse = son(parse); 390 if (!*parse) 391 errx(1, "malformed line (missing fields):\n%s", 392 errline); 393 *parse = '\0'; 394 } else 395 working->uid = working->gid = NONE; 396 397 if (!sscanf(q, "%o", &working->permissions)) 398 errx(1, "error in config file; bad permissions:\n%s", 399 errline); 400 401 q = parse = missing_field(sob(++parse), errline); 402 parse = son(parse); 403 if (!*parse) 404 errx(1, "malformed line (missing fields):\n%s", 405 errline); 406 *parse = '\0'; 407 if (!sscanf(q, "%d", &working->numlogs)) 408 errx(1, "error in config file; bad number:\n%s", 409 errline); 410 411 q = parse = missing_field(sob(++parse), errline); 412 parse = son(parse); 413 if (!*parse) 414 errx(1, "malformed line (missing fields):\n%s", 415 errline); 416 *parse = '\0'; 417 if (isdigit(*q)) 418 working->size = atoi(q); 419 else 420 working->size = -1; 421 422 working->flags = 0; 423 q = parse = missing_field(sob(++parse), errline); 424 parse = son(parse); 425 eol = !*parse; 426 *parse = '\0'; 427 { 428 char *ep; 429 u_long ul; 430 431 ul = strtoul(q, &ep, 10); 432 if (ep == q) 433 working->hours = 0; 434 else if (*ep == '*') 435 working->hours = -1; 436 else if (ul > INT_MAX) 437 errx(1, "interval is too large:\n%s", errline); 438 else 439 working->hours = ul; 440 441 if (*ep != '\0' && *ep != '@' && *ep != '*' && 442 *ep != '$') 443 errx(1, "malformed interval/at:\n%s", errline); 444 if (*ep == '@') { 445 if ((working->trim_at = parse8601(ep + 1, errline)) 446 == (time_t) - 1) 447 errx(1, "malformed at:\n%s", errline); 448 working->flags |= CE_TRIMAT; 449 } else if (*ep == '$') { 450 if ((working->trim_at = parseDWM(ep + 1, errline)) 451 == (time_t) - 1) 452 errx(1, "malformed at:\n%s", errline); 453 working->flags |= CE_TRIMAT; 454 } 455 } 456 457 if (eol) 458 q = NULL; 459 else { 460 q = parse = sob(++parse); /* Optional field */ 461 parse = son(parse); 462 if (!*parse) 463 eol = 1; 464 *parse = '\0'; 465 } 466 467 while (q && *q && !isspace(*q)) { 468 if ((*q == 'Z') || (*q == 'z')) 469 working->flags |= CE_COMPACT; 470 else if ((*q == 'J') || (*q == 'j')) 471 working->flags |= CE_BZCOMPACT; 472 else if ((*q == 'B') || (*q == 'b')) 473 working->flags |= CE_BINARY; 474 else if ((*q == 'G') || (*q == 'c')) 475 working->flags |= CE_GLOB; 476 else if (*q != '-') 477 errx(1, "illegal flag in config file -- %c", 478 *q); 479 q++; 480 } 481 482 if (eol) 483 q = NULL; 484 else { 485 q = parse = sob(++parse); /* Optional field */ 486 parse = son(parse); 487 if (!*parse) 488 eol = 1; 489 *parse = '\0'; 490 } 491 492 working->pid_file = NULL; 493 if (q && *q) { 494 if (*q == '/') 495 working->pid_file = strdup(q); 496 else if (isdigit(*q)) 497 goto got_sig; 498 else 499 errx(1, 500 "illegal pid file or signal number in config file:\n%s", 501 errline); 502 } 503 if (eol) 504 q = NULL; 505 else { 506 q = parse = sob(++parse); /* Optional field */ 507 *(parse = son(parse)) = '\0'; 508 } 509 510 working->sig = SIGHUP; 511 if (q && *q) { 512 if (isdigit(*q)) { 513 got_sig: 514 working->sig = atoi(q); 515 } else { 516 err_sig: 517 errx(1, 518 "illegal signal number in config file:\n%s", 519 errline); 520 } 521 if (working->sig < 1 || working->sig >= NSIG) 522 goto err_sig; 523 } 524 free(errline); 525 } 526 if (working) 527 working->next = (struct conf_entry *) NULL; 528 (void) fclose(f); 529 return (first); 530} 531 532static char * 533missing_field(char *p, char *errline) 534{ 535 536 if (!p || !*p) 537 errx(1, "missing field in config file:\n%s", errline); 538 return (p); 539} 540 541static void 542dotrim(char *log, const char *pid_file, int numdays, int flags, int perm, 543 int owner_uid, int group_gid, int sig) 544{ 545 char dirpart[MAXPATHLEN], namepart[MAXPATHLEN]; 546 char file1[MAXPATHLEN], file2[MAXPATHLEN]; 547 char zfile1[MAXPATHLEN], zfile2[MAXPATHLEN]; 548 char jfile1[MAXPATHLEN]; 549 char tfile[MAXPATHLEN]; 550 int notified, need_notification, fd, _numdays; 551 struct stat st; 552 pid_t pid; 553 554#ifdef _IBMR2 555 /* 556 * AIX 3.1 has a broken fchown- if the owner_uid is -1, it will 557 * actually change it to be owned by uid -1, instead of leaving it 558 * as is, as it is supposed to. 559 */ 560 if (owner_uid == -1) 561 owner_uid = geteuid(); 562#endif 563 564 if (archtodir) { 565 char *p; 566 567 /* build complete name of archive directory into dirpart */ 568 if (*archdirname == '/') { /* absolute */ 569 strlcpy(dirpart, archdirname, sizeof(dirpart)); 570 } else { /* relative */ 571 /* get directory part of logfile */ 572 strlcpy(dirpart, log, sizeof(dirpart)); 573 if ((p = rindex(dirpart, '/')) == NULL) 574 dirpart[0] = '\0'; 575 else 576 *(p + 1) = '\0'; 577 strlcat(dirpart, archdirname, sizeof(dirpart)); 578 } 579 580 /* check if archive directory exists, if not, create it */ 581 if (lstat(dirpart, &st)) 582 createdir(dirpart); 583 584 /* get filename part of logfile */ 585 if ((p = rindex(log, '/')) == NULL) 586 strlcpy(namepart, log, sizeof(namepart)); 587 else 588 strlcpy(namepart, p + 1, sizeof(namepart)); 589 590 /* name of oldest log */ 591 (void) snprintf(file1, sizeof(file1), "%s/%s.%d", dirpart, 592 namepart, numdays); 593 (void) snprintf(zfile1, sizeof(zfile1), "%s%s", file1, 594 COMPRESS_POSTFIX); 595 snprintf(jfile1, sizeof(jfile1), "%s%s", file1, 596 BZCOMPRESS_POSTFIX); 597 } else { 598 /* name of oldest log */ 599 (void) snprintf(file1, sizeof(file1), "%s.%d", log, numdays); 600 (void) snprintf(zfile1, sizeof(zfile1), "%s%s", file1, 601 COMPRESS_POSTFIX); 602 snprintf(jfile1, sizeof(jfile1), "%s%s", file1, 603 BZCOMPRESS_POSTFIX); 604 } 605 606 if (noaction) { 607 printf("rm -f %s\n", file1); 608 printf("rm -f %s\n", zfile1); 609 printf("rm -f %s\n", jfile1); 610 } else { 611 (void) unlink(file1); 612 (void) unlink(zfile1); 613 (void) unlink(jfile1); 614 } 615 616 /* Move down log files */ 617 _numdays = numdays; /* preserve */ 618 while (numdays--) { 619 620 (void) strlcpy(file2, file1, sizeof(file2)); 621 622 if (archtodir) 623 (void) snprintf(file1, sizeof(file1), "%s/%s.%d", 624 dirpart, namepart, numdays); 625 else 626 (void) snprintf(file1, sizeof(file1), "%s.%d", log, 627 numdays); 628 629 (void) strlcpy(zfile1, file1, sizeof(zfile1)); 630 (void) strlcpy(zfile2, file2, sizeof(zfile2)); 631 if (lstat(file1, &st)) { 632 (void) strlcat(zfile1, COMPRESS_POSTFIX, 633 sizeof(zfile1)); 634 (void) strlcat(zfile2, COMPRESS_POSTFIX, 635 sizeof(zfile2)); 636 if (lstat(zfile1, &st)) { 637 strlcpy(zfile1, file1, sizeof(zfile1)); 638 strlcpy(zfile2, file2, sizeof(zfile2)); 639 strlcat(zfile1, BZCOMPRESS_POSTFIX, 640 sizeof(zfile1)); 641 strlcat(zfile2, BZCOMPRESS_POSTFIX, 642 sizeof(zfile2)); 643 if (lstat(zfile1, &st)) 644 continue; 645 } 646 } 647 if (noaction) { 648 printf("mv %s %s\n", zfile1, zfile2); 649 printf("chmod %o %s\n", perm, zfile2); 650 printf("chown %d:%d %s\n", 651 owner_uid, group_gid, zfile2); 652 } else { 653 (void) rename(zfile1, zfile2); 654 (void) chmod(zfile2, perm); 655 (void) chown(zfile2, owner_uid, group_gid); 656 } 657 } 658 if (!noaction && !(flags & CE_BINARY)) 659 (void) log_trim(log); /* Report the trimming to the old log */ 660 661 if (!_numdays) { 662 if (noaction) 663 printf("rm %s\n", log); 664 else 665 (void) unlink(log); 666 } else { 667 if (noaction) 668 printf("mv %s to %s\n", log, file1); 669 else { 670 if (archtodir) 671 movefile(log, file1, perm, owner_uid, 672 group_gid); 673 else 674 (void) rename(log, file1); 675 } 676 } 677 678 if (noaction) 679 printf("Start new log..."); 680 else { 681 strlcpy(tfile, log, sizeof(tfile)); 682 strlcat(tfile, ".XXXXXX", sizeof(tfile)); 683 mkstemp(tfile); 684 fd = creat(tfile, perm); 685 if (fd < 0) 686 err(1, "can't start new log"); 687 if (fchown(fd, owner_uid, group_gid)) 688 err(1, "can't chmod new log file"); 689 (void) close(fd); 690 if (!(flags & CE_BINARY)) 691 if (log_trim(tfile)) /* Add status message */ 692 err(1, "can't add status message to log"); 693 } 694 if (noaction) 695 printf("chmod %o %s...\n", perm, log); 696 else { 697 (void) chmod(tfile, perm); 698 if (rename(tfile, log) < 0) { 699 err(1, "can't start new log"); 700 (void) unlink(tfile); 701 } 702 } 703 704 pid = 0; 705 need_notification = notified = 0; 706 if (pid_file != NULL) { 707 need_notification = 1; 708 pid = get_pid(pid_file); 709 } 710 if (pid) { 711 if (noaction) { 712 notified = 1; 713 printf("kill -%d %d\n", sig, (int) pid); 714 } else if (kill(pid, sig)) 715 warn("can't notify daemon, pid %d", (int) pid); 716 else { 717 notified = 1; 718 if (verbose) 719 printf("daemon pid %d notified\n", (int) pid); 720 } 721 } 722 if ((flags & CE_COMPACT) || (flags & CE_BZCOMPACT)) { 723 if (need_notification && !notified) 724 warnx( 725 "log %s not compressed because daemon not notified", 726 log); 727 else if (noaction) 728 printf("Compress %s.0\n", log); 729 else { 730 if (notified) { 731 if (verbose) 732 printf("small pause to allow daemon to close log\n"); 733 sleep(10); 734 } 735 if (archtodir) { 736 (void) snprintf(file1, sizeof(file1), "%s/%s", 737 dirpart, namepart); 738 if (flags & CE_COMPACT) 739 compress_log(file1); 740 else if (flags & CE_BZCOMPACT) 741 bzcompress_log(file1); 742 } else { 743 if (flags & CE_COMPACT) 744 compress_log(log); 745 else if (flags & CE_BZCOMPACT) 746 bzcompress_log(log); 747 } 748 } 749 } 750} 751 752/* Log the fact that the logs were turned over */ 753static int 754log_trim(char *log) 755{ 756 FILE *f; 757 758 if ((f = fopen(log, "a")) == NULL) 759 return (-1); 760 fprintf(f, "%s %s newsyslog[%d]: logfile turned over\n", 761 daytime, hostname, (int) getpid()); 762 if (fclose(f) == EOF) 763 err(1, "log_trim: fclose:"); 764 return (0); 765} 766 767/* Fork of gzip to compress the old log file */ 768static void 769compress_log(char *log) 770{ 771 pid_t pid; 772 char tmp[MAXPATHLEN]; 773 774 (void) snprintf(tmp, sizeof(tmp), "%s.0", log); 775 pid = fork(); 776 if (pid < 0) 777 err(1, "gzip fork"); 778 else if (!pid) { 779 (void) execl(_PATH_GZIP, _PATH_GZIP, "-f", tmp, (char *)0); 780 err(1, _PATH_GZIP); 781 } 782} 783 784/* Fork of bzip2 to compress the old log file */ 785static void 786bzcompress_log(char *log) 787{ 788 pid_t pid; 789 char tmp[MAXPATHLEN]; 790 791 snprintf(tmp, sizeof(tmp), "%s.0", log); 792 pid = fork(); 793 if (pid < 0) 794 err(1, "bzip2 fork"); 795 else if (!pid) { 796 execl(_PATH_BZIP2, _PATH_BZIP2, "-f", tmp, (char *)0); 797 err(1, _PATH_BZIP2); 798 } 799} 800 801/* Return size in kilobytes of a file */ 802static int 803sizefile(char *file) 804{ 805 struct stat sb; 806 807 if (stat(file, &sb) < 0) 808 return (-1); 809 return (kbytes(dbtob(sb.st_blocks))); 810} 811 812/* Return the age of old log file (file.0) */ 813static int 814age_old_log(char *file) 815{ 816 struct stat sb; 817 char tmp[MAXPATHLEN + sizeof(".0") + sizeof(COMPRESS_POSTFIX) + 1]; 818 819 if (archtodir) { 820 char *p; 821 822 /* build name of archive directory into tmp */ 823 if (*archdirname == '/') { /* absolute */ 824 strlcpy(tmp, archdirname, sizeof(tmp)); 825 } else { /* relative */ 826 /* get directory part of logfile */ 827 strlcpy(tmp, file, sizeof(tmp)); 828 if ((p = rindex(tmp, '/')) == NULL) 829 tmp[0] = '\0'; 830 else 831 *(p + 1) = '\0'; 832 strlcat(tmp, archdirname, sizeof(tmp)); 833 } 834 835 strlcat(tmp, "/", sizeof(tmp)); 836 837 /* get filename part of logfile */ 838 if ((p = rindex(file, '/')) == NULL) 839 strlcat(tmp, file, sizeof(tmp)); 840 else 841 strlcat(tmp, p + 1, sizeof(tmp)); 842 } else { 843 (void) strlcpy(tmp, file, sizeof(tmp)); 844 } 845 846 if (stat(strcat(tmp, ".0"), &sb) < 0) 847 if (stat(strcat(tmp, COMPRESS_POSTFIX), &sb) < 0) 848 return (-1); 849 return ((int) (timenow - sb.st_mtime + 1800) / 3600); 850} 851 852static pid_t 853get_pid(const char *pid_file) 854{ 855 FILE *f; 856 char line[BUFSIZ]; 857 pid_t pid = 0; 858 859 if ((f = fopen(pid_file, "r")) == NULL) 860 warn("can't open %s pid file to restart a daemon", 861 pid_file); 862 else { 863 if (fgets(line, BUFSIZ, f)) { 864 pid = atol(line); 865 if (pid < MIN_PID || pid > MAX_PID) { 866 warnx("preposterous process number: %d", 867 (int)pid); 868 pid = 0; 869 } 870 } else 871 warn("can't read %s pid file to restart a daemon", 872 pid_file); 873 (void) fclose(f); 874 } 875 return pid; 876} 877 878/* Skip Over Blanks */ 879char * 880sob(char *p) 881{ 882 while (p && *p && isspace(*p)) 883 p++; 884 return (p); 885} 886 887/* Skip Over Non-Blanks */ 888char * 889son(char *p) 890{ 891 while (p && *p && !isspace(*p)) 892 p++; 893 return (p); 894} 895 896/* 897 * Parse a limited subset of ISO 8601. The specific format is as follows: 898 * 899 * [CC[YY[MM[DD]]]][THH[MM[SS]]] (where `T' is the literal letter) 900 * 901 * We don't accept a timezone specification; missing fields (including timezone) 902 * are defaulted to the current date but time zero. 903 */ 904static time_t 905parse8601(char *s, char *errline) 906{ 907 char *t; 908 time_t tsecs; 909 struct tm tm, *tmp; 910 u_long ul; 911 912 tmp = localtime(&timenow); 913 tm = *tmp; 914 915 tm.tm_hour = tm.tm_min = tm.tm_sec = 0; 916 917 ul = strtoul(s, &t, 10); 918 if (*t != '\0' && *t != 'T') 919 return -1; 920 921 /* 922 * Now t points either to the end of the string (if no time was 923 * provided) or to the letter `T' which separates date and time in 924 * ISO 8601. The pointer arithmetic is the same for either case. 925 */ 926 switch (t - s) { 927 case 8: 928 tm.tm_year = ((ul / 1000000) - 19) * 100; 929 ul = ul % 1000000; 930 case 6: 931 tm.tm_year -= tm.tm_year % 100; 932 tm.tm_year += ul / 10000; 933 ul = ul % 10000; 934 case 4: 935 tm.tm_mon = (ul / 100) - 1; 936 ul = ul % 100; 937 case 2: 938 tm.tm_mday = ul; 939 case 0: 940 break; 941 default: 942 return -1; 943 } 944 945 /* sanity check */ 946 if (tm.tm_year < 70 || tm.tm_mon < 0 || tm.tm_mon > 12 947 || tm.tm_mday < 1 || tm.tm_mday > 31) 948 return -1; 949 950 if (*t != '\0') { 951 s = ++t; 952 ul = strtoul(s, &t, 10); 953 if (*t != '\0' && !isspace(*t)) 954 return -1; 955 956 switch (t - s) { 957 case 6: 958 tm.tm_sec = ul % 100; 959 ul /= 100; 960 case 4: 961 tm.tm_min = ul % 100; 962 ul /= 100; 963 case 2: 964 tm.tm_hour = ul; 965 case 0: 966 break; 967 default: 968 return -1; 969 } 970 971 /* sanity check */ 972 if (tm.tm_sec < 0 || tm.tm_sec > 60 || tm.tm_min < 0 973 || tm.tm_min > 59 || tm.tm_hour < 0 || tm.tm_hour > 23) 974 return -1; 975 } 976 if ((tsecs = mktime(&tm)) == -1) 977 errx(1, "nonexistent time:\n%s", errline); 978 return tsecs; 979} 980 981/* physically move file */ 982static void 983movefile(char *from, char *to, int perm, int owner_uid, int group_gid) 984{ 985 FILE *src, *dst; 986 int c; 987 988 if ((src = fopen(from, "r")) == NULL) 989 err(1, "can't fopen %s for reading", from); 990 if ((dst = fopen(to, "w")) == NULL) 991 err(1, "can't fopen %s for writing", to); 992 if (fchown(fileno(dst), owner_uid, group_gid)) 993 err(1, "can't fchown %s", to); 994 if (fchmod(fileno(dst), perm)) 995 err(1, "can't fchmod %s", to); 996 997 while ((c = getc(src)) != EOF) { 998 if ((putc(c, dst)) == EOF) 999 err(1, "error writing to %s", to); 1000 } 1001 1002 if (ferror(src)) 1003 err(1, "error reading from %s", from); 1004 if ((fclose(src)) != 0) 1005 err(1, "can't fclose %s", to); 1006 if ((fclose(dst)) != 0) 1007 err(1, "can't fclose %s", from); 1008 if ((unlink(from)) != 0) 1009 err(1, "can't unlink %s", from); 1010} 1011 1012/* create one or more directory components of a path */ 1013static void 1014createdir(char *dirpart) 1015{ 1016 char *s, *d; 1017 char mkdirpath[MAXPATHLEN]; 1018 struct stat st; 1019 1020 s = dirpart; 1021 d = mkdirpath; 1022 1023 for (;;) { 1024 *d++ = *s++; 1025 if (*s == '/' || *s == '\0') { 1026 *d = '\0'; 1027 if (lstat(mkdirpath, &st)) 1028 mkdir(mkdirpath, 0755); 1029 } 1030 if (*s == '\0') 1031 break; 1032 } 1033} 1034 1035/*- 1036 * Parse a cyclic time specification, the format is as follows: 1037 * 1038 * [Dhh] or [Wd[Dhh]] or [Mdd[Dhh]] 1039 * 1040 * to rotate a logfile cyclic at 1041 * 1042 * - every day (D) within a specific hour (hh) (hh = 0...23) 1043 * - once a week (W) at a specific day (d) OR (d = 0..6, 0 = Sunday) 1044 * - once a month (M) at a specific day (d) (d = 1..31,l|L) 1045 * 1046 * We don't accept a timezone specification; missing fields 1047 * are defaulted to the current date but time zero. 1048 */ 1049static time_t 1050parseDWM(char *s, char *errline) 1051{ 1052 char *t; 1053 time_t tsecs; 1054 struct tm tm, *tmp; 1055 long l; 1056 int nd; 1057 static int mtab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; 1058 int WMseen = 0; 1059 int Dseen = 0; 1060 1061 tmp = localtime(&timenow); 1062 tm = *tmp; 1063 1064 /* set no. of days per month */ 1065 1066 nd = mtab[tm.tm_mon]; 1067 1068 if (tm.tm_mon == 1) { 1069 if (((tm.tm_year + 1900) % 4 == 0) && 1070 ((tm.tm_year + 1900) % 100 != 0) && 1071 ((tm.tm_year + 1900) % 400 == 0)) { 1072 nd++; /* leap year, 29 days in february */ 1073 } 1074 } 1075 tm.tm_hour = tm.tm_min = tm.tm_sec = 0; 1076 1077 for (;;) { 1078 switch (*s) { 1079 case 'D': 1080 if (Dseen) 1081 return -1; 1082 Dseen++; 1083 s++; 1084 l = strtol(s, &t, 10); 1085 if (l < 0 || l > 23) 1086 return -1; 1087 tm.tm_hour = l; 1088 break; 1089 1090 case 'W': 1091 if (WMseen) 1092 return -1; 1093 WMseen++; 1094 s++; 1095 l = strtol(s, &t, 10); 1096 if (l < 0 || l > 6) 1097 return -1; 1098 if (l != tm.tm_wday) { 1099 int save; 1100 1101 if (l < tm.tm_wday) { 1102 save = 6 - tm.tm_wday; 1103 save += (l + 1); 1104 } else { 1105 save = l - tm.tm_wday; 1106 } 1107 1108 tm.tm_mday += save; 1109 1110 if (tm.tm_mday > nd) { 1111 tm.tm_mon++; 1112 tm.tm_mday = tm.tm_mday - nd; 1113 } 1114 } 1115 break; 1116 1117 case 'M': 1118 if (WMseen) 1119 return -1; 1120 WMseen++; 1121 s++; 1122 if (tolower(*s) == 'l') { 1123 tm.tm_mday = nd; 1124 s++; 1125 t = s; 1126 } else { 1127 l = strtol(s, &t, 10); 1128 if (l < 1 || l > 31) 1129 return -1; 1130 1131 if (l > nd) 1132 return -1; 1133 tm.tm_mday = l; 1134 } 1135 break; 1136 1137 default: 1138 return (-1); 1139 break; 1140 } 1141 1142 if (*t == '\0' || isspace(*t)) 1143 break; 1144 else 1145 s = t; 1146 } 1147 if ((tsecs = mktime(&tm)) == -1) 1148 errx(1, "nonexistent time:\n%s", errline); 1149 return tsecs; 1150} 1151