rpcgen
protocol compiler accepts a remote program interface definition written in
RPC language, which is similar to C. It then produces C
language output consisting of
skeleton versions of the client routines, a server skeleton, XDR
filter routines for both parameters and results, a header file that
contains common definitions, and optionally, dispatch tables that the
server uses to invoke routines that are based on authorization checks. See
rpcgen(1)
for more information.
The
client skeleton
interface to the RPC library hides the
network from its callers, and the
server skeleton
hides the network
from the server procedures invoked by remote clients.
You compile and link output files from
rpcgen
as usual. The server code generated by
rpcgen
supports
inetd.
You can start the server via
inetd
or at the command line.
You can write server procedures in any language that has
system calling conventions. To get
an executable server program, link the server procedure with
the server skeleton from
rpcgen.
To create an executable program for a remote program,
write an ordinary main program that makes local procedure calls to the
client skeletons, and link the program with the
rpcgen
skeletons. If necessary, the
rpcgen
options enable you to suppress skeleton generation and specify the
transport to be used by the server skeleton.
The
rpcgen
protocol compiler helps to reduce development time in the
following ways:
rpcgen
output.
rpcgen
output as a starting point, and rewrite as necessary.
Refer to
rpcgen(1)
for more information about programming applications that use
remote procedure calls or for writing XDR
routines that convert procedure arguments and results into
their network format (or vice versa).
For a discussion of RPC programming without
rpcgen,
see
Chapter 3.
This section shows how to convert a simple routine -- one
that prints messages to the console of a single machine -- to an
ONC RPC application that runs remotely over the network. To do this,
the
rpcgen
protocol compiler is used to generate client and server RPC code.
Example 2-1
shows the routine before conversion.
/* printmsg.c: print a message on the console */ #include <stdio.h>
main(argc, argv) int argc; char *argv[]; { char *message;
if (argc != 2) { fprintf(stderr, "usage: %s <message>\n", argv[0]); exit (1); } message = argv[1];
if (!printmessage(message)) { fprintf(stderr, "%s: couldn't print your message\n", argv[0]); exit (1); } printf("Message Delivered!\n"); exit (0); } /* * Print a message to the console. Return a Boolean showing result. */ printmessage(msg) char *msg; { FILE *f;
f = fopen("/dev/console", "w"); if (f == NULL) { return (0); } fprintf(f, "%s\n", msg); fclose(f); return (1); }
Compile and run this program from a machine called
cyrkle:
cyrkle%
cc printmsg.c -o printmsg
cyrkle%
printmsg "red rubber ball"
Message delivered!
cyrkle%
If the
printmessage
procedure at the bottom of the
printmsg.c
program of
Example 2-1
were converted into a remote procedure,
you could call it from anywhere in the network, instead of only from the
program where it is embedded.
Before doing this, it is necessary to write a protocol specification in RPC
language that describes the remote procedure, as shown in the next section.
To create the specification file,
you must know all the input and output parameter types. In
Example 2-1,
the
printmessage
procedure takes a string as input, and returns an integer
as output.
Example 2-2
is the RPC protocol
specification file that describes the remote version of the
printmessage
procedure.
/* * msg.x: Remote message printing protocol */
program MESSAGEPROG { version MESSAGEVERS { int PRINTMESSAGE(string) = 1; } = 1; } = 0x20000099;
Remote procedures are part of remote programs, so
Example 2-2
actually
declares a remote program containing a single procedure,
PRINTMESSAGE.
By convention, all RPC services provide for a NULL procedure (procedure 0),
normally used for "pinging." (See
ping(8).)
The RPC protocol specification file in
Example 2-2
declares the
PRINTMESSAGE
procedure to be in version 1 of the
remote program. No NULL procedure (procedure 0) is necessary in the
protocol definition because
rpcgen
generates it automatically and the user does not need it.
In RPC language, the convention (though not a requirement) is to make
all declarations in uppercase characters.
Notice that the argument type is
string,
not
char *,
because
char *
in C is ambiguous. Programmers usually intend it
to mean a null-terminated
string of characters, but it could also
be a pointer to a single character or to an array of
characters. In RPC language, a null-terminated string is
unambiguously of type
string.
Example 2-3 defines the remote procedure declared in the RPC protocol specification file of the previous example.
/* * msg_proc.c: implementation of the remote procedure * "printmessage" */
#include <stdio.h> #include <rpc/rpc.h> /* always needed */ #include "msg.h" /* msg.h will be generated by rpcgen */
/* * Remote version of "printmessage" */ int * printmessage_1(msg) [1] char **msg; [2] { static int result; /* must be static! */ FILE *f;
f = fopen("/dev/console", "w"); if (f == NULL) { result = 0; return (&result); } fprintf(f, "%s\n", *msg); fclose(f); result = 1; return (&result); [3] }
In this example, the declaration of the remote procedure,
printmessage_1,
differs from that of the local procedure
printmessage
in three ways:
_1
appended to its name. In general, all remote
procedures called by
rpcgen
are named by the following rule: the name in the procedure definition
(here,
PRINTMESSAGE)
is converted to all
lowercase letters, and an underscore
(_)
and version number (here, 1) is appended to it.
[Return to example]
void.
[Return to example]
static;
if there are no arguments, specify
void.
[Return to example]
Example 2-4
declares the main client program,
rprintmsg.c,
that will call the remote procedure.
/* * rprintmsg.c: remote version of "printmsg.c" */
#include <stdio.h> #include <rpc/rpc.h> /* always needed */ #include "msg.h" /* msg.h will be generated by rpcgen */
main(argc, argv) int argc; char *argv[]; { CLIENT *cl; int *result; char *server; char *message;
if (argc != 3) { fprintf(stderr, "usage: %s host message\n", argv[0]); exit(1); }
server = argv[1]; message = argv[2];
/* * Create client "handle" used for calling MESSAGEPROG on * the server designated on the command line. We tell * the RPC package to use the TCP protocol when * contacting the server. */
cl = clnt_create(server, MESSAGEPROG, MESSAGEVERS, "tcp"); [1] [2] if (cl == NULL) {
/* * Couldn't establish connection with server. * Print error message and stop. */
clnt_pcreateerror(server); exit(1); }
/* * Call the remote procedure "printmessage" on the server */
result = printmessage_1(&message, cl); [3] if (result == NULL) { [4]
/* * An error occurred while calling the server. * Print error message and stop. */
clnt_perror(cl, server); exit(1); }
/* * Okay, we successfully called the remote procedure. */
if (*result == 0) { [4]
/* * Server was unable to print our message. * Print error message and stop. */
fprintf(stderr, "%s: %s couldn't print your message\n", argv[0], server); exit(1); }
/* * The message got printed on the server's console */
printf("Message delivered to %s!\n", server); exit(0); }
In this example, the following events occur:
clnt_create().
This client handle will be passed to the skeleton routines
that call the remote procedure.
[Return to example]
clnt_create
is
tcp,
the transport on which you want to run your application.
(Alternatively
udp
could have been used.)
[Return to example]
printmessage_1
is called exactly the same way as in
msg_proc.c,
except for the inserted client handle as the second argument.
[Return to example]
print_message_1,
returns with a NULL.
In the latter case, error reporting is application-dependent.
In this example, the error is reported via
*result.
[Return to example]
Use the
rpcgen
protocol compiler
on the RPC protocol specification file,
msg.x,
(from
Example 2-2)
to
generate client and server RPC code automatically:
cyrkle%
rpcgen msg.x
Using
rpcgen
like this -- without options --
automatically creates the following files from the input file
msg.x:
msg.h
that contains
#define
statements for
MESSAGEPROG,
MESSAGEVERS,
and
PRINTMESSAGE
so that they can be used in the other modules.
You must include
msg.h
in both the client and server modules.
msg_clnt.c,
is formed by appending
_clnt
to the file name and
substituting the file type suffix,
.c.
The
msg_clnt.c
file contains only one client skeleton routine,
printmessage_1,
referred from the
printmsg
client program.
msg_svc.c,
created by appending
_svc
to the file name and substituting
the file type suffix,
.c.
The
msg_svc.c
program calls
printmessage_1
from
msg_proc.c.
Note
The
-Toption ofrpcgencreates an additional output file of index information for dispatching service routines.
After the
rpcgen
protocol compilation, use two
cc
compilation statements to create a client program
and a server program:
rprintmsg,
compile the
rprintmsg.c
and
msg_clnt.c
programs together, and use the
-o
option to specify the executable output in
the file
rprintmsg:
cyrkle%
cc rprintmsg.c msg_clnt.c -o rprintmsg
msg_server,
compile the
msg_proc.c
and
msg_svc.c
programs together, and use the
-o
option to specify the executable output in
the file
msg_server:
cyrkle%
cc msg_proc.c msg_svc.c -o msg_server
Copy the server program
msg_server
to a remote machine called
space
in this example.
Then, run it in background mode there -- indicated by
&:
space%
msg_server &
Note
Servers generated by
rpcgencan be invoked with port monitors likeinetd, as well as from the command line, if they are invoked with the-Ioption.
From a local machine
(earth)
you can now print a message on the console of the remote machine
space:
earth%
rprintmsg space "Hello out there..."
The message
Hello out there...
will appear on the console of the machine
space.
You can print a
message on any console (including your own) with this program if
you copy the server to that machine and run it.
Section 2.1
explains how to use
rpcgen
to generate
client and server RPC code automatically to convert a simple procedure to
one that can be run remotely over the network. The
rpcgen
protocol compiler
can also generate the external data representation (XDR)
routines that
convert local data
structures into network format (and vice versa).
The following sections present
a more advanced example of a
complete RPC service -- a remote directory listing service that uses
rpcgen
to generate both skeleton and XDR
routines.
As with the simple example, you must first create an
RPC protocol specification file. This file,
dir.x,
is for a remote directory listing, shown in
!A spec_file_adv_ex .
/* * dir.x: Remote directory listing protocol */
/* maximum length of a directory entry */ const MAXNAMELEN = 255;
/* a directory entry */ typedef string nametype<MAXNAMELEN>;
/* a link in the listing */ typedef struct namenode *namelist;
/* * A node in the directory listing */ struct namenode { nametype name; /* name of directory entry */ namelist next; /* next entry */ };
/* * The result of a READDIR operation. */ union readdir_res switch (int errno) {
case 0: namelist list; /* no error: return directory listing */
default: void; /* error occurred: nothing else to return */ };
/* * The directory program definition */ program DIRPROG { version DIRVERS { readdir_res READDIR(nametype) = 1; } = 1; } = 0x20000076;
Note
You can define types (like
readdir_resin Example 2-5) by using thestruct,union, andenumkeywords, but do not use these keywords in later variable declarations of those types. For example, if you defineunion foo, you must declare it later by usingfoo, notunion foo. Therpcgenprotocol compiler compiles RPC unions into C structures, so it is an error to declare them later by using theunionkeyword.
Running
rpcgen
on
dir.x
creates four output files:
The first three files have already been described. The fourth file,
dir_xdr.c,
contains the XDR routines that convert
the declared data types into XDR format (and vice versa).
For each data type used in the
.x
file,
rpcgen
assumes that the RPC/XDR library contains a routine with the name of that
data type prefixed by
xdr_,
for example,
xdr_int.
If the data type was defined in the
.x
file, then
rpcgen
generates the required XDR routine. If there are no such data types,
then the file (for example,
dir_xdr.c)
will not be generated. If the data types were used but not defined, then
the user has to provide that XDR routine. This enables you to create
your own customized XDR routines.
Example 2-6
consists of the
dir_proc.c
file that implements the remote
READDIR
procedure from the previous RPC protocol specification file.
/* * dir_proc.c: remote readdir implementation */ #include <rpc/rpc.h> /* Always needed */ #include <sys/dir.h> #include "dir.h" /* Created by rpcgen */
extern int errno; extern char *malloc(); extern char *strdup();
readdir_res * readdir_1(dirname) nametype *dirname; { DIR *dirp; struct direct *d; namelist nl; namelist *nlp; static readdir_res res; /* must be static! */
/* * Open directory */ dirp = opendir(*dirname); if (dirp == NULL) { res.errno = errno; return (&res); }
/* * Free previous result */ xdr_free(xdr_readdir_res, &res);
/* * Collect directory entries. * Memory allocated here will be freed by xdr_free * next time readdir_1 is called */ nlp = &res.readdir_res_u.list; while (d = readdir(dirp)) { nl = *nlp = (namenode *) malloc(sizeof(namenode)); nl->name = strdup(d->d_name); nlp = &nl->next; } *nlp = NULL;
/* * Return the result */ res.errno = 0; closedir(dirp); return (&res); }
Example 2-7
shows the client side program,
rls.c,
that calls the remote server procedure.
/* * rls.c: Remote directory listing client */ #include <stdio.h> #include <rpc/rpc.h> /* always need this */ #include "dir.h" /* will be generated by rpcgen */
extern int errno;
main(argc, argv) int argc; char *argv[]; { CLIENT *cl; char *server; char *dir; readdir_res *result; namelist nl;
if (argc != 3) { fprintf(stderr, "usage: %s host directory\n", argv[0]); exit(1); }
server = argv[1]; dir = argv[2];
/* * Create client "handle" used for calling DIRPROG on * the server designated on the command line. Use * the tcp protocol when contacting the server. */ cl = clnt_create(server, DIRPROG, DIRVERS, "tcp"); if (cl == NULL) { /* * Couldn't establish connection with server. * Print error message and stop. */ clnt_pcreateerror(server); exit(1); }
/* * Call the remote procedure readdir on the server */ result = readdir_1(&dir, cl); if (result == NULL) { /* * An RPC error occurred while calling the server. * Print error message and stop. */ clnt_perror(cl, server); exit(1); }
/* * Okay, we successfully called the remote procedure. */ if (result->errno != 0) { /* * A remote system error occurred. * Print error message and stop. */ errno = result->errno; perror(dir); exit(1); }
/* * Successfully got a directory listing. * Print it out. */ for (nl = result->readdir_res_u.list; nl != NULL; nl = nl->next) { printf("%s\n", nl->name); } exit(0); }
As with the simple example, you must run the
rpcgen
protocol compiler
on the RPC protocol specification file
dir.x,
to create a
header file,
dir.h,
an output file of client skeletons,
dir_clnt.c,
and a server program,
dir_svc.c.
For this advanced example,
rpcgen
also generates the file of XDR routines,
dir_xdr.c:
earth%
rpcgen dir.x
The next step is to compile the file of XDR routines,
dir_xdr.c,
with the following
cc -c
command:
earth%
cc -c dir_xdr.c
Here, the
-c
option is used to suppress the loading phase of the compilation and
to produce an object file, even if only one program is compiled.
Next, the remote directory listing client program,
rls.c,
is compiled with the file of client skeletons,
dir_clnt.c,
and the object file of the previous
compilation,
dir_xdr.o.
The
-o
option places the executable output
of the compilation into the file
rls:
earth%
cc rls.c dir_clnt.c dir_xdr.o -o rls
The following statement compiles three files together: the server program from
the original
rpcgen
compilation,
dir_svc.c;
the remote READDIR
implementation program,
dir_proc.c;
and the object file,
dir_xdr.o,
produced by the recent
cc
compilation of the
file of XDR routines.
Through the
-o
option, the executable output is placed in the file
dir_svc:
earth%
cc dir_svc.c dir_proc.c dir_xdr.o -o dir_svc
To run the remote directory program, use
the new
dir_svc
and
rls
commands. The following statement runs the
dir_svc
command in background mode on the local machine
earth:
earth%
dir_svc &
The
rls
command can then be used from the remote machine
space
to provide a directory listing on the machine where
dir_svc
is running in background mode. The command
and output (a directory listing of
/usr/pub
on machine
earth)
is shown here:
space%
rls earth /usr/pub
. .. ascii eqnchar kbd marg8 tabclr tabs tabs4
Note
Client code generated by
rpcgendoes not release the memory allocated for the results of the RPC call. You can callxdr_freeto deallocate the memory when no longer needed. This is similar to callingfree, except that you must also pass the XDR routine for the result. For example, after printing the directory listing in the previous example, you could callxdr_freeas follows:
xdr_free(xdr_readdir_res, result);
It is difficult to debug distributed applications that have separate
client and server processes.
To simplify this,
you can test the client program and the server procedure
as a single program by linking them with each other rather
than with the client and server skeletons.
To do this, you must first remove calls to client creation RPC library
routines (for example,
clnt_create).
To create the single debuggable file
rls,
use the
-o
option when you compile
rls.c,
dir_clnt.c,
dir_proc.c,
and
dir_xdr.c
together as follows:
%
cc rls.c dir_clnt.c dir_proc.c dir_xdr.c -o rls
The procedure calls are
executed as ordinary local procedure calls and the program can be
debugged with a local debugger such as
dbx.
When the program is working, the client program can be linked to
the client skeleton produced by
rpcgen
and the server procedures can be linked to the server skeleton produced
by
rpcgen.
There are two kinds of errors possible in an RPC call:
This occurs when
a procedure is unavailable, the remote server does not respond, the
remote server cannot decode the arguments, and so on.
As in
Example 2-7,
an RPC error occurs if
result
is NULL.
The reason for the failure can be printed by using
clnt_perror,
or you can return an error string through
clnt_sperror.
As in
Example 2-6,
an error occurs if
opendir
fails; that is why
readdir_res
is of type
union.
The handling of these types of errors are the
responsibility of the programmer.
The C-preprocessor,
cpp,
runs on all input files before they are compiled, so all the
preprocessor directives are legal within an
.x
file. Five macro identifiers
may have been defined, depending upon which output file is being
generated. The following table lists these macros:
| Identifier | Usage |
| RPC_HDR | For header-file output |
| RPC_XDR | For XDR routine output |
| RPC_SVC | For server-skeleton output |
| RPC_CLNT | For client-skeleton output |
| RPC_TBL | For index-table output |
Also,
rpcgen
does some additional preprocessing of the input file. Any line that
begins with a percent sign
(%)
passes directly into the output file,
without any interpretation.
Example 2-8
demonstrates this processing feature.
/*
* time.x: Remote time protocol
*/
program TIMEPROG {
version TIMEVERS {
unsigned int TIMEGET(void) = 1;
} = 1;
} = 44;
#ifdef RPC_SVC
%int *
%timeget_1()
%{
% static int thetime;
%
% thetime = time(0);
% return (&thetime);
%}
#endif
Using the percent sign feature does not guarantee that
rpcgen
will place the output where you intend. If you have problems of this
type, do not use this feature.
The following sections contain additional
rpcgen
programming information about network types, defining symbols,
inetd
support, and dispatch tables.
By default,
rpcgen
generates server code for both UDP and TCP transports. The
-s
flag creates a server that responds to requests on the specified transport.
The following example creates a UDP server:
rpcgen -s udp proto.x
The
rpcgen
protocol compiler provides a way to define symbols and assign values to
them. These defined symbols are passed on to the C preprocessor
when it is invoked. This facility is useful when, for example,
invoking debugging code that is enabled only when the
DEBUG
symbol is defined. For example:
rpcgen -DDEBUG proto.x
The
rpcgen
protocol compiler
can create RPC servers that can be invoked by
inetd
when a request for that service is received.
rpcgen -I proto.x
The server code in
proto_svc.c
supports
inetd.
For more information on setting up the entry for RPC services in
/etc/inetd.conf,
see
Section 3.3.6.
In many applications, it is useful
for services to wait after responding to a request, on the
chance that another will soon follow. However, if there is no call within a
certain time (by default, 120 seconds), the server exits and the
port monitor continues to monitor requests for its services.
You can use the
-K
option to change the default waiting time. In the following example,
the server waits only 20 seconds before exiting:
rpcgen -I -K 20 proto.x
If you want the server to exit immediately,
use
-K 0;
if you want the server to
wait forever (a normal server situation), use
-K -1.
Dispatch tables are often useful.
For example, the server dispatch routine may need to check
authorization and then invoke the service routine,
or a client library may need to control all details of
storage management and XDR data conversion.
The following
rpcgen
command generates RPC dispatch tables for each program defined in
the protocol
description file,
proto.x,
and places them in the file
proto_tbl.i
(the suffix
.i
indicates index):
rpcgen -T proto.x
Each entry in the table is a
struct rpcgen_table
defined in the header file,
proto.h,
as follows:
struct rpcgen_table {
char *(*proc)();
xdrproc_t xdr_arg;
unsigned len_arg;
xdrproc_t xdr_res;
unsigned len_res;
};
In this
proto.h
definition,
proc
is a pointer to the service routine,
xdr_arg
is a pointer to the input (argument)
xdr_routine,
len_arg
is the length in bytes of the input argument,
xdr_res
is a pointer to the output (result)
xdr_routine,
and
len_res
is the length in bytes of the output result.
The table
dirprog_1_table
is indexed by procedure number. The variable
dirprog_1_nproc
contains the number of entries in the table. The
find_proc
routine in
Example 2-9
shows how to locate a procedure in the dispatch tables.
struct rpcgen_table *
find_proc(proc)
long proc;
{
if (proc >= dirprog_1_nproc)
/* error */
else
return (&dirprog_1_table[proc]);
}
Each entry in the dispatch table contains a pointer to the corresponding
service routine. However, the service routine is not defined
in the client code. To avoid generating unresolved external references,
and to require only one source file for the dispatch table, the actual
service routine initializer is
RPCGEN_ACTION(proc_ver).
With this, you can include the same dispatch table in both the client and
the server. Use the following
define
statement when compiling the client:
#define RPCGEN_ACTION(routine) 0
Next, use the following
define
statement when compiling the server:
#define RPCGEN_ACTION(routine) routine
The following sections contain client programming information about default timeouts and client authentication.
RPC sets a default timeout of 25 seconds for RPC calls when
clnt_create
is used.
RPC waits for 25 seconds to get the results from the server. If it
does not, then this usually means one of the following conditions exists:
In such cases, the function returns NULL; you can print the error with
clnt_perrno.
Sometimes you may need to change the timeout value to accommodate
the application or because the server is too slow or far away.
Change the timeout by using
clnt_control.
The code segment in
Example 2-10
demonstrates the use of
clnt_control.
struct timeval tv; CLIENT *cl;
cl = clnt_create("somehost", SOMEPROG, SOMEVERS, "tcp"); if (cl == NULL) { exit(1); } tv.tv_sec = 60; /* change timeout to 1 minute */ tv.tv_usec = 0; /* this should always be set */ clnt_control(cl, CLSET_TIMEOUT, &tv);
By default, client creation routines do not handle client authentication. Sometimes, you may want the client to authenticate itself to the server. This is easy to do, as shown in the following coding:
CLIENT *cl;
cl = client_create("somehost", SOMEPROG, SOMEVERS, "udp"); if (cl != NULL) { /* To set UNIX style authentication */ cl->cl_auth = authunix_create_default(); }
For more information on authentication, see Section 3.3.5.
The following sections contain server programming information about system broadcasts and passing data to server procedures.
Sometimes, clients broadcast to determine whether a particular server
exists on the network, or to determine all the servers for a
particular program and version number.
You make these calls with
clnt_broadcast
(for which there is no
rpcgen
support). Refer to
Section 3.3.2.
When a procedure is known to be called via broadcast RPC,
it is best for the server not to reply unless it can provide
useful information to the client. Otherwise, the network
can be overloaded with useless replies.
To prevent the server from replying, a remote procedure can
return NULL as its result; the server code generated by
rpcgen
can detect this and prevent a reply.
The following example shows a procedure that replies only if it acts as an NFS server:
void *
reply_if_nfsserver()
{
char notnull; /* just here so we can use its address */
if (access("/etc/exports", F_OK) < 0) {
return (NULL); /* prevent RPC from replying */
}
/*
* return non-null pointer so RPC will send out a reply
*/
return ((void *)¬null);
}
If a procedure returns type
void *,
it must return a non-NULL
pointer if it wants RPC to reply for it.
Server procedures often need to know more about an RPC call
than just its arguments. For example, getting authentication information
is useful to procedures that want to implement some level of security.
This information is supplied to the server procedure as a
second argument. (For details see the structure of
svc_req
in
Section 3.3.5.2.)
The following example shows the
use of
svc_req,
where the previous
printmessage_1
procedure is rewritten to allow only root users to print a message to
the console:
int *
printmessage_1(msg, rqstp)
char **msg;
struct svc_req *rqstp;
{
static int result; /* Must be static */
FILE *f;
struct authunix_parms *aup;
aup = (struct authunix_parms *)rqstp->rq_clntcred;
if (aup->aup_uid != 0) {
result = 0;
return (&result);
}
/*
* Same code as before.
*/
}
RPC language is an extension of XDR language, through
the addition of the
program
and
version
types. The XDR language is similar to C.
For a complete description of the XDR language syntax, see
the
External Data Representation Standard: Protocol Specification RFC 1014.
For a description of the RPC extensions to the XDR language,
see the
Remote Procedure Calls: Protocol Specification RFC 1050.
The following sections describe the syntax of the RPC and XDR languages, with examples and descriptions of how the various RPC and XDR type definitions are compiled into C type definitions in the output header file.
An RPC language file consists of a series of definitions:
definition-list:
definition ";"
definition ";" definition-list
RPC recognizes the following definition types:
definition:
enum-definition
typedef-definition
const-definition
declaration-definition
struct-definition
union-definition
program-definition
XDR enumerations have the same syntax as C enumerations:
enum-definition:
"enum" enum-ident "{"
enum-value-list
"}"
enum-value-list:
enum-value
enum-value "," enum-value-list
enum-value:
enum-value-ident
enum-value-ident "=" value
A comparison of the next two examples show how close XDR
language is to C by showing an XDR language
enum
(Example 2-11),
followed by the C language
enum
that results after compilation
(Example 2-12).
enum colortype {
RED = 0,
GREEN = 1,
BLUE = 2
};
enum colortype {
RED = 0,
GREEN = 1,
BLUE = 2,
};
typedef enum colortype colortype;
XDR typedefs have the same syntax as C typedefs:
typedef-definition:
"typedef" declaration
The following example in XDR defines a
fname_type
that declares file name strings with a maximum length of 255 characters:
typedef string fname_type<255>;
The following example shows the corresponding C definition for this:
typedef char *fname_type;
XDR constants are used wherever an integer constant is used (for example, in array size specifications), as shown by the following syntax:
const-definition:
"const" const-ident "=" integer
The following XDR example defines a constant
DOZEN
equal to
12:
const DOZEN = 12;
The following example shows the corresponding C definition for this:
#define DOZEN 12
XDR provides only four kinds of declarations, shown by the following syntax:
declaration:
simple-declaration [1]
fixed-array-declaration [2]
variable-array-declaration [3]
pointer-declaration [4]
The syntax for each, followed by examples, is listed here:
simple-declaration:
type-ident variable-ident
For example,
colortype color
in XDR, is the same in C:
colortype color.
[Return to example]
fixed-array-declaration:
type-ident variable-ident "[" value "]"
For example,
colortype palette[8]
in XDR, is the same in C:
colortype palette[8].
[Return to example]
These have no explicit syntax in C, so XDR creates its own by using angle brackets, as in the following syntax:
variable-array-declaration:
type-ident variable-ident "<" value ">"
type-ident variable-ident "<" ">"
The maximum size is specified between the angle brackets; it may be omitted, indicating that the array can be of any size, as shown in the following example:
int heights<12>; /* at most 12 items */
int widths<>; /* any number of items */
Variable-length arrays have no explicit syntax in C, so each of their
declarations is compiled into a
struct.
For example, the
heights
declaration is compiled into the following struct:
struct {
u_int heights_len; /* number of items in array */
int *heights_val; /* pointer to array */
} heights;
Here, the
_len
component
stores the number of items in the array
and the
_val
component stores the pointer to the array.
The first part of each of these component names is the
same as the name of the declared XDR variable.
[Return to example]
These are the same in XDR as in C.
You cannot send pointers over the network, but you can use XDR pointers
to send recursive data types, such as lists and trees. In
XDR language, this type is called
optional-data,
not
pointer,
as in the following syntax:
optional-data:
type-ident "*"variable-ident
An example of this (the same in both XDR and C) follows:
listitem *next;
XDR declares a
struct
almost exactly like its C counterpart. The XDR syntax is
the following:
struct-definition:
"struct" struct-ident "{"
declaration-list
"}"
declaration-list:
declaration ";"
declaration ";" declaration-list
The following example shows an XDR structure to a two-dimensional coordinate, followed by the C structure into which it is compiled in the output header file:
struct coord {
int x;
int y;
};
The following example shows the C structure that results from compiling the previous XDR structure:
struct coord {
int x;
int y;
};
typedef struct coord coord;
Here, the output is identical to the input, except for the added
typedef
at the end of the output. This enables the use of
coord
instead of
struct coord
in declarations.
XDR unions are discriminated unions, and are different from C unions. They are more analogous to Pascal variant records than to C unions. The syntax is shown here:
union-definition:
"union" union-ident "switch" ("simple declaration") "{"
case-list
"}"
case-list:
"case" value ":" declaration ";"
"case" value ":" declaration ";" case-list
"default" ":" declaration ";"
The following is an example of a type that
might be returned as the result of a
read data.
If there is no error, it returns
a block of data; otherwise, it returns nothing:
union read_result switch (int errno) {
case 0:
opaque data[1024];
default:
void;
};
This coding is compiled into the following:
struct read_result {
int errno;
union {
char data[1024];
} read_result_u;
};
typedef struct read_result read_result;
Notice that the
union
component of the output
struct
has the same name as
the structure type name, except for the suffix,
_u.
You declare RPC programs using the following syntax:
program-definition:
"program" program-ident "{"
version-list
"}" "=" value
version-list:
version ";"
version ";" version-list
version:
"version" version-ident "{"
procedure-list
"}" "=" value
procedure-list:
procedure ";"
procedure ";" procedure-list
procedure:
type-ident procedure-ident "("type-ident")" "=" value
Example 2-13 shows a program of time protocol.
/*
* time.x: Get or set the time. Time is represented as number
* of seconds since 0:00, January 1, 1970.
*/
program TIMEPROG {
version TIMEVERS {
unsigned int TIMEGET(void) = 1;
void TIMESET(unsigned) = 2;
} = 1;
} = 44;
This file compiles into
the following
#define
statements in the output header file:
#define TIMEPROG 44 #define TIMEVERS 1 #define TIMEGET 1 #define TIMESET 2
The following list describes exceptions to the syntax rules described in the previous sections:
C has no built-in Boolean type. However, the RPC library has a
Boolean type called
bool_t
that is either TRUE or FALSE.
Items declared as type
bool
in XDR language are compiled into
bool_t
in the output header file. For example,
bool married
is compiled into
bool_t married.
C has no built-in string type, but instead uses the null-terminated
char *
convention. In XDR language, you declare strings by using the
string
keyword, and each string is compiled into a
char *
in the output header
file. The maximum size contained in the angle brackets specifies the
maximum number of characters allowed in the strings (excluding the
NULL character). For example,
string name<32>
would be compiled into
char *name.
You can omit a maximum size to indicate a string
of arbitrary length. For example,
string longname<>
would be compiled into
char *longname.
RPC and XDR use
opaque data
to describe untyped data, which consists simply of
sequences of arbitrary bytes. You declare opaque data as an array of either
fixed or variable length. An
opaque
declaration of a fixed length array is
opaque diskblock[512],
whose C counterpart is
char diskblock[512].
An
opaque
declaration of a variable length array is
opaque filedata<1024>,
whose C counterpart could be the following:
struct {
u_int filedata_len;
char *filedata_val;
} filedata;
In a
void
declaration, the variable is not named. The declaration is
just a
void.
Declarations of
void
occur only in union and program definitions (as the argument or
result of a remote procedure).