#!/usr/bin/perl

#
# miniRPC - TCP RPC library with asynchronous operations
#
# Copyright (C) 2007-2008 Carnegie Mellon University
#
# This code is distributed "AS IS" without warranty of any kind under the
# terms of the GNU Lesser General Public License version 2.1, as shown in
# the file COPYING.  The output of minirpcgen is not covered by this license,
# and may be distributed, with or without modification, under the terms of
# your choice.
#

use strict;
use warnings;
use File::Spec;
use Getopt::Long;
use Text::Wrap;

my $rpcgen = "/usr/bin/rpcgen";
my $version = "0.3.2";
my $version_code = "197120";

our $infile;
our $base;
our $basepath;
our $defines;
our %outfiles;
our %types;
our %procSets;
our %procNames;

END {
	my $status = $?;
	my $file;
	foreach $file (values %outfiles) {
		unlink("$file.$$");
	}
	# Special case: temporary file which is never promoted to a real
	# output file
	unlink("$basepath.x.$$")
		if $basepath;
	$? = $status;
}

sub parseErr {
	my $file = shift;
	my $line = shift;
	my $msg = shift;

	print(STDERR "$file, line $line: $msg\n");
	exit 1;
}

sub openFile {
	my $handle = shift;
	my $name = shift;
	my $disclaim = shift;

	open($handle, ">", "$name.$$") || die "Can't open $name.$$";
	$outfiles{$handle} = $name;
	print $handle "/* AUTOGENERATED FILE -- DO NOT EDIT */\n\n";

	if ($disclaim) {
		print $handle <<EOF;
/* This file was generated by minirpcgen $version.  The authors of
 * minirpcgen do not claim any copyright in the contents of this file.  You
 * may distribute it, with or without modification, under the terms of your
 * choice.
 */

EOF
	}
}

sub closeFiles {
	my $handle;
	my $file;

	while (($handle, $file) = each %outfiles) {
		close($handle);
		rename("$file.$$", $file) || die "Couldn't write $file";
		delete $outfiles{$handle};
	}
}

sub wrapc {
	my $input = shift;

	my $output;
	my $line;

	local($Text::Wrap::columns) = 80;
	local($Text::Wrap::huge) = "overflow";
	# Only break at argument boundaries
	local($Text::Wrap::break) = qr/, /;
	# ...and re-add the comma afterward.  Newer versions of Text::Wrap use
	# separator2 for newly-added line breaks; older versions just use
	# separator.  Test is formatted strangely since local() declaration
	# can't be made inside a block
	local($Text::Wrap::separator2) = ",\n"
		if defined($Text::Wrap::separator2);
	local($Text::Wrap::separator) = ",\n"
		if !defined($Text::Wrap::separator2);

	foreach $line (split(/\n/, $input)) {
		$output .= Text::Wrap::wrap("", "\t\t\t", $line) . "\n";
	}
	return $output;
}

sub argument {
	my $type = shift;
	my $var = shift;

	if ($type ne "void") {
		return ", $type $var";
	} else {
		return "";
	}
}

sub parameter {
	my $type = shift;
	my $param = shift;

	if ($type ne "void") {
		return $param;
	} else {
		return "NULL";
	}
}

sub opt_parameter {
	my $type = shift;
	my $param = shift;

	if ($type ne "void") {
		return ", $param";
	} else {
		return "";
	}
}

sub typesize {
	my $type = shift;

	if ($type ne "void") {
		return "sizeof($type)";
	} else {
		return "0";
	}
}

sub antirole {
	my $role = shift;

	return ($role eq "client") ? "server" : "client";
}

# Sort hash keys numerically: 0..MAX, -1..MIN
sub opcodeSort {
	my $hash = shift;

	my @nums;

	@nums = sort {$a <=> $b} grep ($_ >= 0, keys %$hash);
	@nums = (@nums, sort {$b <=> $a} grep ($_ < 0, keys %$hash));
	return @nums;
}

