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