newsyslog.c revision 36817
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.19 1998/05/10 21:13:29 hoek Exp $"; 31#endif /* not lint */ 32 33#ifndef CONF 34#define CONF "/etc/athena/newsyslog.conf" /* Configuration file */ 35#endif 36#ifndef PIDFILE 37#define PIDFILE "/etc/syslog.pid" 38#endif 39#ifndef COMPRESS_PATH 40#define COMPRESS_PATH "/usr/ucb/compress" /* File compression program */ 41#endif 42#ifndef COMPRESS_PROG 43#define COMPRESS_PROG "compress" 44#endif 45#ifndef COMPRESS_POSTFIX 46#define COMPRESS_POSTFIX ".Z" 47#endif 48 49#include <ctype.h> 50#include <err.h> 51#include <fcntl.h> 52#include <grp.h> 53#include <pwd.h> 54#include <signal.h> 55#include <stdio.h> 56#include <stdlib.h> 57#include <string.h> 58#include <unistd.h> 59#include <sys/types.h> 60#include <sys/time.h> 61#include <sys/stat.h> 62#include <sys/param.h> 63#include <sys/wait.h> 64 65#define kbytes(size) (((size) + 1023) >> 10) 66#ifdef _IBMR2 67/* Calculates (db * DEV_BSIZE) */ 68#define dbtob(db) ((unsigned)(db) << UBSHIFT) 69#endif 70 71#define CE_COMPACT 1 /* Compact the achived log files */ 72#define CE_BINARY 2 /* Logfile is in binary, don't add */ 73 /* status messages */ 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 int permissions; /* File permissions on the log */ 85 int flags; /* Flags (CE_COMPACT & CE_BINARY) */ 86 int sig; /* Signal to send */ 87 struct conf_entry *next; /* Linked list pointer */ 88}; 89 90int verbose = 0; /* Print out what's going on */ 91int needroot = 1; /* Root privs are necessary */ 92int noaction = 0; /* Don't do anything, just show it */ 93int force = 0; /* Force the trim no matter what*/ 94char *conf = CONF; /* Configuration file to use */ 95time_t timenow; 96#define MIN_PID 5 97#define MAX_PID 30000 /* was 65534, see /usr/include/sys/proc.h */ 98char hostname[MAXHOSTNAMELEN+1]; /* hostname */ 99char *daytime; /* timenow in human readable form */ 100 101#ifndef OSF 102char *strdup(char *strp); 103#endif 104 105static struct conf_entry *parse_file(); 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(); 112static void dotrim(char *log,char *pid_file,int numdays,int falgs,int perm,int owner_uid,int group_gid,int sig); 113static int log_trim(char *log); 114static void compress_log(char *log); 115static int sizefile(char *file); 116static int age_old_log(char *file); 117static pid_t get_pid(char *pid_file); 118 119int main(argc,argv) 120 int argc; 121 char **argv; 122{ 123 struct conf_entry *p, *q; 124 125 PRS(argc,argv); 126 if (needroot && getuid() && geteuid()) 127 errx(1, "must have root privs"); 128 p = q = parse_file(); 129 130 while (p) { 131 do_entry(p); 132 p=p->next; 133 free((char *) q); 134 q=p; 135 } 136 return(0); 137} 138 139static void do_entry(ent) 140 struct conf_entry *ent; 141 142{ 143 int size, modtime; 144 char *pid_file; 145 146 if (verbose) { 147 if (ent->flags & CE_COMPACT) 148 printf("%s <%dZ>: ",ent->log,ent->numlogs); 149 else 150 printf("%s <%d>: ",ent->log,ent->numlogs); 151 } 152 size = sizefile(ent->log); 153 modtime = age_old_log(ent->log); 154 if (size < 0) { 155 if (verbose) 156 printf("does not exist.\n"); 157 } else { 158 if (verbose && (ent->size > 0)) 159 printf("size (Kb): %d [%d] ", size, ent->size); 160 if (verbose && (ent->hours > 0)) 161 printf(" age (hr): %d [%d] ", modtime, ent->hours); 162 if (force || ((ent->size > 0) && (size >= ent->size)) || 163 ((ent->hours > 0) && ((modtime >= ent->hours) 164 || (modtime < 0)))) { 165 if (verbose) 166 printf("--> trimming log....\n"); 167 if (noaction && !verbose) { 168 if (ent->flags & CE_COMPACT) 169 printf("%s <%dZ>: trimming\n", 170 ent->log,ent->numlogs); 171 else 172 printf("%s <%d>: trimming\n", 173 ent->log,ent->numlogs); 174 } 175 if (ent->pid_file) { 176 pid_file = ent->pid_file; 177 } else { 178 /* Only try to notify syslog if we are root */ 179 if (needroot) 180 pid_file = PIDFILE; 181 else 182 pid_file = NULL; 183 } 184 dotrim(ent->log, pid_file, ent->numlogs, 185 ent->flags, ent->permissions, ent->uid, ent->gid, ent->sig); 186 } else { 187 if (verbose) 188 printf("--> skipping\n"); 189 } 190 } 191} 192 193static void PRS(argc,argv) 194 int argc; 195 char **argv; 196{ 197 int c; 198 char *p; 199 200 timenow = time((time_t *) 0); 201 daytime = ctime(&timenow) + 4; 202 daytime[15] = '\0'; 203 204 /* Let's get our hostname */ 205 (void) gethostname(hostname, sizeof(hostname)); 206 207 /* Truncate domain */ 208 if ((p = strchr(hostname, '.'))) { 209 *p = '\0'; 210 } 211 212 optind = 1; /* Start options parsing */ 213 while ((c=getopt(argc,argv,"nrvFf:t:")) != -1) 214 switch (c) { 215 case 'n': 216 noaction++; 217 break; 218 case 'r': 219 needroot = 0; 220 break; 221 case 'v': 222 verbose++; 223 break; 224 case 'f': 225 conf = optarg; 226 break; 227 case 'F': 228 force++; 229 break; 230 default: 231 usage(); 232 } 233 } 234 235static void usage() 236{ 237 fprintf(stderr, "usage: newsyslog [-Fnrv] [-f config-file]\n"); 238 exit(1); 239} 240 241/* Parse a configuration file and return a linked list of all the logs 242 * to process 243 */ 244static struct conf_entry *parse_file() 245{ 246 FILE *f; 247 char line[BUFSIZ], *parse, *q; 248 char *errline, *group; 249 struct conf_entry *first = NULL; 250 struct conf_entry *working = NULL; 251 struct passwd *pass; 252 struct group *grp; 253 int eol; 254 255 if (strcmp(conf,"-")) 256 f = fopen(conf,"r"); 257 else 258 f = stdin; 259 if (!f) 260 err(1, "%s", conf); 261 while (fgets(line,BUFSIZ,f)) { 262 if ((line[0]== '\n') || (line[0] == '#')) 263 continue; 264 errline = strdup(line); 265 if (!first) { 266 working = (struct conf_entry *) malloc(sizeof(struct conf_entry)); 267 first = working; 268 } else { 269 working->next = (struct conf_entry *) malloc(sizeof(struct conf_entry)); 270 working = working->next; 271 } 272 273 q = parse = missing_field(sob(line),errline); 274 parse = son(line); 275 if (!*parse) 276 errx(1, "malformed line (missing fields):\n%s", errline); 277 *parse = '\0'; 278 working->log = strdup(q); 279 280 q = parse = missing_field(sob(++parse),errline); 281 parse = son(parse); 282 if (!*parse) 283 errx(1, "malformed line (missing fields):\n%s", errline); 284 *parse = '\0'; 285 if ((group = strchr(q, '.')) != NULL) { 286 *group++ = '\0'; 287 if (*q) { 288 if (!(isnumber(*q))) { 289 if ((pass = getpwnam(q)) == NULL) 290 errx(1, 291 "error in config file; unknown user:\n%s", 292 errline); 293 working->uid = pass->pw_uid; 294 } else 295 working->uid = atoi(q); 296 } else 297 working->uid = NONE; 298 299 q = group; 300 if (*q) { 301 if (!(isnumber(*q))) { 302 if ((grp = getgrnam(q)) == NULL) 303 errx(1, 304 "error in config file; unknown group:\n%s", 305 errline); 306 working->gid = grp->gr_gid; 307 } else 308 working->gid = atoi(q); 309 } else 310 working->gid = NONE; 311 312 q = parse = missing_field(sob(++parse),errline); 313 parse = son(parse); 314 if (!*parse) 315 errx(1, "malformed line (missing fields):\n%s", errline); 316 *parse = '\0'; 317 } 318 else 319 working->uid = working->gid = NONE; 320 321 if (!sscanf(q,"%o",&working->permissions)) 322 errx(1, "error in config file; bad permissions:\n%s", 323 errline); 324 325 q = parse = missing_field(sob(++parse),errline); 326 parse = son(parse); 327 if (!*parse) 328 errx(1, "malformed line (missing fields):\n%s", errline); 329 *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