What Is rpcgen?

The rpcgen tool generates remote program interface
modules. It compiles source code written in the RPC language. The RPC language
is similar in syntax and structure to C. The rpcgen tool
produces one or more C language source modules, which are then compiled by
a C compiler.

The default output of rpcgen is:

A header file of definitions common to the server and the
client

A set of XDR routines
that translate each data type defined in the header file

A stub program for the server

A stub program for the client

rpcgen can optionally generate:

Various transports

A timeout for servers

Server stubs that are multithread safe

Server stubs that are not main programs

C-style arguments passing ANSI C-compliant code

An RPC dispatch table that checks authorizations and invokes
service routines

rpcgen significantly reduces the development time
that would otherwise be spent developing low-level routines. Handwritten routines
link easily with the rpcgen output. For a discussion of
RPC programming without rpcgen, see Chapter 4, Programmer's Interface to RPC.

SunOS 9 Software Environment Features

This section lists the features found in the current rpcgen code generator.

SunOS C-style Mode: rpcgen has two compilation
modes, C-style and default. In C-style mode arguments can be passed by value,
instead of as pointers to a structure. It also supports passing multiple arguments.
The default mode is the same as in previous releases. See Compile-Time C-style Mode
for the example code for both modes.

SunOS Multithread-Safe Code: rpcgen generates
MT-safe code for use in a threaded environment. By default, the code generated
by rpcgen is not MT safe. See Compile-Time MT-Safe Code
for the description and example code.

rpcgen Tutorial

rpcgen provides
programmers a direct way to write distributed applications. Server procedures
can be written in any language that observes procedure-calling conventions.
These procedures are linked with the server stub produced by rpcgen to form an executable server program. Client procedures are written
and linked in the same way.

This section presents some basic rpcgen programming
examples. Refer also to the rpcgen(1) man page.

Converting Local Procedures to Remote Procedures

Assume that an application runs on a single computer and you want to
convert it to run in a “distributed” manner on a network. This
example shows the stepwise conversion of this program that writes a message
to the system console. The following code example shows the original program.

If the printmessage() function is turned into a remote
procedure, the function can be called from anywhere in the network.

First, determine the data types of all procedure-calling arguments and
the resulting argument. The calling argument of printmessage()
is a string, and the result is an integer. You can write a protocol specification
in the RPC language that describes the remote version of printmessage(). The RPC language source code for such a specification is:

Remote procedures are always declared as part of remote programs. The
previous code declares an entire remote program that contains the single procedure PRINTMESSAGE. In this example, the PRINTMESSAGE
procedure is declared to be procedure 1, in version 1 of the remote program MESSAGEPROG, with the
program number 0x20000001. See Appendix B, RPC Protocol and Language Specification
for guidance on choosing program numbers.

Version numbers are incremented when functionality is changed in the
remote program. Existing procedures can be changed or new ones can be added.
More than one version of a remote program can be defined and a version can
have more than one procedure defined.

Note that the program and procedure names are declared with all capital
letters.

Note also that the argument type is string and not char * as it would be in C. This is because a char * in
C is ambiguous. char usually means an array of characters, but
it could also represent a pointer to a single character. In the RPC language,
a null-terminated array of char is called a string.

The declaration of the remote procedure printmessage_1()
differs from that of the local procedure printmessage()
in four ways:

printmessage_1() takes a pointer to the
character array instead of the pointer itself. This principle is true of all
remote procedures when the -N option is not used. These procedures
always take pointers to their arguments rather than the arguments themselves.
Without the -N option, remote procedures are always called
with a single argument. If more than one argument is required the arguments
must be passed in a struct.

printmessage_1() is called with two arguments.
The second argument contains information on the context of an invocation:
the program, version, and procedure numbers; raw and canonical credentials;
and an SVCXPRT structure pointer. The SVCXPRT structure contains transport information. All of the
information is made available in case the invoked procedure requires it to
perform the request.

printmessage_1() returns a pointer to an
integer instead of the integer itself. This principle is also true of remote
procedures when the -N option is not used. These procedures
return pointers to the result. The result should be declared static unless the -M (multithread) or -A
(Auto mode) options are used. Ordinarily, if the result is declared local
to the remote procedure, references to the result by the server stub are invalid
after the remote procedure returns. In the case of -M and -A options, a pointer to the result is passed as a third argument
to the procedure, so the result is not declared in the procedure.

