OpenSolaris_b135/cmd/auditreduce/main.c

Compare this file to the similar file:
Show the results in this format:

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * The Secure SunOS audit reduction tool - auditreduce.
 * Document SM0071 is the primary source of information on auditreduce.
 *
 * Composed of 4 source modules:
 * main.c - main driver.
 * option.c - command line option processing.
 * process.c - record/file/process functions.
 * time.c - date/time handling.
 *
 * Main(), write_header(), audit_stats(), and a_calloc()
 * are the only functions visible outside this module.
 */

#include <siginfo.h>
#include <locale.h>
#include <libintl.h>
#include "auditr.h"
#include "auditrd.h"

#if !defined(TEXT_DOMAIN)
#define	TEXT_DOMAIN "SUNW_OST_OSCMD"
#endif

extern void	derive_str(time_t, char *);
extern int	process_options(int, char **);
extern int	mproc(audit_pcb_t *);
extern void	init_tokens(void);	/* shared with praudit */

static int	a_pow(int, int);
static void	calc_procs(void);
static void	chld_handler(int);
static int	close_outfile(void);
static void	c_close(audit_pcb_t *, int);
static void	delete_infiles(void);
static void	gather_pcb(audit_pcb_t *, int, int);
static void	init_options(void);
static int	init_sig(void);
static void	int_handler(int);
static int	mfork(audit_pcb_t *, int, int, int);
static void	mcount(int, int);
static int	open_outfile(void);
static void	p_close(audit_pcb_t *);
static int	rename_outfile(void);
static void	rm_mem(audit_pcb_t *);
static void	rm_outfile(void);
static void	trim_mem(audit_pcb_t *);
static int	write_file_token(time_t);
static int	write_trailer(void);

/*
 * File globals.
 */
static int	max_sproc;	/* maximum number of subprocesses per process */
static int	total_procs;	/* number of processes in the process tree */
static int	total_layers;	/* number of layers in the process tree */

/*
 * .func main - main.
 * .desc The beginning. Main() calls each of the initialization routines
 *	and then allocates the root pcb. Then it calls mfork() to get
 *	the work done.
 * .call	main(argc, argv).
 * .arg	argc	- number of arguments.
 * .arg	argv	- array of pointers to arguments.
 * .ret	0	- via exit() - no errors detected.
 * .ret	1	- via exit() - errors detected (messages printed).
 */
int
main(int argc, char **argv)
{
	int	ret;
	audit_pcb_t *pcb;

	/* Internationalization */
	(void) setlocale(LC_ALL, "");
	(void) textdomain(TEXT_DOMAIN);

	root_pid = getpid();	/* know who is root process for error */
	init_options();		/* initialize options */
	init_tokens();		/* initialize token processing table */
	if (init_sig())		/* initialize signals */
		exit(1);
	if (process_options(argc, argv))
		exit(1);	/* process command line options */
	if (open_outfile())	/* setup root process output stream */
		exit(1);
	calc_procs();		/* see how many subprocesses we need */
	/*
	 * Allocate the root pcb and set it up.
	 */
	pcb = (audit_pcb_t *)a_calloc(1, sizeof (audit_pcb_t));
	pcb->pcb_procno = root_pid;
	pcb->pcb_flags |= PF_ROOT;
	pcb->pcb_fpw = stdout;
	pcb->pcb_time = -1;
	/*
	 * Now start the whole thing rolling.
	 */
	if (mfork(pcb, pcbnum, 0, pcbnum - 1)) {
		/*
		 * Error in processing somewhere. A message is already printed.
		 * Display usage statistics and remove the outfile.
		 */
		if (getpid() == root_pid) {
			audit_stats();
			(void) close_outfile();
			rm_outfile();
		}
		exit(1);
	}
	/*
	 * Clean up afterwards.
	 * Only do outfile cleanup if we are root process.
	 */
	if (getpid() == root_pid) {
		if ((ret = write_trailer()) == 0) { /* write trailer to file */

			ret = close_outfile();	/* close the outfile */
		}
		/*
		 * If there was an error in cleanup then remove outfile.
		 */
		if (ret) {
			rm_outfile();
			exit(1);
		}
		/*
		 * And lastly delete the infiles if the user so wishes.
		 */
		if (f_delete)
			delete_infiles();
	}
	return (0);
/*NOTREACHED*/
}


