newsyslog.c revision 25496
174462Salfred/* 274462Salfred * This file contains changes from the Open Software Foundation. 374462Salfred */ 474462Salfred 574462Salfred/* 674462Salfred 774462SalfredCopyright 1988, 1989 by the Massachusetts Institute of Technology 874462Salfred 974462SalfredPermission to use, copy, modify, and distribute this software 1074462Salfredand its documentation for any purpose and without fee is 1174462Salfredhereby granted, provided that the above copyright notice 1274462Salfredappear in all copies and that both that copyright notice and 1374462Salfredthis permission notice appear in supporting documentation, 1474462Salfredand that the names of M.I.T. and the M.I.T. S.I.P.B. not be 1574462Salfredused in advertising or publicity pertaining to distribution 1674462Salfredof the software without specific, written prior permission. 1774462SalfredM.I.T. and the M.I.T. S.I.P.B. make no representations about 1874462Salfredthe suitability of this software for any purpose. It is 1974462Salfredprovided "as is" without express or implied warranty. 2074462Salfred 2174462Salfred*/ 2274462Salfred 2374462Salfred/* 2474462Salfred * newsyslog - roll over selected logs at the appropriate time, 2574462Salfred * keeping the a specified number of backup files around. 2674462Salfred * 2774462Salfred * $Source: /home/ncvs/src/usr.sbin/newsyslog/newsyslog.c,v $ 2874462Salfred * $Author: ache $ 2974462Salfred */ 3074462Salfred 3174462Salfred#ifndef lint 3274462Salfredstatic char rcsid[] = "$Id: newsyslog.c,v 1.11 1997/05/04 01:53:53 ache Exp $"; 3374462Salfred#endif /* not lint */ 3474462Salfred 3574462Salfred#ifndef CONF 3674462Salfred#define CONF "/etc/athena/newsyslog.conf" /* Configuration file */ 3774462Salfred#endif 3874462Salfred#ifndef PIDFILE 3974462Salfred#define PIDFILE "/etc/syslog.pid" 4074462Salfred#endif 4174462Salfred#ifndef COMPRESS_PATH 4274462Salfred#define COMPRESS_PATH "/usr/ucb/compress" /* File compression program */ 4374462Salfred#endif 4474462Salfred#ifndef COMPRESS_PROG 4574462Salfred#define COMPRESS_PROG "compress" 4674462Salfred#endif 4774462Salfred#ifndef COMPRESS_POSTFIX 4874462Salfred#define COMPRESS_POSTFIX ".Z" 4974462Salfred#endif 5074462Salfred 5174462Salfred#include <stdio.h> 5274462Salfred#include <stdlib.h> 5374462Salfred#include <string.h> 5474462Salfred#include <ctype.h> 5574462Salfred#include <signal.h> 5674462Salfred#include <pwd.h> 5774462Salfred#include <grp.h> 5874462Salfred#include <fcntl.h> 5974462Salfred#include <unistd.h> 6074462Salfred#include <err.h> 6174462Salfred#include <sys/types.h> 6274462Salfred#include <sys/time.h> 6374462Salfred#include <sys/stat.h> 6474462Salfred#include <sys/param.h> 6574462Salfred#include <sys/wait.h> 6674462Salfred 6774462Salfred#define kbytes(size) (((size) + 1023) >> 10) 6874462Salfred#ifdef _IBMR2 6974462Salfred/* Calculates (db * DEV_BSIZE) */ 7074462Salfred#define dbtob(db) ((unsigned)(db) << UBSHIFT) 7174462Salfred#endif 7274462Salfred 7374462Salfred#define CE_COMPACT 1 /* Compact the achived log files */ 7474462Salfred#define CE_BINARY 2 /* Logfile is in binary, don't add */ 7574462Salfred /* status messages */ 7674462Salfred#define NONE -1 7774462Salfred 7874462Salfredstruct conf_entry { 7974462Salfred char *log; /* Name of the log */ 8074462Salfred char *pid_file; /* PID file */ 8174462Salfred int uid; /* Owner of log */ 8274462Salfred int gid; /* Group of log */ 8374462Salfred int numlogs; /* Number of logs to keep */ 8474462Salfred int size; /* Size cutoff to trigger trimming the log */ 8574462Salfred int hours; /* Hours between log trimming */ 8674462Salfred int permissions; /* File permissions on the log */ 8774462Salfred int flags; /* Flags (CE_COMPACT & CE_BINARY) */ 8874462Salfred struct conf_entry *next; /* Linked list pointer */ 8974462Salfred}; 90126243Sgreen 9174462Salfredchar *progname; /* contains argv[0] */ 9274462Salfredint verbose = 0; /* Print out what's going on */ 9374462Salfredint needroot = 1; /* Root privs are necessary */ 9474462Salfredint noaction = 0; /* Don't do anything, just show it */ 95126243Sgreenchar *conf = CONF; /* Configuration file to use */ 9674462Salfredtime_t timenow; 9774462Salfredpid_t syslog_pid; /* read in from /etc/syslog.pid */ 9874462Salfred#define MIN_PID 5 9974462Salfred#define MAX_PID 30000 /* was 65534, see /usr/include/sys/proc.h */ 10074462Salfredchar hostname[MAXHOSTNAMELEN+1]; /* hostname */ 10174462Salfredchar *daytime; /* timenow in human readable form */ 10274462Salfred 10374462Salfred#ifndef OSF 10474462Salfredchar *strdup(char *strp); 10574462Salfred#endif 10674462Salfred 107106288Sdfrstatic struct conf_entry *parse_file(); 108106288Sdfrstatic char *sob(char *p); 10974462Salfredstatic char *son(char *p); 11074462Salfredstatic char *missing_field(char *p,char *errline); 11174462Salfredstatic void do_entry(struct conf_entry *ent); 11274462Salfredstatic void PRS(int argc,char **argv); 11374462Salfredstatic void usage(); 11474462Salfredstatic void dotrim(char *log,char *pid_file,int numdays,int falgs,int perm, int owner_uid,int group_gid); 11574462Salfredstatic int log_trim(char *log); 11674462Salfredstatic void compress_log(char *log); 11774462Salfredstatic int sizefile(char *file); 11874462Salfredstatic int age_old_log(char *file); 11974462Salfredstatic pid_t get_pid(char *pid_file); 12074462Salfred 12174462Salfredint main(argc,argv) 12274462Salfred int argc; 12374462Salfred char **argv; 12474462Salfred{ 125126243Sgreen struct conf_entry *p, *q; 12674462Salfred 12774462Salfred PRS(argc,argv); 12874462Salfred 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 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 (needroot && !(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