newsyslog.c revision 94352
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 94352 2002-04-10 10:38:44Z sheldonh $"; 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 char tfile[MAXPATHLEN]; 517 int notified, need_notification, fd, _numdays; 518 struct stat st; 519 pid_t pid; 520 521#ifdef _IBMR2 522 /* 523 * AIX 3.1 has a broken fchown- if the owner_uid is -1, it will 524 * actually change it to be owned by uid -1, instead of leaving it 525 * as is, as it is supposed to. 526 */ 527 if (owner_uid == -1) 528 owner_uid = geteuid(); 529#endif 530 531 if (archtodir) { 532 char *p; 533 534 /* build complete name of archive directory into dirpart */ 535 if (*archdirname == '/') { /* absolute */ 536 strlcpy(dirpart, archdirname, sizeof(dirpart)); 537 } else { /* relative */ 538 /* get directory part of logfile */ 539 strlcpy(dirpart, log, sizeof(dirpart)); 540 if ((p = rindex(dirpart, '/')) == NULL) 541 dirpart[0] = '\0'; 542 else 543 *(p + 1) = '\0'; 544 strlcat(dirpart, archdirname, sizeof(dirpart)); 545 } 546 547 /* check if archive directory exists, if not, create it */ 548 if (lstat(dirpart, &st)) 549 createdir(dirpart); 550 551 /* get filename part of logfile */ 552 if ((p = rindex(log, '/')) == NULL) 553 strlcpy(namepart, log, sizeof(namepart)); 554 else 555 strlcpy(namepart, p + 1, sizeof(namepart)); 556 557 /* name of oldest log */ 558 (void) snprintf(file1, sizeof(file1), "%s/%s.%d", dirpart, 559 namepart, numdays); 560 (void) snprintf(zfile1, sizeof(zfile1), "%s%s", file1, 561 COMPRESS_POSTFIX); 562 snprintf(jfile1, sizeof(jfile1), "%s%s", file1, 563 BZCOMPRESS_POSTFIX); 564 } else { 565 /* name of oldest log */ 566 (void) snprintf(file1, sizeof(file1), "%s.%d", log, numdays); 567 (void) snprintf(zfile1, sizeof(zfile1), "%s%s", file1, 568 COMPRESS_POSTFIX); 569 snprintf(jfile1, sizeof(jfile1), "%s%s", file1, 570 BZCOMPRESS_POSTFIX); 571 } 572 573 if (noaction) { 574 printf("rm -f %s\n", file1); 575 printf("rm -f %s\n", zfile1); 576 printf("rm -f %s\n", jfile1); 577 } else { 578 (void) unlink(file1); 579 (void) unlink(zfile1); 580 (void) unlink(jfile1); 581 } 582 583 /* Move down log files */ 584 _numdays = numdays; /* preserve */ 585 while (numdays--) { 586 587 (void) strlcpy(file2, file1, sizeof(file2)); 588 589 if (archtodir) 590 (void) snprintf(file1, sizeof(file1), "%s/%s.%d", 591 dirpart, namepart, numdays); 592 else 593 (void) snprintf(file1, sizeof(file1), "%s.%d", log, 594 numdays); 595 596 (void) strlcpy(zfile1, file1, sizeof(zfile1)); 597 (void) strlcpy(zfile2, file2, sizeof(zfile2)); 598 if (lstat(file1, &st)) { 599 (void) strlcat(zfile1, COMPRESS_POSTFIX, 600 sizeof(zfile1)); 601 (void) strlcat(zfile2, COMPRESS_POSTFIX, 602 sizeof(zfile2)); 603 if (lstat(zfile1, &st)) { 604 strlcpy(zfile1, file1, sizeof(zfile1)); 605 strlcpy(zfile2, file2, sizeof(zfile2)); 606 strlcat(zfile1, BZCOMPRESS_POSTFIX, 607 sizeof(zfile1)); 608 strlcat(zfile2, BZCOMPRESS_POSTFIX, 609 sizeof(zfile2)); 610 if (lstat(zfile1, &st)) 611 continue; 612 } 613 } 614 if (noaction) { 615 printf("mv %s %s\n", zfile1, zfile2); 616 printf("chmod %o %s\n", perm, zfile2); 617 printf("chown %d:%d %s\n", 618 owner_uid, group_gid, zfile2); 619 } else { 620 (void) rename(zfile1, zfile2); 621 (void) chmod(zfile2, perm); 622 (void) chown(zfile2, owner_uid, group_gid); 623 } 624 } 625 if (!noaction && !(flags & CE_BINARY)) 626 (void) log_trim(log); /* Report the trimming to the old log */ 627 628 if (!_numdays) { 629 if (noaction) 630 printf("rm %s\n", log); 631 else 632 (void) unlink(log); 633 } else { 634 if (noaction) 635 printf("mv %s to %s\n", log, file1); 636 else { 637 if (archtodir) 638 movefile(log, file1, perm, owner_uid, 639 group_gid); 640 else 641 (void) rename(log, file1); 642 } 643 } 644 645 if (noaction) 646 printf("Start new log..."); 647 else { 648 strlcpy(tfile, log, sizeof(tfile)); 649 strlcat(tfile, ".XXXXXX", sizeof(tfile)); 650 mkstemp(tfile); 651 fd = creat(tfile, perm); 652 if (fd < 0) 653 err(1, "can't start new log"); 654 if (fchown(fd, owner_uid, group_gid)) 655 err(1, "can't chmod new log file"); 656 (void) close(fd); 657 if (!(flags & CE_BINARY)) 658 if (log_trim(tfile)) /* Add status message */ 659 err(1, "can't add status message to log"); 660 } 661 if (noaction) 662 printf("chmod %o %s...\n", perm, log); 663 else { 664 (void) chmod(tfile, perm); 665 if (rename(tfile, log) < 0) { 666 err(1, "can't start new log"); 667 (void) unlink(tfile); 668 } 669 } 670 671 pid = 0; 672 need_notification = notified = 0; 673 if (pid_file != NULL) { 674 need_notification = 1; 675 pid = get_pid(pid_file); 676 } 677 if (pid) { 678 if (noaction) { 679 notified = 1; 680 printf("kill -%d %d\n", sig, (int) pid); 681 } else if (kill(pid, sig)) 682 warn("can't notify daemon, pid %d", (int) pid); 683 else { 684 notified = 1; 685 if (verbose) 686 printf("daemon pid %d notified\n", (int) pid); 687 } 688 } 689 if ((flags & CE_COMPACT) || (flags & CE_BZCOMPACT)) { 690 if (need_notification && !notified) 691 warnx( 692 "log %s not compressed because daemon not notified", 693 log); 694 else if (noaction) 695 printf("Compress %s.0\n", log); 696 else { 697 if (notified) { 698 if (verbose) 699 printf("small pause to allow daemon to close log\n"); 700 sleep(10); 701 } 702 if (archtodir) { 703 (void) snprintf(file1, sizeof(file1), "%s/%s", 704 dirpart, namepart); 705 if (flags & CE_COMPACT) 706 compress_log(file1); 707 else if (flags & CE_BZCOMPACT) 708 bzcompress_log(file1); 709 } else { 710 if (flags & CE_COMPACT) 711 compress_log(log); 712 else if (flags & CE_BZCOMPACT) 713 bzcompress_log(log); 714 } 715 } 716 } 717} 718 719/* Log the fact that the logs were turned over */ 720static int 721log_trim(char *log) 722{ 723 FILE *f; 724 725 if ((f = fopen(log, "a")) == NULL) 726 return (-1); 727 fprintf(f, "%s %s newsyslog[%d]: logfile turned over\n", 728 daytime, hostname, (int) getpid()); 729 if (fclose(f) == EOF) 730 err(1, "log_trim: fclose:"); 731 return (0); 732} 733 734/* Fork of gzip to compress the old log file */ 735static void 736compress_log(char *log) 737{ 738 pid_t pid; 739 char tmp[MAXPATHLEN]; 740 741 (void) snprintf(tmp, sizeof(tmp), "%s.0", log); 742 pid = fork(); 743 if (pid < 0) 744 err(1, "gzip fork"); 745 else if (!pid) { 746 (void) execl(_PATH_GZIP, _PATH_GZIP, "-f", tmp, (char *)0); 747 err(1, _PATH_GZIP); 748 } 749} 750 751/* Fork of bzip2 to compress the old log file */ 752static void 753bzcompress_log(char *log) 754{ 755 pid_t pid; 756 char tmp[MAXPATHLEN]; 757 758 snprintf(tmp, sizeof(tmp), "%s.0", log); 759 pid = fork(); 760 if (pid < 0) 761 err(1, "bzip2 fork"); 762 else if (!pid) { 763 execl(_PATH_BZIP2, _PATH_BZIP2, "-f", tmp, (char *)0); 764 err(1, _PATH_BZIP2); 765 } 766} 767 768/* Return size in kilobytes of a file */ 769static int 770sizefile(char *file) 771{ 772 struct stat sb; 773 774 if (stat(file, &sb) < 0) 775 return (-1); 776 return (kbytes(dbtob(sb.st_blocks))); 777} 778 779/* Return the age of old log file (file.0) */ 780static int 781age_old_log(char *file) 782{ 783 struct stat sb; 784 char tmp[MAXPATHLEN + sizeof(".0") + sizeof(COMPRESS_POSTFIX) + 1]; 785 786 if (archtodir) { 787 char *p; 788 789 /* build name of archive directory into tmp */ 790 if (*archdirname == '/') { /* absolute */ 791 strlcpy(tmp, archdirname, sizeof(tmp)); 792 } else { /* relative */ 793 /* get directory part of logfile */ 794 strlcpy(tmp, file, sizeof(tmp)); 795 if ((p = rindex(tmp, '/')) == NULL) 796 tmp[0] = '\0'; 797 else 798 *(p + 1) = '\0'; 799 strlcat(tmp, archdirname, sizeof(tmp)); 800 } 801 802 strlcat(tmp, "/", sizeof(tmp)); 803 804 /* get filename part of logfile */ 805 if ((p = rindex(file, '/')) == NULL) 806 strlcat(tmp, file, sizeof(tmp)); 807 else 808 strlcat(tmp, p + 1, sizeof(tmp)); 809 } else { 810 (void) strlcpy(tmp, file, sizeof(tmp)); 811 } 812 813 if (stat(strcat(tmp, ".0"), &sb) < 0) 814 if (stat(strcat(tmp, COMPRESS_POSTFIX), &sb) < 0) 815 return (-1); 816 return ((int) (timenow - sb.st_mtime + 1800) / 3600); 817} 818 819static pid_t 820get_pid(const char *pid_file) 821{ 822 FILE *f; 823 char line[BUFSIZ]; 824 pid_t pid = 0; 825 826 if ((f = fopen(pid_file, "r")) == NULL) 827 warn("can't open %s pid file to restart a daemon", 828 pid_file); 829 else { 830 if (fgets(line, BUFSIZ, f)) { 831 pid = atol(line); 832 if (pid < MIN_PID || pid > MAX_PID) { 833 warnx("preposterous process number: %d", 834 (int)pid); 835 pid = 0; 836 } 837 } else 838 warn("can't read %s pid file to restart a daemon", 839 pid_file); 840 (void) fclose(f); 841 } 842 return pid; 843} 844 845/* Skip Over Blanks */ 846char * 847sob(char *p) 848{ 849 while (p && *p && isspace(*p)) 850 p++; 851 return (p); 852} 853 854/* Skip Over Non-Blanks */ 855char * 856son(char *p) 857{ 858 while (p && *p && !isspace(*p)) 859 p++; 860 return (p); 861} 862 863/* 864 * Parse a limited subset of ISO 8601. The specific format is as follows: 865 * 866 * [CC[YY[MM[DD]]]][THH[MM[SS]]] (where `T' is the literal letter) 867 * 868 * We don't accept a timezone specification; missing fields (including timezone) 869 * are defaulted to the current date but time zero. 870 */ 871static time_t 872parse8601(char *s, char *errline) 873{ 874 char *t; 875 time_t tsecs; 876 struct tm tm, *tmp; 877 u_long ul; 878 879 tmp = localtime(&timenow); 880 tm = *tmp; 881 882 tm.tm_hour = tm.tm_min = tm.tm_sec = 0; 883 884 ul = strtoul(s, &t, 10); 885 if (*t != '\0' && *t != 'T') 886 return -1; 887 888 /* 889 * Now t points either to the end of the string (if no time was 890 * provided) or to the letter `T' which separates date and time in 891 * ISO 8601. The pointer arithmetic is the same for either case. 892 */ 893 switch (t - s) { 894 case 8: 895 tm.tm_year = ((ul / 1000000) - 19) * 100; 896 ul = ul % 1000000; 897 case 6: 898 tm.tm_year -= tm.tm_year % 100; 899 tm.tm_year += ul / 10000; 900 ul = ul % 10000; 901 case 4: 902 tm.tm_mon = (ul / 100) - 1; 903 ul = ul % 100; 904 case 2: 905 tm.tm_mday = ul; 906 case 0: 907 break; 908 default: 909 return -1; 910 } 911 912 /* sanity check */ 913 if (tm.tm_year < 70 || tm.tm_mon < 0 || tm.tm_mon > 12 914 || tm.tm_mday < 1 || tm.tm_mday > 31) 915 return -1; 916 917 if (*t != '\0') { 918 s = ++t; 919 ul = strtoul(s, &t, 10); 920 if (*t != '\0' && !isspace(*t)) 921 return -1; 922 923 switch (t - s) { 924 case 6: 925 tm.tm_sec = ul % 100; 926 ul /= 100; 927 case 4: 928 tm.tm_min = ul % 100; 929 ul /= 100; 930 case 2: 931 tm.tm_hour = ul; 932 case 0: 933 break; 934 default: 935 return -1; 936 } 937 938 /* sanity check */ 939 if (tm.tm_sec < 0 || tm.tm_sec > 60 || tm.tm_min < 0 940 || tm.tm_min > 59 || tm.tm_hour < 0 || tm.tm_hour > 23) 941 return -1; 942 } 943 if ((tsecs = mktime(&tm)) == -1) 944 errx(1, "nonexistent time:\n%s", errline); 945 return tsecs; 946} 947 948/* physically move file */ 949static void 950movefile(char *from, char *to, int perm, int owner_uid, int group_gid) 951{ 952 FILE *src, *dst; 953 int c; 954 955 if ((src = fopen(from, "r")) == NULL) 956 err(1, "can't fopen %s for reading", from); 957 if ((dst = fopen(to, "w")) == NULL) 958 err(1, "can't fopen %s for writing", to); 959 if (fchown(fileno(dst), owner_uid, group_gid)) 960 err(1, "can't fchown %s", to); 961 if (fchmod(fileno(dst), perm)) 962 err(1, "can't fchmod %s", to); 963 964 while ((c = getc(src)) != EOF) { 965 if ((putc(c, dst)) == EOF) 966 err(1, "error writing to %s", to); 967 } 968 969 if (ferror(src)) 970 err(1, "error reading from %s", from); 971 if ((fclose(src)) != 0) 972 err(1, "can't fclose %s", to); 973 if ((fclose(dst)) != 0) 974 err(1, "can't fclose %s", from); 975 if ((unlink(from)) != 0) 976 err(1, "can't unlink %s", from); 977} 978 979/* create one or more directory components of a path */ 980static void 981createdir(char *dirpart) 982{ 983 char *s, *d; 984 char mkdirpath[MAXPATHLEN]; 985 struct stat st; 986 987 s = dirpart; 988 d = mkdirpath; 989 990 for (;;) { 991 *d++ = *s++; 992 if (*s == '/' || *s == '\0') { 993 *d = '\0'; 994 if (lstat(mkdirpath, &st)) 995 mkdir(mkdirpath, 0755); 996 } 997 if (*s == '\0') 998 break; 999 } 1000} 1001 1002/*- 1003 * Parse a cyclic time specification, the format is as follows: 1004 * 1005 * [Dhh] or [Wd[Dhh]] or [Mdd[Dhh]] 1006 * 1007 * to rotate a logfile cyclic at 1008 * 1009 * - every day (D) within a specific hour (hh) (hh = 0...23) 1010 * - once a week (W) at a specific day (d) OR (d = 0..6, 0 = Sunday) 1011 * - once a month (M) at a specific day (d) (d = 1..31,l|L) 1012 * 1013 * We don't accept a timezone specification; missing fields 1014 * are defaulted to the current date but time zero. 1015 */ 1016static time_t 1017parseDWM(char *s, char *errline) 1018{ 1019 char *t; 1020 time_t tsecs; 1021 struct tm tm, *tmp; 1022 long l; 1023 int nd; 1024 static int mtab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; 1025 int WMseen = 0; 1026 int Dseen = 0; 1027 1028 tmp = localtime(&timenow); 1029 tm = *tmp; 1030 1031 /* set no. of days per month */ 1032 1033 nd = mtab[tm.tm_mon]; 1034 1035 if (tm.tm_mon == 1) { 1036 if (((tm.tm_year + 1900) % 4 == 0) && 1037 ((tm.tm_year + 1900) % 100 != 0) && 1038 ((tm.tm_year + 1900) % 400 == 0)) { 1039 nd++; /* leap year, 29 days in february */ 1040 } 1041 } 1042 tm.tm_hour = tm.tm_min = tm.tm_sec = 0; 1043 1044 for (;;) { 1045 switch (*s) { 1046 case 'D': 1047 if (Dseen) 1048 return -1; 1049 Dseen++; 1050 s++; 1051 l = strtol(s, &t, 10); 1052 if (l < 0 || l > 23) 1053 return -1; 1054 tm.tm_hour = l; 1055 break; 1056 1057 case 'W': 1058 if (WMseen) 1059 return -1; 1060 WMseen++; 1061 s++; 1062 l = strtol(s, &t, 10); 1063 if (l < 0 || l > 6) 1064 return -1; 1065 if (l != tm.tm_wday) { 1066 int save; 1067 1068 if (l < tm.tm_wday) { 1069 save = 6 - tm.tm_wday; 1070 save += (l + 1); 1071 } else { 1072 save = l - tm.tm_wday; 1073 } 1074 1075 tm.tm_mday += save; 1076 1077 if (tm.tm_mday > nd) { 1078 tm.tm_mon++; 1079 tm.tm_mday = tm.tm_mday - nd; 1080 } 1081 } 1082 break; 1083 1084 case 'M': 1085 if (WMseen) 1086 return -1; 1087 WMseen++; 1088 s++; 1089 if (tolower(*s) == 'l') { 1090 tm.tm_mday = nd; 1091 s++; 1092 t = s; 1093 } else { 1094 l = strtol(s, &t, 10); 1095 if (l < 1 || l > 31) 1096 return -1; 1097 1098 if (l > nd) 1099 return -1; 1100 tm.tm_mday = l; 1101 } 1102 break; 1103 1104 default: 1105 return (-1); 1106 break; 1107 } 1108 1109 if (*t == '\0' || isspace(*t)) 1110 break; 1111 else 1112 s = t; 1113 } 1114 if ((tsecs = mktime(&tm)) == -1) 1115 errx(1, "nonexistent time:\n%s", errline); 1116 return tsecs; 1117} 1118