xref: /illumos-gate/usr/src/cmd/auditreduce/main.c (revision 2b24ab6b3865caeede9eeb9db6b83e1d89dcd1ea)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*
28  * The Secure SunOS audit reduction tool - auditreduce.
29  * Document SM0071 is the primary source of information on auditreduce.
30  *
31  * Composed of 4 source modules:
32  * main.c - main driver.
33  * option.c - command line option processing.
34  * process.c - record/file/process functions.
35  * time.c - date/time handling.
36  *
37  * Main(), write_header(), audit_stats(), and a_calloc()
38  * are the only functions visible outside this module.
39  */
40 
41 #include <siginfo.h>
42 #include <locale.h>
43 #include <libintl.h>
44 #include "auditr.h"
45 #include "auditrd.h"
46 
47 #if !defined(TEXT_DOMAIN)
48 #define	TEXT_DOMAIN "SUNW_OST_OSCMD"
49 #endif
50 
51 extern void	derive_str(time_t, char *);
52 extern int	process_options(int, char **);
53 extern int	mproc(audit_pcb_t *);
54 extern void	init_tokens(void);	/* shared with praudit */
55 
56 static int	a_pow(int, int);
57 static void	calc_procs(void);
58 static void	chld_handler(int);
59 static int	close_outfile(void);
60 static void	c_close(audit_pcb_t *, int);
61 static void	delete_infiles(void);
62 static void	gather_pcb(audit_pcb_t *, int, int);
63 static void	init_options(void);
64 static int	init_sig(void);
65 static void	int_handler(int);
66 static int	mfork(audit_pcb_t *, int, int, int);
67 static void	mcount(int, int);
68 static int	open_outfile(void);
69 static void	p_close(audit_pcb_t *);
70 static int	rename_outfile(void);
71 static void	rm_mem(audit_pcb_t *);
72 static void	rm_outfile(void);
73 static void	trim_mem(audit_pcb_t *);
74 static int	write_file_token(time_t);
75 static int	write_trailer(void);
76 
77 /*
78  * File globals.
79  */
80 static int	max_sproc;	/* maximum number of subprocesses per process */
81 static int	total_procs;	/* number of processes in the process tree */
82 static int	total_layers;	/* number of layers in the process tree */
83 
84 /*
85  * .func main - main.
86  * .desc The beginning. Main() calls each of the initialization routines
87  *	and then allocates the root pcb. Then it calls mfork() to get
88  *	the work done.
89  * .call	main(argc, argv).
90  * .arg	argc	- number of arguments.
91  * .arg	argv	- array of pointers to arguments.
92  * .ret	0	- via exit() - no errors detected.
93  * .ret	1	- via exit() - errors detected (messages printed).
94  */
95 int
96 main(int argc, char **argv)
97 {
98 	int	ret;
99 	audit_pcb_t *pcb;
100 
101 	/* Internationalization */
102 	(void) setlocale(LC_ALL, "");
103 	(void) textdomain(TEXT_DOMAIN);
104 
105 	root_pid = getpid();	/* know who is root process for error */
106 	init_options();		/* initialize options */
107 	init_tokens();		/* initialize token processing table */
108 	if (init_sig())		/* initialize signals */
109 		exit(1);
110 	if (process_options(argc, argv))
111 		exit(1);	/* process command line options */
112 	if (open_outfile())	/* setup root process output stream */
113 		exit(1);
114 	calc_procs();		/* see how many subprocesses we need */
115 	/*
116 	 * Allocate the root pcb and set it up.
117 	 */
118 	pcb = (audit_pcb_t *)a_calloc(1, sizeof (audit_pcb_t));
119 	pcb->pcb_procno = root_pid;
120 	pcb->pcb_flags |= PF_ROOT;
121 	pcb->pcb_fpw = stdout;
122 	pcb->pcb_time = -1;
123 	/*
124 	 * Now start the whole thing rolling.
125 	 */
126 	if (mfork(pcb, pcbnum, 0, pcbnum - 1)) {
127 		/*
128 		 * Error in processing somewhere. A message is already printed.
129 		 * Display usage statistics and remove the outfile.
130 		 */
131 		if (getpid() == root_pid) {
132 			audit_stats();
133 			(void) close_outfile();
134 			rm_outfile();
135 		}
136 		exit(1);
137 	}
138 	/*
139 	 * Clean up afterwards.
140 	 * Only do outfile cleanup if we are root process.
141 	 */
142 	if (getpid() == root_pid) {
143 		if ((ret = write_trailer()) == 0) { /* write trailer to file */
144 
145 			ret = close_outfile();	/* close the outfile */
146 		}
147 		/*
148 		 * If there was an error in cleanup then remove outfile.
149 		 */
150 		if (ret) {
151 			rm_outfile();
152 			exit(1);
153 		}
154 		/*
155 		 * And lastly delete the infiles if the user so wishes.
156 		 */
157 		if (f_delete)
158 			delete_infiles();
159 	}
160 	return (0);
161 /*NOTREACHED*/
162 }
163 
164 
165 /*
166  * .func mfork - main fork routine.
167  * .desc Create a (sub-)tree of processses if needed, or just do the work
168  *	if we have few enough groups to process. This is a recursive routine
169  *	which stops recursing when the number of files to process is small
170  *	enough. Each call to mfork() is responsible for a range of pcbs
171  *	from audit_pcbs[]. This range is designated by the lo and hi
172  *	arguments (inclusive). If the number of pcbs is small enough
173  *	then we have hit a leaf of the tree and mproc() is called to
174  *	do the processing. Otherwise we fork some processes and break
175  *	the range of pcbs up amongst them.
176  * .call	ret = mfork(pcb, nsp, lo, hi).
177  * .arg	pcb	- ptr to pcb that is root node of the to-be-created tree.
178  * .arg	nsp	- number of sub-processes this tree must process.
179  * .arg	lo	- lower-limit of process number range. Index into audit_pcbs.
180  * .arg	hi	- higher limit of pcb range. Index into audit_pcbs.
181  * .ret	0	- succesful completion.
182  * .ret	-1	- error encountered in processing - message already printed.
183  */
184 static int
185 mfork(audit_pcb_t *pcb, int nsp, int lo, int hi)
186 {
187 	int	range, procno, i, tofork, nnsp, nrem;
188 	int	fildes[2];
189 	audit_pcb_t *pcbn;
190 
191 #if AUDIT_PROC_TRACE
192 	(void) fprintf(stderr, "mfork: nsp %d %d->%d\n", nsp, lo, hi);
193 #endif
194 
195 	/*
196 	 * The range of pcb's to process is small enough now. Do the work.
197 	 */
198 	if (nsp <= max_sproc) {
199 		pcb->pcb_flags |= PF_LEAF;	/* leaf in process tree */
200 		pcb->pcb_below = audit_pcbs;	/* proc pcbs from audit_pcbs */
201 		gather_pcb(pcb, lo, hi);
202 		trim_mem(pcb);			/* trim allocated memory */
203 		return (mproc(pcb));		/* do the work */
204 	}
205 	/*
206 	 * Too many pcb's for one process - must fork.
207 	 * Try to balance the tree as it grows and make it short and fat.
208 	 * The thing to minimize is the number of times a record passes
209 	 * through a pipe.
210 	 */
211 	else {
212 		/*
213 		 * Fork less than the maximum number of processes.
214 		 */
215 		if (nsp <= max_sproc * (max_sproc - 1)) {
216 			tofork = nsp / max_sproc;
217 			if (nsp % max_sproc)
218 				tofork++;	/* how many to fork */
219 		}
220 		/*
221 		 * Fork the maximum number of processes.
222 		 */
223 		else {
224 			tofork = max_sproc;	/* how many to fork */
225 		}
226 		/*
227 		 * Allocate the nodes below us in the process tree.
228 		 */
229 		pcb->pcb_below = (audit_pcb_t *)
230 			a_calloc(tofork, sizeof (*pcb));
231 		nnsp = nsp / tofork;	/* # of pcbs per forked process */
232 		nrem = nsp % tofork;	/* remainder to spread around */
233 		/*
234 		 * Loop to fork all of the subs. Open a pipe for each.
235 		 * If there are any errors in pipes, forks, or getting streams
236 		 * for the pipes then quit altogether.
237 		 */
238 		for (i = 0; i < tofork; i++) {
239 			pcbn = &pcb->pcb_below[i];
240 			pcbn->pcb_time = -1;
241 			if (pipe(fildes)) {
242 				perror(gettext(
243 					"auditreduce: couldn't get a pipe"));
244 				return (-1);
245 			}
246 			/*
247 			 * Convert descriptors to streams.
248 			 */
249 			if ((pcbn->pcb_fpr = fdopen(fildes[0], "r")) == NULL) {
250 	perror(gettext("auditreduce: couldn't get read stream for pipe"));
251 				return (-1);
252 			}
253 			if ((pcbn->pcb_fpw = fdopen(fildes[1], "w")) == NULL) {
254 	perror(gettext("auditreduce: couldn't get write stream for pipe"));
255 				return (-1);
256 			}
257 			if ((procno = fork()) == -1) {
258 				perror(gettext("auditreduce: fork failed"));
259 				return (-1);
260 			}
261 			/*
262 			 * Calculate the range of pcbs from audit_pcbs [] this
263 			 * branch of the tree will be responsible for.
264 			 */
265 			range = (nrem > 0) ? nnsp + 1 : nnsp;
266 			/*
267 			 * Child route.
268 			 */
269 			if (procno == 0) {
270 				pcbn->pcb_procno = getpid();
271 				c_close(pcb, i); /* close unused streams */
272 				/*
273 				 * Continue resolving this branch.
274 				 */
275 				return (mfork(pcbn, range, lo, lo + range - 1));
276 			}
277 			/* Parent route. */
278 			else {
279 				pcbn->pcb_procno = i;
280 				/* allocate buffer to hold record */
281 				pcbn->pcb_rec = (char *)a_calloc(1,
282 				    AUDITBUFSIZE);
283 				pcbn->pcb_size = AUDITBUFSIZE;
284 				p_close(pcbn);	/* close unused streams */
285 
286 				nrem--;
287 				lo += range;
288 			}
289 		}
290 		/*
291 		 * Done forking all of the subs.
292 		 */
293 		gather_pcb(pcb, 0, tofork - 1);
294 		trim_mem(pcb);			/* free unused memory */
295 		return (mproc(pcb));
296 	}
297 }
298 
299 
300 /*
301  * .func	trim_mem - trim memory usage.
302  * .desc	Free un-needed allocated memory.
303  * .call	trim_mem(pcb).
304  * .arg	pcb	- ptr to pcb for current process.
305  * .ret	void.
306  */
307 static void
308 trim_mem(audit_pcb_t *pcb)
309 {
310 	int	count;
311 	size_t	size;
312 
313 	/*
314 	 * For the root don't free anything. We need to save audit_pcbs[]
315 	 * in case we are deleting the infiles at the end.
316 	 */
317 	if (pcb->pcb_flags & PF_ROOT)
318 		return;
319 	/*
320 	 * For a leaf save its part of audit_pcbs[] and then remove it all.
321 	 */
322 	if (pcb->pcb_flags & PF_LEAF) {
323 		count = pcb->pcb_count;
324 		size = sizeof (audit_pcb_t);
325 		/* allocate a new buffer to hold the pcbs */
326 		pcb->pcb_below = (audit_pcb_t *)a_calloc(count, size);
327 		/* save this pcb's portion */
328 		(void) memcpy((void *) pcb->pcb_below,
329 		    (void *) &audit_pcbs[pcb->pcb_lo], count * size);
330 		rm_mem(pcb);
331 		gather_pcb(pcb, 0, count - 1);
332 	}
333 		/*
334 		 * If this is an intermediate node then just remove it all.
335 		 */
336 	else {
337 		rm_mem(pcb);
338 	}
339 }
340 
341 
342 /*
343  * .func	rm_mem - remove memory.
344  * .desc	Remove unused memory associated with audit_pcbs[]. For each
345  *	pcb in audit_pcbs[] free the record buffer and all of
346  *	the fcbs. Then free audit_pcbs[].
347  * .call	rm_mem(pcbr).
348  * .arg	pcbr	- ptr to pcb of current process.
349  * .ret	void.
350  */
351 static void
352 rm_mem(audit_pcb_t *pcbr)
353 {
354 	int	i;
355 	audit_pcb_t *pcb;
356 	audit_fcb_t *fcb, *fcbn;
357 
358 	for (i = 0; i < pcbsize; i++) {
359 		/*
360 		 * Don't free the record buffer and fcbs for the pcbs this
361 		 * process is using.
362 		 */
363 		if (pcbr->pcb_flags & PF_LEAF) {
364 			if (pcbr->pcb_lo <= i || i <= pcbr->pcb_hi)
365 				continue;
366 		}
367 		pcb = &audit_pcbs[i];
368 		free(pcb->pcb_rec);
369 		for (fcb = pcb->pcb_first; fcb != NULL; /* */) {
370 			fcbn = fcb->fcb_next;
371 			free((char *)fcb);
372 			fcb = fcbn;
373 		}
374 	}
375 	free((char *)audit_pcbs);
376 }
377 
378 
379 /*
380  * .func	c_close - close unused streams.
381  * .desc	This is called for each child process just after being born.
382  *	The child closes the read stream for the pipe to its parent.
383  *	It also closes the read streams for the other children that
384  *	have been born before it. If any closes fail a warning message
385  *	is printed, but processing continues.
386  * .call	ret = c_close(pcb, i).
387  * .arg	pcb	- ptr to the child's parent pcb.
388  * .arg	i	- iteration # of child in forking loop.
389  * .ret	void.
390  */
391 static void
392 c_close(audit_pcb_t *pcb, int	i)
393 {
394 	int	j;
395 	audit_pcb_t *pcbt;
396 
397 	/*
398 	 * Do all pcbs in parent's group up to and including us
399 	 */
400 	for (j = 0; j <= i; j++) {
401 		pcbt = &pcb->pcb_below[j];
402 		if (fclose(pcbt->pcb_fpr) == EOF) {
403 			if (!f_quiet)
404 		perror(gettext("auditreduce: initial close on pipe failed"));
405 		}
406 		/*
407 		 * Free the buffer allocated to hold incoming records.
408 		 */
409 		if (i != j) {
410 			free(pcbt->pcb_rec);
411 		}
412 	}
413 }
414 
415 
416 /*
417  * .func	p_close - close unused streams for parent.
418  * .desc	Called by the parent right after forking a child.
419  *	Closes the write stream on the pipe to the child since
420  *	we will never use it.
421  * .call	p_close(pcbn),
422  * .arg	pcbn	- ptr to pcb.
423  * .ret	void.
424  */
425 static void
426 p_close(audit_pcb_t *pcbn)
427 {
428 	if (fclose(pcbn->pcb_fpw) == EOF) {
429 		if (!f_quiet)
430 		perror(gettext("auditreduce: close for write pipe failed"));
431 	}
432 }
433 
434 
435 /*
436  * .func	audit_stats - print statistics.
437  * .desc	Print usage statistics for the user if the run fails.
438  *	Tells them how many files they had and how many groups this
439  *	totalled. Also tell them how many layers and processes the
440  *	process tree had.
441  * .call	audit_stats().
442  * .arg	none.
443  * .ret	void.
444  */
445 void
446 audit_stats(void)
447 {
448 	struct rlimit rl;
449 
450 	if (getrlimit(RLIMIT_NOFILE, &rl) != -1)
451 		(void) fprintf(stderr,
452 		    gettext("%s The system allows %d files per process.\n"),
453 		    ar, rl.rlim_cur);
454 	(void) fprintf(stderr, gettext(
455 "%s There were %d file(s) %d file group(s) %d process(es) %d layer(s).\n"),
456 		ar, filenum, pcbnum, total_procs, total_layers);
457 }
458 
459 
460 /*
461  * .func gather_pcb - gather pcbs.
462  * .desc Gather together the range of the sub-processes that we are
463  *	responsible for. For a pcb that controls processes this is all
464  *	of the sub-processes that it forks. For a pcb that controls
465  *	files this is the the range of pcbs from audit_pcbs[].
466  * .call gather_pcb(pcb, lo, hi).
467  * .arg	pcb	- ptr to pcb.
468  * .arg	lo	- lo index into pcb_below.
469  * .arg	hi	- hi index into pcb_below.
470  * .ret	void.
471  */
472 static void
473 gather_pcb(audit_pcb_t *pcb, int lo, int hi)
474 {
475 	pcb->pcb_lo = lo;
476 	pcb->pcb_hi = hi;
477 	pcb->pcb_count = hi - lo + 1;
478 }
479 
480 
481 /*
482  * .func calc_procs - calculate process parameters.
483  * .desc Calculate the current run's paramters regarding how many
484  *	processes will have to be forked (maybe none).
485  *	5 is subtracted from maxfiles_proc to allow for stdin, stdout,
486  *	stderr, and the pipe to a parent process. The outfile
487  *	in the root process is assigned to stdout. The unused half of each
488  *	pipe is closed, to allow for more connections, but we still
489  *	have to have the 5th spot because in order to get the pipe
490  *	we need 2 descriptors up front.
491  * .call calc_procs().
492  * .arg	none.
493  * .ret	void.
494  */
495 static void
496 calc_procs(void)
497 {
498 	int	val;
499 	int	maxfiles_proc;
500 	struct rlimit rl;
501 
502 	if (getrlimit(RLIMIT_NOFILE, &rl) == -1) {
503 		perror("auditreduce: getrlimit");
504 		exit(1);
505 	}
506 
507 	maxfiles_proc = rl.rlim_cur;
508 
509 	max_sproc = maxfiles_proc - 5;	/* max subprocesses per process */
510 
511 	/*
512 	 * Calculate how many layers the process tree has.
513 	 */
514 	total_layers = 1;
515 	for (/* */; /* */; /* */) {
516 		val = a_pow(max_sproc, total_layers);
517 		if (val > pcbnum)
518 			break;
519 		total_layers++;
520 	}
521 	/*
522 	 * Count how many processes are in the process tree.
523 	 */
524 	mcount(pcbnum, 0);
525 
526 #if AUDIT_PROC_TRACE
527 	(void) fprintf(stderr,
528 	    "pcbnum %d filenum %d mfp %d msp %d ly %d tot %d\n\n",
529 	    pcbnum, filenum, maxfiles_proc, max_sproc,
530 	    total_layers, total_procs);
531 #endif
532 }
533 
534 
535 static int
536 a_pow(int base, int exp)
537 {
538 	int	i;
539 	int	answer;
540 
541 	if (exp == 0) {
542 		answer = 1;
543 	} else {
544 		answer = base;
545 		for (i = 0; i < (exp - 1); i++)
546 			answer *= base;
547 	}
548 	return (answer);
549 }
550 
551 
552 /*
553  * .func mcount - main count.
554  * .desc Go through the motions of building the process tree just
555  *	to count how many processes there are. Don't really
556  *	build anything. Answer is in global var total_procs.
557  * .call mcount(nsp, lo).
558  * .arg	nsp	- number of subs for this tree branch.
559  * .arg	lo	- lo side of range of subs.
560  * .ret	void.
561  */
562 static void
563 mcount(int nsp, int lo)
564 {
565 	int	range, i, tofork, nnsp, nrem;
566 
567 	total_procs++;		/* count another process created */
568 
569 	if (nsp > max_sproc) {
570 		if (nsp <= max_sproc * (max_sproc - 1)) {
571 			tofork = nsp / max_sproc;
572 			if (nsp % max_sproc)
573 				tofork++;
574 		} else {
575 			tofork = max_sproc;
576 		}
577 		nnsp = nsp / tofork;
578 		nrem = nsp % tofork;
579 		for (i = 0; i < tofork; i++) {
580 			range = (nrem > 0) ? nnsp + 1 : nnsp;
581 			mcount(range, lo);
582 			nrem--;
583 			lo += range;
584 		}
585 	}
586 }
587 
588 
589 /*
590  * .func delete_infiles - delete the input files.
591  * .desc If the user asked us to (via 'D' flag) then unlink the input files.
592  * .call ret = delete_infiles().
593  * .arg none.
594  * .ret void.
595  */
596 static void
597 delete_infiles(void)
598 {
599 	int	i;
600 	audit_pcb_t *pcb;
601 	audit_fcb_t *fcb;
602 
603 	for (i = 0; i < pcbsize; i++) {
604 		pcb = &audit_pcbs[i];
605 		fcb = pcb->pcb_dfirst;
606 		while (fcb != NULL) {
607 			/*
608 			 * Only delete a file if it was succesfully processed.
609 			 * If there were any read errors or bad records
610 			 * then don't delete it.
611 			 * There may still be unprocessed records in it.
612 			 */
613 			if (fcb->fcb_flags & FF_DELETE) {
614 				if (unlink(fcb->fcb_file)) {
615 					if (f_verbose) {
616 						(void) sprintf(errbuf, gettext(
617 						"%s delete on %s failed"),
618 						ar, fcb->fcb_file);
619 					}
620 					perror(errbuf);
621 				}
622 			}
623 			fcb = fcb->fcb_next;
624 		}
625 	}
626 }
627 
628 
629 /*
630  * .func rm_outfile - remove the outfile.
631  * .desc Remove the file we are writing the records to. We do this if
632  *	processing failed and we are quitting before finishing.
633  *	Update - don't actually remove the outfile, but generate
634  *	a warning about its possible heathen nature.
635  * .call ret = rm_outfile().
636  * .arg	none.
637  * .ret	void.
638  */
639 static void
640 rm_outfile(void)
641 {
642 #if 0
643 	if (f_outfile) {
644 		if (unlink(f_outtemp) == -1) {
645 			(void) sprintf(errbuf,
646 				gettext("%s delete on %s failed"),
647 				ar, f_outtemp);
648 			perror(errbuf);
649 		}
650 	}
651 #else
652 	(void) fprintf(stderr,
653 gettext("%s Warning: Incomplete audit file may have been generated - %s\n"),
654 		ar,
655 		(f_outfile == NULL) ? gettext("standard output") : f_outfile);
656 #endif
657 }
658 
659 
660 /*
661  * .func	close_outfile - close the outfile.
662  * .desc	Close the file we are writing records to.
663  * .call	ret = close_outfile().
664  * .arg	none.
665  * .ret	0	- close was succesful.
666  * .ret	-1	- close failed.
667  */
668 static int
669 close_outfile(void)
670 {
671 	if (fclose(stdout) == EOF) {
672 		(void) sprintf(errbuf, gettext("%s close on %s failed"),
673 		    ar, f_outfile ? f_outfile : "standard output");
674 		perror(errbuf);
675 		return (-1);
676 	}
677 	(void) fsync(fileno(stdout));
678 	return (rename_outfile());
679 }
680 
681 
682 /*
683  * .func write_header - write audit file header.
684  * .desc Write an audit file header to the output stream. The time in the
685  *	header is the time of the first record written to the stream. This
686  *	routine is called by the process handling the root node of the
687  *	process tree just before it writes the first record to the output
688  *	stream.
689  * .ret	0 - succesful write.
690  * .ret -1 - failed write - message printed.
691  */
692 int
693 write_header(void)
694 {
695 	return (write_file_token(f_start));
696 }
697 
698 
699 static int
700 write_file_token(time_t when)
701 {
702 	adr_t adr;			/* adr ptr */
703 	struct timeval tv;		/* time now */
704 	char	for_adr[16];		/* plenty of room */
705 #ifdef _LP64
706 	char	token_id = AUT_OTHER_FILE64;
707 #else
708 	char	token_id = AUT_OTHER_FILE32;
709 #endif
710 	short	i = 1;
711 	char	c = '\0';
712 
713 	tv.tv_sec = when;
714 	tv.tv_usec = 0;
715 	adr_start(&adr, for_adr);
716 	adr_char(&adr, &token_id, 1);
717 #ifdef _LP64
718 	adr_int64(&adr, (int64_t *)&tv, 2);
719 #else
720 	adr_int32(&adr, (int32_t *)&tv, 2);
721 #endif
722 	adr_short(&adr, &i, 1);
723 	adr_char(&adr, &c, 1);
724 
725 	if (fwrite(for_adr, sizeof (char), adr_count(&adr), stdout) !=
726 	    adr_count(&adr)) {
727 		if (when == f_start) {
728 			(void) sprintf(errbuf,
729 				gettext("%s error writing header to %s. "),
730 				ar,
731 				f_outfile ? f_outfile :
732 					gettext("standard output"));
733 		} else {
734 			(void) sprintf(errbuf,
735 				gettext("%s error writing trailer to %s. "),
736 				ar,
737 				f_outfile ? f_outfile :
738 					gettext("standard output"));
739 		}
740 		perror(errbuf);
741 		return (-1);
742 	}
743 	return (0);
744 }
745 
746 
747 /*
748  * .func  write_trailer - write audit file trailer.
749  * .desc  Write an audit file trailer to the output stream. The finish
750  *	time for the trailer is the time of the last record written
751  *	to the stream.
752  * .ret	0 - succesful write.
753  * .ret	-1 - failed write - message printed.
754  */
755 static int
756 write_trailer(void)
757 {
758 	return (write_file_token(f_end));
759 }
760 
761 
762 /*
763  * .func rename_outfile - rename the outfile.
764  * .desc If the user used the -O flag they only gave us the suffix name
765  *	for the outfile. We have to add the time stamps to put the filename
766  *	in the proper audit file name format. The start time will be the time
767  *	of the first record in the file and the end time will be the time of
768  *	the last record in the file.
769  * .ret	0 - rename succesful.
770  * .ret	-1 - rename failed - message printed.
771  */
772 static int
773 rename_outfile(void)
774 {
775 	char	f_newfile[MAXFILELEN];
776 	char	buf1[15], buf2[15];
777 	char	*f_file, *f_nfile, *f_time, *f_name;
778 
779 	if (f_outfile != NULL) {
780 		/*
781 		 * Get string representations of start and end times.
782 		 */
783 		derive_str(f_start, buf1);
784 		derive_str(f_end, buf2);
785 
786 		f_nfile = f_time = f_newfile;	/* working copy */
787 		f_file = f_name = f_outfile;	/* their version */
788 		while (*f_file) {
789 			if (*f_file == '/') {	/* look for filename */
790 				f_time = f_nfile + 1;
791 				f_name = f_file + 1;
792 			}
793 			*f_nfile++ = *f_file++;	/* make copy of their version */
794 		}
795 		*f_time = '\0';
796 		/* start time goes first */
797 		(void) strcat(f_newfile, buf1);
798 		(void) strcat(f_newfile, ".");
799 		/* then the finish time */
800 		(void) strcat(f_newfile, buf2);
801 		(void) strcat(f_newfile, ".");
802 		/* and the name they gave us */
803 		(void) strcat(f_newfile, f_name);
804 
805 #if AUDIT_FILE
806 		(void) fprintf(stderr, "rename_outfile: <%s> --> <%s>\n",
807 			f_outfile, f_newfile);
808 #endif
809 
810 #if AUDIT_RENAME
811 		if (rename(f_outtemp, f_newfile) == -1) {
812 			(void) fprintf(stderr,
813 			    "%s rename of %s to %s failed.\n",
814 			    ar, f_outtemp, f_newfile);
815 			return (-1);
816 		}
817 		f_outfile = f_newfile;
818 #else
819 		if (rename(f_outtemp, f_outfile) == -1) {
820 			(void) fprintf(stderr,
821 			    gettext("%s rename of %s to %s failed.\n"),
822 			    ar, f_outtemp, f_outfile);
823 			return (-1);
824 		}
825 #endif
826 	}
827 	return (0);
828 }
829 
830 
831 /*
832  * .func open_outfile - open the outfile.
833  * .desc Open the outfile specified by the -O option. Assign it to the
834  *	the standard output. Get a unique temporary name to use so we
835  *	don't clobber an existing file.
836  * .ret	0 - no errors detected.
837  * .ret	-1 - errors in processing (message already printed).
838  */
839 static int
840 open_outfile(void)
841 {
842 	int	tmpfd = -1;
843 
844 	if (f_outfile != NULL) {
845 		f_outtemp = (char *)a_calloc(1, strlen(f_outfile) + 8);
846 		(void) strcpy(f_outtemp, f_outfile);
847 		(void) strcat(f_outtemp, "XXXXXX");
848 		if ((tmpfd = mkstemp(f_outtemp)) == -1) {
849 			(void) sprintf(errbuf,
850 			    gettext("%s couldn't create temporary file"), ar);
851 			perror(errbuf);
852 			return (-1);
853 		}
854 		(void) fflush(stdout);
855 		if (tmpfd != fileno(stdout)) {
856 			if ((dup2(tmpfd, fileno(stdout))) == -1) {
857 				(void) sprintf(errbuf,
858 				    gettext("%s can't assign %s to the "
859 				    "standard output"), ar, f_outfile);
860 				perror(errbuf);
861 				return (-1);
862 			}
863 			(void) close(tmpfd);
864 		}
865 	}
866 	return (0);
867 }
868 
869 
870 /*
871  * .func init_options - initialize the options.
872  * .desc Give initial and/or default values to some options.
873  * .call init_options();
874  * .arg	none.
875  * .ret	void.
876  */
877 static void
878 init_options(void)
879 {
880 	struct timeval tp;
881 	struct timezone tpz;
882 
883 	/*
884 	 * Get current time for general use.
885 	 */
886 	if (gettimeofday(&tp, &tpz) == -1)
887 		perror(gettext("auditreduce: initial getttimeofday failed"));
888 
889 	time_now = tp.tv_sec;		/* save for general use */
890 	f_start = 0;			/* first record time default */
891 	f_end = time_now;		/* last record time default */
892 	m_after = 0;			/* Jan 1, 1970 00:00:00 */
893 
894 	/*
895 	 * Setup initial size of audit_pcbs[].
896 	 */
897 	pcbsize = PCB_INITSIZE;		/* initial size of file-holding pcb's */
898 
899 	audit_pcbs = (audit_pcb_t *)a_calloc(pcbsize, sizeof (audit_pcb_t));
900 
901 	/* description of 'current' error */
902 	error_str = gettext("initial error");
903 
904 }
905 
906 
907 /*
908  * .func a_calloc - audit calloc.
909  * .desc Calloc with check for failure. This is called by all of the
910  *	places that want memory.
911  * .call ptr = a_calloc(nelem, size).
912  * .arg	nelem - number of elements to allocate.
913  * .arg	size - size of each element.
914  * .ret	ptr - ptr to allocated and zeroed memory.
915  * .ret	never - if calloc fails then we never return.
916  */
917 void	*
918 a_calloc(int nelem, size_t size)
919 {
920 	void	*ptr;
921 
922 	if ((ptr = calloc((unsigned)nelem, size)) == NULL) {
923 		perror(gettext("auditreduce: memory allocation failed"));
924 		exit(1);
925 	}
926 	return (ptr);
927 }
928 
929 
930 /*
931  * .func init_sig - initial signal catching.
932  *
933  * .desc
934  *	Setup the signal catcher to catch the SIGCHLD signal plus
935  *	"environmental" signals -- keyboard plus other externally
936  *	generated signals such as out of file space or cpu time.  If a
937  *	child exits with either a non-zero exit code or was killed by
938  *	a signal to it then we will also exit with a non-zero exit
939  *	code. In this way abnormal conditions can be passed up to the
940  *	root process and the entire run be halted. Also catch the int
941  *	and quit signals. Remove the output file since it is in an
942  *	inconsistent state.
943  * .call ret = init_sig().
944  * .arg none.
945  * .ret 0 - no errors detected.
946  * .ret -1 - signal failed (message printed).
947  */
948 static int
949 init_sig(void)
950 {
951 	if (signal(SIGCHLD, chld_handler) == SIG_ERR) {
952 		perror(gettext("auditreduce: SIGCHLD signal failed"));
953 		return (-1);
954 	}
955 
956 	if (signal(SIGHUP, int_handler) == SIG_ERR) {
957 		perror(gettext("auditreduce: SIGHUP signal failed"));
958 		return (-1);
959 	}
960 	if (signal(SIGINT, int_handler) == SIG_ERR) {
961 		perror(gettext("auditreduce: SIGINT signal failed"));
962 		return (-1);
963 	}
964 	if (signal(SIGQUIT, int_handler) == SIG_ERR) {
965 		perror(gettext("auditreduce: SIGQUIT signal failed"));
966 		return (-1);
967 	}
968 	if (signal(SIGABRT, int_handler) == SIG_ERR) {
969 		perror(gettext("auditreduce: SIGABRT signal failed"));
970 		return (-1);
971 	}
972 	if (signal(SIGTERM, int_handler) == SIG_ERR) {
973 		perror(gettext("auditreduce: SIGTERM signal failed"));
974 		return (-1);
975 	}
976 	if (signal(SIGPWR, int_handler) == SIG_ERR) {
977 		perror(gettext("auditreduce: SIGPWR signal failed"));
978 		return (-1);
979 	}
980 	if (signal(SIGXCPU, int_handler) == SIG_ERR) {
981 		perror(gettext("auditreduce: SIGXCPU signal failed"));
982 		return (-1);
983 	}
984 	if (signal(SIGXFSZ, int_handler) == SIG_ERR) {
985 		perror(gettext("auditreduce: SIGXFSZ signal failed"));
986 		return (-1);
987 	}
988 	if (signal(SIGSEGV, int_handler) == SIG_ERR) {
989 		perror(gettext("auditreduce: SIGSEGV signal failed"));
990 		return (-1);
991 	}
992 
993 	return (0);
994 }
995 
996 
997 /*
998  * .func chld_handler - handle child signals.
999  * .desc Catch the SIGCHLD signals. Remove the root process
1000  *	output file because it is in an inconsistent state.
1001  *	Print a message giving the signal number and/or return code
1002  *	of the child who caused the signal.
1003  * .ret	void.
1004  */
1005 /* ARGSUSED */
1006 void
1007 chld_handler(int sig)
1008 {
1009 	int	pid;
1010 	int	status;
1011 
1012 	/*
1013 	 * Get pid and reasons for cause of event.
1014 	 */
1015 	pid = wait(&status);
1016 
1017 	if (pid > 0) {
1018 		/*
1019 		 * If child received a signal or exited with a non-zero
1020 		 * exit status then print message and exit
1021 		 */
1022 		if ((WHIBYTE(status) == 0 && WLOBYTE(status) != 0) ||
1023 		    (WHIBYTE(status) != 0 && WLOBYTE(status) == 0)) {
1024 			(void) fprintf(stderr,
1025 			    gettext("%s abnormal child termination - "), ar);
1026 
1027 			if (WHIBYTE(status) == 0 && WLOBYTE(status) != 0) {
1028 				psignal(WLOBYTE(status), "signal");
1029 				if (WCOREDUMP(status))
1030 					(void) fprintf(stderr,
1031 					    gettext("core dumped\n"));
1032 			}
1033 
1034 			if (WHIBYTE(status) != 0 && WLOBYTE(status) == 0)
1035 				(void) fprintf(stderr, gettext(
1036 					"return code %d\n"),
1037 					WHIBYTE(status));
1038 
1039 			/*
1040 			 * Get rid of outfile - it is suspect.
1041 			 */
1042 			if (f_outfile != NULL) {
1043 				(void) close_outfile();
1044 				rm_outfile();
1045 			}
1046 			/*
1047 			 * Give statistical info that may be useful.
1048 			 */
1049 			audit_stats();
1050 
1051 			exit(1);
1052 		}
1053 	}
1054 }
1055 
1056 
1057 /*
1058  * .func	int_handler - handle quit/int signals.
1059  * .desc	Catch the keyboard and other environmental signals.
1060  *		Remove the root process output file because it is in
1061  *		an inconsistent state.
1062  * .ret	void.
1063  */
1064 /* ARGSUSED */
1065 void
1066 int_handler(int sig)
1067 {
1068 	if (getpid() == root_pid) {
1069 		(void) close_outfile();
1070 		rm_outfile();
1071 		exit(1);
1072 	}
1073 	/*
1074 	 * For a child process don't give an error exit or the
1075 	 * parent process will catch it with the chld_handler and
1076 	 * try to erase the outfile again.
1077 	 */
1078 	exit(0);
1079 }
1080