/*
 * .func mfork - main fork routine.
 * .desc Create a (sub-)tree of processses if needed, or just do the work
 *	if we have few enough groups to process. This is a recursive routine
 *	which stops recursing when the number of files to process is small
 *	enough. Each call to mfork() is responsible for a range of pcbs
 *	from audit_pcbs[]. This range is designated by the lo and hi
 *	arguments (inclusive). If the number of pcbs is small enough
 *	then we have hit a leaf of the tree and mproc() is called to
 *	do the processing. Otherwise we fork some processes and break
 *	the range of pcbs up amongst them.
 * .call	ret = mfork(pcb, nsp, lo, hi).
 * .arg	pcb	- ptr to pcb that is root node of the to-be-created tree.
 * .arg	nsp	- number of sub-processes this tree must process.
 * .arg	lo	- lower-limit of process number range. Index into audit_pcbs.
 * .arg	hi	- higher limit of pcb range. Index into audit_pcbs.
 * .ret	0	- succesful completion.
 * .ret	-1	- error encountered in processing - message already printed.
 */
static int
mfork(audit_pcb_t *pcb, int nsp, int lo, int hi)
{
	int	range, procno, i, tofork, nnsp, nrem;
	int	fildes[2];
	audit_pcb_t *pcbn;

#if AUDIT_PROC_TRACE
	(void) fprintf(stderr, "mfork: nsp %d %d->%d\n", nsp, lo, hi);
#endif

	/*
	 * The range of pcb's to process is small enough now. Do the work.
	 */
	if (nsp <= max_sproc) {
		pcb->pcb_flags |= PF_LEAF;	/* leaf in process tree */
		pcb->pcb_below = audit_pcbs;	/* proc pcbs from audit_pcbs */
		gather_pcb(pcb, lo, hi);
		trim_mem(pcb);			/* trim allocated memory */
		return (mproc(pcb));		/* do the work */
	}
	/*
	 * Too many pcb's for one process - must fork.
	 * Try to balance the tree as it grows and make it short and fat.
	 * The thing to minimize is the number of times a record passes
	 * through a pipe.
	 */
	else {
		/*
		 * Fork less than the maximum number of processes.
		 */
		if (nsp <= max_sproc * (max_sproc - 1)) {
			tofork = nsp / max_sproc;
			if (nsp % max_sproc)
				tofork++;	/* how many to fork */
		}
		/*
		 * Fork the maximum number of processes.
		 */
		else {
			tofork = max_sproc;	/* how many to fork */
		}
		/*
		 * Allocate the nodes below us in the process tree.
		 */
		pcb->pcb_below = (audit_pcb_t *)
			a_calloc(tofork, sizeof (*pcb));
		nnsp = nsp / tofork;	/* # of pcbs per forked process */
		nrem = nsp % tofork;	/* remainder to spread around */
		/*
		 * Loop to fork all of the subs. Open a pipe for each.
		 * If there are any errors in pipes, forks, or getting streams
		 * for the pipes then quit altogether.
		 */
		for (i = 0; i < tofork; i++) {
			pcbn = &pcb->pcb_below[i];
			pcbn->pcb_time = -1;
			if (pipe(fildes)) {
				perror(gettext(
					"auditreduce: couldn't get a pipe"));
				return (-1);
			}
			/*
			 * Convert descriptors to streams.
			 */
			if ((pcbn->pcb_fpr = fdopen(fildes[0], "r")) == NULL) {
	perror(gettext("auditreduce: couldn't get read stream for pipe"));
				return (-1);
			}
			if ((pcbn->pcb_fpw = fdopen(fildes[1], "w")) == NULL) {
	perror(gettext("auditreduce: couldn't get write stream for pipe"));
				return (-1);
			}
			if ((procno = fork()) == -1) {
				perror(gettext("auditreduce: fork failed"));
				return (-1);
			}
			/*
			 * Calculate the range of pcbs from audit_pcbs [] this
			 * branch of the tree will be responsible for.
			 */
			range = (nrem > 0) ? nnsp + 1 : nnsp;
			/*
			 * Child route.
			 */
			if (procno == 0) {
				pcbn->pcb_procno = getpid();
				c_close(pcb, i); /* close unused streams */
				/*
				 * Continue resolving this branch.
				 */
				return (mfork(pcbn, range, lo, lo + range - 1));
			}
			/* Parent route. */
			else {
				pcbn->pcb_procno = i;
				/* allocate buffer to hold record */
				pcbn->pcb_rec = (char *)a_calloc(1,
				    AUDITBUFSIZE);
				pcbn->pcb_size = AUDITBUFSIZE;
				p_close(pcbn);	/* close unused streams */

				nrem--;
				lo += range;
			}
		}
		/*
		 * Done forking all of the subs.
		 */
		gather_pcb(pcb, 0, tofork - 1);
		trim_mem(pcb);			/* free unused memory */
		return (mproc(pcb));
	}
}


