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