=head1 NAME

rxgen - Stub generator for the Rx remote procedure call package

=head1 SYNOPSIS

=for html
<div class="synopsis">

B<rxgen> [B<-h> | B<-c> | B<-C> | B<-S> | B<-r>] [B<-dkp>]
    [B<-I> I<dir>] [B<-P> I<prefix>] [B<-o> I<outfile>] [I<infile>]

=for html
</div>

=head1 DESCRIPTION

B<rxgen> is a tool that generates C code to implement the Rx RPC protocol;
it takes as input a description of an application interface similar to C
and produces a number of server and/or client stub routines to be linked
with RPC-based programs.  These stubs allow programs to invoke remote
procedures through local procedure calls.  B<rxgen> is based on Sun's
B<rpcgen> (version 3.9) but does not maintain compatibility with rpcgen
RPC descriptions.

=head1 OPTIONS

B<rxgen> operates in several different modes.  The generated output files
can be produced individually (using one of B<-h>, B<-c>, B<-C>, or B<-S>)
or collectively.  All output files are created when the default is used
(i.e., no options), or the output is limited to the server stubs (B<-C>
and B<-S>) when the B<-r> flag is used.  The following describes the types
of generated output files (for simplicity, I<filename> refers to the main
output filename):

=over 4

=item B<-h>

Generate C data definitions (a header file) from standard RPCL definitions
(default extension: I<filename>.h).

=item B<-c>

Compile the XDR routines required to serialize the protocol described by
RPCL.  Generate XDR routines for all declarations (default extension:
I<filename>.xdr.c).

=item B<-C>

Generate all the client-side stub routines (default extension:
I<filename>.cs.c).  Calling a routine in this file will cause the
arguments to be packed up and sent via Rx (or R).

=item B<-S>

Generate all the server-side stub routines (default extension:
I<filename>.ss.c).  Arguments are unpacked, and the corresponding server
routine is called.

=item B<-r>

Generate the two default extension files produced by the B<-C> and B<-S>
options.

=back

The following options can be used on any combination of B<rxgen> calls:

=over 4

=item B<-k>

Must be specified when the generated code is intended to be used by the
kernel; special "includes" and other specifics are produced when the
target output is for the kernel.

=item B<-p>

Package combination flag: when multiple packages are included within a
single specification file, a single Execute Request routine will be used
for all of them as a result of this flag.  The default is to generate
individual Execute Request stubs for each package.

=item B<-I> I<dir>

Similar to the B<-I> flag in the C compiler (B<cc>). This flag is passed
to the pre-processor (B<cpp>) so that directory I<dir> is searched before
the standard lookup list for #include files.  As expected, multiple B<-I>
flags can be used simultaneously.

=item B<-P> I<prefix>

The I<prefix> string following this switch is prepended to all generated
output files; useful when multiple runs want to produce different versions
of the same interface (say, kernel and non-kernel versions).

=item B<-d>

Debugging mode; only needed when B<rxgen> is to be debugged (say, via
B<dbx>).

=item B<-o> I<outfile>

Specify the name of the output file.  If none is specified, the standard
output is used (B<-c>, B<-h>, B<-C>, and B<-S> modes only).  Note that if
an output file is specified in a multi-output file option (such as the
default, or with option B<-r>), then the I<outfile> replaces the name
generated by default (which is based on the configuration's main file
name).

=back

=head1 B<rxgen> SYNTAX SUMMARY

    Specification file:

        <Package description option> |
        <Prefix description option> |
        <StartingOpcode description option> |
        <SplitPrefix description option> |
        <Procedure description option> |
        <RPCL language description option>

    <Package description option>:

        "package" <Package_ident>

    <Prefix description option>:

        "prefix" <Prefix_ident>

    <StartingOpcode description option>:

        "startingopcode" <constant>

    <SplitPrefix description option>:

        "splitprefix" <split options> ";"

    <Split options>:

        "IN =" <Start_prefix_ident> "|"
        "OUT =" <End_prefix_ident> "|"
        <Split options>

    <Procedure description option>:

        ["proc"] [<Procedure_ident>] [<ServerStub_ident>]
            <Argument list> ["split" | "multi"]
            ["=" <Opcode_ident>] ";"

    <Argument list>:

        "(" <Argument definition> <Comma_joined argument> ")"

    <Argument definition>:

        <Direction option> <Standard RPCL type decl> <Arg_ident>
            ["<" <Max_size> ">" | "[" <Max_size> "]"] | NULL

    <Comma_joined argument>:

        "," <Argument definition> | NULL

    <Direction option>:

        "IN" | "OUT" | "INOUT" | NULL

    <Max_size>:

        <constant> | NULL

    <Package_ident>:
    <Prefix_ident>:
    <String_ident>:
    <Start_prefix_ident>:
    <End_prefix_ident>:
    <Procedure_ident>:
    <ServerStub_ident>:
    <Arg_ident>:
    <Opcode_ident>:

        <identifier>

    <RPCL language description option>:
    <Standard RPCL type decl>:

        Sun's RPCL language syntax (see rpcgen(1))

=head1 B<rxgen> COMMANDS

=head2 Comments and Preprocessing