/*
 * .func	trim_mem - trim memory usage.
 * .desc	Free un-needed allocated memory.
 * .call	trim_mem(pcb).
 * .arg	pcb	- ptr to pcb for current process.
 * .ret	void.
 */
static void
trim_mem(audit_pcb_t *pcb)
{
	int	count;
	size_t	size;

	/*
	 * For the root don't free anything. We need to save audit_pcbs[]
	 * in case we are deleting the infiles at the end.
	 */
	if (pcb->pcb_flags & PF_ROOT)
		return;
	/*
	 * For a leaf save its part of audit_pcbs[] and then remove it all.
	 */
	if (pcb->pcb_flags & PF_LEAF) {
		count = pcb->pcb_count;
		size = sizeof (audit_pcb_t);
		/* allocate a new buffer to hold the pcbs */
		pcb->pcb_below = (audit_pcb_t *)a_calloc(count, size);
		/* save this pcb's portion */
		(void) memcpy((void *) pcb->pcb_below,
		    (void *) &audit_pcbs[pcb->pcb_lo], count * size);
		rm_mem(pcb);
		gather_pcb(pcb, 0, count - 1);
	}
		/*
		 * If this is an intermediate node then just remove it all.
		 */
	else {
		rm_mem(pcb);
	}
}


/*
 * .func	rm_mem - remove memory.
 * .desc	Remove unused memory associated with audit_pcbs[]. For each
 *	pcb in audit_pcbs[] free the record buffer and all of
 *	the fcbs. Then free audit_pcbs[].
 * .call	rm_mem(pcbr).
 * .arg	pcbr	- ptr to pcb of current process.
 * .ret	void.
 */
static void
rm_mem(audit_pcb_t *pcbr)
{
	int	i;
	audit_pcb_t *pcb;
	audit_fcb_t *fcb, *fcbn;

	for (i = 0; i < pcbsize; i++) {
		/*
		 * Don't free the record buffer and fcbs for the pcbs this
		 * process is using.
		 */
		if (pcbr->pcb_flags & PF_LEAF) {
			if (pcbr->pcb_lo <= i || i <= pcbr->pcb_hi)
				continue;
		}
		pcb = &audit_pcbs[i];
		free(pcb->pcb_rec);
		for (fcb = pcb->pcb_first; fcb != NULL; /* */) {
			fcbn = fcb->fcb_next;
			free((char *)fcb);
			fcb = fcbn;
		}
	}
	free((char *)audit_pcbs);
}


/*
 * .func	c_close - close unused streams.
 * .desc	This is called for each child process just after being born.
 *	The child closes the read stream for the pipe to its parent.
 *	It also closes the read streams for the other children that
 *	have been born before it. If any closes fail a warning message
 *	is printed, but processing continues.
 * .call	ret = c_close(pcb, i).
 * .arg	pcb	- ptr to the child's parent pcb.
 * .arg	i	- iteration # of child in forking loop.
 * .ret	void.
 */