sub gen_sender_stub_sync_c {
	my $fh = shift;
	my $role = shift;
	my $func = shift;
	my $in = shift;
	my $out = shift;

	my $inarg = argument($in, "*in");
	my $outarg = argument($out, "**out");
	my $inparam = parameter($in, "in");
	my $outparam = parameter($out, "out");
	my $antirole = antirole($role);

	print $fh wrapc(<<EOF);

mrpc_status_t ${base}_$func(struct mrpc_connection *conn$inarg$outarg)
{
	return mrpc_send_request(${base}_$antirole, conn, nr_${base}_$func, $inparam, (void **) $outparam);
}
EOF
}

sub gen_sender_stub_sync_h {
	my $fh = shift;
	my $func = shift;
	my $in = shift;
	my $out = shift;

	my $inarg = argument($in, "*in");
	my $outarg = argument($out, "**out");

	print $fh wrapc(<<EOF);
mrpc_status_t ${base}_$func(struct mrpc_connection *conn$inarg$outarg);
EOF
}

sub gen_sender_stub_typedef_h {
	my $fh = shift;
	my $func = shift;
	my $out = shift;

	my $outarg = argument($out, "*reply");

	print $fh wrapc(<<EOF);
typedef void (${base}_${func}_callback_fn)(void *conn_private, void *msg_private, mrpc_status_t status$outarg);
EOF
}

sub gen_sender_stub_async_c {
	my $fh = shift;
	my $role = shift;
	my $func = shift;
	my $in = shift;

	my $inarg = argument($in, "*in");
	my $inparam = parameter($in, "in");
	my $antirole = antirole($role);

	print $fh wrapc(<<EOF);

mrpc_status_t ${base}_${func}_async(struct mrpc_connection *conn, ${base}_${func}_callback_fn *callback, void *private$inarg)
{
	return mrpc_send_request_async(${base}_$antirole, conn, nr_${base}_$func, (reply_callback_fn *)callback, private, $inparam);
}
EOF
}

sub gen_sender_stub_async_h {
	my $fh = shift;
	my $func = shift;
	my $in = shift;

	my $inarg = argument($in, "*in");

	print $fh wrapc(<<EOF);
mrpc_status_t ${base}_${func}_async(struct mrpc_connection *conn, ${base}_${func}_callback_fn *callback, void *private$inarg);
EOF
}

sub gen_receiver_stub_c {
	my $fh = shift;
	my $role = shift;
	my $func = shift;
	my $out = shift;

	my $outarg = argument($out, "*out");
	my $outparam = parameter($out, "out");

	print $fh wrapc(<<EOF);

mrpc_status_t ${base}_${func}_send_async_reply(struct mrpc_message *request$outarg)
{
	return mrpc_send_reply(${base}_$role, nr_${base}_$func, request, $outparam);
}
EOF
}

sub gen_receiver_stub_h {
	my $fh = shift;
	my $func = shift;
	my $out = shift;

	my $outarg = argument($out, "*out");

	print $fh wrapc(<<EOF);
mrpc_status_t ${base}_${func}_send_async_reply(struct mrpc_message *request$outarg);
EOF
}

sub gen_receiver_error_stub_c {
	my $fh = shift;
	my $role = shift;
	my $func = shift;
	my $out = shift;

	print $fh wrapc(<<EOF);

mrpc_status_t ${base}_${func}_send_async_reply_error(struct mrpc_message *request, mrpc_status_t status)
{
	return mrpc_send_reply_error(${base}_$role, nr_${base}_$func, request, status);
}
EOF
}

sub gen_receiver_error_stub_h {
	my $fh = shift;
	my $func = shift;
	my $out = shift;

	print $fh wrapc(<<EOF);
mrpc_status_t ${base}_${func}_send_async_reply_error(struct mrpc_message *request, mrpc_status_t status);
EOF
}

