newsyslog.c revision 59003
1/* 2 * This file contains changes from the Open Software Foundation. 3 */ 4 5/* 6 7Copyright 1988, 1989 by the Massachusetts Institute of Technology 8 9Permission to use, copy, modify, and distribute this software 10and its documentation for any purpose and without fee is 11hereby granted, provided that the above copyright notice 12appear in all copies and that both that copyright notice and 13this permission notice appear in supporting documentation, 14and that the names of M.I.T. and the M.I.T. S.I.P.B. not be 15used in advertising or publicity pertaining to distribution 16of the software without specific, written prior permission. 17M.I.T. and the M.I.T. S.I.P.B. make no representations about 18the suitability of this software for any purpose. It is 19provided "as is" without express or implied warranty. 20 21*/ 22 23/* 24 * newsyslog - roll over selected logs at the appropriate time, 25 * keeping the a specified number of backup files around. 26 */ 27 28#ifndef lint 29static const char rcsid[] = 30"$FreeBSD: head/usr.sbin/newsyslog/newsyslog.c 59003 2000-04-04 08:38:30Z hm $"; 31 32#endif /* not lint */ 33 34#define OSF 35#ifndef COMPRESS_POSTFIX 36#define COMPRESS_POSTFIX ".gz" 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#ifdef _IBMR2 60/* Calculates (db * DEV_BSIZE) */ 61#define dbtob(db) ((unsigned)(db) << UBSHIFT) 62#endif 63 64#define CE_COMPACT 1 /* Compact the achived log files */ 65#define CE_BINARY 2 /* Logfile is in binary, don't add */ 66 /* status messages */ 67#define CE_TRIMAT 4 /* trim at a specific time */ 68 69#define NONE -1 70 71struct conf_entry { 72 char *log; /* Name of the log */ 73 char *pid_file; /* PID file */ 74 int uid; /* Owner of log */ 75 int gid; /* Group of log */ 76 int numlogs; /* Number of logs to keep */ 77 int size; /* Size cutoff to trigger trimming the log */ 78 int hours; /* Hours between log trimming */ 79 time_t trim_at; /* Specific time to do trimming */ 80 int permissions; /* File permissions on the log */ 81 int flags; /* Flags (CE_COMPACT & CE_BINARY) */ 82 int sig; /* Signal to send */ 83 struct conf_entry *next;/* Linked list pointer */ 84}; 85 86int verbose = 0; /* Print out what's going on */ 87int needroot = 1; /* Root privs are necessary */ 88int noaction = 0; /* Don't do anything, just show it */ 89int force = 0; /* Force the trim no matter what */ 90char *conf = _PATH_CONF; /* Configuration file to use */ 91time_t timenow; 92 93#define MIN_PID 5 94#define MAX_PID 99999 /* was lower, see /usr/include/sys/proc.h */ 95char hostname[MAXHOSTNAMELEN + 1]; /* hostname */ 96char *daytime; /* timenow in human readable form */ 97 98static struct conf_entry *parse_file(); 99static char *sob(char *p); 100static char *son(char *p); 101static char *missing_field(char *p, char *errline); 102static void do_entry(struct conf_entry * ent); 103static void PRS(int argc, char **argv); 104static void usage(); 105static void dotrim(char *log, char *pid_file, int numdays, int falgs, int perm, int owner_uid, int group_gid, int sig); 106static int log_trim(char *log); 107static void compress_log(char *log); 108static int sizefile(char *file); 109static int age_old_log(char *file); 110static pid_t get_pid(char *pid_file); 111static time_t parse8601(const char *s); 112 113int 114main(argc, argv) 115 int argc; 116 char **argv; 117{ 118 struct conf_entry *p, *q; 119 120 PRS(argc, argv); 121 if (needroot && getuid() && geteuid()) 122 errx(1, "must have root privs"); 123 p = q = parse_file(); 124 125 while (p) { 126 do_entry(p); 127 p = p->next; 128 free((char *) q); 129 q = p; 130 } 131 return (0); 132} 133 134static void 135do_entry(ent) 136 struct conf_entry *ent; 137 138{ 139 int size, modtime; 140 char *pid_file; 141 142 if (verbose) { 143 if (ent->flags & CE_COMPACT) 144 printf("%s <%dZ>: ", ent->log, ent->numlogs); 145 else 146 printf("%s <%d>: ", ent->log, ent->numlogs); 147 } 148 size = sizefile(ent->log); 149 modtime = age_old_log(ent->log); 150 if (size < 0) { 151 if (verbose) 152 printf("does not exist.\n"); 153 } else { 154 if (ent->flags & CE_TRIMAT) { 155 if (timenow < ent->trim_at 156 || difftime(timenow, ent->trim_at) >= 60 * 60) { 157 if (verbose) 158 printf("--> will trim at %s", 159 ctime(&ent->trim_at)); 160 return; 161 } else if (verbose && ent->hours <= 0) { 162 printf("--> time is up\n"); 163 } 164 } 165 if (verbose && (ent->size > 0)) 166 printf("size (Kb): %d [%d] ", size, ent->size); 167 if (verbose && (ent->hours > 0)) 168 printf(" age (hr): %d [%d] ", modtime, ent->hours); 169 if (force || ((ent->size > 0) && (size >= ent->size)) || 170 (ent->hours <= 0 && (ent->flags & CE_TRIMAT)) || 171 ((ent->hours > 0) && ((modtime >= ent->hours) 172 || (modtime < 0)))) { 173 if (verbose) 174 printf("--> trimming log....\n"); 175 if (noaction && !verbose) { 176 if (ent->flags & CE_COMPACT) 177 printf("%s <%dZ>: trimming\n", 178 ent->log, ent->numlogs); 179 else 180 printf("%s <%d>: trimming\n", 181 ent->log, ent->numlogs); 182 } 183 if (ent->pid_file) { 184 pid_file = ent->pid_file; 185 } else { 186 /* Only try to notify syslog if we are root */ 187 if (needroot) 188 pid_file = _PATH_SYSLOGPID; 189 else 190 pid_file = NULL; 191 } 192 dotrim(ent->log, pid_file, ent->numlogs, 193 ent->flags, ent->permissions, ent->uid, ent->gid, ent->sig); 194 } else { 195 if (verbose) 196 printf("--> skipping\n"); 197 } 198 } 199} 200 201static void 202PRS(argc, argv) 203 int argc; 204 char **argv; 205{ 206 int c; 207 char *p; 208 209 timenow = time((time_t *) 0); 210 daytime = ctime(&timenow) + 4; 211 daytime[15] = '\0'; 212 213 /* Let's get our hostname */ 214 (void) gethostname(hostname, sizeof(hostname)); 215 216 /* Truncate domain */ 217 if ((p = strchr(hostname, '.'))) { 218 *p = '\0'; 219 } 220 optind = 1; /* Start options parsing */ 221 while ((c = getopt(argc, argv, "nrvFf:t:")) != -1) 222 switch (c) { 223 case 'n': 224 noaction++; 225 break; 226 case 'r': 227 needroot = 0; 228 break; 229 case 'v': 230 verbose++; 231 break; 232 case 'f': 233 conf = optarg; 234 break; 235 case 'F': 236 force++; 237 break; 238 default: 239 usage(); 240 } 241} 242 243static void 244usage() 245{ 246 fprintf(stderr, "usage: newsyslog [-Fnrv] [-f config-file]\n"); 247 exit(1); 248} 249 250/* Parse a configuration file and return a linked list of all the logs 251 * to process 252 */ 253static struct conf_entry * 254parse_file() 255{ 256 FILE *f; 257 char line[BUFSIZ], *parse, *q; 258 char *errline, *group; 259 struct conf_entry *first = NULL; 260 struct conf_entry *working = NULL; 261 struct passwd *pass; 262 struct group *grp; 263 int eol; 264 265 if (strcmp(conf, "-")) 266 f = fopen(conf, "r"); 267 else 268 f = stdin; 269 if (!f) 270 err(1, "%s", conf); 271 while (fgets(line, BUFSIZ, f)) { 272 if ((line[0] == '\n') || (line[0] == '#')) 273 continue; 274 errline = strdup(line); 275 if (!first) { 276 working = (struct conf_entry *) malloc(sizeof(struct conf_entry)); 277 first = working; 278 } else { 279 working->next = (struct conf_entry *) malloc(sizeof(struct conf_entry)); 280 working = working->next; 281 } 282 283 q = parse = missing_field(sob(line), errline); 284 parse = son(line); 285 if (!*parse) 286 errx(1, "malformed line (missing fields):\n%s", errline); 287 *parse = '\0'; 288 working->log = strdup(q); 289 290 q = parse = missing_field(sob(++parse), errline); 291 parse = son(parse); 292 if (!*parse) 293 errx(1, "malformed line (missing fields):\n%s", errline); 294 *parse = '\0'; 295 if ((group = strchr(q, ':')) != NULL || 296 (group = strrchr(q, '.')) != NULL) { 297 *group++ = '\0'; 298 if (*q) { 299 if (!(isnumber(*q))) { 300 if ((pass = getpwnam(q)) == NULL) 301 errx(1, 302 "error in config file; unknown user:\n%s", 303 errline); 304 working->uid = pass->pw_uid; 305 } else 306 working->uid = atoi(q); 307 } else 308 working->uid = NONE; 309 310 q = group; 311 if (*q) { 312 if (!(isnumber(*q))) { 313 if ((grp = getgrnam(q)) == NULL) 314 errx(1, 315 "error in config file; unknown group:\n%s", 316 errline); 317 working->gid = grp->gr_gid; 318 } else 319 working->gid = atoi(q); 320 } else 321 working->gid = NONE; 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", errline); 327 *parse = '\0'; 328 } else 329 working->uid = working->gid = NONE; 330 331 if (!sscanf(q, "%o", &working->permissions)) 332 errx(1, "error in config file; bad permissions:\n%s", 333 errline); 334 335 q = parse = missing_field(sob(++parse), errline); 336 parse = son(parse); 337 if (!*parse) 338 errx(1, "malformed line (missing fields):\n%s", errline); 339 *parse = '\0'; 340 if (!sscanf(q, "%d", &working->numlogs)) 341 errx(1, "error in config file; bad number:\n%s", 342 errline); 343 344 q = parse = missing_field(sob(++parse), errline); 345 parse = son(parse); 346 if (!*parse) 347 errx(1, "malformed line (missing fields):\n%s", errline); 348 *parse = '\0'; 349 if (isdigit(*q)) 350 working->size = atoi(q); 351 else 352 working->size = -1; 353 354 working->flags = 0; 355 q = parse = missing_field(sob(++parse), errline); 356 parse = son(parse); 357 eol = !*parse; 358 *parse = '\0'; 359 { 360 char *ep; 361 u_long ul; 362 363 ul = strtoul(q, &ep, 10); 364 if (ep == q) 365 working->hours = 0; 366 else if (*ep == '*') 367 working->hours = -1; 368 else if (ul > INT_MAX) 369 errx(1, "interval is too large:\n%s", errline); 370 else 371 working->hours = ul; 372 373 if (*ep != '\0' && *ep != '@' && *ep != '*') 374 errx(1, "malformed interval/at:\n%s", errline); 375 if (*ep == '@') { 376 if ((working->trim_at = parse8601(ep + 1)) 377 == (time_t) - 1) 378 errx(1, "malformed at:\n%s", errline); 379 working->flags |= CE_TRIMAT; 380 } 381 } 382 383 if (eol) 384 q = NULL; 385 else { 386 q = parse = sob(++parse); /* Optional field */ 387 parse = son(parse); 388 if (!*parse) 389 eol = 1; 390 *parse = '\0'; 391 } 392 393 while (q && *q && !isspace(*q)) { 394 if ((*q == 'Z') || (*q == 'z')) 395 working->flags |= CE_COMPACT; 396 else if ((*q == 'B') || (*q == 'b')) 397 working->flags |= CE_BINARY; 398 else if (*q != '-') 399 errx(1, "illegal flag in config file -- %c", *q); 400 q++; 401 } 402 403 if (eol) 404 q = NULL; 405 else { 406 q = parse = sob(++parse); /* Optional field */ 407 parse = son(parse); 408 if (!*parse) 409 eol = 1; 410 *parse = '\0'; 411 } 412 413 working->pid_file = NULL; 414 if (q && *q) { 415 if (*q == '/') 416 working->pid_file = strdup(q); 417 else if (isdigit(*q)) 418 goto got_sig; 419 else 420 errx(1, "illegal pid file or signal number in config file:\n%s", errline); 421 } 422 if (eol) 423 q = NULL; 424 else { 425 q = parse = sob(++parse); /* Optional field */ 426 *(parse = son(parse)) = '\0'; 427 } 428 429 working->sig = SIGHUP; 430 if (q && *q) { 431 if (isdigit(*q)) { 432 got_sig: 433 working->sig = atoi(q); 434 } else { 435 err_sig: 436 errx(1, "illegal signal number in config file:\n%s", errline); 437 } 438 if (working->sig < 1 || working->sig >= NSIG) 439 goto err_sig; 440 } 441 free(errline); 442 } 443 if (working) 444 working->next = (struct conf_entry *) NULL; 445 (void) fclose(f); 446 return (first); 447} 448 449static char * 450missing_field(p, errline) 451 char *p, *errline; 452{ 453 if (!p || !*p) 454 errx(1, "missing field in config file:\n%s", errline); 455 return (p); 456} 457 458static void 459dotrim(log, pid_file, numdays, flags, perm, owner_uid, group_gid, sig) 460 char *log; 461 char *pid_file; 462 int numdays; 463 int flags; 464 int perm; 465 int owner_uid; 466 int group_gid; 467 int sig; 468{ 469 char file1[MAXPATHLEN + 1], file2[MAXPATHLEN + 1]; 470 char zfile1[MAXPATHLEN + 1], zfile2[MAXPATHLEN + 1]; 471 int notified, need_notification, fd, _numdays; 472 struct stat st; 473 pid_t pid; 474 475#ifdef _IBMR2 476/* AIX 3.1 has a broken fchown- if the owner_uid is -1, it will actually */ 477/* change it to be owned by uid -1, instead of leaving it as is, as it is */ 478/* supposed to. */ 479 if (owner_uid == -1) 480 owner_uid = geteuid(); 481#endif 482 483 /* Remove oldest log */ 484 (void) sprintf(file1, "%s.%d", log, numdays); 485 (void) strcpy(zfile1, file1); 486 (void) strcat(zfile1, COMPRESS_POSTFIX); 487 488 if (noaction) { 489 printf("rm -f %s\n", file1); 490 printf("rm -f %s\n", zfile1); 491 } else { 492 (void) unlink(file1); 493 (void) unlink(zfile1); 494 } 495 496 /* Move down log files */ 497 _numdays = numdays; /* preserve */ 498 while (numdays--) { 499 (void) strcpy(file2, file1); 500 (void) sprintf(file1, "%s.%d", log, numdays); 501 (void) strcpy(zfile1, file1); 502 (void) strcpy(zfile2, file2); 503 if (lstat(file1, &st)) { 504 (void) strcat(zfile1, COMPRESS_POSTFIX); 505 (void) strcat(zfile2, COMPRESS_POSTFIX); 506 if (lstat(zfile1, &st)) 507 continue; 508 } 509 if (noaction) { 510 printf("mv %s %s\n", zfile1, zfile2); 511 printf("chmod %o %s\n", perm, zfile2); 512 printf("chown %d.%d %s\n", 513 owner_uid, group_gid, zfile2); 514 } else { 515 (void) rename(zfile1, zfile2); 516 (void) chmod(zfile2, perm); 517 (void) chown(zfile2, owner_uid, group_gid); 518 } 519 } 520 if (!noaction && !(flags & CE_BINARY)) 521 (void) log_trim(log); /* Report the trimming to the old log */ 522 523 if (!_numdays) { 524 if (noaction) 525 printf("rm %s\n", log); 526 else 527 (void) unlink(log); 528 } else { 529 if (noaction) 530 printf("mv %s to %s\n", log, file1); 531 else 532 (void) rename(log, file1); 533 } 534 535 if (noaction) 536 printf("Start new log..."); 537 else { 538 fd = creat(log, perm); 539 if (fd < 0) 540 err(1, "can't start new log"); 541 if (fchown(fd, owner_uid, group_gid)) 542 err(1, "can't chmod new log file"); 543 (void) close(fd); 544 if (!(flags & CE_BINARY)) 545 if (log_trim(log)) /* Add status message */ 546 err(1, "can't add status message to log"); 547 } 548 if (noaction) 549 printf("chmod %o %s...\n", perm, log); 550 else 551 (void) chmod(log, perm); 552 553 pid = 0; 554 need_notification = notified = 0; 555 if (pid_file != NULL) { 556 need_notification = 1; 557 pid = get_pid(pid_file); 558 } 559 if (pid) { 560 if (noaction) { 561 notified = 1; 562 printf("kill -%d %d\n", sig, (int) pid); 563 } else if (kill(pid, sig)) 564 warn("can't notify daemon, pid %d", (int) pid); 565 else { 566 notified = 1; 567 if (verbose) 568 printf("daemon pid %d notified\n", (int) pid); 569 } 570 } 571 if ((flags & CE_COMPACT)) { 572 if (need_notification && !notified) 573 warnx("log not compressed because daemon not notified"); 574 else if (noaction) 575 printf("Compress %s.0\n", log); 576 else { 577 if (notified) { 578 if (verbose) 579 printf("small pause to allow daemon to close log\n"); 580 sleep(10); 581 } 582 compress_log(log); 583 } 584 } 585} 586 587/* Log the fact that the logs were turned over */ 588static int 589log_trim(log) 590 char *log; 591{ 592 FILE *f; 593 594 if ((f = fopen(log, "a")) == NULL) 595 return (-1); 596 fprintf(f, "%s %s newsyslog[%d]: logfile turned over\n", 597 daytime, hostname, (int) getpid()); 598 if (fclose(f) == EOF) 599 err(1, "log_trim: fclose:"); 600 return (0); 601} 602 603/* Fork of /usr/ucb/compress to compress the old log file */ 604static void 605compress_log(log) 606 char *log; 607{ 608 pid_t pid; 609 char tmp[MAXPATHLEN + 1]; 610 611 (void) sprintf(tmp, "%s.0", log); 612 pid = fork(); 613 if (pid < 0) 614 err(1, "fork"); 615 else if (!pid) { 616 (void) execl(_PATH_GZIP, _PATH_GZIP, "-f", tmp, 0); 617 err(1, _PATH_GZIP); 618 } 619} 620 621/* Return size in kilobytes of a file */ 622static int 623sizefile(file) 624 char *file; 625{ 626 struct stat sb; 627 628 if (stat(file, &sb) < 0) 629 return (-1); 630 return (kbytes(dbtob(sb.st_blocks))); 631} 632 633/* Return the age of old log file (file.0) */ 634static int 635age_old_log(file) 636 char *file; 637{ 638 struct stat sb; 639 char tmp[MAXPATHLEN + sizeof(".0") + sizeof(COMPRESS_POSTFIX) + 1]; 640 641 (void) strcpy(tmp, file); 642 if (stat(strcat(tmp, ".0"), &sb) < 0) 643 if (stat(strcat(tmp, COMPRESS_POSTFIX), &sb) < 0) 644 return (-1); 645 return ((int) (timenow - sb.st_mtime + 1800) / 3600); 646} 647 648static pid_t 649get_pid(pid_file) 650 char *pid_file; 651{ 652 FILE *f; 653 char line[BUFSIZ]; 654 pid_t pid = 0; 655 656 if ((f = fopen(pid_file, "r")) == NULL) 657 warn("can't open %s pid file to restart a daemon", 658 pid_file); 659 else { 660 if (fgets(line, BUFSIZ, f)) { 661 pid = atol(line); 662 if (pid < MIN_PID || pid > MAX_PID) { 663 warnx("preposterous process number: %d", (int) pid); 664 pid = 0; 665 } 666 } else 667 warn("can't read %s pid file to restart a daemon", 668 pid_file); 669 (void) fclose(f); 670 } 671 return pid; 672} 673 674/* Skip Over Blanks */ 675char * 676sob(p) 677 register char *p; 678{ 679 while (p && *p && isspace(*p)) 680 p++; 681 return (p); 682} 683 684/* Skip Over Non-Blanks */ 685char * 686son(p) 687 register char *p; 688{ 689 while (p && *p && !isspace(*p)) 690 p++; 691 return (p); 692} 693 694/* 695 * Parse a limited subset of ISO 8601. 696 * The specific format is as follows: 697 * 698 * [CC[YY[MM[DD]]]][THH[MM[SS]]] (where `T' is the literal letter) 699 * 700 * We don't accept a timezone specification; missing fields (including 701 * timezone) are defaulted to the current date but time zero. 702 */ 703static time_t 704parse8601(const char *s) 705{ 706 char *t; 707 struct tm tm, *tmp; 708 u_long ul; 709 710 tmp = localtime(&timenow); 711 tm = *tmp; 712 713 tm.tm_hour = tm.tm_min = tm.tm_sec = 0; 714 715 ul = strtoul(s, &t, 10); 716 if (*t != '\0' && *t != 'T') 717 return -1; 718 719 /* 720 * Now t points either to the end of the string (if no time was 721 * provided) or to the letter `T' which separates date and time in 722 * ISO 8601. The pointer arithmetic is the same for either case. 723 */ 724 switch (t - s) { 725 case 8: 726 tm.tm_year = ((ul / 1000000) - 19) * 100; 727 ul = ul % 1000000; 728 case 6: 729 tm.tm_year = tm.tm_year - (tm.tm_year % 100); 730 tm.tm_year += ul / 10000; 731 ul = ul % 10000; 732 case 4: 733 tm.tm_mon = (ul / 100) - 1; 734 ul = ul % 100; 735 case 2: 736 tm.tm_mday = ul; 737 case 0: 738 break; 739 default: 740 return -1; 741 } 742 743 /* sanity check */ 744 if (tm.tm_year < 70 || tm.tm_mon < 0 || tm.tm_mon > 12 745 || tm.tm_mday < 1 || tm.tm_mday > 31) 746 return -1; 747 748 if (*t != '\0') { 749 s = ++t; 750 ul = strtoul(s, &t, 10); 751 if (*t != '\0' && !isspace(*t)) 752 return -1; 753 754 switch (t - s) { 755 case 6: 756 tm.tm_sec = ul % 100; 757 ul /= 100; 758 case 4: 759 tm.tm_min = ul % 100; 760 ul /= 100; 761 case 2: 762 tm.tm_hour = ul; 763 case 0: 764 break; 765 default: 766 return -1; 767 } 768 769 /* sanity check */ 770 if (tm.tm_sec < 0 || tm.tm_sec > 60 || tm.tm_min < 0 771 || tm.tm_min > 59 || tm.tm_hour < 0 || tm.tm_hour > 23) 772 return -1; 773 } 774 return mktime(&tm); 775} 776