An _1 is appended to the printmessage_1() name. In general, all remote procedures calls generated by rpcgen are named as follows: the procedure name in the program definition
(here PRINTMESSAGE) is converted to all lowercase letters,
an underbar (_) is appended to it, and the version number (here 1) is appended.
This naming scheme enables you to have multiple versions of the same procedure.

The following code example shows the main client program that calls
the remote procedure.

In the example code, a client handle is created by the RPC library routine clnt_create(). This client handle is passed to the stub routine that
calls the remote procedure. See Chapter 4, Programmer's Interface to RPC for details
on how the client handle can be created in other ways. If no more calls are
to be made using the client handle, destroy it with a call to clnt_destroy() to conserve system resources.

The last parameter to clnt_create() is visible, which specifies that any transport noted as visible in /etc/netconfig can be used. For further information on transports,
see the /etc/netconfig file and its description in Programming Interfaces Guide.

The remote procedure printmessage_1() is called exactly
the same way as it is declared in msg_proc.c, except
for the inserted client handle as the second argument. The remote procedure
also returns a pointer to the result instead of the result.

The remote procedure call can fail in two ways. The RPC mechanism can
fail or an error can occur in the execution of the remote procedure. In the
former case, the remote procedure printmessage_1() returns
a NULL. In the latter case, the error reporting is application
dependent. Here, the error is returned through *result.

rpcgen is used to generate the header files (msg.h), client stub (msg_clnt.c), and server
stub (msg_svc.c). Then, two programs are compiled: the
client program rprintmsg and the server program msg_server. The C object files must be linked with the library libnsl, which contains all of the networking functions, including
those for RPC and XDR.

In this example, no XDR routines were generated because the application
uses only the basic types that are included in libnsl.

rpcgen received the input file msg.x
and created:

A header file called msg.h that contained #define statements for MESSAGEPROG, MESSAGEVERS, and PRINTMESSAGE for use in the
other modules. This file must be included by both the client and server modules.

The client stub routines in the msg_clnt.c
file. Only one routine, the printmessage_1() routine, was
called from the rprintmsg client program. If the name of
an rpcgen input file is FOO.x, the
client stub's output file is called FOO_clnt.c.

The server program in msg_svc.c that
calls printmessage_1() from msg_proc.c.
The rule for naming the server output file is similar to that of the client:
for an input file called FOO.x, the output server file
is named FOO_svc.c.

After the server program is created, it is installed on a remote machine
and run. If the machines are homogeneous, the server binary can just be copied.
If they are not homogeneous, the server source files must be copied to and
compiled on the remote machine. For this example, the remote machine is called remote and the local machine is called local.
The server is started from the shell on the remote system:

remote$ msg_server

Server processes generated with rpcgen always run
in the background. You do not have to follow the server's invocation with
an ampersand (&). Servers generated by rpcgen can also be invoked by port monitors like listen()
and inetd(), instead of from the command line.

Thereafter, a user on local can print a message on
the console of machine remote as follows:

local$ rprintmsg remote "Hello, there."

Using rprintmsg, a user can print a message on any
system console, including the local console, when the server msg_server is running on the target system.

You can redefine types (like readdir_res in the previous
example) using the struct, union, and enum RPC language keywords. These keywords are not used in later declarations
of variables of those types. For example, if you define a union, foo, you declare using only foo, and
not unionfoo.

rpcgen compiles RPC unions into C structures. Do
not declare C unions using the union keyword.

Running rpcgen on dir.x generates
four output files:

Header file

Client stub

Server skeleton

XDR routines in the file dir_xdr.c.

The dir_xdr.c file contains the XDR routines
to convert declared data types from the host platform representation into
XDR format, and the reverse.

For each RPC data type used in the.x file, rpcgen assumes that libnsl contains a routine
with a name that is the name of the data type, prepended by the XDR routine
header xdr_ (for example, xdr_int).
If a data type is defined in the.x file, rpcgen generates the required xdr_ routine. If there
is no data type definition in the .x source file (for
example, msg.x), then no _xdr.c
file is generated.

