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