static void
c_close(audit_pcb_t *pcb, int	i)
{
	int	j;
	audit_pcb_t *pcbt;

	/*
	 * Do all pcbs in parent's group up to and including us
	 */
	for (j = 0; j <= i; j++) {
		pcbt = &pcb->pcb_below[j];
		if (fclose(pcbt->pcb_fpr) == EOF) {
			if (!f_quiet)
		perror(gettext("auditreduce: initial close on pipe failed"));
		}
		/*
		 * Free the buffer allocated to hold incoming records.
		 */
		if (i != j) {
			free(pcbt->pcb_rec);
		}
	}
}


/*
 * .func	p_close - close unused streams for parent.
 * .desc	Called by the parent right after forking a child.
 *	Closes the write stream on the pipe to the child since
 *	we will never use it.
 * .call	p_close(pcbn),
 * .arg	pcbn	- ptr to pcb.
 * .ret	void.
 */
static void
p_close(audit_pcb_t *pcbn)
{
	if (fclose(pcbn->pcb_fpw) == EOF) {
		if (!f_quiet)
		perror(gettext("auditreduce: close for write pipe failed"));
	}
}


/*
 * .func	audit_stats - print statistics.
 * .desc	Print usage statistics for the user if the run fails.
 *	Tells them how many files they had and how many groups this
 *	totalled. Also tell them how many layers and processes the
 *	process tree had.
 * .call	audit_stats().
 * .arg	none.
 * .ret	void.
 */
void
audit_stats(void)
{
	struct rlimit rl;

	if (getrlimit(RLIMIT_NOFILE, &rl) != -1)
		(void) fprintf(stderr,
		    gettext("%s The system allows %d files per process.\n"),
		    ar, rl.rlim_cur);
	(void) fprintf(stderr, gettext(
"%s There were %d file(s) %d file group(s) %d process(es) %d layer(s).\n"),
		ar, filenum, pcbnum, total_procs, total_layers);
}


/*
 * .func gather_pcb - gather pcbs.
 * .desc Gather together the range of the sub-processes that we are
 *	responsible for. For a pcb that controls processes this is all
 *	of the sub-processes that it forks. For a pcb that controls
 *	files this is the the range of pcbs from audit_pcbs[].
 * .call gather_pcb(pcb, lo, hi).
 * .arg	pcb	- ptr to pcb.
 * .arg	lo	- lo index into pcb_below.
 * .arg	hi	- hi index into pcb_below.
 * .ret	void.
 */
static void
gather_pcb(audit_pcb_t *pcb, int lo, int hi)
{
	pcb->pcb_lo = lo;
	pcb->pcb_hi = hi;
	pcb->pcb_count = hi - lo + 1;
}


/*
 * .func calc_procs - calculate process parameters.
 * .desc Calculate the current run's paramters regarding how many
 *	processes will have to be forked (maybe none).
 *	5 is subtracted from maxfiles_proc to allow for stdin, stdout,
 *	stderr, and the pipe to a parent process. The outfile
 *	in the root process is assigned to stdout. The unused half of each
 *	pipe is closed, to allow for more connections, but we still
 *	have to have the 5th spot because in order to get the pipe
 *	we need 2 descriptors up front.
 * .call calc_procs().
 * .arg	none.
 * .ret	void.
 */
static void
calc_procs(void)
{
	int	val;
	int	maxfiles_proc;
	struct rlimit rl;

	if (getrlimit(RLIMIT_NOFILE, &rl) == -1) {
		perror("auditreduce: getrlimit");
		exit(1);
	}

	maxfiles_proc = rl.rlim_cur;

	max_sproc = maxfiles_proc - 5;	/* max subprocesses per process */

	/*
	 * Calculate how many layers the process tree has.
	 */
	total_layers = 1;
	for (/* */; /* */; /* */) {
		val = a_pow(max_sproc, total_layers);
		if (val > pcbnum)
			break;
		total_layers++;
	}
	/*
	 * Count how many processes are in the process tree.
	 */
	mcount(pcbnum, 0);

#if AUDIT_PROC_TRACE
	(void) fprintf(stderr,
	    "pcbnum %d filenum %d mfp %d msp %d ly %d tot %d\n\n",
	    pcbnum, filenum, maxfiles_proc, max_sproc,
	    total_layers, total_procs);
#endif
}


