RXGEN(1)
πΈ RXGEN(1) πΈ
AFS Command Reference π and yeah babes, today's main character is rxgen from the AFS Command Reference. Sit down, grab a strawberry π, we're doing this.
β¨ NAME β¨
rxgen β the stub generator for the Rx remote procedure call package. She writes the glue code so you don't have to, fr fr. π«Ά
β¨ SYNOPSIS β¨
rxgen [-h | -c | -C | -S | -r] [-dkp]
[-I dir] [-P prefix] [-o outfile] [infile]
Like, that tiny line is doing SO much, let me tell you. π
β¨ DESCRIPTION β¨
Okay so rxgen is the tool that generates C code to implement the Rx RPC protocol. Basically it takes a description of an application interface β written in something that looks suspiciously like C β as its input, and it pops out a whole pile of server and/or client stub routines that you then link with your RPC-based programs. π
The way that these stubs work is giving: they let a program invoke remote procedures through local procedure calls. You call a local function, and it secretly ships everything across the network. Magic. Iconic. No cap. β¨
And like, full transparency bestie β rxgen is based on Sun's rpcgen (version 3.9), but she does not maintain compatibility with rpcgen RPC descriptions. So don't come over here assuming your old rpcgen files will just vibe. They won't. π
β¨ OPTIONS β¨
So rxgen operates in several different modes, and she's flexible like that. π¦ You can produce the generated output files individually (using one of -h, -c, -C, or -S) or collectively, as a little group chat. π
- When you use the default (i.e., no options at all), all output files get created.
- When you use the
-rflag, the output is limited to the server stubs (-Cand-S).
The following describes the types of generated output files β and just so we're clear, filename refers to the main output filename. Keep up, bestie. π
-hβ Generate the C data definitions (a header file) from standard RPCL definitions. Default extension:filename.h. π-cβ Compile the XDR routines required to serialize the protocol described by RPCL. Generates XDR routines for all declarations. Default extension:filename.xdr.c. π₯-Cβ Generate all the client-side stub routines. Default extension:filename.cs.c. Calling a routine in this file causes the arguments to get all packed up and sent via Rx (or R). Postage paid. π¦-Sβ Generate all the server-side stub routines. Default extension:filename.ss.c. The arguments get unpacked, and then the corresponding server routine is called. She's serving. π½οΈ-rβ Generate the two default extension files produced by the-Cand-Soptions. Just the server-side duo, nothing else.
π the "works with anything" options π
The following options can be used on any combination of rxgen calls. Slay, versatility. π
-kβ Must be specified when the generated code is intended to be used by the kernel. Special "includes" and other specifics get produced when the target output is for the kernel. Like, it's not optional if you're going kernel-mode, she said what she said. π§-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 behaviour is to generate individual Execute Request stubs for each package. So-pis the "y'all are one big happy family now" button. π-Idir β Similar to the-Iflag in the C compiler (cc). This flag gets passed to the pre-processor (cpp) so that directory dir is searched before the standard lookup list for#includefiles. And as expected, multiple-Iflags can be used simultaneously. Stack 'em. π-Pprefix β The prefix string following this switch is prepended to all generated output files. Super useful when multiple runs want to produce different versions of the same interface β say, kernel and non-kernel versions. Two flavours, one vibe. π-dβ Debugging mode; only needed whenrxgenherself is to be debugged (say, viadbx). Like, this isrxgenon the therapist's couch. ποΈ-ooutfile β Specify the name of the output file. If none is specified, the standard output is used (-c,-h,-C, and-Smodes only). π
Heads up bestie: if an output file is specified in a multi-output file option (such as the default, or with option -r), then the outfile replaces the name generated by default (which is based on the configuration's main file name). So don't be surprised when your carefully chosen name shows up everywhere. π
β¨ rxgen SYNTAX SUMMARY β¨
Okay this next bit is the grammar, and honestly it's giving "CS textbook but make it fashion". Read it slow, it's literally the whole language. π¦
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
All the _ident girlies π
<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> and <Standard RPCL type decl>
Sun's RPCL language syntax (see rpcgen(1))
And that's where this part wraps... but we're not done, bestie β there is SO much more coming. β¨π
β¨ rxgen COMMANDS β¨
π¬ Comments and Preprocessing
So your input interface can totally contain preprocessor directives, bestie, and they get passed straight through the C preprocessor (aka cpp). β¨ And since the preprocessor runs over all the input files before rxgen itself ever interprets them, literally every cpp directive (#include, #ifdefs, #defines, etc.) is legal and welcomed inside an rxgen input file. π«Ά Ofc none of those preprocessor directives end up in any of the generated files β they're just doing their lil behind-the-scenes thing. π
To make it easier to tell the different types of output files apart, rxgen defines certain special cpp symbols for the rxgen programmer to use:
RPC_HDRβ defined when compiling into header files,filename.hπRPC_XDRβ defined when compiling into xdr files,filename.xdr.cβ¨RPC_CLIENTβ defined when compiling into client stubs,filename.cs.cπRPC_SERVERβ defined when compiling into server stubs,filename.ss.cπΈ
On top of that, rxgen does a little preprocessing of its own. Any line beginning with % gets passed directly into the output file, totally uninterpreted by rxgen. π And if you wanna do some heavy en masse dumping of uninterpreted code, the move is to put all that code in an #include file and pass it in preceded by %. The input interface may also contain any C-style comments, which are, ofc, ignored. π¦
Interpretation is token-based, so don't stress about special line-orientation of separate statements β not necessary. π rxgen also serves up a quite rich and helpful set of error reports, identifying them by exact line location and error type (so helpful, honestly π). And rxgen will automatically generate #include lines for standard include files β such as rx/xdr.h and rx/rx.h β along with the generated header file from this interface. Periodt.
π Prefixing stub procedures
The package statement tells rxgen the name of the interface package. It's used for prefixing the naming of all generated stub routines and the execute request procedure. Like, for example:
package AFS_
causes the execute request procedure to be named AFS_ExecuteRequest β and okay, heads up: Warning: in the older version an additional "_" was appended after the package name to the ExecuteRequest name, so make sure you don't have an ExecuteRequest interface routine. π And a given stub routine, say Fetch, gets actually named AFS_Fetch. Slay. π
Multiple package statements (current maximum size is 10) per configuration are permitted, and they're useful when multiple sets of interfaces are implemented (peep the example at the end). π Note that in such cases, use of the -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 though, independent ExecuteRequest procedures will be created for each packaged group of remote procedure calls.
The prefix statement supplies a name to prepend to all calls to remote procedure names in the ExecuteRequest stub routine. It's useful when the server makes RPC calls to other servers (say, for debugging purposes π). For example:
prefix S
causes the name "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. Iconic.
π¦ rxgen procedure declaration
The proc statement is the most common (and meaningful, fr fr) in the rxgen interface. Its syntax description is:
[proc] [<proc_name>] [<server_stub>] (<arg>, ..., <arg>)
[split | multi] [= <opcode>] ;
where:
"proc"is an optional prefix of the procedure statement. This is just a stylistic item and not a required procedure delimiter.<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 lastpackagestatement (i.e.,"package RCallBack"and the declaration of the"RCallBack"procedure).<server_stub>, if present, causes theExecuteRequestprocedure to call that stub instead of the automatically generated stub when a call with that opcode is decoded.<opcode>is a constant or symbol that is the opcode for that procedure. One might use the preprocessor features (i.e.,#define), theconstRPC-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, theExecuteRequestprocedure uses thecasestatement rather than the faster (and smaller, codewise) indexed array method. π§
Also, 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 rxgen programmer. Also, notice that the 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 startingopcode (for lack of a better name) 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 startingopcode statement. rxgen will complain in all such cases.
- The 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! β οΈ)
"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's 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"Begin") is used to pass allINandINOUTparameters to the server side. The second (with the default prefix of"End") is used to get back theINOUTandOUTparameters 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 packageAFS_:
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 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. π
- The
"multi"option is nearly identical to the"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"multi"option is the generation of a special macro (i.e.,"multi_<Procedure-name>") which passes back as arguments the"Begin"and"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. π₯
π OBSOLETE rxgen FEATURES
Okay bestie, listen. π Although the following rxgen commands are still in effect, they will soon be removed since there are better alternatives.
DO NOT USE THEM! π«
The 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 rxgen that the entry "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 special is the customized statement, which is simply the "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 (-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 "xdr_CBS" routine would be provided by the user where during the DECODE xdr opcode, appropriate space for the "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. π
π 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 "xdr_" prepended to it. A selected set of rxgen features is presented below, but for a more comprehensive one (unions, complex examples, etc) please refer to the rpcgen Programming Guide and eXternal Data Representation: Sun Technical Notes. π
Typedefs π«
The RPC typedef statement is literally identical to the C one, bestie β like, typedef <declaration>, that's it, same energy. By default, most user declarations (we're talking structs, unions, etc.) get automatically typedef'ed by rxgen anyway. And since it makes parsing simpler, its usage is recommended by rxgen scripts. Slay.
Strings π
Okay so the C char * string convention? Kind of ambiguous, ngl. It's 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 a string can be arbitrary (like "bigname" up there) or, preferably, specified in angle brackets (i.e. "name" and "volname"). In practice you 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 you to understand when string parameters should be allocated and/or freed by your client and/or server programs, bestie. A short analysis on string parameter handling follows (and psst β a similar method is used for the handling of variable length arrays, as we'll show later π):
- In the client side:
INandINOUTstring parameters are the programmer's responsibility and should be allocated (static or viamalloc) before calling the rpc and freed (ifmallocwas used) after the rpc's return in your client program. Of course, forINOUTparameters, 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 rxgen client stubs (in filename.cs.c) and must be freed by the client program. Admittedly, this could be somewhat confusing since you need to free something that you didn't allocate. π΅βπ«
- In the server side:
INandINOUTstring parameters are automatically malloced (based on the size of incoming strings) by therxgenserver stubs (infilename.ss.c) before they are passed to your server procedure. That space is automatically freed just before therxgenserver stub returns, so you don't need to do anything special forINandINOUTstring parameters. π«Ά
OUT string parameters must be malloced by your server procedure (i.e. a null pointer is passed to it by the rxgen server stub) and it is automatically freed at the end of the 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 rxgen server stub).
Note that forINOUTandOUTstring parameters, in both the client and server sides their arguments must be char of pointers (i.e.char **). Don't come for me, that's just what it says. π
Pointers π
Pointer declarations in RPC are also exactly as they are in C (i.e. struct single_vldbentry *vldblist;). Of course, one can't send pointers over the network β like, obviously β 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, keep reading bestie. π¦
Arrays πΈ
Fixed arrays are just like standard C array declarations (i.e. struct UpdateEntry entries[20]) without any side effect problems in 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 "bulk" array, and similarly for the "blkentries<100>" array,
struct {
u_int blkentries_len; /* no of items in array */
vldbentry *blkentries_val; /* pointer to array */
} blkentries;
Therefore you 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 you don't have to first use the typedef statement (and admittedly, programmers prefer avoiding typedefs), you should realize that rxgen does the structure expansion and the xdr creation implicitly β so be aware of the "vldbentries_val" and "vldbentries_len" fields as before (see following examples). β¨
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, bestie. π 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 "_len") chunk of junk code is going to be transferred to the server as a 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 rxgen and should not be used. Periodt.
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 rxgen stub; the client side should free the space allocated by the rxgen-calling stub.
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. π
okay so real talk about those three array examples up there π pay attention to the comments, especially the parts that tell you when you, the user, have to allocate and free space for variable length arrays. The whole mechanism is basically the same as how strings work, so you might wanna scroll back up and reread the strings section π«Ά β but heads up, linked lists get handled a little differently than that, so don't @ me when they surprise you. π
Miscellaneous examples
Below is a lil abbreviated version of a random interface file that shows off 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;
Lowkey iconic, right? π
Output of an actual interface configuration
okay bestie, now we're gonna demonstrate some of the actual output that rxgen generates, by following an abbreviated actual interface configuration. it's giving β¨ before and after β¨
Configuration file
Contents of the interface configuration file (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 of the Rx-related calls tucked inside the generated stubs (like rx_NewCall(), rx_EndCall(), bestie π―), plus all the deets on what actually goes down inside certain calls (say, xdrrx_create()), please refer to the Rx documentation. π Typing rxgen vldbint.xg will result in the creation of four files: vldbint.h, vldbint.xdr.c, vldbint.cs.c and vldbint.ss.c. Let's take a closer look at each one. π
Header file (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 how all the structures get automatically typedef'ed, and every const gets converted into a #define. πͺ Some data structures β like bulkentries, bestie, look at her β are taken straight from procedure params (from the ListAttributes proc). So keep that in mind when you're creating stubs piecemeal with rxgen (i.e. using the -c, -h, -C, or -S flags). Also, one of the side effects of the "multi" option (in the "ReplaceEntry" proc) is the generation of that multi_VL_ReplaceEntry up there. no cap. π
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 xdr_bulkentries() is automatically generated as a side effect of a procedure parameter declaration. Which means β and this is important, bestie β if identical multiple type parameter declarations get used, then multiply-defined xdr_* stubs will be created! π We felt this was a better alternative to having the rxgen programmer deal with types such as bulkentries_1, bulkentries_2β¦ so like, that's the tradeoff. π
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);
}
and there's still more where that came from, so don't go anywhere π¦β¨
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);
}
okay bestie, clock the tea on what you just scrolled past π β that whole stretch of code is the side effect of the "multi" feature: you get three different modules for the "ReplaceEntry" proc. Not a typo, not a glitch, that's literally the feature doing its little thing. VL_ReplaceEntry, StartVL_ReplaceEntry, EndVL_ReplaceEntry β three of them, all your children, all valid. π
β¨
π Server-Side stub routines (vldbint.ss.c) π
now flip the phone around π± β this is the other side of the call, the server-side stubs. same energy, opposite direction. π¦
/* 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;
}
notice how the server side is the one doing the un-marshal β call β marshal little dance π©° and the z_xdrs->x_op = XDR_FREE cleanup era at the bottom? yeah. that's the whole personality of a generated stub file, bestie β /* Machine generated file -- Do NOT edit */ and they mean it π
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;
}
okay bestie look at this π
<StubProcsArray0> is giving β¨clean lookup tableβ¨ and VL_ExecuteRequest() is just doing index math: op - VL_LOWEST_OPCODE and boom, straight into the array. Main character energy fr fr.
But here's the β¨loreβ¨: if there were gaps in the procedures' opcode sequence, the code for VL_ExecuteRequest() would have been drastically different β it would have been a case statement for each procedure. π No cute little array index, just a big ol' switch. So yeah, contiguous opcodes are lowkey the whole reason this shit is so short π«Ά.
πΈ NOTES πΈ
rxgenis implemented from Sun'srpcgenutility. π§ Heritage, y'all.- When the
%#include <include file">feature is used make sure that you don't have any rxgen language features (i.e.%#defines) since you'll get syntax errors during compilations. π Don't do it. Just don't. - Since this is an ongoing project many of the above may change/disappear without a major warning. π Nothing is stable, everything is vibe, keep your eyes open bestie.
π SEE ALSO π
- Rxgen Syntax Summary: Summary description of
rxgen's grammar. π - Rpcgen Programming Guide: Sun's RPC protocol compiler.
rxgenwas implemented as an extension to that compiler. π - External Data Representation: Sun Technical Notes: Detailed examples in using XDR. β¨
- RPCL Syntax Summary: Summary of Sun's Remote Procedure Call Language. π
- Rx: An extended Remote Procedure Call Protocol. π¦
- rgen: An earlier version of a similar stub generator used for the R RPC protocol. (the ancestral icon, we stan) π
π 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 rxgen manual to POD by Russ Allbery.
And that's the whole page bestie!! π«Ά We did it, we survived, we are so back π. This one shipped as OpenAFS, dated 2026-07-27, filed under RXGEN(1) β go forth and generate those stubs, you absolute icon π
πβ¨
π¦ where this page comes from
- ubuntu stonking: libopenafs-dev 1.8.16.1-1
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms