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

# auditxml takes the audit record description (.xml file) and
# generates the files needed for the C audit api. 

my $prog = $0; $prog =~ s|.*/||g;
my $usage = <<EOF;

Usage: $prog [options] <xml-input-file>
Options:
	-d	Enable debug output
	-e pfx	Internal event prefix (default: AUE)
	-i pfx	Interface prefix (default: adt)
		External event prefix is uppercase version of this string.
	-o dir	Output directory (default: current dir)

EOF

use auditxml;
use Getopt::Std;
use strict;

our $debug = 0; # normal use is to set via the file being parsed.
               # <debug set="on"/> or <debug set="off"/> or <debug/>
               # if the set attribute is omitted, debug state is toggled
               # Override with appDebug, but toggle won't do what you
               # want.
my $appDebug = 0; # used after return from "new auditxml";

our $errExit = 0;	# Exit code set for fatal errors
			# set to 3 for all errors.

# Process command-line options
our ($opt_d, $opt_e, $opt_i, $opt_o);
if (!getopts('de:i:o:') || $#ARGV != 0) {
    die $usage;
}
my $outdir = defined ($opt_o) ? $opt_o : ".";
my $pfx_adt = defined ($opt_i) ? lc($opt_i) : "adt";
my $pfx_ADT = uc($pfx_adt);
my $pfx_AUE = defined ($opt_e) ? uc($opt_e) : "AUE";

$appDebug = $opt_d;

my $uniLabel = "adr";
my $xlateUniLabelInc = 0;


# where everything comes from and where it goes:

my $xlateFile = "$outdir/${pfx_adt}_xlate.c";
my $headerFile = "$outdir/${pfx_adt}_event_N.h";

my $filename = $ARGV[0];  # input XML file
my $doc = new auditxml ($filename);
$filename =~ s|.*/||g;

my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
my $cYear = 1900 + $year;

$debug = $appDebug;

