newsyslog.c revision 35920
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.18 1998/05/10 19:04:06 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 struct conf_entry *next; /* Linked list pointer */ 87}; 88 89int verbose = 0; /* Print out what's going on */ 90int needroot = 1; /* Root privs are necessary */ 91int noaction = 0; /* Don't do anything, just show it */ 92int force = 0; /* Force the trim no matter what*/ 93char *conf = CONF; /* Configuration file to use */ 94time_t timenow; 95#define MIN_PID 5 96#define MAX_PID 30000 /* was 65534, see /usr/include/sys/proc.h */ 97char hostname[MAXHOSTNAMELEN+1]; /* hostname */ 98char *daytime; /* timenow in human readable form */ 99 100#ifndef OSF 101char *strdup(char *strp); 102#endif 103 104static struct conf_entry *parse_file(); 105static char *sob(char *p); 106static char *son(char *p); 107static char *missing_field(char *p,char *errline); 108static void do_entry(struct conf_entry *ent); 109static void PRS(int argc,char **argv); 110static void usage(); 111static void dotrim(char *log,char *pid_file,int numdays,int falgs,int perm, int owner_uid,int group_gid); 112static int log_trim(char *log); 113static void compress_log(char *log); 114static int sizefile(char *file); 115static int age_old_log(char *file); 116static pid_t get_pid(char *pid_file); 117 118int main(argc,argv) 119 int argc; 120 char **argv; 121{ 122 struct conf_entry *p, *q; 123 124 PRS(argc,argv); 125 if (needroot && getuid() && geteuid()) 126 errx(1, "must have root privs"); 127 p = q = parse_file(); 128 129 while (p) { 130 do_entry(p); 131 p=p->next; 132 free((char *) q); 133 q=p; 134 } 135 return(0); 136} 137 138static void do_entry(ent) 139 struct conf_entry *ent; 140 141{ 142 int size, modtime; 143 char *pid_file; 144 145 if (verbose) { 146 if (ent->flags & CE_COMPACT) 147 printf("%s <%dZ>: ",ent->log,ent->numlogs); 148 else 149 printf("%s <%d>: ",ent->log,ent->numlogs); 150 } 151 size = sizefile(ent->log); 152 modtime = age_old_log(ent->log); 153 if (size < 0) { 154 if (verbose) 155 printf("does not exist.\n"); 156 } else { 157 if (verbose && (ent->size > 0)) 158 printf("size (Kb): %d [%d] ", size, ent->size); 159 if (verbose && (ent->hours > 0)) 160 printf(" age (hr): %d [%d] ", modtime, ent->hours); 161 if (force || ((ent->size > 0) && (size >= ent->size)) || 162 ((ent->hours > 0) && ((modtime >= ent->hours) 163 || (modtime < 0)))) { 164 if (verbose) 165 printf("--> trimming log....\n"); 166 if (noaction && !verbose) { 167 if (ent->flags & CE_COMPACT) 168 printf("%s <%dZ>: trimming\n", 169 ent->log,ent->numlogs); 170 else 171 printf("%s <%d>: trimming\n", 172 ent->log,ent->numlogs); 173 } 174 if (ent->pid_file) { 175 pid_file = ent->pid_file; 176 } else { 177 /* Only try to notify syslog if we are root */ 178 if (needroot) 179 pid_file = PIDFILE; 180 else 181 pid_file = NULL; 182 } 183 dotrim(ent->log, pid_file, ent->numlogs, 184 ent->flags, ent->permissions, ent->uid, ent->gid); 185 } else { 186 if (verbose) 187 printf("--> skipping\n"); 188 } 189 } 190} 191 192static void PRS(argc,argv) 193 int argc; 194 char **argv; 195{ 196 int c; 197 char *p; 198 199 timenow = time((time_t *) 0); 200 daytime = ctime(&timenow) + 4; 201 daytime[15] = '\0'; 202 203 /* Let's get our hostname */ 204 (void) gethostname(hostname, sizeof(hostname)); 205 206 /* Truncate domain */ 207 if ((p = strchr(hostname, '.'))) { 208 *p = '\0'; 209 } 210 211 optind = 1; /* Start options parsing */ 212 while ((c=getopt(argc,argv,"nrvFf:t:")) != -1) 213 switch (c) { 214 case 'n': 215 noaction++; 216 break; 217 case 'r': 218 needroot = 0; 219 break; 220 case 'v': 221 verbose++; 222 break; 223 case 'f': 224 conf = optarg; 225 break; 226 case 'F': 227 force++; 228 break; 229 default: 230 usage(); 231 } 232 } 233 234static void usage() 235{ 236 fprintf(stderr, "usage: newsyslog [-Fnrv] [-f config-file]\n"); 237 exit(1); 238} 239 240/* Parse a configuration file and return a linked list of all the logs 241 * to process 242 */ 243static struct conf_entry *parse_file() 244{ 245 FILE *f; 246 char line[BUFSIZ], *parse, *q; 247 char *errline, *group; 248 struct conf_entry *first = NULL; 249 struct conf_entry *working = NULL; 250 struct passwd *pass; 251 struct group *grp; 252 int eol; 253 254 if (strcmp(conf,"-")) 255 f = fopen(conf,"r"); 256 else 257 f = stdin; 258 if (!f) 259 err(1, "%s", conf); 260 while (fgets(line,BUFSIZ,f)) { 261 if ((line[0]== '\n') || (line[0] == '#')) 262 continue; 263 errline = strdup(line); 264 if (!first) { 265 working = (struct conf_entry *) malloc(sizeof(struct conf_entry)); 266 first = working; 267 } else { 268 working->next = (struct conf_entry *) malloc(sizeof(struct conf_entry)); 269 working = working->next; 270 } 271 272 q = parse = missing_field(sob(line),errline); 273 parse = son(line); 274 if (!*parse) 275 errx(1, "malformed line (missing fields):\n%s", errline); 276 *parse = '\0'; 277 working->log = strdup(q); 278 279 q = parse = missing_field(sob(++parse),errline); 280 parse = son(parse); 281 if (!*parse) 282 errx(1, "malformed line (missing fields):\n%s", errline); 283 *parse = '\0'; 284 if ((group = strchr(q, '.')) != NULL) { 285 *group++ = '\0'; 286 if (*q) { 287 if (!(isnumber(*q))) { 288 if ((pass = getpwnam(q)) == NULL) 289 errx(1, 290 "error in config file; unknown user:\n%s", 291 errline); 292 working->uid = pass->pw_uid; 293 } else 294 working->uid = atoi(q); 295 } else 296 working->uid = NONE; 297 298 q = group; 299 if (*q) { 300 if (!(isnumber(*q))) { 301 if ((grp = getgrnam(q)) == NULL) 302 errx(1, 303 "error in config file; unknown group:\n%s", 304 errline); 305 working->gid = grp->gr_gid; 306 } else 307 working->gid = atoi(q); 308 } else 309 working->gid = NONE; 310 311 q = parse = missing_field(sob(++parse),errline); 312 parse = son(parse); 313 if (!*parse) 314 errx(1, "malformed line (missing fields):\n%s", errline); 315 *parse = '\0'; 316 } 317 else 318 working->uid = working->gid = NONE; 319 320 if (!sscanf(q,"%o",&working->permissions)) 321 errx(1, "error in config file; bad permissions:\n%s", 322 errline); 323 324 q = parse = missing_field(sob(++parse),errline); 325 parse = son(parse); 326 if (!*parse) 327 errx(1, "malformed line (missing fields):\n%s", errline); 328 *parse = '\0'; 329 if (!sscanf(q,"%d",&working->numlogs)) 330 errx(1, "error in config file; bad number:\n%s", 331 errline); 332 333 q = parse = missing_field(sob(++parse),errline); 334 parse = son(parse); 335 if (!*parse) 336 errx(1, "malformed line (missing fields):\n%s", errline); 337 *parse = '\0'; 338 if (isdigit(*q)) 339 working->size = atoi(q); 340 else 341 working->size = -1; 342 343 q = parse = missing_field(sob(++parse),errline); 344 parse = son(parse); 345 eol = !*parse; 346 *parse = '\0'; 347 if (isdigit(*q)) 348 working->hours = atoi(q); 349 else 350 working->hours = -1; 351 352 if (eol) 353 q = NULL; 354 else { 355 q = parse = sob(++parse); /* Optional field */ 356 parse = son(parse); 357 if (!*parse) 358 eol = 1; 359 *parse = '\0'; 360 } 361 362 working->flags = 0; 363 while (q && *q && !isspace(*q)) { 364 if ((*q == 'Z') || (*q == 'z')) 365 working->flags |= CE_COMPACT; 366 else if ((*q == 'B') || (*q == 'b')) 367 working->flags |= CE_BINARY; 368 else if (*q != '-') 369 errx(1, "illegal flag in config file -- %c", *q); 370 q++; 371 } 372 373 if (eol) 374 q = NULL; 375 else { 376 q = parse = sob(++parse); /* Optional field */ 377 *(parse = son(parse)) = '\0'; 378 } 379 380 working->pid_file = NULL; 381 if (q && *q) { 382 if (*q == '/') 383 working->pid_file = strdup(q); 384 else 385 errx(1, "illegal pid file in config file:\n%s", q); 386 } 387 388 free(errline); 389 } 390 if (working) 391 working->next = (struct conf_entry *) NULL; 392 (void) fclose(f); 393 return(first); 394} 395 396static char *missing_field(p,errline) 397 char *p,*errline; 398{ 399 if (!p || !*p) 400 errx(1, "missing field in config file:\n%s", errline); 401 return(p); 402} 403 404static void dotrim(log,pid_file,numdays,flags,perm,owner_uid,group_gid) 405 char *log; 406 char *pid_file; 407 int numdays; 408 int flags; 409 int perm; 410 int owner_uid; 411 int group_gid; 412{ 413 char file1 [MAXPATHLEN+1], file2 [MAXPATHLEN+1]; 414 char zfile1[MAXPATHLEN+1], zfile2[MAXPATHLEN+1]; 415 int notified, need_notification, fd, _numdays; 416 struct stat st; 417 pid_t pid; 418 419#ifdef _IBMR2 420/* AIX 3.1 has a broken fchown- if the owner_uid is -1, it will actually */ 421/* change it to be owned by uid -1, instead of leaving it as is, as it is */ 422/* supposed to. */ 423 if (owner_uid == -1) 424 owner_uid = geteuid(); 425#endif 426 427 /* Remove oldest log */ 428 (void) sprintf(file1,"%s.%d",log,numdays); 429 (void) strcpy(zfile1, file1); 430 (void) strcat(zfile1, COMPRESS_POSTFIX); 431 432 if (noaction) { 433 printf("rm -f %s\n", file1); 434 printf("rm -f %s\n", zfile1); 435 } else { 436 (void) unlink(file1); 437 (void) unlink(zfile1); 438 } 439 440 /* Move down log files */ 441 _numdays = numdays; /* preserve */ 442 while (numdays--) { 443 (void) strcpy(file2,file1); 444 (void) sprintf(file1,"%s.%d",log,numdays); 445 (void) strcpy(zfile1, file1); 446 (void) strcpy(zfile2, file2); 447 if (lstat(file1, &st)) { 448 (void) strcat(zfile1, COMPRESS_POSTFIX); 449 (void) strcat(zfile2, COMPRESS_POSTFIX); 450 if (lstat(zfile1, &st)) continue; 451 } 452 if (noaction) { 453 printf("mv %s %s\n",zfile1,zfile2); 454 printf("chmod %o %s\n", perm, zfile2); 455 printf("chown %d.%d %s\n", 456 owner_uid, group_gid, zfile2); 457 } else { 458 (void) rename(zfile1, zfile2); 459 (void) chmod(zfile2, perm); 460 (void) chown(zfile2, owner_uid, group_gid); 461 } 462 } 463 if (!noaction && !(flags & CE_BINARY)) 464 (void) log_trim(log); /* Report the trimming to the old log */ 465 466 if (!_numdays) { 467 if (noaction) 468 printf("rm %s\n",log); 469 else 470 (void)unlink(log); 471 } 472 else { 473 if (noaction) 474 printf("mv %s to %s\n",log,file1); 475 else 476 (void)rename(log, file1); 477 } 478 479 if (noaction) 480 printf("Start new log..."); 481 else { 482 fd = creat(log,perm); 483 if (fd < 0) 484 err(1, "can't start new log"); 485 if (fchown(fd, owner_uid, group_gid)) 486 err(1, "can't chmod new log file"); 487 (void) close(fd); 488 if (!(flags & CE_BINARY)) 489 if (log_trim(log)) /* Add status message */ 490 err(1, "can't add status message to log"); 491 } 492 if (noaction) 493 printf("chmod %o %s...",perm,log); 494 else 495 (void) chmod(log,perm); 496 497 pid = 0; 498 need_notification = notified = 0; 499 if (pid_file != NULL) { 500 need_notification = 1; 501 pid = get_pid(pid_file); 502 } 503 504 if (pid) { 505 if (noaction) { 506 notified = 1; 507 printf("kill -HUP %d\n", (int)pid); 508 } else if (kill(pid,SIGHUP)) 509 warn("can't notify daemon, pid %d", (int)pid); 510 else { 511 notified = 1; 512 if (verbose) 513 printf("daemon pid %d notified\n", (int)pid); 514 } 515 } 516 517 if ((flags & CE_COMPACT)) { 518 if (need_notification && !notified) 519 warnx("log not compressed because daemon not notified"); 520 else if (noaction) 521 printf("Compress %s.0\n",log); 522 else { 523 if (notified) { 524 if (verbose) 525 printf("small pause to allow daemon to close log\n"); 526 sleep(10); 527 } 528 compress_log(log); 529 } 530 } 531} 532 533/* Log the fact that the logs were turned over */ 534static int log_trim(log) 535 char *log; 536{ 537 FILE *f; 538 if ((f = fopen(log,"a")) == NULL) 539 return(-1); 540 fprintf(f,"%s %s newsyslog[%d]: logfile turned over\n", 541 daytime, hostname, (int)getpid()); 542 if (fclose(f) == EOF) 543 err(1, "log_trim: fclose:"); 544 return(0); 545} 546 547/* Fork of /usr/ucb/compress to compress the old log file */ 548static void compress_log(log) 549 char *log; 550{ 551 pid_t pid; 552 char tmp[MAXPATHLEN+1]; 553 554 (void) sprintf(tmp,"%s.0",log); 555 pid = fork(); 556 if (pid < 0) 557 err(1, "fork"); 558 else if (!pid) { 559 (void) execl(COMPRESS_PATH,COMPRESS_PROG,"-f",tmp,0); 560 err(1, COMPRESS_PATH); 561 } 562} 563 564/* Return size in kilobytes of a file */ 565static int sizefile(file) 566 char *file; 567{ 568 struct stat sb; 569 570 if (stat(file,&sb) < 0) 571 return(-1); 572 return(kbytes(dbtob(sb.st_blocks))); 573} 574 575/* Return the age of old log file (file.0) */ 576static int age_old_log(file) 577 char *file; 578{ 579 struct stat sb; 580 char tmp[MAXPATHLEN+sizeof(".0")+sizeof(COMPRESS_POSTFIX)+1]; 581 582 (void) strcpy(tmp,file); 583 if (stat(strcat(tmp,".0"),&sb) < 0) 584 if (stat(strcat(tmp,COMPRESS_POSTFIX), &sb) < 0) 585 return(-1); 586 return( (int) (timenow - sb.st_mtime + 1800) / 3600); 587} 588 589static pid_t get_pid(pid_file) 590 char *pid_file; 591{ 592 FILE *f; 593 char line[BUFSIZ]; 594 pid_t pid = 0; 595 596 if ((f = fopen(pid_file,"r")) == NULL) 597 warn("can't open %s pid file to restart a daemon", 598 pid_file); 599 else { 600 if (fgets(line,BUFSIZ,f)) { 601 pid = atol(line); 602 if (pid < MIN_PID || pid > MAX_PID) { 603 warnx("preposterous process number: %d", (int)pid); 604 pid = 0; 605 } 606 } else 607 warn("can't read %s pid file to restart a daemon", 608 pid_file); 609 (void)fclose(f); 610 } 611 return pid; 612} 613 614#ifndef OSF 615/* Duplicate a string using malloc */ 616 617char *strdup(strp) 618register char *strp; 619{ 620 register char *cp; 621 622 if ((cp = malloc((unsigned) strlen(strp) + 1)) == NULL) 623 abort(); 624 return(strcpy (cp, strp)); 625} 626#endif 627 628/* Skip Over Blanks */ 629char *sob(p) 630 register char *p; 631{ 632 while (p && *p && isspace(*p)) 633 p++; 634 return(p); 635} 636 637/* Skip Over Non-Blanks */ 638char *son(p) 639 register char *p; 640{ 641 while (p && *p && !isspace(*p)) 642 p++; 643 return(p); 644} 645