newsyslog.c revision 36817
160786Sps/* 2161475Sdelphij * This file contains changes from the Open Software Foundation. 360786Sps */ 460786Sps 560786Sps/* 660786Sps 760786SpsCopyright 1988, 1989 by the Massachusetts Institute of Technology 860786Sps 960786SpsPermission to use, copy, modify, and distribute this software 1060786Spsand its documentation for any purpose and without fee is 1160786Spshereby granted, provided that the above copyright notice 1260786Spsappear in all copies and that both that copyright notice and 1360786Spsthis permission notice appear in supporting documentation, 1460786Spsand that the names of M.I.T. and the M.I.T. S.I.P.B. not be 1560786Spsused in advertising or publicity pertaining to distribution 1660786Spsof the software without specific, written prior permission. 1760786SpsM.I.T. and the M.I.T. S.I.P.B. make no representations about 1860786Spsthe suitability of this software for any purpose. It is 1960786Spsprovided "as is" without express or implied warranty. 2060786Sps 2160786Sps*/ 2260786Sps 2360786Sps/* 2460786Sps * newsyslog - roll over selected logs at the appropriate time, 2560786Sps * keeping the a specified number of backup files around. 2660786Sps */ 2760786Sps 2860786Sps#ifndef lint 2960786Spsstatic const char rcsid[] = 3060786Sps "$Id: newsyslog.c,v 1.19 1998/05/10 21:13:29 hoek Exp $"; 3160786Sps#endif /* not lint */ 3260786Sps 3360786Sps#ifndef CONF 3460786Sps#define CONF "/etc/athena/newsyslog.conf" /* Configuration file */ 3560786Sps#endif 3660786Sps#ifndef PIDFILE 3760786Sps#define PIDFILE "/etc/syslog.pid" 3860786Sps#endif 3960786Sps#ifndef COMPRESS_PATH 4060786Sps#define COMPRESS_PATH "/usr/ucb/compress" /* File compression program */ 4160786Sps#endif 4260786Sps#ifndef COMPRESS_PROG 4360786Sps#define COMPRESS_PROG "compress" 4460786Sps#endif 4560786Sps#ifndef COMPRESS_POSTFIX 4660786Sps#define COMPRESS_POSTFIX ".Z" 4760786Sps#endif 4860786Sps 4960786Sps#include <ctype.h> 5060786Sps#include <err.h> 5160786Sps#include <fcntl.h> 5260786Sps#include <grp.h> 5360786Sps#include <pwd.h> 5460786Sps#include <signal.h> 5560786Sps#include <stdio.h> 5660786Sps#include <stdlib.h> 5760786Sps#include <string.h> 5860786Sps#include <unistd.h> 5960786Sps#include <sys/types.h> 6060786Sps#include <sys/time.h> 6160786Sps#include <sys/stat.h> 6260786Sps#include <sys/param.h> 6360786Sps#include <sys/wait.h> 6460786Sps 6560786Sps#define kbytes(size) (((size) + 1023) >> 10) 6660786Sps#ifdef _IBMR2 6760786Sps/* Calculates (db * DEV_BSIZE) */ 6860786Sps#define dbtob(db) ((unsigned)(db) << UBSHIFT) 6960786Sps#endif 7060786Sps 7160786Sps#define CE_COMPACT 1 /* Compact the achived log files */ 7260786Sps#define CE_BINARY 2 /* Logfile is in binary, don't add */ 7360786Sps /* status messages */ 7460786Sps#define NONE -1 7560786Sps 7660786Spsstruct conf_entry { 7760786Sps char *log; /* Name of the log */ 7860786Sps char *pid_file; /* PID file */ 7960786Sps int uid; /* Owner of log */ 8060786Sps int gid; /* Group of log */ 8160786Sps int numlogs; /* Number of logs to keep */ 8260786Sps int size; /* Size cutoff to trigger trimming the log */ 8360786Sps int hours; /* Hours between log trimming */ 8460786Sps int permissions; /* File permissions on the log */ 8560786Sps int flags; /* Flags (CE_COMPACT & CE_BINARY) */ 8660786Sps int sig; /* Signal to send */ 8760786Sps struct conf_entry *next; /* Linked list pointer */ 8860786Sps}; 8960786Sps 9060786Spsint verbose = 0; /* Print out what's going on */ 9160786Spsint needroot = 1; /* Root privs are necessary */ 9260786Spsint noaction = 0; /* Don't do anything, just show it */ 9360786Spsint force = 0; /* Force the trim no matter what*/ 9460786Spschar *conf = CONF; /* Configuration file to use */ 9560786Spstime_t timenow; 9660786Sps#define MIN_PID 5 9760786Sps#define MAX_PID 30000 /* was 65534, see /usr/include/sys/proc.h */ 9860786Spschar hostname[MAXHOSTNAMELEN+1]; /* hostname */ 9963128Spschar *daytime; /* timenow in human readable form */ 10063128Sps 10163128Sps#ifndef OSF 10263128Spschar *strdup(char *strp); 10363128Sps#endif 10463128Sps 10563128Spsstatic struct conf_entry *parse_file(); 10660786Spsstatic char *sob(char *p); 10760786Spsstatic char *son(char *p); 10860786Spsstatic char *missing_field(char *p,char *errline); 10960786Spsstatic void do_entry(struct conf_entry *ent); 11060786Spsstatic void PRS(int argc,char **argv); 11160786Spsstatic void usage(); 11260786Spsstatic void dotrim(char *log,char *pid_file,int numdays,int falgs,int perm,int owner_uid,int group_gid,int sig); 11363128Spsstatic int log_trim(char *log); 11460786Spsstatic void compress_log(char *log); 11560786Spsstatic int sizefile(char *file); 11660786Spsstatic int age_old_log(char *file); 11760786Spsstatic pid_t get_pid(char *pid_file); 11860786Sps 11960786Spsint main(argc,argv) 12060786Sps int argc; 12160786Sps char **argv; 12260786Sps{ 12360786Sps struct conf_entry *p, *q; 12460786Sps 12560786Sps PRS(argc,argv); 12660786Sps if (needroot && getuid() && geteuid()) 12760786Sps errx(1, "must have root privs"); 12860786Sps p = q = parse_file(); 12960786Sps 13060786Sps while (p) { 13160786Sps do_entry(p); 13260786Sps p=p->next; 13360786Sps free((char *) q); 13460786Sps q=p; 13560786Sps } 13660786Sps return(0); 13760786Sps} 13860786Sps 13960786Spsstatic void do_entry(ent) 14060786Sps struct conf_entry *ent; 14160786Sps 14260786Sps{ 14360786Sps int size, modtime; 14460786Sps char *pid_file; 14560786Sps 14660786Sps if (verbose) { 14760786Sps if (ent->flags & CE_COMPACT) 14860786Sps printf("%s <%dZ>: ",ent->log,ent->numlogs); 14960786Sps else 15060786Sps printf("%s <%d>: ",ent->log,ent->numlogs); 15160786Sps } 15260786Sps size = sizefile(ent->log); 15360786Sps modtime = age_old_log(ent->log); 15460786Sps if (size < 0) { 15560786Sps if (verbose) 15660786Sps printf("does not exist.\n"); 15760786Sps } else { 15860786Sps if (verbose && (ent->size > 0)) 15960786Sps printf("size (Kb): %d [%d] ", size, ent->size); 16060786Sps if (verbose && (ent->hours > 0)) 16160786Sps printf(" age (hr): %d [%d] ", modtime, ent->hours); 16260786Sps if (force || ((ent->size > 0) && (size >= ent->size)) || 16360786Sps ((ent->hours > 0) && ((modtime >= ent->hours) 16460786Sps || (modtime < 0)))) { 16560786Sps if (verbose) 16660786Sps printf("--> trimming log....\n"); 16760786Sps if (noaction && !verbose) { 16860786Sps if (ent->flags & CE_COMPACT) 16960786Sps printf("%s <%dZ>: trimming\n", 17060786Sps ent->log,ent->numlogs); 17160786Sps else 17260786Sps printf("%s <%d>: trimming\n", 17360786Sps ent->log,ent->numlogs); 17460786Sps } 17560786Sps if (ent->pid_file) { 17660786Sps pid_file = ent->pid_file; 17760786Sps } else { 17860786Sps /* Only try to notify syslog if we are root */ 17960786Sps if (needroot) 18060786Sps pid_file = PIDFILE; 18160786Sps else 18260786Sps pid_file = NULL; 18360786Sps } 18460786Sps dotrim(ent->log, pid_file, ent->numlogs, 18560786Sps ent->flags, ent->permissions, ent->uid, ent->gid, ent->sig); 18660786Sps } else { 18760786Sps if (verbose) 18860786Sps printf("--> skipping\n"); 18960786Sps } 19060786Sps } 19160786Sps} 19260786Sps 19360786Spsstatic void PRS(argc,argv) 19460786Sps int argc; 19560786Sps char **argv; 19660786Sps{ 19760786Sps int c; 19860786Sps char *p; 19960786Sps 20060786Sps timenow = time((time_t *) 0); 20160786Sps daytime = ctime(&timenow) + 4; 20260786Sps daytime[15] = '\0'; 20360786Sps 20460786Sps /* Let's get our hostname */ 20560786Sps (void) gethostname(hostname, sizeof(hostname)); 20660786Sps 20760786Sps /* Truncate domain */ 20860786Sps if ((p = strchr(hostname, '.'))) { 20960786Sps *p = '\0'; 21060786Sps } 21160786Sps 21260786Sps optind = 1; /* Start options parsing */ 21360786Sps while ((c=getopt(argc,argv,"nrvFf:t:")) != -1) 21460786Sps switch (c) { 21560786Sps case 'n': 21660786Sps noaction++; 21760786Sps break; 21860786Sps case 'r': 219161475Sdelphij needroot = 0; 22060786Sps break; 22160786Sps case 'v': 22260786Sps verbose++; 22360786Sps break; 22460786Sps case 'f': 22560786Sps conf = optarg; 22660786Sps break; 22760786Sps case 'F': 228161475Sdelphij force++; 229161475Sdelphij break; 230161475Sdelphij default: 23160786Sps usage(); 23260786Sps } 23360786Sps } 23460786Sps 23560786Spsstatic void usage() 23660786Sps{ 23760786Sps fprintf(stderr, "usage: newsyslog [-Fnrv] [-f config-file]\n"); 23860786Sps exit(1); 23960786Sps} 24060786Sps 24160786Sps/* Parse a configuration file and return a linked list of all the logs 24289019Sps * to process 24389019Sps */ 24489019Spsstatic struct conf_entry *parse_file() 24589019Sps{ 24660786Sps FILE *f; 24760786Sps char line[BUFSIZ], *parse, *q; 24860786Sps char *errline, *group; 24960786Sps struct conf_entry *first = NULL; 25060786Sps struct conf_entry *working = NULL; 25160786Sps struct passwd *pass; 25260786Sps struct group *grp; 25360786Sps int eol; 25460786Sps 25560786Sps if (strcmp(conf,"-")) 25660786Sps f = fopen(conf,"r"); 25760786Sps else 25889019Sps f = stdin; 25989019Sps if (!f) 26089019Sps err(1, "%s", conf); 26189019Sps while (fgets(line,BUFSIZ,f)) { 26289019Sps if ((line[0]== '\n') || (line[0] == '#')) 26389019Sps continue; 26460786Sps errline = strdup(line); 26560786Sps if (!first) { 26660786Sps working = (struct conf_entry *) malloc(sizeof(struct conf_entry)); 26760786Sps first = working; 26889019Sps } else { 26989019Sps working->next = (struct conf_entry *) malloc(sizeof(struct conf_entry)); 27089019Sps working = working->next; 27189019Sps } 27289019Sps 27389019Sps q = parse = missing_field(sob(line),errline); 27489019Sps parse = son(line); 27589019Sps if (!*parse) 27689019Sps errx(1, "malformed line (missing fields):\n%s", errline); 27789019Sps *parse = '\0'; 27889019Sps working->log = strdup(q); 27989019Sps 28089019Sps q = parse = missing_field(sob(++parse),errline); 28189019Sps parse = son(parse); 28289019Sps if (!*parse) 28389019Sps errx(1, "malformed line (missing fields):\n%s", errline); 28489019Sps *parse = '\0'; 28589019Sps if ((group = strchr(q, '.')) != NULL) { 28689019Sps *group++ = '\0'; 28789019Sps if (*q) { 28889019Sps if (!(isnumber(*q))) { 28989019Sps if ((pass = getpwnam(q)) == NULL) 29089019Sps errx(1, 29189019Sps "error in config file; unknown user:\n%s", 29289019Sps errline); 29389019Sps working->uid = pass->pw_uid; 29489019Sps } else 29589019Sps working->uid = atoi(q); 29689019Sps } else 29789019Sps working->uid = NONE; 29889019Sps 29989019Sps q = group; 30089019Sps if (*q) { 30189019Sps if (!(isnumber(*q))) { 30289019Sps if ((grp = getgrnam(q)) == NULL) 30360786Sps errx(1, 30460786Sps "error in config file; unknown group:\n%s", 30560786Sps errline); 30660786Sps working->gid = grp->gr_gid; 30760786Sps } else 30860786Sps working->gid = atoi(q); 30960786Sps } else 31060786Sps working->gid = NONE; 31160786Sps 31260786Sps q = parse = missing_field(sob(++parse),errline); 31360786Sps parse = son(parse); 31460786Sps if (!*parse) 31560786Sps errx(1, "malformed line (missing fields):\n%s", errline); 31660786Sps *parse = '\0'; 31760786Sps } 31889019Sps else 31960786Sps working->uid = working->gid = NONE; 32060786Sps 32160786Sps if (!sscanf(q,"%o",&working->permissions)) 32260786Sps errx(1, "error in config file; bad permissions:\n%s", 32360786Sps errline); 32460786Sps 32560786Sps q = parse = missing_field(sob(++parse),errline); 32660786Sps parse = son(parse); 32760786Sps if (!*parse) 32860786Sps errx(1, "malformed line (missing fields):\n%s", errline); 32960786Sps *parse = '\0'; 330 if (!sscanf(q,"%d",&working->numlogs)) 331 errx(1, "error in config file; bad number:\n%s", 332 errline); 333 334 q = parse = missing_field(sob(++parse),errline); 335 parse = son(parse); 336 if (!*parse) 337 errx(1, "malformed line (missing fields):\n%s", errline); 338 *parse = '\0'; 339 if (isdigit(*q)) 340 working->size = atoi(q); 341 else 342 working->size = -1; 343 344 q = parse = missing_field(sob(++parse),errline); 345 parse = son(parse); 346 eol = !*parse; 347 *parse = '\0'; 348 if (isdigit(*q)) 349 working->hours = atoi(q); 350 else 351 working->hours = -1; 352 353 if (eol) 354 q = NULL; 355 else { 356 q = parse = sob(++parse); /* Optional field */ 357 parse = son(parse); 358 if (!*parse) 359 eol = 1; 360 *parse = '\0'; 361 } 362 363 working->flags = 0; 364 while (q && *q && !isspace(*q)) { 365 if ((*q == 'Z') || (*q == 'z')) 366 working->flags |= CE_COMPACT; 367 else if ((*q == 'B') || (*q == 'b')) 368 working->flags |= CE_BINARY; 369 else if (*q != '-') 370 errx(1, "illegal flag in config file -- %c", *q); 371 q++; 372 } 373 374 if (eol) 375 q = NULL; 376 else { 377 q = parse = sob(++parse); /* Optional field */ 378 parse = son(parse); 379 if (!*parse) 380 eol = 1; 381 *parse = '\0'; 382 } 383 384 working->pid_file = NULL; 385 if (q && *q) { 386 if (*q == '/') 387 working->pid_file = strdup(q); 388 else if (isdigit(*q)) 389 goto got_sig; 390 else 391 errx(1, "illegal pid file or signal number in config file:\n%s", errline); 392 } 393 394 if (eol) 395 q = NULL; 396 else { 397 q = parse = sob(++parse); /* Optional field */ 398 *(parse = son(parse)) = '\0'; 399 } 400 401 working->sig = SIGHUP; 402 if (q && *q) { 403 if (isdigit(*q)) { 404 got_sig: 405 working->sig = atoi(q); 406 } else { 407 err_sig: 408 errx(1, "illegal signal number in config file:\n%s", errline); 409 } 410 if (working->sig < 1 || working->sig >= NSIG) 411 goto err_sig; 412 } 413 414 free(errline); 415 } 416 if (working) 417 working->next = (struct conf_entry *) NULL; 418 (void) fclose(f); 419 return(first); 420} 421 422static char *missing_field(p,errline) 423 char *p,*errline; 424{ 425 if (!p || !*p) 426 errx(1, "missing field in config file:\n%s", errline); 427 return(p); 428} 429 430static void dotrim(log,pid_file,numdays,flags,perm,owner_uid,group_gid,sig) 431 char *log; 432 char *pid_file; 433 int numdays; 434 int flags; 435 int perm; 436 int owner_uid; 437 int group_gid; 438 int sig; 439{ 440 char file1 [MAXPATHLEN+1], file2 [MAXPATHLEN+1]; 441 char zfile1[MAXPATHLEN+1], zfile2[MAXPATHLEN+1]; 442 int notified, need_notification, fd, _numdays; 443 struct stat st; 444 pid_t pid; 445 446#ifdef _IBMR2 447/* AIX 3.1 has a broken fchown- if the owner_uid is -1, it will actually */ 448/* change it to be owned by uid -1, instead of leaving it as is, as it is */ 449/* supposed to. */ 450 if (owner_uid == -1) 451 owner_uid = geteuid(); 452#endif 453 454 /* Remove oldest log */ 455 (void) sprintf(file1,"%s.%d",log,numdays); 456 (void) strcpy(zfile1, file1); 457 (void) strcat(zfile1, COMPRESS_POSTFIX); 458 459 if (noaction) { 460 printf("rm -f %s\n", file1); 461 printf("rm -f %s\n", zfile1); 462 } else { 463 (void) unlink(file1); 464 (void) unlink(zfile1); 465 } 466 467 /* Move down log files */ 468 _numdays = numdays; /* preserve */ 469 while (numdays--) { 470 (void) strcpy(file2,file1); 471 (void) sprintf(file1,"%s.%d",log,numdays); 472 (void) strcpy(zfile1, file1); 473 (void) strcpy(zfile2, file2); 474 if (lstat(file1, &st)) { 475 (void) strcat(zfile1, COMPRESS_POSTFIX); 476 (void) strcat(zfile2, COMPRESS_POSTFIX); 477 if (lstat(zfile1, &st)) continue; 478 } 479 if (noaction) { 480 printf("mv %s %s\n",zfile1,zfile2); 481 printf("chmod %o %s\n", perm, zfile2); 482 printf("chown %d.%d %s\n", 483 owner_uid, group_gid, zfile2); 484 } else { 485 (void) rename(zfile1, zfile2); 486 (void) chmod(zfile2, perm); 487 (void) chown(zfile2, owner_uid, group_gid); 488 } 489 } 490 if (!noaction && !(flags & CE_BINARY)) 491 (void) log_trim(log); /* Report the trimming to the old log */ 492 493 if (!_numdays) { 494 if (noaction) 495 printf("rm %s\n",log); 496 else 497 (void)unlink(log); 498 } 499 else { 500 if (noaction) 501 printf("mv %s to %s\n",log,file1); 502 else 503 (void)rename(log, file1); 504 } 505 506 if (noaction) 507 printf("Start new log..."); 508 else { 509 fd = creat(log,perm); 510 if (fd < 0) 511 err(1, "can't start new log"); 512 if (fchown(fd, owner_uid, group_gid)) 513 err(1, "can't chmod new log file"); 514 (void) close(fd); 515 if (!(flags & CE_BINARY)) 516 if (log_trim(log)) /* Add status message */ 517 err(1, "can't add status message to log"); 518 } 519 if (noaction) 520 printf("chmod %o %s...",perm,log); 521 else 522 (void) chmod(log,perm); 523 524 pid = 0; 525 need_notification = notified = 0; 526 if (pid_file != NULL) { 527 need_notification = 1; 528 pid = get_pid(pid_file); 529 } 530 531 if (pid) { 532 if (noaction) { 533 notified = 1; 534 printf("kill -%d %d\n", sig, (int)pid); 535 } else if (kill(pid,sig)) 536 warn("can't notify daemon, pid %d", (int)pid); 537 else { 538 notified = 1; 539 if (verbose) 540 printf("daemon pid %d notified\n", (int)pid); 541 } 542 } 543 544 if ((flags & CE_COMPACT)) { 545 if (need_notification && !notified) 546 warnx("log not compressed because daemon not notified"); 547 else if (noaction) 548 printf("Compress %s.0\n",log); 549 else { 550 if (notified) { 551 if (verbose) 552 printf("small pause to allow daemon to close log\n"); 553 sleep(10); 554 } 555 compress_log(log); 556 } 557 } 558} 559 560/* Log the fact that the logs were turned over */ 561static int log_trim(log) 562 char *log; 563{ 564 FILE *f; 565 if ((f = fopen(log,"a")) == NULL) 566 return(-1); 567 fprintf(f,"%s %s newsyslog[%d]: logfile turned over\n", 568 daytime, hostname, (int)getpid()); 569 if (fclose(f) == EOF) 570 err(1, "log_trim: fclose:"); 571 return(0); 572} 573 574/* Fork of /usr/ucb/compress to compress the old log file */ 575static void compress_log(log) 576 char *log; 577{ 578 pid_t pid; 579 char tmp[MAXPATHLEN+1]; 580 581 (void) sprintf(tmp,"%s.0",log); 582 pid = fork(); 583 if (pid < 0) 584 err(1, "fork"); 585 else if (!pid) { 586 (void) execl(COMPRESS_PATH,COMPRESS_PROG,"-f",tmp,0); 587 err(1, COMPRESS_PATH); 588 } 589} 590 591/* Return size in kilobytes of a file */ 592static int sizefile(file) 593 char *file; 594{ 595 struct stat sb; 596 597 if (stat(file,&sb) < 0) 598 return(-1); 599 return(kbytes(dbtob(sb.st_blocks))); 600} 601 602/* Return the age of old log file (file.0) */ 603static int age_old_log(file) 604 char *file; 605{ 606 struct stat sb; 607 char tmp[MAXPATHLEN+sizeof(".0")+sizeof(COMPRESS_POSTFIX)+1]; 608 609 (void) strcpy(tmp,file); 610 if (stat(strcat(tmp,".0"),&sb) < 0) 611 if (stat(strcat(tmp,COMPRESS_POSTFIX), &sb) < 0) 612 return(-1); 613 return( (int) (timenow - sb.st_mtime + 1800) / 3600); 614} 615 616static pid_t get_pid(pid_file) 617 char *pid_file; 618{ 619 FILE *f; 620 char line[BUFSIZ]; 621 pid_t pid = 0; 622 623 if ((f = fopen(pid_file,"r")) == NULL) 624 warn("can't open %s pid file to restart a daemon", 625 pid_file); 626 else { 627 if (fgets(line,BUFSIZ,f)) { 628 pid = atol(line); 629 if (pid < MIN_PID || pid > MAX_PID) { 630 warnx("preposterous process number: %d", (int)pid); 631 pid = 0; 632 } 633 } else 634 warn("can't read %s pid file to restart a daemon", 635 pid_file); 636 (void)fclose(f); 637 } 638 return pid; 639} 640 641#ifndef OSF 642/* Duplicate a string using malloc */ 643 644char *strdup(strp) 645register char *strp; 646{ 647 register char *cp; 648 649 if ((cp = malloc((unsigned) strlen(strp) + 1)) == NULL) 650 abort(); 651 return(strcpy (cp, strp)); 652} 653#endif 654 655/* Skip Over Blanks */ 656char *sob(p) 657 register char *p; 658{ 659 while (p && *p && isspace(*p)) 660 p++; 661 return(p); 662} 663 664/* Skip Over Non-Blanks */ 665char *son(p) 666 register char *p; 667{ 668 while (p && *p && !isspace(*p)) 669 p++; 670 return(p); 671} 672