sub gen_oneway_stub_c {
	my $fh = shift;
	my $role = shift;
	my $func = shift;
	my $in = shift;

	my $inarg = argument($in, "*in");
	my $inparam = parameter($in, "in");
	my $antirole = antirole($role);

	print $fh wrapc(<<EOF);

mrpc_status_t ${base}_${func}(struct mrpc_connection *conn$inarg)
{
	return mrpc_send_request_noreply(${base}_$antirole, conn, nr_${base}_$func, $inparam);
}
EOF
}

sub gen_oneway_stub_h {
	my $fh = shift;
	my $func = shift;
	my $in = shift;

	my $inarg = argument($in, "*in");

	print $fh wrapc(<<EOF);
mrpc_status_t ${base}_${func}(struct mrpc_connection *conn$inarg);
EOF
}

sub gen_free_proc_h {
	my $fh = shift;
	my $type = shift;

	print $fh wrapc(<<EOF);

#ifndef MRPC_FREE_${type}_DEFINED
#define MRPC_FREE_${type}_DEFINED
static inline void free_$type($type *in, int container)
{
	if (in == NULL)
		return;
	xdr_free((xdrproc_t)xdr_$type, (char *)in);
	if (container)
		free(in);
}
#endif
EOF
}

sub gen_request_proc {
	my $fh = shift;
	my $role = shift;
	my $procs = shift;

	my $num;
	my $func;
	my $in;
	my $out;
	my $inparam;
	my $outparam;

	print $fh wrapc(<<EOF);

static mrpc_status_t ${base}_${role}_request(const void *p_ops, void *conn_data, struct mrpc_message *msg, int cmd, void *in, void *out)
{
	const struct ${base}_${role}_operations *ops=p_ops;

	/* Avoid possible compiler warning */
	(void)in;
	(void)out;

	if (ops == NULL)
		return MINIRPC_PROCEDURE_UNAVAIL;

	switch (cmd) {
EOF

	foreach $num (opcodeSort($procs)) {
		($func, $in, $out) = @{$procs->{$num}}[2..4];
		$inparam = opt_parameter($in, "in");
		$outparam = opt_parameter($out, "out");
		print $fh wrapc(<<EOF);
	case nr_${base}_$func:
		if (ops->$func == NULL)
			return MINIRPC_PROCEDURE_UNAVAIL;
EOF
		if ($num >= 0) {
			print $fh wrapc(<<EOF);
		else
			return ops->$func(conn_data, msg$inparam$outparam);
EOF
		} else {
			print $fh wrapc(<<EOF);
		else {
			ops->$func(conn_data, msg$inparam);
			return MINIRPC_OK;
		}
EOF
		}
	}

	print $fh wrapc(<<EOF);
	default:
		return MINIRPC_PROCEDURE_UNAVAIL;
	}
}
EOF
}

sub gen_info_proc {
	my $fh = shift;
	my $role = shift;
	my $isReply = shift;
	my $procs = shift;

	my $reply = $isReply ? "reply" : "request";
	my $num;
	my $func;
	my $type;
	my $typesize;

	print $fh wrapc(<<EOF);

static mrpc_status_t ${base}_${role}_${reply}_info(unsigned cmd, xdrproc_t *type, unsigned *size)
{
	/* Avoid possible compiler warning */
	(void)type;
	(void)size;

	switch (cmd) {
EOF

	foreach $num (opcodeSort($procs)) {
		next if $isReply and $num < 0;
		$func = @{$procs->{$num}}[2];
		$type = @{$procs->{$num}}[$isReply ? 4 : 3];
		$typesize = typesize($type);
		print $fh wrapc(<<EOF);
	case nr_${base}_$func:
		SET_PTR_IF_NOT_NULL(type, (xdrproc_t)xdr_$type);
		SET_PTR_IF_NOT_NULL(size, $typesize);
		return MINIRPC_OK;
EOF
	}

	print $fh wrapc(<<EOF);
	default:
		return MINIRPC_PROCEDURE_UNAVAIL;
	}
}
EOF
}

