newsyslog.c revision 35915
1139738Simp/* 2110211Smarcel * This file contains changes from the Open Software Foundation. 3110211Smarcel */ 4110211Smarcel 5110211Smarcel/* 6110211Smarcel 7110211SmarcelCopyright 1988, 1989 by the Massachusetts Institute of Technology 8110211Smarcel 9110211SmarcelPermission to use, copy, modify, and distribute this software 10110211Smarceland its documentation for any purpose and without fee is 11110211Smarcelhereby granted, provided that the above copyright notice 12110211Smarcelappear in all copies and that both that copyright notice and 13110211Smarcelthis permission notice appear in supporting documentation, 14110211Smarceland that the names of M.I.T. and the M.I.T. S.I.P.B. not be 15110211Smarcelused in advertising or publicity pertaining to distribution 16110211Smarcelof the software without specific, written prior permission. 17110211SmarcelM.I.T. and the M.I.T. S.I.P.B. make no representations about 18110211Smarcelthe suitability of this software for any purpose. It is 19110211Smarcelprovided "as is" without express or implied warranty. 20110211Smarcel 21110211Smarcel*/ 22110211Smarcel 23110211Smarcel/* 24110211Smarcel * newsyslog - roll over selected logs at the appropriate time, 25110211Smarcel * keeping the a specified number of backup files around. 26110211Smarcel */ 27119880Sobrien 28119880Sobrien#ifndef lint 29119880Sobrienstatic const char rcsid[] = 30193530Sjkim "$Id: newsyslog.c,v 1.16 1998/03/14 22:28:25 pst Exp $"; 31110211Smarcel#endif /* not lint */ 32110211Smarcel 33110211Smarcel#ifndef CONF 34110211Smarcel#define CONF "/etc/athena/newsyslog.conf" /* Configuration file */ 35110211Smarcel#endif 36110211Smarcel#ifndef PIDFILE 37110211Smarcel#define PIDFILE "/etc/syslog.pid" 38110211Smarcel#endif 39167814Sjkim#ifndef COMPRESS_PATH 40167814Sjkim#define COMPRESS_PATH "/usr/ucb/compress" /* File compression program */ 41167814Sjkim#endif 42167814Sjkim#ifndef COMPRESS_PROG 43167814Sjkim#define COMPRESS_PROG "compress" 44167814Sjkim#endif 45167814Sjkim#ifndef COMPRESS_POSTFIX 46110211Smarcel#define COMPRESS_POSTFIX ".Z" 47110211Smarcel#endif 48110211Smarcel 49110211Smarcel#include <ctype.h> 50167814Sjkim#include <err.h> 51167814Sjkim#include <fcntl.h> 52167814Sjkim#include <grp.h> 53167814Sjkim#include <pwd.h> 54167814Sjkim#include <signal.h> 55110211Smarcel#include <stdio.h> 56110211Smarcel#include <stdlib.h> 57110211Smarcel#include <string.h> 58110211Smarcel#include <unistd.h> 59110211Smarcel#include <sys/types.h> 60110211Smarcel#include <sys/time.h> 61167814Sjkim#include <sys/stat.h> 62167814Sjkim#include <sys/param.h> 63167814Sjkim#include <sys/wait.h> 64167814Sjkim 65167814Sjkim#define kbytes(size) (((size) + 1023) >> 10) 66167814Sjkim#ifdef _IBMR2 67110211Smarcel/* Calculates (db * DEV_BSIZE) */ 68110211Smarcel#define dbtob(db) ((unsigned)(db) << UBSHIFT) 69110211Smarcel#endif 70167814Sjkim 71123343Smarcel#define CE_COMPACT 1 /* Compact the achived log files */ 72123343Smarcel#define CE_BINARY 2 /* Logfile is in binary, don't add */ 73163927Smarcel /* status messages */ 74123343Smarcel#define NONE -1 75123343Smarcel 76123343Smarcelstruct conf_entry { 77123343Smarcel char *log; /* Name of the log */ 78123343Smarcel char *pid_file; /* PID file */ 79110211Smarcel int uid; /* Owner of log */ 80110211Smarcel int gid; /* Group of log */ 81110211Smarcel int numlogs; /* Number of logs to keep */ 82110211Smarcel int size; /* Size cutoff to trigger trimming the log */ 83123343Smarcel int hours; /* Hours between log trimming */ 84123343Smarcel int permissions; /* File permissions on the log */ 85110211Smarcel int flags; /* Flags (CE_COMPACT & CE_BINARY) */ 86110211Smarcel struct conf_entry *next; /* Linked list pointer */ 87110211Smarcel}; 88110211Smarcel 89110211Smarcelint verbose = 0; /* Print out what's going on */ 90110211Smarcelint needroot = 1; /* Root privs are necessary */ 91110211Smarcelint noaction = 0; /* Don't do anything, just show it */ 92110211Smarcelint force = 0; /* Force the trim no matter what*/ 93123343Smarcelchar *conf = CONF; /* Configuration file to use */ 94123343Smarceltime_t timenow; 95110211Smarcelpid_t syslog_pid; /* read in from /etc/syslog.pid */ 96110211Smarcel#define MIN_PID 5 97110211Smarcel#define MAX_PID 30000 /* was 65534, see /usr/include/sys/proc.h */ 98110211Smarcelchar hostname[MAXHOSTNAMELEN+1]; /* hostname */ 99110211Smarcelchar *daytime; /* timenow in human readable form */ 100110211Smarcel 101110211Smarcel#ifndef OSF 102110211Smarcelchar *strdup(char *strp); 103123343Smarcel#endif 104123343Smarcel 105110211Smarcelstatic struct conf_entry *parse_file(); 106110211Smarcelstatic char *sob(char *p); 107110211Smarcelstatic char *son(char *p); 108110211Smarcelstatic char *missing_field(char *p,char *errline); 109110211Smarcelstatic void do_entry(struct conf_entry *ent); 110110211Smarcelstatic void PRS(int argc,char **argv); 111110211Smarcelstatic void usage(); 112110211Smarcelstatic void dotrim(char *log,char *pid_file,int numdays,int falgs,int perm, int owner_uid,int group_gid); 113123343Smarcelstatic int log_trim(char *log); 114123343Smarcelstatic void compress_log(char *log); 115110211Smarcelstatic int sizefile(char *file); 116110211Smarcelstatic int age_old_log(char *file); 117110211Smarcelstatic pid_t get_pid(char *pid_file); 118110211Smarcel 119110211Smarcelint main(argc,argv) 120110211Smarcel int argc; 121110211Smarcel char **argv; 122110211Smarcel{ 123123343Smarcel struct conf_entry *p, *q; 124123343Smarcel 125110211Smarcel PRS(argc,argv); 126110211Smarcel if (needroot && getuid() && geteuid()) 127110211Smarcel errx(1, "must have root privs"); 128110211Smarcel p = q = parse_file(); 129110211Smarcel 130110211Smarcel syslog_pid = needroot ? get_pid(PIDFILE) : 0; 131110211Smarcel 132110211Smarcel while (p) { 133110211Smarcel do_entry(p); 134110211Smarcel p=p->next; 135110211Smarcel free((char *) q); 136110211Smarcel q=p; 137167814Sjkim } 138110211Smarcel return(0); 139110211Smarcel} 140110211Smarcel 141110211Smarcelstatic void do_entry(ent) 142110211Smarcel struct conf_entry *ent; 143110211Smarcel 144110211Smarcel{ 145110211Smarcel int size, modtime; 146110211Smarcel 147135700Smarcel if (verbose) { 148110211Smarcel if (ent->flags & CE_COMPACT) 149110211Smarcel printf("%s <%dZ>: ",ent->log,ent->numlogs); 150167814Sjkim else 151167814Sjkim printf("%s <%d>: ",ent->log,ent->numlogs); 152110211Smarcel } 153110211Smarcel size = sizefile(ent->log); 154110211Smarcel modtime = age_old_log(ent->log); 155135700Smarcel if (size < 0) { 156110211Smarcel if (verbose) 157135700Smarcel printf("does not exist.\n"); 158110211Smarcel } else { 159110211Smarcel if (verbose && (ent->size > 0)) 160110211Smarcel printf("size (Kb): %d [%d] ", size, ent->size); 161110211Smarcel if (verbose && (ent->hours > 0)) 162110211Smarcel printf(" age (hr): %d [%d] ", modtime, ent->hours); 163110211Smarcel if (force || ((ent->size > 0) && (size >= ent->size)) || 164110211Smarcel ((ent->hours > 0) && ((modtime >= ent->hours) 165110211Smarcel || (modtime < 0)))) { 166110211Smarcel if (verbose) 167110211Smarcel printf("--> trimming log....\n"); 168110211Smarcel if (noaction && !verbose) { 169110211Smarcel if (ent->flags & CE_COMPACT) 170110211Smarcel printf("%s <%dZ>: trimming", 171110211Smarcel ent->log,ent->numlogs); 172110211Smarcel else 173110211Smarcel printf("%s <%d>: trimming", 174110211Smarcel ent->log,ent->numlogs); 175110211Smarcel } 176110211Smarcel dotrim(ent->log, ent->pid_file, ent->numlogs, 177110211Smarcel ent->flags, ent->permissions, ent->uid, ent->gid); 178110211Smarcel } else { 179110211Smarcel if (verbose) 180167814Sjkim printf("--> skipping\n"); 181110211Smarcel } 182110211Smarcel } 183110211Smarcel} 184 185static void PRS(argc,argv) 186 int argc; 187 char **argv; 188{ 189 int c; 190 char *p; 191 192 timenow = time((time_t *) 0); 193 daytime = ctime(&timenow) + 4; 194 daytime[15] = '\0'; 195 196 /* Let's get our hostname */ 197 (void) gethostname(hostname, sizeof(hostname)); 198 199 /* Truncate domain */ 200 if ((p = strchr(hostname, '.'))) { 201 *p = '\0'; 202 } 203 204 optind = 1; /* Start options parsing */ 205 while ((c=getopt(argc,argv,"nrvFf:t:")) != -1) 206 switch (c) { 207 case 'n': 208 noaction++; /* This implies needroot as off */ 209 /* fall through */ 210 case 'r': 211 needroot = 0; 212 break; 213 case 'v': 214 verbose++; 215 break; 216 case 'f': 217 conf = optarg; 218 break; 219 case 'F': 220 force++; 221 break; 222 default: 223 usage(); 224 } 225 } 226 227static void usage() 228{ 229 fprintf(stderr, "usage: newsyslog [-nrvF] [-f config-file]\n"); 230 exit(1); 231} 232 233/* Parse a configuration file and return a linked list of all the logs 234 * to process 235 */ 236static struct conf_entry *parse_file() 237{ 238 FILE *f; 239 char line[BUFSIZ], *parse, *q; 240 char *errline, *group; 241 struct conf_entry *first = NULL; 242 struct conf_entry *working = NULL; 243 struct passwd *pass; 244 struct group *grp; 245 int eol; 246 247 if (strcmp(conf,"-")) 248 f = fopen(conf,"r"); 249 else 250 f = stdin; 251 if (!f) 252 err(1, "%s", conf); 253 while (fgets(line,BUFSIZ,f)) { 254 if ((line[0]== '\n') || (line[0] == '#')) 255 continue; 256 errline = strdup(line); 257 if (!first) { 258 working = (struct conf_entry *) malloc(sizeof(struct conf_entry)); 259 first = working; 260 } else { 261 working->next = (struct conf_entry *) malloc(sizeof(struct conf_entry)); 262 working = working->next; 263 } 264 265 q = parse = missing_field(sob(line),errline); 266 parse = son(line); 267 if (!*parse) 268 errx(1, "malformed line (missing fields):\n%s", errline); 269 *parse = '\0'; 270 working->log = strdup(q); 271 272 q = parse = missing_field(sob(++parse),errline); 273 parse = son(parse); 274 if (!*parse) 275 errx(1, "malformed line (missing fields):\n%s", errline); 276 *parse = '\0'; 277 if ((group = strchr(q, '.')) != NULL) { 278 *group++ = '\0'; 279 if (*q) { 280 if (!(isnumber(*q))) { 281 if ((pass = getpwnam(q)) == NULL) 282 errx(1, 283 "error in config file; unknown user:\n%s", 284 errline); 285 working->uid = pass->pw_uid; 286 } else 287 working->uid = atoi(q); 288 } else 289 working->uid = NONE; 290 291 q = group; 292 if (*q) { 293 if (!(isnumber(*q))) { 294 if ((grp = getgrnam(q)) == NULL) 295 errx(1, 296 "error in config file; unknown group:\n%s", 297 errline); 298 working->gid = grp->gr_gid; 299 } else 300 working->gid = atoi(q); 301 } else 302 working->gid = NONE; 303 304 q = parse = missing_field(sob(++parse),errline); 305 parse = son(parse); 306 if (!*parse) 307 errx(1, "malformed line (missing fields):\n%s", errline); 308 *parse = '\0'; 309 } 310 else 311 working->uid = working->gid = NONE; 312 313 if (!sscanf(q,"%o",&working->permissions)) 314 errx(1, "error in config file; bad permissions:\n%s", 315 errline); 316 317 q = parse = missing_field(sob(++parse),errline); 318 parse = son(parse); 319 if (!*parse) 320 errx(1, "malformed line (missing fields):\n%s", errline); 321 *parse = '\0'; 322 if (!sscanf(q,"%d",&working->numlogs)) 323 errx(1, "error in config file; bad number:\n%s", 324 errline); 325 326 q = parse = missing_field(sob(++parse),errline); 327 parse = son(parse); 328 if (!*parse) 329 errx(1, "malformed line (missing fields):\n%s", errline); 330 *parse = '\0'; 331 if (isdigit(*q)) 332 working->size = atoi(q); 333 else 334 working->size = -1; 335 336 q = parse = missing_field(sob(++parse),errline); 337 parse = son(parse); 338 eol = !*parse; 339 *parse = '\0'; 340 if (isdigit(*q)) 341 working->hours = atoi(q); 342 else 343 working->hours = -1; 344 345 if (eol) 346 q = NULL; 347 else { 348 q = parse = sob(++parse); /* Optional field */ 349 parse = son(parse); 350 if (!*parse) 351 eol = 1; 352 *parse = '\0'; 353 } 354 355 working->flags = 0; 356 while (q && *q && !isspace(*q)) { 357 if ((*q == 'Z') || (*q == 'z')) 358 working->flags |= CE_COMPACT; 359 else if ((*q == 'B') || (*q == 'b')) 360 working->flags |= CE_BINARY; 361 else if (*q != '-') 362 errx(1, "illegal flag in config file -- %c", *q); 363 q++; 364 } 365 366 if (eol) 367 q = NULL; 368 else { 369 q = parse = sob(++parse); /* Optional field */ 370 *(parse = son(parse)) = '\0'; 371 } 372 373 working->pid_file = NULL; 374 if (q && *q) { 375 if (*q == '/') 376 working->pid_file = strdup(q); 377 else 378 errx(1, "illegal pid file in config file:\n%s", q); 379 } 380 381 free(errline); 382 } 383 if (working) 384 working->next = (struct conf_entry *) NULL; 385 (void) fclose(f); 386 return(first); 387} 388 389static char *missing_field(p,errline) 390 char *p,*errline; 391{ 392 if (!p || !*p) 393 errx(1, "missing field in config file:\n%s", errline); 394 return(p); 395} 396 397static void dotrim(log,pid_file,numdays,flags,perm,owner_uid,group_gid) 398 char *log; 399 char *pid_file; 400 int numdays; 401 int flags; 402 int perm; 403 int owner_uid; 404 int group_gid; 405{ 406 char file1 [MAXPATHLEN+1], file2 [MAXPATHLEN+1]; 407 char zfile1[MAXPATHLEN+1], zfile2[MAXPATHLEN+1]; 408 int notified, need_notification, fd, _numdays; 409 struct stat st; 410 pid_t pid; 411 412#ifdef _IBMR2 413/* AIX 3.1 has a broken fchown- if the owner_uid is -1, it will actually */ 414/* change it to be owned by uid -1, instead of leaving it as is, as it is */ 415/* supposed to. */ 416 if (owner_uid == -1) 417 owner_uid = geteuid(); 418#endif 419 420 /* Remove oldest log */ 421 (void) sprintf(file1,"%s.%d",log,numdays); 422 (void) strcpy(zfile1, file1); 423 (void) strcat(zfile1, COMPRESS_POSTFIX); 424 425 if (noaction) { 426 printf("rm -f %s\n", file1); 427 printf("rm -f %s\n", zfile1); 428 } else { 429 (void) unlink(file1); 430 (void) unlink(zfile1); 431 } 432 433 /* Move down log files */ 434 _numdays = numdays; /* preserve */ 435 while (numdays--) { 436 (void) strcpy(file2,file1); 437 (void) sprintf(file1,"%s.%d",log,numdays); 438 (void) strcpy(zfile1, file1); 439 (void) strcpy(zfile2, file2); 440 if (lstat(file1, &st)) { 441 (void) strcat(zfile1, COMPRESS_POSTFIX); 442 (void) strcat(zfile2, COMPRESS_POSTFIX); 443 if (lstat(zfile1, &st)) continue; 444 } 445 if (noaction) { 446 printf("mv %s %s\n",zfile1,zfile2); 447 printf("chmod %o %s\n", perm, zfile2); 448 printf("chown %d.%d %s\n", 449 owner_uid, group_gid, zfile2); 450 } else { 451 (void) rename(zfile1, zfile2); 452 (void) chmod(zfile2, perm); 453 (void) chown(zfile2, owner_uid, group_gid); 454 } 455 } 456 if (!noaction && !(flags & CE_BINARY)) 457 (void) log_trim(log); /* Report the trimming to the old log */ 458 459 if (!_numdays) { 460 if (noaction) 461 printf("rm %s\n",log); 462 else 463 (void)unlink(log); 464 } 465 else { 466 if (noaction) 467 printf("mv %s to %s\n",log,file1); 468 else 469 (void)rename(log, file1); 470 } 471 472 if (noaction) 473 printf("Start new log..."); 474 else { 475 fd = creat(log,perm); 476 if (fd < 0) 477 err(1, "can't start new log"); 478 if (fchown(fd, owner_uid, group_gid)) 479 err(1, "can't chmod new log file"); 480 (void) close(fd); 481 if (!(flags & CE_BINARY)) 482 if (log_trim(log)) /* Add status message */ 483 err(1, "can't add status message to log"); 484 } 485 if (noaction) 486 printf("chmod %o %s...",perm,log); 487 else 488 (void) chmod(log,perm); 489 490 pid = 0; 491 need_notification = notified = 0; 492 if (pid_file != NULL) { 493 need_notification = 1; 494 pid = get_pid(pid_file); 495 } else if (needroot && !(flags & CE_BINARY)) { 496 need_notification = 1; 497 pid = syslog_pid; 498 } 499 500 if (pid) { 501 if (noaction) { 502 notified = 1; 503 printf("kill -HUP %d\n", (int)pid); 504 } else if (kill(pid,SIGHUP)) 505 warn("can't notify daemon, pid %d", (int)pid); 506 else { 507 notified = 1; 508 if (verbose) 509 printf("daemon pid %d notified\n", (int)pid); 510 } 511 } 512 513 if ((flags & CE_COMPACT)) { 514 if (need_notification && !notified) 515 warnx("log not compressed because daemon not notified"); 516 else if (noaction) 517 printf("Compress %s.0\n",log); 518 else { 519 if (notified) { 520 if (verbose) 521 printf("small pause to allow daemon to close log\n"); 522 sleep(10); 523 } 524 compress_log(log); 525 } 526 } 527} 528 529/* Log the fact that the logs were turned over */ 530static int log_trim(log) 531 char *log; 532{ 533 FILE *f; 534 if ((f = fopen(log,"a")) == NULL) 535 return(-1); 536 fprintf(f,"%s %s newsyslog[%d]: logfile turned over\n", 537 daytime, hostname, (int)getpid()); 538 if (fclose(f) == EOF) 539 err(1, "log_trim: fclose:"); 540 return(0); 541} 542 543/* Fork of /usr/ucb/compress to compress the old log file */ 544static void compress_log(log) 545 char *log; 546{ 547 pid_t pid; 548 char tmp[MAXPATHLEN+1]; 549 550 (void) sprintf(tmp,"%s.0",log); 551 pid = fork(); 552 if (pid < 0) 553 err(1, "fork"); 554 else if (!pid) { 555 (void) execl(COMPRESS_PATH,COMPRESS_PROG,"-f",tmp,0); 556 err(1, COMPRESS_PATH); 557 } 558} 559 560/* Return size in kilobytes of a file */ 561static int sizefile(file) 562 char *file; 563{ 564 struct stat sb; 565 566 if (stat(file,&sb) < 0) 567 return(-1); 568 return(kbytes(dbtob(sb.st_blocks))); 569} 570 571/* Return the age of old log file (file.0) */ 572static int age_old_log(file) 573 char *file; 574{ 575 struct stat sb; 576 char tmp[MAXPATHLEN+sizeof(".0")+sizeof(COMPRESS_POSTFIX)+1]; 577 578 (void) strcpy(tmp,file); 579 if (stat(strcat(tmp,".0"),&sb) < 0) 580 if (stat(strcat(tmp,COMPRESS_POSTFIX), &sb) < 0) 581 return(-1); 582 return( (int) (timenow - sb.st_mtime + 1800) / 3600); 583} 584 585static pid_t get_pid(pid_file) 586 char *pid_file; 587{ 588 FILE *f; 589 char line[BUFSIZ]; 590 pid_t pid = 0; 591 592 if ((f = fopen(pid_file,"r")) == NULL) 593 warn("can't open %s pid file to restart a daemon", 594 pid_file); 595 else { 596 if (fgets(line,BUFSIZ,f)) { 597 pid = atol(line); 598 if (pid < MIN_PID || pid > MAX_PID) { 599 warnx("preposterous process number: %d", (int)pid); 600 pid = 0; 601 } 602 } else 603 warn("can't read %s pid file to restart a daemon", 604 pid_file); 605 (void)fclose(f); 606 } 607 return pid; 608} 609 610#ifndef OSF 611/* Duplicate a string using malloc */ 612 613char *strdup(strp) 614register char *strp; 615{ 616 register char *cp; 617 618 if ((cp = malloc((unsigned) strlen(strp) + 1)) == NULL) 619 abort(); 620 return(strcpy (cp, strp)); 621} 622#endif 623 624/* Skip Over Blanks */ 625char *sob(p) 626 register char *p; 627{ 628 while (p && *p && isspace(*p)) 629 p++; 630 return(p); 631} 632 633/* Skip Over Non-Blanks */ 634char *son(p) 635 register char *p; 636{ 637 while (p && *p && !isspace(*p)) 638 p++; 639 return(p); 640} 641