The input interface may contain preprocessor directives which are passed
through the C preprocessor (i.e. C<cpp>).  Since the preprocessor runs on
all input files before they are actually interpreted by B<rxgen>, all
B<cpp> directives (#include, #ifdefs, #defines, etc.) are legal and
welcomed within an B<rxgen> input file.  Of course, none of these
preprocessor directives will be included in any of the generated files.
To facilitate distinctions between the different types of output files,
B<rxgen> defines certain special B<cpp> symbols for use by the B<rxgen>
programmer.  These are RPC_HDR (defined when compiling into header,
I<filename>.h, files), RPC_XDR (defined when compiling into xdr,
I<filename>.xdr.c, files), RPC_CLIENT (defined when compiling into client
stubs, I<filename>.cs.c, files), and RPC_SERVER (defined when compiling
into server stubs, I<filename>.ss.c, files).

In addition, B<rxgen> does a little preprocessing of its own.  Any line
beginning with C<%> is passed directly into the output file, uninterpreted
by B<rxgen>.  For a more heavy en masse dumping of uninterpreted code, it
would be advised to include all such code in an C<#include> file and pass
it in preceded by C<%>.  The input interface may also contain any C-style
comments which are, of course, ignored. Interpretation is token-based,
thus special line-orientation of separate statements is not necessary.
B<rxgen> also provides a quite rich and helpful set of error reports,
identifying them by exact line location and error type.  Also, B<rxgen>
will automatically generate #include lines for standard include files,
such as F<rx/xdr.h> and F<rx/rx.h>, along with the generated header file
from this interface.

=head2 Prefixing stub procedures

The I<package> statement tells B<rxgen> the name of the interface package.
It is used for prefixing the naming of all generated stub routines and the
execute request procedure.  For example:

    package AFS_

causes the execute request procedure to be named AFS_ExecuteRequest
(Warning: in the older version an additional C<_> was appended after the
package name to the ExecuteRequest name; thus make sure you don't have an
ExecuteRequest interface routine) and a given stub routine, say Fetch, to
be actually named AFS_Fetch.  Multiple package statements (current maximum
size is 10) per configuration are permitted and are useful when multiple
sets of interfaces are implemented (see the example at the end).  Note
that in such cases, use of the B<-p> flag results in the generation of
just one ExecuteRequest procedure which recognizes the multiple interfaces
and whose name is prefixed by the first package statement.  In the default
case, independent ExecuteRequest procedures will be created for each
packaged group of remote procedure calls.

The I<prefix> statement supplies a name to prepend to all calls to remote
procedure names in the ExecuteRequest stub routine.  It is useful when the
server makes RPC calls to other servers (say, for debugging purposes).
For example:

    prefix S

causes the name C<S> to be prepended to the name of all routines called
from the server stubs.  The server can then call the original name and get
the client stubs.

=head2 B<rxgen> procedure declaration

The I<proc> statement is the most common (and meaningful) in the B<rxgen>
interface.  Its syntax description is:

        [proc] [<proc_name>] [<server_stub>] (<arg>, ..., <arg>)
            [split | multi] [= <opcode>] ;

where:

=over 2

=item *

C<proc> is an optional prefix of the procedure statement. This is just a
stylistic item and not a required procedure delimiter.

=item *

<proc_name> is the name of the procedure.  Note that even the name of the
procedure is optional.  This only makes sense when the name of the given
procedure is identical to the name of the last I<package> statement (i.e.,
C<package RCallBack> and the declaration of the C<RCallBack> procedure).

=item *

<server_stub>, if present, causes the ExecuteRequest procedure to call
that stub instead of the automatically generated stub when a call with
that opcode is decoded.

=item *

<opcode> is a constant or symbol that is the opcode for that procedure.
One might use the preprocessor features (i.e., #define), the I<const>
RPC-language feature, or the old good constants as opcodes. Some further
evaluation/processing of opcodes is done.  Particularly, checks for
duplicate and non-existent opcodes are performed, along with checks for
"holes" (i.e., gaps in consecutive opcodes) in the opcode sequences.  For
example, we use the fact that when "holes" in opcodes exist, the
ExecuteRequest procedure uses the I<case> statement rather than the faster
(and smaller, codewise) indexed array method.

Also, B<rxgen> defines (i.e., appends to the header file) three valuable
macros for each package group: <package-name>LOWEST_OPCODE,
<package-name>HIGHEST_OPCODE, and <package-name>NUMBER_OPCODES.  These may
be useful to the B<rxgen> programmer.  Also, notice that the I<opcode>
statement is an optional feature, and can be omitted.  In such cases,
automatic opcode numbers are generated sequentially, starting from 0.

One can change the initial opcode number by using the I<startingopcode>
(for lack of a better name) B<rxgen> command.  Its syntax is:

    startingopcode <constant>

where <constant> must be reasonable!  Note that one can not mix
procedures, some with opcodes and some without, nor allow opcodes after
the specification of the I<startingopcode> statement.  B<rxgen> will
complain in all such cases.

=item *

The I<argument> entry represents a given parameter of the procedure.  Its
syntax is:

    [IN | INOUT | OUT | <null>] <type_decl> <arg_name>
        [<max>|<>|[max]|[]]

If the type is an indirect type (i.e., is followed by *), it is assumed
that the pointer should be followed one level and the data pointed to is
to be transmitted. This should normally be used for all structures/arrays
and out parameters.  A noticeable exception is when explicit
array/structure maximum size is given; since no array-of-pointer
declarations are allowed one should use typedefs to achieve the similar
effect.  The parameters could be input parameters (preceded by IN), output
parameters (preceded by OUT), or input/output parameters (preceded by
INOUT).  If not specified, then the direction of the previous parameter in
the procedure is used.  (Note: the first parameter must be preceded by the
directional primitive!)

=item *

C<split> is a hack to handle stub routines that do things such as file
transfers or any other operation that has to exchange information (e.g.,
length of a file) before the call returns its output parameters.  Because
of the particular handshake that is involved when doing remote file
transfer, we currently break all such calls into two client-side stub
routines.  The first (with the default prefix of C<Begin>) is used to pass
all IN and INOUT parameters to the server side.  The second (with the
default prefix of C<End>) is used to get back the INOUT and OUT parameters
from the server.  Between the two calls, the user is supposed to do the
appropriate calls for the file transfer. For example, the following
procedure declaration in package AFS_

    Fetch (IN a, b,INOUT c, OUT d) split = FETCHOPCODE;

will roughly generate the two independent client stub routines:

    BeginAFS_Fetch (IN a, b, c)

and

    EndAFS_Fetch(OUT c, d)

The I<splitprefix> statement is used to change the default prefix names
used by the two client-side stub generated routines when dealing with file
transfer-related procedure calls.  For example:

    splitprefix IN=Before_ OUT=After_

will cause the naming of the two client stubs for a file transfer-related
routine, say Fetch(), to be Before_AFS_Fetch() and After_AFS_Fetch(),
respectively.

=item *

The C<multi> option is nearly identical to the C<split> feature described
above.  The only significant visible difference is that along with the two
client stubs, the standard client stub is also generated.  Since the
intention is to handle the multi-Rx calls, we need the whole standard
procedure stub in the cases where no multi-Rx call of the procedure is
performed.  A side effect of the C<multi> option is the generation of a
special macro (i.e., C<< multi_<Procedure-name> >> which passes back as
arguments the C<Begin> and C<End> stubs in the header output file. This
macro is used directly by the Rx code when a multi-Rx call of this
procedure is performed.

=back

=head2 OBSOLETE B<rxgen> FEATURES

Although the following rxgen commands are still in effect, they will soon
be removed since there are better alternatives. DO NOT USE THEM!

The I<special> statement is a temporary hack used to handle certain
inefficiencies of standard xdr routines to handle some user-customized
declarations.  In particular, this applies to a string pointer specified
as part of a declaration.  For example,

    special struct BBS SeqBody;

tells B<rxgen> that the entry C<SeqBody> in the user-defined BBS xdr
routine is a string (note that more than one string can be "special" per
structure -- multiple ones are separated by commas); it will thus allocate
and de-allocate space properly in the server-generated stubs that contain
this structure as an IN or INOUT parameter.

A better alternative to I<special> is the I<customized> statement, which
is simply the C<customized> token followed by the regular declaration of a
struct based on the RPCL rules. In this case, the declaration will be
included in the generated header file (B<-h> option) but no xdr routine
will be generated for this structure -- the user will supply this.  All
pointer entries in this structure will be remembered so when the structure
is used as an IN or INOUT in the server stub, no core leaks will occur.
For example, consider

    customized struct CBS {
        long Seqlen;
        char *SeqBody;
    }

The C<xdr_CBS> routine would be provided by the user where during the
DECODE xdr opcode, appropriate space for the C<SeqBody> string is
allocated.  Similarly, that space is freed during the FREE xdr opcode.

Note: Old style "Array parameter specifications" are not supported any
more.

=head1 EXAMPLES

In case there are some requirements not available by the current RPC
language, one can customize some XDR routines by leaving those data types
undefined. For every data type that is undefined, it will be assumed that
a routine exists with the name C<xdr_> prepended to it.  A selected set of
B<rxgen> features is presented below, but for a more comprehensive one
(unions, complex examples, etc) please refer to the I<rpcgen Programming
Guide> and I<eXternal Data Representation: Sun Technical Notes>.

=head2 Typedefs

The RPC typedef statement is identical to the C typedef (i.e. C<< typedef
<declaration> >>).  By default, most user declarations (i.e. structs,
unions, etc) are automatically typedef'ed by B<rxgen>.  Since it makes
parsing simpler, its usage is recommended by B<rxgen> scripts.

=head2 Strings

The C C<char *> string convention is kind of ambiguous, since it is
usually intended to mean a null-terminated string of characters, but it
could also represent a pointer to a single character, a pointer to an
array of characters, etc.  In the RPC language, a null-terminated string
is unambiguously called a "string".  Examples,

    string bigname<>;
    string name<MAXNAMELEN>;
    typedef string volname<MAXVOLNAME>;

Notice that the maximum size of string can be arbitrary (like C<bigname>
above) or, preferably, or specified in angle brackets (i.e. C<name> and
C<volname> above).  In practice, one should always use only bounded
strings in interfaces.  A sample calling proc using the declarations above
would be:

    GetEntryByName (IN volname name, 
        OUT struct vldbentry *entry) = VL_GETENTRYBYNAME;

or, of course,

    GetEntryByName (IN string volname<MAXVOLNAME>,
        OUT struct vldbentry *entry) = VL_GETENTRYBYNAME;

It is very important for the user to understand when the string parameters
should be allocated and/or freed by the his/her client and/or server
programs. A short analysis on string parameters handling follows (note
that a similar method is used for the handling of variable length arrays
as it will be shown later on):

=over 2

=item *

In the client side: IN and INOUT string parameters are the programmer's
responsibility and should be allocated (static or via malloc) before
calling the rpc and freed (if malloc was used) after the rpc's return in
the user's client program; of course, for INOUT parameters, the returned
string can't be bigger than the malloced input string.

OUT string parameters are automatically malloced (based on the length of
the returned string and not the maxsize) by the B<rxgen> client stubs (in
I<filename>.cs.c) and must be freed by the client program; admittedly,
this could be somewhat confusing since the user needs to free something
that he/she didn't allocate.}

=item *

In the server side: IN and INOUT string parameters are automatically
malloced (based on the size of incoming strings) by the rxgen server stubs
(in I<filename>.ss.c) before they are passed to the user's server
procedure; that space is automatically freed just before the rxgen server
stub returns; therefore the user need not do anything special for IN and
INOUT string parameters.

OUT string parameters must be malloced by the user's server procedure
(i.e. null pointer is passed to it by the rxgen server stub) and it is
automatically freed at the end of the B<rxgen> server stub.  Like in the
client side, the OUT parameters are somewhat unorthodox (i.e. the server
routine must malloc a string without ever freeing it itself; this is done
by the B<rxgen> server stub).

=back

Note that for INOUT and OUT string parameters, in both the client and
server sides their arguments must be char of pointers (i.e. char **).

=head2 Pointers

Pointer declarations in RPC are also exactly as they are in C
(i.e. C<struct single_vldbentry *vldblist;>).  Of course, one can't send
pointers over the network, but one can use XDR pointers for sending
recursive data types such as lists and trees (an example of a linked list
will be demonstrated shortly).

=head2 Arrays

Fixed arrays are just like standard C array declarations (i.e. C<struct
UpdateEntry entries[20]>) without any side effect problems in
B<rxgen>. Since variable-length arrays have no explicit syntax in C, the
angle-brackets are used for it and the array declarations are actually
compiled into "struct"s. For example, declarations such as:

    const   MAXBULKSIZE     = 10000;
    const   MAXENTRIES      = 100;
    opaque  bulk<MAXBULKSIZE>;           /* At most 10000 items */
    int     hosts<>;                     /* any number of items */
    typedef vldbentry blkentries<100>;   /* Preferable array decl */

are compiled into the following structs:

    struct {
        u_int   bulk_len;       /* no of items */
        char    *bulk_val;      /* pointer to array */
    } bulk;

for the C<bulk> array, and similarly for the C<< blkentries<100> >> array,

    struct {
        u_int      blkentries_len;   /* no of items in array */
        vldbentry  *blkentries_val;  /* pointer to array */
    } blkentries;

Therefore the user should be aware of the "magically" generated structure
entries such as the number of items in the array (<array_name>_len) and
the pointer to the array (<array_name>_val) since some of the entries will
have to be filled in from the client/server programs.  A sample proc would
be:

    typedef vldbentry blkentries<MAXENTRIES>;
    proc GetBlk (OUT blkentries *vlentries) = VL_GETBLK;

or, more directly,

    GetBlk(OUT vldbentry vlentries<MAXENTRIES>) = VL_GETBLK;

Note that although the latest method is preferable since one does not have
to first use the typedef statement (and admittedly, programmers prefer
avoiding typedefs), one should realize that B<rxgen> does the structure
expansion and the xdr creation implicitly; therefore the user should be
aware of the C<vldbentries_val> and C<vldbentries_len> fields as before
(see following examples).

=head3 Array example I (least desirable)

Procedure declaration in the interface configuration:

    proc ListAttributes (IN vldblistbyattributes *attributes, 
                 INOUT blkentries *vldbentries) = VL_LISTATTRIBUTES;

Sample CLIENT code:

    blkentries entries, *pnt;
    entries.blkentries_len = 10;   /* max # returned entries */
    entries.blkentries_val = (vldbentry *)malloc(LEN);
                                   /* It must be set */

    code = VL_ListAttributes(&attributes, &entries);
    if (!code) {
        pnt = entries.blkentries_val;
        for (i=0; i < entries.blkentries_len; i++, pnt++)
                display_vldbentry(pnt);
        /* Make sure you free the allocated space */
        free((char *)entries.blkentries_val);   
    }

Sample SERVER code:

    VL_ListAttributes(attributes, entries)
    {
        vldbentry *singleentry = entries->blkentries_val;
        entries->blkentries_len = 0;

        while (copy_to_vldbentry(&vlentry, singleentry))
            singleentry++, vldbentries->entries_len++;
    }

Although this method for variable-size arrays works fine, there are some
major drawbacks.  The array parameter (i.e. vldbentries above) must be
declared as INOUT since we need to pass the max length of the expected
returned array; more importantly, a big (depending on the value of
C<_len>) chunk of junk code is going to be transferred to the server as
result of the IN(out) side-effect of the array.  It's an easy and
convenient method if the returned array size can be predicted from the
start and when the size is quite high.  This method is included as an
example of erroneous use (and abuse) of B<rxgen> and should not be used.

=head3 Array example II (Desirable method)

Procedure declaration in the interface configuration (using Example I
above):

    proc ListAttributes (IN vldblistbyattributes *attributes, 
        OUT blkentries *vldbentries) = VL_LISTATTRIBUTES;

Sample CLIENT code:

    blkentries entries, *pnt;

    code = VL_ListAttributes(&attributes, &entries);
    if (!code) {
        pnt = entries.blkentries_val;
        for (i=0; i < entries.blkentries_len; i++, pnt++)
                display_vldbentry(pnt);
        /* Make sure you free the allocated space (by rxgen) */
        free((char *)entries.blkentries_val);   
    }

Sample SERVER code:

    VL_ListAttributes(attributes, entries)
    {
        vldbentry *singleentry;
        entries->blkentries_len = 0;
        singleentry = entries->blkentries_val
            = (vldbentry *)malloc(MAXENTRIES * sizeof(vldbentry));

        while (copy_to_vldbentry(&vlentry, singleentry))
                singleentry++, vldbentries->entries_len++;
    }

This is the best (and simplest) way of using variable-size arrays as an
output parameter.  It is the responsibility of the server-side stub to
malloc() the adequate space which is automatically freed by the B<rxgen>
stub; the client side should free the space allocated by the
B<rxgen>-calling stub.

=head3 Array example III (Linked Lists)

Considering the following 3 declarations (could have applied some
optimizations) in the configuration file:

    typedef struct single_vldbentry *vldblist;
    struct single_vldbentry {
        vldbentry vlentry;
        vldblist  next_vldb;
    };

    struct vldb_list {
        vldblist node;
    };

and the rxgen procedure declaration:

    LinkedList (IN vldblistbyattributes *attributes, 
        OUT vldb_list *linkedentries) = VL_LINKEDLIST;

Sample CLIENT code:

    vldb_list       linkedvldbs;
    vldblist        vllist, vllist1;

    memset(&linkedvldbs, 0, sizeof(vldb_list));
    code = VL_LinkedList(&attributes, &nentries, &linkedvldbs);
    if (!code) {
        printf("We got %d vldb entries\n", nentries);
        for (vllist = linkedvldbs.node; vllist; vllist = vllist1) {
            vllist1 = vllist->next_vldb;
            display_entry(&vllist->vlentry);
            free((char *)vllist);
        }
    }

Sample SERVER code:

    VL_LinkedList(rxcall, attributes, nentries, linkedvldbs);
    {
        vldblist vllist, *vllistptr = &linkedvldbs->node;
        while (...) {
            vllist = *vllistptr
                = (single_vldbentry *)malloc (sizeof (single_vldbentry));
            copy_to_vldbentry(&tentry, &vllist->vlentry);
            nentries++;     
            vllistptr = &vllist->next_vldb;
        };
        *vllistptr = NULL;
    }

Using a linked list offers many advantages: Nothing is passed to the
server (the parameter is OUT), no additional overhead is involved, and the
caller doesn't have to explicitly prepare for an arbitrary return size.  A
drawback is that the caller has the responsibility of malloc() (on the
server) and free (on the client) of each entry (to avoid unwanted
core-leaks).  Another drawback is that since it's a recursive call, the C
stack will grow linearly with respect to the number of nodes in the list
(so it's wise to increase the Rx LWP stack if huge amounts of data are
expected back -- default stack size is 4K).  The advantages should
outweigh the disadvantages here.

It's important to pay attention to the comments of the three array
examples above particularly when they're references to when the user
should allocate/free space for the variable length arrays.  The mechanism
is very similar to the handling of strings thus you might need to review
the strings section above; note that the linked lists are handled somewhat
differently...

=head2 Miscellaneous examples

Below is an abbreviated version of a random interface file which shows
some of the common cases.

    /* Declaration of all structures used by the R.xg script interface */

    struct AFSFid {
        unsigned long Volume;
        unsigned long Vnode;
        unsigned long Unique;
    };

    typedef long ViceDataType;

    /* Note that TEST would be equivalent to "HEADER" only during the 
       processing of the header, *.h, file */

    #ifdef RPC_HDR
    #define TEST "HEADER"
    #else
    #define TEST "REST"
    #endif

    /* This is the standard *.xg specification file */

    package AFS_
    splitprefix IN=BEFORE_ OUT=AFTER_;
    Prefix Test

    proc Remove(IN struct AFSFid *Did, IN string volname<64>,
        OUT struct AFSStatus *Status) = AFS_REMOVE;

    DisconnectFS AUX_disconnectFS() = AFS_DISCONNECTFS;

    proc GetVolumeInfo(IN string Vid, 
        OUT struct VolumeInfo *Info) = AFS_GETVOLUMEINFO;

    /* You could have more than an interface per configuration */

    package VOTE_

    /* Using the "multi" feature; thus VOTE_Beacon can be called as an 
       multi-Rx call or as a regular call */

    Beacon (IN long state, long voteStart, 
        net_version *version, net_tid *tid) 
        multi = VOTE_BEACON;

    package DISK_

    /* Using the "split" feature */

    SendFile (IN long file, long offset, 
        long length, net_version *version) 
        split = DISK_SENDFILE;

=head2 Output of an actual interface configuration

We'll demonstrate some of the actual output generated by B<rxgen> by
following an abbreviated actual interface configuration.

=head3 Configuration file

Contents of the interface configuration file (F<vldbint.xg>):

    package VL_
    #include "vl_opcodes.h"   /* The opcodes are included here */
    %#include "vl_opcodes.h"  /* directly to other places */

    /* Current limitations on parameters that affect other packages
       (i.e. volume) */

    const   MAXNAMELEN      =       65;
    const   MAXNSERVERS     =       8;
    const   MAXTYPES        =       3;

    /* External (visible) representation of an individual vldb entry */

    struct vldbentry {
        char    name[MAXNAMELEN];       
        long    volumeType;             
        long    nServers;               
        long    serverNumber[MAXNSERVERS];
        long    serverPartition[MAXNSERVERS];
        long    serverFlags[MAXNSERVERS];
        u_long  volumeId[MAXTYPES];     
        long    flags;                  
    };

    typedef struct single_vldbentry  *vldblist;
    struct single_vldbentry {
        vldbentry VldbEntry;
        vldblist next_vldb;
    };

    struct vldb_list {
        vldblist node;
    };

    /* vldb interface calls */

    CreateEntry     (IN long Volid, 
                    vldbentry *newentry) = VLCREATEENTRY;

    GetEntryByName  (IN string volumename<MAXNAMELEN>, 
                    OUT vldbentry *entry) = VLGETENTRYBYNAME;

    GetNewVolumeId  (IN long bumpcount,
                    OUT long *newvolumid) = VLGETNEWVOLUMEID;

    ReplaceEntry    (IN long Volid, 
                    long voltype,
                    vldbentry *newentry,
                    long ReleaseType) multi = VLREPLACEENTRY;

    ListAttributes  (IN VldbListByAttributes *attributes, 
                    OUT long *nentries, 
                    OUT vldbentry bulkentries<MAXVLDBLEN>) 
                    = VLLISTATTRIBUTES;

    LinkedList      (IN VldbListByAttributes *attributes, 
                    OUT long *nentries, 
                    OUT vldb_list *linkedentries) = VLLINKEDLIST;

For a detailed description on the Rx-related calls inside the generated
stubs (i.e., rx_NewCall(), rx_EndCall()), along with details on what happens
inside certain calls (like xdrrx_create()) please refer to the Rx
documentation. Typing C<rxgen vldbint.xg> will result in the creation of
four files: F<vldbint.h>, F<vldbint.xdr.c>, F<vldbint.cs.c> and
F<vldbint.ss.c>.  A closer look at these files follows.

=head3 Header file (F<vldbint.h>)

    /* Machine generated file -- Do NOT edit */

    #include "vl_opcodes.h"  /* directly to other places */
    #define MAXNAMELEN 65
    #define MAXNSERVERS 8
    #define MAXTYPES 3

    struct vldbentry {
        char name[MAXNAMELEN];
        long volumeType;
        long nServers;
        long serverNumber[MAXNSERVERS];
        long serverPartition[MAXNSERVERS];
        long serverFlags[MAXNSERVERS];
        u_long volumeId[MAXTYPES];
        long flags;
    };
    typedef struct vldbentry vldbentry;
    bool_t xdr_vldbentry();

    typedef struct single_vldbentry *vldblist;
    bool_t xdr_vldblist();

    struct single_vldbentry {
        vldbentry VldbEntry;
        vldblist next_vldb;
    };
    typedef struct single_vldbentry single_vldbentry;
    bool_t xdr_single_vldbentry();

    struct vldb_list {
        vldblist node;
    };
    typedef struct vldb_list vldb_list;
    bool_t xdr_vldb_list();

    #include <rx/rx_multi.h>
    #define multi_VL_ReplaceEntry(Volid, voltype, newentry, ReleaseType) \
        multi_Body(StartVL_ReplaceEntry(multi_call, Volid, voltype,
                   newentry, ReleaseType), EndVL_ReplaceEntry(multi_call))

    typedef struct bulkentries {
        u_int bulkentries_len;
        vldbentry *bulkentries_val;
    } bulkentries;
    bool_t xdr_bulkentries();

    /* Opcode-related useful stats for package: VL_ */
    #define VL_LOWEST_OPCODE        501
    #define VL_HIGHEST_OPCODE       506
    #define VL_NUMBER_OPCODES       6

Notice that all structures are automatically typedef'ed and all C<const>s
are converted to C<#define>s. Some data structures, such as bulkentries,
are taken from procedure params (from ListAttributes proc). Thus, this
should be kept in mind when creating stubs piecemeal with B<rxgen> (i.e.,
using the B<-c>, B<-h>, B<-C>, or B<-S> flags).  Also, one of the side
effects of the C<multi> option (in C<ReplaceEntry> proc) is the generation
of the C<multi_VL_ReplaceEntry> above.

=head3 XDR routines for structures (vldbint.xdr.c)

    /* Machine generated file -- Do NOT edit */

    #include <rx/xdr.h>
    #include "vldbint.h"

    #include "vl_opcodes.h"  /* directly to other places */

    bool_t
    xdr_vldbentry(xdrs, objp)
        XDR *xdrs;
        vldbentry *objp;
    {
        if (!xdr_vector(xdrs, (char *)objp->name, MAXNAMELEN,
                        sizeof(char), xdr_char))
            return (FALSE);
        if (!xdr_long(xdrs, &objp->volumeType))
            return (FALSE);
        if (!xdr_long(xdrs, &objp->nServers))
            return (FALSE);
        if (!xdr_vector(xdrs, (char *)objp->serverNumber, MAXNSERVERS,
                        sizeof(long), xdr_long))
            return (FALSE);
        if (!xdr_vector(xdrs, (char *)objp->serverPartition,
                        MAXNSERVERS, sizeof(long), xdr_long))
            return (FALSE);
        if (!xdr_vector(xdrs, (char *)objp->serverFlags, MAXNSERVERS,
                        sizeof(long), xdr_long))
            return (FALSE);
        if (!xdr_vector(xdrs, (char *)objp->volumeId, MAXTYPES,
                        sizeof(u_long), xdr_u_long))
            return (FALSE);
        if (!xdr_long(xdrs, &objp->flags))
            return (FALSE);
        return (TRUE);
    }

    bool_t
    xdr_vldblist(xdrs, objp)
        XDR *xdrs;
        vldblist *objp;
    {
        if (!xdr_pointer(xdrs, (char **)objp,
                         sizeof(struct single_vldbentry), 
                         xdr_single_vldbentry))
            return (FALSE);
        return (TRUE);
    }

    bool_t
    xdr_single_vldbentry(xdrs, objp)
        XDR *xdrs;
        single_vldbentry *objp;
    {
        if (!xdr_vldbentry(xdrs, &objp->VldbEntry))
            return (FALSE);
        if (!xdr_vldblist(xdrs, &objp->next_vldb))
            return (FALSE);
        return (TRUE);
    }

    bool_t
    xdr_vldb_list(xdrs, objp)
        XDR *xdrs;
        vldb_list *objp;
    {
        if (!xdr_vldblist(xdrs, &objp->node))
            return (FALSE);
        return (TRUE);
    }

    bool_t
    xdr_bulkentries(xdrs, objp)
        XDR *xdrs;
        bulkentries *objp;
    {
        if (!xdr_array(xdrs, (char **)&objp->bulkentries_val,
                       (u_int *)&objp->bulkentries_len, MAXVLDBLEN,
                       sizeof(vldbentry), xdr_vldbentry))
            return (FALSE);
        return (TRUE);
    }

Note that the xdr_bulkentries() is automatically generated as a side
effect of a procedure parameter declaration.  Thus, if identical multiple
type parameter declarations are used, then multiply-defined xdr_* stubs
will be created!  We felt this was a better alternative to having the
B<rxgen> programmer deal with types such as bulkentries_1,
bulkentries_2...

=head3 Client-Side stub routines (vldbint.cs.c)

    /* Machine generated file -- Do NOT edit */

    #include <rx/xdr.h>
    #include <rx/rx.h>
    #include <afs/rxgen_consts.h>
    #include "vldbint.h"

    #include "vl_opcodes.h"  /* directly to other places */

    int VL_CreateEntry(z_conn, Volid, newentry)
        register struct rx_connection *z_conn;
        long Volid;
        vldbentry * newentry;
    {
        struct rx_call *z_call = rx_NewCall(z_conn);
        static int z_op = 501;
        int z_result;
        XDR z_xdrs;

        xdrrx_create(&z_xdrs, z_call, XDR_ENCODE);

        /* Marshal the arguments */
        if ((!xdr_int(&z_xdrs, &z_op))
             || (!xdr_long(&z_xdrs, &Volid))
             || (!xdr_vldbentry(&z_xdrs, newentry))) {
                z_result = RXGEN_CC_MARSHAL;
                goto fail;
        }

        z_result = RXGEN_SUCCESS;
    fail:
        return rx_EndCall(z_call, z_result);
    }

    int VL_GetEntryByName(z_conn, volumename, entry)
        register struct rx_connection *z_conn;
        char * volumename;
        vldbentry * entry;
    {
        struct rx_call *z_call = rx_NewCall(z_conn);
        static int z_op = 504;
        int z_result;
        XDR z_xdrs;

        xdrrx_create(&z_xdrs, z_call, XDR_ENCODE);

        /* Marshal the arguments */
        if ((!xdr_int(&z_xdrs, &z_op))
             || (!xdr_string(&z_xdrs, &volumename, 65))) {
                z_result = RXGEN_CC_MARSHAL;
                goto fail;
        }

        /* Un-marshal the reply arguments */
        z_xdrs.x_op = XDR_DECODE;
        if ((!xdr_vldbentry(&z_xdrs, entry))) {
                z_result = RXGEN_CC_UNMARSHAL;
                goto fail;
        }

        z_result = RXGEN_SUCCESS;
    fail:
        return rx_EndCall(z_call, z_result);
    }

    int VL_GetNewVolumeId(z_conn, bumpcount, newvolumid)
        register struct rx_connection *z_conn;
        long bumpcount;
        long * newvolumid;
    {
        struct rx_call *z_call = rx_NewCall(z_conn);
        static int z_op = 505;
        int z_result;
        XDR z_xdrs;

        xdrrx_create(&z_xdrs, z_call, XDR_ENCODE);

        /* Marshal the arguments */
        if ((!xdr_int(&z_xdrs, &z_op))
             || (!xdr_long(&z_xdrs, &bumpcount))) {
                z_result = RXGEN_CC_MARSHAL;
                goto fail;
        }

        /* Un-marshal the reply arguments */
        z_xdrs.x_op = XDR_DECODE;
        if ((!xdr_long(&z_xdrs, newvolumid))) {
                z_result = RXGEN_CC_UNMARSHAL;
                goto fail;
        }

        z_result = RXGEN_SUCCESS;
    fail:
        return rx_EndCall(z_call, z_result);
    }

    int VL_ReplaceEntry(z_conn, Volid, voltype, newentry, ReleaseType)
        register struct rx_connection *z_conn;
        long Volid, voltype, ReleaseType;
        vldbentry * newentry;
    {
        struct rx_call *z_call = rx_NewCall(z_conn);
        static int z_op = 506;
        int z_result;
        XDR z_xdrs;

        xdrrx_create(&z_xdrs, z_call, XDR_ENCODE);

        /* Marshal the arguments */
        if ((!xdr_int(&z_xdrs, &z_op))
             || (!xdr_long(&z_xdrs, &Volid))
             || (!xdr_long(&z_xdrs, &voltype))
             || (!xdr_vldbentry(&z_xdrs, newentry))
             || (!xdr_long(&z_xdrs, &ReleaseType))) {
                z_result = RXGEN_CC_MARSHAL;
                goto fail;
        }

        z_result = RXGEN_SUCCESS;
    fail:
        return rx_EndCall(z_call, z_result);
    }

    int StartVL_ReplaceEntry(z_call, Volid, voltype, newentry, ReleaseType)
        register struct rx_call *z_call;
        long Volid, voltype, ReleaseType;
        vldbentry * newentry;
    {
        static int z_op = 506;
        int z_result;
        XDR z_xdrs;

        xdrrx_create(&z_xdrs, z_call, XDR_ENCODE);

        /* Marshal the arguments */
        if ((!xdr_int(&z_xdrs, &z_op))
             || (!xdr_long(&z_xdrs, &Volid))
             || (!xdr_long(&z_xdrs, &voltype))
             || (!xdr_vldbentry(&z_xdrs, newentry))
             || (!xdr_long(&z_xdrs, &ReleaseType))) {
                z_result = RXGEN_CC_MARSHAL;
                goto fail;
        }

        z_result = RXGEN_SUCCESS;
    fail:
        return z_result;
    }

    int EndVL_ReplaceEntry(z_call)
        register struct rx_call *z_call;
    {
        int z_result;
        XDR z_xdrs;

        z_result = RXGEN_SUCCESS;
    fail:
        return z_result;
    }

    int VL_ListAttributes(z_conn, attributes, nentries, bulkentries_1)
        register struct rx_connection *z_conn;
        VldbListByAttributes * attributes;
        long * nentries;
        bulkentries * bulkentries_1;
    {
        struct rx_call *z_call = rx_NewCall(z_conn);
        static int z_op = 511;
        int z_result;
        XDR z_xdrs;

        xdrrx_create(&z_xdrs, z_call, XDR_ENCODE);

        /* Marshal the arguments */
        if ((!xdr_int(&z_xdrs, &z_op))
             || (!xdr_VldbListByAttributes(&z_xdrs, attributes))) {
                z_result = RXGEN_CC_MARSHAL;
                goto fail;
        }

        /* Un-marshal the reply arguments */
        z_xdrs.x_op = XDR_DECODE;
        if ((!xdr_long(&z_xdrs, nentries))
             || (!xdr_bulkentries(&z_xdrs, bulkentries_1))) {
                z_result = RXGEN_CC_UNMARSHAL;
                goto fail;
        }

        z_result = RXGEN_SUCCESS;
    fail:
        return rx_EndCall(z_call, z_result);
    }

    int VL_LinkedList(z_conn, attributes, nentries, linkedentries)
        register struct rx_connection *z_conn;
        VldbListByAttributes * attributes;
        long * nentries;
        vldb_list * linkedentries;
    {
        struct rx_call *z_call = rx_NewCall(z_conn);
        static int z_op = 512;
        int z_result;
        XDR z_xdrs;

        xdrrx_create(&z_xdrs, z_call, XDR_ENCODE);

        /* Marshal the arguments */
        if ((!xdr_int(&z_xdrs, &z_op))
             || (!xdr_VldbListByAttributes(&z_xdrs, attributes))) {
                z_result = RXGEN_CC_MARSHAL;
                goto fail;
        }

        /* Un-marshal the reply arguments */
        z_xdrs.x_op = XDR_DECODE;
        if ((!xdr_long(&z_xdrs, nentries))
             || (!xdr_vldb_list(&z_xdrs, linkedentries))) {
                z_result = RXGEN_CC_UNMARSHAL;
                goto fail;
        }

        z_result = RXGEN_SUCCESS;
    fail:
        return rx_EndCall(z_call, z_result);
    }

Notice the side effect of the C<multi> feature (three different modules
for C<ReplaceEntry> proc).

=head3 Server-Side stub routines (vldbint.ss.c)

    /* Machine generated file -- Do NOT edit */

    #include <rx/xdr.h>
    #include <rx/rx.h>
    #include <afs/rxgen_consts.h>
    #include "vldbint.h"

    #include "vl_opcodes.h"  /* directly to other places */

    long _VL_CreateEntry(z_call, z_xdrs)
        struct rx_call *z_call;
        XDR *z_xdrs;
    {
        long z_result;
        long Volid;
        vldbentry newentry;

        if ((!xdr_long(z_xdrs, &Volid))
             || (!xdr_vldbentry(z_xdrs, &newentry))) {
                z_result = RXGEN_SS_UNMARSHAL;
                goto fail;
        }

        z_result = VL_CreateEntry(z_call, Volid, &newentry);
    fail:
        return z_result;
    }

    long _VL_GetEntryByName(z_call, z_xdrs)
        struct rx_call *z_call;
        XDR *z_xdrs;
    {
        long z_result;
        char *volumename = (char *)0;
        vldbentry entry;

        if ((!xdr_string(z_xdrs, &volumename, 65))) {
                z_result = RXGEN_SS_UNMARSHAL;
                goto fail;
        }

        z_result = VL_GetEntryByName(z_call, &volumename, &entry);
        z_xdrs->x_op = XDR_ENCODE;
        if ((!xdr_vldbentry(z_xdrs, &entry)))
                z_result = RXGEN_SS_MARSHAL;
    fail:
        z_xdrs->x_op = XDR_FREE;
        if (!xdr_string(z_xdrs, &volumename, 65)) goto fail1;
        return z_result;
    fail1:
        return RXGEN_SS_XDRFREE;
    }

    long _VL_GetNewVolumeId(z_call, z_xdrs)
        struct rx_call *z_call;
        XDR *z_xdrs;
    {
        long z_result;
        long bumpcount;
        long newvolumid;

        if ((!xdr_long(z_xdrs, &bumpcount))) {
                z_result = RXGEN_SS_UNMARSHAL;
                goto fail;
        }

        z_result = VL_GetNewVolumeId(z_call, bumpcount, &newvolumid);
        z_xdrs->x_op = XDR_ENCODE;
        if ((!xdr_long(z_xdrs, &newvolumid)))
                z_result = RXGEN_SS_MARSHAL;
    fail:
        return z_result;
    }

    long _VL_ReplaceEntry(z_call, z_xdrs)
        struct rx_call *z_call;
        XDR *z_xdrs;
    {
        long z_result;
        long Volid, voltype, ReleaseType;
        vldbentry newentry;

        if ((!xdr_long(z_xdrs, &Volid))
             || (!xdr_long(z_xdrs, &voltype))
             || (!xdr_vldbentry(z_xdrs, &newentry))
             || (!xdr_long(z_xdrs, &ReleaseType))) {
                z_result = RXGEN_SS_UNMARSHAL;
                goto fail;
        }

        z_result = VL_ReplaceEntry(z_call, Volid, voltype, &newentry,
                                   ReleaseType);
    fail:
        return z_result;
    }

    long _VL_ListAttributes(z_call, z_xdrs)
        struct rx_call *z_call;
        XDR *z_xdrs;
    {
        long z_result;
        VldbListByAttributes attributes;
        long nentries;
        bulkentries bulkentries_1;

        if ((!xdr_VldbListByAttributes(z_xdrs, &attributes))) {
                z_result = RXGEN_SS_UNMARSHAL;
                goto fail;
        }

        z_result = VL_ListAttributes(z_call, &attributes, &nentries,
                                     &bulkentries_1);
        z_xdrs->x_op = XDR_ENCODE;
        if ((!xdr_long(z_xdrs, &nentries))
             || (!xdr_bulkentries(z_xdrs, &bulkentries_1)))
                z_result = RXGEN_SS_MARSHAL;
    fail:
        z_xdrs->x_op = XDR_FREE;
        if (!xdr_bulkentries(z_xdrs, &bulkentries_1)) goto fail1;
        return z_result;
    fail1:
        return RXGEN_SS_XDRFREE;
    }

    long _VL_LinkedList(z_call, z_xdrs)
        struct rx_call *z_call;
        XDR *z_xdrs;
    {
        long z_result;
        VldbListByAttributes attributes;
        long nentries;
        vldb_list linkedentries;

        if ((!xdr_VldbListByAttributes(z_xdrs, &attributes))) {
                z_result = RXGEN_SS_UNMARSHAL;
                goto fail;
        }

        z_result = VL_LinkedList(z_call, &attributes, &nentries,
                                 &linkedentries);
        z_xdrs->x_op = XDR_ENCODE;
        if ((!xdr_long(z_xdrs, &nentries))
             || (!xdr_vldb_list(z_xdrs, &linkedentries)))
                z_result = RXGEN_SS_MARSHAL;
    fail:
        return z_result;
    }

    long _VL_CreateEntry();
    long _VL_GetEntryByName();
    long _VL_GetNewVolumeId();
    long _VL_ReplaceEntry();
    long _VL_ListAttributes();
    long _VL_LinkedList();

    static long (*StubProcsArray0[])() = {_VL_CreateEntry,
        _VL_GetEntryByName, _VL_GetNewVolumeId, _VL_ReplaceEntry,
        _VL_ListAttributes, _VL_LinkedList};

    VL_ExecuteRequest(z_call)
        register struct rx_call *z_call;
    {
        int op;
        XDR z_xdrs;
        long z_result;

        xdrrx_create(&z_xdrs, z_call, XDR_DECODE);
        if (!xdr_int(&z_xdrs, &op))
            z_result = RXGEN_DECODE;
        else if (op < VL_LOWEST_OPCODE || op > VL_HIGHEST_OPCODE)
            z_result = RXGEN_OPCODE;
        else
            z_result = (*StubProcsArray0[op - VL_LOWEST_OPCODE])
                (z_call, &z_xdrs);
        return z_result;
    }

If there were gaps in the procedures' opcode sequence the code for
VL_ExecuteRequest() routine would be have been drastically different (it
would have been a case statement for each procedure).

=head1 NOTES

B<rxgen> is implemented from Sun's B<rpcgen> utility.

When the C<%#include <include file>> feature is used make sure that you
don't have any B<rxgen> language features (i.e. %#defines) since you'll
get syntax errors during compilations..

Since this is an ongoing project many of the above may change/disappear
without a major warning.

=head1 SEE ALSO

I<Rxgen Syntax Summary>: Summary description of rxgen's grammar.

I<Rpcgen Programming Guide>: Sun's RPC protocol compiler.  B<rxgen> was
implemented as an extension to that compiler.

I<External Data Representation: Sun Technical Notes>: Detailed examples in
using XDR.

I<RPCL Syntax Summary>: Summary of Sun's Remote Procedure Call Language.

I<Rx>: An extended Remote Procedure Call Protocol.

I<rgen>: An earlier version of a similar stub generator used for the R RPC
protocol.

=head1 COPYRIGHT

IBM Corporation 2000. <http://www.ibm.com/> All Rights Reserved.

This documentation is covered by the IBM Public License Version 1.0.  It
was converted from the original TeX B<rxgen> manual to POD by Russ
Allbery.
