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