static int
a_pow(int base, int exp)
{
	int	i;
	int	answer;

	if (exp == 0) {
		answer = 1;
	} else {
		answer = base;
		for (i = 0; i < (exp - 1); i++)
			answer *= base;
	}
	return (answer);
}


/*
 * .func mcount - main count.
 * .desc Go through the motions of building the process tree just
 *	to count how many processes there are. Don't really
 *	build anything. Answer is in global var total_procs.
 * .call mcount(nsp, lo).
 * .arg	nsp	- number of subs for this tree branch.
 * .arg	lo	- lo side of range of subs.
 * .ret	void.
 */
static void
mcount(int nsp, int lo)
{
	int	range, i, tofork, nnsp, nrem;

	total_procs++;		/* count another process created */

	if (nsp > max_sproc) {
		if (nsp <= max_sproc * (max_sproc - 1)) {
			tofork = nsp / max_sproc;
			if (nsp % max_sproc)
				tofork++;
		} else {
			tofork = max_sproc;
		}
		nnsp = nsp / tofork;
		nrem = nsp % tofork;
		for (i = 0; i < tofork; i++) {
			range = (nrem > 0) ? nnsp + 1 : nnsp;
			mcount(range, lo);
			nrem--;
			lo += range;
		}
	}
}


/*
 * .func delete_infiles - delete the input files.
 * .desc If the user asked us to (via 'D' flag) then unlink the input files.
 * .call ret = delete_infiles().
 * .arg none.
 * .ret void.
 */
static void
delete_infiles(void)
{
	int	i;
	audit_pcb_t *pcb;
	audit_fcb_t *fcb;

	for (i = 0; i < pcbsize; i++) {
		pcb = &audit_pcbs[i];
		fcb = pcb->pcb_dfirst;
		while (fcb != NULL) {
			/*
			 * Only delete a file if it was succesfully processed.
			 * If there were any read errors or bad records
			 * then don't delete it.
			 * There may still be unprocessed records in it.
			 */
			if (fcb->fcb_flags & FF_DELETE) {
				if (unlink(fcb->fcb_file)) {
					if (f_verbose) {
						(void) sprintf(errbuf, gettext(
						"%s delete on %s failed"),
						ar, fcb->fcb_file);
					}
					perror(errbuf);
				}
			}
			fcb = fcb->fcb_next;
		}
	}
}


/*
 * .func rm_outfile - remove the outfile.
 * .desc Remove the file we are writing the records to. We do this if
 *	processing failed and we are quitting before finishing.
 *	Update - don't actually remove the outfile, but generate
 *	a warning about its possible heathen nature.
 * .call ret = rm_outfile().
 * .arg	none.
 * .ret	void.
 */
static void
rm_outfile(void)
{
#if 0
	if (f_outfile) {
		if (unlink(f_outtemp) == -1) {
			(void) sprintf(errbuf,
				gettext("%s delete on %s failed"),
				ar, f_outtemp);
			perror(errbuf);
		}
	}
#else
	(void) fprintf(stderr,
gettext("%s Warning: Incomplete audit file may have been generated - %s\n"),
		ar,
		(f_outfile == NULL) ? gettext("standard output") : f_outfile);
#endif
}


/*
 * .func	close_outfile - close the outfile.
 * .desc	Close the file we are writing records to.
 * .call	ret = close_outfile().
 * .arg	none.
 * .ret	0	- close was succesful.
 * .ret	-1	- close failed.
 */
