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