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:
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:
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:
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:
Note
The -T option of rpcgen creates 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:
cyrkle%
cc rprintmsg.c msg_clnt.c -o rprintmsg
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 rpcgen can be invoked with port monitors like inetd, 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.
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_res in Example 2-5) by using the struct, union, and enum keywords, but do not use these keywords in later variable declarations of those types. For example, if you define union foo, you must declare it later by using foo, not union foo. The rpcgen protocol compiler compiles RPC unions into C structures, so it is an error to declare them later by using the union keyword.
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 rpcgen does not release the memory allocated for the results of the RPC call. You can call xdr_free to deallocate the memory when no longer needed. This is similar to calling free, 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:
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).