1: /* 2: * Steven Schultz - sms@moe.2bsd.com 3: * 4: * @(#)acctd.c 1.0 (2.11BSD) 1999/2/10 5: * 6: * acctd - process accounting daemon 7: */ 8: 9: #include <signal.h> 10: #include <stdio.h> 11: #include <errno.h> 12: #include <fcntl.h> 13: #include <stdlib.h> 14: #include <string.h> 15: #include <syslog.h> 16: #include <varargs.h> 17: #include <sys/types.h> 18: #include <sys/param.h> 19: #include <sys/acct.h> 20: #include <sys/mount.h> 21: #include <sys/stat.h> 22: #include <sys/time.h> 23: #include <sys/resource.h> 24: 25: struct ACCTPARM 26: { 27: int suspend; 28: int resume; 29: int chkfreq; 30: char *acctfile; /* malloc'd */ 31: }; 32: 33: int Suspend = 2; /* %free when accounting suspended */ 34: int Resume = 4; /* %free when accounting to be resumed */ 35: int Chkfreq = 30; /* how often (seconds) to check disk space */ 36: int Debug; 37: int Disabled; 38: char *Acctfile; 39: int Alogfd; 40: struct ACCTPARM Acctparms; 41: FILE *Acctfp; 42: char *Acctdcf = _PATH_ACCTDCF; 43: int checkacctspace(), hupcatch(), terminate(); 44: void usage(), errline(); 45: void die(), reportit(); 46: 47: extern char *__progname; 48: 49: main(argc, argv) 50: int argc; 51: char **argv; 52: { 53: int c, i; 54: pid_t pid; 55: register FILE *fp; 56: struct ACCTPARM ajunk; 57: sigset_t smask; 58: struct sigaction sa; 59: 60: if (getuid()) 61: die("%s", "Only root can run this program"); 62: 63: opterr = 0; 64: while ((c = getopt(argc, argv, "d")) != EOF) 65: { 66: switch (c) 67: { 68: case 'd': 69: Debug++; 70: break; 71: case '?': 72: default: 73: usage(); 74: /* NOTREACHED */ 75: } 76: } 77: argc -= optind; 78: argv += optind; 79: if (argc != 0) 80: { 81: usage(); 82: /* NOTREACHED */ 83: } 84: /* 85: * Catch the signals of interest and ignore the ones that could get generated 86: * from the keyboard. If additional signals are caught remember to add them 87: * to the masks of the other signals! 88: */ 89: daemon(0,0); 90: 91: sigemptyset(&smask); 92: sigaddset(&smask, SIGTERM); 93: sigaddset(&smask, SIGHUP); 94: sa.sa_handler = checkacctspace; 95: sa.sa_mask = smask; 96: sa.sa_flags = SA_RESTART; 97: sigaction(SIGALRM, &sa, NULL); 98: 99: sigemptyset(&smask); 100: sigaddset(&smask, SIGALRM); 101: sigaddset(&smask, SIGHUP); 102: sa.sa_handler = terminate; 103: sa.sa_mask = smask; 104: sa.sa_flags = SA_RESTART; 105: sigaction(SIGTERM, &sa, NULL); 106: 107: sigemptyset(&smask); 108: sigaddset(&smask, SIGALRM); 109: sigaddset(&smask, SIGTERM); 110: sa.sa_handler = hupcatch; 111: sa.sa_mask = smask; 112: sa.sa_flags = SA_RESTART; 113: sigaction(SIGHUP, &sa, NULL); 114: 115: signal(SIGQUIT, SIG_IGN); 116: signal(SIGTSTP, SIG_IGN); 117: signal(SIGINT, SIG_IGN); 118: 119: if (parseconf(&ajunk) < 0) 120: die("%s owner/mode/reading/parsing error", Acctdcf); 121: reconfig(&ajunk); 122: /* 123: * The conf file has been opened, parsed/validated and output file created. 124: * It's time to open the accounting log device. 125: * 126: * The open is retried a few times (using usleep which does not involve 127: * signals or alarms) because the previous 'acctd' may be in its SIGTERM 128: * handling - see the comments in terminate(). Could try longer perhaps. 129: */ 130: for (i = 0; i < 4; i++) 131: { 132: Alogfd = open(_PATH_DEVALOG, O_RDONLY); 133: if (Alogfd > 0) 134: break; 135: usleep(1100000L); 136: } 137: if (Alogfd < 0) 138: die("open(%s) errno: %d", _PATH_DEVALOG, errno); 139: /* 140: * Save our pid for 'accton' to use 141: */ 142: fp = fopen(_PATH_ACCTDPID, "w"); 143: pid = getpid(); 144: if (!fp) 145: die("fopen(%s,w) error %d\n", _PATH_ACCTDPID, errno); 146: fprintf(fp, "%d\n", pid); 147: fclose(fp); 148: 149: /* 150: * Raise our priority slightly. The kernel can buffer quite a bit but 151: * if the system gets real busy we might be starved for cpu time and lose 152: * accounting events. We do not run often or for long so this won't impact 153: * the system too much. 154: */ 155: setpriority(PRIO_PROCESS, pid, -1); 156: doit(); 157: /* NOTREACHED */ 158: } 159: 160: /* 161: * The central loop is here. Try to read 4 accounting records at a time 162: * to cut the overhead down some. 163: */ 164: 165: doit() 166: { 167: struct acct abuf[4]; 168: struct ACCTPARM ajunk; 169: sigset_t smask, omask; 170: int len; 171: 172: while (1) 173: { 174: /* 175: * Should a check for 'n' being a multiple of 'sizeof struct acct' be made? 176: * No. The kernel's operations are atomic and we're using SA_RESTART, either 177: * we get all that we asked for or we stay suspended. 178: */ 179: len = read(Alogfd, abuf, sizeof (abuf)); 180: if (len < 0) 181: { 182: /* 183: * Shouldn't happen. If it does then it's best to log the error and die 184: * rather than go into an endless loop of retrying the read. Since SA_RESTART 185: * is used on the signals we will not see EINTR. 186: */ 187: die("doit read(%d,...): %d\n", Alogfd, errno); 188: } 189: /* 190: * If accounting has not been disabled and an accounting file is open 191: * write the data out. Probably should save the current position and 192: * truncate the file if the write fails. Hold off signals so things don't 193: * change while writing (this makes it safe for the signal handlers to do 194: * more than just set a flag). 195: */ 196: sigemptyset(&smask); 197: sigaddset(&smask, SIGHUP); 198: sigaddset(&smask, SIGTERM); 199: sigaddset(&smask, SIGALRM); 200: if (sigprocmask(SIG_BLOCK, &smask, &omask) < 0) 201: die("doit() sigprocmask(BLOCK) errno=%d\n", errno); 202: if (!Disabled) 203: fwrite(abuf, len, 1, Acctfp); 204: sigprocmask(SIG_SETMASK, &omask, NULL); 205: } 206: } 207: 208: checkacctspace() 209: { 210: struct statfs fsb; 211: float suspendfree, totalfree, resumefree; 212: 213: if (fstatfs(fileno(Acctfp), &fsb) < 0) 214: die("checkacctspace(%d) errno: %d\n", fileno(Acctfp), errno); 215: totalfree = (float)fsb.f_bfree; 216: suspendfree = ((float)fsb.f_blocks * (float)Acctparms.suspend) / 100.0; 217: 218: if (totalfree <= suspendfree) 219: { 220: if (!Disabled) 221: reportit("less than %d%% freespace on %s, accounting suspended\n", Acctparms.suspend, fsb.f_mntfromname); 222: Disabled = 1; 223: return(0); 224: } 225: /* 226: * If accounting is not disabled then just return. If it has been disabled 227: * check if enough space is free to resume accounting. 228: */ 229: if (!Disabled) 230: return(0); 231: 232: resumefree = ((float)fsb.f_blocks * (float)Acctparms.resume) / 100.0; 233: if (totalfree >= resumefree) 234: { 235: reportit("more than %d%% freespace on %s, accounting resumed\n", 236: Acctparms.resume, fsb.f_mntfromname); 237: Disabled = 0; 238: } 239: return(0); 240: } 241: 242: /* 243: * When a SIGHUP is received parse the config file. It is safe to do this 244: * in the signal handler because other signals are blocked. 245: */ 246: 247: hupcatch() 248: { 249: struct ACCTPARM ajunk; 250: 251: /* 252: * What to do if the config file is banged up or has wrong mode/owner...? 253: * Safest thing to do is log a message and exit rather than continue with 254: * old information or trust corrupted new information. 255: */ 256: if (parseconf(&ajunk) < 0) 257: die("%s owner/mode/reading/parsing error", Acctdcf); 258: reconfig(&ajunk); 259: } 260: 261: /* 262: * init(8) used to turn off accounting via the old acct(2) syscall when 263: * the system went into single user mode on a shutdown. Since 'acctd' is 264: * just another user process as far as init(8) is concerned we receive a 265: * SIGTERM when the system is being shutdown. In order to capture as much 266: * data as possible we delay exiting for a few seconds (can't be too long 267: * because init(8) will SIGKILL 'hung' processes). 268: * 269: * Mark the accounting device nonblocking and read data until either 270: * nothing is available or we've gone thru the maximum delay. The same 271: * assumption is made here as in doit() - that the reads are atomic, we 272: * either get all that we asked for or nothing. 273: */ 274: terminate() 275: { 276: register int i, cnt; 277: struct acct a; 278: 279: if (fcntl(Alogfd, F_SETFL, O_NONBLOCK) < 0) 280: reportit("fcntl(%d): %d\n", Alogfd, errno); 281: for (i = 0; Acctfp && i < 3; i++) 282: { 283: while ((cnt = read(Alogfd, &a, sizeof (a)) > 0)) 284: fwrite(&a, sizeof (a), 1, Acctfp); 285: usleep(1000000L); 286: } 287: if (Acctfp) 288: fclose(Acctfp); 289: close(Alogfd); 290: exit(0); 291: } 292: 293: /* 294: * Parse the conf file. The parse is _extremely_ simple minded because 295: * only 'accton' should be writing the file. If manual editing is done 296: * be very careful not to add extra whitespace (or comments). Sanity/range 297: * checking of the arguments is performed here. 298: */ 299: parseconf(ap) 300: register struct ACCTPARM *ap; 301: { 302: int err = 0, count; 303: register FILE *fp; 304: char line[256], *cp; 305: long l; 306: struct stat st; 307: 308: /* 309: * The conf file must be owned by root and not writeable by group or other. 310: * This is because the conf file contains a pathname that will be trusted 311: * by this program and it is running as root. 312: */ 313: fp = fopen(Acctdcf, "r"); 314: if (!fp) 315: return(-1); 316: if (fstat(fileno(fp), &st) == 0) 317: { 318: if ((st.st_uid != 0) || (st.st_mode & (S_IWGRP|S_IWOTH))) 319: { 320: fclose(fp); 321: return(-1); 322: } 323: } 324: bzero(ap, sizeof (*ap)); 325: for (count = 1; fgets(line, sizeof (line), fp) && !err; count++) 326: { 327: cp = index(line, '\n'); 328: if (cp) 329: *cp = '\0'; 330: if (bcmp(line, "suspend=", 8) == 0) 331: { 332: l = strtol(line + 8, &cp, 10); 333: if (l < 0 || l > 99 || (cp && *cp)) 334: { 335: errline(count); 336: err = -1; 337: } 338: ap->suspend = (int)l; 339: } 340: else if (bcmp(line, "resume=", 7) == 0) 341: { 342: l = strtol(line + 7, &cp, 10); 343: if (l < 0 || l > 99 || (cp && *cp)) 344: { 345: errline(count); 346: err = -1; 347: } 348: ap->resume = (int)l; 349: } 350: else if (bcmp(line, "chkfreq=", 8) == 0) 351: { 352: l = strtol(line + 8, &cp, 10); 353: /* 354: * Doesn't make sense to check more often than every 10 seconds. Put a 355: * upper bound of an hour. 356: */ 357: if (l < 10 || l > 3600 || (cp && *cp)) 358: { 359: errline(count); 360: err = -1; 361: } 362: ap->chkfreq = (int)l; 363: } 364: else if (bcmp(line, "acctfile=", 9) == 0) 365: { 366: cp = line + 9; 367: if (ap->acctfile) 368: free(ap->acctfile); 369: ap->acctfile = strdup(cp); 370: } 371: else 372: /* 373: * An unknown string could be the sign of a corrupted file. Declare an error 374: * so we don't trust potential garbage. 375: */ 376: { 377: errline(count); 378: err = -1; 379: } 380: } 381: fclose(fp); 382: if (err) 383: { 384: if (ap->acctfile) 385: { 386: free(ap->acctfile); 387: ap->acctfile = NULL; 388: } 389: return(err); 390: } 391: /* 392: * Now see which fields were not filled in and apply the defaults. The 393: * 'accton' program does this but if the conf file was manually edited some 394: * fields may have been left out. Basic range checking has already been done 395: * if the fields were present. 396: */ 397: if (ap->suspend == 0) 398: ap->suspend = Suspend; 399: if (ap->resume == 0) 400: ap->resume = Resume; 401: if (ap->chkfreq == 0) 402: ap->chkfreq = Chkfreq; 403: if (ap->acctfile == NULL) 404: ap->acctfile = strdup(_PATH_ACCTFILE); 405: return(0); 406: } 407: 408: void 409: errline(l) 410: { 411: reportit("error in line %d of %s\n", l, Acctdcf); 412: } 413: 414: /* 415: * This routine completes the reconfiguration of the accounting daemon. The 416: * parsing and validation has been performed by parseconf() and the results 417: * stored in a structure (a pointer to which is passed to this routine). 418: */ 419: 420: reconfig(new) 421: struct ACCTPARM *new; 422: { 423: struct itimerval itmr; 424: int fd; 425: 426: if (Acctfp) 427: fclose(Acctfp); 428: if (Acctparms.acctfile) 429: free(Acctparms.acctfile); 430: Acctparms = *new; 431: 432: fd = open(Acctparms.acctfile, O_WRONLY | O_APPEND, 644); 433: if (fd < 0) 434: die("open(%s,O_WRONLY|O_APPEND): %d\n", Acctparms.acctfile, 435: errno); 436: Acctfp = fdopen(fd, "a"); 437: if (!Acctfp) 438: die("fdopen(%d,a): %d\n", fd, errno); 439: itmr.it_interval.tv_sec = Acctparms.chkfreq; 440: itmr.it_interval.tv_usec = 0; 441: itmr.it_value.tv_sec = Acctparms.chkfreq; 442: itmr.it_value.tv_usec = 0; 443: if (setitimer(ITIMER_REAL, &itmr, NULL) < 0) 444: die("setitmer: %d\n", errno); 445: } 446: 447: /* 448: * The logfile is opened/closed per message to conserve resources 449: * (file table and descriptor). In the case of die() this isn't terribly 450: * important since we're about to exit anyhow ;) For reportit() the 451: * messages are of such low frequency that an extra openlog/closelog 452: * pair isn't too much extra overhead. 453: */ 454: 455: void 456: die(str, va_alist) 457: char *str; 458: va_dcl 459: { 460: va_list ap; 461: 462: openlog("acctd", LOG_CONS|LOG_NDELAY|LOG_PID, LOG_DAEMON); 463: va_start(ap); 464: vsyslog(LOG_ERR, str, ap); 465: va_end(ap); 466: exit(1); 467: } 468: 469: void 470: reportit(str, va_alist) 471: char *str; 472: va_dcl 473: { 474: va_list ap; 475: 476: openlog("acctd", LOG_CONS|LOG_NDELAY|LOG_PID, LOG_DAEMON); 477: va_start(ap); 478: vsyslog(LOG_WARNING, str, ap); 479: va_end(ap); 480: } 481: 482: void 483: usage() 484: { 485: 486: die("Usage: %s [-f acctfile] [-s %suspend] [-r %resume] [-t chkfreq] [acctfile]", __progname); 487: /* NOTREACHED */ 488: }