sources-request@mirror.UUCP (08/06/86)
Submitted by: sun!ferne!marks (Mark Stein) Mod.sources: Volume 6, Issue 90 Archive-name: rpc2/Part02 [ All I have done is verify that the shar files unpack correctly. -r$ ] Sun RPC source (part 2 of 11). This software package contains code and documentation for Revision 3.0 of the Sun Remote Procedure Call library. In addition, a beta version of the XDR/RPC protocol compiler is included. Comments about this latest release may be mailed to sun!rpc or rpc@sun.com. Sun RPC is a product of Sun Microsystems, Inc. and is provided for unrestricted use provided that this legend is included on all tape media and as a part of the software program in whole or part. Users may copy or modify Sun RPC without charge, but are not authorized to license or distribute it to anyone else except as part of a product or program developed by the user. - - - - - - - - - C U T - H E R E - - - - - - - - - - - - - - - - - - #! /bin/sh # This is a shell archive, meaning: # 1. Remove everything above the #! /bin/sh line. # 2. Save the resulting text in a file. # 3. Execute the file with /bin/sh (not csh) to create: # rpc/doc/rpc.prog.p1 # This archive created: Mon Jul 14 16:54:58 1986 export PATH; PATH=/bin:/usr/bin:$PATH for d in rpc rpc/doc rpc/rpclib rpc/tools rpc/toys rpc/rpclib/profiled rpc/rpcgen rpc/rpcgen/test do if test ! -d $d then echo "shar: Making directory $d" mkdir $d chmod 755 $d fi done echo shar: "extracting 'rpc/doc/rpc.prog.p1'" '(49365 characters)' if test -f 'rpc/doc/rpc.prog.p1' then echo shar: "will not over-write existing file 'rpc/doc/rpc.prog.p1'" else sed 's/^X//' << \SHAR_EOF > 'rpc/doc/rpc.prog.p1' X.PL RIGHT X.TL XRemote Procedure Call X.br XProgramming Guide X.bp X.NH XIntroduction X.LP XThis document is intended for programmers Xwho wish to write network applications Xusing remote procedure calls (explained below), Xthus avoiding low-level system primitives based on sockets. XThe reader must be familiar with the C programming language, Xand should have a working knowledge of network theory. X.LP XPrograms that communicate over a network Xneed a paradigm for communication. XA low-level mechanism might Xsend a signal on the arrival of incoming packets, Xcausing a network signal handler to execute. XA high-level mechanism would be the Ada X.LW rendezvous . XThe method used at Sun is the XRemote Procedure Call (RPC) paradigm, Xin which a client communicates with a server. XIn this process, Xthe client first calls a procedure to send a data packet to the server. XWhen the packet arrives, the server calls a dispatch routine, Xperforms whatever service is requested, sends back the reply, Xand the procedure call returns to the client. X.NH 2 XLayers of RPC X.LP XThe RPC interface is divided into three layers. XThe highest layer is totally transparent to the programmer. XTo illustrate, Xat this level a program can contain a call to X.LW rnusers() , Xwhich returns the number of users on a remote machine. XYou don't have to be aware that RPC is being used, Xsince you simply make the call in a program, Xjust as you would call X.LW malloc() . X.LP XAt the middle layer, the routines X.LW registerrpc() Xand X.LW callrpc() Xare used to make RPC calls: X.LW registerrpc() Xobtains a unique system-wide number, while X.LW callrpc() Xexecutes a remote procedure call. XThe X.LW rnusers() Xcall is implemented using these two routines. XThe middle-layer routines are designed for most common applications, Xand shield the user from knowing about sockets. X.LP XThe lowest layer is for more sophisticated applications, Xsuch as altering the defaults of the routines. XAt this layer, you can explicitly manipulate Xsockets that transmit RPC messages. XThis level should be avoided if possible. X.LP XSection 2 of this manual illustrates use of the highest two layers Xwhile Section 3 presents the low-level interface. XSection 4 of the manual discusses miscellaneous topics. XThe final section summarizes Xall the entry points into the RPC system. X.LP XAlthough this document only discusses the interface to C, Xremote procedure calls can be made from any language. XEven though this document discusses RPC Xwhen it is used to communicate Xbetween processes on different machines, Xit works just as well for communication Xbetween different processes on the same machine. X.NH 2 XThe RPC Paradigm X.LP XHere is a diagram of the RPC paradigm: X.LP X.PL FULL X.PS XL1: arrow down 1i "client " rjust "program " rjust XL2: line right 1.5i "\fLcallrpc()\fP" "function" Xmove up 1.5i; line dotted down 6i; move up 4.5i Xarrow right 1i XL3: arrow down 1i "execute " rjust "request " rjust XL4: arrow right 1.5i "call" "service" XL5: arrow down 1i " service" ljust " executes" ljust XL6: arrow left 1.5i "\fLreturn\fP" "answer" XL7: arrow down 1i "request " rjust "completed " rjust XL8: line left 1i Xarrow left 1.5i "\fLreturn\fP" "reply" XL9: arrow down 1i "program " rjust "continues " rjust Xline dashed down from L2 to L9 Xline dashed down from L4 to L7 Xline dashed up 1i from L3 "service " rjust "daemon " rjust Xarrow dashed down 1i from L8 Xmove right 1i from L3 Xbox invis "Machine B" Xmove left 1.2i from L2; move down Xbox invis "Machine A" X.PE X.FN "Network Communication with the Remote Procedure Call" X.PL RIGHT X.bp X.NH XHigher Layers of RPC X.NH 2 XHighest Layer X.LP XImagine you're writing a program that needs to know Xhow many users are logged into a remote machine. XYou can do this by calling the library routine X.LW rnusers() , Xas illustrated below: X.BS X.LS X#include <stdio.h> X.sp.5 Xmain(argc, argv) X int argc; X char **argv; X{ X unsigned num; X.sp.5 X if (argc < 2) { X fprintf(stderr, "usage: rnusers hostname\en"); X exit(1); X } X if ((num = rnusers(argv[1])) < 0) { X fprintf(stderr, "error: rnusers\en"); X exit(-1); X } X printf("%d users on %s\en", num, argv[1]); X exit(0); X} X.Lf X.BE XRPC library routines such as X.LW rnusers() Xare in the RPC services library X.LW librpcsvc.a . XThus, the program above should be compiled with X.BS X.LS X% cc \fIprogram\fP.c -lrpcsvc X.Lf X.BE XThis routine, and other RPC library routines, Xare documented in section 3R of the X.I "System Interface Manual for the Sun Workstation" . XHere is a table of RPC service library routines Xavailable to the C programmer: X.TN "RPC Service Library Routines" X.TS Xbox; XcfBI s Xc c XlfL l. X.sp.5 X\s+2RPC Service Library Routines\s-2 X.sp.5 X_ X\fIroutine description\fP X_ Xrnusers() return number of users on remote machine Xrusers() return information about users on remote machine Xhavedisk() determine if remote machine has disk Xrstat() get performance data from remote kernel Xrwall() write to specified remote machines Xgetmaster() get name of YP master Xgetrpcport() get RPC port number Xyppasswd() update user password in yellow pages X.TE X.LP XThe other RPC services \(em X.LW ether , X.LW mount , X.LW rquota , Xand X.LW spray X\(em are not available to the C programmer as library routines. XThey do, however, Xhave RPC program numbers so they can be invoked with X.LW callrpc() , Xwhich will be discussed in the next section. X.bp X.NH 2 XIntermediate Layer X.LP XThe simplest interface, which explicitly makes RPC Xcalls, uses the functions X.LW callrpc() Xand X.LW registerrpc() . XUsing this method, another way to get the number of remote users is: X.BS X.LS X#include <stdio.h> X#include <rpcsvc/rusers.h> X.sp.5 Xmain(argc, argv) X int argc; X char **argv; X{ X unsigned long nusers; X.sp.5 X if (argc < 2) { X fprintf(stderr, "usage: nusers hostname\en"); X exit(-1); X } X if (callrpc(argv[1], X RUSERSPROG, RUSERSVERS, RUSERSPROC_NUM, X xdr_void, 0, xdr_u_long, &nusers) != 0) { X fprintf(stderr, "error: callrpc\en"); X exit(1); X } X printf("%d users on %s\en", nusers, argv[1]); X exit(0); X} X.Lf X.BE XA program number, version number, and procedure number Xdefines each RPC procedure. XThe program number defines a group Xof related remote procedures, each of which has a different Xprocedure number. XEach program also has a version number, Xso when a minor change is made to a remote service X(adding a new procedure, for example), Xa new program number doesn't have to be assigned. XWhen you want to call a procedure to Xfind the number of remote users, you look up the appropriate Xprogram, version and procedure numbers Xin a manual, similar to when you look up the name of memory Xallocator when you want to allocate memory. X.LP XThe simplest routine in the RPC library Xused to make remote procedure calls is X.LW callrpc() . XIt has eight parameters. XThe first is the name of the remote machine. XThe next three parameters Xare the program, version, and procedure numbers. XThe following two parameters Xdefine the argument of the RPC call, and the final two parameters Xare for the return value of the call. XIf it completes successfully, X.LW callrpc() Xreturns zero, but nonzero otherwise. XThe exact meaning of the return codes is found in X.LW <rpc/clnt.h> , Xand is in fact an X.LW "enum clnt_stat" Xcast into an integer. X.LP XSince data types may be represented differently on different machines, X.LW callrpc() Xneeds both the type of the RPC argument, as well as Xa pointer to the argument itself (and similarly for the result). For X.LW RUSERSPROC_NUM , Xthe return value is an X.LW "unsigned long" , Xso X.LW callrpc() Xhas X.LW xdr_u_long Xas its first return parameter, which says Xthat the result is of type X.LW "unsigned long" , Xand X.LW &nusers Xas its second return parameter, Xwhich is a pointer to where the long result will be placed. Since X.LW RUSERSPROC_NUM Xtakes no argument, the argument parameter of X.LW callrpc() Xis X.LW xdr_void . X.LP XAfter trying several times to deliver a message, if X.LW callrpc() Xgets no answer, it returns with an error code. XThe delivery mechanism is UDP, Xwhich stands for User Datagram Protocol. XMethods for adjusting the number of retries Xor for using a different protocol require you to use the lower Xlayer of the RPC library, discussed later in this document. XThe remote server procedure Xcorresponding to the above might look like this: X.BS X.LS Xchar * Xnuser(indata) X char *indata; X{ X static int nusers; X.sp.5 X /* X * code here to compute the number of users X * and place result in variable nusers X */ X return((char *)&nusers); X} X.Lf X.BE X.LP XIt takes one argument, which is a pointer to the input Xof the remote procedure call (ignored in our example), Xand it returns a pointer to the result. XIn the current version of C, Xcharacter pointers are the generic pointers, Xso both the input argument and the return value are cast to X.LW "char *" . X.LP XNormally, a server registers all of the RPC calls it plans Xto handle, and then goes into an infinite loop waiting to service requests. XIn this example, there is only a single procedure Xto register, so the main body of the server would look like this: X.BS X.LS X#include <stdio.h> X#include <rpcsvc/rusers.h> X.sp.5 Xchar *nuser(); X.sp.5 Xmain() X{ X registerrpc(RUSERSPROG, RUSERSVERS, RUSERSPROC_NUM, X nuser, xdr_void, xdr_u_long); X svc_run(); /* never returns */ X fprintf(stderr, "Error: svc_run returned!\en"); X exit(1); X} X.Lf X.BE X.LP XThe X.LW registerrpc() Xroutine establishes what C procedure Xcorresponds to each RPC procedure number. XThe first three parameters, X.LW RUSERPROG , X.LW RUSERSVERS , Xand X.LW RUSERSPROC_NUM Xare the program, version, and procedure numbers Xof the remote procedure to be registered; X.LW nuser() Xis the name of the C procedure implementing it; Xand X.LW xdr_void Xand X.LW xdr_u_long Xare the types of the input to and output from the procedure. X.LP XOnly the UDP transport mechanism can use X.LW registerrpc() ; Xthus, it is always safe in conjunction with calls generated by X.LW callrpc() . X.LP XWarning: the UDP transport mechanism can only deal with Xarguments and results less than 8K bytes in length. X.NH 2 XAssigning Program Numbers X.LP XProgram numbers are assigned in groups of 0x20000000 (536870912) Xaccording to the following chart: X.BS X.LS X 0 - 1fffffff defined by sun X20000000 - 3fffffff defined by user X40000000 - 5fffffff transient X60000000 - 7fffffff reserved X80000000 - 9fffffff reserved Xa0000000 - bfffffff reserved Xc0000000 - dfffffff reserved Xe0000000 - ffffffff reserved X.Lf X.BE XSun Microsystems administers the first group of numbers, Xwhich should be identical for all Sun customers. XIf a customer develops an application that might be of general interest, Xthat application should be given an assigned number in the first range. XThe second group of numbers is reserved for specific customer applications. XThis range is intended primarily for debugging new programs. XThe third group is reserved for applications that Xgenerate program numbers dynamically. XThe final groups are reserved for future use, and should not be used. X.LP XTo register a protocol specification, Xsend a request by network mail to X.LW sun!rpc , Xor write to: X.DS XRPC Administrator XSun Microsystems X2550 Garcia Ave. XMountain View, CA 94043 X.DE XPlease include a complete protocol specification, Xsimilar to those in this manual for NFS and YP. XYou will be given a unique program number in return. X.NH 2 XPassing Arbitrary Data Types X.LP XIn the previous example, the RPC call passes a single X.LW "unsigned long" . XRPC can handle arbitrary data structures, regardless of Xdifferent machines' byte orders or structure layout conventions, Xby always converting them to a network standard called X.I "eXternal Data Representation" X(XDR) before Xsending them over the wire. XThe process of converting from a particular machine representation Xto XDR format is called X.I serializing , Xand the reverse process is called X.I deserializing . XThe type field parameters of X.LW callrpc() Xand X.LW registerrpc() Xcan be a built-in procedure like X.LW xdr_u_long() Xin the previous example, or a user supplied one. XXDR has these built-in type routines: X.BS X.LS Xxdr_int() xdr_u_int() xdr_enum() Xxdr_long() xdr_u_long() xdr_bool() Xxdr_short() xdr_u_short() xdr_string() X.Lf X.BE XAs an example of a user-defined type routine, Xif you wanted to send the structure X.BS X.LS Xstruct simple { X int a; X short b; X} simple; X.Lf X.BE Xthen you would call X.LW callrpc() Xas X.BS X.LS Xcallrpc(hostname, PROGNUM, VERSNUM, PROCNUM, X xdr_simple, &simple ...); X.Lf X.BE Xwhere X.LW xdr_simple() Xis written as: X.BS X.LS X#include <rpc/rpc.h> X.sp.5 Xxdr_simple(xdrsp, simplep) X XDR *xdrsp; X struct simple *simplep; X{ X if (!xdr_int(xdrsp, &simplep->a)) X return (0); X if (!xdr_short(xdrsp, &simplep->b)) X return (0); X return (1); X} X.Lf X.BE X.LP XAn XDR routine returns nonzero (true in the sense of C) Xif it completes successfully, and zero otherwise. XA complete description of XDR is in the X.I "XDR Protocol Specification" , Xso this section only gives a few examples of XDR implementation. X.LP XIn addition to the built-in primitives, Xthere are also the prefabricated building blocks: X.BS X.LS Xxdr_array() xdr_bytes() Xxdr_reference() xdr_union() X.Lf X.BE XTo send a variable array of integers, Xyou might package them up as a structure like this X.BS X.LS Xstruct varintarr { X int *data; X int arrlnth; X} arr; X.Lf X.BE Xand make an RPC call such as X.BS X.LS Xcallrpc(hostname, PROGNUM, VERSNUM, PROCNUM, X xdr_varintarr, &arr...); X.Lf X.BE Xwith X.LW xdr_varintarr() Xdefined as: X.BS X.LS Xxdr_varintarr(xdrsp, arrp) X XDR *xdrsp; X struct varintarr *arrp; X{ X xdr_array(xdrsp, &arrp->data, &arrp->arrlnth, MAXLEN, X sizeof(int), xdr_int); X} X.Lf X.BE XThis routine takes as parameters the XDR handle, Xa pointer to the array, a pointer to the size of the array, Xthe maximum allowable array size, Xthe size of each array element, Xand an XDR routine for handling each array element. X.LP XIf the size of the array is known in advance, then Xthe following could also be used to send Xout an array of length X.LW SIZE : X.BS X.LS Xint intarr[SIZE]; X.sp.5 Xxdr_intarr(xdrsp, intarr) X XDR *xdrsp; X int intarr[]; X{ X int i; X.sp.5 X for (i = 0; i < SIZE; i++) { X if (!xdr_int(xdrsp, &intarr[i])) X return (0); X } X return (1); X} X.Lf X.BE X.LP XXDR always converts quantities to 4-byte multiples when deserializing. XThus, if either of the examples above involved characters Xinstead of integers, each character would occupy 32 bits. XThat is the reason for the XDR routine X.LW xdr_bytes() , Xwhich is like X.LW xdr_array() Xexcept that it packs characters; X.LW xdr_bytes() Xhas four parameters, similar to the first four parameters of X.LW xdr_array() . XFor null-terminated strings, there is also the X.LW xdr_string() Xroutine, which is the same as X.LW xdr_bytes() Xwithout the length parameter. XOn serializing it gets the string length from X.LW strlen() , Xand on deserializing it creates a null-terminated string. X.LP XHere is a final example that calls the previously written X.LW xdr_simple() Xas well as the built-in functions X.LW xdr_string() Xand X.LW xdr_reference() , Xwhich chases pointers: X.BS X.LS Xstruct finalexample { X char *string; X struct simple *simplep; X} finalexample; X.sp.5 Xxdr_finalexample(xdrsp, finalp) X XDR *xdrsp; X struct finalexample *finalp; X{ X int i; X.sp.5 X if (!xdr_string(xdrsp, &finalp->string, MAXSTRLEN)) X return (0); X if (!xdr_reference(xdrsp, &finalp->simplep, X sizeof(struct simple), xdr_simple); X return (0); X return (1); X} X.Lf X.BE X.bp X.NH XLowest Layer of RPC X.LP XIn the examples given so far, XRPC takes care of many details automatically for you. XIn this section, we'll show you how you can change the defaults Xby using lower layers of the RPC library. XIt is assumed that you are familiar with sockets Xand the system calls for dealing with them. XIf not, consult the X.I "IPC Primer" . X.LP XThere are several occasions when you may need to use lower layers of RPC. XFirst, you may need to use TCP. XThe higher layer uses UDP, Xwhich restricts RPC calls to 8K bytes of data. XUsing TCP permits calls to send long streams of data. XFor an example, see section 5.2 below. XSecond, you may want to allocate and free memory Xwhile serializing or deserializing with XDR routines. XThere is no call at the higher level to let you free memory explicitly. XFor more explanation, see section 3.2 below. XThird, you may need to perform authentication Xon either the client or server side, Xby supplying credentials or verifying them. XSee the explanation in section 4.4 below. X.NH 2 XMore on the Server Side X.LP XThe server for the X.LW nusers Xprogram shown below does the same thing as the one using X.LW registerrpc() Xabove, but is written using a lower layer of the RPC package: X.BS X.LS no X#include <stdio.h> X#include <rpc/rpc.h> X#include <rpcsvc/rusers.h> X.sp.5 Xmain() X{ X SVCXPRT *transp; X int nuser(); X.sp.5 X transp = svcudp_create(RPC_ANYSOCK); X if (transp == NULL){ X fprintf(stderr, "can't create an RPC server\en"); X exit(1); X } X pmap_unset(RUSERSPROG, RUSERSVERS); X if (!svc_register(transp, RUSERSPROG, RUSERSVERS, X nuser, IPPROTO_UDP)) { X fprintf(stderr, "can't register RUSER service\en"); X exit(1); X } X svc_run(); /* never returns */ X fprintf(stderr, "should never reach this point\en"); X} X.sp.5 Xnuser(rqstp, tranp) X struct svc_req *rqstp; X SVCXPRT *transp; X{ X unsigned long nusers; X.sp.5 X switch (rqstp->rq_proc) { X case NULLPROC: X if (!svc_sendreply(transp, xdr_void, 0)) { X fprintf(stderr, "can't reply to RPC call\en"); X exit(1); X } X return; X case RUSERSPROC_NUM: X /* X * code here to compute the number of users X * and put in variable nusers X */ X if (!svc_sendreply(transp, xdr_u_long, &nusers) { X fprintf(stderr, "can't reply to RPC call\en"); X exit(1); X } X return; X default: X svcerr_noproc(transp); X return; X } X} X.Lf X.BE X.LP XFirst, the server gets a transport handle, which is used Xfor sending out RPC messages. X.LW registerrpc() Xuses X.LW svcudp_create() Xto get a UDP handle. XIf you require a reliable protocol, call X.LW svctcp_create() Xinstead. XIf the argument to X.LW svcudp_create() Xis X.LW RPC_ANYSOCK , Xthe RPC library creates a socket Xon which to send out RPC calls. XOtherwise, X.LW svcudp_create() Xexpects its argument to be a valid socket number. XIf you specify your own socket, it can be bound or unbound. XIf it is bound to a port by the user, the port numbers of X.LW svcudp_create() Xand X.LW clntudp_create() X(the low-level client routine) must match. X.LP XWhen the user specifies X.LW RPC_ANYSOCK Xfor a socket or gives an unbound socket, Xthe system determines port numbers in the following way: Xwhen a server starts up, Xit advertises to a port mapper demon on its local machine, Xwhich picks a port number for the RPC procedure Xif the socket specified to X.LW svcudp_create() Xisn't already bound. XWhen the X.LW clntudp_create() Xcall is made with an unbound socket, Xthe system queries the port mapper on Xthe machine to which the call is being made, Xand gets the appropriate port number. XIf the port mapper is not running Xor has no port corresponding to the RPC call, Xthe RPC call fails. XUsers can make RPC calls Xto the port mapper themselves. XThe appropriate procedure Xnumbers are in the include file X.LW <rpc/pmap_prot.h> . X.LP XAfter creating an X.LW SVCXPRT , Xthe next step is to call X.LW pmap_unset() Xso that if the X.LW nusers Xserver crashed earlier, Xany previous trace of it is erased before restarting. XMore precisely, X.LW pmap_unset() Xerases the entry for X.LW RUSERSPROG Xfrom the port mapper's tables. X.LP XFinally, we associate the program number for X.LW nusers Xwith the procedure X.LW nuser() . XThe final argument to X.LW svc_register() Xis normally the protocol being used, Xwhich, in this case, is X.LW IPPROTO_UDP . XNotice that unlike X.LW registerrpc() , Xthere are no XDR routines involved Xin the registration process. XAlso, registration is done on the program, Xrather than procedure, level. X.LP XThe user routine X.LW nuser() Xmust call and dispatch the appropriate XDR routines Xbased on the procedure number. XNote that Xtwo things are handled by X.LW nuser() Xthat X.LW registerrpc() Xhandles automatically. XThe first is that procedure X.LW NULLPROC X(currently zero) returns with no arguments. XThis can be used as a simple test Xfor detecting if a remote program is running. XSecond, there is a check for invalid procedure numbers. XIf one is detected, X.LW svcerr_noproc() Xis called to handle the error. X.LP XThe user service routine serializes the results and returns Xthem to the RPC caller via X.LW svc_sendreply() . XIts first parameter is the X.LW SVCXPRT Xhandle, the second is the XDR routine, Xand the third is a pointer to the data to be returned. XNot illustrated above is how a server Xhandles an RPC program that passes data. XAs an example, we can add a procedure X.LW RUSERSPROC_BOOL , Xwhich has an argument X.LW nusers , Xand returns X.LW TRUE Xor X.LW FALSE Xdepending on whether there are nusers logged on. XIt would look like this: X.BS X.LS Xcase RUSERSPROC_BOOL: { X int bool; X unsigned nuserquery; X.sp.5 X if (!svc_getargs(transp, xdr_u_int, &nuserquery) { X svcerr_decode(transp); X return; X } X /* X * code to set nusers = number of users X */ X if (nuserquery == nusers) X bool = TRUE; X else X bool = FALSE; X if (!svc_sendreply(transp, xdr_bool, &bool){ X fprintf(stderr, "can't reply to RPC call\en"); X exit(1); X } X return; X} X.Lf X.BE X.LP XThe relevant routine is X.LW svc_getargs() , Xwhich takes an X.LW SVCXPRT Xhandle, the XDR routine, Xand a pointer to where the input is to be placed as arguments. X.NH 2 XMemory Allocation with XDR X.LP XXDR routines not only do input and output, Xthey also do memory allocation. XThis is why the second parameter of X.LW xdr_array() Xis a pointer to an array, rather than the array itself. XIf it is X.LW NULL , Xthen X.LW xdr_array() Xallocates space for the array and returns a pointer to it, Xputting the size of the array in the third argument. XAs an example, consider the following XDR routine X.LW xdr_chararr1() , Xwhich deals with a fixed array of bytes with length X.LW SIZE : X.BS X.LS Xxdr_chararr1(xdrsp, chararr) X XDR *xdrsp; X char chararr[]; X{ X char *p; X int len; X.sp.5 X p = chararr; X len = SIZE; X return (xdr_bytes(xdrsp, &p, &len, SIZE)); X} X.Lf X.BE XIt might be called from a server like this, X.BS X.LS Xchar chararr[SIZE]; X.sp.5 Xsvc_getargs(transp, xdr_chararr1, chararr); X.Lf X.BE Xwhere X.LW chararr Xhas already allocated space. XIf you want XDR to do the allocation, Xyou would have to rewrite this routine in the following way: X.BS X.LS Xxdr_chararr2(xdrsp, chararrp) X XDR *xdrsp; X char **chararrp; X{ X int len; X.sp.5 X len = SIZE; X return (xdr_bytes(xdrsp, charrarrp, &len, SIZE)); X} X.Lf X.BE XThen the RPC call might look like this: X.BS X.LS Xchar *arrptr; X.sp.5 Xarrptr = NULL; Xsvc_getargs(transp, xdr_chararr2, &arrptr); X/* X * use the result here X */ Xsvc_freeargs(transp, xdr_chararr2, &arrptr); X.Lf X.BE XAfter using the character array, it can be freed with X.LW svc_freeargs() . XIn the routine X.LW xdr_finalexample() Xgiven earlier, if X.LW finalp->string Xwas X.LW NULL Xin the call X.BS X.LS Xsvc_getargs(transp, xdr_finalexample, &finalp); X.Lf X.BE Xthen X.BS X.LS Xsvc_freeargs(xdrsp, xdr_finalexample, &finalp); X.Lf X.BE Xfrees the array allocated to hold X.LW finalp->string ; Xotherwise, it frees nothing. XThe same is true for X.LW finalp->simplep . X.LP XTo summarize, each XDR routine is responsible Xfor serializing, deserializing, and allocating memory. XWhen an XDR routine is called from X.LW callrpc() , Xthe serializing part is used. XWhen called from X.LW svc_getargs() , Xthe deserializer is used. XAnd when called from X.LW svc_freeargs() , Xthe memory deallocator is used. XWhen building simple examples like those in this section, Xa user doesn't have to worry about the three modes. XThe XDR reference manual has examples of more Xsophisticated XDR routines that Xdetermine which of the three modes they are in Xto function correctly. X.NH 2 XThe Calling Side X.LP XWhen you use X.LW callrpc() , Xyou have no control over the RPC delivery Xmechanism or the socket used to transport the data. XTo illustrate the layer of RPC that lets you adjust these Xparameters, consider the following code to call the X.LW nusers Xservice: X.BS X.LS no X#include <stdio.h> X#include <rpc/rpc.h> X#include <rpcsvc/rusers.h> X#include <sys/socket.h> X#include <sys/time.h> X#include <netdb.h> X.sp.5 Xmain(argc, argv) X int argc; X char **argv; X{ X struct hostent *hp; X struct timeval pertry_timeout, total_timeout; X struct sockaddr_in server_addr; X int addrlen, sock = RPC_ANYSOCK; X register CLIENT *client; X enum clnt_stat clnt_stat; X unsigned long nusers; X.sp.5 X if (argc < 2) { X fprintf(stderr, "usage: nusers hostname\en"); X exit(-1); X } X if ((hp = gethostbyname(argv[1])) == NULL) { X fprintf(stderr, "can't get addr for %s\en",argv[1]); X exit(-1); X } X pertry_timeout.tv_sec = 3; X pertry_timeout.tv_usec = 0; X addrlen = sizeof(struct sockaddr_in); X bcopy(hp->h_addr, (caddr_t)&server_addr.sin_addr, X hp->h_length); X server_addr.sin_family = AF_INET; X server_addr.sin_port = 0; X if ((client = clntudp_create(&server_addr, RUSERSPROG, X RUSERSVERS, pertry_timeout, &sock)) == NULL) { X clnt_pcreateerror("clntudp_create"); X exit(-1); X } X total_timeout.tv_sec = 20; X total_timeout.tv_usec = 0; X clnt_stat = clnt_call(client, RUSERSPROC_NUM, xdr_void, X 0, xdr_u_long, &nusers, total_timeout); X if (clnt_stat != RPC_SUCCESS) { X clnt_perror(client, "rpc"); X exit(-1); X } X clnt_destroy(client); X} X.Lf X.BE XThe low-level version of X.LW callrpc() Xis X.LW clnt_call() , Xwhich takes a X.LW CLIENT Xpointer rather than a host name. The parameters to X.LW clnt_call() Xare a X.LW CLIENT Xpointer, the procedure number, Xthe XDR routine for serializing the argument, Xa pointer to the argument, Xthe XDR routine for deserializing the return value, Xa pointer to where the return value will be placed, Xand the time in seconds to wait for a reply. X.LP XThe X.LW CLIENT Xpointer is encoded with the transport mechanism. X.LW callrpc() Xuses UDP, thus it calls X.LW clntudp_create() Xto get a X.LW CLIENT Xpointer. To get TCP (Transport Control Protocol), you would use X.LW clnttcp_create() . X.LP XThe parameters to X.LW clntudp_create() Xare the server address, the length of the server address, Xthe program number, the version number, Xa timeout value (between tries), and a pointer to a socket. XThe final argument to X.LW clnt_call() Xis the total time to wait for a response. XThus, the number of tries is the X.LW clnt_call() Xtimeout divided by the X.LW clntudp_create() Xtimeout. X.LP XThere is one thing to note when using the X.LW clnt_destroy() Xcall. XIt deallocates any space associated with the X.LW CLIENT Xhandle, but it does not close the socket associated with it, Xwhich was passed as an argument to X.LW clntudp_create() . XThe reason is that if Xthere are multiple client handles using the same socket, Xthen it is possible to close one handle Xwithout destroying the socket that other handles Xare using. X.LP XTo make a stream connection, the call to X.LW clntudp_create() Xis replaced with a call to X.LW clnttcp_create() . X.BS X.LS Xclnttcp_create(&server_addr, prognum, versnum, &socket, X inputsize, outputsize); X.Lf X.BE XThere is no timeout argument; instead, the receive and send buffer Xsizes must be specified. When the X.LW clnttcp_create() Xcall is made, a TCP connection is established. XAll RPC calls using that X.LW CLIENT Xhandle would use this connection. XThe server side of an RPC call using TCP has X.LW svcudp_create() Xreplaced by X.LW svctcp_create() . X.bp X.NH XOther RPC Features X.LP XThis section discusses some other aspects of RPC Xthat are occasionally useful. X.NH 2 XSelect on the Server Side X.LP XSuppose a process is processing RPC requests Xwhile performing some other activity. XIf the other activity involves periodically updating a data structure, Xthe process can set an alarm signal before calling X.LW svc_run() . XBut if the other activity Xinvolves waiting on a a file descriptor, the X.LW svc_run() Xcall won't work. XThe code for X.LW svc_run() Xis as follows: X.BS X.LS Xvoid Xsvc_run() X{ X int readfds; X.sp.5 X for (;;) { X readfds = svc_fds; X switch (select(32, &readfds, NULL, NULL, NULL)) { X.sp.5 X case -1: X if (errno == EINTR) X continue; X perror("rstat: select"); X return; X case 0: X break; X default: X svc_getreq(readfds); X } X } X} X.Lf X.BE X.LP XYou can bypass X.LW svc_run() Xand call X.LW svc_getreq() Xyourself. XAll you need to know are the file descriptors Xof the socket(s) associated with the programs you are waiting on. XThus you can have your own X.LW select() Xthat waits on both the RPC socket, Xand your own descriptors. X.NH 2 XBroadcast RPC X.LP XThe X.I portmapper Xis a daemon that converts RPC program numbers Xinto DARPA protocol port numbers; see X.LW portmap (8). XYou can't do broadcast RPC without the portmapper, X.LW pmap , Xin conjunction with standard RPC protocols. XHere are the main differences between Xbroadcast RPC and normal RPC calls: X.IP 1. XNormal RPC expects one answer, whereas Xbroadcast RPC expects many answers X(one or more answer from each responding machine). X.IP 2. XBroadcast RPC can only be supported by packet-oriented (connectionless) Xtransport protocols like UPD/IP. X.IP 3. XThe implementation of broadcast RPC Xtreats all unsuccessful responses as garbage by filtering them out. XThus, if there is a version mismatch between the Xbroadcaster and a remote service, Xthe user of broadcast RPC never knows. X.IP 4. XAll broadcast messages are sent to the portmap port. XThus, only services that register themselves with their portmapper Xare accessible via the broadcast RPC mechanism. X.NH 3 XBroadcast RPC Synopsis X.LP X.BS X.LS X#include <rpc/pmap_clnt.h> X.sp.5 Xenum clnt_stat clnt_stat; X.sp.5 Xclnt_stat = Xclnt_broadcast(prog, vers, proc, xargs, argsp, xresults, X resultsp, eachresult) Xu_long prog; /* program number */ Xu_long vers; /* version number */ Xu_long proc; /* procedure number */ Xxdrproc_t xargs; /* xdr routine for args */ Xcaddr_t argsp; /* pointer to args */ Xxdrproc_t xresults; /* xdr routine for results */ Xcaddr_t resultsp; /* pointer to results */ Xbool_t (*eachresult)(); /* call with each result gotten */ X.Lf X.BE XThe procedure X.LW eachresult() Xis called each time a valid result is obtained. XIt returns a boolean that indicates Xwhether or not the client wants more responses. X.BS X.LS Xbool_t done; X.sp.5 Xdone = Xeachresult(resultsp, raddr) Xcaddr_t resultsp; Xstruct sockaddr_in *raddr; /* addr of responding machine */ X.Lf X.BE XIf X.LW done Xis X.LW TRUE , Xthen broadcasting stops and X.LW clnt_broadcast() Xreturns successfully. XOtherwise, the routine waits for another response. XThe request is rebroadcast Xafter a few seconds of waiting. XIf no responses come back, Xthe routine returns with X.LW RPC_TIMEDOUT . XTo interpret X.LW clnt_stat Xerrors, feed the error code to X.LW clnt_perrno() . X.NH 2 XBatching X.LP XThe RPC architecture is designed so that clients send a call message, Xand wait for servers to reply that the call succeeded. XThis implies that clients do not compute Xwhile servers are processing a call. XThis is inefficient if the client does not want or need Xan acknowledgement for every message sent. XIt is possible for clients to continue computing Xwhile waiting for a response, Xusing RPC batch facilities. X.LP XRPC messages can be placed in a ``pipeline'' of calls Xto a desired server; this is called batching. XBatching assumes that: X1) each RPC call in the pipeline requires no response from the server, Xand the server does not send a response message; and X2) the pipeline of calls is transported on a reliable Xbyte stream transport such as TCP/IP. XSince the server does not respond to every call, Xthe client can generate new calls in parallel Xwith the server executing previous calls. XFurthermore, the TCP/IP implementation can buffer up Xmany call messages, and send them to the server in one X.LW write() Xsystem call. This overlapped execution Xgreatly decreases the interprocess communication overhead of Xthe client and server processes, Xand the total elapsed time of a series of calls. X.LP XSince the batched calls are buffered, Xthe client should eventually do a legitimate call Xin order to flush the pipeline. X.LP XA contrived example of batching follows. XAssume a string rendering service (like a window system) Xhas two similar calls: one renders a string and returns void results, Xwhile the other renders a string and remains silent. XThe service (using the TCP/IP transport) may look like: X.BS X.LS no X#include <stdio.h> X#include <rpc/rpc.h> X#include <rpcsvc/windows.h> X.sp.5 Xvoid windowdispatch(); X.sp.5 Xmain() X{ X SVCXPRT *transp; X.sp.5 X transp = svctcp_create(RPC_ANYSOCK, 0, 0); X if (transp == NULL){ X fprintf(stderr, "can't create an RPC server\en"); X exit(1); X } X pmap_unset(WINDOWPROG, WINDOWVERS); X if (!svc_register(transp, WINDOWPROG, WINDOWVERS, X windowdispatch, IPPROTO_TCP)) { X fprintf(stderr, "can't register WINDOW service\en"); X exit(1); X } X svc_run(); /* never returns */ X fprintf(stderr, "should never reach this point\en"); X} X.sp.5 Xvoid Xwindowdispatch(rqstp, transp) X struct svc_req *rqstp; X SVCXPRT *transp; X{ X char *s = NULL; X.sp.5 X switch (rqstp->rq_proc) { X case NULLPROC: X if (!svc_sendreply(transp, xdr_void, 0)) { X fprintf(stderr, "can't reply to RPC call\en"); X exit(1); X } X return; X case RENDERSTRING: X if (!svc_getargs(transp, xdr_wrapstring, &s)) { X fprintf(stderr, "can't decode arguments\en"); X /* X * tell caller he screwed up X */ X svcerr_decode(transp); X break; X } X /* X * call here to render the string s X */ X if (!svc_sendreply(transp, xdr_void, NULL)) { X fprintf(stderr, "can't reply to RPC call\en"); X exit(1); X } X break; X case RENDERSTRING_BATCHED: X if (!svc_getargs(transp, xdr_wrapstring, &s)) { X fprintf(stderr, "can't decode arguments\en"); X /* X * we are silent in the face of protocol errors X */ X break; X } X /* X * call here to render string s, but send no reply! X */ X break; X default: X svcerr_noproc(transp); X return; X } X /* X * now free string allocated while decoding arguments X */ X svc_freeargs(transp, xdr_wrapstring, &s); X} X.Lf X.BE XOf course the service could have one procedure Xthat takes the string and a boolean Xto indicate whether or not the procedure should respond. X.LP XIn order for a client to take advantage of batching, Xthe client must perform RPC calls on a TCP-based transport Xand the actual calls must have the following attributes: X1) the result's XDR routine must be zero X.LW NULL ), ( Xand 2) the RPC call's timeout must be zero. X.LP XHere is an example of a client that uses batching Xto render a bunch of strings; Xthe batching is flushed when the client gets a null string: X.BS X.LS no X#include <stdio.h> X#include <rpc/rpc.h> X#include <rpcsvc/windows.h> X#include <sys/socket.h> X#include <sys/time.h> X#include <netdb.h> X.sp.5 Xmain(argc, argv) X int argc; X char **argv; X{ X struct hostent *hp; X struct timeval pertry_timeout, total_timeout; X struct sockaddr_in server_addr; X int addrlen, sock = RPC_ANYSOCK; X register CLIENT *client; X enum clnt_stat clnt_stat; X char buf[1000], *s = buf; X.sp.5 X /* initial as in example 3.3 X */ X if ((client = clnttcp_create(&server_addr, X WINDOWPROG, WINDOWVERS, &sock, 0, 0)) == NULL) { X perror("clnttcp_create"); X exit(-1); X } X total_timeout.tv_sec = 0; X total_timeout.tv_usec = 0; X while (scanf("%s", s) != EOF) { X clnt_stat = clnt_call(client, RENDERSTRING_BATCHED, X xdr_wrapstring, &s, NULL, NULL, total_timeout); X if (clnt_stat != RPC_SUCCESS) { X clnt_perror(client, "batched rpc"); X exit(-1); X } X } X /* now flush the pipeline X */ X total_timeout.tv_sec = 20; X clnt_stat = clnt_call(client, NULLPROC, xdr_void, NULL, X xdr_void, NULL, total_timeout); X if (clnt_stat != RPC_SUCCESS) { X clnt_perror(client, "rpc"); X exit(-1); X } X clnt_destroy(client); X} X.Lf X.BE XSince the server sends no message, Xthe clients cannot be notified of any of the failures that may occur. XTherefore, clients are on their own when it comes to handling errors. X.LP XThe above example was completed to render Xall of the (2000) lines in the file X.I /etc/termcap . XThe rendering service did nothing but throw the lines away. XThe example was run in the following four configurations: X1) machine to itself, regular RPC; X2) machine to itself, batched RPC; X3) machine to another, regular RPC; and X4) machine to another, batched RPC. XThe results are as follows: X1) 50 seconds; X2) 16 seconds; X3) 52 seconds; X4) 10 seconds. XRunning X.LW fscanf() Xon X.I /etc/termcap Xonly requires six seconds. XThese timings show the advantage of protocols Xthat allow for overlapped execution, Xthough these protocols are often hard to design. X.NH 2 XAuthentication X.LP XIn the examples presented so far, Xthe caller never identified itself to the server, Xand the server never required an ID from the caller. XClearly, some network services, such as a network filesystem, Xrequire stronger security than what has been presented so far. X.LP XIn reality, every RPC call is authenticated by Xthe RPC package on the server, and similarly, Xthe RPC client package generates and sends authentication parameters. XJust as different transports (TCP/IP or UDP/IP) Xcan be used when creating RPC clients and servers, Xdifferent forms of authentication can be associated with RPC clients; Xthe default authentication type used as a default is type X.I none . X.LP XThe authentication subsystem of the RPC package is open ended. XThat is, numerous types of authentication are easy to support. XHowever, this section deals only with X.I unix Xtype authentication, which besides X.I none Xis the only supported type. X.NH 3 XThe Client Side X.LP XWhen a caller creates a new RPC client handle as in: X.BS X.LS Xclnt = clntudp_create(address, prognum, versnum, X wait, sockp) X.Lf X.BE Xthe appropriate transport instance defaults Xthe associate authentication handle to be X.BS X.LS Xclnt->cl_auth = authnone_create(); X.Lf X.BE XThe RPC client can choose to use X.I unix Xstyle authentication by setting X.LW clnt->cl_auth Xafter creating the RPC client handle: X.BS X.LS Xclnt->cl_auth = authunix_create_default(); X.Lf X.BE XThis causes each RPC call associated with X.LW clnt Xto carry with it the following authentication credentials structure: X.BS X.LS X/* X * Unix style credentials. X */ Xstruct authunix_parms { X u_long aup_time; /* credentials creation time */ X char *aup_machname; /* host name where client is */ X int aup_uid; /* client's UNIX effective uid */ X int aup_gid; /* client's current group id */ X u_int aup_len; /* element length of aup_gids */ X int *aup_gids; /* array of groups user is in */ X}; X.Lf X.BE XThese fields are set by X.LW authunix_create_default() Xby invoking the appropriate system calls. XSince the RPC user created this new style of authentication, Xthe user is responsible for destroying it with: X.BS X.LS Xauth_destroy(clnt->cl_auth); X.Lf X.BE XThis should be done in all cases, to conserve memory. X.NH 3 XThe Server Side X.LP XService implementors have a harder time dealing with authentication issues Xsince the RPC package passes the service dispatch routine a request Xthat has an arbitrary authentication style associated with it. XConsider the fields of a request handle passed to a service dispatch routine: X.BS X.LS X/* X * An RPC Service request X */ Xstruct svc_req { X u_long rq_prog; /* service program number */ X u_long rq_vers; /* service protocol vers num */ X u_long rq_proc; /* desired procedure number */ X struct opaque_auth X rq_cred; /* raw credentials from wire */ X caddr_t rq_clntcred; /* credentials (read only) */ X}; X.Lf X.BE XThe X.LW rq_cred Xis mostly opaque, except for one field of interest: Xthe style of authentication credentials: X.BS X.LS X/* X * Authentication info. Mostly opaque to the programmer. X */ Xstruct opaque_auth { X enum_t oa_flavor; /* style of credentials */ X caddr_t oa_base; /* address of more auth stuff */ X u_int oa_length; /* not to exceed MAX_AUTH_BYTES */ X}; X.Lf X.BE XThe RPC package guarantees the following Xto the service dispatch routine: X.IP 1. XThat the request's X.LW rq_cred Xis well formed. Thus the service implementor may inspect the request's X.LW rq_cred.oa_flavor Xto determine which style of authentication the caller used. XThe service implementor may also wish to inspect the other fields of X.LW rq_cred Xif the style is not one of the styles supported by the RPC package. X.IP 2. XThat the request's X.LW rq_clntcred Xfield is either X.LW NULL Xor points to a well formed structure Xthat corresponds to a supported style of authentication credentials. XRemember that only X.I unix Xstyle is currently supported, so (currently) X.LW rq_clntcred Xcould be cast to a pointer to an X.LW authunix_parms Xstructure. If X.LW rq_clntcred Xis X.LW NULL , Xthe service implementor may wish to inspect the other (opaque) fileds of X.LW rq_cred Xin case the service knows about a new type of authentication Xthat the RPC package does not know about. X.LP XOur remote users service example can be extended so that Xit computes results for all users except UID 16: X.BS X.LS no Xnuser(rqstp, tranp) X struct svc_req *rqstp; X SVCXPRT *transp; X{ X struct authunix_parms *unix_cred; X int uid; X unsigned long nusers; X.sp.5 X /* X * we don't care about authentication for null proc X */ X if (rqstp->rq_proc == NULLPROC) { X if (!svc_sendreply(transp, xdr_void, 0)) { X fprintf(stderr, "can't reply to RPC call\en"); X exit(1); X } X return; X } X /* X * now get the uid X */ X switch (rqstp->rq_cred.oa_flavor) { X case AUTH_UNIX: X unix_cred = (struct authunix_parms *)rqstp->rq_clntcred; X uid = unix_cred->aup_uid; X break; X case AUTH_NULL: X default: X svcerr_weakauth(transp); X return; X } X switch (rqstp->rq_proc) { X case RUSERSPROC_NUM: X /* X * make sure caller is allowed to call this proc X */ X if (uid == 16) { X svcerr_systemerr(transp); X return; X } X /* X * code here to compute the number of users X * and put in variable nusers X */ X if (!svc_sendreply(transp, xdr_u_long, &nusers) { X fprintf(stderr, "can't reply to RPC call\en"); X exit(1); X } X return; X default: X svcerr_noproc(transp); X return; X } X} X.Lf X.BE XA few things should be noted here. XFirst, it is customary not to check Xthe authentication parameters associated with the X.LW NULLPROC X(procedure number zero). XSecond, if the authentication parameter's type is not suitable Xfor your service, you should call X.LW svcerr_weakauth() . XAnd finally, the service protocol itself should return status Xfor access denied; in the case of our example, the protocol Xdoes not have such a status, so we call the service primitive X.LW svcerr_systemerr() Xinstead. X.LP XThe last point underscores the relation between Xthe RPC authentication package and the services; XRPC deals only with authentication and not with Xindividual services' access control. XThe services themselves must implement their own access control policies Xand reflect these policies as return statuses in their protocols. X.NH 2 XUsing Inetd X.LP XAn RPC server can be started from X.LW inetd . XThe only difference Xfrom the usual code is that X.LW svcudp_create() Xshould be called as X.BS X.LS Xtransp = svcudp_create(0); X.Lf X.BE Xsince X.LW inet Xpasses a socket as file descriptor 0. XAlso, X.LW svc_register() Xshould be called as X.BS X.LS Xsvc_register(transp, PROGNUM, VERSNUM, service, 0); X.Lf X.BE Xwith the final flag as 0, Xsince the program would already be registered by X.LW inetd . XRemember that if you want to exit Xfrom the server process and return control to X.LW inet , Xyou need to explicitly exit, since X.LW svc_run() Xnever returns. X.LP XThe format of entries in /etc/servers for RPC services is X.BS X.LS Xrpc udp \fIserver \0program \0version\fP X.Lf X.BE Xwhere X.I server Xis the C code implementing the server, Xand X.I program Xand X.I version Xare the program and version numbers of the service. XThe key word X.LW udp Xcan be replaced by X.LW tcp Xfor TCP-based RPC services. X.LP XIf the same program handles multiple versions, Xthen the version number can be a range, Xas in this example: X.BS X.LS Xrpc udp /usr/etc/rstatd 100001 1-2 X.Lf X.BE X.bp X.NH XMore Examples X.NH 2 XVersions X.LP XBy convention, the first version number of program X.LW PROG Xis X.LW PROGVERS_ORIG Xand the most recent version is X.LW PROGVERS . XSuppose there is a new version of the X.LW user Xprogram that returns an X.LW "unsigned short" Xrather than a X.LW long . XIf we name this version X.LW RUSERSVERS_SHORT , Xthen a server that wants to support both versions Xwould do a double register. X.BS X.LS Xif (!svc_register(transp, RUSERSPROG, RUSERSVERS_ORIG, X nuser, IPPROTO_TCP)) { X fprintf(stderr, "can't register RUSER service\en"); X exit(1); X} Xif (!svc_register(transp, RUSERSPROG, RUSERSVERS_SHORT, X nuser, IPPROTO_TCP)) { X fprintf(stderr, "can't register RUSER service\en"); X exit(1); X} X.Lf X.BE XBoth versions can be handled by the same C procedure: X.BS X.LS no Xnuser(rqstp, tranp) X struct svc_req *rqstp; X SVCXPRT *transp; X{ X unsigned long nusers; X unsigned short nusers2 X.sp.5 X switch (rqstp->rq_proc) { X case NULLPROC: X if (!svc_sendreply(transp, xdr_void, 0)) { X fprintf(stderr, "can't reply to RPC call\en"); X exit(1); X } X return; X case RUSERSPROC_NUM: X /* X * code here to compute the number of users X * and put in variable nusers X */ X nusers2 = nusers; X if (rqstp->rq_vers != RUSERSVERS_ORIG) X return; X if (!svc_sendreply(transp, xdr_u_long, &nusers) { X fprintf(stderr, "can't reply to RPC call\en"); X exit(1); X } else X if (!svc_sendreply(transp, xdr_u_short, &nusers2) { X fprintf(stderr, "can't reply to RPC call\en"); X exit(1); X } X return; X default: X svcerr_noproc(transp); X return; X } X} X.Lf X.BE X.NH 2 XTCP X.LP XHere is an example that is essentially X.LW rcp . XThe initiator of the RPC X.LW snd() Xcall takes its standard input and sends it to the server X.LW rcv() , Xwhich prints it on standard output. XThe RPC call uses TCP. XThis also illustrates an XDR procedure that behaves differently Xon serialization than on deserialization. X.BS X.LS no X/* X * The xdr routine: X * on decode, read from wire, write onto fp X * on encode, read from fp, write onto wire X */ X#include <stdio.h> X#include <rpc/rpc.h> X.sp.5 Xxdr_rcp(xdrs, fp) X XDR *xdrs; X FILE *fp; X{ X unsigned long size; X char buf[BUFSIZ], *p; X.sp.5 X if (xdrs->x_op == XDR_FREE)/* nothing to free */ X return 1; X while (1) { X if (xdrs->x_op == XDR_ENCODE) { X if ((size = fread(buf, sizeof(char), BUFSIZ, X fp)) == 0 && ferror(fp)) { X fprintf(stderr, "can't fread\en"); X exit(1); X } X } X p = buf; X if (!xdr_bytes(xdrs, &p, &size, BUFSIZ)) X return 0; X if (size == 0) X return 1; X if (xdrs->x_op == XDR_DECODE) { X if (fwrite(buf, sizeof(char), size, X fp) != size) { X fprintf(stderr, "can't fwrite\en"); X exit(1); X } X } X } X} X.sp.5 X/* X * The sender routines X */ X#include <stdio.h> X#include <netdb.h> X#include <rpc/rpc.h> X#include <sys/socket.h> X#include <sys/time.h> X.sp.5 Xmain(argc, argv) X int argc; X char **argv; X{ X int err; X.sp.5 X if (argc < 2) { X fprintf(stderr, "usage: %s servername\en", argv[0]); X exit(-1); X } X if ((err = callrpctcp(argv[1], RCPPROG, RCPPROC_FP, X RCPVERS, xdr_rcp, stdin, xdr_void, 0) != 0)) { X clnt_perrno(err); X fprintf(stderr, "can't make RPC call\en"); X exit(1); X } X} X.sp.5 Xcallrpctcp(host, prognum, procnum, versnum, X inproc, in, outproc, out) X char *host, *in, *out; X xdrproc_t inproc, outproc; X{ X struct sockaddr_in server_addr; X int socket = RPC_ANYSOCK; X enum clnt_stat clnt_stat; X struct hostent *hp; X register CLIENT *client; X struct timeval total_timeout; X.sp.5 X if ((hp = gethostbyname(host)) == NULL) { X fprintf(stderr, "can't get addr for '%s'\en", host); X exit(-1); X } X bcopy(hp->h_addr, (caddr_t)&server_addr.sin_addr, X hp->h_length); X server_addr.sin_family = AF_INET; X server_addr.sin_port = 0; X if ((client = clnttcp_create(&server_addr, prognum, X versnum, &socket, BUFSIZ, BUFSIZ)) == NULL) { X perror("rpctcp_create"); X exit(-1); X } X total_timeout.tv_sec = 20; X total_timeout.tv_usec = 0; X clnt_stat = clnt_call(client, procnum, X inproc, in, outproc, out, total_timeout); X clnt_destroy(client) X return (int)clnt_stat; X} X.sp.5 X/* X * The receiving routines X */ X#include <stdio.h> X#include <rpc/rpc.h> X.sp.5 Xmain() X{ X register SVCXPRT *transp; X.sp.5 X if ((transp = svctcp_create(RPC_ANYSOCK, X BUFSIZ, BUFSIZ)) == NULL) { X fprintf("svctcp_create: error\en"); X exit(1); X } X pmap_unset(RCPPROG, RCPVERS); X if (!svc_register(transp, X RCPPROG, RCPVERS, rcp_service, IPPROTO_TCP)) { X fprintf(stderr, "svc_register: error\en"); X exit(1); X } X svc_run(); /* never returns */ X fprintf(stderr, "svc_run should never return\en"); X} X.sp.5 Xrcp_service(rqstp, transp) X register struct svc_req *rqstp; X register SVCXPRT *transp; X{ X switch (rqstp->rq_proc) { X case NULLPROC: X if (svc_sendreply(transp, xdr_void, 0) == 0) { X fprintf(stderr, "err: rcp_service"); X exit(1); X } X return; X case RCPPROC_FP: X if (!svc_getargs(transp, xdr_rcp, stdout)) { X svcerr_decode(transp); X return; X } X if (!svc_sendreply(transp, xdr_void, 0)) { X fprintf(stderr, "can't reply\en"); X return; X } X exit(0); X default: X svcerr_noproc(transp); X return; X } X} X.Lf X.BE X.NH 2 XCallback Procedures X.LP XOccasionally, it is useful to have a server become a client, Xand make an RPC call back the process which is its client. XAn example is remote debugging, Xwhere the client is a window system program, Xand the server is a debugger running on the remote machine. XMost of the time, Xthe user clicks a mouse button at the debugging window, Xwhich converts this to a debugger command, Xand then makes an RPC call to the server X(where the debugger is actually running), Xtelling it to execute that command. XHowever, when the debugger hits a breakpoint, the roles are reversed, Xand the debugger wants to make an rpc call to the window program, Xso that it can inform the user that a breakpoint has been reached. SHAR_EOF if test 49365 -ne "`wc -c < 'rpc/doc/rpc.prog.p1'`" then echo shar: "error transmitting 'rpc/doc/rpc.prog.p1'" '(should have been 49365 characters)' fi chmod 444 'rpc/doc/rpc.prog.p1' fi exit 0 # End of shell archive