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