sub gen_opcode_enum {
	my $fh = shift;
	my $role = shift;
	my $procs = shift;

	my $num;
	my $func;

	print $fh "\nenum ${base}_${role}_procedures {\n";
	foreach $num (opcodeSort($procs)) {
		$func = @{$procs->{$num}}[2];
		print $fh "\tnr_${base}_$func = $num,\n";
	}
	print $fh "};\n";
}

sub gen_operations_struct {
	my $fh = shift;
	my $role = shift;
	my $procs = shift;

	my $num;
	my $func;
	my $in;
	my $out;
	my $inarg;
	my $outarg;
	my $retType;

	print $fh "\nstruct ${base}_${role}_operations {\n";
	foreach $num (opcodeSort($procs)) {
		($func, $in, $out) = @{$procs->{$num}}[2..4];
		$inarg = argument($in, "*in");
		$outarg = argument($out, "*out");
		$retType = ($num >= 0) ? "mrpc_status_t" : "void";
		print $fh wrapc("\t$retType (*$func)(void *conn_data, struct mrpc_message *msg$inarg$outarg);");
	}
	print $fh "};\n";
}

sub gen_set_operations_c {
	my $fh = shift;
	my $role = shift;

	print $fh wrapc(<<EOF);

int ${base}_${role}_set_operations(struct mrpc_connection *conn, const struct ${base}_${role}_operations *ops)
{
	return mrpc_conn_set_operations(conn, ${base}_$role, ops);
}
EOF
}

sub gen_set_operations_h {
	my $fh = shift;
	my $role = shift;

	print $fh wrapc(<<EOF);

int ${base}_${role}_set_operations(struct mrpc_connection *conn, const struct ${base}_${role}_operations *ops);
EOF
}

sub gen_protocol_struct_c {
	my $fh = shift;
	my $role = shift;
	my $haveRequest = shift;

	my $antirole = ($role eq "client") ? "server" : "client";
	my $isServer = ($role eq "server") ? "1" : "0";
	my $requestFunc = $haveRequest ? "${base}_${role}_request" : "NULL";

	print $fh wrapc(<<EOF);

const struct mrpc_protocol * const ${base}_$role =
&(const struct mrpc_protocol) {
	.is_server = $isServer,
	.request = $requestFunc,
	.sender_request_info = ${base}_${antirole}_request_info,
	.sender_reply_info = ${base}_${antirole}_reply_info,
	.receiver_request_info = ${base}_${role}_request_info,
	.receiver_reply_info = ${base}_${role}_reply_info
};
EOF
}

sub gen_protocol_struct_h {
	my $fh = shift;
	my $role = shift;

	print $fh wrapc(<<EOF);

extern const struct mrpc_protocol * const ${base}_$role;
EOF
}

sub genstubs_sync {
	my $role = shift;
	my $procs = shift;
	my $cf = shift;
	my $hf = shift;

	my @keys;
	my $num;
	my $func;
	my $arg;
	my $ret;

	@keys = sort {$a <=> $b} grep ($_ >= 0, keys %$procs);
	return if !@keys;
	print $hf "\n";
	foreach $num (@keys) {
		($func, $arg, $ret) = @{$procs->{$num}}[2..5];
		gen_sender_stub_sync_c($cf, $role, $func, $arg, $ret);
		gen_sender_stub_sync_h($hf, $func, $arg, $ret);
	}
}

sub genstubs_sender_async {
	my $role = shift;
	my $procs = shift;
	my $cf = shift;
	my $hf = shift;

	my @keys;
	my $num;
	my $func;
	my $arg;
	my $ret;

	@keys = sort {$a <=> $b} grep ($_ >= 0, keys %$procs);
	return if !@keys;
	print $hf "\n";
	foreach $num (@keys) {
		($func, $arg, $ret) = @{$procs->{$num}}[2..5];
		gen_sender_stub_async_c($cf, $role, $func, $arg);
		gen_sender_stub_typedef_h($hf, $func, $ret);
	}
	print $hf "\n";
	foreach $num (@keys) {
		($func, $arg, $ret) = @{$procs->{$num}}[2..5];
		gen_sender_stub_async_h($hf, $func, $arg);
	}
}