my $genNotice = "
 CDDL HEADER START

 The contents of this file are subject to the terms of the
 Common Development and Distribution License (the \"License\").
 You may not use this file except in compliance with the License.

 You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 or http://www.opensolaris.org/os/licensing.
 See the License for the specific language governing permissions
 and limitations under the License.

 When distributing Covered Code, include this CDDL HEADER in each
 file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 If applicable, add the following below this CDDL HEADER, with the
 fields enclosed by brackets \"[]\" replaced with your own identifying
 information: Portions Copyright [yyyy] [name of copyright owner]

 CDDL HEADER END

 Copyright (c) 2007, $cYear, Oracle and/or its affiliates. All rights reserved.

 DO NOT EDIT. This file is auto generated by the Solaris Audit
 system from $filename.

";

# trim leading/trailing newlines
$genNotice =~ s/^\n//s;
$genNotice =~ s/\n$//s;

my %xlateEventTable = ();
my @xlateTypeList = ();
my %xlateTypeList = ();
my %eventAPI = ();
my %eventExtra = ();
my %headers = ();
my %externalIdNo = ();
my @outputState = ();
my %nameTranslation = ();
my @xlateDefaults = ();
my %xlateDefault = ();
my %msg_list = ();

my $event;
while ($event = $doc->getNextEvent()) {
    my $eventId = $event->getId();
    my $eventHeader = $event->getHeader();
    my $idNo = $event->getIdNo();
    $externalIdNo{$eventId} = $idNo;
    addHeader($eventHeader) if defined ($eventHeader);
    my $super;
    my $omit = $event->getOmit();
    my $eventType = '';
    if ($super = $event->getSuperClass()) {
	$event = $super;
	$eventType = 'instance';
    } else {
	$eventType = $event->getType();
    }

    # header file for API use
    generateAPIFile($event, $eventId, $eventType, $eventHeader, $idNo)
        unless $omit eq 'always';

    # c file table for translation
    generateTableC($event, $eventId, $eventType, $eventHeader, $omit);
}

my $textList;
while ($textList = $doc->getNextMsgId()) {
    generateMsgLists($textList);  # enum -> text mappings
}

printTableC($xlateFile);
printAPIFile($headerFile, $doc);

exit $errExit;


sub printTableC {
    my $file = shift;

    unless (open(Cfile, ">$file")) {
	print STDERR "can't open output file ($file): $!\n";
	$errExit = 3;
	return;
    }

    my $notice = $genNotice;
    $notice =~ s/\n/\n */gs;
    $notice =~ s/\s+\n/\n/gs;
    print Cfile <<EOF;
/*
 * $notice
 */

#include <bsm/libbsm.h>
#include <adt_xlate.h>
#include <libintl.h>

EOF
    print Cfile "#ifndef _PRAUDIT\n";
    print Cfile "/* Internal data type definitions */\n\n";
    my $extDef;
    foreach $extDef (@xlateTypeList) {
      print Cfile "static $extDef\n";
    }
    @xlateTypeList = ();

    print Cfile "\n/* External event structure to internal event structure",
        " */\n\n";

    my @pointers = ();

    foreach my $eventId (sort keys %xlateEventTable) {
	if ($xlateEventTable{$eventId}) {
	    my ($ref1, $eventType, $firstToken, $eventHeader) =
	      @{$xlateEventTable{$eventId}};
	    my @entries = @$ref1;
	    my $entry;
	    my $entries = $#entries;
	    my $count = $entries + 1;
	    my $externalName = $nameTranslation{$eventId};
	    my $externalRoot = $externalName;
	    $externalRoot =~ s/${pfx_AUE}_//;
	    my $structName = "XX_$externalRoot";
	    my $root = $eventId;
	    $root =~ s/${pfx_AUE}_//;
	    my $externalId = $eventId;
	    $externalId =~ s/${pfx_AUE}_/${pfx_ADT}_/;

	    unless ($eventType eq 'generic') {
		print Cfile "static struct entry $structName\[$count\] = {\n";
		foreach $entry (@entries) {
		    if ($entries--) {
			$entry =~ s/EOL/,/;
		    }
		    else {
			$entry =~ s/EOL//;
		    }
		    $entry =~ s/selfReference/$structName/;
		    print Cfile "\t$entry\n";
		}
		print Cfile "};\n";

		print Cfile "static struct translation X_$externalRoot = {\n";
		push (@pointers, "X_$externalRoot");

		print Cfile "\t0,\n";   # tx_offsetsCalculated = 0
		print Cfile "\t$externalId,\n";
		print Cfile "\t$externalName,\n";

		print Cfile "\t$count,\n";
		print Cfile "\t&XX_$externalRoot\[$firstToken\],\n";
		print Cfile "\t&XX_$externalRoot\[0\]\n};\n";
	    }
	} else {
	    print STDERR "expected entry for $eventId but none found\n";
	    $errExit = 3;
	}
    }

    my $count = $#pointers + 2;
    print Cfile "adt_translation_t *${pfx_adt}_xlate_table[$count] = {\n";

    my $firstEvent = 1;
    foreach my $eventId (@pointers) {
	if ($firstEvent) {
	    $firstEvent = 0;
	}
	else {
	    print Cfile ",\n";
	}
	print Cfile "\t&$eventId";
    }
    print Cfile ",\n\tNULL\n};\n";

    # generate the Event preload() function

    print Cfile <<EOF;

void
${pfx_adt}_preload(au_event_t event_id, adt_event_data_t *event_data)
{
	switch (event_id) {
EOF

        foreach my $id (@xlateDefaults) {
		my $adtID = $id;
		$adtID =~ s/${pfx_AUE}/${pfx_ADT}/;

		print Cfile <<EOF;
	case $adtID:
EOF
		my @preloads = @{$xlateDefault{$id}};
		while (@preloads) {
			my $fieldName = shift @preloads;
			my $default = shift @preloads;
			$id =~ s/${pfx_AUE}_/${pfx_adt}_/;

			print Cfile <<EOF;
		event_data->$id.$fieldName = $default;
EOF
		}

		print Cfile <<EOF;
		break;
EOF
	}

    print Cfile <<EOF;
	default:
		break;
	}
}
#endif

EOF

    print Cfile "/* message lists */\n\n";
    my $listName;
    my @listName;
    foreach $listName (sort keys %msg_list) {
        my ($listRef, $headref) = @{$msg_list{$listName}};
	my ($header, $start, $public, $deprecated) = @$headref;

	my @listValue =  @$listRef;
	my $listValue;
	my $listLength = $#listValue + 1;

	$listName = 'NULL' if ($#listValue < 0);

        push (@listName, [$listName, $listLength - 1, $start, $public]);

	next if ($#listValue < 0);

	print Cfile "/* Deprecated message list */\n" if ($deprecated);
	print Cfile "static char *msg_$listName\[$listLength] = {\n";

	my $ffirst = 1;
	foreach $listValue (@listValue) {
	    print Cfile ",\n" unless $ffirst;
	    $ffirst = 0;
	    my ($id, $text) = split(/\s*::\s*/, $listValue);
	    if ($text) {
	        print Cfile "\t\"$text\"";
	    }
	    else {
	        print Cfile "\tNULL";
	    }
	}
	print Cfile "\n};\n";
    }

    if ($#listName >= 0) {
	print Cfile "\nstruct msg_text ${pfx_adt}_msg_text[", $#listName + 1,
			"] = {\n";
	my $ffirst = 1;
	foreach $listName (@listName) {
            my ($name, $max, $start) = @$listName;
	    $start = -$start if $start;
            print Cfile ",\n" unless $ffirst;
	    $ffirst = 0;
	    $name = "msg_$name" if ($name ne 'NULL');
            print Cfile "\t{0, $max, $name, $start}";
	}
	print Cfile "\n};\n";
    }

    close Cfile;
}

sub printAPIFile {
    my $file = shift;
    my $xmlDoc = shift;

    my @Hfile;
    @Hfile = openHeaderFiles($file);

    my $notice = $genNotice;
    $notice =~ s/\n/\n */gs;
    $notice =~ s/\s+\n/\n/gs;

    foreach my $header (keys %headers) {
    	next unless $Hfile[$header];
	*Hfile = $Hfile[$header];
	my $include = "adt.h";
	my $adt_event_n = "_${pfx_ADT}_EVENT_H";
	if ($header > 0) {
	    $include = "${pfx_adt}_event.h";
	    $adt_event_n = "_${pfx_ADT}_EVENT_".$header."_H";
	}
	print Hfile <<EOF;
/*
 * $notice
 */

#ifndef $adt_event_n
#define	$adt_event_n

#include <bsm/$include>

#ifdef	__cplusplus
extern "C" {
#endif

/*
 * adt_put_event() status values.  Positive values are for kernel-generated
 * failure, -1 for user-space.  For ADT_SUCCESS, the adt_put_event() return_val
 * is not used; the convention is to set it to ADT_SUCCESS.
 */
#define	ADT_SUCCESS	0
#define	ADT_FAILURE	-1

EOF
    }

    foreach my $listName (sort keys %msg_list) {
	my $shortName = uc $listName;
	$shortName =~ s/_TEXT//;

        my ($listRef, $headref) = @{$msg_list{$listName}};
	my ($header, $start, $public, $deprecated) = @$headref;
	next unless $Hfile[$header];
	*Hfile = $Hfile[$header];

	print Hfile "/* Deprecated message list */\n" if $deprecated;
	print Hfile "#define\t${pfx_ADT}_$shortName\t$start\n" if $start;

	my @listValue =  @$listRef;
	next unless ($#listValue >= 0);
	print Hfile "enum\t${pfx_adt}_$listName", " {\n";

	my $listValue;
	my $i = 0;
	my $j = $#listValue;
	my $comma = ',';
	foreach $listValue (@listValue) {
	    my ($id, $text) = split(/\s*::\s*/, $listValue);
	    $comma = '' if $i++ == $j;
	    if ($start) {
		$start = " = $start$comma";
	    } else {
	        $start = "$comma\t";
	    }
	    $text = "(no token will be generated)" unless $text;
	    my $line = "\t${pfx_ADT}_$shortName"."_$id$start\t/* ";
	    # ensure whole line does not exceed 80 chars
	    my $eline = $line.$text;
	    #expand tabs
	    1 while $eline =~ s/\t+/' ' x (length($&) * 8 - length($`) % 8)/e;
	    if ((length($eline) > 77) && ($line =~ /\t\t/)) {
	    	# 77 = 80 - length(" */")
		# strip off double tab so that comment can be longer
		$line =~ s/\t\t/\t/;
		# shorten eline; don't mind where the spaces are removed, it is
		# only $eline length which matters
		$eline =~ s/ {8}//; 
	    }
	    if (length($eline) > 77) { # 80 - length(" */")
	    	# here we use negative length in substr to leave off from the
		# right side; 74 = 77 - length("...")
	    	$line .= substr($text, 0, 74 - length($eline));
		# strip off part of last word (already cut)
		$line =~ s/\s(\S+)$/ /;
		$line .= "...";
	    } else {
	    	$line .= $text;
	    }
	    print Hfile "$line */\n";
	    $start = '';
	}
	print Hfile "};\n";
    }

    # generate defines for external event names

    foreach my $eventId (sort keys %eventAPI) {
        my ($header, $idNo) = @{$eventExtra{$eventId}};
	unless (defined ($header)) {
	    print STDERR "missing header selection for $eventId\n";
	    $errExit = 3;
	    next;
	}
	*Hfile = $Hfile[$header];
	next unless $Hfile[$header];

	my $l = length($eventId) + 8; # label plus preceding #define\t
	$l = 5 - int(($l + 8)/8);
	$l = 1 if $l < 1;
	my $tab = "\t" x $l;

	unless ($idNo) {
            print STDERR "missing id number for $eventId\n";
	    $errExit = 3;
	}

	$eventId =~ s/${pfx_AUE}_/${pfx_ADT}_/;
	print Hfile "#define\t$eventId$tab$idNo\n";
    }


    # generate per-event structures

    foreach my $eventId (sort keys %eventAPI) {
        my ($header, $idNo) = @{$eventExtra{$eventId}};
	my $dataId = $eventId;
	$dataId =~ s/^${pfx_AUE}_/${pfx_adt}_/;
	unless(defined ($header)) {
	    print STDERR "$eventId is missing the header assignment\n";
	    $errExit = 3;
	    next;
	}
	*Hfile = $Hfile[$header];
	next unless $Hfile[$header];

	my $externalId = $eventId;
	$externalId =~ s/${pfx_AUE}_/${pfx_ADT}_/;

	print Hfile "\nstruct $dataId {\t/* $externalId */\n";

	my @entries = @{$eventAPI{$eventId}};
	my $entry;
	if ($#entries < 0) {
	    print Hfile "\tint\tdummy;\t/* not used */\n";
	} else {
	    foreach $entry (@entries) {
		$entry =~ s/termid/adt_termid_t/;
		print Hfile "\t$entry\n";
	    }
	}
	print Hfile "};\n";
	$eventId =~ s/^${pfx_AUE}_/${pfx_adt}_/;
	print Hfile "typedef struct $dataId $eventId","_t;\n";
    }

    foreach my $header (sort keys %headers) {
	$outputState[$header] = 0;
    }
    
    foreach my $eventId (sort keys %eventAPI) {
        my ($header, $idNo) = @{$eventExtra{$eventId}};
	unless(defined ($header)) {
	    # don't print duplicate error message
	    next;
	}
	*Hfile = $Hfile[$header];
	next unless $Hfile[$header];
	if ($outputState[$header] == 0) {
	    $outputState[$header] = 1;
	    my $suffix = '';
	    $suffix = "_$header" if $header;
	    print Hfile "\nunion adt_event_data$suffix {\n";
	}
        my $elementName = $eventId;
	$elementName =~ s/^${pfx_AUE}_/${pfx_adt}_/;
	$eventId =~ s/^${pfx_AUE}_/${pfx_adt}_/;
	$elementName =~ s/_t$//;
        
	print Hfile "\t\t$eventId","_t\t$elementName;\n";
    }
    foreach my $header (sort keys %headers) {
	if ($outputState[$header]) {
	    *Hfile = $Hfile[$header];
	    next unless $Hfile[$header];
	    print Hfile "};\n";
	}
    }
    foreach my $header (keys %headers) {
    	next unless $Hfile[$header];
	*Hfile = $Hfile[$header];
	my $adt_event_n = "_${pfx_ADT}_EVENT_H";
	if ($header > 0) {
	    $adt_event_n = "_${pfx_ADT}_EVENT_".$header."_H";
	}
	print Hfile <<EOF;


#ifndef	${pfx_ADT}_PRIVATE
#define	${pfx_ADT}_PRIVATE

/*
 * These interfaces are project private and will change without
 * notice as needed for the Solaris Audit project.
 */

extern	void	adt_get_auid(const adt_session_data_t *, au_id_t *);
extern	void	adt_set_auid(const adt_session_data_t *, const au_id_t);

extern	void	adt_get_mask(const adt_session_data_t *, au_mask_t *);
extern	void	adt_set_mask(const adt_session_data_t *, const au_mask_t *);

extern	void	adt_get_termid(const adt_session_data_t *, au_tid_addr_t *);
extern	void	adt_set_termid(const adt_session_data_t *,
    const au_tid_addr_t *);

extern	void	adt_get_asid(const adt_session_data_t *, au_asid_t *);
extern	void	adt_set_asid(const adt_session_data_t *, const au_asid_t);
extern	au_asid_t adt_get_unique_id(au_id_t);
extern	void	adt_load_table(const adt_session_data_t *, adt_translation_t **,
    void (*preload)(au_event_t, adt_event_data_t *));

extern	void	${pfx_adt}_preload(au_event_t, adt_event_data_t *);

extern adt_translation_t *${pfx_adt}_xlate_table[];

#endif

#ifdef	__cplusplus
}
#endif

#endif	/* $adt_event_n */
EOF
    }
    closeHeaderFiles(@Hfile);
}

sub generateTableC {
    my $event = shift;
    my $eventId = shift;
    my $eventType = shift;
    my $eventHeader = shift;
    my $omit = shift;

    my %tokenType = (
	#
	#	tokenTypes are the ones that are actually defined
	#	for use in adt.xml audit records
	#

	#	  'acl'			=> 'AUT_ACL',		# not defined
	#	  'arbitrary'		=> 'AUT_ARBITRARY',	# not defined
		  'arg'			=> 'AUT_ARG',
	#	  'atom'		=> 'AUT_XATOM',		# not defined
		  'attr'		=> 'AUT_ATTR64',
	#	  'colormap'		=> 'AUT_XCOLORMAP',	# not defined
		  'command'		=> 'AUT_CMD',
		  'command_alt'		=> 'ADT_CMD_ALT',	# dummy token id
	#	  'cursor'		=> 'AUT_XCURSOR',	# not defined
	#	  'date'		=> 'AUT_TEXT',		# not used
	#	  'exec_args'   	=> 'AUT_EXEC_ARGS',	# not defined
	#	  'exec_env'    	=> 'AUT_EXEC_ENV',	# not defined
	#	  'exit'        	=> 'AUT_EXIT',		# not defined
	#	  'font'        	=> 'AUT_XFONT',		# not defined
		  'fmri'        	=> 'AUT_FMRI',
	#	  'gc'			=> 'AUT_XGC',		# not defined
	#	  'groups'      	=> 'AUT_GROUPS',	# not defined
	#	  'header'      	=> 'AUT_HEADER',	# not defined
		  'in_peer'     	=> 'ADT_IN_PEER',	# dummy token id
		  'in_remote'     	=> 'ADT_IN_REMOTE',	# dummy token id
	#	  'ipc'         	=> 'AUT_IPC',		# not defined
	#	  'ipc_perm'    	=> 'AUT_IPC_PERM',	# not defined
		  'iport'		=> 'AUT_IPORT',
		  'label'		=> 'AUT_LABEL',
		  'newgroups'   	=> 'AUT_NEWGROUPS',
	#	  'opaque'      	=> 'AUT_OPAQUE',	# not defined
		  'path'        	=> 'AUT_PATH',
		  'path_list'		=> '-AUT_PATH',		# dummy token id
	#	  'pixmap'      	=> 'AUT_XPIXMAP',	# not defined
		  'process'     	=> 'AUT_PROCESS',
		  'priv_effective'	=> 'ADT_AUT_PRIV_E',	# dummy token id
		  'priv_limit'		=> 'ADT_AUT_PRIV_L', 	# dummy token id
		  'priv_inherit'	=> 'ADT_AUT_PRIV_I',	# dummy token id
		  'return'      	=> 'AUT_RETURN',
	#	  'seq'         	=> 'AUT_SEQ',		# not defined
	#	  'socket'      	=> 'AUT_SOCKET',	# not defined
	#	  'socket-inet' 	=> 'AUT_SOCKET_INET',
		  'subject'     	=> 'AUT_SUBJECT',
		  'text'        	=> 'AUT_TEXT',
		  'tid'          	=> 'AUT_TID',
	#	  'trailer'     	=> 'AUT_TRAILER',	# not defined
		  'uauth'		=> 'AUT_UAUTH',
		  'user'		=> 'AUT_USER',
		  'xclient'		=> 'AUT_XCLIENT',
	#	  'xobj'	     	=> 'AUT_XOBJ',		# not defined
	#	  'xproto'	     	=> 'AUT_XPROTO',	# not defined
		  'zonename'		=> 'AUT_ZONENAME'
		 );

    my @xlateEntryList = ();

    my $external = $event->getExternal();
    my $internal = $event->getInternal();

    unless ($external) {
	print STDERR "No external object captured for event $eventId\n";
	$errExit = 3;
	return;
    }
    if ($eventType) {
	$nameTranslation{$eventId} = $eventId;
    } else {
	$nameTranslation{$eventId} = $external->getInternalName();
    }
    unless ($internal) {
	print STDERR "No internal object captured for event $eventId\n";
	$errExit = 3;
	return;
    }
    my @entryRef = $internal->getEntries();
    my $entryRef;
    my @tokenOrder = ();
    my $firstTokenIndex = 0; # djdj not used yet, djdj BUG!
    			     # needs to be used by translate table

    if ($internal->isReorder()) { # prescan the entry list to get the token order
      my @inputOrder;
      foreach $entryRef (@entryRef) {
	my ($intEntry, $entry) = @$entryRef;
	push (@inputOrder, $intEntry->getAttr('order'));
      }

      my $i; # walk down the inputOrder list once
      my $k = 1; # discover next in line
      my $l = 0; # who should point to next in line
      for ($i = 0; $i <= $#inputOrder; $i++) {
	my $j;
	for ($j = 0; $j <= $#inputOrder; $j++) {
	  if ($k == $inputOrder[$j]) {
	    if ($k == 1) {
	        $firstTokenIndex = $j;
	    } else {
	        $tokenOrder[$l] = "&(selfReference[$j])";
	    }
	    $l = $j;
	    last;
	  }
	}
	$k++;
      }
      $tokenOrder[$l] = 'NULL';
    }
    else { # default order -- input order same as output
      my $i;
      my $j;
      for ($i = 0; $i < $#entryRef; $i++) {
	my $j = $i + 1;
	$tokenOrder[$i] = "&(selfReference[$j])";
      }
      $tokenOrder[$#entryRef] = 'NULL';
    }

    my $sequence = 0;
    foreach $entryRef (@entryRef) {
      my ($intEntry, $entry) = @$entryRef;
      my $entryId = $entry->getAttr('id');

      my ($extEntry, $unusedEntry, $tokenId) =
	$external->getEntry($entryId);
      my $opt = $extEntry->getAttr('opt');

      if ($opt eq 'none') {
	if (defined ($doc->getToken($tokenId))) {
	  if (defined ($tokenType{$tokenId})) {
	    $tokenId = $tokenType{$tokenId};
	  }
	  else {
	    print STDERR "token id $tokenId not implemented\n";
	    $errExit = 3;
	  }
	}
	else {
	  print STDERR "token = $tokenId is undefined\n";
	  $tokenId = 'error';
	  $errExit = 3;
	}
	my ($xlate, $jni) =
	  formatTableEntry ('', $tokenId, $eventId, '', 0, 0,
			    $tokenOrder[$sequence], 'NULL', $omit);
	push (@xlateEntryList, $xlate);
      }
      else {
	my $dataType = $extEntry->getAttr('type');
	$dataType =~ s/\s+//g;   # remove blanks (char * => char*)

	my $enumGroup = '';
	if ($dataType =~ /^msg/i) {
	    $enumGroup = $dataType;
	    $enumGroup =~ s/^msg\s*//i;
	    $enumGroup = "${pfx_adt}_" . $enumGroup;
	}
	my $required = ($opt eq 'required') ? 1 : 0;
	my $tsol = 0;
	my $tokenId = $intEntry->getAttr('token');
	my $token;
	my $tokenName;
	my $tokenFormat = $intEntry->getAttr('format');
	if (defined ($tokenFormat)) {
	  $tokenFormat = "\"$tokenFormat\"";
	}
	else {
	  $tokenFormat = 'NULL';
	}
	
	if (defined ($token = $doc->getToken($tokenId))) {
	  $tsol = (lc $token->getUsage() eq 'tsol') ? 1 : 0;
	  if (defined ($tokenType{$tokenId})) {
	    $tokenName = $tokenType{$tokenId};
	  }
	  else {
	    print STDERR "token id $tokenId not implemented\n";
	    $errExit = 3;
	  }
	}
	else {
	  print STDERR 
	    "$tokenId is an unimplemented token ($entryId in $eventId)\n";
	  $tokenName = 'AUT_TEXT';
	  $errExit = 3;
	}
	my ($xlate, $jni) =
	  formatTableEntry($entryId, $tokenName, $eventId, $dataType, $required,
			   $tsol, $tokenOrder[$sequence], $tokenFormat,
			   $enumGroup, $omit);
	push (@xlateEntryList, $xlate);
      }
      $sequence++;
    }
    $xlateEventTable{$eventId} = [\@xlateEntryList, $eventType, $firstTokenIndex,
				 $eventHeader];
}

sub formatTableEntry {
    my ($id, $token, $eventId, $type, $required, $tsol, $sequence, $format,
	$enumGroup, $omitEntry) = @_;

    $omitEntry = defined ($omitEntry) ? $omitEntry : "";

    # does this map belong in the xml source?  (at least the defaults?)
    # fill in the default value only if it is other than zero.
    #		      base type		    adt name,	default value
    my %entryDef = ( 'au_asid_t'       	=> ['ADT_UINT32',	''],
		     'uint_t'		=> ['ADT_UINT32',      	''],
		     'int'		=> ['ADT_INT',		''],
		     'int32_t'		=> ['ADT_INT32',	''],
		     'uid_t'		=> ['ADT_UID',		'AU_NOAUDITID'],
		     'gid_t'		=> ['ADT_GID',		'AU_NOAUDITID'],
		     'uid_t*'		=> ['ADT_UIDSTAR',	''],
		     'gid_t*'		=> ['ADT_GIDSTAR',	''],
		     'char'		=> ['ADT_CHAR',		''],
		     'char*'		=> ['ADT_CHARSTAR',	''],
		     'char**'		=> ['ADT_CHAR2STAR',	''],
		     'long'		=> ['ADT_LONG',		''],
		     'pid_t'		=> ['ADT_PID',		''],
		     'priv_set_t*'	=> ['ADT_PRIVSTAR',	''],
		     'ulong_t'		=> ['ADT_ULONG',	''],
		     'uint16_t',	=> ['ADT_UINT16',	''],
		     'uint32_t'		=> ['ADT_UINT32',	''],
		     'uint32_t*'	=> ['ADT_UINT32STAR',	''],
		     'uint32_t[]'	=> ['ADT_UINT32ARRAY',  ''],
		     'uint64_t'		=> ['ADT_UINT64',	''],
		     'uint64_t*'	=> ['ADT_UINT64STAR',	''],
		     'm_label_t*'	=> ['ADT_MLABELSTAR',	''],
		     'fd_t'		=> ['ADT_FD',		'-1'],
		     'adt_stat_t*'	=> ['ADT_STATSTAR',	''],
		    );
    my $xlateLabel = $uniLabel.$xlateUniLabelInc;
    my $xlateLabelInc = 0;
    my $xlateLine = '';
    my @jniLine = ();

	# the list handling should be a simple loop with a loop of one
        # falling out naturally.

    unless ($type =~ /,/) {	# if list, then generate sequence of entries
      my $dataType;
      my $dataSize;
      my $xlateLabelRef = '';

      my $arraySize = '';
      $arraySize = $1 if ($type =~ s/\[(\d+)\]/[]/);

      my $entryType = ${$entryDef{$type}}[0];

      my @xlateType = ();	# for adt_xlate.c
      my $typeCount = 1;

      if ($entryType) {
	$dataType = $entryType;
	$type =~ s/([^*]+)\s*(\*+)/$1 $2/;
	$type =~ s/\[\]//;
	$dataSize = "sizeof ($type)";
	if ($arraySize) {
		$dataSize = "$arraySize * " . $dataSize;
	}
	$xlateLine = "{{$dataType, $dataSize}}";
	push (@jniLine, [$id, $dataType, $format, $enumGroup, $required]);
      } elsif ($type eq '') {
	  $xlateLabelRef = 'NULL';
      } elsif ($type =~ /^msg/i) {
	$type =~ s/^msg//i;
	$dataType = 'ADT_MSG';
	my $dataEnum = 'ADT_LIST_' . uc $type;
	$xlateLine = "{{$dataType, $dataEnum}}";
	push (@jniLine, [$id, $dataType, $format, $enumGroup, $required]);
      } elsif ($type =~ /time_t/i) {
	$dataType = 'ADT_DATE';
	$dataSize = "sizeof (time_t)";
	$xlateLine = "{{$dataType, $dataSize}}";
	push (@jniLine, [$id, $dataType, $format, $enumGroup, $required]);
      } elsif ($type =~ /termid/i) {
	$dataType = 'ADT_TERMIDSTAR';
	$dataSize = "sizeof (au_tid_addr_t *)";
	$xlateLine = "{{$dataType, $dataSize}}";
	push (@jniLine, [$id, $dataType, $format, $enumGroup, $required]);
      } elsif (uc $omitEntry eq 'JNI') {
	$xlateLabelRef = 'NULL';
      } else {
	print STDERR "$type is not an implemented data type\n";
	$xlateLabelRef = 'NULL';
	$errExit = 3;
      }
      if ($xlateLine && !($xlateTypeList{$xlateLine})) {
	$xlateTypeList{$xlateLine} = $xlateLabel;
	push (@xlateTypeList, "datadef\t$xlateLabel\[1\] =\t$xlateLine;");
	$xlateLabelInc = 1;
      } else {
	$xlateLabel = $xlateTypeList{$xlateLine};
      }
      $xlateLabelRef = '&' . $xlateLabel . '[0]'
	unless $xlateLabelRef eq 'NULL';

      # "EOL" is where a comma should go unless end of list
      $xlateLine = "{$token,\t1,\t$xlateLabelRef,\t$sequence,\n" .
	  "\t\t0,\t$required,\t$tsol,\t$format}EOL";
      
      if (uc $omitEntry ne 'ALWAYS' && ${$entryDef{$type}}[1]) {
	  my @list = ();
	  if ($xlateDefault{$eventId}) {
	      @list = @{$xlateDefault{$eventId}};
	  } else {
	      push (@xlateDefaults, $eventId);
	  }
	  push (@list, $id, ${$entryDef{$type}}[1]);
	  $xlateDefault{$eventId} = \@list;
      }
    } else {	# is a list
      my @type = split(/,/, $type);
      my @arraySize = ();
      my @id   = split(/,/, $id);
      my @jniId  = @id;
      my $dataType;
      my $typeCount = ($#type + 1);
      my @xlateType = ();
      my @default = ();

      foreach my $dtype (@type) {
	my $jniId = shift @jniId;
	my $id = shift @id;
	my $arraySize = '';
	$arraySize = $1 if ($dtype =~ s/\[(\d+)\]/[]/);

	my $entryType = ${$entryDef{$dtype}}[0];
	if ($entryType) {
	  my $type = $dtype;
	  $type =~ s/([^*]+)\s*(\*+)/$1 $2/;
	  $type =~ s/\[\]//;

	  my $sizeString = "sizeof";
	  $sizeString = "$arraySize * " . $sizeString if $arraySize;
	  push (@xlateType, "\{$entryType, $sizeString ($type)\}");
	  push (@jniLine, [$jniId, $entryType, $format, $enumGroup, $required]);
	} elsif ($type =~ /^msg/i) {
	  $type =~ s/^msg//i;
	  $dataType = 'ADT_MSG';
	  my $dataEnum = 'ADT_LIST_' . uc $type;
	  push (@xlateType, "\{$dataType, $dataEnum\}};");
	  push (@jniLine, [$jniId, $dataType, $format, $enumGroup, $required]);
	} elsif ($type =~ /time_t/i) {
	  $dataType = 'ADT_DATE';
	  push (@xlateType, "\{$entryType, sizeof ($type)\}");
	  push (@jniLine, [$jniId, $entryType, $format, $enumGroup, $required]);
	} elsif ($type =~ /termid/i) {
	  $dataType = 'ADT_TERMIDSTAR';
	  push (@xlateType, "\{$dataType, sizeof (au_tid_addr_t *)\}");
	  push (@jniLine, [$jniId, $dataType, $format, $enumGroup, $required]);
	} elsif (uc $omitEntry eq 'JNI') {
	  # nothing to do.
	} else {
	  print STDERR "$dtype is not an implemented data type\n";
	  $errExit = 3;
	}
	if (uc $omitEntry ne 'ALWAYS' && ${$entryDef{$dtype}}[1]) {
	  push (@default, $id, ${$entryDef{$dtype}}[1]);
	}
      }
      my $xlateArray = "\[$typeCount\] =\t{" . join(",\n\t\t\t\t", @xlateType) . "};";
      
      unless ($xlateTypeList{$xlateArray}) {
	$xlateTypeList{$xlateArray} = $xlateLabel;
	$xlateArray = "datadef\t$xlateLabel" . $xlateArray;
	push (@xlateTypeList, $xlateArray);
	$xlateLabelInc = 1;
      } else {
	$xlateLabel = $xlateTypeList{$xlateArray};
      }
      $xlateLine =
	"{$token,\t$typeCount,\t&$xlateLabel\[0\],\t$sequence,\n" .
        "\t\t0,\t$required,\t$tsol,\t$format}EOL";
      if (@default) {
	  my @list = ();
	  if ($xlateDefault{$eventId}) {
	      @list = @{$xlateDefault{$eventId}};
	  } else {
	      push (@xlateDefaults, $eventId);
	  }
	  push (@list, @default);
	  $xlateDefault{$eventId} = \@list;
      }
    }
    $xlateUniLabelInc++ if $xlateLabelInc;
    return ($xlateLine, \@jniLine);
}

sub generateAPIFile {
    my $event = shift;
    my $eventId = shift;
    my $eventType = shift;
    my $eventHeader = shift;
    my $idNo = shift;

    my @entryList = ();

    my $external = $event->getExternal();

    if ($eventType && $debug) {
	print STDERR "event $eventId is of type $eventType\n";
	$errExit = 3;
    }

    return unless $external;

    my ($extEntry, $entry, $tokenId, $format);
    while (($extEntry, $entry, $tokenId, $format) = $external->getNextEntry()) {
	last unless $entry;
	my $entryId = $entry->getAttr('id');

	unless (defined $entryId) {
	    print STDERR "undefined entry id for external $eventId\n";
	    $errExit = 3;
	    next;
	}
	my $option = $extEntry->getAttr('opt');
	next if ($option eq 'none');

	if (defined (my $token = $doc->getToken($tokenId))) {
	  $option = 'Trusted Solaris only'
	    if (lc $token->getUsage() eq 'tsol') ? 1 : 0;
	}
	$option .= " (format: $format)" if $format;

	my $dataType = $extEntry->getAttr('type');
	unless (defined $dataType) {
	  print STDERR "no type defined for external tag for $eventId\n";
	  $dataType = "error";
	  $errExit = 3;
	}

	my $comment = $entry->getContent();

	if (($dataType =~ /,/) || ($entryId =~ /,/)) {
	  my @type = split(/\s*,\s*/, $dataType);
	  my @id   = split(/\s*,\s*/, $entryId);
	  if ($#type != $#id) {
	    print STDERR
	      "number of data types ($dataType) does not match number of ids ",
	      "($entryId)  for event $eventId\n";
	    $errExit = 3;
	    if ($#type < $#id) {
	      $#id = $#type;
	    }
	    else {
	      $#type = $#id;
	    }
	  }

	  my $i;
	  my $line = '';
	  $line = "/* $comment */\n\t" if defined $comment;
	  for ($i = 0; $i <= $#type; $i++) {
	    my ($primitive, $dereference) =
	        ($type[$i] =~ /([^\*]+)\s*(\**)/);
	    $id[$i] .= $1 if ($primitive =~ s/(\[\d+\])//);
	    $line .= "$primitive\t$dereference$id[$i];\t/*  $option  */";
	    push (@entryList, $line);
	    $line = '';
	  }
	}
	else {
	  my $line = '';
	  $line = "/* $comment */\n\t" if defined $comment;
	  if ($dataType =~ /^msg/i) {
	      $dataType =~ s/^msg\s*//i;
	      $line .= "enum ${pfx_adt}_$dataType" . "\t$entryId;\t/*  $option  */";
	  }
	  elsif ($dataType =~ /time_t/i) {
	      $line .= "time_t\t$entryId;\t/* $option */";
	  }
	  else {
	    my ($primitive, $dereference) =
	        ($dataType =~ /([^\*]+)\s*(\**)/);
	    $entryId .= $1 if ($primitive =~ s/(\[\d+\])//);
	    $line .= "$primitive\t$dereference$entryId;\t/* $option */";
	  }
	  push (@entryList, $line);
	}
    }
    $eventExtra{$eventId} = [$eventHeader, $idNo];
    $eventAPI{$eventId} = \@entryList;
}

sub generateMsgLists {
    my $textList = shift;

    my $textName = $textList->getId();
    my $header = $textList->getHeader();
    my $start = $textList->getMsgStart();
    my $public = $textList->getMsgPublic();
    my $deprecated = $textList->getDeprecated();

    addHeader($header);
    print "$textName starts at $start\n" if $debug;

    my $entry;
    my @entry;
    while ($entry = $textList->getNextMsg()) {
        if ($debug) {
	    my ($id, $text) = split(/\s*::\s*/, $entry);
	    print "   $id = $text\n";
	}
	unshift (@entry, $entry);
    }
    $msg_list{$textName} =
	[\@entry, [$header, $start, $public, $deprecated]];
}

sub addHeader {
    my $header_index = shift;

    die "invalid adt_event_N.h index: $header_index\n"
        unless ($header_index =~ /^\d+$/);

    $headers{$header_index} = $header_index;
}

# $header = 0 is a special case; it is for adt_event.h
# $header > 0 creates adt_event_N.h, where N = $header

sub openHeaderFiles {
    my $outfile = shift;	# path to an adt_event_N.h file

    my $header;
    my @Hfile = (); # potentially sparse array of file handles
    my @HfileName = (); # parallel array to Hfile, file name (not path)
    foreach $header (sort keys %headers) {
        my $file = $outfile;
	if ($header > 0) {
	    $file =~ s/_N/_$header/;
	} else {
	    $file =~ s/_N//;
	}
	unless (open($Hfile[$header], ">$file")) {
	    print STDERR "can't open output ($file): $!\n";
	    $HfileName[$header] = '';
	    $Hfile[$header] = '';
	    $errExit = 3;
	} else {
	    my @tmp = split(/\//, $file);
	    $HfileName[$header] = $tmp[$#tmp];
	}
    }
    return (@Hfile);
}

sub closeHeaderFiles {
    my @Hfile = @_;

    my $header;
    foreach $header (sort keys %headers) {
	close $Hfile[$header] if $Hfile[$header];
    }
}
