newsyslog.c revision 35917
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.17 1998/05/10 18:47:50 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; 95pid_t syslog_pid; /* read in from /etc/syslog.pid */ 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); 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 syslog_pid = needroot ? get_pid(PIDFILE) : 0; 131 132 while (p) { 133 do_entry(p); 134 p=p->next; 135 free((char *) q); 136 q=p; 137 } 138 return(0); 139} 140 141static void do_entry(ent) 142 struct conf_entry *ent; 143 144{ 145 int size, modtime; 146 147 if (verbose) { 148 if (ent->flags & CE_COMPACT) 149 printf("%s <%dZ>: ",ent->log,ent->numlogs); 150 else 151 printf("%s <%d>: ",ent->log,ent->numlogs); 152 } 153 size = sizefile(ent->log); 154 modtime = age_old_log(ent->log); 155 if (size < 0) { 156 if (verbose) 157 printf("does not exist.\n"); 158 } else { 159 if (verbose && (ent->size > 0)) 160 printf("size (Kb): %d [%d] ", size, ent->size); 161 if (verbose && (ent->hours > 0)) 162 printf(" age (hr): %d [%d] ", modtime, ent->hours); 163 if (force || ((ent->size > 0) && (size >= ent->size)) || 164 ((ent->hours > 0) && ((modtime >= ent->hours) 165 || (modtime < 0)))) { 166 if (verbose) 167 printf("--> trimming log....\n"); 168 if (noaction && !verbose) { 169 if (ent->flags & CE_COMPACT) 170 printf("%s <%dZ>: trimming", 171 ent->log,ent->numlogs); 172 else 173 printf("%s <%d>: trimming", 174 ent->log,ent->numlogs); 175 } 176 dotrim(ent->log, ent->pid_file, ent->numlogs, 177 ent->flags, ent->permissions, ent->uid, ent->gid); 178 } else { 179 if (verbose) 180 printf("--> skipping\n"); 181 } 182 } 183} 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 [-Fnrv] [-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