newsyslog.c revision 25443
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: imp $ 29 */ 30 31#ifndef lint 32static char rcsid[] = "$Id: newsyslog.c,v 1.10 1997/03/31 05:10:25 imp 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 = get_pid(PIDFILE); 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 249 if (strcmp(conf,"-")) 250 f = fopen(conf,"r"); 251 else 252 f = stdin; 253 if (!f) 254 err(1, "%s", conf); 255 while (fgets(line,BUFSIZ,f)) { 256 if ((line[0]== '\n') || (line[0] == '#')) 257 continue; 258 errline = strdup(line); 259 if (!first) { 260 working = (struct conf_entry *) malloc(sizeof(struct conf_entry)); 261 first = working; 262 } else { 263 working->next = (struct conf_entry *) malloc(sizeof(struct conf_entry)); 264 working = working->next; 265 } 266 267 q = parse = missing_field(sob(line),errline); 268 *(parse = son(line)) = '\0'; 269 working->log = strdup(q); 270 271 q = parse = missing_field(sob(++parse),errline); 272 *(parse = son(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)) = '\0'; 302 } 303 else 304 working->uid = working->gid = NONE; 305 306 if (!sscanf(q,"%o",&working->permissions)) 307 errx(1, "Error in config file; bad permissions:\n%s", 308 errline); 309 310 q = parse = missing_field(sob(++parse),errline); 311 *(parse = son(parse)) = '\0'; 312 if (!sscanf(q,"%d",&working->numlogs)) 313 errx(1, "Error in config file; bad number:\n%s", 314 errline); 315 316 q = parse = missing_field(sob(++parse),errline); 317 *(parse = son(parse)) = '\0'; 318 if (isdigit(*q)) 319 working->size = atoi(q); 320 else 321 working->size = -1; 322 323 q = parse = missing_field(sob(++parse),errline); 324 *(parse = son(parse)) = '\0'; 325 if (isdigit(*q)) 326 working->hours = atoi(q); 327 else 328 working->hours = -1; 329 330 q = parse = sob(++parse); /* Optional field */ 331 *(parse = son(parse)) = '\0'; 332 333 working->flags = 0; 334 while (q && *q && !isspace(*q)) { 335 if ((*q == 'Z') || (*q == 'z')) 336 working->flags |= CE_COMPACT; 337 else if ((*q == 'B') || (*q == 'b')) 338 working->flags |= CE_BINARY; 339 else 340 errx(1, "Illegal flag in config file -- %c", *q); 341 q++; 342 } 343 344 q = parse = sob(++parse); /* Optional field */ 345 *(parse = son(parse)) = '\0'; 346 347 working->pid_file = NULL; 348 if (q && *q) { 349 if (*q == '/') 350 working->pid_file = strdup(q); 351 else 352 errx(1, "Illegal pid file in config file:\n%s", q); 353 } 354 355 free(errline); 356 } 357 if (working) 358 working->next = (struct conf_entry *) NULL; 359 (void) fclose(f); 360 return(first); 361} 362 363static char *missing_field(p,errline) 364 char *p,*errline; 365{ 366 if (!p || !*p) 367 errx(1, "Missing field in config file:\n%s", errline); 368 return(p); 369} 370 371static void dotrim(log,pid_file,numdays,flags,perm,owner_uid,group_gid) 372 char *log; 373 char *pid_file; 374 int numdays; 375 int flags; 376 int perm; 377 int owner_uid; 378 int group_gid; 379{ 380 char file1 [MAXPATHLEN+1], file2 [MAXPATHLEN+1]; 381 char zfile1[MAXPATHLEN+1], zfile2[MAXPATHLEN+1]; 382 int notified, need_notification, fd, _numdays; 383 struct stat st; 384 pid_t pid; 385 386#ifdef _IBMR2 387/* AIX 3.1 has a broken fchown- if the owner_uid is -1, it will actually */ 388/* change it to be owned by uid -1, instead of leaving it as is, as it is */ 389/* supposed to. */ 390 if (owner_uid == -1) 391 owner_uid = geteuid(); 392#endif 393 394 /* Remove oldest log */ 395 (void) sprintf(file1,"%s.%d",log,numdays); 396 (void) strcpy(zfile1, file1); 397 (void) strcat(zfile1, COMPRESS_POSTFIX); 398 399 if (noaction) { 400 printf("rm -f %s\n", file1); 401 printf("rm -f %s\n", zfile1); 402 } else { 403 (void) unlink(file1); 404 (void) unlink(zfile1); 405 } 406 407 /* Move down log files */ 408 _numdays = numdays; /* preserve */ 409 while (numdays--) { 410 (void) strcpy(file2,file1); 411 (void) sprintf(file1,"%s.%d",log,numdays); 412 (void) strcpy(zfile1, file1); 413 (void) strcpy(zfile2, file2); 414 if (lstat(file1, &st)) { 415 (void) strcat(zfile1, COMPRESS_POSTFIX); 416 (void) strcat(zfile2, COMPRESS_POSTFIX); 417 if (lstat(zfile1, &st)) continue; 418 } 419 if (noaction) { 420 printf("mv %s %s\n",zfile1,zfile2); 421 printf("chmod %o %s\n", perm, zfile2); 422 printf("chown %d.%d %s\n", 423 owner_uid, group_gid, zfile2); 424 } else { 425 (void) rename(zfile1, zfile2); 426 (void) chmod(zfile2, perm); 427 (void) chown(zfile2, owner_uid, group_gid); 428 } 429 } 430 if (!noaction && !(flags & CE_BINARY)) 431 (void) log_trim(log); /* Report the trimming to the old log */ 432 433 if (!_numdays) { 434 if (noaction) 435 printf("rm %s\n",log); 436 else 437 (void)unlink(log); 438 } 439 else { 440 if (noaction) 441 printf("mv %s to %s\n",log,file1); 442 else 443 (void)rename(log, file1); 444 } 445 446 if (noaction) 447 printf("Start new log..."); 448 else { 449 fd = creat(log,perm); 450 if (fd < 0) 451 err(1, "can't start new log"); 452 if (fchown(fd, owner_uid, group_gid)) 453 err(1, "can't chmod new log file"); 454 (void) close(fd); 455 if (!(flags & CE_BINARY)) 456 if (log_trim(log)) /* Add status message */ 457 err(1, "can't add status message to log"); 458 } 459 if (noaction) 460 printf("chmod %o %s...",perm,log); 461 else 462 (void) chmod(log,perm); 463 464 pid = 0; 465 need_notification = notified = 0; 466 if (pid_file != NULL) { 467 need_notification = 1; 468 pid = get_pid(pid_file); 469 } else if (!(flags & CE_BINARY)) { 470 need_notification = 1; 471 pid = syslog_pid; 472 } 473 474 if (pid) { 475 if (noaction) { 476 notified = 1; 477 printf("kill -HUP %d\n", (int)pid); 478 } else if (kill(pid,SIGHUP)) 479 warn("can't notify daemon, pid %d", (int)pid); 480 else { 481 notified = 1; 482 if (verbose) 483 printf("daemon pid %d notified\n", (int)pid); 484 } 485 } 486 487 if ((flags & CE_COMPACT)) { 488 if (need_notification && !notified) 489 warnx("log not compressed because daemon not notified"); 490 else if (noaction) 491 printf("Compress %s.0\n",log); 492 else { 493 if (notified) { 494 if (verbose) 495 printf("small pause to allow daemon to close log\n"); 496 sleep(3); 497 } 498 compress_log(log); 499 } 500 } 501} 502 503/* Log the fact that the logs were turned over */ 504static int log_trim(log) 505 char *log; 506{ 507 FILE *f; 508 if ((f = fopen(log,"a")) == NULL) 509 return(-1); 510 fprintf(f,"%s %s newsyslog[%d]: logfile turned over\n", 511 daytime, hostname, (int)getpid()); 512 if (fclose(f) == EOF) 513 err(1, "log_trim: fclose:"); 514 return(0); 515} 516 517/* Fork of /usr/ucb/compress to compress the old log file */ 518static void compress_log(log) 519 char *log; 520{ 521 pid_t pid; 522 char tmp[MAXPATHLEN+1]; 523 524 (void) sprintf(tmp,"%s.0",log); 525 pid = fork(); 526 if (pid < 0) 527 err(1, "fork"); 528 else if (!pid) { 529 (void) execl(COMPRESS_PATH,COMPRESS_PROG,"-f",tmp,0); 530 err(1, COMPRESS_PATH); 531 } 532} 533 534/* Return size in kilobytes of a file */ 535static int sizefile(file) 536 char *file; 537{ 538 struct stat sb; 539 540 if (stat(file,&sb) < 0) 541 return(-1); 542 return(kbytes(dbtob(sb.st_blocks))); 543} 544 545/* Return the age of old log file (file.0) */ 546static int age_old_log(file) 547 char *file; 548{ 549 struct stat sb; 550 char tmp[MAXPATHLEN+sizeof(".0")+sizeof(COMPRESS_POSTFIX)+1]; 551 552 (void) strcpy(tmp,file); 553 if (stat(strcat(tmp,".0"),&sb) < 0) 554 if (stat(strcat(tmp,COMPRESS_POSTFIX), &sb) < 0) 555 return(-1); 556 return( (int) (timenow - sb.st_mtime + 1800) / 3600); 557} 558 559static pid_t get_pid(pid_file) 560 char *pid_file; 561{ 562 FILE *f; 563 char line[BUFSIZ]; 564 pid_t pid = 0; 565 566 if ((f = fopen(pid_file,"r")) == NULL) 567 warn("can't open %s pid file to restart a daemon", 568 pid_file); 569 else { 570 if (fgets(line,BUFSIZ,f)) { 571 pid = atol(line); 572 if (pid < MIN_PID || pid > MAX_PID) { 573 warnx("preposterous process number: %d", (int)pid); 574 pid = 0; 575 } 576 } else 577 warn("can't read %s pid file to restart a daemon", 578 pid_file); 579 (void)fclose(f); 580 } 581 return pid; 582} 583 584#ifndef OSF 585/* Duplicate a string using malloc */ 586 587char *strdup(strp) 588register char *strp; 589{ 590 register char *cp; 591 592 if ((cp = malloc((unsigned) strlen(strp) + 1)) == NULL) 593 abort(); 594 return(strcpy (cp, strp)); 595} 596#endif 597 598/* Skip Over Blanks */ 599char *sob(p) 600 register char *p; 601{ 602 while (p && *p && isspace(*p)) 603 p++; 604 return(p); 605} 606 607/* Skip Over Non-Blanks */ 608char *son(p) 609 register char *p; 610{ 611 while (p && *p && !isspace(*p)) 612 p++; 613 return(p); 614} 615