Subject: Accounting daemon (#413 2 of 2) Index: sys/kern_acct.c,usr.sbin/accton,usr.sbin/sa 2.11BSD Description: 1) In kern_acct.c there has been, for many years, the comment: * * SHOULD REPLACE THIS WITH A DRIVER THAT CAN BE READ TO SIMPLIFY. * 2) malloc(3) would corrupt the arena when passed 0 as the requested size. This would cause the program to coredump later in a non- obvious way. Repeat-By: Observation ;-) Fix: Make sure you have both parts (#412 and 413) before proceeding with the patching. The first part contains the instructions (which are not repeated in the this the 2nd part) and a shar file. This is the 2nd part (#413) and contains a shar file of the new program which is being added to the system ('acctd'): Follow the instructions in the first part for applying this part of the update. As always this and previous updates to 2.11BSD are available via anonymous FTP to either FTP.IIPO.GTEGSC.COM or MOE.2BSD.COM in the directory /pub/2.11BSD. ---------------------------cut here-------------------------- #! /bin/sh # This is a shell archive, meaning: # 1. Remove everything above the #! /bin/sh line. # 2. Save the resulting text in a file. # 3. Execute the file with /bin/sh (not csh) to create: # /usr/src/libexec/acctd # /usr/src/usr.sbin/accton/accton.8 # This archive created: Tue Apr 27 21:41:22 1999 export PATH; PATH=/bin:/usr/bin:$PATH if test ! -d '/usr/src/libexec/acctd' then mkdir '/usr/src/libexec/acctd' fi cd '/usr/src/libexec/acctd' if test -f 'acctd.c' then echo shar: "will not over-write existing file 'acctd.c'" else sed 's/^X//' << \SHAR_EOF > 'acctd.c' X/* X * Steven Schultz - sms@moe.2bsd.com X * X * @(#)acctd.c 1.0 (2.11BSD) 1999/2/10 X * X * acctd - process accounting daemon X*/ X X#include X#include X#include X#include X#include X#include X#include X#include X#include X#include X#include X#include X#include X#include X#include X X struct ACCTPARM X { X int suspend; X int resume; X int chkfreq; X char *acctfile; /* malloc'd */ X }; X X int Suspend = 2; /* %free when accounting suspended */ X int Resume = 4; /* %free when accounting to be resumed */ X int Chkfreq = 30; /* how often (seconds) to check disk space */ X int Debug; X int Disabled; X char *Acctfile; X int Alogfd; X struct ACCTPARM Acctparms; X FILE *Acctfp; X char *Acctdcf = _PATH_ACCTDCF; X int checkacctspace(), hupcatch(), terminate(); X void usage(), errline(); X void die(), reportit(); X Xextern char *__progname; X Xmain(argc, argv) X int argc; X char **argv; X { X int c, i; X pid_t pid; X register FILE *fp; X struct ACCTPARM ajunk; X sigset_t smask; X struct sigaction sa; X X if (getuid()) X die("%s", "Only root can run this program"); X X opterr = 0; X while ((c = getopt(argc, argv, "d")) != EOF) X { X switch (c) X { X case 'd': X Debug++; X break; X case '?': X default: X usage(); X /* NOTREACHED */ X } X } X argc -= optind; X argv += optind; X if (argc != 0) X { X usage(); X /* NOTREACHED */ X } X/* X * Catch the signals of interest and ignore the ones that could get generated X * from the keyboard. If additional signals are caught remember to add them X * to the masks of the other signals! X*/ X daemon(0,0); X X sigemptyset(&smask); X sigaddset(&smask, SIGTERM); X sigaddset(&smask, SIGHUP); X sa.sa_handler = checkacctspace; X sa.sa_mask = smask; X sa.sa_flags = SA_RESTART; X sigaction(SIGALRM, &sa, NULL); X X sigemptyset(&smask); X sigaddset(&smask, SIGALRM); X sigaddset(&smask, SIGHUP); X sa.sa_handler = terminate; X sa.sa_mask = smask; X sa.sa_flags = SA_RESTART; X sigaction(SIGTERM, &sa, NULL); X X sigemptyset(&smask); X sigaddset(&smask, SIGALRM); X sigaddset(&smask, SIGTERM); X sa.sa_handler = hupcatch; X sa.sa_mask = smask; X sa.sa_flags = SA_RESTART; X sigaction(SIGHUP, &sa, NULL); X X signal(SIGQUIT, SIG_IGN); X signal(SIGTSTP, SIG_IGN); X signal(SIGINT, SIG_IGN); X X if (parseconf(&ajunk) < 0) X die("%s owner/mode/reading/parsing error", Acctdcf); X reconfig(&ajunk); X/* X * The conf file has been opened, parsed/validated and output file created. X * It's time to open the accounting log device. X * X * The open is retried a few times (using usleep which does not involve X * signals or alarms) because the previous 'acctd' may be in its SIGTERM X * handling - see the comments in terminate(). Could try longer perhaps. X*/ X for (i = 0; i < 4; i++) X { X Alogfd = open(_PATH_DEVALOG, O_RDONLY); X if (Alogfd > 0) X break; X usleep(1100000L); X } X if (Alogfd < 0) X die("open(%s) errno: %d", _PATH_DEVALOG, errno); X/* X * Save our pid for 'accton' to use X*/ X fp = fopen(_PATH_ACCTDPID, "w"); X pid = getpid(); X if (!fp) X die("fopen(%s,w) error %d\n", _PATH_ACCTDPID, errno); X fprintf(fp, "%d\n", pid); X fclose(fp); X X/* X * Raise our priority slightly. The kernel can buffer quite a bit but X * if the system gets real busy we might be starved for cpu time and lose X * accounting events. We do not run often or for long so this won't impact X * the system too much. X*/ X setpriority(PRIO_PROCESS, pid, -1); X doit(); X /* NOTREACHED */ X } X X/* X * The central loop is here. Try to read 4 accounting records at a time X * to cut the overhead down some. X*/ X Xdoit() X { X struct acct abuf[4]; X struct ACCTPARM ajunk; X sigset_t smask, omask; X int len; X X while (1) X { X/* X * Should a check for 'n' being a multiple of 'sizeof struct acct' be made? X * No. The kernel's operations are atomic and we're using SA_RESTART, either X * we get all that we asked for or we stay suspended. X*/ X len = read(Alogfd, abuf, sizeof (abuf)); X if (len < 0) X { X/* X * Shouldn't happen. If it does then it's best to log the error and die X * rather than go into an endless loop of retrying the read. Since SA_RESTART X * is used on the signals we will not see EINTR. X*/ X die("doit read(%d,...): %d\n", Alogfd, errno); X } X/* X * If accounting has not been disabled and an accounting file is open X * write the data out. Probably should save the current position and X * truncate the file if the write fails. Hold off signals so things don't X * change while writing (this makes it safe for the signal handlers to do X * more than just set a flag). X*/ X sigemptyset(&smask); X sigaddset(&smask, SIGHUP); X sigaddset(&smask, SIGTERM); X sigaddset(&smask, SIGALRM); X if (sigprocmask(SIG_BLOCK, &smask, &omask) < 0) X die("doit() sigprocmask(BLOCK) errno=%d\n", errno); X if (!Disabled) X fwrite(abuf, len, 1, Acctfp); X sigprocmask(SIG_SETMASK, &omask, NULL); X } X } X Xcheckacctspace() X { X struct statfs fsb; X float suspendfree, totalfree, resumefree; X X if (fstatfs(fileno(Acctfp), &fsb) < 0) X die("checkacctspace(%d) errno: %d\n", fileno(Acctfp), errno); X totalfree = (float)fsb.f_bfree; X suspendfree = ((float)fsb.f_blocks * (float)Acctparms.suspend) / 100.0; X X if (totalfree <= suspendfree) X { X if (!Disabled) X reportit("less than %d%% freespace on %s, accounting suspended\n", Acctparms.suspend, fsb.f_mntfromname); X Disabled = 1; X return(0); X } X/* X * If accounting is not disabled then just return. If it has been disabled X * check if enough space is free to resume accounting. X*/ X if (!Disabled) X return(0); X X resumefree = ((float)fsb.f_blocks * (float)Acctparms.resume) / 100.0; X if (totalfree >= resumefree) X { X reportit("more than %d%% freespace on %s, accounting resumed\n", X Acctparms.resume, fsb.f_mntfromname); X Disabled = 0; X } X return(0); X } X X/* X * When a SIGHUP is received parse the config file. It is safe to do this X * in the signal handler because other signals are blocked. X*/ X Xhupcatch() X { X struct ACCTPARM ajunk; X X/* X * What to do if the config file is banged up or has wrong mode/owner...? X * Safest thing to do is log a message and exit rather than continue with X * old information or trust corrupted new information. X*/ X if (parseconf(&ajunk) < 0) X die("%s owner/mode/reading/parsing error", Acctdcf); X reconfig(&ajunk); X } X X/* X * init(8) used to turn off accounting via the old acct(2) syscall when X * the system went into single user mode on a shutdown. Since 'acctd' is X * just another user process as far as init(8) is concerned we receive a X * SIGTERM when the system is being shutdown. In order to capture as much X * data as possible we delay exiting for a few seconds (can't be too long X * because init(8) will SIGKILL 'hung' processes). X * X * Mark the accounting device nonblocking and read data until either X * nothing is available or we've gone thru the maximum delay. The same X * assumption is made here as in doit() - that the reads are atomic, we X * either get all that we asked for or nothing. X*/ Xterminate() X { X register int i, cnt; X struct acct a; X X if (fcntl(Alogfd, F_SETFL, O_NONBLOCK) < 0) X reportit("fcntl(%d): %d\n", Alogfd, errno); X for (i = 0; Acctfp && i < 3; i++) X { X while ((cnt = read(Alogfd, &a, sizeof (a)) > 0)) X fwrite(&a, sizeof (a), 1, Acctfp); X usleep(1000000L); X } X if (Acctfp) X fclose(Acctfp); X close(Alogfd); X exit(0); X } X X/* X * Parse the conf file. The parse is _extremely_ simple minded because X * only 'accton' should be writing the file. If manual editing is done X * be very careful not to add extra whitespace (or comments). Sanity/range X * checking of the arguments is performed here. X*/ Xparseconf(ap) X register struct ACCTPARM *ap; X { X int err = 0, count; X register FILE *fp; X char line[256], *cp; X long l; X struct stat st; X X/* X * The conf file must be owned by root and not writeable by group or other. X * This is because the conf file contains a pathname that will be trusted X * by this program and it is running as root. X*/ X fp = fopen(Acctdcf, "r"); X if (!fp) X return(-1); X if (fstat(fileno(fp), &st) == 0) X { X if ((st.st_uid != 0) || (st.st_mode & (S_IWGRP|S_IWOTH))) X { X fclose(fp); X return(-1); X } X } X bzero(ap, sizeof (*ap)); X for (count = 1; fgets(line, sizeof (line), fp) && !err; count++) X { X cp = index(line, '\n'); X if (cp) X *cp = '\0'; X if (bcmp(line, "suspend=", 8) == 0) X { X l = strtol(line + 8, &cp, 10); X if (l < 0 || l > 99 || (cp && *cp)) X { X errline(count); X err = -1; X } X ap->suspend = (int)l; X } X else if (bcmp(line, "resume=", 7) == 0) X { X l = strtol(line + 7, &cp, 10); X if (l < 0 || l > 99 || (cp && *cp)) X { X errline(count); X err = -1; X } X ap->resume = (int)l; X } X else if (bcmp(line, "chkfreq=", 8) == 0) X { X l = strtol(line + 8, &cp, 10); X/* X * Doesn't make sense to check more often than every 10 seconds. Put a X * upper bound of an hour. X*/ X if (l < 10 || l > 3600 || (cp && *cp)) X { X errline(count); X err = -1; X } X ap->chkfreq = (int)l; X } X else if (bcmp(line, "acctfile=", 9) == 0) X { X cp = line + 9; X if (ap->acctfile) X free(ap->acctfile); X ap->acctfile = strdup(cp); X } X else X/* X * An unknown string could be the sign of a corrupted file. Declare an error X * so we don't trust potential garbage. X*/ X { X errline(count); X err = -1; X } X } X fclose(fp); X if (err) X { X if (ap->acctfile) X { X free(ap->acctfile); X ap->acctfile = NULL; X } X return(err); X } X/* X * Now see which fields were not filled in and apply the defaults. The X * 'accton' program does this but if the conf file was manually edited some X * fields may have been left out. Basic range checking has already been done X * if the fields were present. X*/ X if (ap->suspend == 0) X ap->suspend = Suspend; X if (ap->resume == 0) X ap->resume = Resume; X if (ap->chkfreq == 0) X ap->chkfreq = Chkfreq; X if (ap->acctfile == NULL) X ap->acctfile = strdup(_PATH_ACCTFILE); X return(0); X } X Xvoid Xerrline(l) X { X reportit("error in line %d of %s\n", l, Acctdcf); X } X X/* X * This routine completes the reconfiguration of the accounting daemon. The X * parsing and validation has been performed by parseconf() and the results X * stored in a structure (a pointer to which is passed to this routine). X*/ X Xreconfig(new) X struct ACCTPARM *new; X { X struct itimerval itmr; X int fd; X X if (Acctfp) X fclose(Acctfp); X if (Acctparms.acctfile) X free(Acctparms.acctfile); X Acctparms = *new; X X fd = open(Acctparms.acctfile, O_WRONLY | O_APPEND, 644); X if (fd < 0) X die("open(%s,O_WRONLY|O_APPEND): %d\n", Acctparms.acctfile, X errno); X Acctfp = fdopen(fd, "a"); X if (!Acctfp) X die("fdopen(%d,a): %d\n", fd, errno); X itmr.it_interval.tv_sec = Acctparms.chkfreq; X itmr.it_interval.tv_usec = 0; X itmr.it_value.tv_sec = Acctparms.chkfreq; X itmr.it_value.tv_usec = 0; X if (setitimer(ITIMER_REAL, &itmr, NULL) < 0) X die("setitmer: %d\n", errno); X } X X/* X * The logfile is opened/closed per message to conserve resources X * (file table and descriptor). In the case of die() this isn't terribly X * important since we're about to exit anyhow ;) For reportit() the X * messages are of such low frequency that an extra openlog/closelog X * pair isn't too much extra overhead. X*/ X Xvoid Xdie(str, va_alist) X char *str; X va_dcl X { X va_list ap; X X openlog("acctd", LOG_CONS|LOG_NDELAY|LOG_PID, LOG_DAEMON); X va_start(ap); X vsyslog(LOG_ERR, str, ap); X va_end(ap); X exit(1); X } X Xvoid Xreportit(str, va_alist) X char *str; X va_dcl X { X va_list ap; X X openlog("acctd", LOG_CONS|LOG_NDELAY|LOG_PID, LOG_DAEMON); X va_start(ap); X vsyslog(LOG_WARNING, str, ap); X va_end(ap); X } X Xvoid Xusage() X { X X die("Usage: %s [-f acctfile] [-s %suspend] [-r %resume] [-t chkfreq] [acctfile]", __progname); X /* NOTREACHED */ X } SHAR_EOF fi if test -f 'Makefile' then echo shar: "will not over-write existing file 'Makefile'" else sed 's/^X//' << \SHAR_EOF > 'Makefile' X# X# Public Domain. 1999/2/19 - Steven Schultz X# X# @(#)Makefile 1.2 (2.11BSD) 1999/2/19 X# XCFLAGS= -O XSEPFLAG= -i XSRCS= acctd.c XOBJS= acctd.o XMAN= acctd.0 XMANSRC= acctd.8 X Xall: acctd acctd.0 X Xacctd: ${OBJS} X ${CC} ${CFLAGS} ${SEPFLAG} -o $@ ${OBJS} X Xacctd.0: ${MANSRC} X /usr/man/manroff ${MANSRC} > ${MAN} X Xclean: X rm -f ${OBJS} acctd tags ${MAN} X Xdepend: ${SRCS} X mkdep ${CFLAGS} ${SRCS} X Xinstall: acctd X install -s -o root -g bin -m 700 acctd ${DESTDIR}/usr/libexec/acctd X install -c -o bin -g bin -m 444 ${MAN} ${DESTDIR}/usr/man/cat8/${MAN} X Xlint: ${SRCS} X lint -hax ${SRCS} X Xtags: ${SRCS} X ctags ${SRCS} X# DO NOT DELETE THIS LINE -- mkdep uses it. X# DO NOT PUT ANYTHING AFTER THIS LINE, IT WILL GO AWAY. SHAR_EOF fi if test -f 'acctd.8' then echo shar: "will not over-write existing file 'acctd.8'" else sed 's/^X//' << \SHAR_EOF > 'acctd.8' X.\" X.\" @(#) 2.11BSD acctd.8 1.0 1999/2/19 X.\" X.TH ACCTD 8 "February 19, 1999" X.UC 4 X.SH NAME Xacctd \- system accounting daemon X.SH SYNOPSIS X.B acctd [\fB\-d\fP] X.SH DESCRIPTION X.B Acctd Xreads accounting records from the kernel's accounting driver (/dev/acctlog) Xand writes the records to a file for later analysis. The X.BR accton (8) Xprogram is used to update (or create) the config file (/etc/acctd.cf). X.PP X.BR Acctd (8) Xreads the config file upon startup and reciept of a SIGHUP signal. XIf a SIGTERM signal is received the accounting file is closed and the Xdaemon exits. XThe one option is: X.TP 10 X\-f XEnable debugging mode. Currently no debugging logic is present. X.SH FILES X.TP 20 X/etc/acctd.cf XThe configuration file. Must be owned by root and writeable only by root. XThis file, while simple text, is not meant for human editing since the Xparse is simpleminded and the daemon is paranoid. X.SH SEE ALSO Xacct(5), Xaccton(8), Xsa(8) X.SH HISTORY X.B Acctd Xfirst appeared in 2.11BSD SHAR_EOF fi cd .. if test -f '/usr/src/usr.sbin/accton/accton.8' then echo shar: "will not over-write existing file '/usr/src/usr.sbin/accton/accton.8'" else sed 's/^X//' << \SHAR_EOF > '/usr/src/usr.sbin/accton/accton.8' X.\" X.\" @(#) 2.11BSD accton.8 1.0 1999/2/19 X.\" X.TH ACCTON 8 "February 19, 1999" X.UC 4 X.SH NAME Xaccton \- enable/disable system accounting X.SH SYNOPSIS X.B accton X[\fB\-f file\fP] X[\fB\-r resum\fP] X[\fB\-s suspend\fP] X[\fB\-t freq\fP] X[\fBfilename\fP] X.SH DESCRIPTION XWith no argument, X.B accton Xwill disable system accounting. If the \fB\-f\fP option is Xgiven or the last argument is an existing pathname Xaccounting is enabled and for every process which terminates Xunder normal conditions an accounting record is sent to the accounting Xdaemon. X.PP X.B accton Xis a frontend to the accounting daemon: X.BR acctd (8). XAccounting is turned off by sending a SIGTERM to the running accounting daemon. XChanges in configuration (free space thresholds, etc) are made by writing X/etc/acctd.cf and issuing a SIGHUP to the accounting daemon. If the daemon Xis not running it is started. X.PP XThe options are: X.TP 10 X\-f file XSpecifies the name of an existing file to which accounting records Xare to be appended. If this option and a trailing filename (the Xhistorical form of use) is given then the last filename given is used. X.TP 10 X\-r resum XPercentage of diskspace that must be free in order for accounting to Xbe resumed. The default is 4%. X.TP 10 X\-s susp XIf the percentage of free diskspace falls below \fBsusp\fP accounting Xis suspended. The default is 2%. X.TP 10 X\-t freq XHow often (in seconds) to check the free diskpace. Default is 30. X.SH FILES X.TP 20 X/usr/libexec/acctd XThe accounting daemon that reads /dev/acct X.TP 20 X/etc/acctd.cf XThe configuration file. Must be owned by root and writeable only by root. XThis file, while simple text, is not meant for human editing since the Xparse is simpleminded and the daemon is paranoid. X.SH SEE ALSO Xacct(5), Xacctd(8), Xsa(8) X.SH HISTORY XA X.B accton Xcommand appeared in Version 7 AT&T UNIX. SHAR_EOF fi exit 0 # End of shell archive