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