use Config; use File::Basename qw(&basename &dirname); chdir dirname($0); $file = basename($0, '.PL'); open(OUT, "> $file") || die "Can't create $file: $!"; print "Extracting $file (with variable substitutions)\n"; print OUT <<"!GROK!THIS!"; $Config{startperl} eval 'exec $Config{perlpath} -S \$0 \${1+"\$@"}' if \$running_under_some_shell; !GROK!THIS! print OUT <<'!NO!SUBS!'; # $Id: perlrpcgen.PL,v 1.4 1997/05/01 22:06:47 jake Exp $ # Copyright 1997 Jake Donham # You may distribute under the terms of either the GNU General # Public License or the Artistic License, as specified in the README # file. use perlrpcgen::RPCLParser; use perlrpcgen::Fstream; use perlrpcgen::RPCL::cliprocs; use perlrpcgen::RPCL::fix_typenames; use perlrpcgen::RPCL::gen_constants; use perlrpcgen::RPCL::gen_typemap; use perlrpcgen::RPCL::serprocs; use perlrpcgen::RPCL::structprocs; use perlrpcgen::RPCL::types; use Getopt::Long; # ----------------------------------------------------------------------------- # main sub usage { print STDERR < \$module, 'typemap=s@' => \@typemaps, 'makefile' => \$makefile, 'perl=s' => \$perl, 'cc=s' => \$cc, 'rpclibs=s' => \$rpclibs, 'all' => \$all, 'client' => \$client, 'server' => \$server, 'data' => \$data, 'constants' => \$constants, 'fork' => \$fork); &usage unless $ARGV[0]; $intfile = $ARGV[0]; $intfile =~ s/\.x$//; # strip trailing .x $module = $intfile unless $module; !NO!SUBS! print OUT <<"!GROK!THIS!"; \$perl = '$Config{perlpath}' unless \$perl; \$cc = '$Config{cc}' unless \$cc; !GROK!THIS! print OUT <<'!NO!SUBS!'; $rpclibs = '-lrpcsvc -lnsl' unless $rpclibs; if ($all) { $client = 1; $server = 1; $data = 1; $constants = 1; } if ($makefile) { &do_makefile; } else { $interface = &read_interface($ARGV[0]); # collapse all the typenames and typedefs foreach $def (@$interface) { $def = $def->fix_typenames; } # topologically sort the type graph so that typemaps which depend on # one another come out right. my $marks = {}; my $order = []; foreach $def (@$interface) { $def->topo_sort($marks, $order) unless $marks->{$def}; } # generate the typemaps. $typein{'T_PTRUBJ'} = $t_ptrubj_in; $typeout{'T_PTRUBJ'} = $t_ptrubj_out; foreach $def (@$order) { $def->gen_typemap($module); } # generate the constants. foreach $def (@$interface) { $def->gen_constants; @constants = sort @constants; } mkdir($module, $dirmode); &do_typemap; &do_typedefs; &do_root_makefile; if ($data) { mkdir("$module/Data", $dirmode); &do_data_xs; &do_data_perlmod; &do_data_makefile; } if ($constants) { mkdir("$module/Constants", $dirmode); &do_constants_perlmod; &do_constants_xs; &do_constants_makefile; } if ($client) { mkdir("$module/Client", $dirmode); &do_client_xs; &do_client_perlmod; &do_client_makefile; } if ($server) { mkdir("$module/server", $dirmode); # push(@typemaps, "$module/typemap"); &read_typemaps(@typemaps); &do_server; &do_server_makefile; } } # ----------------------------------------------------------------------------- # Read the interface definition and return a syntax tree sub read_interface { my ($fn) = @_; open (INT, $fn) || die $!; $s = Fstream->new(\*INT, $fn); $p = RPCLParser->new(\&RPCLParser::yylex, \&RPCLParser::yyerror, 0); my $interface = $p->yyparse($s); close INT; return $interface; } # ----------------------------------------------------------------------------- # Write the typemap. sub do_typemap { open (FH, "> $module/typemap") || die $!; print FH "# Generated by perlrpcgen.\n"; print FH "TYPEMAP\n"; foreach $name (keys %typemap) { print FH $name . "\t" . $typemap{$name} . "\n"; } print FH "RPC::ONC::Client\tT_PTROBJ\n"; print FH "\nINPUT\n"; foreach $name (keys %typein) { print FH $name . "\n" . $typein{$name}; } print FH "\nOUTPUT\n"; foreach $name (keys %typeout) { print FH $name . "\n" . $typeout{$name}; } close FH; } # ----------------------------------------------------------------------------- # Write typedefs.h sub do_typedefs { open (FH, "> $module/typedefs.h") || die $!; print FH "/* Generated by perlrpcgen. */\n"; foreach $name (keys %structs) { print FH $structs{$name} . ";\n"; } foreach $name (keys %typedef) { my $cname = $name; $cname =~ s/::/__/; print FH "typedef " . $typedef{$name} . "\t" . $cname . ";\n"; } print FH "typedef CLIENT *\tRPC__ONC__Client;\n"; close FH; } # ----------------------------------------------------------------------------- # Write the common data structure code. sub do_data_xs { open (FH, "> $module/Data/Data.xs") || die $!; print FH < MODULE = ${module}::Data\tPACKAGE = ${module}::Data PROTOTYPES: DISABLE EOF foreach $def (@$interface) { $def->structprocs($module, \*FH); } close FH; } # ----------------------------------------------------------------------------- # Write the client code. sub do_client_xs { open (FH, "> $module/Client/Client.xs") || die $!; print FH < /* kinda stupid to put this in every client module, but I'm not sure how to get to symbols in another shared object library */ void set_perl_error(int errno, char *errstr) { static SV *sv_errno = 0; static SV *sv_errstr = 0; if (!sv_errno) sv_errno = perl_get_sv("RPC::ONC::errno", TRUE); if (!sv_errstr) sv_errstr = perl_get_sv("RPC::ONC::errstr", TRUE); sv_setiv(sv_errno, (IV) errno); sv_setpv(sv_errstr, errstr); } void set_perl_error_clnt(CLIENT *clnt) { struct rpc_err err; clnt_geterr(clnt, &err); set_perl_error(err.re_status, clnt_sperrno(err.re_status)); } MODULE = ${module}::Client\tPACKAGE = ${module}::Client PROTOTYPES: DISABLE EOF foreach $def (@$interface) { $def->cliprocs($module, \*FH); } close FH; } # ----------------------------------------------------------------------------- # Write the server code. sub do_server { open (FH, "> $module/server/server.c") || die $!; print FH < #include static PerlInterpreter *my_perl; extern void xs_init(); void initperl(int argc, char **argv) { int ret; my_perl = perl_alloc(); perl_construct(my_perl); if ((ret = perl_parse(my_perl, xs_init, argc, argv, 0)) != 0) exit(ret); if ((ret = perl_run(my_perl)) != 0) exit(ret); } SV *callperl(char *func, SV *arg, struct svc_req *rqstp, SVCXPRT *transp, int checkarg) { SV *ret, *req = sv_newmortal(), *tr = sv_newmortal(); int count; dSP; ENTER; SAVETMPS; PUSHMARK(sp); sv_setref_pv(req, "RPC::ONC::svc_req", (void *)rqstp); sv_setref_pv(tr, "RPC::ONC::Svcxprt", (void *)transp); if (arg) { XPUSHs(arg); } XPUSHs(req); XPUSHs(tr); PUTBACK; count = perl_call_pv(func, G_SCALAR|G_EVAL); SPAGAIN; if (checkarg && count != 1) croak("subroutine did not return 1 argument"); ret = POPs; SvREFCNT_inc(ret); PUTBACK; FREETMPS; LEAVE; return ret; } EOF foreach $def (@$interface) { $def->serprocs($module, \*FH); } close FH; } # ----------------------------------------------------------------------------- # Write the constants code (borrowed from h2xs). sub do_constants_xs { open (FH, "> $module/Constants/Constants.xs") || die $!; print FH < $module/Data/Data.pm") || die $!; print FH <ident, $self->versions->[0]->ident); } sub do_client_perlmod { open (FH, "> $module/Client/Client.pm") || die $!; my ($prog, $vers); foreach $def (@$interface) { last if ($prog, $vers) = $def->progvers; } print FH < $module/Constants/Constants.pm") || die $!; print FH < $module/Makefile.PL") || die $!; print FH < '${module}', 'VERSION' => '0.1', 'OPTIMIZE' => '-g', ); EOF close FH; } sub do_data_makefile { open (FH, "> $module/Data/Makefile.PL") || die $!; print FH < '${module}::Data', 'VERSION' => '0.1', 'INC' => '-I.. -I../..', 'CC' => '$cc', 'LD' => '$cc', ); EOF close FH; } sub do_constants_makefile { open (FH, "> $module/Constants/Makefile.PL") || die $!; print FH < '${module}::Constants', 'VERSION' => '0.1', 'INC' => '-I.. -I../..', 'CC' => '$cc', 'LD' => '$cc', ); EOF close FH; } sub do_client_makefile { open (FH, "> $module/Client/Makefile.PL") || die $!; print FH < '${module}::Client', 'VERSION' => '0.1', 'LIBS' => ['-lrpcsvc -lnsl'], 'INC' => '-I.. -I../..', 'CC' => '$cc', 'LD' => '$cc', 'LDFROM' => 'Client.o ../../${intfile}_xdr.o ../../${intfile}_clnt.o', ); EOF close FH; } sub do_server_makefile { open (FH, "> $module/server/Makefile") || die $!; print FH < Makefile") || die $!; pop @orig_argv; # remove $intfile.x @orig_argv = grep { $_ !~ /-ma/ } @orig_argv; # remove -makefile my $opts = join(' ', map { "'$_'" } @orig_argv); my $serv = ($server ? "\$(MOD)/server/\$(INT)_svc" : ''); my $forkfix = ''; if ($fork) { $forkfix = "-e 's/result = (/if (!fork()) { _rpcsvccount--; " . "_rpcsvcstate = _SERVED; return; }; result = (/' \\\n\t " . "-e 's/^\treturn/\texit(0)/'\\\n\t"; } print FH< \$(INT)_svc.c .perlmods: .Makefiles \$(INT)_xdr.o \$(INT)_clnt.o cd \$(MOD); make touch .perlmods \$(MOD)/server/\$(INT)_svc: .perlrpcgen \$(INT)_svc.o cd \$(MOD)/server; make \$(INT)_svc tidy: rm -f *~ "#*" core clean: tidy rm -f .perlrpcgen .Makefiles .perlmods rm -f *.h *.c *.o rm -rf \$(MOD) install: all cd \$(MOD); make install EOF close FH; } # ----------------------------------------------------------------------------- # Code to deal with typemaps (taken from xsubpp). It would be nice to # have a more general embedpp that would generate embedding stubs from # typemaps and some kind of embedding language. sub read_typemaps { my (@tm) = @_; my ($mode, $junk, $current, $proto); foreach $typemap (@tm) { next unless -e $typemap ; warn("Warning: ignoring non-text typemap file '$typemap'\n"), next unless -T $typemap ; open(TYPEMAP, $typemap) or warn ("Warning: could not open typemap file '$typemap': $!\n"), next; $mode = 'Typemap'; $junk = "" ; $current = \$junk; while () { next if /^\s*#/; my $line_no = $. + 1; if (/^INPUT\s*$/) { $mode = 'Input'; $current = \$junk; next; } if (/^OUTPUT\s*$/) { $mode = 'Output'; $current = \$junk; next; } if (/^TYPEMAP\s*$/) { $mode = 'Typemap'; $current = \$junk; next; } if ($mode eq 'Typemap') { chomp; my $line = $_ ; TrimWhitespace($_) ; # skip blank lines and comment lines next if /^$/ or /^#/ ; my($type,$kind, $proto) = /^\s*(.*?\S)\s+(\S+)\s*($proto_re*)\s*$/ or warn("Warning: File '$typemap' Line $. '$line' TYPEMAP entry needs 2 or 3 columns\n"), next; $type = TidyType($type) ; $type_kind{$type} = $kind ; # prototype defaults to '$' $proto = "\$" unless $proto ; warn("Warning: File '$typemap' Line $. '$line' Invalid prototype '$proto'\n") unless ValidProtoString($proto) ; $proto_letter{$type} = C_string($proto) ; } elsif (/^\s/) { $$current .= $_; } elsif ($mode eq 'Input') { s/\s+$//; $input_expr{$_} = ''; $current = \$input_expr{$_}; } else { s/\s+$//; $output_expr{$_} = ''; $current = \$output_expr{$_}; } } close(TYPEMAP); } foreach $key (keys %input_expr) { $input_expr{$key} =~ s/\n+$//; } } sub TrimWhitespace { $_[0] =~ s/^\s+|\s+$//go ; } sub TidyType { local ($_) = @_ ; # rationalise any '*' by joining them into bunches and removing whitespace s#\s*(\*+)\s*#$1#g; s#(\*+)# $1 #g ; # change multiple whitespace into a single space s/\s+/ /g ; # trim leading & trailing whitespace TrimWhitespace($_) ; $_ ; } sub ValidProtoString ($) { my($string) = @_ ; if ( $string =~ /^$proto_re+$/ ) { return $string ; } return 0 ; } sub C_string ($) { my($string) = @_ ; $string =~ s[\\][\\\\]g ; $string ; } __END__ =head1 NAME perlrpcgen - generate Perl interfaces from ONC RPC interface definitions =head1 SYNOPSIS B [B<--makefile>] [B<--all>] [B<--client>] [B<--server>] [B<--data>] [B<--constants>] [B<--module> module] [B<--typemap> typemap] [B<--fork>] [B<--perl> perl] [B<--cc> cc] [B<--rpclibs> rpclibs] rpcfile.x =head1 DESCRIPTION I builds a set of Perl extensions and a server shell from an ONC RPC interface definition. For an interface Foo, I creates modules Foo::Client, Foo::Data, and Foo::Constants. Foo::Client contains routines for creating a Foo client and making remote procedure calls via the client. Foo::Data contains routines for creating and manipulating the data structures defined in the Foo interface. Foo::Constants contains functions to retrieve the constants defined in the Foo interface. =head1 OPTIONS The option parsing uses Getopt::Long, so you can abbreviate option names. =over 4 =item B<--makefile> Generates a top-level Makefile which will build all the pieces. Include all the other options you want so they'll be propagated to the Makefile. =item B<--all> Implies --client, --server, --data, and --constants. This is usually what you want. =item B<--client> Generates Foo::Client module. =item B<--server> Generates Foo/server/foo_svc. =item B<--data> Generates Foo::Data module. =item B<--constants> Generates Foo::Constants module. =item B<--module> module Sets the basename of the modules. If not given, the name defaults to the basename of the interface file. =item B<--typemap> typemap Uses the given typemap during stub generation. This option can be specified many times. I generates a typemap for the data structures in the interface, but you also need the main Perl typemap. =item B<--fork> Munges the server shell code so that it forks for each request. You probably don't want to do this (it's better to prefork several processes when you start the server and let them fight over accept() calls). =item B<--perl> perl Sets the Perl binary against which extensions should be built. Defaults to the Perl you used to install I. =item B<--cc> cc Sets the C compiler to use. Defaults to the compiler used when building Perl. =item B<--rpclibs> rpclibs Sets the RPC libraries to link against. Defaults to '-lnsl -lrpcsvc'. =back =head1 MODULES =head2 Foo::Client RPC clients and the procedure calls they support are encapsulated in Foo::Client objects (which inherit from RPC::ONC::Client). A new Foo::Client can be created like this: $foo = Foo::Client->new($server, $prot, $prog, $vers); $prot, $prog, and $vers are optional, and will default to 'netpath', the value of the first 'program' declaration in the interface, and the value of the first 'version' declaration in the interface, respectively. If the client creation fails, the constructor will set $RPC::ONC::errno and $RPC::ONC::errstr to the error number and error string, respectively, and croak. For each procedure there is a method with the same name (with an underscore and the version number appended as in C). Methods are called like this: $val = $foo->someproc_1($arg); $arg must be an object of the correct type as given in the interface definition (see Foo::Data for details), or nothing if the interface specifies a 'void' argument. If the remote procedure call fails, the method will set $RPC::ONC::errno and $RPC::ONC::errstr and croak. You'll need to use RPC::ONC, Foo::Data, Foo::Constants, and Foo::Client in your client code. =head2 Foo::Data For each structure and union definition 'bar' in the interface definition, I creates a package in the Foo::Data module called Foo::bar. (If your interface contains an anonymous structure or union you'll get a package with an arbitrary name--you'll have to check out the Foo::Data module to see what it is. A warning would be nice here.) You can create a new Foo::bar object like this: $bar = Foo::bar->new; $bar2 = Foo::bar->new($bar); The first form creates an uninitialized object. The second form initializes an object by doing a shallow copy of another object. Be careful; you can cause coredumps and other weird behavior if you try to get an uninitialized field from a structure or return a structure with any uninitialized fields to a client (in that case the XDR routine for the structure will blow up). Finally, there's nothing stopping you from setting or retrieving the wrong type from a union (this may be fixed in a future version), and you can coredump that way too. For each field in a structure or union (including the discriminant of a union) there is a getter (with the same name) that returns the value of the field, and a setter (with the same name prepended with 'set_') which takes an argument of the appropriate type and sets the field. For fields which are structures or unions themselves, the getter returns a reference to the field, so if you set the fields on this sub-structure you'll side-effect the super-structure. In order to avoid having to doink around with various levels of indirection (as in C), all structures and unions are passed by reference, and multiple indirections are collapsed into one. So you can do $foo->set_bar($baz) whether foo is a structure, a pointer to a structure, a pointer to a pointer to a structure, etc. As part of this magic, all the typedefs are collapsed as well. So if your interface has, say, 'typedef bar *quux' and a particular procedure calls for a 'quux', pass it a Foo::bar from Perl--there is no Perl type corresponding to 'quux'. (This is perhaps less of a good thing than the pointer collapsing but I couldn't see a simple way to do one without the other.) Finally, functions which call for a pointer to a structure or union may be passed undef, which translates to a NULL pointer. Types are converted from their XDR/RPC definitions as follows: =over 4 =item int, unsigned int, enum Become Perl numbers. =item string, opaque Become Perl strings (and are length-checked if a length is specified). =item struct, union Become Perl objects as described above. =item bar[], bar<> Become Perl arrays of Foo::bar objects (and are length-checked if a length is specified). =back =head2 Foo::Constants All the constants (including enums and identifiers for 'program' and 'version' definitions) defined in the interface are available via Foo::Constants (and are imported by default). =head2 Server I creates a server shell which embeds Perl. To implement the procedure calls, you must define a function in the main package for each procedure defined in the interface (as in C, they should have the same names with an underscore and the version number appended). These functions are called as follows: $val = someproc_1($arg, $svc_req, $transp); $val = someproc_1($svc_req, $transp); $arg is the procedure argument (of the appropriate type) or nothing if the procedure is declared to take 'void'. $svc_req is an RPC::ONC::svc_req object (see RPC::ONC) through which the client's credentials can be accessed. $transp is an RPC::ONC::Svcxprt object (see RPC::ONC) through which the client's IP address can be accessed. You'll need to use RPC::ONC, Foo::Data, and Foo::Constants, but don't try to use Foo::Client in a server, because you'll get symbol conflicts. The command line arguments are passed unchanged to Perl. Usually you start a server with something like: Foo/server/foo_svc Foo.pl If you're planning to write a lot of servers you might want to build Perl with a shared libperl. =head1 AUTHOR Jake Donham =head1 THANKS Thanks to Organic Online for letting me hack at work. =head1 SEE ALSO L, L =head1 BUGS If your interface has comments or other preprocessor statements in it you need to run it through cpp before giving it to I. I should use MakeMaker for the top-level Makefile. =cut !NO!SUBS! close OUT or die "Can't close $file: $!"; chmod 0755, $file or die "Can't reset permissions for $file: $!\n"; exec("$Config{'eunicefix'} $file") if $Config{'eunicefix'} ne ':';