sub genstubs_receiver_async {
	my $role = shift;
	my $procs = shift;
	my $cf = shift;
	my $hf = shift;

	my @keys;
	my $num;
	my $func;
	my $arg;
	my $ret;

	@keys = sort {$a <=> $b} grep ($_ >= 0, keys %$procs);
	return if !@keys;
	print $hf "\n";
	foreach $num (@keys) {
		($func, $arg, $ret) = @{$procs->{$num}}[2..5];
		gen_receiver_stub_c($cf, $role, $func, $ret);
		gen_receiver_stub_h($hf, $func, $ret);
	}
}

sub genstubs_receiver_async_error {
	my $role = shift;
	my $procs = shift;
	my $cf = shift;
	my $hf = shift;

	my @keys;
	my $num;
	my $func;
	my $arg;
	my $ret;

	@keys = sort {$a <=> $b} grep ($_ >= 0, keys %$procs);
	return if !@keys;
	print $hf "\n";
	foreach $num (@keys) {
		($func, $arg, $ret) = @{$procs->{$num}}[2..5];
		gen_receiver_error_stub_c($cf, $role, $func, $ret);
		gen_receiver_error_stub_h($hf, $func, $ret);
	}
}

sub genstubs_noreply {
	my $role = shift;
	my $procs = shift;
	my $cf = shift;
	my $hf = shift;

	my @keys;
	my $num;
	my $func;
	my $arg;

	@keys = sort {$b <=> $a} grep ($_ < 0, keys %$procs);
	return if !@keys;
	print $hf "\n";
	foreach $num (@keys) {
		($func, $arg) = @{$procs->{$num}}[2..4];
		gen_oneway_stub_c($cf, $role, $func, $arg);
		gen_oneway_stub_h($hf, $func, $arg);
	}
}

sub genstubs_free {
	my $hf = shift;

	my @typelist;
	my $type;

	@typelist = grep(!/^void$/, sort keys %types);
	return if !@typelist;
	foreach $type (@typelist) {
		gen_free_proc_h($hf, $type);
	}
}

