The
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)
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:
It greatly reduces network interface programming.
It can mix low-level code with high-level code.
For speed-critical applications, you can link customized high-level
code with the
rpcgen
output.
You can use
rpcgen
output as a starting
point, and rewrite as necessary.
Refer to
rpcgen
(1)rpcgen
, see
Chapter 3.
2.1 Simple Example: Using rpcgen to Generate Client and Server RPC Code
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.
Example 2-1: Printing a Remote Message Without ONC RPC
/* 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.
2.1.1 RPC Protocol Specification File Describing Remote Procedure
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.
Example 2-2: RPC Protocol Specification File, Simple Example
/* * 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)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
.
2.1.2 Implementing the Procedure Declared in the Protocol Specification
Example 2-3
defines the remote procedure declared
in the RPC protocol specification file of the previous example.
Example 2-3: Remote Procedure Definition
/* * 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:
It has
_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]
It takes a pointer to a string instead of a string itself.
This is true of all remote procedures they always take pointers to
their arguments rather than the arguments themselves; if there are no arguments,
specify
void
.
[Return to example]
It returns a pointer to an integer instead of an integer itself.
This is also characteristic of remote procedures they return pointers
to their results.
Therefore it is important to have the result declared as
a
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.
Example 2-4: Client Program that Calls 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:
A client "handle"
is created first, using the RPC library routine
clnt_create()
.
This client handle will be passed to the skeleton routines that call the remote
procedure.
[Return to example]
The last parameter to
clnt_create
is
tcp
, the transport on which you want to run your application.
(Alternatively
udp
could have been used.)
[Return to example]
The remote procedure
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]
The remote procedure call can fail in two
ways: the RPC mechanism itself can fail or there can be an error in the execution
of the remote procedure.
In the former case, the remote procedure,
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
:
A header file called
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.
An output file and the client skeleton routines within it.
The output file,
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.
The server 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
-T
option ofrpcgen
creates an additional output file of index information for dispatching service routines.
2.1.5 Compiling the Client and Server Programs
After the
rpcgen
protocol compilation, use two
cc
compilation statements to create a client program and a server
program:
To create the client program called
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
To create a server program called
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
2.1.6 Copying the Server to a Remote Machine and Running It
Copy the server program
msg_server
to a remote machine
called
space
in this example.
Then, run the program in
background mode on the server indicated by
&
:
space% msg_server &
Note
Servers generated by
rpcgen
can be invoked with port monitors likeinetd
, as well as from the command line, if they are invoked with the-I
option.
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.
2.2 Advanced Example: Using rpcgen to Generate XDR Routines
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.
2.2.1 The RPC Protocol Specification
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
Example 2-5.
Example 2-5: RPC Protocol Specification File, Advanced Example
/* * 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_res
in Example 2-5) by using thestruct
,union
, andenum
keywords, 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
. Therpcgen
protocol compiler compiles RPC unions into C structures, so it is an error to declare them later by using theunion
keyword.
Running
rpcgen
on
dir.x
creates
four output files:
Header file
File of client skeleton routines
Server skeleton file
File of XDR routines
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.
2.2.2 Implementing the Procedure Declared in the Protocol Specification
Example 2-6
consists of the
dir_proc.c
file that implements the remote
READDIR
procedure from
the previous RPC protocol specification file.
Example 2-6: Remote Procedure Implementation
/* * 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); }
2.2.3 The Client Program that Calls the Remote Procedure
Example 2-7
shows the client side program,
rls.c
, that calls the remote server procedure.
Example 2-7: Client Program that Calls the Server
/* * 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
2.2.5 Compiling the File of XDR Routines
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.
2.2.6 Compiling the Client Side Program with rpcgen Output
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
2.2.7 Compiling the Server Side Program with rpcgen Output
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
2.2.8 Running the Remote Directory Program
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
rpcgen
does not release the memory allocated for the results of the RPC call. You can callxdr_free
to 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 call
xdr_free
as 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:
A problem with the remote procedure call mechanism
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
.
A problem with the server itself
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.
Example 2-8: Using the Percent Sign to Bypass Interpretation of a Line
/* * 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.
2.5 rpcgen Programming
The following sections
contain additional
rpcgen
programming information about
network types, defining symbols,
inetd
support, and dispatch
tables.
2.5.1 Network Types
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
2.5.2 User-Provided Define Statements
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
.
2.5.4 Dispatch Tables
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.
Example 2-9: Locating a Procedure in a Dispatch Table
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.
2.6.1 Timeout Changes
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:
The server is not running
The remote machine has crashed
The network is unreachable
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
.
Example 2-10: Using the clnt_control Routine
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.
2.7 Server Programming
The following sections contain server programming information about
system broadcasts and passing data to server procedures.
2.7.1 Handling Broadcasts
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.
2.7.2 Passing Data to Server Procedures
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
RFC 1014,
External Data Representation Standard: Protocol Specification .
For a description of the RPC extensions to the XDR language,
see
RFC 1050,
Remote Procedure Calls: Protocol Specification .
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.
2.8.1 Definitions
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).
Example 2-11: XDR enum Before Compilation
enum colortype { RED = 0, GREEN = 1, BLUE = 2 };
Example 2-12: C enum Resulting from Compiling XDR enum
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 declarations
simple-declaration: type-ident variable-ident
For example,
colortype color
in XDR, is the same in C:
colortype color
.
[Return to example]
Fixed-length array declarations
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]
Variable-length array declarations
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]
Pointer declarations
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.
2.8.7 Unions
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
.
2.8.8 Programs
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.
Example 2-13: RPC Program Illustrating 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 #defRine TIMEGET 1 #define TIMESET 2
The following list describes exceptions to the syntax rules described in the previous sections:
Booleans
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
.
Strings
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
.
Opaque data
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;
Voids
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).