static int
close_outfile(void)
{
	if (fclose(stdout) == EOF) {
		(void) sprintf(errbuf, gettext("%s close on %s failed"),
		    ar, f_outfile ? f_outfile : "standard output");
		perror(errbuf);
		return (-1);
	}
	(void) fsync(fileno(stdout));
	return (rename_outfile());
}


/*
 * .func write_header - write audit file header.
 * .desc Write an audit file header to the output stream. The time in the
 *	header is the time of the first record written to the stream. This
 *	routine is called by the process handling the root node of the
 *	process tree just before it writes the first record to the output
 *	stream.
 * .ret	0 - succesful write.
 * .ret -1 - failed write - message printed.
 */
int
write_header(void)
{
	return (write_file_token(f_start));
}


static int
write_file_token(time_t when)
{
	adr_t adr;			/* adr ptr */
	struct timeval tv;		/* time now */
	char	for_adr[16];		/* plenty of room */
#ifdef _LP64
	char	token_id = AUT_OTHER_FILE64;
#else
	char	token_id = AUT_OTHER_FILE32;
#endif
	short	i = 1;
	char	c = '\0';

	tv.tv_sec = when;
	tv.tv_usec = 0;
	adr_start(&adr, for_adr);
	adr_char(&adr, &token_id, 1);
#ifdef _LP64
	adr_int64(&adr, (int64_t *)&tv, 2);
#else
	adr_int32(&adr, (int32_t *)&tv, 2);
#endif
	adr_short(&adr, &i, 1);
	adr_char(&adr, &c, 1);

	if (fwrite(for_adr, sizeof (char), adr_count(&adr), stdout) !=
	    adr_count(&adr)) {
		if (when == f_start) {
			(void) sprintf(errbuf,
				gettext("%s error writing header to %s. "),
				ar,
				f_outfile ? f_outfile :
					gettext("standard output"));
		} else {
			(void) sprintf(errbuf,
				gettext("%s error writing trailer to %s. "),
				ar,
				f_outfile ? f_outfile :
					gettext("standard output"));
		}
		perror(errbuf);
		return (-1);
	}
	return (0);
}


/*
 * .func  write_trailer - write audit file trailer.
 * .desc  Write an audit file trailer to the output stream. The finish
 *	time for the trailer is the time of the last record written
 *	to the stream.
 * .ret	0 - succesful write.
 * .ret	-1 - failed write - message printed.
 */
static int
write_trailer(void)
{
	return (write_file_token(f_end));
}


/*
 * .func rename_outfile - rename the outfile.
 * .desc If the user used the -O flag they only gave us the suffix name
 *	for the outfile. We have to add the time stamps to put the filename
 *	in the proper audit file name format. The start time will be the time
 *	of the first record in the file and the end time will be the time of
 *	the last record in the file.
 * .ret	0 - rename succesful.
 * .ret	-1 - rename failed - message printed.
 */
static int
rename_outfile(void)
{
	char	f_newfile[MAXFILELEN];
	char	buf1[15], buf2[15];
	char	*f_file, *f_nfile, *f_time, *f_name;

	if (f_outfile != NULL) {
		/*
		 * Get string representations of start and end times.
		 */
		derive_str(f_start, buf1);
		derive_str(f_end, buf2);

		f_nfile = f_time = f_newfile;	/* working copy */
		f_file = f_name = f_outfile;	/* their version */
		while (*f_file) {
			if (*f_file == '/') {	/* look for filename */
				f_time = f_nfile + 1;
				f_name = f_file + 1;
			}
			*f_nfile++ = *f_file++;	/* make copy of their version */
		}
		*f_time = '\0';
		/* start time goes first */
		(void) strcat(f_newfile, buf1);
		(void) strcat(f_newfile, ".");
		/* then the finish time */
		(void) strcat(f_newfile, buf2);
		(void) strcat(f_newfile, ".");
		/* and the name they gave us */
		(void) strcat(f_newfile, f_name);

#if AUDIT_FILE
		(void) fprintf(stderr, "rename_outfile: <%s> --> <%s>\n",
			f_outfile, f_newfile);
#endif

#if AUDIT_RENAME
		if (rename(f_outtemp, f_newfile) == -1) {
			(void) fprintf(stderr,
			    "%s rename of %s to %s failed.\n",
			    ar, f_outtemp, f_newfile);
			return (-1);
		}
		f_outfile = f_newfile;
#else
		if (rename(f_outtemp, f_outfile) == -1) {
			(void) fprintf(stderr,
			    gettext("%s rename of %s to %s failed.\n"),
			    ar, f_outtemp, f_outfile);
			return (-1);
		}
#endif
	}
	return (0);
}