You can write a.x source file that uses a data
type not supported by libnsl, and deliberately omit defining
the type in the.x file. In doing so, you must provide
the xdr_ routine. This is a way to provide your own customized xdr_ routines. See Chapter 4, Programmer's Interface to RPC for more details
on passing arbitrary data types. The server-side of the READDIR
procedure is shown in the following example.

Client code generated by rpcgen does not release
the memory allocated for the results of the RPC call. Call xdr_free() to release the memory when you are finished with it. Calling xdr_free() is similar to calling the free() routine,
except that you pass the XDR routine for the result. In this example, after
printing the list, xdr_free(xdr_readdir_res, result); was
called.

Note –

Use xdr_free() to release memory allocated
by malloc(). Failure to use xdr_free()
to release memory results in memory leaks.

Preprocessing Directives

rpcgen supports C and other preprocessing features.
C preprocessing is performed on rpcgen input files before
they are compiled. All standard C preprocessing directives are allowed in
the.x source files. Depending on the type of output file
being generated, five symbols are defined by rpcgen.

rpcgen provides an additional preprocessing feature:
any line that begins with a percent sign (%) is passed directly
to the output file, with no action on the line's content. Use caution because rpcgen does not always place the lines where you intend. Check the
output source file and, if needed, edit it.

rpcgen uses the preprocessing directives listed in
the following table.

Table 3–1 rpcgen Preprocessing Directives

Symbol

Use

RPC_HDR

Header file output

RPC_XDR

XDR routine output

RPC_SVC

Server stub output

RPC_CLNT

Client stub output

RPC_TBL

Index table output

