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:     }

Defined functions

checkacctspace defined in line 208; used 2 times
die defined in line 455; used 13 times
doit defined in line 165; used 1 times
errline defined in line 408; used 5 times
hupcatch defined in line 247; used 2 times
main defined in line 49; never used
parseconf defined in line 299; used 2 times
reconfig defined in line 420; used 2 times
reportit defined in line 469; used 5 times
terminate defined in line 274; used 2 times
usage defined in line 482; used 3 times

Defined variables

Acctdcf defined in line 42; used 4 times
Acctfile defined in line 38; never used
Acctparms defined in line 40; used 11 times
Alogfd defined in line 39; used 9 times
Chkfreq defined in line 35; used 1 times
Debug defined in line 36; used 1 times
  • in line 69
Disabled defined in line 37; used 5 times
Resume defined in line 34; used 1 times
Suspend defined in line 33; used 1 times

Defined struct's

ACCTPARM defined in line 25; used 12 times
Last modified: 1999-03-31
Generated: 2016-12-26
Generated by src2html V0.67
page hit count: 5103
Valid CSS Valid XHTML 1.0 Strict