/*
 * .func open_outfile - open the outfile.
 * .desc Open the outfile specified by the -O option. Assign it to the
 *	the standard output. Get a unique temporary name to use so we
 *	don't clobber an existing file.
 * .ret	0 - no errors detected.
 * .ret	-1 - errors in processing (message already printed).
 */
static int
open_outfile(void)
{
	int	tmpfd = -1;

	if (f_outfile != NULL) {
		f_outtemp = (char *)a_calloc(1, strlen(f_outfile) + 8);
		(void) strcpy(f_outtemp, f_outfile);
		(void) strcat(f_outtemp, "XXXXXX");
		if ((tmpfd = mkstemp(f_outtemp)) == -1) {
			(void) sprintf(errbuf,
			    gettext("%s couldn't create temporary file"), ar);
			perror(errbuf);
			return (-1);
		}
		(void) fflush(stdout);
		if (tmpfd != fileno(stdout)) {
			if ((dup2(tmpfd, fileno(stdout))) == -1) {
				(void) sprintf(errbuf,
				    gettext("%s can't assign %s to the "
				    "standard output"), ar, f_outfile);
				perror(errbuf);
				return (-1);
			}
			(void) close(tmpfd);
		}
	}
	return (0);
}


/*
 * .func init_options - initialize the options.
 * .desc Give initial and/or default values to some options.
 * .call init_options();
 * .arg	none.
 * .ret	void.
 */
static void
init_options(void)
{
	struct timeval tp;
	struct timezone tpz;

	/*
	 * Get current time for general use.
	 */
	if (gettimeofday(&tp, &tpz) == -1)
		perror(gettext("auditreduce: initial getttimeofday failed"));

	time_now = tp.tv_sec;		/* save for general use */
	f_start = 0;			/* first record time default */
	f_end = time_now;		/* last record time default */
	m_after = 0;			/* Jan 1, 1970 00:00:00 */

	/*
	 * Setup initial size of audit_pcbs[].
	 */
	pcbsize = PCB_INITSIZE;		/* initial size of file-holding pcb's */

	audit_pcbs = (audit_pcb_t *)a_calloc(pcbsize, sizeof (audit_pcb_t));

	/* description of 'current' error */
	error_str = gettext("initial error");

}


/*
 * .func a_calloc - audit calloc.
 * .desc Calloc with check for failure. This is called by all of the
 *	places that want memory.
 * .call ptr = a_calloc(nelem, size).
 * .arg	nelem - number of elements to allocate.
 * .arg	size - size of each element.
 * .ret	ptr - ptr to allocated and zeroed memory.
 * .ret	never - if calloc fails then we never return.
 */
void	*
a_calloc(int nelem, size_t size)
{
	void	*ptr;

	if ((ptr = calloc((unsigned)nelem, size)) == NULL) {
		perror(gettext("auditreduce: memory allocation failed"));
		exit(1);
	}
	return (ptr);
}


/*
 * .func init_sig - initial signal catching.
 *
 * .desc
 *	Setup the signal catcher to catch the SIGCHLD signal plus
 *	"environmental" signals -- keyboard plus other externally
 *	generated signals such as out of file space or cpu time.  If a
 *	child exits with either a non-zero exit code or was killed by
 *	a signal to it then we will also exit with a non-zero exit
 *	code. In this way abnormal conditions can be passed up to the
 *	root process and the entire run be halted. Also catch the int
 *	and quit signals. Remove the output file since it is in an
 *	inconsistent state.
 * .call ret = init_sig().
 * .arg none.
 * .ret 0 - no errors detected.
 * .ret -1 - signal failed (message printed).
 */
static int
init_sig(void)
{
	if (signal(SIGCHLD, chld_handler) == SIG_ERR) {
		perror(gettext("auditreduce: SIGCHLD signal failed"));
		return (-1);
	}

	if (signal(SIGHUP, int_handler) == SIG_ERR) {
		perror(gettext("auditreduce: SIGHUP signal failed"));
		return (-1);
	}
	if (signal(SIGINT, int_handler) == SIG_ERR) {
		perror(gettext("auditreduce: SIGINT signal failed"));
		return (-1);
	}
	if (signal(SIGQUIT, int_handler) == SIG_ERR) {
		perror(gettext("auditreduce: SIGQUIT signal failed"));
		return (-1);
	}
	if (signal(SIGABRT, int_handler) == SIG_ERR) {
		perror(gettext("auditreduce: SIGABRT signal failed"));
		return (-1);
	}
	if (signal(SIGTERM, int_handler) == SIG_ERR) {
		perror(gettext("auditreduce: SIGTERM signal failed"));
		return (-1);
	}
	if (signal(SIGPWR, int_handler) == SIG_ERR) {
		perror(gettext("auditreduce: SIGPWR signal failed"));
		return (-1);
	}
	if (signal(SIGXCPU, int_handler) == SIG_ERR) {
		perror(gettext("auditreduce: SIGXCPU signal failed"));
		return (-1);
	}
	if (signal(SIGXFSZ, int_handler) == SIG_ERR) {
		perror(gettext("auditreduce: SIGXFSZ signal failed"));
		return (-1);
	}
	if (signal(SIGSEGV, int_handler) == SIG_ERR) {
		perror(gettext("auditreduce: SIGSEGV signal failed"));
		return (-1);
	}

	return (0);
}


/*
 * .func chld_handler - handle child signals.
 * .desc Catch the SIGCHLD signals. Remove the root process
 *	output file because it is in an inconsistent state.
 *	Print a message giving the signal number and/or return code
 *	of the child who caused the signal.
 * .ret	void.
 */
/* ARGSUSED */
void
chld_handler(int sig)
{
	int	pid;
	int	status;

	/*
	 * Get pid and reasons for cause of event.
	 */
	pid = wait(&status);

	if (pid > 0) {
		/*
		 * If child received a signal or exited with a non-zero
		 * exit status then print message and exit
		 */
		if ((WHIBYTE(status) == 0 && WLOBYTE(status) != 0) ||
		    (WHIBYTE(status) != 0 && WLOBYTE(status) == 0)) {
			(void) fprintf(stderr,
			    gettext("%s abnormal child termination - "), ar);

			if (WHIBYTE(status) == 0 && WLOBYTE(status) != 0) {
				psignal(WLOBYTE(status), "signal");
				if (WCOREDUMP(status))
					(void) fprintf(stderr,
					    gettext("core dumped\n"));
			}

			if (WHIBYTE(status) != 0 && WLOBYTE(status) == 0)
				(void) fprintf(stderr, gettext(
					"return code %d\n"),
					WHIBYTE(status));

			/*
			 * Get rid of outfile - it is suspect.
			 */
			if (f_outfile != NULL) {
				(void) close_outfile();
				rm_outfile();
			}
			/*
			 * Give statistical info that may be useful.
			 */
			audit_stats();

			exit(1);
		}
	}
}


/*
 * .func	int_handler - handle quit/int signals.
 * .desc	Catch the keyboard and other environmental signals.
 *		Remove the root process output file because it is in
 *		an inconsistent state.
 * .ret	void.
 */
/* ARGSUSED */
void
int_handler(int sig)
{
	if (getpid() == root_pid) {
		(void) close_outfile();
		rm_outfile();
		exit(1);
	}
	/*
	 * For a child process don't give an error exit or the
	 * parent process will catch it with the chld_handler and
	 * try to erase the outfile again.
	 */
	exit(0);
}