ONC+ Developer's Guide

Passing Complex Data Structures

"Converting Local Procedures to Remote Procedures" shows how to generate client and server RPC code. rpcgen can also be used to generate XDR routines (the routines that convert local data structures into XDR format and vice versa).

Example 3-4 presents a complete RPC service: a remote directory listing service, built using rpcgen both to generate stub routines and to generate the XDR routines.


Example 3-4 RPC Protocol Description File: dir.x

/*
 * dir.x: Remote directory listing protocol
 *
 * This example demonstrates the functions of rpcgen.
 */
 
const MAXNAMELEN = 255;						/* max length of directory
entry */
typedef string nametype<MAXNAMELEN>;		/* director entry */
typedef struct namenode *namelist;			/* link in the listing */
 
/* A node in the directory listing */
struct namenode {
	nametype name;						/* name of directory entry */
	namelist next;						/* next entry */
};
 
/*
 * The result of a READDIR operation
 *
 * a truly portable application would use
 * an agreed upon list of error codes
 * rather than (as this sample program
 * does) rely upon passing UNIX errno's
 * back.
 *
 * In this example: The union is used 
 * here to discriminate between successful
 * and unsuccessful remote calls.
 */
 
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; 

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

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

Running rpcgen on dir.x generates four output files: (1) the header file, (2) the client stub, (3) the server skeleton, and (4) the XDR routines in the file dir_xdr.c. This last file contains the XDR routines to convert declared data types from the host platform representation into XDR format, and vice versa.

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

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


Example 3-5 Server dir_proc.c Example

/*
 * dir_proc.c: remote readdir
 * implementation
 */
#include <dirent.h>
#include "dir.h"                /* Created by rpcgen */
 
extern int errno;
extern char *malloc();
extern char *strdup();
 
readdir_res *
readdir_1(dirname, req)
	nametype *dirname;
	struct svc_req *req;
 
{
	DIR *dirp;
	struct dirent *d;
	namelist nl;
	namelist *nlp;
	static readdir_res res; /* must be static! */
	
	/* Open directory */
	dirp = opendir(*dirname);
	if (dirp == (DIR *)NULL) {
		res.errno = errno;
		return (&res);
	}
	/* Free previous result */
	xdr_free(xdr_readdir_res, &res);
	/*
	 * Collect directory entries.
 * Memory allocated here is free by
	 * xdr_free the next time readdir_1
 * is called
	 */
	nlp = &res.readdir_res_u.list;
	while (d = readdir(dirp)) {
		nl = *nlp = (namenode *) 
							malloc(sizeof(namenode));
		if (nl == (namenode *) NULL) {
			res.errno = EAGAIN;
			closedir(dirp);
			return(&res);
		}
		nl->name = strdup(d->d_name);
		nlp = &nl->next;
	}
	*nlp = (namelist)NULL;
	/* Return the result */
	res.errno = 0;
	closedir(dirp);
	return (&res);
}

Example 3-6 shows the client-side implementation of the READDIR procedure.


Example 3-6 Client-side Implementation of rls.c

/*
 * rls.c: Remote directory listing client
 */
 
#include <stdio.h>
#include "dir.h"                       /* generated by rpcgen */
 
extern int errno;
 
main(argc, argv)
	int argc;
	char *argv[];
{
	CLIENT *clnt;
	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 MESSAGEPROG on the server
 * designated on the command line.
	 */
	cl = clnt_create(server, DIRPROG,
								DIRVERS, "tcp");
	if (clnt == (CLIENT *)NULL) {
		clnt_pcreateerror(server);
		exit(1);
	}
	result = readdir_1(&dir, clnt);
	if (result == (readdir_res *)NULL) {
		clnt_perror(clnt, server);
		exit(1);
	}
	/* Okay, we successfully called
 * the remote procedure.
 */
	if (result->errno != 0) {
		/* Remote system error. Print
    * error message and die.
    */
		errno = result->errno;
		perror(dir);
		exit(1);
	}
	/* Successfully got a directory listing.
 * Print it.
 */
	for (nl = result->readdir_res_u.list;
								nl != NULL;
		nl = nl->next) {
		printf("%s\n", nl->name);
	}
	xdr_free(xdr_readdir_res, result);
	clnt_destroy(cl);
	exit(0);
}

As in other examples, execution is on systems named local and remote. The files are compiled and run as follows:

remote$ rpcgen dir.x
 remote$ cc -c dir_xdr.c
 remote$ cc rls.c dir_clnt.c dir_xdr.o -o rls -lnsl
 remote$ cc dir_svc.c dir_proc.c dir_xdr.o -o dir_svc -lnsl
 remote$ dir_svc

When you install rls() on system local, you can list the contents of /usr/share/lib on system remote as follows:

local$ rls remote /usr/share/lib
 ascii
 eqnchar
 greek
 kbd
 marg8
 tabclr
 tabs
 tabs4
 local$

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


Note -

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