The following code example is a simple rpcgen example.
Note the use of rpcgen`s pre-processing features.

cpp Directive

rpcgen supports C preprocessing features. rpcgen defaults to use /usr/ccs/lib/cpp as
the C preprocessor. If that fails, rpcgen tries to use /lib/cpp. You can specify a library containing a different cpp to rpcgen with the -Y flag.

For example, if /usr/local/bin/cpp exists, you
can specify it to rpcgen as follows:

rpcgen -Y /usr/local/bin test.x

Compile-Time Flags

This section describes the rpcgen options available
at compile time. The following table summarizes the options that are discussed
in this section.

Uses
five packed elements as default, but other number can be specified

Compile-Time Client and Server Templates

rpcgen generates sample code for the client and server
sides. Use the options described in the following table to generate the desired
templates.

Table 3–3 rpcgen Template Selection Flags

Flag

Function

-a

Generates all template files

-Sc

Generates client-side template

-Ss

Generates server-side template

-Sm

Generates makefile template

The files can be used as guides by filling in the missing parts. These
files are in addition to the stubs generated.

A C-style mode server template is generated from the add.x source by the command rpcgen -N -Ss -o add_server_template.c
add.x

The result is stored in the file add_server_template.c.
A C-style mode, client template for the same add.x source
is generated with the command rpcgen -N -Sc -o add_client_template.c
add.x

The result is stored in the file add_client_template.c.
A makefile template for the same add.x source is generated
with the command rpcgen -N -Sm -o mkfile_template add.x

The result is stored in the file mkfile_template.
It can be used to compile the client and the server. The -a
flag, used in the command rpcgen -N -a add.x, generates
all three template files. The client template is stored in add_client.c, the server template in add_server.c, and
the makefile template inmakefile.a. If any of these files
already exists, rpcgen displays an error message and exits.

Note –

When you generate template files, give them new names to avoid
the files being overwritten the next time rpcgen is executed.

Compile-Time C-style Mode

Also called Newstyle mode, the -N flag causes rpcgen to produce code in which arguments are passed by value and
multiple arguments are passed without a struct. These changes
enable RPC code that is more like C and other high-level languages. For compatibility
with existing programs and make files, the previous standard mode of argument
passing is the default. The following examples demonstrate the new feature.
The source modules for both modes, C-style and default, are shown in Example 3–8 and Example 3–9 respectively.

Example 3–13 Default Mode Server Stub

Compile-Time MT-Safe Code

By default, the code generated by rpcgen is not MT
safe. It uses unprotected global variables and returns results in the form
of static variables. The -M flag generates MT-safe code that
can be used in a multithreaded environment. This code can be used with the
C-style flag, the ANSI C flag, or both.

An example of an MT-safe program with this interface follows. The rpcgen protocol file is msg.x, shown in the
following code example.

A pointer to both the arguments and the results needs to be passed in
to the rpcgen-generated code in order to preserve re-entrancy.
The value returned by the stub function indicates whether this call is a success
or a failure. The stub returns RPC_SUCCESS if the
call is successful. Compare the MT-safe client stub, generated with the -M option, and the MT-unsafe client stub shown in Example 3–16.
The client stub that is not MT-safe uses a static variable to store returned
results and can use only one thread at a time.

The server-side code should not use statics to store returned results.
A pointer to the result is passed in and this should be used to pass the result
back to the calling routine. A return value of 1 indicates success to the
calling routine, while 0 indicates a failure.

In addition, the code generated by rpcgen also generates
a call to a routine to free any memory that might have been allocated when
the procedure was called. To prevent memory leaks, any memory allocated in
the service routine needs to be freed in this routine. messageprog_1_freeresult() frees the memory.

Normally, xdr_free() frees any allocated memory for
you. In this example, no memory was allocated, so no freeing needs to take
place.

The following add.x file shows the use of the -M flag with the C-style and ANSI C flag.

Example 3–18 MT-Safe Program: add.x

This program adds two numbers and returns its result to the client.
rpcgen is invoked on it, with the rpcgen -N
-M -C add.x command. The following example shows the multithreaded
client code to call this code.

Compile-Time MT Auto Mode

MT Auto mode enables RPC servers to automatically use Solaris threads
to process client requests concurrently. Use the -A option
to generate RPC code in MT Auto mode. The -A option also has
the effect of turning on the -M option, so -M
does not need to be explicitly specified. The -M option is
necessary because any code that is generated has to be multithread safe.

An example of an Auto mode program generated by rpcgen
follows in the rpcgen protocol file time.x.
A string is passed to the remote procedure, which prints the string and returns
its length to the client.

Example 3–21 MT Auto Mode: time.x

When the -A option is used, the generated server code
contains instructions for enabling MT Auto mode for the server.

Note –

When compiling a server that uses MT Auto mode, you must link
in the threads library. To do so, specify the -lthread option
in the compile command.

Compile-Time TI-RPC or TS-RPC Library Selection

In older SunOS releases, rpcgen created stubs that
used the socket functions. With the current SunOS release, you can use either
the transport-independent RPC (TI-RPC) or the transport-specific socket (TS-RPC)
routines. These routines provides backward compatibility with previous releases.
The default uses the TI-RPC interfaces. The -b flag tells rpcgen to create TS-RPC variant source code as its output.

Compile-Time ANSI C-compliant Code

rpcgen can also produce output that is compatible
with ANSI C. This feature is selected with the -C compile
flag and is most often used with the -N flag, described in Compile-Time C-style Mode.

The add.x example of the server template is generated
by the rpcgen -N -C -Ss -o add_server_template.c add.x
command:

Note that on the C++ 3.0 server, remote procedure names require an _svc suffix. In the following example, the add.x
template and the -C compile flag produce the client side add_1 and the server stub add_1_svc.

This output conforms to the syntax requirements and structure of ANSI
C. The header files that are generated when this option is invoked can be
used with ANSI C or with C++.

Compile-Time xdr_inline() Count

rpcgen tries to generate more efficient code by using xdr_inline() when possible (see the xdr_admin(3NSL) man page).
When a structure contains elements that xdr_inline() can
be used on (for example integer(), long(), bool()), the relevant portion of the structure is packed with xdr_inline(). A default of five or more packed elements in sequence
causes inline code to be generated. You can change this default with the -i flag. The rpcgen -i 3 test.x command causes rpcgen to start generating inline code after three qualifying elements
are found in sequence.The rpcgen -i 0 test.x command
prevents any inline code from being generated.

In most situations, you do not need to use the -i flag.
The _xdr.c stub is the only file affected by this feature.

rpcgen Programming Techniques

This section suggests some common RPC and rpcgen
programming techniques.

Table 3–4 RPC Programming Techniques

Technique

Description

Network type

rpcgen can produce server code for specific transport types.

Define statements

You can define C-preprocessing symbols on rpcgen
command lines.

Broadcast calls

Servers need
not send error replies to broadcast calls.

Debugging applications

Debug as normal function calls, then change to a distributed
application.

Network Types/Transport Selection

The -s flag creates a server that responds to requests
on the specified type of transport. For example, the command rpcgen
-s datagram_n prot.x writes a server to standard output that responds
to any of the connectionless transports specified in the NETPATH environment variable, or in /etc/netconfig, if NETPATH is not
defined. A command line can contain multiple -s flags and
their network types.

Similarly, the -n flag creates a server that responds
only to requests from the transport specified by a single network identifier.

Note –

Be careful using servers created by rpcgen
with the -n flag. Network identifiers are host specific, so
the resulting server might not run as expected on other hosts.

Command-Line Define Statements

You can define C-preprocessing symbols and assign values to them from
the command line. Command-line define statements can be used to generate conditional
debugging code when the DEBUG symbol is defined. For example:

$ rpcgen -DDEBUG proto.x

Server Response to Broadcast Calls

When a procedure has been called through broadcast RPC and cannot provide
a useful response, the server should send no reply to the client, thus reducing
network traffic. To prevent the server from replying, a remote procedure can
return NULL as its result. The server code generated by rpcgen detects this return and sends no reply.

The following code example is a procedure that replies only if it reaches
an NFS server.

A procedure must return a non-NULL pointer when it
is appropriate for RPC library routines to send a reply.

In the example, if the procedure reply_if_nfsserver()
is defined to return nonvoid values, the return value &notnull should point to a static variable.

Port Monitor Support

Port monitors such as inetd and listen
can monitor network addresses for specified RPC services. When a request arrives
for a particular service, the port monitor spawns a server process. After
the call has been serviced, the server can exit. This technique conserves
system resources. The main server function generated by rpcgen
allows invocation by inetd. See Using inetd
for details.

Services might wait for a specified interval after completing a service
request, in case another request follows. If no call arrives in the specified
time, the server exits, and some port monitors, like inetd,
continue to monitor for the server. If a later request for the service occurs,
the port monitor gives the request to a waiting server process (if any), rather
than spawning a new process.

Note –

When monitoring for a server, some port monitors, like listen(), always spawn a new process in response to a service request.
If a server is used with such a monitor, the server should exit immediately
on completion.

By default, services created using rpcgen wait for
120 seconds after servicing a request before exiting. You can change the interval
with the -K flag. In the following example, the server waits
for 20 seconds before exiting. To create a server that exits immediately,
you can use zero value for the interval period.

Time-out Changes

After sending a request to the server, a client program waits for a
default period (25 seconds) to receive a reply. You can change this timeout
by using the clnt_control() routine. See Standard Interfaces
for additional uses of the clnt_control() routine. See
also the rpc(3NSL)
man page. When considering time-out periods, be sure to allow the minimum
amount of time required for “round-trip” communications over the
network. The following code example illustrates the use of clnt_control().

Example 3–25 AUTH_SYS Authentication Program

Authentication information is important to servers that have to achieve
some level of security. This extra information is supplied to the server as
a second argument.

The following example is for a server that checks client authentication
data. It is modified from printmessage_1() in rpcgen Tutorial.
The code allows only superusers to print a message to the console.

Dispatch Tables

Sometimes programs should have access to the dispatch tables used by
the RPC package. For example, the server dispatch routine might check authorization
and then invoke the service routine. Or, a client library might handle the
details of storage management and XDR data conversion.

When invoked with the -T option, rpcgen
generates RPC dispatch tables for each program defined in the protocol description
file, proto.x, in the file proto_tbl.i.
The suffix.i stands for “index.” You can
invoke rpcgen with the -t option to build
only the header file. You cannot invoke rpcgen in C-style
mode (-N) with either the -T or -t flag.

Each entry in the dispatch table is a structrpcgen_table, defined in the header file proto.h
as follows:

Example 3–27 Using a Dispatch Table

Each entry in the dispatch table contains a pointer to the corresponding
service routine. However, that service routine is usually not defined in the
client code. To avoid generating unresolved external references, and to require
only one source file for the dispatch table, the rpcgen
service routine initializer is RPCGEN_ACTION(proc_ver).

Using this technique, the same dispatch table can be included in both
the client and the server. Use the following define statement when compiling
the client.

#define RPCGEN_ACTION(routine) 0

Use the following define when writing the server.

#define RPCGEN_ACTION(routine)routine

64–Bit Considerations for rpcgen

In Example 3–27proc is declared as type rpcproc_t. Formerly,
RPC programs, versions, procedures, and ports were declared to be of type u_long. On a 32–bit machine, a u_long is
a 4–byte quantity (as is an int); on a 64–bit
system, a u_long is an 8-byte quantity. The data types rpcprog_t, rpcvers_t, rpc_proc_t,
and rpcport_t, introduced in the Solaris 7 environment,
should be used whenever possible in declaring RPC programs, versions, procedures,
and ports in place of both u_long and long.
These newer types provide backwards compatibility with 32–bit systems.
They are guaranteed to be 4–byte quantities no matter which system rpcgen is run on. While rpcgen programs using u_long versions of programs, versions, and procedures can still
run, they have different consequences on 32– and 64–bit machines.
For that reason, replace them with the appropriate newer data types. In fact,
avoid using long and u_long whenever
possible.

Beginning with the Solaris 7 environment, source files created with rpcgen containing XDR routines use different inline macros depending
on whether the code is to run on a 32–bit or 64–bit machine. Specifically,
the source files will use the IXDR_GET_INT32() and IXDR_PUT_INT32() macros instead of IXDR_GETLONG()
and IXDR_PUTLONG(). For example, if the rpcgen source file foo.x contains the following code,
the resulting foo_xdr.c file ensures that the correct inline
macro is used.

The code declares buf to be either int or long, depending on whether the machine
is 64–bit or 32–bit.

Currently,
data types transported by using RPC are limited in size to 4-byte quantities
(32 bits). The 8-byte long is provided to enable applications
to make maximum use of 64–bit architecture. However, programmers should
avoid using longs, and functions that use longs, such as x_putlong(), in favor of ints whenever possible. As noted previously, RPC programs, versions,
procedures, and ports have their own dedicated types. xdr_long()
fails if the data value is not between INT32_MIN and INT32_MAX. Also, the data could be truncated if inline macros such
as IXDR_GET_LONG() and IXDR_PUT_LONG()
are used. The same concerns apply to u_long variables.
See also the xdr_long(3NSL) man page.

IPv6 Considerations for rpcgen

Only TI-RPC supports IPv6 transport. If an application is intended to
run over IPv6, now or in the future, you must not use the backward compatibility
switch. The selection of IPv4 or IPv6 is determined by the respective order
of associated entries in /etc/netconfig.

Debugging Applications

To simplify the testing and debugging process, first test the client
program and the server procedure in a single process by linking them with
each other rather than with the client and server skeletons. Comment out calls
to the client create RPC library routines (see the rpc_clnt_create(3NSL)
man page) and the authentication routines. Do not link with libnsl.

Link the procedures from the previous example by using the command cc rls.c dir_clnt.c dir_proc.c -o rls

With the RPC and XDR functions commented out, the procedure calls execute
as ordinary local function calls, and the program is debugged with a local
debugger such as dbxtool. When the program works, the client
program is linked to the client skeleton produced by rpcgen
and the server procedures are linked to the server skeleton produced by rpcgen.

Two kinds of errors can happen in an RPC call. The first kind of error
is caused by a problem with the mechanism of the remote procedure calls. Examples
of this problem are:

The procedure is not available

The remote server is not responding

The remote server is unable to decode the arguments.

In Example 3–26, an RPC error happens
if result is NULL. The reason
for the failure can be displayed by using clnt_perror(),
or an error string can be returned through clnt_sperror().

The second type of error is caused by the server itself. In Example 3–26,
an error can be returned by opendir(). The handling of
these errors is application specific and is the responsibility of the programmer.

Note that you will be unable to link the client and server programs
to each other if you are using the -C option, because of the -_svc suffix added to the server-side routines.