sub genstubs {
	my $role;
	my $procs;
	my $file;
	my $line;
	my $num;
	my $func;
	my $arg;
	my $ret;
	my $hf;

	# Validate procedure definitions
	foreach $role ("server", "client") {
		$procs = $procSets{$role};
		foreach $num (opcodeSort($procs)) {
			($file, $line, $func, $arg, $ret) = @{$procs->{$num}};
			parseErr($file, $line, "No such type: $arg")
				if !defined($types{$arg});
			parseErr($file, $line, "No such type: $ret")
				if !defined($types{$ret});
			parseErr($file, $line, "Procedures in ${role}msgs " .
						"section cannot return a value")
				if $num < 0 && $ret ne "void";
		}
	}

	# Create output files
	openFile(*MCF, "${basepath}_minirpc.c", 1);
	openFile(*MHF, "${basepath}_minirpc.h", 1);
	openFile(*CHF, "${basepath}_client.h", 1);
	openFile(*SHF, "${basepath}_server.h", 1);

	# Generate cpp directives
	print MCF "#define MINIRPC_PROTOCOL\n";
	print MCF "#include <minirpc/protocol.h>\n";
	print MCF "#include \"${base}_client.h\"\n";
	print MCF "#include \"${base}_server.h\"\n";
	print MHF "#ifndef " . uc $base . "_MINIRPC_H\n";
	print MHF "#define " . uc $base . "_MINIRPC_H\n\n";
	print MHF "#include <minirpc/minirpc.h>\n";
	print MHF "#include \"${base}_xdr.h\"\n\n";
	print MHF "#define " . uc $base . "_MRPC_VERSION_CODE() " .
				"$version_code\n";
	foreach $role ("server", "client") {
		$hf = ($role eq "server") ? *SHF : *CHF;
		print $hf "#ifndef " . uc "${base}_${role}_H" . "\n";
		print $hf "#define " . uc "${base}_${role}_H" . "\n\n";
		print $hf "#include \"${base}_minirpc.h\"\n";
	}

	# Generate toplevel structures
	foreach $role ("server", "client") {
		gen_opcode_enum(*MHF, $role, $procSets{$role})
			if keys %{$procSets{$role}};
		gen_info_proc(*MCF, $role, 0, $procSets{$role});
		gen_info_proc(*MCF, $role, 1, $procSets{$role});
	}
	foreach $role ("server", "client") {
		next if !keys %{$procSets{$role}};
		gen_request_proc(*MCF, $role, $procSets{$role});
	}
	foreach $role ("server", "client") {
		$hf = ($role eq "server") ? *SHF : *CHF;
		gen_protocol_struct_c(*MCF, $role,
					scalar keys %{$procSets{$role}});
		gen_protocol_struct_h($hf, $role);
	}
	foreach $role ("server", "client") {
		next if !keys %{$procSets{$role}};
		$hf = ($role eq "server") ? *SHF : *CHF;
		gen_operations_struct($hf, $role, $procSets{$role});
		gen_set_operations_c(*MCF, $role);
		gen_set_operations_h($hf, $role);
	}
	foreach $role ("server", "client") {
		$hf = ($role eq "server") ? *CHF : *SHF;
		genstubs_sync($role, $procSets{$role}, *MCF, $hf);
	}
	foreach $role ("server", "client") {
		$hf = ($role eq "server") ? *CHF : *SHF;
		genstubs_sender_async($role, $procSets{$role}, *MCF, $hf);
	}
	foreach $role ("server", "client") {
		$hf = ($role eq "server") ? *SHF : *CHF;
		genstubs_receiver_async($role, $procSets{$role}, *MCF, $hf);
	}
	foreach $role ("server", "client") {
		$hf = ($role eq "server") ? *SHF : *CHF;
		genstubs_receiver_async_error($role, $procSets{$role}, *MCF,
					$hf);
	}
	foreach $role ("server", "client") {
		$hf = ($role eq "server") ? *CHF : *SHF;
		genstubs_noreply($role, $procSets{$role}, *MCF, $hf);
	}
	genstubs_free(*MHF);

	foreach $hf (*MHF, *CHF, *SHF) {
		print $hf "\n#endif\n";
	}
}

sub parseFile {
	my $filename;
	my $line;
	my $data;
	my $curProcData;
	my $curDefs;
	my $noreply;
	my $sym_re = '([a-zA-Z0-9_]+)';
	my $type_re = '((unsigned\s+)?[a-zA-Z0-9_]+)';
	my $func;
	my $num;

	$data=`cpp -DMINIRPC=1 $defines $infile`;
	die "Couldn't open $infile"
		if $?;
	$filename = $infile;
	$line = 0;
	foreach $_ (split /\n/, $data) {
		$line++;
		if (/^# ([1-9][0-9]*) "([^"]+)"/) {
			# cpp line marker
			$filename = $2;
			$line = $1 - 1;
			next;
		}
		if (!$curDefs) {
			if (/^\s*(client|server)(procs|msgs)\s*{/) {
				$curDefs = $1;
				$noreply = ($2 eq "msgs");
				$procSets{$curDefs} = {}
					if !exists($procSets{$curDefs});
				next;
			}
			if (/^\s*(struct|enum)\s+$sym_re\s+{/o) {
				$types{$2} = 1;
			}
			if (/^\s*typedef\s+$type_re\s+$sym_re[<\[]?/o) {
				$types{$3} = 1;
			}
		} else {
			if (/}/) {
				undef $curDefs;
				next;
			}
			if (/^\s*$sym_re\s*\(\s*($type_re\s*
						(,\s*$type_re\s*)?)?\)\s*=
						\s*([1-9][0-9]*)\s*;/ox) {
				$func = $1;
				$num = $8;
				$num = -$num
					if $noreply;
				# file, line, func, arg, ret
				$curProcData = [$filename, $line, $func,
							$3 ? $3 : "void",
							$6 ? $6 : "void"];
				parseErr($filename, $line, "Duplicate " .
							"procedure number")
					if defined($procSets{$curDefs}->{$num});
				parseErr($filename, $line, "Duplicate " .
							"procedure name")
					if defined($procNames{$func});
				$procSets{$curDefs}->{$num} = $curProcData;
				$procNames{$func} = 1;
			} elsif (/^\s*$/) {
				next;
			} else {
				parseErr($filename, $line, "Invalid syntax");
			}
		}
	}
}

sub genRpclFile {
	my $inDefs = 0;

	open(XF, ">", "${basepath}.x.$$") || die "Can't open ${basepath}.x.$$";
	open(FH, "<", $infile) || die "Can't open $infile";
	while (<FH>) {
		if (!$inDefs) {
			if (/^\s*(client|server)(procs|msgs)\s*{/) {
				$inDefs = 1;
				print XF "\n";
			} else {
				print XF;
			}
		} else {
			$inDefs = 0
				if (/}/);
			print XF "\n";
		}
	}
	close(XF);
	close(FH);
}

sub usage {
	my $progname;

	($progname = $0) =~ s:.*/::;
	print <<EOF;
minirpcgen $version
Usage: $progname [options] <input_file>
Options:
\t-D <name[=value]>
\t-o <output_file_base_name>
EOF
	exit 1;
}

Getopt::Long::Configure('no_ignore_case');
GetOptions(
	'o=s' => \$basepath,
	'D=s' => sub {
		$defines .= " "
			if $defines;
		$defines .= "-D$_[1]";
	}
);
usage()
	if @ARGV != 1;
$infile = $ARGV[0];
if (defined $basepath) {
	$base = (File::Spec->splitpath($basepath))[2];
} else {
	# If -o is not given, use a default basepath in the current directory
	$basepath = (File::Spec->splitpath($infile))[2];
	$basepath =~ s/\.mx$//;
	$base = $basepath;
}
$defines = ""
	if !defined $defines;
usage()
	if !$base;

# Initialize primitive types
# These are the primitive types that can appear as procedure parameters.
# Right now we only support void, as a special case, because we don't want
# to make assumptions about the native (unserialized) length of the data
# types that XDR produces.  Regular types ("int", "bool", etc.) can still
# be used with XDR typedefs, because sizeof(some_typedef) will do the right
# thing (as opposed to sizeof(hyper), which is the only thing we could do
# since we don't know what C type "hyper" corresponds to).
$types{"void"} = 1;

# Preprocess the input file with cpp and parse it
parseFile;

# Generate stubs
genstubs;

# Read the input file again, this time without cpp, and generate a .x file
# suitable for parsing with rpcgen.  Try to preserve line numbers.
genRpclFile;

# Generate xdr.c
open(IF, "-|", "$rpcgen $defines -c $basepath.x.$$") or
	die "Couldn't generate ${basepath}_xdr.c";
openFile(*XCF, "${basepath}_xdr.c", 0);
while (<IF>) {
	s/${basepath}\.x\.h/${base}_xdr.h/
		if /#include/;
	print XCF;
}
close(IF)
	or exit 1;

# Generate xdr.h
my $olddefine = uc "_$base.x_H_RPCGEN";
my $newdefine = uc "${base}_XDR_H";
open(IF, "-|", "$rpcgen $defines -h $basepath.x.$$") or
	die "Couldn't generate ${basepath}_xdr.h";
openFile(*XHF, "${basepath}_xdr.h", 0);
while (<IF>) {
	s/$olddefine/$newdefine/;
	print XHF;
}
close(IF)
	or exit 1;

# Commit output
closeFiles();
