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	<signal.h>
X#include	<stdio.h>
X#include	<errno.h>
X#include	<fcntl.h>
X#include	<stdlib.h>
X#include	<string.h>
X#include	<syslog.h>
X#include	<varargs.h>
X#include	<sys/types.h>
X#include	<sys/param.h>
X#include	<sys/acct.h>
X#include	<sys/mount.h>
X#include	<sys/stat.h>
X#include	<sys/time.h>
X#include	<sys/resource.h>
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
