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