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