From 9e4ded5d0e1054f0c364754e1d53942ff71fb8ba Mon Sep 17 00:00:00 2001 From: Garrett Wollman Date: Sun, 7 Aug 1994 18:46:28 +0000 Subject: [PATCH] Add Sun RPC documentation, which should eventually go into our PSD. (I think I'm up to part 6.) --- lib/libc/rpc/PSD.doc/nfs.rfc.ms | 1372 +++++++++++++++ lib/libc/rpc/PSD.doc/rpc.prog.ms | 2684 ++++++++++++++++++++++++++++++ lib/libc/rpc/PSD.doc/rpc.rfc.ms | 1302 +++++++++++++++ lib/libc/rpc/PSD.doc/rpcgen.ms | 1299 +++++++++++++++ lib/libc/rpc/PSD.doc/xdr.nts.ms | 1966 ++++++++++++++++++++++ lib/libc/rpc/PSD.doc/xdr.rfc.ms | 1058 ++++++++++++ 6 files changed, 9681 insertions(+) create mode 100644 lib/libc/rpc/PSD.doc/nfs.rfc.ms create mode 100644 lib/libc/rpc/PSD.doc/rpc.prog.ms create mode 100644 lib/libc/rpc/PSD.doc/rpc.rfc.ms create mode 100644 lib/libc/rpc/PSD.doc/rpcgen.ms create mode 100644 lib/libc/rpc/PSD.doc/xdr.nts.ms create mode 100644 lib/libc/rpc/PSD.doc/xdr.rfc.ms diff --git a/lib/libc/rpc/PSD.doc/nfs.rfc.ms b/lib/libc/rpc/PSD.doc/nfs.rfc.ms new file mode 100644 index 000000000000..0c9a8995b5c7 --- /dev/null +++ b/lib/libc/rpc/PSD.doc/nfs.rfc.ms @@ -0,0 +1,1372 @@ +.\" +.\" Must use -- tbl -- with this one +.\" +.\" @(#)nfs.rfc.ms 2.2 88/08/05 4.0 RPCSRC +.de BT +.if \\n%=1 .tl ''- % -'' +.. +.ND +.\" prevent excess underlining in nroff +.if n .fp 2 R +.OH 'Network File System: Version 2 Protocol Specification''Page %' +.EH 'Page %''Network File System: Version 2 Protocol Specification' +.if \\n%=1 .bp +.SH +\&Network File System: Version 2 Protocol Specification +.IX NFS "" "" "" PAGE MAJOR +.IX "Network File System" "" "" "" PAGE MAJOR +.IX NFS "version-2 protocol specification" +.IX "Network File System" "version-2 protocol specification" +.LP +.NH 0 +\&Status of this Standard +.LP +Note: This document specifies a protocol that Sun Microsystems, Inc., +and others are using. It specifies it in standard ARPA RFC form. +.NH 1 +\&Introduction +.IX NFS introduction +.LP +The Sun Network Filesystem (NFS) protocol provides transparent remote +access to shared filesystems over local area networks. The NFS +protocol is designed to be machine, operating system, network architecture, +and transport protocol independent. This independence is +achieved through the use of Remote Procedure Call (RPC) primitives +built on top of an External Data Representation (XDR). Implementations +exist for a variety of machines, from personal computers to +supercomputers. +.LP +The supporting mount protocol allows the server to hand out remote +access privileges to a restricted set of clients. It performs the +operating system-specific functions that allow, for example, to +attach remote directory trees to some local file system. +.NH 2 +\&Remote Procedure Call +.IX "Remote Procedure Call" +.LP +Sun's remote procedure call specification provides a procedure- +oriented interface to remote services. Each server supplies a +program that is a set of procedures. NFS is one such "program". +The combination of host address, program number, and procedure +number specifies one remote service procedure. RPC does not depend +on services provided by specific protocols, so it can be used with +any underlying transport protocol. See the +.I "Remote Procedure Calls: Protocol Specification" +chapter of this manual. +.NH 2 +\&External Data Representation +.IX "External Data Representation" +.LP +The External Data Representation (XDR) standard provides a common +way of representing a set of data types over a network. +The NFS +Protocol Specification is written using the RPC data description +language. +For more information, see the +.I " External Data Representation Standard: Protocol Specification." +Sun provides implementations of XDR and +RPC, but NFS does not require their use. Any software that +provides equivalent functionality can be used, and if the encoding +is exactly the same it can interoperate with other implementations +of NFS. +.NH 2 +\&Stateless Servers +.IX "stateless servers" +.IX servers stateless +.LP +The NFS protocol is stateless. That is, a server does not need to +maintain any extra state information about any of its clients in +order to function correctly. Stateless servers have a distinct +advantage over stateful servers in the event of a failure. With +stateless servers, a client need only retry a request until the +server responds; it does not even need to know that the server has +crashed, or the network temporarily went down. The client of a +stateful server, on the other hand, needs to either detect a server +crash and rebuild the server's state when it comes back up, or +cause client operations to fail. +.LP +This may not sound like an important issue, but it affects the +protocol in some unexpected ways. We feel that it is worth a bit +of extra complexity in the protocol to be able to write very simple +servers that do not require fancy crash recovery. +.LP +On the other hand, NFS deals with objects such as files and +directories that inherently have state -- what good would a file be +if it did not keep its contents intact? The goal is to not +introduce any extra state in the protocol itself. Another way to +simplify recovery is by making operations "idempotent" whenever +possible (so that they can potentially be repeated). +.NH 1 +\&NFS Protocol Definition +.IX NFS "protocol definition" +.IX NFS protocol +.LP +Servers have been known to change over time, and so can the +protocol that they use. So RPC provides a version number with each +RPC request. This RFC describes version two of the NFS protocol. +Even in the second version, there are various obsolete procedures +and parameters, which will be removed in later versions. An RFC +for version three of the NFS protocol is currently under +preparation. +.NH 2 +\&File System Model +.IX filesystem model +.LP +NFS assumes a file system that is hierarchical, with directories as +all but the bottom-level files. Each entry in a directory (file, +directory, device, etc.) has a string name. Different operating +systems may have restrictions on the depth of the tree or the names +used, as well as using different syntax to represent the "pathname", +which is the concatenation of all the "components" (directory and +file names) in the name. A "file system" is a tree on a single +server (usually a single disk or physical partition) with a specified +"root". Some operating systems provide a "mount" operation to make +all file systems appear as a single tree, while others maintain a +"forest" of file systems. Files are unstructured streams of +uninterpreted bytes. Version 3 of NFS uses a slightly more general +file system model. +.LP +NFS looks up one component of a pathname at a time. It may not be +obvious why it does not just take the whole pathname, traipse down +the directories, and return a file handle when it is done. There are +several good reasons not to do this. First, pathnames need +separators between the directory components, and different operating +systems use different separators. We could define a Network Standard +Pathname Representation, but then every pathname would have to be +parsed and converted at each end. Other issues are discussed in +\fINFS Implementation Issues\fP below. +.LP +Although files and directories are similar objects in many ways, +different procedures are used to read directories and files. This +provides a network standard format for representing directories. The +same argument as above could have been used to justify a procedure +that returns only one directory entry per call. The problem is +efficiency. Directories can contain many entries, and a remote call +to return each would be just too slow. +.NH 2 +\&RPC Information +.IX NFS "RPC information" +.IP \fIAuthentication\fP +The NFS service uses +.I AUTH_UNIX , +.I AUTH_DES , +or +.I AUTH_SHORT +style +authentication, except in the NULL procedure where +.I AUTH_NONE +is also allowed. +.IP "\fITransport Protocols\fP" +NFS currently is supported on UDP/IP only. +.IP "\fIPort Number\fP" +The NFS protocol currently uses the UDP port number 2049. This is +not an officially assigned port, so later versions of the protocol +use the \*QPortmapping\*U facility of RPC. +.NH 2 +\&Sizes of XDR Structures +.IX "XDR structure sizes" +.LP +These are the sizes, given in decimal bytes, of various XDR +structures used in the protocol: +.DS +/* \fIThe maximum number of bytes of data in a READ or WRITE request\fP */ +const MAXDATA = 8192; + +/* \fIThe maximum number of bytes in a pathname argument\fP */ +const MAXPATHLEN = 1024; + +/* \fIThe maximum number of bytes in a file name argument\fP */ +const MAXNAMLEN = 255; + +/* \fIThe size in bytes of the opaque "cookie" passed by READDIR\fP */ +const COOKIESIZE = 4; + +/* \fIThe size in bytes of the opaque file handle\fP */ +const FHSIZE = 32; +.DE +.NH 2 +\&Basic Data Types +.IX "NFS data types" +.IX NFS "basic data types" +.LP +The following XDR definitions are basic structures and types used +in other structures described further on. +.KS +.NH 3 +\&stat +.IX "NFS data types" stat "" \fIstat\fP +.DS +enum stat { + NFS_OK = 0, + NFSERR_PERM=1, + NFSERR_NOENT=2, + NFSERR_IO=5, + NFSERR_NXIO=6, + NFSERR_ACCES=13, + NFSERR_EXIST=17, + NFSERR_NODEV=19, + NFSERR_NOTDIR=20, + NFSERR_ISDIR=21, + NFSERR_FBIG=27, + NFSERR_NOSPC=28, + NFSERR_ROFS=30, + NFSERR_NAMETOOLONG=63, + NFSERR_NOTEMPTY=66, + NFSERR_DQUOT=69, + NFSERR_STALE=70, + NFSERR_WFLUSH=99 +}; +.DE +.KE +.LP +The +.I stat +type is returned with every procedure's results. A +value of +.I NFS_OK +indicates that the call completed successfully and +the results are valid. The other values indicate some kind of +error occurred on the server side during the servicing of the +procedure. The error values are derived from UNIX error numbers. +.IP \fBNFSERR_PERM\fP: +Not owner. The caller does not have correct ownership +to perform the requested operation. +.IP \fBNFSERR_NOENT\fP: +No such file or directory. The file or directory +specified does not exist. +.IP \fBNFSERR_IO\fP: +Some sort of hard error occurred when the operation was +in progress. This could be a disk error, for example. +.IP \fBNFSERR_NXIO\fP: +No such device or address. +.IP \fBNFSERR_ACCES\fP: +Permission denied. The caller does not have the +correct permission to perform the requested operation. +.IP \fBNFSERR_EXIST\fP: +File exists. The file specified already exists. +.IP \fBNFSERR_NODEV\fP: +No such device. +.IP \fBNFSERR_NOTDIR\fP: +Not a directory. The caller specified a +non-directory in a directory operation. +.IP \fBNFSERR_ISDIR\fP: +Is a directory. The caller specified a directory in +a non- directory operation. +.IP \fBNFSERR_FBIG\fP: +File too large. The operation caused a file to grow +beyond the server's limit. +.IP \fBNFSERR_NOSPC\fP: +No space left on device. The operation caused the +server's filesystem to reach its limit. +.IP \fBNFSERR_ROFS\fP: +Read-only filesystem. Write attempted on a read-only filesystem. +.IP \fBNFSERR_NAMETOOLONG\fP: +File name too long. The file name in an operation was too long. +.IP \fBNFSERR_NOTEMPTY\fP: +Directory not empty. Attempted to remove a +directory that was not empty. +.IP \fBNFSERR_DQUOT\fP: +Disk quota exceeded. The client's disk quota on the +server has been exceeded. +.IP \fBNFSERR_STALE\fP: +The "fhandle" given in the arguments was invalid. +That is, the file referred to by that file handle no longer exists, +or access to it has been revoked. +.IP \fBNFSERR_WFLUSH\fP: +The server's write cache used in the +.I WRITECACHE +call got flushed to disk. +.LP +.KS +.NH 3 +\&ftype +.IX "NFS data types" ftype "" \fIftype\fP +.DS +enum ftype { + NFNON = 0, + NFREG = 1, + NFDIR = 2, + NFBLK = 3, + NFCHR = 4, + NFLNK = 5 +}; +.DE +.KE +The enumeration +.I ftype +gives the type of a file. The type +.I NFNON +indicates a non-file, +.I NFREG +is a regular file, +.I NFDIR +is a directory, +.I NFBLK +is a block-special device, +.I NFCHR +is a character-special device, and +.I NFLNK +is a symbolic link. +.KS +.NH 3 +\&fhandle +.IX "NFS data types" fhandle "" \fIfhandle\fP +.DS +typedef opaque fhandle[FHSIZE]; +.DE +.KE +The +.I fhandle +is the file handle passed between the server and the client. +All file operations are done using file handles to refer to a file or +directory. The file handle can contain whatever information the server +needs to distinguish an individual file. +.KS +.NH 3 +\&timeval +.IX "NFS data types" timeval "" \fItimeval\fP +.DS +struct timeval { + unsigned int seconds; + unsigned int useconds; +}; +.DE +.KE +The +.I timeval +structure is the number of seconds and microseconds +since midnight January 1, 1970, Greenwich Mean Time. It is used to +pass time and date information. +.KS +.NH 3 +\&fattr +.IX "NFS data types" fattr "" \fIfattr\fP +.DS +struct fattr { + ftype type; + unsigned int mode; + unsigned int nlink; + unsigned int uid; + unsigned int gid; + unsigned int size; + unsigned int blocksize; + unsigned int rdev; + unsigned int blocks; + unsigned int fsid; + unsigned int fileid; + timeval atime; + timeval mtime; + timeval ctime; +}; +.DE +.KE +The +.I fattr +structure contains the attributes of a file; "type" is the type of +the file; "nlink" is the number of hard links to the file (the number +of different names for the same file); "uid" is the user +identification number of the owner of the file; "gid" is the group +identification number of the group of the file; "size" is the size in +bytes of the file; "blocksize" is the size in bytes of a block of the +file; "rdev" is the device number of the file if it is type +.I NFCHR +or +.I NFBLK ; +"blocks" is the number of blocks the file takes up on disk; "fsid" is +the file system identifier for the filesystem containing the file; +"fileid" is a number that uniquely identifies the file within its +filesystem; "atime" is the time when the file was last accessed for +either read or write; "mtime" is the time when the file data was last +modified (written); and "ctime" is the time when the status of the +file was last changed. Writing to the file also changes "ctime" if +the size of the file changes. +.LP +"mode" is the access mode encoded as a set of bits. Notice that the +file type is specified both in the mode bits and in the file type. +This is really a bug in the protocol and will be fixed in future +versions. The descriptions given below specify the bit positions +using octal numbers. +.TS +box tab (&) ; +cfI cfI +lfL l . +Bit&Description +_ +0040000&This is a directory; "type" field should be NFDIR. +0020000&This is a character special file; "type" field should be NFCHR. +0060000&This is a block special file; "type" field should be NFBLK. +0100000&This is a regular file; "type" field should be NFREG. +0120000&This is a symbolic link file; "type" field should be NFLNK. +0140000&This is a named socket; "type" field should be NFNON. +0004000&Set user id on execution. +0002000&Set group id on execution. +0001000&Save swapped text even after use. +0000400&Read permission for owner. +0000200&Write permission for owner. +0000100&Execute and search permission for owner. +0000040&Read permission for group. +0000020&Write permission for group. +0000010&Execute and search permission for group. +0000004&Read permission for others. +0000002&Write permission for others. +0000001&Execute and search permission for others. +.TE +.KS +Notes: +.IP +The bits are the same as the mode bits returned by the +.I stat(2) +system call in the UNIX system. The file type is specified both in +the mode bits and in the file type. This is fixed in future +versions. +.IP +The "rdev" field in the attributes structure is an operating system +specific device specifier. It will be removed and generalized in +the next revision of the protocol. +.KE +.LP +.KS +.NH 3 +\&sattr +.IX "NFS data types" sattr "" \fIsattr\fP +.DS +struct sattr { + unsigned int mode; + unsigned int uid; + unsigned int gid; + unsigned int size; + timeval atime; + timeval mtime; +}; +.DE +.KE +The +.I sattr +structure contains the file attributes which can be set +from the client. The fields are the same as for +.I fattr +above. A "size" of zero means the file should be truncated. +A value of -1 indicates a field that should be ignored. +.LP +.KS +.NH 3 +\&filename +.IX "NFS data types" filename "" \fIfilename\fP +.DS +typedef string filename; +.DE +.KE +The type +.I filename +is used for passing file names or pathname components. +.LP +.KS +.NH 3 +\&path +.IX "NFS data types" path "" \fIpath\fP +.DS +typedef string path; +.DE +.KE +The type +.I path +is a pathname. The server considers it as a string +with no internal structure, but to the client it is the name of a +node in a filesystem tree. +.LP +.KS +.NH 3 +\&attrstat +.IX "NFS data types" attrstat "" \fIattrstat\fP +.DS +union attrstat switch (stat status) { + case NFS_OK: + fattr attributes; + default: + void; +}; +.DE +.KE +The +.I attrstat +structure is a common procedure result. It contains +a "status" and, if the call succeeded, it also contains the +attributes of the file on which the operation was done. +.LP +.KS +.NH 3 +\&diropargs +.IX "NFS data types" diropargs "" \fIdiropargs\fP +.DS +struct diropargs { + fhandle dir; + filename name; +}; +.DE +.KE +The +.I diropargs +structure is used in directory operations. The +"fhandle" "dir" is the directory in which to find the file "name". +A directory operation is one in which the directory is affected. +.LP +.KS +.NH 3 +\&diropres +.IX "NFS data types" diropres "" \fIdiropres\fP +.DS +union diropres switch (stat status) { + case NFS_OK: + struct { + fhandle file; + fattr attributes; + } diropok; + default: + void; +}; +.DE +.KE +The results of a directory operation are returned in a +.I diropres +structure. If the call succeeded, a new file handle "file" and the +"attributes" associated with that file are returned along with the +"status". +.NH 2 +\&Server Procedures +.IX "NFS server procedures" "" "" "" PAGE MAJOR +.LP +The protocol definition is given as a set of procedures with +arguments and results defined using the RPC language. A brief +description of the function of each procedure should provide enough +information to allow implementation. +.LP +All of the procedures in the NFS protocol are assumed to be +synchronous. When a procedure returns to the client, the client +can assume that the operation has completed and any data associated +with the request is now on stable storage. For example, a client +.I WRITE +request may cause the server to update data blocks, +filesystem information blocks (such as indirect blocks), and file +attribute information (size and modify times). When the +.I WRITE +returns to the client, it can assume that the write is safe, even +in case of a server crash, and it can discard the data written. +This is a very important part of the statelessness of the server. +If the server waited to flush data from remote requests, the client +would have to save those requests so that it could resend them in +case of a server crash. +.ie t .DS +.el .DS L + +.ft I +/* +* Remote file service routines +*/ +.ft CW +program NFS_PROGRAM { + version NFS_VERSION { + void NFSPROC_NULL(void) = 0; + attrstat NFSPROC_GETATTR(fhandle) = 1; + attrstat NFSPROC_SETATTR(sattrargs) = 2; + void NFSPROC_ROOT(void) = 3; + diropres NFSPROC_LOOKUP(diropargs) = 4; + readlinkres NFSPROC_READLINK(fhandle) = 5; + readres NFSPROC_READ(readargs) = 6; + void NFSPROC_WRITECACHE(void) = 7; + attrstat NFSPROC_WRITE(writeargs) = 8; + diropres NFSPROC_CREATE(createargs) = 9; + stat NFSPROC_REMOVE(diropargs) = 10; + stat NFSPROC_RENAME(renameargs) = 11; + stat NFSPROC_LINK(linkargs) = 12; + stat NFSPROC_SYMLINK(symlinkargs) = 13; + diropres NFSPROC_MKDIR(createargs) = 14; + stat NFSPROC_RMDIR(diropargs) = 15; + readdirres NFSPROC_READDIR(readdirargs) = 16; + statfsres NFSPROC_STATFS(fhandle) = 17; + } = 2; +} = 100003; +.DE +.KS +.NH 3 +\&Do Nothing +.IX "NFS server procedures" NFSPROC_NULL() "" \fINFSPROC_NULL()\fP +.DS +void +NFSPROC_NULL(void) = 0; +.DE +.KE +This procedure does no work. It is made available in all RPC +services to allow server response testing and timing. +.KS +.NH 3 +\&Get File Attributes +.IX "NFS server procedures" NFSPROC_GETATTR() "" \fINFSPROC_GETATTR()\fP +.DS +attrstat +NFSPROC_GETATTR (fhandle) = 1; +.DE +.KE +If the reply status is +.I NFS_OK , +then the reply attributes contains +the attributes for the file given by the input fhandle. +.KS +.NH 3 +\&Set File Attributes +.IX "NFS server procedures" NFSPROC_SETATTR() "" \fINFSPROC_SETATTR()\fP +.DS +struct sattrargs { + fhandle file; + sattr attributes; + }; + +attrstat +NFSPROC_SETATTR (sattrargs) = 2; +.DE +.KE +The "attributes" argument contains fields which are either -1 or +are the new value for the attributes of "file". If the reply +status is +.I NFS_OK , +then the reply attributes have the attributes of +the file after the "SETATTR" operation has completed. +.LP +Note: The use of -1 to indicate an unused field in "attributes" is +changed in the next version of the protocol. +.KS +.NH 3 +\&Get Filesystem Root +.IX "NFS server procedures" NFSPROC_ROOT "" \fINFSPROC_ROOT\fP +.DS +void +NFSPROC_ROOT(void) = 3; +.DE +.KE +Obsolete. This procedure is no longer used because finding the +root file handle of a filesystem requires moving pathnames between +client and server. To do this right we would have to define a +network standard representation of pathnames. Instead, the +function of looking up the root file handle is done by the +.I MNTPROC_MNT() +procedure. (See the +.I "Mount Protocol Definition" +later in this chapter for details). +.KS +.NH 3 +\&Look Up File Name +.IX "NFS server procedures" NFSPROC_LOOKUP() "" \fINFSPROC_LOOKUP()\fP +.DS +diropres +NFSPROC_LOOKUP(diropargs) = 4; +.DE +.KE +If the reply "status" is +.I NFS_OK , +then the reply "file" and reply +"attributes" are the file handle and attributes for the file "name" +in the directory given by "dir" in the argument. +.KS +.NH 3 +\&Read From Symbolic Link +.IX "NFS server procedures" NFSPROC_READLINK() "" \fINFSPROC_READLINK()\fP +.DS +union readlinkres switch (stat status) { + case NFS_OK: + path data; + default: + void; +}; + +readlinkres +NFSPROC_READLINK(fhandle) = 5; +.DE +.KE +If "status" has the value +.I NFS_OK , +then the reply "data" is the data in +the symbolic link given by the file referred to by the fhandle argument. +.LP +Note: since NFS always parses pathnames on the client, the +pathname in a symbolic link may mean something different (or be +meaningless) on a different client or on the server if a different +pathname syntax is used. +.KS +.NH 3 +\&Read From File +.IX "NFS server procedures" NFSPROC_READ "" \fINFSPROC_READ\fP +.DS +struct readargs { + fhandle file; + unsigned offset; + unsigned count; + unsigned totalcount; +}; + +union readres switch (stat status) { + case NFS_OK: + fattr attributes; + opaque data; + default: + void; +}; + +readres +NFSPROC_READ(readargs) = 6; +.DE +.KE +Returns up to "count" bytes of "data" from the file given by +"file", starting at "offset" bytes from the beginning of the file. +The first byte of the file is at offset zero. The file attributes +after the read takes place are returned in "attributes". +.LP +Note: The argument "totalcount" is unused, and is removed in the +next protocol revision. +.KS +.NH 3 +\&Write to Cache +.IX "NFS server procedures" NFSPROC_WRITECACHE() "" \fINFSPROC_WRITECACHE()\fP +.DS +void +NFSPROC_WRITECACHE(void) = 7; +.DE +.KE +To be used in the next protocol revision. +.KS +.NH 3 +\&Write to File +.IX "NFS server procedures" NFSPROC_WRITE() "" \fINFSPROC_WRITE()\fP +.DS +struct writeargs { + fhandle file; + unsigned beginoffset; + unsigned offset; + unsigned totalcount; + opaque data; +}; + +attrstat +NFSPROC_WRITE(writeargs) = 8; +.DE +.KE +Writes "data" beginning "offset" bytes from the beginning of +"file". The first byte of the file is at offset zero. If the +reply "status" is NFS_OK, then the reply "attributes" contains the +attributes of the file after the write has completed. The write +operation is atomic. Data from this call to +.I WRITE +will not be mixed with data from another client's calls. +.LP +Note: The arguments "beginoffset" and "totalcount" are ignored and +are removed in the next protocol revision. +.KS +.NH 3 +\&Create File +.IX "NFS server procedures" NFSPROC_CREATE() "" \fINFSPROC_CREATE()\fP +.DS +struct createargs { + diropargs where; + sattr attributes; +}; + +diropres +NFSPROC_CREATE(createargs) = 9; +.DE +.KE +The file "name" is created in the directory given by "dir". The +initial attributes of the new file are given by "attributes". A +reply "status" of NFS_OK indicates that the file was created, and +reply "file" and reply "attributes" are its file handle and +attributes. Any other reply "status" means that the operation +failed and no file was created. +.LP +Note: This routine should pass an exclusive create flag, meaning +"create the file only if it is not already there". +.KS +.NH 3 +\&Remove File +.IX "NFS server procedures" NFSPROC_REMOVE() "" \fINFSPROC_REMOVE()\fP +.DS +stat +NFSPROC_REMOVE(diropargs) = 10; +.DE +.KE +The file "name" is removed from the directory given by "dir". A +reply of NFS_OK means the directory entry was removed. +.LP +Note: possibly non-idempotent operation. +.KS +.NH 3 +\&Rename File +.IX "NFS server procedures" NFSPROC_RENAME() "" \fINFSPROC_RENAME()\fP +.DS +struct renameargs { + diropargs from; + diropargs to; +}; + +stat +NFSPROC_RENAME(renameargs) = 11; +.DE +.KE +The existing file "from.name" in the directory given by "from.dir" +is renamed to "to.name" in the directory given by "to.dir". If the +reply is +.I NFS_OK , +the file was renamed. The +RENAME +operation is +atomic on the server; it cannot be interrupted in the middle. +.LP +Note: possibly non-idempotent operation. +.KS +.NH 3 +\&Create Link to File +.IX "NFS server procedures" NFSPROC_LINK() "" \fINFSPROC_LINK()\fP +.DS +struct linkargs { + fhandle from; + diropargs to; +}; + +stat +NFSPROC_LINK(linkargs) = 12; +.DE +.KE +Creates the file "to.name" in the directory given by "to.dir", +which is a hard link to the existing file given by "from". If the +return value is +.I NFS_OK , +a link was created. Any other return value +indicates an error, and the link was not created. +.LP +A hard link should have the property that changes to either of the +linked files are reflected in both files. When a hard link is made +to a file, the attributes for the file should have a value for +"nlink" that is one greater than the value before the link. +.LP +Note: possibly non-idempotent operation. +.KS +.NH 3 +\&Create Symbolic Link +.IX "NFS server procedures" NFSPROC_SYMLINK() "" \fINFSPROC_SYMLINK()\fP +.DS +struct symlinkargs { + diropargs from; + path to; + sattr attributes; +}; + +stat +NFSPROC_SYMLINK(symlinkargs) = 13; +.DE +.KE +Creates the file "from.name" with ftype +.I NFLNK +in the directory +given by "from.dir". The new file contains the pathname "to" and +has initial attributes given by "attributes". If the return value +is +.I NFS_OK , +a link was created. Any other return value indicates an +error, and the link was not created. +.LP +A symbolic link is a pointer to another file. The name given in +"to" is not interpreted by the server, only stored in the newly +created file. When the client references a file that is a symbolic +link, the contents of the symbolic link are normally transparently +reinterpreted as a pathname to substitute. A +.I READLINK +operation returns the data to the client for interpretation. +.LP +Note: On UNIX servers the attributes are never used, since +symbolic links always have mode 0777. +.KS +.NH 3 +\&Create Directory +.IX "NFS server procedures" NFSPROC_MKDIR() "" \fINFSPROC_MKDIR()\fP +.DS +diropres +NFSPROC_MKDIR (createargs) = 14; +.DE +.KE +The new directory "where.name" is created in the directory given by +"where.dir". The initial attributes of the new directory are given +by "attributes". A reply "status" of NFS_OK indicates that the new +directory was created, and reply "file" and reply "attributes" are +its file handle and attributes. Any other reply "status" means +that the operation failed and no directory was created. +.LP +Note: possibly non-idempotent operation. +.KS +.NH 3 +\&Remove Directory +.IX "NFS server procedures" NFSPROC_RMDIR() "" \fINFSPROC_RMDIR()\fP +.DS +stat +NFSPROC_RMDIR(diropargs) = 15; +.DE +.KE +The existing empty directory "name" in the directory given by "dir" +is removed. If the reply is +.I NFS_OK , +the directory was removed. +.LP +Note: possibly non-idempotent operation. +.KS +.NH 3 +\&Read From Directory +.IX "NFS server procedures" NFSPROC_READDIR() "" \fINFSPROC_READDIR()\fP +.DS +struct readdirargs { + fhandle dir; + nfscookie cookie; + unsigned count; +}; + +struct entry { + unsigned fileid; + filename name; + nfscookie cookie; + entry *nextentry; +}; + +union readdirres switch (stat status) { + case NFS_OK: + struct { + entry *entries; + bool eof; + } readdirok; + default: + void; +}; + +readdirres +NFSPROC_READDIR (readdirargs) = 16; +.DE +.KE +Returns a variable number of directory entries, with a total size +of up to "count" bytes, from the directory given by "dir". If the +returned value of "status" is +.I NFS_OK , +then it is followed by a +variable number of "entry"s. Each "entry" contains a "fileid" +which consists of a unique number to identify the file within a +filesystem, the "name" of the file, and a "cookie" which is an +opaque pointer to the next entry in the directory. The cookie is +used in the next +.I READDIR +call to get more entries starting at a +given point in the directory. The special cookie zero (all bits +zero) can be used to get the entries starting at the beginning of +the directory. The "fileid" field should be the same number as the +"fileid" in the the attributes of the file. (See the +.I "Basic Data Types" +section.) +The "eof" flag has a value of +.I TRUE +if there are no more entries in the directory. +.KS +.NH 3 +\&Get Filesystem Attributes +.IX "NFS server procedures" NFSPROC_STATFS() "" \fINFSPROC_STATFS()\fP +.DS +union statfsres (stat status) { + case NFS_OK: + struct { + unsigned tsize; + unsigned bsize; + unsigned blocks; + unsigned bfree; + unsigned bavail; + } info; + default: + void; +}; + +statfsres +NFSPROC_STATFS(fhandle) = 17; +.DE +.KE +If the reply "status" is +.I NFS_OK , +then the reply "info" gives the +attributes for the filesystem that contains file referred to by the +input fhandle. The attribute fields contain the following values: +.IP tsize: +The optimum transfer size of the server in bytes. This is +the number of bytes the server would like to have in the +data part of READ and WRITE requests. +.IP bsize: +The block size in bytes of the filesystem. +.IP blocks: +The total number of "bsize" blocks on the filesystem. +.IP bfree: +The number of free "bsize" blocks on the filesystem. +.IP bavail: +The number of "bsize" blocks available to non-privileged users. +.LP +Note: This call does not work well if a filesystem has variable +size blocks. +.NH 1 +\&NFS Implementation Issues +.IX NFS implementation +.LP +The NFS protocol is designed to be operating system independent, but +since this version was designed in a UNIX environment, many +operations have semantics similar to the operations of the UNIX file +system. This section discusses some of the implementation-specific +semantic issues. +.NH 2 +\&Server/Client Relationship +.IX NFS "server/client relationship" +.LP +The NFS protocol is designed to allow servers to be as simple and +general as possible. Sometimes the simplicity of the server can be a +problem, if the client wants to implement complicated filesystem +semantics. +.LP +For example, some operating systems allow removal of open files. A +process can open a file and, while it is open, remove it from the +directory. The file can be read and written as long as the process +keeps it open, even though the file has no name in the filesystem. +It is impossible for a stateless server to implement these semantics. +The client can do some tricks such as renaming the file on remove, +and only removing it on close. We believe that the server provides +enough functionality to implement most file system semantics on the +client. +.LP +Every NFS client can also potentially be a server, and remote and +local mounted filesystems can be freely intermixed. This leads to +some interesting problems when a client travels down the directory +tree of a remote filesystem and reaches the mount point on the server +for another remote filesystem. Allowing the server to follow the +second remote mount would require loop detection, server lookup, and +user revalidation. Instead, we decided not to let clients cross a +server's mount point. When a client does a LOOKUP on a directory on +which the server has mounted a filesystem, the client sees the +underlying directory instead of the mounted directory. A client can +do remote mounts that match the server's mount points to maintain the +server's view. +.LP +.NH 2 +\&Pathname Interpretation +.IX NFS "pathname interpretation" +.LP +There are a few complications to the rule that pathnames are always +parsed on the client. For example, symbolic links could have +different interpretations on different clients. Another common +problem for non-UNIX implementations is the special interpretation of +the pathname ".." to mean the parent of a given directory. The next +revision of the protocol uses an explicit flag to indicate the parent +instead. +.NH 2 +\&Permission Issues +.IX NFS "permission issues" +.LP +The NFS protocol, strictly speaking, does not define the permission +checking used by servers. However, it is expected that a server +will do normal operating system permission checking using +.I AUTH_UNIX +style authentication as the basis of its protection mechanism. The +server gets the client's effective "uid", effective "gid", and groups +on each call and uses them to check permission. There are various +problems with this method that can been resolved in interesting ways. +.LP +Using "uid" and "gid" implies that the client and server share the +same "uid" list. Every server and client pair must have the same +mapping from user to "uid" and from group to "gid". Since every +client can also be a server, this tends to imply that the whole +network shares the same "uid/gid" space. +.I AUTH_DES +(and the next +revision of the NFS protocol) uses string names instead of numbers, +but there are still complex problems to be solved. +.LP +Another problem arises due to the usually stateful open operation. +Most operating systems check permission at open time, and then check +that the file is open on each read and write request. With stateless +servers, the server has no idea that the file is open and must do +permission checking on each read and write call. On a local +filesystem, a user can open a file and then change the permissions so +that no one is allowed to touch it, but will still be able to write +to the file because it is open. On a remote filesystem, by contrast, +the write would fail. To get around this problem, the server's +permission checking algorithm should allow the owner of a file to +access it regardless of the permission setting. +.LP +A similar problem has to do with paging in from a file over the +network. The operating system usually checks for execute permission +before opening a file for demand paging, and then reads blocks from +the open file. The file may not have read permission, but after it +is opened it doesn't matter. An NFS server can not tell the +difference between a normal file read and a demand page-in read. To +make this work, the server allows reading of files if the "uid" given +in the call has execute or read permission on the file. +.LP +In most operating systems, a particular user (on the user ID zero) +has access to all files no matter what permission and ownership they +have. This "super-user" permission may not be allowed on the server, +since anyone who can become super-user on their workstation could +gain access to all remote files. The UNIX server by default maps +user id 0 to -2 before doing its access checking. This works except +for NFS root filesystems, where super-user access cannot be avoided. +.NH 2 +\&Setting RPC Parameters +.IX NFS "setting RPC parameters" +.LP +Various file system parameters and options should be set at mount +time. The mount protocol is described in the appendix below. For +example, "Soft" mounts as well as "Hard" mounts are usually both +provided. Soft mounted file systems return errors when RPC +operations fail (after a given number of optional retransmissions), +while hard mounted file systems continue to retransmit forever. +Clients and servers may need to keep caches of recent operations to +help avoid problems with non-idempotent operations. +.NH 1 +\&Mount Protocol Definition +.IX "mount protocol" "" "" "" PAGE MAJOR +.sp 1 +.NH 2 +\&Introduction +.IX "mount protocol" introduction +.LP +The mount protocol is separate from, but related to, the NFS +protocol. It provides operating system specific services to get the +NFS off the ground -- looking up server path names, validating user +identity, and checking access permissions. Clients use the mount +protocol to get the first file handle, which allows them entry into a +remote filesystem. +.LP +The mount protocol is kept separate from the NFS protocol to make it +easy to plug in new access checking and validation methods without +changing the NFS server protocol. +.LP +Notice that the protocol definition implies stateful servers because +the server maintains a list of client's mount requests. The mount +list information is not critical for the correct functioning of +either the client or the server. It is intended for advisory use +only, for example, to warn possible clients when a server is going +down. +.LP +Version one of the mount protocol is used with version two of the NFS +protocol. The only connecting point is the +.I fhandle +structure, which is the same for both protocols. +.NH 2 +\&RPC Information +.IX "mount protocol" "RPC information" +.IP \fIAuthentication\fP +The mount service uses +.I AUTH_UNIX +and +.I AUTH_DES +style authentication only. +.IP "\fITransport Protocols\fP" +The mount service is currently supported on UDP/IP only. +.IP "\fIPort Number\fP" +Consult the server's portmapper, described in the chapter +.I "Remote Procedure Calls: Protocol Specification", +to find the port number on which the mount service is registered. +.NH 2 +\&Sizes of XDR Structures +.IX "mount protocol" "XDR structure sizes" +.LP +These are the sizes, given in decimal bytes, of various XDR +structures used in the protocol: +.DS +/* \fIThe maximum number of bytes in a pathname argument\fP */ +const MNTPATHLEN = 1024; + +/* \fIThe maximum number of bytes in a name argument\fP */ +const MNTNAMLEN = 255; + +/* \fIThe size in bytes of the opaque file handle\fP */ +const FHSIZE = 32; +.DE +.NH 2 +\&Basic Data Types +.IX "mount protocol" "basic data types" +.IX "mount data types" +.LP +This section presents the data types used by the mount protocol. +In many cases they are similar to the types used in NFS. +.KS +.NH 3 +\&fhandle +.IX "mount data types" fhandle "" \fIfhandle\fP +.DS +typedef opaque fhandle[FHSIZE]; +.DE +.KE +The type +.I fhandle +is the file handle that the server passes to the +client. All file operations are done using file handles to refer +to a file or directory. The file handle can contain whatever +information the server needs to distinguish an individual file. +.LP +This is the same as the "fhandle" XDR definition in version 2 of +the NFS protocol; see +.I "Basic Data Types" +in the definition of the NFS protocol, above. +.KS +.NH 3 +\&fhstatus +.IX "mount data types" fhstatus "" \fIfhstatus\fP +.DS +union fhstatus switch (unsigned status) { + case 0: + fhandle directory; + default: + void; +}; +.DE +.KE +The type +.I fhstatus +is a union. If a "status" of zero is returned, +the call completed successfully, and a file handle for the +"directory" follows. A non-zero status indicates some sort of +error. In this case the status is a UNIX error number. +.KS +.NH 3 +\&dirpath +.IX "mount data types" dirpath "" \fIdirpath\fP +.DS +typedef string dirpath; +.DE +.KE +The type +.I dirpath +is a server pathname of a directory. +.KS +.NH 3 +\&name +.IX "mount data types" name "" \fIname\fP +.DS +typedef string name; +.DE +.KE +The type +.I name +is an arbitrary string used for various names. +.NH 2 +\&Server Procedures +.IX "mount server procedures" +.LP +The following sections define the RPC procedures supplied by a +mount server. +.ie t .DS +.el .DS L +.ft I +/* +* Protocol description for the mount program +*/ +.ft CW + +program MOUNTPROG { +.ft I +/* +* Version 1 of the mount protocol used with +* version 2 of the NFS protocol. +*/ +.ft CW + version MOUNTVERS { + void MOUNTPROC_NULL(void) = 0; + fhstatus MOUNTPROC_MNT(dirpath) = 1; + mountlist MOUNTPROC_DUMP(void) = 2; + void MOUNTPROC_UMNT(dirpath) = 3; + void MOUNTPROC_UMNTALL(void) = 4; + exportlist MOUNTPROC_EXPORT(void) = 5; + } = 1; +} = 100005; +.DE +.KS +.NH 3 +\&Do Nothing +.IX "mount server procedures" MNTPROC_NULL() "" \fIMNTPROC_NULL()\fP +.DS +void +MNTPROC_NULL(void) = 0; +.DE +.KE +This procedure does no work. It is made available in all RPC +services to allow server response testing and timing. +.KS +.NH 3 +\&Add Mount Entry +.IX "mount server procedures" MNTPROC_MNT() "" \fIMNTPROC_MNT()\fP +.DS +fhstatus +MNTPROC_MNT(dirpath) = 1; +.DE +.KE +If the reply "status" is 0, then the reply "directory" contains the +file handle for the directory "dirname". This file handle may be +used in the NFS protocol. This procedure also adds a new entry to +the mount list for this client mounting "dirname". +.KS +.NH 3 +\&Return Mount Entries +.IX "mount server procedures" MNTPROC_DUMP() "" \fIMNTPROC_DUMP()\fP +.DS +struct *mountlist { + name hostname; + dirpath directory; + mountlist nextentry; +}; + +mountlist +MNTPROC_DUMP(void) = 2; +.DE +.KE +Returns the list of remote mounted filesystems. The "mountlist" +contains one entry for each "hostname" and "directory" pair. +.KS +.NH 3 +\&Remove Mount Entry +.IX "mount server procedures" MNTPROC_UMNT() "" \fIMNTPROC_UMNT()\fP +.DS +void +MNTPROC_UMNT(dirpath) = 3; +.DE +.KE +Removes the mount list entry for the input "dirpath". +.KS +.NH 3 +\&Remove All Mount Entries +.IX "mount server procedures" MNTPROC_UMNTALL() "" \fIMNTPROC_UMNTALL()\fP +.DS +void +MNTPROC_UMNTALL(void) = 4; +.DE +.KE +Removes all of the mount list entries for this client. +.KS +.NH 3 +\&Return Export List +.IX "mount server procedures" MNTPROC_EXPORT() "" \fIMNTPROC_EXPORT()\fP +.DS +struct *groups { + name grname; + groups grnext; +}; + +struct *exportlist { + dirpath filesys; + groups groups; + exportlist next; +}; + +exportlist +MNTPROC_EXPORT(void) = 5; +.DE +.KE +Returns a variable number of export list entries. Each entry +contains a filesystem name and a list of groups that are allowed to +import it. The filesystem name is in "filesys", and the group name +is in the list "groups". +.LP +Note: The exportlist should contain +more information about the status of the filesystem, such as a +read-only flag. diff --git a/lib/libc/rpc/PSD.doc/rpc.prog.ms b/lib/libc/rpc/PSD.doc/rpc.prog.ms new file mode 100644 index 000000000000..3b02447fe84a --- /dev/null +++ b/lib/libc/rpc/PSD.doc/rpc.prog.ms @@ -0,0 +1,2684 @@ +.\" +.\" Must use -- tbl and pic -- with this one +.\" +.\" @(#)rpc.prog.ms 2.3 88/08/11 4.0 RPCSRC +.de BT +.if \\n%=1 .tl ''- % -'' +.. +.IX "Network Programming" "" "" "" PAGE MAJOR +.nr OF 0 +.ND +.\" prevent excess underlining in nroff +.if n .fp 2 R +.OH 'Remote Procedure Call Programming Guide''Page %' +.EH 'Page %''Remote Procedure Call Programming Guide' +.SH +\&Remote Procedure Call Programming Guide +.nr OF 1 +.IX "RPC Programming Guide" +.LP +This document assumes a working knowledge of network theory. It is +intended for programmers who wish to write network applications using +remote procedure calls (explained below), and who want to understand +the RPC mechanisms usually hidden by the +.I rpcgen(1) +protocol compiler. +.I rpcgen +is described in detail in the previous chapter, the +.I "\fBrpcgen\fP \fIProgramming Guide\fP". +.SH +Note: +.I +.IX rpcgen "" \fIrpcgen\fP +Before attempting to write a network application, or to convert an +existing non-network application to run over the network, you may want to +understand the material in this chapter. However, for most applications, +you can circumvent the need to cope with the details presented here by using +.I rpcgen . +The +.I "Generating XDR Routines" +section of that chapter contains the complete source for a working RPC +service\(ema remote directory listing service which uses +.I rpcgen +to generate XDR routines as well as client and server stubs. +.LP +.LP +What are remote procedure calls? Simply put, they are the high-level +communications paradigm used in the operating system. +RPC presumes the existence of +low-level networking mechanisms (such as TCP/IP and UDP/IP), and upon them +it implements a logical client to server communications system designed +specifically for the support of network applications. With RPC, the client +makes a procedure call to send a data packet to the server. When the +packet arrives, the server calls a dispatch routine, performs whatever +service is requested, sends back the reply, and the procedure call returns +to the client. +.NH 0 +\&Layers of RPC +.IX "layers of RPC" +.IX "RPC" "layers" +.LP +The RPC interface can be seen as being divided into three layers.\** +.FS +For a complete specification of the routines in the remote procedure +call Library, see the +.I rpc(3N) +manual page. +.FE +.LP +.I "The Highest Layer:" +.IX RPC "The Highest Layer" +The highest layer is totally transparent to the operating system, +machine and network upon which is is run. It's probably best to +think of this level as a way of +.I using +RPC, rather than as +a \fIpart of\fP RPC proper. Programmers who write RPC routines +should (almost) always make this layer available to others by way +of a simple C front end that entirely hides the networking. +.LP +To illustrate, at this level a program can simply make a call to +.I rnusers (), +a C routine which returns the number of users on a remote machine. +The user is not explicitly aware of using RPC \(em they simply +call a procedure, just as they would call +.I malloc() . +.LP +.I "The Middle Layer:" +.IX RPC "The Middle Layer" +The middle layer is really \*QRPC proper.\*U Here, the user doesn't +need to consider details about sockets, the UNIX system, or other low-level +implementation mechanisms. They simply make remote procedure calls +to routines on other machines. The selling point here is simplicity. +It's this layer that allows RPC to pass the \*Qhello world\*U test \(em +simple things should be simple. The middle-layer routines are used +for most applications. +.LP +RPC calls are made with the system routines +.I registerrpc() +.I callrpc() +and +.I svc_run (). +The first two of these are the most fundamental: +.I registerrpc() +obtains a unique system-wide procedure-identification number, and +.I callrpc() +actually executes a remote procedure call. At the middle level, a +call to +.I rnusers() +is implemented by way of these two routines. +.LP +The middle layer is unfortunately rarely used in serious programming +due to its inflexibility (simplicity). It does not allow timeout +specifications or the choice of transport. It allows no UNIX +process control or flexibility in case of errors. It doesn't support +multiple kinds of call authentication. The programmer rarely needs +all these kinds of control, but one or two of them is often necessary. +.LP +.I "The Lowest Layer:" +.IX RPC "The Lowest Layer" +The lowest layer does allow these details to be controlled by the +programmer, and for that reason it is often necessary. Programs +written at this level are also most efficient, but this is rarely a +real issue \(em since RPC clients and servers rarely generate +heavy network loads. +.LP +Although this document only discusses the interface to C, +remote procedure calls can be made from any language. +Even though this document discusses RPC +when it is used to communicate +between processes on different machines, +it works just as well for communication +between different processes on the same machine. +.br +.KS +.NH 2 +\&The RPC Paradigm +.IX RPC paradigm +.LP +Here is a diagram of the RPC paradigm: +.LP +\fBFigure 1-1\fI Network Communication with the Remote Reocedure Call\fR +.LP +.PS +L1: arrow down 1i "client " rjust "program " rjust +L2: line right 1.5i "\fIcallrpc\fP" "function" +move up 1.5i; line dotted down 6i; move up 4.5i +arrow right 1i +L3: arrow down 1i "invoke " rjust "service " rjust +L4: arrow right 1.5i "call" "service" +L5: arrow down 1i " service" ljust " executes" ljust +L6: arrow left 1.5i "\fIreturn\fP" "answer" +L7: arrow down 1i "request " rjust "completed " rjust +L8: line left 1i +arrow left 1.5i "\fIreturn\fP" "reply" +L9: arrow down 1i "program " rjust "continues " rjust +line dashed down from L2 to L9 +line dashed down from L4 to L7 +line dashed up 1i from L3 "service " rjust "daemon " rjust +arrow dashed down 1i from L8 +move right 1i from L3 +box invis "Machine B" +move left 1.2i from L2; move down +box invis "Machine A" +.PE +.KE +.KS +.NH 1 +\&Higher Layers of RPC +.NH 2 +\&Highest Layer +.IX "highest layer of RPC" +.IX RPC "highest layer" +.LP +Imagine you're writing a program that needs to know +how many users are logged into a remote machine. +You can do this by calling the RPC library routine +.I rnusers() +as illustrated below: +.ie t .DS +.el .DS L +.ft CW +#include + +main(argc, argv) + int argc; + char **argv; +{ + int num; + + if (argc != 2) { + fprintf(stderr, "usage: rnusers hostname\en"); + exit(1); + } + if ((num = rnusers(argv[1])) < 0) { + fprintf(stderr, "error: rnusers\en"); + exit(-1); + } + printf("%d users on %s\en", num, argv[1]); + exit(0); +} +.DE +.KE +RPC library routines such as +.I rnusers() +are in the RPC services library +.I librpcsvc.a +Thus, the program above should be compiled with +.DS +.ft CW +% cc \fIprogram.c -lrpcsvc\fP +.DE +.I rnusers (), +like the other RPC library routines, is documented in section 3R +of the +.I "System Interface Manual for the Sun Workstation" , +the same section which documents the standard Sun RPC services. +.IX "RPC Services" +See the +.I intro(3R) +manual page for an explanation of the documentation strategy +for these services and their RPC protocols. +.LP +Here are some of the RPC service library routines available to the +C programmer: +.LP +\fBTable 3-3\fI RPC Service Library Routines\RP +.TS +box tab (&) ; +cfI cfI +lfL l . +Routine&Description +_ +.sp.5 +rnusers&Return number of users on remote machine +rusers&Return information about users on remote machine +havedisk&Determine if remote machine has disk +rstats&Get performance data from remote kernel +rwall&Write to specified remote machines +yppasswd&Update user password in Yellow Pages +.TE +.LP +Other RPC services \(em for example +.I ether() +.I mount +.I rquota() +and +.I spray +\(em are not available to the C programmer as library routines. +They do, however, +have RPC program numbers so they can be invoked with +.I callrpc() +which will be discussed in the next section. Most of them also +have compilable +.I rpcgen(1) +protocol description files. (The +.I rpcgen +protocol compiler radically simplifies the process of developing +network applications. +See the \fBrpcgen\fI Programming Guide\fR +for detailed information about +.I rpcgen +and +.I rpcgen +protocol description files). +.KS +.NH 2 +\&Intermediate Layer +.IX "intermediate layer of RPC" +.IX "RPC" "intermediate layer" +.LP +The simplest interface, which explicitly makes RPC calls, uses the +functions +.I callrpc() +and +.I registerrpc() +Using this method, the number of remote users can be gotten as follows: +.ie t .DS +.el .DS L +#include +#include +#include +#include + +main(argc, argv) + int argc; + char **argv; +{ + unsigned long nusers; + int stat; + + if (argc != 2) { + fprintf(stderr, "usage: nusers hostname\en"); + exit(-1); + } + if (stat = callrpc(argv[1], + RUSERSPROG, RUSERSVERS, RUSERSPROC_NUM, + xdr_void, 0, xdr_u_long, &nusers) != 0) { + clnt_perrno(stat); + exit(1); + } + printf("%d users on %s\en", nusers, argv[1]); + exit(0); +} +.DE +.KE +Each RPC procedure is uniquely defined by a program number, +version number, and procedure number. The program number +specifies a group of related remote procedures, each of +which has a different procedure number. Each program also +has a version number, so when a minor change is made to a +remote service (adding a new procedure, for example), a new +program number doesn't have to be assigned. When you want +to call a procedure to find the number of remote users, you +look up the appropriate program, version and procedure numbers +in a manual, just as you look up the name of a memory allocator +when you want to allocate memory. +.LP +The simplest way of making remote procedure calls is with the the RPC +library routine +.I callrpc() +It has eight parameters. The first is the name of the remote server +machine. The next three parameters are the program, version, and procedure +numbers\(emtogether they identify the procedure to be called. +The fifth and sixth parameters are an XDR filter and an argument to +be encoded and passed to the remote procedure. +The final two parameters are a filter for decoding the results +returned by the remote procedure and a pointer to the place where +the procedure's results are to be stored. Multiple arguments and +results are handled by embedding them in structures. If +.I callrpc() +completes successfully, it returns zero; else it returns a nonzero +value. The return codes (of type +.IX "enum clnt_stat (in RPC programming)" "" "\fIenum clnt_stat\fP (in RPC programming)" +cast into an integer) are found in +.I . +.LP +Since data types may be represented differently on different machines, +.I callrpc() +needs both the type of the RPC argument, as well as +a pointer to the argument itself (and similarly for the result). For +.I RUSERSPROC_NUM , +the return value is an +.I "unsigned long" +so +.I callrpc() +has +.I xdr_u_long() +as its first return parameter, which says +that the result is of type +.I "unsigned long" +and +.I &nusers +as its second return parameter, +which is a pointer to where the long result will be placed. Since +.I RUSERSPROC_NUM +takes no argument, the argument parameter of +.I callrpc() +is +.I xdr_void (). +.LP +After trying several times to deliver a message, if +.I callrpc() +gets no answer, it returns with an error code. +The delivery mechanism is UDP, +which stands for User Datagram Protocol. +Methods for adjusting the number of retries +or for using a different protocol require you to use the lower +layer of the RPC library, discussed later in this document. +The remote server procedure +corresponding to the above might look like this: +.ie t .DS +.el .DS L +.ft CW +.ft CW +char * +nuser(indata) + char *indata; +{ + unsigned long nusers; + +.ft I + /* + * Code here to compute the number of users + * and place result in variable \fInusers\fP. + */ +.ft CW + return((char *)&nusers); +} +.DE +.LP +It takes one argument, which is a pointer to the input +of the remote procedure call (ignored in our example), +and it returns a pointer to the result. +In the current version of C, +character pointers are the generic pointers, +so both the input argument and the return value are cast to +.I "char *" . +.LP +Normally, a server registers all of the RPC calls it plans +to handle, and then goes into an infinite loop waiting to service requests. +In this example, there is only a single procedure +to register, so the main body of the server would look like this: +.ie t .DS +.el .DS L +.ft CW +#include +#include +#include +#include + +char *nuser(); + +main() +{ + registerrpc(RUSERSPROG, RUSERSVERS, RUSERSPROC_NUM, + nuser, xdr_void, xdr_u_long); + svc_run(); /* \fINever returns\fP */ + fprintf(stderr, "Error: svc_run returned!\en"); + exit(1); +} +.DE +.LP +The +.I registerrpc() +routine registers a C procedure as corresponding to a +given RPC procedure number. The first three parameters, +.I RUSERPROG , +.I RUSERSVERS , +and +.I RUSERSPROC_NUM +are the program, version, and procedure numbers +of the remote procedure to be registered; +.I nuser() +is the name of the local procedure that implements the remote +procedure; and +.I xdr_void() +and +.I xdr_u_long() +are the XDR filters for the remote procedure's arguments and +results, respectively. (Multiple arguments or multiple results +are passed as structures). +.LP +Only the UDP transport mechanism can use +.I registerrpc() +thus, it is always safe in conjunction with calls generated by +.I callrpc() . +.SH +.IX "UDP 8K warning" +Warning: the UDP transport mechanism can only deal with +arguments and results less than 8K bytes in length. +.LP +.LP +After registering the local procedure, the server program's +main procedure calls +.I svc_run (), +the RPC library's remote procedure dispatcher. It is this +function that calls the remote procedures in response to RPC +call messages. Note that the dispatcher takes care of decoding +remote procedure arguments and encoding results, using the XDR +filters specified when the remote procedure was registered. +.NH 2 +\&Assigning Program Numbers +.IX "program number assignment" +.IX "assigning program numbers" +.LP +Program numbers are assigned in groups of +.I 0x20000000 +according to the following chart: +.DS +.ft CW + 0x0 - 0x1fffffff \fRDefined by Sun\fP +0x20000000 - 0x3fffffff \fRDefined by user\fP +0x40000000 - 0x5fffffff \fRTransient\fP +0x60000000 - 0x7fffffff \fRReserved\fP +0x80000000 - 0x9fffffff \fRReserved\fP +0xa0000000 - 0xbfffffff \fRReserved\fP +0xc0000000 - 0xdfffffff \fRReserved\fP +0xe0000000 - 0xffffffff \fRReserved\fP +.ft R +.DE +Sun Microsystems administers the first group of numbers, which +should be identical for all Sun customers. If a customer +develops an application that might be of general interest, that +application should be given an assigned number in the first +range. The second group of numbers is reserved for specific +customer applications. This range is intended primarily for +debugging new programs. The third group is reserved for +applications that generate program numbers dynamically. The +final groups are reserved for future use, and should not be +used. +.LP +To register a protocol specification, send a request by network +mail to +.I rpc@sun +or write to: +.DS +RPC Administrator +Sun Microsystems +2550 Garcia Ave. +Mountain View, CA 94043 +.DE +Please include a compilable +.I rpcgen +\*Q.x\*U file describing your protocol. +You will be given a unique program number in return. +.IX RPC administration +.IX administration "of RPC" +.LP +The RPC program numbers and protocol specifications +of standard Sun RPC services can be +found in the include files in +.I "/usr/include/rpcsvc" . +These services, however, constitute only a small subset +of those which have been registered. The complete list of +registered programs, as of the time when this manual was +printed, is: +.LP +\fBTable 3-2\fI RPC Registered Programs\fR +.TS H +box tab (&) ; +lfBI lfBI lfBI +lfL lfL lfI . +RPC Number&Program&Description +_ +.TH +.sp.5 +100000&PMAPPROG&portmapper +100001&RSTATPROG&remote stats +100002&RUSERSPROG&remote users +100003&NFSPROG&nfs +100004&YPPROG&Yellow Pages +100005&MOUNTPROG&mount demon +100006&DBXPROG&remote dbx +100007&YPBINDPROG&yp binder +100008&WALLPROG&shutdown msg +100009&YPPASSWDPROG&yppasswd server +100010ÐERSTATPROGðer stats +100011&RQUOTAPROG&disk quotas +100012&SPRAYPROG&spray packets +100013&IBM3270PROG&3270 mapper +100014&IBMRJEPROG&RJE mapper +100015&SELNSVCPROG&selection service +100016&RDATABASEPROG&remote database access +100017&REXECPROG&remote execution +100018&ALICEPROG&Alice Office Automation +100019&SCHEDPROG&scheduling service +100020&LOCKPROG&local lock manager +100021&NETLOCKPROG&network lock manager +100022&X25PROG&x.25 inr protocol +100023&STATMON1PROG&status monitor 1 +100024&STATMON2PROG&status monitor 2 +100025&SELNLIBPROG&selection library +100026&BOOTPARAMPROG&boot parameters service +100027&MAZEPROG&mazewars game +100028&YPUPDATEPROG&yp update +100029&KEYSERVEPROG&key server +100030&SECURECMDPROG&secure login +100031&NETFWDIPROG&nfs net forwarder init +100032&NETFWDTPROG&nfs net forwarder trans +100033&SUNLINKMAP_PROG&sunlink MAP +100034&NETMONPROG&network monitor +100035&DBASEPROG&lightweight database +100036&PWDAUTHPROG&password authorization +100037&TFSPROG&translucent file svc +100038&NSEPROG&nse server +100039&NSE_ACTIVATE_PROG&nse activate daemon +.sp .2i +150001&PCNFSDPROG&pc passwd authorization +.sp .2i +200000&PYRAMIDLOCKINGPROG&Pyramid-locking +200001&PYRAMIDSYS5&Pyramid-sys5 +200002&CADDS_IMAGE&CV cadds_image +.sp .2i +300001&ADT_RFLOCKPROG&ADT file locking +.TE +.NH 2 +\&Passing Arbitrary Data Types +.IX "arbitrary data types" +.LP +In the previous example, the RPC call passes a single +.I "unsigned long" +RPC can handle arbitrary data structures, regardless of +different machines' byte orders or structure layout conventions, +by always converting them to a network standard called +.I "External Data Representation" +(XDR) before +sending them over the wire. +The process of converting from a particular machine representation +to XDR format is called +.I serializing , +and the reverse process is called +.I deserializing . +The type field parameters of +.I callrpc() +and +.I registerrpc() +can be a built-in procedure like +.I xdr_u_long() +in the previous example, or a user supplied one. +XDR has these built-in type routines: +.IX RPC "built-in routines" +.DS +.ft CW +xdr_int() xdr_u_int() xdr_enum() +xdr_long() xdr_u_long() xdr_bool() +xdr_short() xdr_u_short() xdr_wrapstring() +xdr_char() xdr_u_char() +.DE +Note that the routine +.I xdr_string() +exists, but cannot be used with +.I callrpc() +and +.I registerrpc (), +which only pass two parameters to their XDR routines. +.I xdr_wrapstring() +has only two parameters, and is thus OK. It calls +.I xdr_string (). +.LP +As an example of a user-defined type routine, +if you wanted to send the structure +.DS +.ft CW +struct simple { + int a; + short b; +} simple; +.DE +then you would call +.I callrpc() +as +.DS +.ft CW +callrpc(hostname, PROGNUM, VERSNUM, PROCNUM, + xdr_simple, &simple ...); +.DE +where +.I xdr_simple() +is written as: +.ie t .DS +.el .DS L +.ft CW +#include + +xdr_simple(xdrsp, simplep) + XDR *xdrsp; + struct simple *simplep; +{ + if (!xdr_int(xdrsp, &simplep->a)) + return (0); + if (!xdr_short(xdrsp, &simplep->b)) + return (0); + return (1); +} +.DE +.LP +An XDR routine returns nonzero (true in the sense of C) if it +completes successfully, and zero otherwise. +A complete description of XDR is in the +.I "XDR Protocol Specification" +section of this manual, only few implementation examples are +given here. +.LP +In addition to the built-in primitives, +there are also the prefabricated building blocks: +.DS +.ft CW +xdr_array() xdr_bytes() xdr_reference() +xdr_vector() xdr_union() xdr_pointer() +xdr_string() xdr_opaque() +.DE +To send a variable array of integers, +you might package them up as a structure like this +.DS +.ft CW +struct varintarr { + int *data; + int arrlnth; +} arr; +.DE +and make an RPC call such as +.DS +.ft CW +callrpc(hostname, PROGNUM, VERSNUM, PROCNUM, + xdr_varintarr, &arr...); +.DE +with +.I xdr_varintarr() +defined as: +.ie t .DS +.el .DS L +.ft CW +xdr_varintarr(xdrsp, arrp) + XDR *xdrsp; + struct varintarr *arrp; +{ + return (xdr_array(xdrsp, &arrp->data, &arrp->arrlnth, + MAXLEN, sizeof(int), xdr_int)); +} +.DE +This routine takes as parameters the XDR handle, +a pointer to the array, a pointer to the size of the array, +the maximum allowable array size, +the size of each array element, +and an XDR routine for handling each array element. +.KS +.LP +If the size of the array is known in advance, one can use +.I xdr_vector (), +which serializes fixed-length arrays. +.ie t .DS +.el .DS L +.ft CW +int intarr[SIZE]; + +xdr_intarr(xdrsp, intarr) + XDR *xdrsp; + int intarr[]; +{ + int i; + + return (xdr_vector(xdrsp, intarr, SIZE, sizeof(int), + xdr_int)); +} +.DE +.KE +.LP +XDR always converts quantities to 4-byte multiples when serializing. +Thus, if either of the examples above involved characters +instead of integers, each character would occupy 32 bits. +That is the reason for the XDR routine +.I xdr_bytes() +which is like +.I xdr_array() +except that it packs characters; +.I xdr_bytes() +has four parameters, similar to the first four parameters of +.I xdr_array (). +For null-terminated strings, there is also the +.I xdr_string() +routine, which is the same as +.I xdr_bytes() +without the length parameter. +On serializing it gets the string length from +.I strlen (), +and on deserializing it creates a null-terminated string. +.LP +Here is a final example that calls the previously written +.I xdr_simple() +as well as the built-in functions +.I xdr_string() +and +.I xdr_reference (), +which chases pointers: +.ie t .DS +.el .DS L +.ft CW +struct finalexample { + char *string; + struct simple *simplep; +} finalexample; + +xdr_finalexample(xdrsp, finalp) + XDR *xdrsp; + struct finalexample *finalp; +{ + + if (!xdr_string(xdrsp, &finalp->string, MAXSTRLEN)) + return (0); + if (!xdr_reference(xdrsp, &finalp->simplep, + sizeof(struct simple), xdr_simple); + return (0); + return (1); +} +.DE +Note that we could as easily call +.I xdr_simple() +here instead of +.I xdr_reference (). +.NH 1 +\&Lowest Layer of RPC +.IX "lowest layer of RPC" +.IX "RPC" "lowest layer" +.LP +In the examples given so far, +RPC takes care of many details automatically for you. +In this section, we'll show you how you can change the defaults +by using lower layers of the RPC library. +It is assumed that you are familiar with sockets +and the system calls for dealing with them. +.LP +There are several occasions when you may need to use lower layers of +RPC. First, you may need to use TCP, since the higher layer uses UDP, +which restricts RPC calls to 8K bytes of data. Using TCP permits calls +to send long streams of data. +For an example, see the +.I TCP +section below. Second, you may want to allocate and free memory +while serializing or deserializing with XDR routines. +There is no call at the higher level to let +you free memory explicitly. +For more explanation, see the +.I "Memory Allocation with XDR" +section below. +Third, you may need to perform authentication +on either the client or server side, by supplying +credentials or verifying them. +See the explanation in the +.I Authentication +section below. +.NH 2 +\&More on the Server Side +.IX RPC "server side" +.LP +The server for the +.I nusers() +program shown below does the same thing as the one using +.I registerrpc() +above, but is written using a lower layer of the RPC package: +.ie t .DS +.el .DS L +.ft CW +#include +#include +#include +#include + +main() +{ + SVCXPRT *transp; + int nuser(); + + transp = svcudp_create(RPC_ANYSOCK); + if (transp == NULL){ + fprintf(stderr, "can't create an RPC server\en"); + exit(1); + } + pmap_unset(RUSERSPROG, RUSERSVERS); + if (!svc_register(transp, RUSERSPROG, RUSERSVERS, + nuser, IPPROTO_UDP)) { + fprintf(stderr, "can't register RUSER service\en"); + exit(1); + } + svc_run(); /* \fINever returns\fP */ + fprintf(stderr, "should never reach this point\en"); +} + +nuser(rqstp, transp) + struct svc_req *rqstp; + SVCXPRT *transp; +{ + unsigned long nusers; + + switch (rqstp->rq_proc) { + case NULLPROC: + if (!svc_sendreply(transp, xdr_void, 0)) + fprintf(stderr, "can't reply to RPC call\en"); + return; + case RUSERSPROC_NUM: +.ft I + /* + * Code here to compute the number of users + * and assign it to the variable \fInusers\fP + */ +.ft CW + if (!svc_sendreply(transp, xdr_u_long, &nusers)) + fprintf(stderr, "can't reply to RPC call\en"); + return; + default: + svcerr_noproc(transp); + return; + } +} +.DE +.LP +First, the server gets a transport handle, which is used +for receiving and replying to RPC messages. +.I registerrpc() +uses +.I svcudp_create() +to get a UDP handle. +If you require a more reliable protocol, call +.I svctcp_create() +instead. +If the argument to +.I svcudp_create() +is +.I RPC_ANYSOCK +the RPC library creates a socket +on which to receive and reply to RPC calls. Otherwise, +.I svcudp_create() +expects its argument to be a valid socket number. +If you specify your own socket, it can be bound or unbound. +If it is bound to a port by the user, the port numbers of +.I svcudp_create() +and +.I clnttcp_create() +(the low-level client routine) must match. +.LP +If the user specifies the +.I RPC_ANYSOCK +argument, the RPC library routines will open sockets. +Otherwise they will expect the user to do so. The routines +.I svcudp_create() +and +.I clntudp_create() +will cause the RPC library routines to +.I bind() +their socket if it is not bound already. +.LP +A service may choose to register its port number with the +local portmapper service. This is done is done by specifying +a non-zero protocol number in +.I svc_register (). +Incidently, a client can discover the server's port number by +consulting the portmapper on their server's machine. This can +be done automatically by specifying a zero port number in +.I clntudp_create() +or +.I clnttcp_create (). +.LP +After creating an +.I SVCXPRT , +the next step is to call +.I pmap_unset() +so that if the +.I nusers() +server crashed earlier, +any previous trace of it is erased before restarting. +More precisely, +.I pmap_unset() +erases the entry for +.I RUSERSPROG +from the port mapper's tables. +.LP +Finally, we associate the program number for +.I nusers() +with the procedure +.I nuser (). +The final argument to +.I svc_register() +is normally the protocol being used, +which, in this case, is +.I IPPROTO_UDP +Notice that unlike +.I registerrpc (), +there are no XDR routines involved +in the registration process. +Also, registration is done on the program, +rather than procedure, level. +.LP +The user routine +.I nuser() +must call and dispatch the appropriate XDR routines +based on the procedure number. +Note that +two things are handled by +.I nuser() +that +.I registerrpc() +handles automatically. +The first is that procedure +.I NULLPROC +(currently zero) returns with no results. +This can be used as a simple test +for detecting if a remote program is running. +Second, there is a check for invalid procedure numbers. +If one is detected, +.I svcerr_noproc() +is called to handle the error. +.KS +.LP +The user service routine serializes the results and returns +them to the RPC caller via +.I svc_sendreply() +Its first parameter is the +.I SVCXPRT +handle, the second is the XDR routine, +and the third is a pointer to the data to be returned. +Not illustrated above is how a server +handles an RPC program that receives data. +As an example, we can add a procedure +.I RUSERSPROC_BOOL +which has an argument +.I nusers (), +and returns +.I TRUE +or +.I FALSE +depending on whether there are nusers logged on. +It would look like this: +.ie t .DS +.el .DS L +.ft CW +case RUSERSPROC_BOOL: { + int bool; + unsigned nuserquery; + + if (!svc_getargs(transp, xdr_u_int, &nuserquery) { + svcerr_decode(transp); + return; + } +.ft I + /* + * Code to set \fInusers\fP = number of users + */ +.ft CW + if (nuserquery == nusers) + bool = TRUE; + else + bool = FALSE; + if (!svc_sendreply(transp, xdr_bool, &bool)) { + fprintf(stderr, "can't reply to RPC call\en"); + return (1); + } + return; +} +.DE +.KE +.LP +The relevant routine is +.I svc_getargs() +which takes an +.I SVCXPRT +handle, the XDR routine, +and a pointer to where the input is to be placed as arguments. +.NH 2 +\&Memory Allocation with XDR +.IX "memory allocation with XDR" +.IX XDR "memory allocation" +.LP +XDR routines not only do input and output, +they also do memory allocation. +This is why the second parameter of +.I xdr_array() +is a pointer to an array, rather than the array itself. +If it is +.I NULL , +then +.I xdr_array() +allocates space for the array and returns a pointer to it, +putting the size of the array in the third argument. +As an example, consider the following XDR routine +.I xdr_chararr1() +which deals with a fixed array of bytes with length +.I SIZE . +.ie t .DS +.el .DS L +.ft CW +xdr_chararr1(xdrsp, chararr) + XDR *xdrsp; + char chararr[]; +{ + char *p; + int len; + + p = chararr; + len = SIZE; + return (xdr_bytes(xdrsp, &p, &len, SIZE)); +} +.DE +If space has already been allocated in +.I chararr , +it can be called from a server like this: +.ie t .DS +.el .DS L +.ft CW +char chararr[SIZE]; + +svc_getargs(transp, xdr_chararr1, chararr); +.DE +If you want XDR to do the allocation, +you would have to rewrite this routine in the following way: +.ie t .DS +.el .DS L +.ft CW +xdr_chararr2(xdrsp, chararrp) + XDR *xdrsp; + char **chararrp; +{ + int len; + + len = SIZE; + return (xdr_bytes(xdrsp, charrarrp, &len, SIZE)); +} +.DE +Then the RPC call might look like this: +.ie t .DS +.el .DS L +.ft CW +char *arrptr; + +arrptr = NULL; +svc_getargs(transp, xdr_chararr2, &arrptr); +.ft I +/* + * Use the result here + */ +.ft CW +svc_freeargs(transp, xdr_chararr2, &arrptr); +.DE +Note that, after being used, the character array can be freed with +.I svc_freeargs() +.I svc_freeargs() +will not attempt to free any memory if the variable indicating it +is NULL. For example, in the the routine +.I xdr_finalexample (), +given earlier, if +.I finalp->string +was NULL, then it would not be freed. The same is true for +.I finalp->simplep . +.LP +To summarize, each XDR routine is responsible +for serializing, deserializing, and freeing memory. +When an XDR routine is called from +.I callrpc() +the serializing part is used. +When called from +.I svc_getargs() +the deserializer is used. +And when called from +.I svc_freeargs() +the memory deallocator is used. When building simple examples like those +in this section, a user doesn't have to worry +about the three modes. +See the +.I "External Data Representation: Sun Technical Notes" +for examples of more sophisticated XDR routines that determine +which of the three modes they are in and adjust their behavior accordingly. +.KS +.NH 2 +\&The Calling Side +.IX RPC "calling side" +.LP +When you use +.I callrpc() +you have no control over the RPC delivery +mechanism or the socket used to transport the data. +To illustrate the layer of RPC that lets you adjust these +parameters, consider the following code to call the +.I nusers +service: +.ie t .DS +.el .DS L +.ft CW +.vs 11 +#include +#include +#include +#include +#include +#include +#include + +main(argc, argv) + int argc; + char **argv; +{ + struct hostent *hp; + struct timeval pertry_timeout, total_timeout; + struct sockaddr_in server_addr; + int sock = RPC_ANYSOCK; + register CLIENT *client; + enum clnt_stat clnt_stat; + unsigned long nusers; + + if (argc != 2) { + fprintf(stderr, "usage: nusers hostname\en"); + exit(-1); + } + if ((hp = gethostbyname(argv[1])) == NULL) { + fprintf(stderr, "can't get addr for %s\en",argv[1]); + exit(-1); + } + pertry_timeout.tv_sec = 3; + pertry_timeout.tv_usec = 0; + bcopy(hp->h_addr, (caddr_t)&server_addr.sin_addr, + hp->h_length); + server_addr.sin_family = AF_INET; + server_addr.sin_port = 0; + if ((client = clntudp_create(&server_addr, RUSERSPROG, + RUSERSVERS, pertry_timeout, &sock)) == NULL) { + clnt_pcreateerror("clntudp_create"); + exit(-1); + } + total_timeout.tv_sec = 20; + total_timeout.tv_usec = 0; + clnt_stat = clnt_call(client, RUSERSPROC_NUM, xdr_void, + 0, xdr_u_long, &nusers, total_timeout); + if (clnt_stat != RPC_SUCCESS) { + clnt_perror(client, "rpc"); + exit(-1); + } + clnt_destroy(client); + close(sock); + exit(0); +} +.vs +.DE +.KE +The low-level version of +.I callrpc() +is +.I clnt_call() +which takes a +.I CLIENT +pointer rather than a host name. The parameters to +.I clnt_call() +are a +.I CLIENT +pointer, the procedure number, +the XDR routine for serializing the argument, +a pointer to the argument, +the XDR routine for deserializing the return value, +a pointer to where the return value will be placed, +and the time in seconds to wait for a reply. +.LP +The +.I CLIENT +pointer is encoded with the transport mechanism. +.I callrpc() +uses UDP, thus it calls +.I clntudp_create() +to get a +.I CLIENT +pointer. To get TCP (Transmission Control Protocol), you would use +.I clnttcp_create() . +.LP +The parameters to +.I clntudp_create() +are the server address, the program number, the version number, +a timeout value (between tries), and a pointer to a socket. +The final argument to +.I clnt_call() +is the total time to wait for a response. +Thus, the number of tries is the +.I clnt_call() +timeout divided by the +.I clntudp_create() +timeout. +.LP +Note that the +.I clnt_destroy() +call +always deallocates the space associated with the +.I CLIENT +handle. It closes the socket associated with the +.I CLIENT +handle, however, only if the RPC library opened it. It the +socket was opened by the user, it stays open. This makes it +possible, in cases where there are multiple client handles +using the same socket, to destroy one handle without closing +the socket that other handles are using. +.LP +To make a stream connection, the call to +.I clntudp_create() +is replaced with a call to +.I clnttcp_create() . +.DS +.ft CW +clnttcp_create(&server_addr, prognum, versnum, &sock, + inputsize, outputsize); +.DE +There is no timeout argument; instead, the receive and send buffer +sizes must be specified. When the +.I clnttcp_create() +call is made, a TCP connection is established. +All RPC calls using that +.I CLIENT +handle would use this connection. +The server side of an RPC call using TCP has +.I svcudp_create() +replaced by +.I svctcp_create() . +.DS +.ft CW +transp = svctcp_create(RPC_ANYSOCK, 0, 0); +.DE +The last two arguments to +.I svctcp_create() +are send and receive sizes respectively. If `0' is specified for +either of these, the system chooses a reasonable default. +.KS +.NH 1 +\&Other RPC Features +.IX "RPC" "miscellaneous features" +.IX "miscellaneous RPC features" +.LP +This section discusses some other aspects of RPC +that are occasionally useful. +.NH 2 +\&Select on the Server Side +.IX RPC select() RPC \fIselect()\fP +.IX select() "" \fIselect()\fP "on the server side" +.LP +Suppose a process is processing RPC requests +while performing some other activity. +If the other activity involves periodically updating a data structure, +the process can set an alarm signal before calling +.I svc_run() +But if the other activity +involves waiting on a a file descriptor, the +.I svc_run() +call won't work. +The code for +.I svc_run() +is as follows: +.ie t .DS +.el .DS L +.ft CW +.vs 11 +void +svc_run() +{ + fd_set readfds; + int dtbsz = getdtablesize(); + + for (;;) { + readfds = svc_fds; + switch (select(dtbsz, &readfds, NULL,NULL,NULL)) { + + case -1: + if (errno == EINTR) + continue; + perror("select"); + return; + case 0: + break; + default: + svc_getreqset(&readfds); + } + } +} +.vs +.DE +.KE +.LP +You can bypass +.I svc_run() +and call +.I svc_getreqset() +yourself. +All you need to know are the file descriptors +of the socket(s) associated with the programs you are waiting on. +Thus you can have your own +.I select() +.IX select() "" \fIselect()\fP +that waits on both the RPC socket, +and your own descriptors. Note that +.I svc_fds() +is a bit mask of all the file descriptors that RPC is using for +services. It can change everytime that +.I any +RPC library routine is called, because descriptors are constantly +being opened and closed, for example for TCP connections. +.NH 2 +\&Broadcast RPC +.IX "broadcast RPC" +.IX RPC "broadcast" +.LP +The +.I portmapper +is a daemon that converts RPC program numbers +into DARPA protocol port numbers; see the +.I portmap +man page. You can't do broadcast RPC without the portmapper. +Here are the main differences between +broadcast RPC and normal RPC calls: +.IP 1. +Normal RPC expects one answer, whereas +broadcast RPC expects many answers +(one or more answer from each responding machine). +.IP 2. +Broadcast RPC can only be supported by packet-oriented (connectionless) +transport protocols like UPD/IP. +.IP 3. +The implementation of broadcast RPC +treats all unsuccessful responses as garbage by filtering them out. +Thus, if there is a version mismatch between the +broadcaster and a remote service, +the user of broadcast RPC never knows. +.IP 4. +All broadcast messages are sent to the portmap port. +Thus, only services that register themselves with their portmapper +are accessible via the broadcast RPC mechanism. +.IP 5. +Broadcast requests are limited in size to the MTU (Maximum Transfer +Unit) of the local network. For Ethernet, the MTU is 1500 bytes. +.KS +.NH 3 +\&Broadcast RPC Synopsis +.IX "broadcast RPC" synopsis +.IX "RPC" "broadcast synopsis" +.ie t .DS +.el .DS L +.ft CW +#include + . . . +enum clnt_stat clnt_stat; + . . . +clnt_stat = clnt_broadcast(prognum, versnum, procnum, + inproc, in, outproc, out, eachresult) + u_long prognum; /* \fIprogram number\fP */ + u_long versnum; /* \fIversion number\fP */ + u_long procnum; /* \fIprocedure number\fP */ + xdrproc_t inproc; /* \fIxdr routine for args\fP */ + caddr_t in; /* \fIpointer to args\fP */ + xdrproc_t outproc; /* \fIxdr routine for results\fP */ + caddr_t out; /* \fIpointer to results\fP */ + bool_t (*eachresult)();/* \fIcall with each result gotten\fP */ +.DE +.KE +The procedure +.I eachresult() +is called each time a valid result is obtained. +It returns a boolean that indicates +whether or not the user wants more responses. +.ie t .DS +.el .DS L +.ft CW +bool_t done; + . . . +done = eachresult(resultsp, raddr) + caddr_t resultsp; + struct sockaddr_in *raddr; /* \fIAddr of responding machine\fP */ +.DE +If +.I done +is +.I TRUE , +then broadcasting stops and +.I clnt_broadcast() +returns successfully. +Otherwise, the routine waits for another response. +The request is rebroadcast +after a few seconds of waiting. +If no responses come back, +the routine returns with +.I RPC_TIMEDOUT . +.NH 2 +\&Batching +.IX "batching" +.IX RPC "batching" +.LP +The RPC architecture is designed so that clients send a call message, +and wait for servers to reply that the call succeeded. +This implies that clients do not compute +while servers are processing a call. +This is inefficient if the client does not want or need +an acknowledgement for every message sent. +It is possible for clients to continue computing +while waiting for a response, +using RPC batch facilities. +.LP +RPC messages can be placed in a \*Qpipeline\*U of calls +to a desired server; this is called batching. +Batching assumes that: +1) each RPC call in the pipeline requires no response from the server, +and the server does not send a response message; and +2) the pipeline of calls is transported on a reliable +byte stream transport such as TCP/IP. +Since the server does not respond to every call, +the client can generate new calls in parallel +with the server executing previous calls. +Furthermore, the TCP/IP implementation can buffer up +many call messages, and send them to the server in one +.I write() +system call. This overlapped execution +greatly decreases the interprocess communication overhead of +the client and server processes, +and the total elapsed time of a series of calls. +.LP +Since the batched calls are buffered, +the client should eventually do a nonbatched call +in order to flush the pipeline. +.LP +A contrived example of batching follows. +Assume a string rendering service (like a window system) +has two similar calls: one renders a string and returns void results, +while the other renders a string and remains silent. +The service (using the TCP/IP transport) may look like: +.ie t .DS +.el .DS L +.ft CW +#include +#include +#include + +void windowdispatch(); + +main() +{ + SVCXPRT *transp; + + transp = svctcp_create(RPC_ANYSOCK, 0, 0); + if (transp == NULL){ + fprintf(stderr, "can't create an RPC server\en"); + exit(1); + } + pmap_unset(WINDOWPROG, WINDOWVERS); + if (!svc_register(transp, WINDOWPROG, WINDOWVERS, + windowdispatch, IPPROTO_TCP)) { + fprintf(stderr, "can't register WINDOW service\en"); + exit(1); + } + svc_run(); /* \fINever returns\fP */ + fprintf(stderr, "should never reach this point\en"); +} + +void +windowdispatch(rqstp, transp) + struct svc_req *rqstp; + SVCXPRT *transp; +{ + char *s = NULL; + + switch (rqstp->rq_proc) { + case NULLPROC: + if (!svc_sendreply(transp, xdr_void, 0)) + fprintf(stderr, "can't reply to RPC call\en"); + return; + case RENDERSTRING: + if (!svc_getargs(transp, xdr_wrapstring, &s)) { + fprintf(stderr, "can't decode arguments\en"); +.ft I + /* + * Tell caller he screwed up + */ +.ft CW + svcerr_decode(transp); + break; + } +.ft I + /* + * Code here to render the string \fIs\fP + */ +.ft CW + if (!svc_sendreply(transp, xdr_void, NULL)) + fprintf(stderr, "can't reply to RPC call\en"); + break; + case RENDERSTRING_BATCHED: + if (!svc_getargs(transp, xdr_wrapstring, &s)) { + fprintf(stderr, "can't decode arguments\en"); +.ft I + /* + * We are silent in the face of protocol errors + */ +.ft CW + break; + } +.ft I + /* + * Code here to render string s, but send no reply! + */ +.ft CW + break; + default: + svcerr_noproc(transp); + return; + } +.ft I + /* + * Now free string allocated while decoding arguments + */ +.ft CW + svc_freeargs(transp, xdr_wrapstring, &s); +} +.DE +Of course the service could have one procedure +that takes the string and a boolean +to indicate whether or not the procedure should respond. +.LP +In order for a client to take advantage of batching, +the client must perform RPC calls on a TCP-based transport +and the actual calls must have the following attributes: +1) the result's XDR routine must be zero +.I NULL ), +and 2) the RPC call's timeout must be zero. +.KS +.LP +Here is an example of a client that uses batching to render a +bunch of strings; the batching is flushed when the client gets +a null string (EOF): +.ie t .DS +.el .DS L +.ft CW +.vs 11 +#include +#include +#include +#include +#include +#include + +main(argc, argv) + int argc; + char **argv; +{ + struct hostent *hp; + struct timeval pertry_timeout, total_timeout; + struct sockaddr_in server_addr; + int sock = RPC_ANYSOCK; + register CLIENT *client; + enum clnt_stat clnt_stat; + char buf[1000], *s = buf; + + if ((client = clnttcp_create(&server_addr, + WINDOWPROG, WINDOWVERS, &sock, 0, 0)) == NULL) { + perror("clnttcp_create"); + exit(-1); + } + total_timeout.tv_sec = 0; + total_timeout.tv_usec = 0; + while (scanf("%s", s) != EOF) { + clnt_stat = clnt_call(client, RENDERSTRING_BATCHED, + xdr_wrapstring, &s, NULL, NULL, total_timeout); + if (clnt_stat != RPC_SUCCESS) { + clnt_perror(client, "batched rpc"); + exit(-1); + } + } + + /* \fINow flush the pipeline\fP */ + + total_timeout.tv_sec = 20; + clnt_stat = clnt_call(client, NULLPROC, xdr_void, NULL, + xdr_void, NULL, total_timeout); + if (clnt_stat != RPC_SUCCESS) { + clnt_perror(client, "rpc"); + exit(-1); + } + clnt_destroy(client); + exit(0); +} +.vs +.DE +.KE +Since the server sends no message, +the clients cannot be notified of any of the failures that may occur. +Therefore, clients are on their own when it comes to handling errors. +.LP +The above example was completed to render +all of the (2000) lines in the file +.I /etc/termcap . +The rendering service did nothing but throw the lines away. +The example was run in the following four configurations: +1) machine to itself, regular RPC; +2) machine to itself, batched RPC; +3) machine to another, regular RPC; and +4) machine to another, batched RPC. +The results are as follows: +1) 50 seconds; +2) 16 seconds; +3) 52 seconds; +4) 10 seconds. +Running +.I fscanf() +on +.I /etc/termcap +only requires six seconds. +These timings show the advantage of protocols +that allow for overlapped execution, +though these protocols are often hard to design. +.NH 2 +\&Authentication +.IX "authentication" +.IX "RPC" "authentication" +.LP +In the examples presented so far, +the caller never identified itself to the server, +and the server never required an ID from the caller. +Clearly, some network services, such as a network filesystem, +require stronger security than what has been presented so far. +.LP +In reality, every RPC call is authenticated by +the RPC package on the server, and similarly, +the RPC client package generates and sends authentication parameters. +Just as different transports (TCP/IP or UDP/IP) +can be used when creating RPC clients and servers, +different forms of authentication can be associated with RPC clients; +the default authentication type used as a default is type +.I none . +.LP +The authentication subsystem of the RPC package is open ended. +That is, numerous types of authentication are easy to support. +.NH 3 +\&UNIX Authentication +.IX "UNIX Authentication" +.IP "\fIThe Client Side\fP" +.LP +When a caller creates a new RPC client handle as in: +.DS +.ft CW +clnt = clntudp_create(address, prognum, versnum, + wait, sockp) +.DE +the appropriate transport instance defaults +the associate authentication handle to be +.DS +.ft CW +clnt->cl_auth = authnone_create(); +.DE +The RPC client can choose to use +.I UNIX +style authentication by setting +.I clnt\->cl_auth +after creating the RPC client handle: +.DS +.ft CW +clnt->cl_auth = authunix_create_default(); +.DE +This causes each RPC call associated with +.I clnt +to carry with it the following authentication credentials structure: +.ie t .DS +.el .DS L +.ft I +/* + * UNIX style credentials. + */ +.ft CW +struct authunix_parms { + u_long aup_time; /* \fIcredentials creation time\fP */ + char *aup_machname; /* \fIhost name where client is\fP */ + int aup_uid; /* \fIclient's UNIX effective uid\fP */ + int aup_gid; /* \fIclient's current group id\fP */ + u_int aup_len; /* \fIelement length of aup_gids\fP */ + int *aup_gids; /* \fIarray of groups user is in\fP */ +}; +.DE +These fields are set by +.I authunix_create_default() +by invoking the appropriate system calls. +Since the RPC user created this new style of authentication, +the user is responsible for destroying it with: +.DS +.ft CW +auth_destroy(clnt->cl_auth); +.DE +This should be done in all cases, to conserve memory. +.sp +.IP "\fIThe Server Side\fP" +.LP +Service implementors have a harder time dealing with authentication issues +since the RPC package passes the service dispatch routine a request +that has an arbitrary authentication style associated with it. +Consider the fields of a request handle passed to a service dispatch routine: +.ie t .DS +.el .DS L +.ft I +/* + * An RPC Service request + */ +.ft CW +struct svc_req { + u_long rq_prog; /* \fIservice program number\fP */ + u_long rq_vers; /* \fIservice protocol vers num\fP */ + u_long rq_proc; /* \fIdesired procedure number\fP */ + struct opaque_auth rq_cred; /* \fIraw credentials from wire\fP */ + caddr_t rq_clntcred; /* \fIcredentials (read only)\fP */ +}; +.DE +The +.I rq_cred +is mostly opaque, except for one field of interest: +the style or flavor of authentication credentials: +.ie t .DS +.el .DS L +.ft I +/* + * Authentication info. Mostly opaque to the programmer. + */ +.ft CW +struct opaque_auth { + enum_t oa_flavor; /* \fIstyle of credentials\fP */ + caddr_t oa_base; /* \fIaddress of more auth stuff\fP */ + u_int oa_length; /* \fInot to exceed \fIMAX_AUTH_BYTES */ +}; +.DE +.IX RPC guarantees +The RPC package guarantees the following +to the service dispatch routine: +.IP 1. +That the request's +.I rq_cred +is well formed. Thus the service implementor may inspect the request's +.I rq_cred.oa_flavor +to determine which style of authentication the caller used. +The service implementor may also wish to inspect the other fields of +.I rq_cred +if the style is not one of the styles supported by the RPC package. +.IP 2. +That the request's +.I rq_clntcred +field is either +.I NULL +or points to a well formed structure +that corresponds to a supported style of authentication credentials. +Remember that only +.I unix +style is currently supported, so (currently) +.I rq_clntcred +could be cast to a pointer to an +.I authunix_parms +structure. If +.I rq_clntcred +is +.I NULL , +the service implementor may wish to inspect the other (opaque) fields of +.I rq_cred +in case the service knows about a new type of authentication +that the RPC package does not know about. +.LP +Our remote users service example can be extended so that +it computes results for all users except UID 16: +.ie t .DS +.el .DS L +.ft CW +.vs 11 +nuser(rqstp, transp) + struct svc_req *rqstp; + SVCXPRT *transp; +{ + struct authunix_parms *unix_cred; + int uid; + unsigned long nusers; + +.ft I + /* + * we don't care about authentication for null proc + */ +.ft CW + if (rqstp->rq_proc == NULLPROC) { + if (!svc_sendreply(transp, xdr_void, 0)) { + fprintf(stderr, "can't reply to RPC call\en"); + return (1); + } + return; + } +.ft I + /* + * now get the uid + */ +.ft CW + switch (rqstp->rq_cred.oa_flavor) { + case AUTH_UNIX: + unix_cred = + (struct authunix_parms *)rqstp->rq_clntcred; + uid = unix_cred->aup_uid; + break; + case AUTH_NULL: + default: + svcerr_weakauth(transp); + return; + } + switch (rqstp->rq_proc) { + case RUSERSPROC_NUM: +.ft I + /* + * make sure caller is allowed to call this proc + */ +.ft CW + if (uid == 16) { + svcerr_systemerr(transp); + return; + } +.ft I + /* + * Code here to compute the number of users + * and assign it to the variable \fInusers\fP + */ +.ft CW + if (!svc_sendreply(transp, xdr_u_long, &nusers)) { + fprintf(stderr, "can't reply to RPC call\en"); + return (1); + } + return; + default: + svcerr_noproc(transp); + return; + } +} +.vs +.DE +A few things should be noted here. +First, it is customary not to check +the authentication parameters associated with the +.I NULLPROC +(procedure number zero). +Second, if the authentication parameter's type is not suitable +for your service, you should call +.I svcerr_weakauth() . +And finally, the service protocol itself should return status +for access denied; in the case of our example, the protocol +does not have such a status, so we call the service primitive +.I svcerr_systemerr() +instead. +.LP +The last point underscores the relation between +the RPC authentication package and the services; +RPC deals only with +.I authentication +and not with individual services' +.I "access control" . +The services themselves must implement their own access control policies +and reflect these policies as return statuses in their protocols. +.NH 2 +\&DES Authentication +.IX RPC DES +.IX RPC authentication +.LP +UNIX authentication is quite easy to defeat. Instead of using +.I authunix_create_default (), +one can call +.I authunix_create() +and then modify the RPC authentication handle it returns by filling in +whatever user ID and hostname they wish the server to think they have. +DES authentication is thus recommended for people who want more security +than UNIX authentication offers. +.LP +The details of the DES authentication protocol are complicated and +are not explained here. +See +.I "Remote Procedure Calls: Protocol Specification" +for the details. +.LP +In order for DES authentication to work, the +.I keyserv(8c) +daemon must be running on both the server and client machines. The +users on these machines need public keys assigned by the network +administrator in the +.I publickey(5) +database. And, they need to have decrypted their secret keys +using their login password. This automatically happens when one +logs in using +.I login(1) , +or can be done manually using +.I keylogin(1) . +The +.I "Network Services" +chapter +./" XXX +explains more how to setup secure networking. +.sp +.IP "\fIClient Side\fP" +.LP +If a client wishes to use DES authentication, it must set its +authentication handle appropriately. Here is an example: +.DS +cl->cl_auth = + authdes_create(servername, 60, &server_addr, NULL); +.DE +The first argument is the network name or \*Qnetname\*U of the owner of +the server process. Typically, server processes are root processes +and their netname can be derived using the following call: +.DS +char servername[MAXNETNAMELEN]; + +host2netname(servername, rhostname, NULL); +.DE +Here, +.I rhostname +is the hostname of the machine the server process is running on. +.I host2netname() +fills in +.I servername +to contain this root process's netname. If the +server process was run by a regular user, one could use the call +.I user2netname() +instead. Here is an example for a server process with the same user +ID as the client: +.DS +char servername[MAXNETNAMELEN]; + +user2netname(servername, getuid(), NULL); +.DE +The last argument to both of these calls, +.I user2netname() +and +.I host2netname (), +is the name of the naming domain where the server is located. The +.I NULL +used here means \*Quse the local domain name.\*U +.LP +The second argument to +.I authdes_create() +is a lifetime for the credential. Here it is set to sixty +seconds. What that means is that the credential will expire 60 +seconds from now. If some mischievous user tries to reuse the +credential, the server RPC subsystem will recognize that it has +expired and not grant any requests. If the same mischievous user +tries to reuse the credential within the sixty second lifetime, +he will still be rejected because the server RPC subsystem +remembers which credentials it has already seen in the near past, +and will not grant requests to duplicates. +.LP +The third argument to +.I authdes_create() +is the address of the host to synchronize with. In order for DES +authentication to work, the server and client must agree upon the +time. Here we pass the address of the server itself, so the +client and server will both be using the same time: the server's +time. The argument can be +.I NULL , +which means \*Qdon't bother synchronizing.\*U You should only do this +if you are sure the client and server are already synchronized. +.LP +The final argument to +.I authdes_create() +is the address of a DES encryption key to use for encrypting +timestamps and data. If this argument is +.I NULL , +as it is in this example, a random key will be chosen. The client +may find out the encryption key being used by consulting the +.I ah_key +field of the authentication handle. +.sp +.IP "\fIServer Side\fP" +.LP +The server side is a lot simpler than the client side. Here is the +previous example rewritten to use +.I AUTH_DES +instead of +.I AUTH_UNIX : +.ie t .DS +.el .DS L +.ft CW +.vs 11 +#include +#include + . . . + . . . +nuser(rqstp, transp) + struct svc_req *rqstp; + SVCXPRT *transp; +{ + struct authdes_cred *des_cred; + int uid; + int gid; + int gidlen; + int gidlist[10]; +.ft I + /* + * we don't care about authentication for null proc + */ +.ft CW + + if (rqstp->rq_proc == NULLPROC) { + /* \fIsame as before\fP */ + } + +.ft I + /* + * now get the uid + */ +.ft CW + switch (rqstp->rq_cred.oa_flavor) { + case AUTH_DES: + des_cred = + (struct authdes_cred *) rqstp->rq_clntcred; + if (! netname2user(des_cred->adc_fullname.name, + &uid, &gid, &gidlen, gidlist)) + { + fprintf(stderr, "unknown user: %s\n", + des_cred->adc_fullname.name); + svcerr_systemerr(transp); + return; + } + break; + case AUTH_NULL: + default: + svcerr_weakauth(transp); + return; + } + +.ft I + /* + * The rest is the same as before + */ +.ft CW +.vs +.DE +Note the use of the routine +.I netname2user (), +the inverse of +.I user2netname (): +it takes a network ID and converts to a unix ID. +.I netname2user () +also supplies the group IDs which we don't use in this example, +but which may be useful to other UNIX programs. +.NH 2 +\&Using Inetd +.IX inetd "" "using \fIinetd\fP" +.LP +An RPC server can be started from +.I inetd +The only difference from the usual code is that the service +creation routine should be called in the following form: +.ie t .DS +.el .DS L +.ft CW +transp = svcudp_create(0); /* \fIFor UDP\fP */ +transp = svctcp_create(0,0,0); /* \fIFor listener TCP sockets\fP */ +transp = svcfd_create(0,0,0); /* \fIFor connected TCP sockets\fP */ +.DE +since +.I inet +passes a socket as file descriptor 0. +Also, +.I svc_register() +should be called as +.ie t .DS +.el .DS L +.ft CW +svc_register(transp, PROGNUM, VERSNUM, service, 0); +.DE +with the final flag as 0, +since the program would already be registered by +.I inetd +Remember that if you want to exit +from the server process and return control to +.I inet +you need to explicitly exit, since +.I svc_run() +never returns. +.LP +The format of entries in +.I /etc/inetd.conf +for RPC services is in one of the following two forms: +.ie t .DS +.el .DS L +.ft CW +p_name/version dgram rpc/udp wait/nowait user server args +p_name/version stream rpc/tcp wait/nowait user server args +.DE +where +.I p_name +is the symbolic name of the program as it appears in +.I rpc(5) , +.I server +is the program implementing the server, +and +.I program +and +.I version +are the program and version numbers of the service. +For more information, see +.I inetd.conf(5) . +.LP +If the same program handles multiple versions, +then the version number can be a range, +as in this example: +.ie t .DS +.el .DS L +.ft CW +rstatd/1-2 dgram rpc/udp wait root /usr/etc/rpc.rstatd +.DE +.NH 1 +\&More Examples +.sp 1 +.NH 2 +\&Versions +.IX "versions" +.IX "RPC" "versions" +.LP +By convention, the first version number of program +.I PROG +is +.I PROGVERS_ORIG +and the most recent version is +.I PROGVERS +Suppose there is a new version of the +.I user +program that returns an +.I "unsigned short" +rather than a +.I long . +If we name this version +.I RUSERSVERS_SHORT +then a server that wants to support both versions +would do a double register. +.ie t .DS +.el .DS L +.ft CW +if (!svc_register(transp, RUSERSPROG, RUSERSVERS_ORIG, + nuser, IPPROTO_TCP)) { + fprintf(stderr, "can't register RUSER service\en"); + exit(1); +} +if (!svc_register(transp, RUSERSPROG, RUSERSVERS_SHORT, + nuser, IPPROTO_TCP)) { + fprintf(stderr, "can't register RUSER service\en"); + exit(1); +} +.DE +Both versions can be handled by the same C procedure: +.ie t .DS +.el .DS L +.ft CW +.vs 11 +nuser(rqstp, transp) + struct svc_req *rqstp; + SVCXPRT *transp; +{ + unsigned long nusers; + unsigned short nusers2; + + switch (rqstp->rq_proc) { + case NULLPROC: + if (!svc_sendreply(transp, xdr_void, 0)) { + fprintf(stderr, "can't reply to RPC call\en"); + return (1); + } + return; + case RUSERSPROC_NUM: +.ft I + /* + * Code here to compute the number of users + * and assign it to the variable \fInusers\fP + */ +.ft CW + nusers2 = nusers; + switch (rqstp->rq_vers) { + case RUSERSVERS_ORIG: + if (!svc_sendreply(transp, xdr_u_long, + &nusers)) { + fprintf(stderr,"can't reply to RPC call\en"); + } + break; + case RUSERSVERS_SHORT: + if (!svc_sendreply(transp, xdr_u_short, + &nusers2)) { + fprintf(stderr,"can't reply to RPC call\en"); + } + break; + } + default: + svcerr_noproc(transp); + return; + } +} +.vs +.DE +.KS +.NH 2 +\&TCP +.IX "TCP" +.LP +Here is an example that is essentially +.I rcp. +The initiator of the RPC +.I snd +call takes its standard input and sends it to the server +.I rcv +which prints it on standard output. +The RPC call uses TCP. +This also illustrates an XDR procedure that behaves differently +on serialization than on deserialization. +.ie t .DS +.el .DS L +.vs 11 +.ft I +/* + * The xdr routine: + * on decode, read from wire, write onto fp + * on encode, read from fp, write onto wire + */ +.ft CW +#include +#include + +xdr_rcp(xdrs, fp) + XDR *xdrs; + FILE *fp; +{ + unsigned long size; + char buf[BUFSIZ], *p; + + if (xdrs->x_op == XDR_FREE)/* nothing to free */ + return 1; + while (1) { + if (xdrs->x_op == XDR_ENCODE) { + if ((size = fread(buf, sizeof(char), BUFSIZ, + fp)) == 0 && ferror(fp)) { + fprintf(stderr, "can't fread\en"); + return (1); + } + } + p = buf; + if (!xdr_bytes(xdrs, &p, &size, BUFSIZ)) + return 0; + if (size == 0) + return 1; + if (xdrs->x_op == XDR_DECODE) { + if (fwrite(buf, sizeof(char), size, + fp) != size) { + fprintf(stderr, "can't fwrite\en"); + return (1); + } + } + } +} +.vs +.DE +.KE +.ie t .DS +.el .DS L +.vs 11 +.ft I +/* + * The sender routines + */ +.ft CW +#include +#include +#include +#include +#include + +main(argc, argv) + int argc; + char **argv; +{ + int xdr_rcp(); + int err; + + if (argc < 2) { + fprintf(stderr, "usage: %s servername\en", argv[0]); + exit(-1); + } + if ((err = callrpctcp(argv[1], RCPPROG, RCPPROC, + RCPVERS, xdr_rcp, stdin, xdr_void, 0) != 0)) { + clnt_perrno(err); + fprintf(stderr, "can't make RPC call\en"); + exit(1); + } + exit(0); +} + +callrpctcp(host, prognum, procnum, versnum, + inproc, in, outproc, out) + char *host, *in, *out; + xdrproc_t inproc, outproc; +{ + struct sockaddr_in server_addr; + int socket = RPC_ANYSOCK; + enum clnt_stat clnt_stat; + struct hostent *hp; + register CLIENT *client; + struct timeval total_timeout; + + if ((hp = gethostbyname(host)) == NULL) { + fprintf(stderr, "can't get addr for '%s'\en", host); + return (-1); + } + bcopy(hp->h_addr, (caddr_t)&server_addr.sin_addr, + hp->h_length); + server_addr.sin_family = AF_INET; + server_addr.sin_port = 0; + if ((client = clnttcp_create(&server_addr, prognum, + versnum, &socket, BUFSIZ, BUFSIZ)) == NULL) { + perror("rpctcp_create"); + return (-1); + } + total_timeout.tv_sec = 20; + total_timeout.tv_usec = 0; + clnt_stat = clnt_call(client, procnum, + inproc, in, outproc, out, total_timeout); + clnt_destroy(client); + return (int)clnt_stat; +} +.vs +.DE +.ie t .DS +.el .DS L +.vs 11 +.ft I +/* + * The receiving routines + */ +.ft CW +#include +#include + +main() +{ + register SVCXPRT *transp; + int rcp_service(), xdr_rcp(); + + if ((transp = svctcp_create(RPC_ANYSOCK, + BUFSIZ, BUFSIZ)) == NULL) { + fprintf("svctcp_create: error\en"); + exit(1); + } + pmap_unset(RCPPROG, RCPVERS); + if (!svc_register(transp, + RCPPROG, RCPVERS, rcp_service, IPPROTO_TCP)) { + fprintf(stderr, "svc_register: error\en"); + exit(1); + } + svc_run(); /* \fInever returns\fP */ + fprintf(stderr, "svc_run should never return\en"); +} + +rcp_service(rqstp, transp) + register struct svc_req *rqstp; + register SVCXPRT *transp; +{ + switch (rqstp->rq_proc) { + case NULLPROC: + if (svc_sendreply(transp, xdr_void, 0) == 0) { + fprintf(stderr, "err: rcp_service"); + return (1); + } + return; + case RCPPROC_FP: + if (!svc_getargs(transp, xdr_rcp, stdout)) { + svcerr_decode(transp); + return; + } + if (!svc_sendreply(transp, xdr_void, 0)) { + fprintf(stderr, "can't reply\en"); + return; + } + return (0); + default: + svcerr_noproc(transp); + return; + } +} +.vs +.DE +.NH 2 +\&Callback Procedures +.IX RPC "callback procedures" +.LP +Occasionally, it is useful to have a server become a client, +and make an RPC call back to the process which is its client. +An example is remote debugging, +where the client is a window system program, +and the server is a debugger running on the remote machine. +Most of the time, +the user clicks a mouse button at the debugging window, +which converts this to a debugger command, +and then makes an RPC call to the server +(where the debugger is actually running), +telling it to execute that command. +However, when the debugger hits a breakpoint, the roles are reversed, +and the debugger wants to make an rpc call to the window program, +so that it can inform the user that a breakpoint has been reached. +.LP +In order to do an RPC callback, +you need a program number to make the RPC call on. +Since this will be a dynamically generated program number, +it should be in the transient range, +.I "0x40000000 - 0x5fffffff" . +The routine +.I gettransient() +returns a valid program number in the transient range, +and registers it with the portmapper. +It only talks to the portmapper running on the same machine as the +.I gettransient() +routine itself. The call to +.I pmap_set() +is a test and set operation, +in that it indivisibly tests whether a program number +has already been registered, +and if it has not, then reserves it. On return, the +.I sockp +argument will contain a socket that can be used +as the argument to an +.I svcudp_create() +or +.I svctcp_create() +call. +.ie t .DS +.el .DS L +.ft CW +.vs 11 +#include +#include +#include + +gettransient(proto, vers, sockp) + int proto, vers, *sockp; +{ + static int prognum = 0x40000000; + int s, len, socktype; + struct sockaddr_in addr; + + switch(proto) { + case IPPROTO_UDP: + socktype = SOCK_DGRAM; + break; + case IPPROTO_TCP: + socktype = SOCK_STREAM; + break; + default: + fprintf(stderr, "unknown protocol type\en"); + return 0; + } + if (*sockp == RPC_ANYSOCK) { + if ((s = socket(AF_INET, socktype, 0)) < 0) { + perror("socket"); + return (0); + } + *sockp = s; + } + else + s = *sockp; + addr.sin_addr.s_addr = 0; + addr.sin_family = AF_INET; + addr.sin_port = 0; + len = sizeof(addr); +.ft I + /* + * may be already bound, so don't check for error + */ +.ft CW + bind(s, &addr, len); + if (getsockname(s, &addr, &len)< 0) { + perror("getsockname"); + return (0); + } + while (!pmap_set(prognum++, vers, proto, + ntohs(addr.sin_port))) continue; + return (prognum-1); +} +.vs +.DE +.SH +Note: +.I +The call to +.I ntohs() +is necessary to ensure that the port number in +.I "addr.sin_port" , +which is in +.I network +byte order, is passed in +.I host +byte order (as +.I pmap_set() +expects). See the +.I byteorder(3N) +man page for more details on the conversion of network +addresses from network to host byte order. +.KS +.LP +The following pair of programs illustrate how to use the +.I gettransient() +routine. +The client makes an RPC call to the server, +passing it a transient program number. +Then the client waits around to receive a callback +from the server at that program number. +The server registers the program +.I EXAMPLEPROG +so that it can receive the RPC call +informing it of the callback program number. +Then at some random time (on receiving an +.I ALRM +signal in this example), it sends a callback RPC call, +using the program number it received earlier. +.ie t .DS +.el .DS L +.vs 11 +.ft I +/* + * client + */ +.ft CW +#include +#include + +int callback(); +char hostname[256]; + +main() +{ + int x, ans, s; + SVCXPRT *xprt; + + gethostname(hostname, sizeof(hostname)); + s = RPC_ANYSOCK; + x = gettransient(IPPROTO_UDP, 1, &s); + fprintf(stderr, "client gets prognum %d\en", x); + if ((xprt = svcudp_create(s)) == NULL) { + fprintf(stderr, "rpc_server: svcudp_create\en"); + exit(1); + } +.ft I + /* protocol is 0 - gettransient does registering + */ +.ft CW + (void)svc_register(xprt, x, 1, callback, 0); + ans = callrpc(hostname, EXAMPLEPROG, EXAMPLEVERS, + EXAMPLEPROC_CALLBACK, xdr_int, &x, xdr_void, 0); + if ((enum clnt_stat) ans != RPC_SUCCESS) { + fprintf(stderr, "call: "); + clnt_perrno(ans); + fprintf(stderr, "\en"); + } + svc_run(); + fprintf(stderr, "Error: svc_run shouldn't return\en"); +} + +callback(rqstp, transp) + register struct svc_req *rqstp; + register SVCXPRT *transp; +{ + switch (rqstp->rq_proc) { + case 0: + if (!svc_sendreply(transp, xdr_void, 0)) { + fprintf(stderr, "err: exampleprog\en"); + return (1); + } + return (0); + case 1: + if (!svc_getargs(transp, xdr_void, 0)) { + svcerr_decode(transp); + return (1); + } + fprintf(stderr, "client got callback\en"); + if (!svc_sendreply(transp, xdr_void, 0)) { + fprintf(stderr, "err: exampleprog"); + return (1); + } + } +} +.vs +.DE +.KE +.ie t .DS +.el .DS L +.vs 11 +.ft I +/* + * server + */ +.ft CW +#include +#include +#include + +char *getnewprog(); +char hostname[256]; +int docallback(); +int pnum; /* \fIprogram number for callback routine\fP */ + +main() +{ + gethostname(hostname, sizeof(hostname)); + registerrpc(EXAMPLEPROG, EXAMPLEVERS, + EXAMPLEPROC_CALLBACK, getnewprog, xdr_int, xdr_void); + fprintf(stderr, "server going into svc_run\en"); + signal(SIGALRM, docallback); + alarm(10); + svc_run(); + fprintf(stderr, "Error: svc_run shouldn't return\en"); +} + +char * +getnewprog(pnump) + char *pnump; +{ + pnum = *(int *)pnump; + return NULL; +} + +docallback() +{ + int ans; + + ans = callrpc(hostname, pnum, 1, 1, xdr_void, 0, + xdr_void, 0); + if (ans != 0) { + fprintf(stderr, "server: "); + clnt_perrno(ans); + fprintf(stderr, "\en"); + } +} +.vs +.DE diff --git a/lib/libc/rpc/PSD.doc/rpc.rfc.ms b/lib/libc/rpc/PSD.doc/rpc.rfc.ms new file mode 100644 index 000000000000..af9c2df2ed83 --- /dev/null +++ b/lib/libc/rpc/PSD.doc/rpc.rfc.ms @@ -0,0 +1,1302 @@ +.\" +.\" Must use -- tbl -- with this one +.\" +.\" @(#)rpc.rfc.ms 2.2 88/08/05 4.0 RPCSRC +.de BT +.if \\n%=1 .tl ''- % -'' +.. +.ND +.\" prevent excess underlining in nroff +.if n .fp 2 R +.OH 'Remote Procedure Calls: Protocol Specification''Page %' +.EH 'Page %''Remote Procedure Calls: Protocol Specification' +.if \\n%=1 .bp +.SH +\&Remote Procedure Calls: Protocol Specification +.LP +.NH 0 +\&Status of this Memo +.LP +Note: This chapter specifies a protocol that Sun Microsystems, Inc., +and others are using. +It has been designated RFC1050 by the ARPA Network +Information Center. +.LP +.NH 1 +\&Introduction +.LP +This chapter specifies a message protocol used in implementing +Sun's Remote Procedure Call (RPC) package. (The message protocol is +specified with the External Data Representation (XDR) language. +See the +.I "External Data Representation Standard: Protocol Specification" +for the details. Here, we assume that the reader is familiar +with XDR and do not attempt to justify it or its uses). The paper +by Birrell and Nelson [1] is recommended as an excellent background +to and justification of RPC. +.NH 2 +\&Terminology +.LP +This chapter discusses servers, services, programs, procedures, +clients, and versions. A server is a piece of software where network +services are implemented. A network service is a collection of one +or more remote programs. A remote program implements one or more +remote procedures; the procedures, their parameters, and results are +documented in the specific program's protocol specification (see the +\fIPort Mapper Program Protocol\fP\, below, for an example). Network +clients are pieces of software that initiate remote procedure calls +to services. A server may support more than one version of a remote +program in order to be forward compatible with changing protocols. +.LP +For example, a network file service may be composed of two programs. +One program may deal with high-level applications such as file system +access control and locking. The other may deal with low-level file +IO and have procedures like "read" and "write". A client machine of +the network file service would call the procedures associated with +the two programs of the service on behalf of some user on the client +machine. +.NH 2 +\&The RPC Model +.LP +The remote procedure call model is similar to the local procedure +call model. In the local case, the caller places arguments to a +procedure in some well-specified location (such as a result +register). It then transfers control to the procedure, and +eventually gains back control. At that point, the results of the +procedure are extracted from the well-specified location, and the +caller continues execution. +.LP +The remote procedure call is similar, in that one thread of control +logically winds through two processes\(emone is the caller's process, +the other is a server's process. That is, the caller process sends a +call message to the server process and waits (blocks) for a reply +message. The call message contains the procedure's parameters, among +other things. The reply message contains the procedure's results, +among other things. Once the reply message is received, the results +of the procedure are extracted, and caller's execution is resumed. +.LP +On the server side, a process is dormant awaiting the arrival of a +call message. When one arrives, the server process extracts the +procedure's parameters, computes the results, sends a reply message, +and then awaits the next call message. +.LP +Note that in this model, only one of the two processes is active at +any given time. However, this model is only given as an example. +The RPC protocol makes no restrictions on the concurrency model +implemented, and others are possible. For example, an implementation +may choose to have RPC calls be asynchronous, so that the client may +do useful work while waiting for the reply from the server. Another +possibility is to have the server create a task to process an +incoming request, so that the server can be free to receive other +requests. +.NH 2 +\&Transports and Semantics +.LP +The RPC protocol is independent of transport protocols. That is, RPC +does not care how a message is passed from one process to another. +The protocol deals only with specification and interpretation of +messages. +.LP +It is important to point out that RPC does not try to implement any +kind of reliability and that the application must be aware of the +type of transport protocol underneath RPC. If it knows it is running +on top of a reliable transport such as TCP/IP[6], then most of the +work is already done for it. On the other hand, if it is running on +top of an unreliable transport such as UDP/IP[7], it must implement +is own retransmission and time-out policy as the RPC layer does not +provide this service. +.LP +Because of transport independence, the RPC protocol does not attach +specific semantics to the remote procedures or their execution. +Semantics can be inferred from (but should be explicitly specified +by) the underlying transport protocol. For example, consider RPC +running on top of an unreliable transport such as UDP/IP. If an +application retransmits RPC messages after short time-outs, the only +thing it can infer if it receives no reply is that the procedure was +executed zero or more times. If it does receive a reply, then it can +infer that the procedure was executed at least once. +.LP +A server may wish to remember previously granted requests from a +client and not regrant them in order to insure some degree of +execute-at-most-once semantics. A server can do this by taking +advantage of the transaction ID that is packaged with every RPC +request. The main use of this transaction is by the client RPC layer +in matching replies to requests. However, a client application may +choose to reuse its previous transaction ID when retransmitting a +request. The server application, knowing this fact, may choose to +remember this ID after granting a request and not regrant requests +with the same ID in order to achieve some degree of +execute-at-most-once semantics. The server is not allowed to examine +this ID in any other way except as a test for equality. +.LP +On the other hand, if using a reliable transport such as TCP/IP, the +application can infer from a reply message that the procedure was +executed exactly once, but if it receives no reply message, it cannot +assume the remote procedure was not executed. Note that even if a +connection-oriented protocol like TCP is used, an application still +needs time-outs and reconnection to handle server crashes. +.LP +There are other possibilities for transports besides datagram- or +connection-oriented protocols. For example, a request-reply protocol +such as VMTP[2] is perhaps the most natural transport for RPC. +.SH +.I +NOTE: At Sun, RPC is currently implemented on top of both TCP/IP +and UDP/IP transports. +.LP +.NH 2 +\&Binding and Rendezvous Independence +.LP +The act of binding a client to a service is NOT part of the remote +procedure call specification. This important and necessary function +is left up to some higher-level software. (The software may use RPC +itself\(emsee the \fIPort Mapper Program Protocol\fP\, below). +.LP +Implementors should think of the RPC protocol as the jump-subroutine +instruction ("JSR") of a network; the loader (binder) makes JSR +useful, and the loader itself uses JSR to accomplish its task. +Likewise, the network makes RPC useful, using RPC to accomplish this +task. +.NH 2 +\&Authentication +.LP +The RPC protocol provides the fields necessary for a client to +identify itself to a service and vice-versa. Security and access +control mechanisms can be built on top of the message authentication. +Several different authentication protocols can be supported. A field +in the RPC header indicates which protocol is being used. More +information on specific authentication protocols can be found in the +\fIAuthentication Protocols\fP\, +below. +.KS +.NH 1 +\&RPC Protocol Requirements +.LP +The RPC protocol must provide for the following: +.IP 1. +Unique specification of a procedure to be called. +.IP 2. +Provisions for matching response messages to request messages. +.KE +.IP 3. +Provisions for authenticating the caller to service and vice-versa. +.LP +Besides these requirements, features that detect the following are +worth supporting because of protocol roll-over errors, implementation +bugs, user error, and network administration: +.IP 1. +RPC protocol mismatches. +.IP 2. +Remote program protocol version mismatches. +.IP 3. +Protocol errors (such as misspecification of a procedure's parameters). +.IP 4. +Reasons why remote authentication failed. +.IP 5. +Any other reasons why the desired procedure was not called. +.NH 2 +\&Programs and Procedures +.LP +The RPC call message has three unsigned fields: remote program +number, remote program version number, and remote procedure number. +The three fields uniquely identify the procedure to be called. +Program numbers are administered by some central authority (like +Sun). Once an implementor has a program number, he can implement his +remote program; the first implementation would most likely have the +version number of 1. Because most new protocols evolve into better, +stable, and mature protocols, a version field of the call message +identifies which version of the protocol the caller is using. +Version numbers make speaking old and new protocols through the same +server process possible. +.LP +The procedure number identifies the procedure to be called. These +numbers are documented in the specific program's protocol +specification. For example, a file service's protocol specification +may state that its procedure number 5 is "read" and procedure number +12 is "write". +.LP +Just as remote program protocols may change over several versions, +the actual RPC message protocol could also change. Therefore, the +call message also has in it the RPC version number, which is always +equal to two for the version of RPC described here. +.LP +The reply message to a request message has enough information to +distinguish the following error conditions: +.IP 1. +The remote implementation of RPC does speak protocol version 2. +The lowest and highest supported RPC version numbers are returned. +.IP 2. +The remote program is not available on the remote system. +.IP 3. +The remote program does not support the requested version number. +The lowest and highest supported remote program version numbers are +returned. +.IP 4. +The requested procedure number does not exist. (This is usually a +caller side protocol or programming error.) +.IP 5. +The parameters to the remote procedure appear to be garbage from the +server's point of view. (Again, this is usually caused by a +disagreement about the protocol between client and service.) +.NH 2 +\&Authentication +.LP +Provisions for authentication of caller to service and vice-versa are +provided as a part of the RPC protocol. The call message has two +authentication fields, the credentials and verifier. The reply +message has one authentication field, the response verifier. The RPC +protocol specification defines all three fields to be the following +opaque type: +.DS +.ft CW +.vs 11 +enum auth_flavor { + AUTH_NULL = 0, + AUTH_UNIX = 1, + AUTH_SHORT = 2, + AUTH_DES = 3 + /* \fIand more to be defined\fP */ +}; + +struct opaque_auth { + auth_flavor flavor; + opaque body<400>; +}; +.DE +.LP +In simple English, any +.I opaque_auth +structure is an +.I auth_flavor +enumeration followed by bytes which are opaque to the RPC protocol +implementation. +.LP +The interpretation and semantics of the data contained within the +authentication fields is specified by individual, independent +authentication protocol specifications. (See +\fIAuthentication Protocols\fP\, +below, for definitions of the various authentication protocols.) +.LP +If authentication parameters were rejected, the response message +contains information stating why they were rejected. +.NH 2 +\&Program Number Assignment +.LP +Program numbers are given out in groups of +.I 0x20000000 +(decimal 536870912) according to the following chart: +.TS +box tab (&) ; +lfI lfI +rfL cfI . +Program Numbers&Description +_ +.sp .5 +0 - 1fffffff&Defined by Sun +20000000 - 3fffffff&Defined by user +40000000 - 5fffffff&Transient +60000000 - 7fffffff&Reserved +80000000 - 9fffffff&Reserved +a0000000 - bfffffff&Reserved +c0000000 - dfffffff&Reserved +e0000000 - ffffffff&Reserved +.TE +.LP +The first group is a range of numbers administered by Sun +Microsystems and should be identical for all sites. The second range +is for applications peculiar to a particular site. This range is +intended primarily for debugging new programs. When a site develops +an application that might be of general interest, that application +should be given an assigned number in the first range. The third +group is for applications that generate program numbers dynamically. +The final groups are reserved for future use, and should not be used. +.NH 2 +\&Other Uses of the RPC Protocol +.LP +The intended use of this protocol is for calling remote procedures. +That is, each call message is matched with a response message. +However, the protocol itself is a message-passing protocol with which +other (non-RPC) protocols can be implemented. Sun currently uses, or +perhaps abuses, the RPC message protocol for the following two +(non-RPC) protocols: batching (or pipelining) and broadcast RPC. +These two protocols are discussed but not defined below. +.NH 3 +\&Batching +.LP +Batching allows a client to send an arbitrarily large sequence of +call messages to a server; batching typically uses reliable byte +stream protocols (like TCP/IP) for its transport. In the case of +batching, the client never waits for a reply from the server, and the +server does not send replies to batch requests. A sequence of batch +calls is usually terminated by a legitimate RPC in order to flush the +pipeline (with positive acknowledgement). +.NH 3 +\&Broadcast RPC +.LP +In broadcast RPC-based protocols, the client sends a broadcast packet +to the network and waits for numerous replies. Broadcast RPC uses +unreliable, packet-based protocols (like UDP/IP) as its transports. +Servers that support broadcast protocols only respond when the +request is successfully processed, and are silent in the face of +errors. Broadcast RPC uses the Port Mapper RPC service to achieve +its semantics. See the \fIPort Mapper Program Protocol\fP\, below, +for more information. +.KS +.NH 1 +\&The RPC Message Protocol +.LP +This section defines the RPC message protocol in the XDR data +description language. The message is defined in a top-down style. +.ie t .DS +.el .DS L +.ft CW +enum msg_type { + CALL = 0, + REPLY = 1 +}; + +.ft I +/* +* A reply to a call message can take on two forms: +* The message was either accepted or rejected. +*/ +.ft CW +enum reply_stat { + MSG_ACCEPTED = 0, + MSG_DENIED = 1 +}; + +.ft I +/* +* Given that a call message was accepted, the following is the +* status of an attempt to call a remote procedure. +*/ +.ft CW +enum accept_stat { + SUCCESS = 0, /* \fIRPC executed successfully \fP*/ + PROG_UNAVAIL = 1, /* \fIremote hasn't exported program \fP*/ + PROG_MISMATCH = 2, /* \fIremote can't support version # \fP*/ + PROC_UNAVAIL = 3, /* \fIprogram can't support procedure \fP*/ + GARBAGE_ARGS = 4 /* \fIprocedure can't decode params \fP*/ +}; +.DE +.ie t .DS +.el .DS L +.ft I +/* +* Reasons why a call message was rejected: +*/ +.ft CW +enum reject_stat { + RPC_MISMATCH = 0, /* \fIRPC version number != 2 \fP*/ + AUTH_ERROR = 1 /* \fIremote can't authenticate caller \fP*/ +}; + +.ft I +/* +* Why authentication failed: +*/ +.ft CW +enum auth_stat { + AUTH_BADCRED = 1, /* \fIbad credentials \fP*/ + AUTH_REJECTEDCRED = 2, /* \fIclient must begin new session \fP*/ + AUTH_BADVERF = 3, /* \fIbad verifier \fP*/ + AUTH_REJECTEDVERF = 4, /* \fIverifier expired or replayed \fP*/ + AUTH_TOOWEAK = 5 /* \fIrejected for security reasons \fP*/ +}; +.DE +.KE +.ie t .DS +.el .DS L +.ft I +/* +* The RPC message: +* All messages start with a transaction identifier, xid, +* followed by a two-armed discriminated union. The union's +* discriminant is a msg_type which switches to one of the two +* types of the message. The xid of a \fIREPLY\fP message always +* matches that of the initiating \fICALL\fP message. NB: The xid +* field is only used for clients matching reply messages with +* call messages or for servers detecting retransmissions; the +* service side cannot treat this id as any type of sequence +* number. +*/ +.ft CW +struct rpc_msg { + unsigned int xid; + union switch (msg_type mtype) { + case CALL: + call_body cbody; + case REPLY: + reply_body rbody; + } body; +}; +.DE +.ie t .DS +.el .DS L +.ft I +/* +* Body of an RPC request call: +* In version 2 of the RPC protocol specification, rpcvers must +* be equal to 2. The fields prog, vers, and proc specify the +* remote program, its version number, and the procedure within +* the remote program to be called. After these fields are two +* authentication parameters: cred (authentication credentials) +* and verf (authentication verifier). The two authentication +* parameters are followed by the parameters to the remote +* procedure, which are specified by the specific program +* protocol. +*/ +.ft CW +struct call_body { + unsigned int rpcvers; /* \fImust be equal to two (2) \fP*/ + unsigned int prog; + unsigned int vers; + unsigned int proc; + opaque_auth cred; + opaque_auth verf; + /* \fIprocedure specific parameters start here \fP*/ +}; +.DE +.ie t .DS +.el .DS L +.ft I +/* +* Body of a reply to an RPC request: +* The call message was either accepted or rejected. +*/ +.ft CW +union reply_body switch (reply_stat stat) { + case MSG_ACCEPTED: + accepted_reply areply; + case MSG_DENIED: + rejected_reply rreply; +} reply; +.DE +.ie t .DS +.el .DS L +.ft I +/* +* Reply to an RPC request that was accepted by the server: +* there could be an error even though the request was accepted. +* The first field is an authentication verifier that the server +* generates in order to validate itself to the caller. It is +* followed by a union whose discriminant is an enum +* accept_stat. The \fISUCCESS\fP arm of the union is protocol +* specific. The \fIPROG_UNAVAIL\fP, \fIPROC_UNAVAIL\fP, and \fIGARBAGE_ARGP\fP +* arms of the union are void. The \fIPROG_MISMATCH\fP arm specifies +* the lowest and highest version numbers of the remote program +* supported by the server. +*/ +.ft CW +struct accepted_reply { + opaque_auth verf; + union switch (accept_stat stat) { + case SUCCESS: + opaque results[0]; + /* \fIprocedure-specific results start here\fP */ + case PROG_MISMATCH: + struct { + unsigned int low; + unsigned int high; + } mismatch_info; + default: +.ft I + /* + * Void. Cases include \fIPROG_UNAVAIL, PROC_UNAVAIL\fP, + * and \fIGARBAGE_ARGS\fP. + */ +.ft CW + void; + } reply_data; +}; +.DE +.ie t .DS +.el .DS L +.ft I +/* +* Reply to an RPC request that was rejected by the server: +* The request can be rejected for two reasons: either the +* server is not running a compatible version of the RPC +* protocol (\fIRPC_MISMATCH\fP), or the server refuses to +* authenticate the caller (\fIAUTH_ERROR\fP). In case of an RPC +* version mismatch, the server returns the lowest and highest +* supported RPC version numbers. In case of refused +* authentication, failure status is returned. +*/ +.ft CW +union rejected_reply switch (reject_stat stat) { + case RPC_MISMATCH: + struct { + unsigned int low; + unsigned int high; + } mismatch_info; + case AUTH_ERROR: + auth_stat stat; +}; +.DE +.NH 1 +\&Authentication Protocols +.LP +As previously stated, authentication parameters are opaque, but +open-ended to the rest of the RPC protocol. This section defines +some "flavors" of authentication implemented at (and supported by) +Sun. Other sites are free to invent new authentication types, with +the same rules of flavor number assignment as there is for program +number assignment. +.NH 2 +\&Null Authentication +.LP +Often calls must be made where the caller does not know who he is or +the server does not care who the caller is. In this case, the flavor +value (the discriminant of the \fIopaque_auth\fP's union) of the RPC +message's credentials, verifier, and response verifier is +.I AUTH_NULL . +The bytes of the opaque_auth's body are undefined. +It is recommended that the opaque length be zero. +.NH 2 +\&UNIX Authentication +.LP +The caller of a remote procedure may wish to identify himself as he +is identified on a UNIX system. The value of the credential's +discriminant of an RPC call message is +.I AUTH_UNIX . +The bytes of +the credential's opaque body encode the following structure: +.DS +.ft CW +struct auth_unix { + unsigned int stamp; + string machinename<255>; + unsigned int uid; + unsigned int gid; + unsigned int gids<10>; +}; +.DE +The +.I stamp +is an arbitrary ID which the caller machine may +generate. The +.I machinename +is the name of the caller's machine (like "krypton"). The +.I uid +is the caller's effective user ID. The +.I gid +is the caller's effective group ID. The +.I gids +is a +counted array of groups which contain the caller as a member. The +verifier accompanying the credentials should be of +.I AUTH_NULL +(defined above). +.LP +The value of the discriminant of the response verifier received in +the reply message from the server may be +.I AUTH_NULL +or +.I AUTH_SHORT . +In the case of +.I AUTH_SHORT , +the bytes of the response verifier's string encode an opaque +structure. This new opaque structure may now be passed to the server +instead of the original +.I AUTH_UNIX +flavor credentials. The server keeps a cache which maps shorthand +opaque structures (passed back by way of an +.I AUTH_SHORT +style response verifier) to the original credentials of the caller. +The caller can save network bandwidth and server cpu cycles by using +the new credentials. +.LP +The server may flush the shorthand opaque structure at any time. If +this happens, the remote procedure call message will be rejected due +to an authentication error. The reason for the failure will be +.I AUTH_REJECTEDCRED . +At this point, the caller may wish to try the original +.I AUTH_UNIX +style of credentials. +.KS +.NH 2 +\&DES Authentication +.LP +UNIX authentication suffers from two major problems: +.IP 1. +The naming is too UNIX-system oriented. +.IP 2. +There is no verifier, so credentials can easily be faked. +.LP +DES authentication attempts to fix these two problems. +.KE +.NH 3 +\&Naming +.LP +The first problem is handled by addressing the caller by a simple +string of characters instead of by an operating system specific +integer. This string of characters is known as the "netname" or +network name of the caller. The server is not allowed to interpret +the contents of the caller's name in any other way except to +identify the caller. Thus, netnames should be unique for every +caller in the internet. +.LP +It is up to each operating system's implementation of DES +authentication to generate netnames for its users that insure this +uniqueness when they call upon remote servers. Operating systems +already know how to distinguish users local to their systems. It is +usually a simple matter to extend this mechanism to the network. +For example, a UNIX user at Sun with a user ID of 515 might be +assigned the following netname: "unix.515@sun.com". This netname +contains three items that serve to insure it is unique. Going +backwards, there is only one naming domain called "sun.com" in the +internet. Within this domain, there is only one UNIX user with +user ID 515. However, there may be another user on another +operating system, for example VMS, within the same naming domain +that, by coincidence, happens to have the same user ID. To insure +that these two users can be distinguished we add the operating +system name. So one user is "unix.515@sun.com" and the other is +"vms.515@sun.com". +.LP +The first field is actually a naming method rather than an +operating system name. It just happens that today there is almost +a one-to-one correspondence between naming methods and operating +systems. If the world could agree on a naming standard, the first +field could be the name of that standard, instead of an operating +system name. +.LP +.NH 3 +\&DES Authentication Verifiers +.LP +Unlike UNIX authentication, DES authentication does have a verifier +so the server can validate the client's credential (and +vice-versa). The contents of this verifier is primarily an +encrypted timestamp. The server can decrypt this timestamp, and if +it is close to what the real time is, then the client must have +encrypted it correctly. The only way the client could encrypt it +correctly is to know the "conversation key" of the RPC session. And +if the client knows the conversation key, then it must be the real +client. +.LP +The conversation key is a DES [5] key which the client generates +and notifies the server of in its first RPC call. The conversation +key is encrypted using a public key scheme in this first +transaction. The particular public key scheme used in DES +authentication is Diffie-Hellman [3] with 192-bit keys. The +details of this encryption method are described later. +.LP +The client and the server need the same notion of the current time +in order for all of this to work. If network time synchronization +cannot be guaranteed, then client can synchronize with the server +before beginning the conversation, perhaps by consulting the +Internet Time Server (TIME[4]). +.LP +The way a server determines if a client timestamp is valid is +somewhat complicated. For any other transaction but the first, the +server just checks for two things: +.IP 1. +the timestamp is greater than the one previously seen from the +same client. +.IP 2. +the timestamp has not expired. +.LP +A timestamp is expired if the server's time is later than the sum +of the client's timestamp plus what is known as the client's +"window". The "window" is a number the client passes (encrypted) +to the server in its first transaction. You can think of it as a +lifetime for the credential. +.LP +This explains everything but the first transaction. In the first +transaction, the server checks only that the timestamp has not +expired. If this was all that was done though, then it would be +quite easy for the client to send random data in place of the +timestamp with a fairly good chance of succeeding. As an added +check, the client sends an encrypted item in the first transaction +known as the "window verifier" which must be equal to the window +minus 1, or the server will reject the credential. +.LP +The client too must check the verifier returned from the server to +be sure it is legitimate. The server sends back to the client the +encrypted timestamp it received from the client, minus one second. +If the client gets anything different than this, it will reject it. +.LP +.NH 3 +\&Nicknames and Clock Synchronization +.LP +After the first transaction, the server's DES authentication +subsystem returns in its verifier to the client an integer +"nickname" which the client may use in its further transactions +instead of passing its netname, encrypted DES key and window every +time. The nickname is most likely an index into a table on the +server which stores for each client its netname, decrypted DES key +and window. +.LP +Though they originally were synchronized, the client's and server's +clocks can get out of sync again. When this happens the client RPC +subsystem most likely will get back +.I RPC_AUTHERROR +at which point it should resynchronize. +.LP +A client may still get the +.I RPC_AUTHERROR +error even though it is +synchronized with the server. The reason is that the server's +nickname table is a limited size, and it may flush entries whenever +it wants. A client should resend its original credential in this +case and the server will give it a new nickname. If a server +crashes, the entire nickname table gets flushed, and all clients +will have to resend their original credentials. +.KS +.NH 3 +\&DES Authentication Protocol (in XDR language) +.ie t .DS +.el .DS L +.ft I +/* +* There are two kinds of credentials: one in which the client uses +* its full network name, and one in which it uses its "nickname" +* (just an unsigned integer) given to it by the server. The +* client must use its fullname in its first transaction with the +* server, in which the server will return to the client its +* nickname. The client may use its nickname in all further +* transactions with the server. There is no requirement to use the +* nickname, but it is wise to use it for performance reasons. +*/ +.ft CW +enum authdes_namekind { + ADN_FULLNAME = 0, + ADN_NICKNAME = 1 +}; + +.ft I +/* +* A 64-bit block of encrypted DES data +*/ +.ft CW +typedef opaque des_block[8]; + +.ft I +/* +* Maximum length of a network user's name +*/ +.ft CW +const MAXNETNAMELEN = 255; + +.ft I +/* +* A fullname contains the network name of the client, an encrypted +* conversation key and the window. The window is actually a +* lifetime for the credential. If the time indicated in the +* verifier timestamp plus the window has past, then the server +* should expire the request and not grant it. To insure that +* requests are not replayed, the server should insist that +* timestamps are greater than the previous one seen, unless it is +* the first transaction. In the first transaction, the server +* checks instead that the window verifier is one less than the +* window. +*/ +.ft CW +struct authdes_fullname { +string name; /* \fIname of client \f(CW*/ +des_block key; /* \fIPK encrypted conversation key \f(CW*/ +unsigned int window; /* \fIencrypted window \f(CW*/ +}; + +.ft I +/* +* A credential is either a fullname or a nickname +*/ +.ft CW +union authdes_cred switch (authdes_namekind adc_namekind) { + case ADN_FULLNAME: + authdes_fullname adc_fullname; + case ADN_NICKNAME: + unsigned int adc_nickname; +}; + +.ft I +/* +* A timestamp encodes the time since midnight, January 1, 1970. +*/ +.ft CW +struct timestamp { + unsigned int seconds; /* \fIseconds \fP*/ + unsigned int useconds; /* \fIand microseconds \fP*/ +}; + +.ft I +/* +* Verifier: client variety +* The window verifier is only used in the first transaction. In +* conjunction with a fullname credential, these items are packed +* into the following structure before being encrypted: +* +* \f(CWstruct {\fP +* \f(CWadv_timestamp; \fP-- one DES block +* \f(CWadc_fullname.window; \fP-- one half DES block +* \f(CWadv_winverf; \fP-- one half DES block +* \f(CW}\fP +* This structure is encrypted using CBC mode encryption with an +* input vector of zero. All other encryptions of timestamps use +* ECB mode encryption. +*/ +.ft CW +struct authdes_verf_clnt { + timestamp adv_timestamp; /* \fIencrypted timestamp \fP*/ + unsigned int adv_winverf; /* \fIencrypted window verifier \fP*/ +}; + +.ft I +/* +* Verifier: server variety +* The server returns (encrypted) the same timestamp the client +* gave it minus one second. It also tells the client its nickname +* to be used in future transactions (unencrypted). +*/ +.ft CW +struct authdes_verf_svr { +timestamp adv_timeverf; /* \fIencrypted verifier \fP*/ +unsigned int adv_nickname; /* \fInew nickname for client \fP*/ +}; +.DE +.KE +.NH 3 +\&Diffie-Hellman Encryption +.LP +In this scheme, there are two constants, +.I BASE +and +.I MODULUS . +The +particular values Sun has chosen for these for the DES +authentication protocol are: +.ie t .DS +.el .DS L +.ft CW +const BASE = 3; +const MODULUS = + "d4a0ba0250b6fd2ec626e7efd637df76c716e22d0944b88b"; /* \fIhex \fP*/ +.DE +.ft R +The way this scheme works is best explained by an example. Suppose +there are two people "A" and "B" who want to send encrypted +messages to each other. So, A and B both generate "secret" keys at +random which they do not reveal to anyone. Let these keys be +represented as SK(A) and SK(B). They also publish in a public +directory their "public" keys. These keys are computed as follows: +.ie t .DS +.el .DS L +.ft CW +PK(A) = ( BASE ** SK(A) ) mod MODULUS +PK(B) = ( BASE ** SK(B) ) mod MODULUS +.DE +.ft R +The "**" notation is used here to represent exponentiation. Now, +both A and B can arrive at the "common" key between them, +represented here as CK(A, B), without revealing their secret keys. +.LP +A computes: +.ie t .DS +.el .DS L +.ft CW +CK(A, B) = ( PK(B) ** SK(A)) mod MODULUS +.DE +.ft R +while B computes: +.ie t .DS +.el .DS L +.ft CW +CK(A, B) = ( PK(A) ** SK(B)) mod MODULUS +.DE +.ft R +These two can be shown to be equivalent: +.ie t .DS +.el .DS L +.ft CW +(PK(B) ** SK(A)) mod MODULUS = (PK(A) ** SK(B)) mod MODULUS +.DE +.ft R +We drop the "mod MODULUS" parts and assume modulo arithmetic to +simplify things: +.ie t .DS +.el .DS L +.ft CW +PK(B) ** SK(A) = PK(A) ** SK(B) +.DE +.ft R +Then, replace PK(B) by what B computed earlier and likewise for +PK(A). +.ie t .DS +.el .DS L +.ft CW +((BASE ** SK(B)) ** SK(A) = (BASE ** SK(A)) ** SK(B) +.DE +.ft R +which leads to: +.ie t .DS +.el .DS L +.ft CW +BASE ** (SK(A) * SK(B)) = BASE ** (SK(A) * SK(B)) +.DE +.ft R +This common key CK(A, B) is not used to encrypt the timestamps used +in the protocol. Rather, it is used only to encrypt a conversation +key which is then used to encrypt the timestamps. The reason for +doing this is to use the common key as little as possible, for fear +that it could be broken. Breaking the conversation key is a far +less serious offense, since conversations are relatively +short-lived. +.LP +The conversation key is encrypted using 56-bit DES keys, yet the +common key is 192 bits. To reduce the number of bits, 56 bits are +selected from the common key as follows. The middle-most 8-bytes +are selected from the common key, and then parity is added to the +lower order bit of each byte, producing a 56-bit key with 8 bits of +parity. +.KS +.NH 1 +\&Record Marking Standard +.LP +When RPC messages are passed on top of a byte stream protocol (like +TCP/IP), it is necessary, or at least desirable, to delimit one +message from another in order to detect and possibly recover from +user protocol errors. This is called record marking (RM). Sun uses +this RM/TCP/IP transport for passing RPC messages on TCP streams. +One RPC message fits into one RM record. +.LP +A record is composed of one or more record fragments. A record +fragment is a four-byte header followed by 0 to (2**31) - 1 bytes of +fragment data. The bytes encode an unsigned binary number; as with +XDR integers, the byte order is from highest to lowest. The number +encodes two values\(ema boolean which indicates whether the fragment +is the last fragment of the record (bit value 1 implies the fragment +is the last fragment) and a 31-bit unsigned binary value which is the +length in bytes of the fragment's data. The boolean value is the +highest-order bit of the header; the length is the 31 low-order bits. +(Note that this record specification is NOT in XDR standard form!) +.KE +.KS +.NH 1 +\&The RPC Language +.LP +Just as there was a need to describe the XDR data-types in a formal +language, there is also need to describe the procedures that operate +on these XDR data-types in a formal language as well. We use the RPC +Language for this purpose. It is an extension to the XDR language. +The following example is used to describe the essence of the +language. +.NH 2 +\&An Example Service Described in the RPC Language +.LP +Here is an example of the specification of a simple ping program. +.ie t .DS +.el .DS L +.vs 11 +.ft I +/* +* Simple ping program +*/ +.ft CW +program PING_PROG { + /* \fILatest and greatest version\fP */ + version PING_VERS_PINGBACK { + void + PINGPROC_NULL(void) = 0; + +.ft I + /* + * Ping the caller, return the round-trip time + * (in microseconds). Returns -1 if the operation + * timed out. + */ +.ft CW + int + PINGPROC_PINGBACK(void) = 1; +} = 2; + +.ft I +/* +* Original version +*/ +.ft CW +version PING_VERS_ORIG { + void + PINGPROC_NULL(void) = 0; + } = 1; +} = 1; + +const PING_VERS = 2; /* \fIlatest version \fP*/ +.vs +.DE +.KE +.LP +The first version described is +.I PING_VERS_PINGBACK +with two procedures, +.I PINGPROC_NULL +and +.I PINGPROC_PINGBACK . +.I PINGPROC_NULL +takes no arguments and returns no results, but it is useful for +computing round-trip times from the client to the server and back +again. By convention, procedure 0 of any RPC protocol should have +the same semantics, and never require any kind of authentication. +The second procedure is used for the client to have the server do a +reverse ping operation back to the client, and it returns the amount +of time (in microseconds) that the operation used. The next version, +.I PING_VERS_ORIG , +is the original version of the protocol +and it does not contain +.I PINGPROC_PINGBACK +procedure. It is useful +for compatibility with old client programs, and as this program +matures it may be dropped from the protocol entirely. +.KS +.NH 2 +\&The RPC Language Specification +.LP +The RPC language is identical to the XDR language, except for the +added definition of a +.I program-def +described below. +.DS +.ft CW +program-def: + "program" identifier "{" + version-def + version-def * + "}" "=" constant ";" + +version-def: + "version" identifier "{" + procedure-def + procedure-def * + "}" "=" constant ";" + +procedure-def: + type-specifier identifier "(" type-specifier ")" + "=" constant ";" +.DE +.KE +.NH 2 +\&Syntax Notes +.IP 1. +The following keywords are added and cannot be used as +identifiers: "program" and "version"; +.IP 2. +A version name cannot occur more than once within the scope of +a program definition. Nor can a version number occur more than once +within the scope of a program definition. +.IP 3. +A procedure name cannot occur more than once within the scope +of a version definition. Nor can a procedure number occur more than +once within the scope of version definition. +.IP 4. +Program identifiers are in the same name space as constant and +type identifiers. +.IP 5. +Only unsigned constants can be assigned to programs, versions +and procedures. +.NH 1 +\&Port Mapper Program Protocol +.LP +The port mapper program maps RPC program and version numbers to +transport-specific port numbers. This program makes dynamic binding +of remote programs possible. +.LP +This is desirable because the range of reserved port numbers is very +small and the number of potential remote programs is very large. By +running only the port mapper on a reserved port, the port numbers of +other remote programs can be ascertained by querying the port mapper. +.LP +The port mapper also aids in broadcast RPC. A given RPC program will +usually have different port number bindings on different machines, so +there is no way to directly broadcast to all of these programs. The +port mapper, however, does have a fixed port number. So, to +broadcast to a given program, the client actually sends its message +to the port mapper located at the broadcast address. Each port +mapper that picks up the broadcast then calls the local service +specified by the client. When the port mapper gets the reply from +the local service, it sends the reply on back to the client. +.KS +.NH 2 +\&Port Mapper Protocol Specification (in RPC Language) +.ie t .DS +.el .DS L +.ft CW +.vs 11 +const PMAP_PORT = 111; /* \fIportmapper port number \fP*/ + +.ft I +/* +* A mapping of (program, version, protocol) to port number +*/ +.ft CW +struct mapping { + unsigned int prog; + unsigned int vers; + unsigned int prot; + unsigned int port; +}; + +.ft I +/* +* Supported values for the "prot" field +*/ +.ft CW +const IPPROTO_TCP = 6; /* \fIprotocol number for TCP/IP \fP*/ +const IPPROTO_UDP = 17; /* \fIprotocol number for UDP/IP \fP*/ + +.ft I +/* +* A list of mappings +*/ +.ft CW +struct *pmaplist { + mapping map; + pmaplist next; +}; +.vs +.DE +.ie t .DS +.el .DS L +.vs 11 +.ft I +/* +* Arguments to callit +*/ +.ft CW +struct call_args { + unsigned int prog; + unsigned int vers; + unsigned int proc; + opaque args<>; +}; + +.ft I +/* +* Results of callit +*/ +.ft CW +struct call_result { + unsigned int port; + opaque res<>; +}; +.vs +.DE +.KE +.ie t .DS +.el .DS L +.vs 11 +.ft I +/* +* Port mapper procedures +*/ +.ft CW +program PMAP_PROG { + version PMAP_VERS { + void + PMAPPROC_NULL(void) = 0; + + bool + PMAPPROC_SET(mapping) = 1; + + bool + PMAPPROC_UNSET(mapping) = 2; + + unsigned int + PMAPPROC_GETPORT(mapping) = 3; + + pmaplist + PMAPPROC_DUMP(void) = 4; + + call_result + PMAPPROC_CALLIT(call_args) = 5; + } = 2; +} = 100000; +.vs +.DE +.NH 2 +\&Port Mapper Operation +.LP +The portmapper program currently supports two protocols (UDP/IP and +TCP/IP). The portmapper is contacted by talking to it on assigned +port number 111 (SUNRPC [8]) on either of these protocols. The +following is a description of each of the portmapper procedures: +.IP \fBPMAPPROC_NULL:\fP +This procedure does no work. By convention, procedure zero of any +protocol takes no parameters and returns no results. +.IP \fBPMAPPROC_SET:\fP +When a program first becomes available on a machine, it registers +itself with the port mapper program on the same machine. The program +passes its program number "prog", version number "vers", transport +protocol number "prot", and the port "port" on which it awaits +service request. The procedure returns a boolean response whose +value is +.I TRUE +if the procedure successfully established the mapping and +.I FALSE +otherwise. The procedure refuses to establish +a mapping if one already exists for the tuple "(prog, vers, prot)". +.IP \fBPMAPPROC_UNSET:\fP +When a program becomes unavailable, it should unregister itself with +the port mapper program on the same machine. The parameters and +results have meanings identical to those of +.I PMAPPROC_SET . +The protocol and port number fields of the argument are ignored. +.IP \fBPMAPPROC_GETPORT:\fP +Given a program number "prog", version number "vers", and transport +protocol number "prot", this procedure returns the port number on +which the program is awaiting call requests. A port value of zeros +means the program has not been registered. The "port" field of the +argument is ignored. +.IP \fBPMAPPROC_DUMP:\fP +This procedure enumerates all entries in the port mapper's database. +The procedure takes no parameters and returns a list of program, +version, protocol, and port values. +.IP \fBPMAPPROC_CALLIT:\fP +This procedure allows a caller to call another remote procedure on +the same machine without knowing the remote procedure's port number. +It is intended for supporting broadcasts to arbitrary remote programs +via the well-known port mapper's port. The parameters "prog", +"vers", "proc", and the bytes of "args" are the program number, +version number, procedure number, and parameters of the remote +procedure. +.LP +.B Note: +.RS +.IP 1. +This procedure only sends a response if the procedure was +successfully executed and is silent (no response) otherwise. +.IP 2. +The port mapper communicates with the remote program using UDP/IP +only. +.RE +.LP +The procedure returns the remote program's port number, and the bytes +of results are the results of the remote procedure. +.bp +.NH 1 +\&References +.LP +[1] Birrell, Andrew D. & Nelson, Bruce Jay; "Implementing Remote +Procedure Calls"; XEROX CSL-83-7, October 1983. +.LP +[2] Cheriton, D.; "VMTP: Versatile Message Transaction Protocol", +Preliminary Version 0.3; Stanford University, January 1987. +.LP +[3] Diffie & Hellman; "New Directions in Cryptography"; IEEE +Transactions on Information Theory IT-22, November 1976. +.LP +[4] Harrenstien, K.; "Time Server", RFC 738; Information Sciences +Institute, October 1977. +.LP +[5] National Bureau of Standards; "Data Encryption Standard"; Federal +Information Processing Standards Publication 46, January 1977. +.LP +[6] Postel, J.; "Transmission Control Protocol - DARPA Internet +Program Protocol Specification", RFC 793; Information Sciences +Institute, September 1981. +.LP +[7] Postel, J.; "User Datagram Protocol", RFC 768; Information Sciences +Institute, August 1980. +.LP +[8] Reynolds, J. & Postel, J.; "Assigned Numbers", RFC 923; Information +Sciences Institute, October 1984. diff --git a/lib/libc/rpc/PSD.doc/rpcgen.ms b/lib/libc/rpc/PSD.doc/rpcgen.ms new file mode 100644 index 000000000000..b4e50e5d6f04 --- /dev/null +++ b/lib/libc/rpc/PSD.doc/rpcgen.ms @@ -0,0 +1,1299 @@ +.\" +.\" Must use -- tbl -- for this one +.\" +.\" @(#)rpcgen.ms 2.2 88/08/04 4.0 RPCSRC +.de BT +.if \\n%=1 .tl ''- % -'' +.. +.ND +.\" prevent excess underlining in nroff +.if n .fp 2 R +.OH '\fBrpcgen\fP Programming Guide''Page %' +.EH 'Page %''\fBrpcgen\fP Programming Guide' +.if \\n%=1 .bp +.SH +\&\fBrpcgen\fP Programming Guide +.NH 0 +\&The \fBrpcgen\fP Protocol Compiler +.IX rpcgen "" \fIrpcgen\fP "" PAGE MAJOR +.LP +.IX RPC "" "" \fIrpcgen\fP +The details of programming applications to use Remote Procedure Calls +can be overwhelming. Perhaps most daunting is the writing of the XDR +routines necessary to convert procedure arguments and results into +their network format and vice-versa. +.LP +Fortunately, +.I rpcgen(1) +exists to help programmers write RPC applications simply and directly. +.I rpcgen +does most of the dirty work, allowing programmers to debug +the main features of their application, instead of requiring them to +spend most of their time debugging their network interface code. +.LP +.I rpcgen +is a compiler. It accepts a remote program interface definition written +in a language, called RPC Language, which is similar to C. It produces a C +language output which includes stub versions of the client routines, a +server skeleton, XDR filter routines for both parameters and results, and a +header file that contains common definitions. The client stubs interface +with the RPC library and effectively hide the network from their callers. +The server stub similarly hides the network from the server procedures that +are to be invoked by remote clients. +.I rpcgen 's +output files can be compiled and linked in the usual way. The developer +writes server procedures\(emin any language that observes Sun calling +conventions\(emand links them with the server skeleton produced by +.I rpcgen +to get an executable server program. To use a remote program, a programmer +writes an ordinary main program that makes local procedure calls to the +client stubs produced by +.I rpcgen . +Linking this program with +.I rpcgen 's +stubs creates an executable program. (At present the main program must be +written in C). +.I rpcgen +options can be used to suppress stub generation and to specify the transport +to be used by the server stub. +.LP +Like all compilers, +.I rpcgen +reduces development time +that would otherwise be spent coding and debugging low-level routines. +All compilers, including +.I rpcgen , +do this at a small cost in efficiency +and flexibility. However, many compilers allow escape hatches for +programmers to mix low-level code with high-level code. +.I rpcgen +is no exception. In speed-critical applications, hand-written routines +can be linked with the +.I rpcgen +output without any difficulty. Also, one may proceed by using +.I rpcgen +output as a starting point, and then rewriting it as necessary. +(If you need a discussion of RPC programming without +.I rpcgen , +see the +.I "Remote Procedure Call Programming Guide)\. +.NH 1 +\&Converting Local Procedures into Remote Procedures +.IX rpcgen "local procedures" \fIrpcgen\fP +.IX rpcgen "remote procedures" \fIrpcgen\fP +.LP +Assume an application that runs on a single machine, one which we want +to convert to run over the network. Here we will demonstrate such a +conversion by way of a simple example\(ema program that prints a +message to the console: +.ie t .DS +.el .DS L +.ft I +/* + * printmsg.c: print a message on the console + */ +.ft CW +#include + +main(argc, argv) + int argc; + char *argv[]; +{ + char *message; + + if (argc < 2) { + fprintf(stderr, "usage: %s \en", argv[0]); + exit(1); + } + message = argv[1]; + + if (!printmessage(message)) { + fprintf(stderr, "%s: couldn't print your message\en", + argv[0]); + exit(1); + } + printf("Message Delivered!\en"); + exit(0); +} +.ft I +/* + * Print a message to the console. + * Return a boolean indicating whether the message was actually printed. + */ +.ft CW +printmessage(msg) + char *msg; +{ + FILE *f; + + f = fopen("/dev/console", "w"); + if (f == NULL) { + return (0); + } + fprintf(f, "%s\en", msg); + fclose(f); + return(1); +} +.DE +.LP +And then, of course: +.ie t .DS +.el .DS L +.ft CW +example% \fBcc printmsg.c -o printmsg\fP +example% \fBprintmsg "Hello, there."\fP +Message delivered! +example% +.DE +.LP +If +.I printmessage() +was turned into a remote procedure, +then it could be called from anywhere in the network. +Ideally, one would just like to stick a keyword like +.I remote +in front of a +procedure to turn it into a remote procedure. Unfortunately, +we have to live within the constraints of the C language, since +it existed long before RPC did. But even without language +support, it's not very difficult to make a procedure remote. +.LP +In general, it's necessary to figure out what the types are for +all procedure inputs and outputs. In this case, we have a +procedure +.I printmessage() +which takes a string as input, and returns an integer +as output. Knowing this, we can write a protocol specification in RPC +language that describes the remote version of +.I printmessage (). +Here it is: +.ie t .DS +.el .DS L +.ft I +/* + * msg.x: Remote message printing protocol + */ +.ft CW + +program MESSAGEPROG { + version MESSAGEVERS { + int PRINTMESSAGE(string) = 1; + } = 1; +} = 99; +.DE +.LP +Remote procedures are part of remote programs, so we actually declared +an entire remote program here which contains the single procedure +.I PRINTMESSAGE . +This procedure was declared to be in version 1 of the +remote program. No null procedure (procedure 0) is necessary because +.I rpcgen +generates it automatically. +.LP +Notice that everything is declared with all capital letters. This is +not required, but is a good convention to follow. +.LP +Notice also that the argument type is \*Qstring\*U and not \*Qchar *\*U. This +is because a \*Qchar *\*U in C is ambiguous. Programmers usually intend it +to mean a null-terminated string of characters, but it could also +represent a pointer to a single character or a pointer to an array of +characters. In RPC language, a null-terminated string is +unambiguously called a \*Qstring\*U. +.LP +There are just two more things to write. First, there is the remote +procedure itself. Here's the definition of a remote procedure +to implement the +.I PRINTMESSAGE +procedure we declared above: +.ie t .DS +.el .DS L +.vs 11 +.ft I +/* + * msg_proc.c: implementation of the remote procedure "printmessage" + */ +.ft CW + +#include +#include /* \fIalways needed\fP */ +#include "msg.h" /* \fIneed this too: msg.h will be generated by rpcgen\fP */ + +.ft I +/* + * Remote verson of "printmessage" + */ +.ft CW +int * +printmessage_1(msg) + char **msg; +{ + static int result; /* \fImust be static!\fP */ + FILE *f; + + f = fopen("/dev/console", "w"); + if (f == NULL) { + result = 0; + return (&result); + } + fprintf(f, "%s\en", *msg); + fclose(f); + result = 1; + return (&result); +} +.vs +.DE +.LP +Notice here that the declaration of the remote procedure +.I printmessage_1() +differs from that of the local procedure +.I printmessage() +in three ways: +.IP 1. +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. +.IP 2. +It returns a pointer to an integer instead of an integer itself. This is +also generally true of remote procedures: they always return a pointer +to their results. +.IP 3. +It has an \*Q_1\*U appended to its name. In general, all remote +procedures called by +.I rpcgen +are named by the following rule: the name in the program definition +(here +.I PRINTMESSAGE ) +is converted to all +lower-case letters, an underbar (\*Q_\*U) is appended to it, and +finally the version number (here 1) is appended. +.LP +The last thing to do is declare the main client program that will call +the remote procedure. Here it is: +.ie t .DS +.el .DS L +.ft I +/* + * rprintmsg.c: remote version of "printmsg.c" + */ +.ft CW +#include +#include /* \fIalways needed\fP */ +#include "msg.h" /* \fIneed this too: msg.h will be generated by rpcgen\fP */ + +main(argc, argv) + int argc; + char *argv[]; +{ + CLIENT *cl; + int *result; + char *server; + char *message; + + if (argc < 3) { + fprintf(stderr, "usage: %s host message\en", argv[0]); + exit(1); + } + +.ft I + /* + * Save values of command line arguments + */ +.ft CW + server = argv[1]; + message = argv[2]; + +.ft I + /* + * Create client "handle" used for calling \fIMESSAGEPROG\fP on the + * server designated on the command line. We tell the RPC package + * to use the "tcp" protocol when contacting the server. + */ +.ft CW + cl = clnt_create(server, MESSAGEPROG, MESSAGEVERS, "tcp"); + if (cl == NULL) { +.ft I + /* + * Couldn't establish connection with server. + * Print error message and die. + */ +.ft CW + clnt_pcreateerror(server); + exit(1); + } + +.ft I + /* + * Call the remote procedure "printmessage" on the server + */ +.ft CW + result = printmessage_1(&message, cl); + if (result == NULL) { +.ft I + /* + * An error occurred while calling the server. + * Print error message and die. + */ +.ft CW + clnt_perror(cl, server); + exit(1); + } + +.ft I + /* + * Okay, we successfully called the remote procedure. + */ +.ft CW + if (*result == 0) { +.ft I + /* + * Server was unable to print our message. + * Print error message and die. + */ +.ft CW + fprintf(stderr, "%s: %s couldn't print your message\en", + argv[0], server); + exit(1); + } + +.ft I + /* + * The message got printed on the server's console + */ +.ft CW + printf("Message delivered to %s!\en", server); +} +.DE +There are two things to note here: +.IP 1. +.IX "client handle, used by rpcgen" "" "client handle, used by \fIrpcgen\fP" +First a client \*Qhandle\*U is created using the RPC library routine +.I clnt_create (). +This client handle will be passed to the stub routines +which call the remote procedure. +.IP 2. +The remote procedure +.I printmessage_1() +is called exactly the same way as it is declared in +.I msg_proc.c +except for the inserted client handle as the first argument. +.LP +Here's how to put all of the pieces together: +.ie t .DS +.el .DS L +.ft CW +example% \fBrpcgen msg.x\fP +example% \fBcc rprintmsg.c msg_clnt.c -o rprintmsg\fP +example% \fBcc msg_proc.c msg_svc.c -o msg_server\fP +.DE +Two programs were compiled here: the client program +.I rprintmsg +and the server program +.I msg_server . +Before doing this though, +.I rpcgen +was used to fill in the missing pieces. +.LP +Here is what +.I rpcgen +did with the input file +.I msg.x : +.IP 1. +It created a header file called +.I msg.h +that contained +.I #define 's +for +.I MESSAGEPROG , +.I MESSAGEVERS +and +.I PRINTMESSAGE +for use in the other modules. +.IP 2. +It created client \*Qstub\*U routines in the +.I msg_clnt.c +file. In this case there is only one, the +.I printmessage_1() +that was referred to from the +.I printmsg +client program. The name of the output file for +client stub routines is always formed in this way: if the name of the +input file is +.I FOO.x , +the client stubs output file is called +.I FOO_clnt.c . +.IP 3. +It created the server program which calls +.I printmessage_1() +in +.I msg_proc.c . +This server program is named +.I msg_svc.c . +The rule for naming the server output file is similar to the +previous one: for an input file called +.I FOO.x , +the output server file is named +.I FOO_svc.c . +.LP +Now we're ready to have some fun. First, copy the server to a +remote machine and run it. For this example, the +machine is called \*Qmoon\*U. Server processes are run in the +background, because they never exit. +.ie t .DS +.el .DS L +.ft CW +moon% \fBmsg_server &\fP +.DE +Then on our local machine (\*Qsun\*U) we can print a message on \*Qmoon\*Us +console. +.ie t .DS +.el .DS L +.ft CW +sun% \fBprintmsg moon "Hello, moon."\fP +.DE +The message will get printed to \*Qmoon\*Us console. You can print a +message on anybody's console (including your own) with this program if +you are able to copy the server to their machine and run it. +.NH 1 +\&Generating XDR Routines +.IX RPC "generating XDR routines" +.LP +The previous example only demonstrated the automatic generation of +client and server RPC code. +.I rpcgen +may also be used to generate XDR routines, that is, the routines +necessary to convert local data +structures into network format and vice-versa. This example presents +a complete RPC service\(ema remote directory listing service, which uses +.I rpcgen +not only to generate stub routines, but also to generate the XDR +routines. Here is the protocol description file: +.ie t .DS +.el .DS L +.ft I +/* + * dir.x: Remote directory listing protocol + */ +.ft CW +const MAXNAMELEN = 255; /* \fImaximum length of a directory entry\fP */ + +typedef string nametype; /* \fIa directory entry\fP */ + +typedef struct namenode *namelist; /* \fIa link in the listing\fP */ + +.ft I +/* + * A node in the directory listing + */ +.ft CW +struct namenode { + nametype name; /* \fIname of directory entry\fP */ + namelist next; /* \fInext entry\fP */ +}; + +.ft I +/* + * The result of a READDIR operation. + */ +.ft CW +union readdir_res switch (int errno) { +case 0: + namelist list; /* \fIno error: return directory listing\fP */ +default: + void; /* \fIerror occurred: nothing else to return\fP */ +}; + +.ft I +/* + * The directory program definition + */ +.ft CW +program DIRPROG { + version DIRVERS { + readdir_res + READDIR(nametype) = 1; + } = 1; +} = 76; +.DE +.SH +Note: +.I +Types (like +.I readdir_res +in the example above) can be defined using +the \*Qstruct\*U, \*Qunion\*U and \*Qenum\*U keywords, but those keywords +should not be used in subsequent declarations of variables of those types. +For example, if you define a union \*Qfoo\*U, you should declare using +only \*Qfoo\*U and not \*Qunion foo\*U. In fact, +.I rpcgen +compiles +RPC unions into C structures and it is an error to declare them using the +\*Qunion\*U keyword. +.LP +Running +.I rpcgen +on +.I dir.x +creates four output files. Three are the same as before: header file, +client stub routines and server skeleton. The fourth are the XDR routines +necessary for converting the data types we declared into XDR format and +vice-versa. These are output in the file +.I dir_xdr.c . +.LP +Here is the implementation of the +.I READDIR +procedure. +.ie t .DS +.el .DS L +.vs 11 +.ft I +/* + * dir_proc.c: remote readdir implementation + */ +.ft CW +#include +#include +#include "dir.h" + +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; /* \fImust be static\fP! */ + +.ft I + /* + * Open directory + */ +.ft CW + dirp = opendir(*dirname); + if (dirp == NULL) { + res.errno = errno; + return (&res); + } + +.ft I + /* + * Free previous result + */ +.ft CW + xdr_free(xdr_readdir_res, &res); + +.ft I + /* + * Collect directory entries. + * Memory allocated here will be freed by \fIxdr_free\fP + * next time \fIreaddir_1\fP is called + */ +.ft CW + 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; + +.ft I + /* + * Return the result + */ +.ft CW + res.errno = 0; + closedir(dirp); + return (&res); +} +.vs +.DE +Finally, there is the client side program to call the server: +.ie t .DS +.el .DS L +.ft I +/* + * rls.c: Remote directory listing client + */ +.ft CW +#include +#include /* \fIalways need this\fP */ +#include "dir.h" /* \fIwill be generated by rpcgen\fI */ + +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\en", + argv[0]); + exit(1); + } + +.ft I + /* + * Remember what our command line arguments refer to + */ +.ft CW + server = argv[1]; + dir = argv[2]; + +.ft I + /* + * Create client "handle" used for calling \fIMESSAGEPROG\fP on the + * server designated on the command line. We tell the RPC package + * to use the "tcp" protocol when contacting the server. + */ +.ft CW + cl = clnt_create(server, DIRPROG, DIRVERS, "tcp"); + if (cl == NULL) { +.ft I + /* + * Couldn't establish connection with server. + * Print error message and die. + */ +.ft CW + clnt_pcreateerror(server); + exit(1); + } + +.ft I + /* + * Call the remote procedure \fIreaddir\fP on the server + */ +.ft CW + result = readdir_1(&dir, cl); + if (result == NULL) { +.ft I + /* + * An error occurred while calling the server. + * Print error message and die. + */ +.ft CW + clnt_perror(cl, server); + exit(1); + } + +.ft I + /* + * Okay, we successfully called the remote procedure. + */ +.ft CW + if (result->errno != 0) { +.ft I + /* + * A remote system error occurred. + * Print error message and die. + */ +.ft CW + errno = result->errno; + perror(dir); + exit(1); + } + +.ft I + /* + * Successfully got a directory listing. + * Print it out. + */ +.ft CW + for (nl = result->readdir_res_u.list; nl != NULL; + nl = nl->next) { + printf("%s\en", nl->name); + } + exit(0); +} +.DE +Compile everything, and run. +.DS +.ft CW +sun% \fBrpcgen dir.x\fP +sun% \fBcc rls.c dir_clnt.c dir_xdr.c -o rls\fP +sun% \fBcc dir_svc.c dir_proc.c dir_xdr.c -o dir_svc\fP + +sun% \fBdir_svc &\fP + +moon% \fBrls sun /usr/pub\fP +\&. +\&.. +ascii +eqnchar +greek +kbd +marg8 +tabclr +tabs +tabs4 +moon% +.DE +.LP +.IX "debugging with rpcgen" "" "debugging with \fIrpcgen\fP" +A final note about +.I rpcgen : +The client program and the server procedure can be tested together +as a single program by simply linking them with each other rather +than with the client and server stubs. The procedure calls will be +executed as ordinary local procedure calls and the program can be +debugged with a local debugger such as +.I dbx . +When the program is working, the client program can be linked to +the client stub produced by +.I rpcgen +and the server procedures can be linked to the server stub produced +by +.I rpcgen . +.SH +.I NOTE : +\fIIf you do this, you may want to comment out calls to RPC library +routines, and have client-side routines call server routines +directly.\fP +.LP +.NH 1 +\&The C-Preprocessor +.IX rpcgen "C-preprocessor" \fIrpcgen\fP +.LP +The C-preprocessor is run on all input files before they are +compiled, so all the preprocessor directives are legal within a \*Q.x\*U +file. Four symbols may be defined, depending upon which output file is +getting generated. The symbols are: +.TS +box tab (&); +lfI lfI +lfL l . +Symbol&Usage +_ +RPC_HDR&for header-file output +RPC_XDR&for XDR routine output +RPC_SVC&for server-skeleton output +RPC_CLNT&for client stub output +.TE +.LP +Also, +.I rpcgen +does a little preprocessing of its own. Any line that +begins with a percent sign is passed directly into the output file, +without any interpretation of the line. Here is a simple example that +demonstrates the preprocessing features. +.ie t .DS +.el .DS L +.ft I +/* + * time.x: Remote time protocol + */ +.ft CW +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 +.DE +The '%' feature is not generally recommended, as there is no guarantee +that the compiler will stick the output where you intended. +.NH 1 +\&\fBrpcgen\fP Programming Notes +.IX rpcgen "other operations" \fIrpcgen\fP +.sp +.NH 2 +\&Timeout Changes +.IX rpcgen "timeout changes" \fIrpcgen\fP +.LP +RPC sets a default timeout of 25 seconds for RPC calls when +.I clnt_create() +is used. This timeout may be changed using +.I clnt_control() +Here is a small code fragment to demonstrate use of +.I clnt_control (): +.ID +struct timeval tv; +CLIENT *cl; +.sp .5 +cl = clnt_create("somehost", SOMEPROG, SOMEVERS, "tcp"); +if (cl == NULL) { + exit(1); +} +tv.tv_sec = 60; /* \fIchange timeout to 1 minute\fP */ +tv.tv_usec = 0; +clnt_control(cl, CLSET_TIMEOUT, &tv); +.DE +.NH 2 +\&Handling Broadcast on the Server Side +.IX "broadcast RPC" +.IX rpcgen "broadcast RPC" \fIrpcgen\fP +.LP +When a procedure is known to be called via broadcast RPC, +it is usually wise for the server to not reply unless it can provide +some useful information to the client. This prevents the network +from getting flooded by useless replies. +.LP +To prevent the server from replying, a remote procedure can +return NULL as its result, and the server code generated by +.I rpcgen +will detect this and not send out a reply. +.LP +Here is an example of a procedure that replies only if it +thinks it is an NFS server: +.ID +void * +reply_if_nfsserver() +{ + char notnull; /* \fIjust here so we can use its address\fP */ +.sp .5 + if (access("/etc/exports", F_OK) < 0) { + return (NULL); /* \fIprevent RPC from replying\fP */ + } +.ft I + /* + * return non-null pointer so RPC will send out a reply + */ +.ft L + return ((void *)¬null); +} +.DE +Note that if procedure returns type \*Qvoid *\*U, they must return a non-NULL +pointer if they want RPC to reply for them. +.NH 2 +\&Other Information Passed to Server Procedures +.LP +Server procedures will often want to know more about an RPC call +than just its arguments. For example, getting authentication information +is important to procedures that want to implement some level of security. +This extra information is actually supplied to the server procedure as a +second argument. Here is an example to demonstrate its use. What we've +done here is rewrite the previous +.I printmessage_1() +procedure to only allow root users to print a message to the console. +.ID +int * +printmessage_1(msg, rq) + char **msg; + struct svc_req *rq; +{ + static in result; /* \fIMust be static\fP */ + FILE *f; + struct suthunix_parms *aup; +.sp .5 + aup = (struct authunix_parms *)rq->rq_clntcred; + if (aup->aup_uid != 0) { + result = 0; + return (&result); + } +.sp +.ft I + /* + * Same code as before. + */ +.ft L +} +.DE +.NH 1 +\&RPC Language +.IX RPCL +.IX rpcgen "RPC Language" \fIrpcgen\fP +.LP +RPC language is an extension of XDR language. The sole extension is +the addition of the +.I program +type. For a complete description of the XDR language syntax, see the +.I "External Data Representation Standard: Protocol Specification" +chapter. For a description of the RPC extensions to the XDR language, +see the +.I "Remote Procedure Calls: Protocol Specification" +chapter. +.LP +However, XDR language is so close to C that if you know C, you know most +of it already. We describe here the syntax of the RPC language, +showing a few examples along the way. We also show how the various +RPC and XDR type definitions get compiled into C type definitions in +the output header file. +.KS +.NH 2 +Definitions +\& +.IX rpcgen definitions \fIrpcgen\fP +.LP +An RPC language file consists of a series of definitions. +.DS L +.ft CW + definition-list: + definition ";" + definition ";" definition-list +.DE +.KE +It recognizes five types of definitions. +.DS L +.ft CW + definition: + enum-definition + struct-definition + union-definition + typedef-definition + const-definition + program-definition +.DE +.NH 2 +Structures +\& +.IX rpcgen structures \fIrpcgen\fP +.LP +An XDR struct is declared almost exactly like its C counterpart. It +looks like the following: +.DS L +.ft CW + struct-definition: + "struct" struct-ident "{" + declaration-list + "}" + + declaration-list: + declaration ";" + declaration ";" declaration-list +.DE +As an example, here is an XDR structure to a two-dimensional +coordinate, and the C structure that it gets compiled into in the +output header file. +.DS +.ft CW + struct coord { struct coord { + int x; --> int x; + int y; int y; + }; }; + typedef struct coord coord; +.DE +The output is identical to the input, except for the added +.I typedef +at the end of the output. This allows one to use \*Qcoord\*U instead of +\*Qstruct coord\*U when declaring items. +.NH 2 +Unions +\& +.IX rpcgen unions \fIrpcgen\fP +.LP +XDR unions are discriminated unions, and look quite different from C +unions. They are more analogous to Pascal variant records than they +are to C unions. +.DS L +.ft CW + union-definition: + "union" union-ident "switch" "(" declaration ")" "{" + case-list + "}" + + case-list: + "case" value ":" declaration ";" + "default" ":" declaration ";" + "case" value ":" declaration ";" case-list +.DE +Here is an example of a type that might be returned as the result of a +\*Qread data\*U operation. If there is no error, return a block of data. +Otherwise, don't return anything. +.DS L +.ft CW + union read_result switch (int errno) { + case 0: + opaque data[1024]; + default: + void; + }; +.DE +It gets compiled into the following: +.DS L +.ft CW + struct read_result { + int errno; + union { + char data[1024]; + } read_result_u; + }; + typedef struct read_result read_result; +.DE +Notice that the union component of the output struct has the name as +the type name, except for the trailing \*Q_u\*U. +.NH 2 +Enumerations +\& +.IX rpcgen enumerations \fIrpcgen\fP +.LP +XDR enumerations have the same syntax as C enumerations. +.DS L +.ft CW + 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 +.DE +Here is a short example of an XDR enum, and the C enum that it gets +compiled into. +.DS L +.ft CW + enum colortype { enum colortype { + RED = 0, RED = 0, + GREEN = 1, --> GREEN = 1, + BLUE = 2 BLUE = 2, + }; }; + typedef enum colortype colortype; +.DE +.NH 2 +Typedef +\& +.IX rpcgen typedef \fIrpcgen\fP +.LP +XDR typedefs have the same syntax as C typedefs. +.DS L +.ft CW + typedef-definition: + "typedef" declaration +.DE +Here is an example that defines a +.I fname_type +used for declaring +file name strings that have a maximum length of 255 characters. +.DS L +.ft CW +typedef string fname_type<255>; --> typedef char *fname_type; +.DE +.NH 2 +Constants +\& +.IX rpcgen constants \fIrpcgen\fP +.LP +XDR constants symbolic constants that may be used wherever a +integer constant is used, for example, in array size specifications. +.DS L +.ft CW + const-definition: + "const" const-ident "=" integer +.DE +For example, the following defines a constant +.I DOZEN +equal to 12. +.DS L +.ft CW + const DOZEN = 12; --> #define DOZEN 12 +.DE +.NH 2 +Programs +\& +.IX rpcgen programs \fIrpcgen\fP +.LP +RPC programs are declared using the following syntax: +.DS L +.ft CW + 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 +.DE +For example, here is the time protocol, revisited: +.ie t .DS +.el .DS L +.ft I +/* + * time.x: Get or set the time. Time is represented as number of seconds + * since 0:00, January 1, 1970. + */ +.ft CW +program TIMEPROG { + version TIMEVERS { + unsigned int TIMEGET(void) = 1; + void TIMESET(unsigned) = 2; + } = 1; +} = 44; +.DE +This file compiles into #defines in the output header file: +.ie t .DS +.el .DS L +.ft CW +#define TIMEPROG 44 +#define TIMEVERS 1 +#define TIMEGET 1 +#define TIMESET 2 +.DE +.NH 2 +Declarations +\& +.IX rpcgen declarations \fIrpcgen\fP +.LP +In XDR, there are only four kinds of declarations. +.DS L +.ft CW + declaration: + simple-declaration + fixed-array-declaration + variable-array-declaration + pointer-declaration +.DE +\fB1) Simple declarations\fP are just like simple C declarations. +.DS L +.ft CW + simple-declaration: + type-ident variable-ident +.DE +Example: +.DS L +.ft CW + colortype color; --> colortype color; +.DE +\fB2) Fixed-length Array Declarations\fP are just like C array declarations: +.DS L +.ft CW + fixed-array-declaration: + type-ident variable-ident "[" value "]" +.DE +Example: +.DS L +.ft CW + colortype palette[8]; --> colortype palette[8]; +.DE +\fB3) Variable-Length Array Declarations\fP have no explicit syntax +in C, so XDR invents its own using angle-brackets. +.DS L +.ft CW +variable-array-declaration: + type-ident variable-ident "<" value ">" + type-ident variable-ident "<" ">" +.DE +The maximum size is specified between the angle brackets. The size may +be omitted, indicating that the array may be of any size. +.DS L +.ft CW + int heights<12>; /* \fIat most 12 items\fP */ + int widths<>; /* \fIany number of items\fP */ +.DE +Since variable-length arrays have no explicit syntax in C, these +declarations are actually compiled into \*Qstruct\*Us. For example, the +\*Qheights\*U declaration gets compiled into the following struct: +.DS L +.ft CW + struct { + u_int heights_len; /* \fI# of items in array\fP */ + int *heights_val; /* \fIpointer to array\fP */ + } heights; +.DE +Note that the number of items in the array is stored in the \*Q_len\*U +component and the pointer to the array is stored in the \*Q_val\*U +component. The first part of each of these component's names is the +same as the name of the declared XDR variable. +.LP +\fB4) Pointer Declarations\fP are made in +XDR exactly as they are in C. You can't +really send pointers over the network, but you can use XDR pointers +for sending recursive data types such as lists and trees. The type is +actually called \*Qoptional-data\*U, not \*Qpointer\*U, in XDR language. +.DS L +.ft CW + pointer-declaration: + type-ident "*" variable-ident +.DE +Example: +.DS L +.ft CW + listitem *next; --> listitem *next; +.DE +.NH 2 +\&Special Cases +.IX rpcgen "special cases" \fIrpcgen\fP +.LP +There are a few exceptions to the rules described above. +.LP +.B Booleans: +C has no built-in boolean type. However, the RPC library does a +boolean type called +.I bool_t +that is either +.I TRUE +or +.I FALSE . +Things declared as type +.I bool +in XDR language are compiled into +.I bool_t +in the output header file. +.LP +Example: +.DS L +.ft CW + bool married; --> bool_t married; +.DE +.B Strings: +C has no built-in string type, but instead uses the null-terminated +\*Qchar *\*U convention. In XDR language, strings are declared using the +\*Qstring\*U keyword, and compiled into \*Qchar *\*Us in the output header +file. The maximum size contained in the angle brackets specifies the +maximum number of characters allowed in the strings (not counting the +.I NULL +character). The maximum size may be left off, indicating a string +of arbitrary length. +.LP +Examples: +.DS L +.ft CW + string name<32>; --> char *name; + string longname<>; --> char *longname; +.DE +.B "Opaque Data:" +Opaque data is used in RPC and XDR to describe untyped data, that is, +just sequences of arbitrary bytes. It may be declared either as a +fixed or variable length array. +.DS L +Examples: +.ft CW + opaque diskblock[512]; --> char diskblock[512]; + + opaque filedata<1024>; --> struct { + u_int filedata_len; + char *filedata_val; + } filedata; +.DE +.B Voids: +In a void declaration, the variable is not named. The declaration is +just \*Qvoid\*U and nothing else. Void declarations can only occur in two +places: union definitions and program definitions (as the argument or +result of a remote procedure). diff --git a/lib/libc/rpc/PSD.doc/xdr.nts.ms b/lib/libc/rpc/PSD.doc/xdr.nts.ms new file mode 100644 index 000000000000..6c2d482dea7f --- /dev/null +++ b/lib/libc/rpc/PSD.doc/xdr.nts.ms @@ -0,0 +1,1966 @@ +.\" +.\" Must use -- eqn -- with this one +.\" +.\" @(#)xdr.nts.ms 2.2 88/08/05 4.0 RPCSRC +.EQ +delim $$ +.EN +.de BT +.if \\n%=1 .tl ''- % -'' +.. +.ND +.\" prevent excess underlining in nroff +.if n .fp 2 R +.OH 'External Data Representation: Sun Technical Notes''Page %' +.EH 'Page %''External Data Representation: Sun Technical Notes' +.if \\n%=1 .bp +.SH +\&External Data Representation: Sun Technical Notes +.IX XDR "Sun technical notes" +.LP +This chapter contains technical notes on Sun's implementation of the +External Data Representation (XDR) standard, a set of library routines +that allow a C programmer to describe arbitrary data structures in a +machinex-independent fashion. +For a formal specification of the XDR +standard, see the +.I "External Data Representation Standard: Protocol Specification". +XDR is the backbone of Sun's Remote Procedure Call package, in the +sense that data for remote procedure calls is transmitted using the +standard. XDR library routines should be used to transmit data +that is accessed (read or written) by more than one type of machine.\** +.FS +.IX XDR "system routines" +For a compete specification of the system External Data Representation +routines, see the +.I xdr(3N) +manual page. +.FE +.LP +This chapter contains a short tutorial overview of the XDR library +routines, a guide to accessing currently available XDR streams, and +information on defining new streams and data types. XDR was designed +to work across different languages, operating systems, and machine +architectures. Most users (particularly RPC users) will only need +the information in the +.I "Number Filters", +.I "Floating Point Filters", +and +.I "Enumeration Filters" +sections. +Programmers wishing to implement RPC and XDR on new machines +will be interested in the rest of the chapter, as well as the +.I "External Data Representaiton Standard: Protocol Specification", +which will be their primary reference. +.SH +Note: +.I +.I rpcgen +can be used to write XDR routines even in cases where no RPC calls are +being made. +.LP +On Sun systems, +C programs that want to use XDR routines +must include the file +.I , +which contains all the necessary interfaces to the XDR system. +Since the C library +.I libc.a +contains all the XDR routines, +compile as normal. +.DS +example% \fBcc\0\fIprogram\fP.c\fI +.DE +.ne 3i +.NH 0 +\&Justification +.IX XDR justification +.LP +Consider the following two programs, +.I writer : +.ie t .DS +.el .DS L +.ft CW +#include +.sp.5 +main() /* \fIwriter.c\fP */ +{ + long i; +.sp.5 + for (i = 0; i < 8; i++) { + if (fwrite((char *)&i, sizeof(i), 1, stdout) != 1) { + fprintf(stderr, "failed!\en"); + exit(1); + } + } + exit(0); +} +.DE +and +.I reader : +.ie t .DS +.el .DS L +.ft CW +#include +.sp.5 +main() /* \fIreader.c\fP */ +{ + long i, j; +.sp.5 + for (j = 0; j < 8; j++) { + if (fread((char *)&i, sizeof (i), 1, stdin) != 1) { + fprintf(stderr, "failed!\en"); + exit(1); + } + printf("%ld ", i); + } + printf("\en"); + exit(0); +} +.DE +The two programs appear to be portable, because (a) they pass +.I lint +checking, and (b) they exhibit the same behavior when executed +on two different hardware architectures, a Sun and a VAX. +.LP +Piping the output of the +.I writer +program to the +.I reader +program gives identical results on a Sun or a VAX. +.DS +.ft CW +sun% \fBwriter | reader\fP +0 1 2 3 4 5 6 7 +sun% + + +vax% \fBwriter | reader\fP +0 1 2 3 4 5 6 7 +vax% +.DE +With the advent of local area networks and 4.2BSD came the concept +of \*Qnetwork pipes\*U \(em a process produces data on one machine, +and a second process consumes data on another machine. +A network pipe can be constructed with +.I writer +and +.I reader . +Here are the results if the first produces data on a Sun, +and the second consumes data on a VAX. +.DS +.ft CW +sun% \fBwriter | rsh vax reader\fP +0 16777216 33554432 50331648 67108864 83886080 100663296 +117440512 +sun% +.DE +Identical results can be obtained by executing +.I writer +on the VAX and +.I reader +on the Sun. These results occur because the byte ordering +of long integers differs between the VAX and the Sun, +even though word size is the same. +Note that $16777216$ is $2 sup 24$ \(em +when four bytes are reversed, the 1 winds up in the 24th bit. +.LP +Whenever data is shared by two or more machine types, there is +a need for portable data. Programs can be made data-portable by +replacing the +.I read() +and +.I write() +calls with calls to an XDR library routine +.I xdr_long() , +a filter that knows the standard representation +of a long integer in its external form. +Here are the revised versions of +.I writer : +.ie t .DS +.el .DS L +.ft CW +#include +#include /* \fIxdr is a sub-library of rpc\fP */ +.sp.5 +main() /* \fIwriter.c\fP */ +{ + XDR xdrs; + long i; +.sp.5 + xdrstdio_create(&xdrs, stdout, XDR_ENCODE); + for (i = 0; i < 8; i++) { + if (!xdr_long(&xdrs, &i)) { + fprintf(stderr, "failed!\en"); + exit(1); + } + } + exit(0); +} +.DE +and +.I reader : +.ie t .DS +.el .DS L +.ft CW +#include +#include /* \fIxdr is a sub-library of rpc\fP */ +.sp.5 +main() /* \fIreader.c\fP */ +{ + XDR xdrs; + long i, j; +.sp.5 + xdrstdio_create(&xdrs, stdin, XDR_DECODE); + for (j = 0; j < 8; j++) { + if (!xdr_long(&xdrs, &i)) { + fprintf(stderr, "failed!\en"); + exit(1); + } + printf("%ld ", i); + } + printf("\en"); + exit(0); +} +.DE +The new programs were executed on a Sun, +on a VAX, and from a Sun to a VAX; +the results are shown below. +.DS +.ft CW +sun% \fBwriter | reader\fP +0 1 2 3 4 5 6 7 +sun% + +vax% \fBwriter | reader\fP +0 1 2 3 4 5 6 7 +vax% + +sun% \fBwriter | rsh vax reader\fP +0 1 2 3 4 5 6 7 +sun% +.DE +.SH +Note: +.I +.IX XDR "portable data" +Integers are just the tip of the portable-data iceberg. Arbitrary +data structures present portability problems, particularly with +respect to alignment and pointers. Alignment on word boundaries +may cause the size of a structure to vary from machine to machine. +And pointers, which are very convenient to use, have no meaning +outside the machine where they are defined. +.LP +.NH 1 +\&A Canonical Standard +.IX XDR "canonical standard" +.LP +XDR's approach to standardizing data representations is +.I canonical . +That is, XDR defines a single byte order (Big Endian), a single +floating-point representation (IEEE), and so on. Any program running on +any machine can use XDR to create portable data by translating its +local representation to the XDR standard representations; similarly, any +program running on any machine can read portable data by translating the +XDR standard representaions to its local equivalents. The single standard +completely decouples programs that create or send portable data from those +that use or receive portable data. The advent of a new machine or a new +language has no effect upon the community of existing portable data creators +and users. A new machine joins this community by being \*Qtaught\*U how to +convert the standard representations and its local representations; the +local representations of other machines are irrelevant. Conversely, to +existing programs running on other machines, the local representations of +the new machine are also irrelevant; such programs can immediately read +portable data produced by the new machine because such data conforms to the +canonical standards that they already understand. +.LP +There are strong precedents for XDR's canonical approach. For example, +TCP/IP, UDP/IP, XNS, Ethernet, and, indeed, all protocols below layer five +of the ISO model, are canonical protocols. The advantage of any canonical +approach is simplicity; in the case of XDR, a single set of conversion +routines is written once and is never touched again. The canonical approach +has a disadvantage, but it is unimportant in real-world data transfer +applications. Suppose two Little-Endian machines are transferring integers +according to the XDR standard. The sending machine converts the integers +from Little-Endian byte order to XDR (Big-Endian) byte order; the receiving +machine performs the reverse conversion. Because both machines observe the +same byte order, their conversions are unnecessary. The point, however, is +not necessity, but cost as compared to the alternative. +.LP +The time spent converting to and from a canonical representation is +insignificant, especially in networking applications. Most of the time +required to prepare a data structure for transfer is not spent in conversion +but in traversing the elements of the data structure. To transmit a tree, +for example, each leaf must be visited and each element in a leaf record must +be copied to a buffer and aligned there; storage for the leaf may have to be +deallocated as well. Similarly, to receive a tree, storage must be +allocated for each leaf, data must be moved from the buffer to the leaf and +properly aligned, and pointers must be constructed to link the leaves +together. Every machine pays the cost of traversing and copying data +structures whether or not conversion is required. In networking +applications, communications overhead\(emthe time required to move the data +down through the sender's protocol layers, across the network and up through +the receiver's protocol layers\(emdwarfs conversion overhead. +.NH 1 +\&The XDR Library +.IX "XDR" "library" +.LP +The XDR library not only solves data portability problems, it also +allows you to write and read arbitrary C constructs in a consistent, +specified, well-documented manner. Thus, it can make sense to use the +library even when the data is not shared among machines on a network. +.LP +The XDR library has filter routines for +strings (null-terminated arrays of bytes), +structures, unions, and arrays, to name a few. +Using more primitive routines, +you can write your own specific XDR routines +to describe arbitrary data structures, +including elements of arrays, arms of unions, +or objects pointed at from other structures. +The structures themselves may contain arrays of arbitrary elements, +or pointers to other structures. +.LP +Let's examine the two programs more closely. +There is a family of XDR stream creation routines +in which each member treats the stream of bits differently. +In our example, data is manipulated using standard I/O routines, +so we use +.I xdrstdio_create (). +.IX xdrstdio_create() "" "\fIxdrstdio_create()\fP" +The parameters to XDR stream creation routines +vary according to their function. +In our example, +.I xdrstdio_create() +takes a pointer to an XDR structure that it initializes, +a pointer to a +.I FILE +that the input or output is performed on, and the operation. +The operation may be +.I XDR_ENCODE +for serializing in the +.I writer +program, or +.I XDR_DECODE +for deserializing in the +.I reader +program. +.LP +Note: RPC users never need to create XDR streams; +the RPC system itself creates these streams, +which are then passed to the users. +.LP +The +.I xdr_long() +.IX xdr_long() "" "\fIxdr_long()\fP" +primitive is characteristic of most XDR library +primitives and all client XDR routines. +First, the routine returns +.I FALSE +(0) if it fails, and +.I TRUE +(1) if it succeeds. +Second, for each data type, +.I xxx , +there is an associated XDR routine of the form: +.DS +.ft CW +xdr_xxx(xdrs, xp) + XDR *xdrs; + xxx *xp; +{ +} +.DE +In our case, +.I xxx +is long, and the corresponding XDR routine is +a primitive, +.I xdr_long() . +The client could also define an arbitrary structure +.I xxx +in which case the client would also supply the routine +.I xdr_xxx (), +describing each field by calling XDR routines +of the appropriate type. +In all cases the first parameter, +.I xdrs +can be treated as an opaque handle, +and passed to the primitive routines. +.LP +XDR routines are direction independent; +that is, the same routines are called to serialize or deserialize data. +This feature is critical to software engineering of portable data. +The idea is to call the same routine for either operation \(em +this almost guarantees that serialized data can also be deserialized. +One routine is used by both producer and consumer of networked data. +This is implemented by always passing the address +of an object rather than the object itself \(em +only in the case of deserialization is the object modified. +This feature is not shown in our trivial example, +but its value becomes obvious when nontrivial data structures +are passed among machines. +If needed, the user can obtain the +direction of the XDR operation. +See the +.I "XDR Operation Directions" +section below for details. +.LP +Let's look at a slightly more complicated example. +Assume that a person's gross assets and liabilities +are to be exchanged among processes. +Also assume that these values are important enough +to warrant their own data type: +.ie t .DS +.el .DS L +.ft CW +struct gnumbers { + long g_assets; + long g_liabilities; +}; +.DE +The corresponding XDR routine describing this structure would be: +.ie t .DS +.el .DS L +.ft CW +bool_t /* \fITRUE is success, FALSE is failure\fP */ +xdr_gnumbers(xdrs, gp) + XDR *xdrs; + struct gnumbers *gp; +{ + if (xdr_long(xdrs, &gp->g_assets) && + xdr_long(xdrs, &gp->g_liabilities)) + return(TRUE); + return(FALSE); +} +.DE +Note that the parameter +.I xdrs +is never inspected or modified; +it is only passed on to the subcomponent routines. +It is imperative to inspect the return value of each XDR routine call, +and to give up immediately and return +.I FALSE +if the subroutine fails. +.LP +This example also shows that the type +.I bool_t +is declared as an integer whose only values are +.I TRUE +(1) and +.I FALSE +(0). This document uses the following definitions: +.ie t .DS +.el .DS L +.ft CW +#define bool_t int +#define TRUE 1 +#define FALSE 0 +.DE +.LP +Keeping these conventions in mind, +.I xdr_gnumbers() +can be rewritten as follows: +.ie t .DS +.el .DS L +.ft CW +xdr_gnumbers(xdrs, gp) + XDR *xdrs; + struct gnumbers *gp; +{ + return(xdr_long(xdrs, &gp->g_assets) && + xdr_long(xdrs, &gp->g_liabilities)); +} +.DE +This document uses both coding styles. +.NH 1 +\&XDR Library Primitives +.IX "library primitives for XDR" +.IX XDR "library primitives" +.LP +This section gives a synopsis of each XDR primitive. +It starts with basic data types and moves on to constructed data types. +Finally, XDR utilities are discussed. +The interface to these primitives +and utilities is defined in the include file +.I , +automatically included by +.I . +.NH 2 +\&Number Filters +.IX "XDR library" "number filters" +.LP +The XDR library provides primitives to translate between numbers +and their corresponding external representations. +Primitives cover the set of numbers in: +.DS +.ft CW +[signed, unsigned] * [short, int, long] +.DE +.ne 2i +Specifically, the eight primitives are: +.DS +.ft CW +bool_t xdr_char(xdrs, cp) + XDR *xdrs; + char *cp; +.sp.5 +bool_t xdr_u_char(xdrs, ucp) + XDR *xdrs; + unsigned char *ucp; +.sp.5 +bool_t xdr_int(xdrs, ip) + XDR *xdrs; + int *ip; +.sp.5 +bool_t xdr_u_int(xdrs, up) + XDR *xdrs; + unsigned *up; +.sp.5 +bool_t xdr_long(xdrs, lip) + XDR *xdrs; + long *lip; +.sp.5 +bool_t xdr_u_long(xdrs, lup) + XDR *xdrs; + u_long *lup; +.sp.5 +bool_t xdr_short(xdrs, sip) + XDR *xdrs; + short *sip; +.sp.5 +bool_t xdr_u_short(xdrs, sup) + XDR *xdrs; + u_short *sup; +.DE +The first parameter, +.I xdrs , +is an XDR stream handle. +The second parameter is the address of the number +that provides data to the stream or receives data from it. +All routines return +.I TRUE +if they complete successfully, and +.I FALSE +otherwise. +.NH 2 +\&Floating Point Filters +.IX "XDR library" "floating point filters" +.LP +The XDR library also provides primitive routines +for C's floating point types: +.DS +.ft CW +bool_t xdr_float(xdrs, fp) + XDR *xdrs; + float *fp; +.sp.5 +bool_t xdr_double(xdrs, dp) + XDR *xdrs; + double *dp; +.DE +The first parameter, +.I xdrs +is an XDR stream handle. +The second parameter is the address +of the floating point number that provides data to the stream +or receives data from it. +Both routines return +.I TRUE +if they complete successfully, and +.I FALSE +otherwise. +.LP +Note: Since the numbers are represented in IEEE floating point, +routines may fail when decoding a valid IEEE representation +into a machine-specific representation, or vice-versa. +.NH 2 +\&Enumeration Filters +.IX "XDR library" "enumeration filters" +.LP +The XDR library provides a primitive for generic enumerations. +The primitive assumes that a C +.I enum +has the same representation inside the machine as a C integer. +The boolean type is an important instance of the +.I enum . +The external representation of a boolean is always +.I TRUE +(1) or +.I FALSE +(0). +.DS +.ft CW +#define bool_t int +#define FALSE 0 +#define TRUE 1 +.sp.5 +#define enum_t int +.sp.5 +bool_t xdr_enum(xdrs, ep) + XDR *xdrs; + enum_t *ep; +.sp.5 +bool_t xdr_bool(xdrs, bp) + XDR *xdrs; + bool_t *bp; +.DE +The second parameters +.I ep +and +.I bp +are addresses of the associated type that provides data to, or +receives data from, the stream +.I xdrs . +.NH 2 +\&No Data +.IX "XDR library" "no data" +.LP +Occasionally, an XDR routine must be supplied to the RPC system, +even when no data is passed or required. +The library provides such a routine: +.DS +.ft CW +bool_t xdr_void(); /* \fIalways returns TRUE\fP */ +.DE +.NH 2 +\&Constructed Data Type Filters +.IX "XDR library" "constructed data type filters" +.LP +Constructed or compound data type primitives +require more parameters and perform more complicated functions +then the primitives discussed above. +This section includes primitives for +strings, arrays, unions, and pointers to structures. +.LP +Constructed data type primitives may use memory management. +In many cases, memory is allocated when deserializing data with +.I XDR_DECODE +Therefore, the XDR package must provide means to deallocate memory. +This is done by an XDR operation, +.I XDR_FREE +To review, the three XDR directional operations are +.I XDR_ENCODE , +.I XDR_DECODE +and +.I XDR_FREE . +.NH 3 +\&Strings +.IX "XDR library" "strings" +.LP +In C, a string is defined as a sequence of bytes +terminated by a null byte, +which is not considered when calculating string length. +However, when a string is passed or manipulated, +a pointer to it is employed. +Therefore, the XDR library defines a string to be a +.I "char *" +and not a sequence of characters. +The external representation of a string is drastically different +from its internal representation. +Externally, strings are represented as +sequences of ASCII characters, +while internally, they are represented with character pointers. +Conversion between the two representations +is accomplished with the routine +.I xdr_string (): +.IX xdr_string() "" \fIxdr_string()\fP +.DS +.ft CW +bool_t xdr_string(xdrs, sp, maxlength) + XDR *xdrs; + char **sp; + u_int maxlength; +.DE +The first parameter +.I xdrs +is the XDR stream handle. +The second parameter +.I sp +is a pointer to a string (type +.I "char **" . +The third parameter +.I maxlength +specifies the maximum number of bytes allowed during encoding or decoding. +its value is usually specified by a protocol. For example, a protocol +specification may say that a file name may be no longer than 255 characters. +.LP +The routine returns +.I FALSE +if the number of characters exceeds +.I maxlength , +and +.I TRUE +if it doesn't. +.SH +Keep +.I maxlength +small. If it is too big you can blow the heap, since +.I xdr_string() +will call +.I malloc() +for space. +.LP +The behavior of +.I xdr_string() +.IX xdr_string() "" \fIxdr_string()\fP +is similar to the behavior of other routines +discussed in this section. The direction +.I XDR_ENCODE +is easiest to understand. The parameter +.I sp +points to a string of a certain length; +if the string does not exceed +.I maxlength , +the bytes are serialized. +.LP +The effect of deserializing a string is subtle. +First the length of the incoming string is determined; +it must not exceed +.I maxlength . +Next +.I sp +is dereferenced; if the the value is +.I NULL , +then a string of the appropriate length is allocated and +.I *sp +is set to this string. +If the original value of +.I *sp +is non-null, then the XDR package assumes +that a target area has been allocated, +which can hold strings no longer than +.I maxlength . +In either case, the string is decoded into the target area. +The routine then appends a null character to the string. +.LP +In the +.I XDR_FREE +operation, the string is obtained by dereferencing +.I sp . +If the string is not +.I NULL , +it is freed and +.I *sp +is set to +.I NULL . +In this operation, +.I xdr_string() +ignores the +.I maxlength +parameter. +.NH 3 +\&Byte Arrays +.IX "XDR library" "byte arrays" +.LP +Often variable-length arrays of bytes are preferable to strings. +Byte arrays differ from strings in the following three ways: +1) the length of the array (the byte count) is explicitly +located in an unsigned integer, +2) the byte sequence is not terminated by a null character, and +3) the external representation of the bytes is the same as their +internal representation. +The primitive +.I xdr_bytes() +.IX xdr_bytes() "" \fIxdr_bytes()\fP +converts between the internal and external +representations of byte arrays: +.DS +.ft CW +bool_t xdr_bytes(xdrs, bpp, lp, maxlength) + XDR *xdrs; + char **bpp; + u_int *lp; + u_int maxlength; +.DE +The usage of the first, second and fourth parameters +are identical to the first, second and third parameters of +.I xdr_string (), +respectively. +The length of the byte area is obtained by dereferencing +.I lp +when serializing; +.I *lp +is set to the byte length when deserializing. +.NH 3 +\&Arrays +.IX "XDR library" "arrays" +.LP +The XDR library package provides a primitive +for handling arrays of arbitrary elements. +The +.I xdr_bytes() +routine treats a subset of generic arrays, +in which the size of array elements is known to be 1, +and the external description of each element is built-in. +The generic array primitive, +.I xdr_array() , +.IX xdr_array() "" \fIxdr_array()\fP +requires parameters identical to those of +.I xdr_bytes() +plus two more: +the size of array elements, +and an XDR routine to handle each of the elements. +This routine is called to encode or decode +each element of the array. +.DS +.ft CW +bool_t +xdr_array(xdrs, ap, lp, maxlength, elementsiz, xdr_element) + XDR *xdrs; + char **ap; + u_int *lp; + u_int maxlength; + u_int elementsiz; + bool_t (*xdr_element)(); +.DE +The parameter +.I ap +is the address of the pointer to the array. +If +.I *ap +is +.I NULL +when the array is being deserialized, +XDR allocates an array of the appropriate size and sets +.I *ap +to that array. +The element count of the array is obtained from +.I *lp +when the array is serialized; +.I *lp +is set to the array length when the array is deserialized. +The parameter +.I maxlength +is the maximum number of elements that the array is allowed to have; +.I elementsiz +is the byte size of each element of the array +(the C function +.I sizeof() +can be used to obtain this value). +The +.I xdr_element() +.IX xdr_element() "" \fIxdr_element()\fP +routine is called to serialize, deserialize, or free +each element of the array. +.br +.LP +Before defining more constructed data types, it is appropriate to +present three examples. +.LP +.I "Example A:" +.br +A user on a networked machine can be identified by +(a) the machine name, such as +.I krypton : +see the +.I gethostname +man page; (b) the user's UID: see the +.I geteuid +man page; and (c) the group numbers to which the user belongs: +see the +.I getgroups +man page. A structure with this information and its associated +XDR routine could be coded like this: +.ie t .DS +.el .DS L +.ft CW +struct netuser { + char *nu_machinename; + int nu_uid; + u_int nu_glen; + int *nu_gids; +}; +#define NLEN 255 /* \fImachine names < 256 chars\fP */ +#define NGRPS 20 /* \fIuser can't be in > 20 groups\fP */ +.sp.5 +bool_t +xdr_netuser(xdrs, nup) + XDR *xdrs; + struct netuser *nup; +{ + return(xdr_string(xdrs, &nup->nu_machinename, NLEN) && + xdr_int(xdrs, &nup->nu_uid) && + xdr_array(xdrs, &nup->nu_gids, &nup->nu_glen, + NGRPS, sizeof (int), xdr_int)); +} +.DE +.LP +.I "Example B:" +.br +A party of network users could be implemented +as an array of +.I netuser +structure. +The declaration and its associated XDR routines +are as follows: +.ie t .DS +.el .DS L +.ft CW +struct party { + u_int p_len; + struct netuser *p_nusers; +}; +#define PLEN 500 /* \fImax number of users in a party\fP */ +.sp.5 +bool_t +xdr_party(xdrs, pp) + XDR *xdrs; + struct party *pp; +{ + return(xdr_array(xdrs, &pp->p_nusers, &pp->p_len, PLEN, + sizeof (struct netuser), xdr_netuser)); +} +.DE +.LP +.I "Example C:" +.br +The well-known parameters to +.I main , +.I argc +and +.I argv +can be combined into a structure. +An array of these structures can make up a history of commands. +The declarations and XDR routines might look like: +.ie t .DS +.el .DS L +.ft CW +struct cmd { + u_int c_argc; + char **c_argv; +}; +#define ALEN 1000 /* \fIargs cannot be > 1000 chars\fP */ +#define NARGC 100 /* \fIcommands cannot have > 100 args\fP */ + +struct history { + u_int h_len; + struct cmd *h_cmds; +}; +#define NCMDS 75 /* \fIhistory is no more than 75 commands\fP */ + +bool_t +xdr_wrap_string(xdrs, sp) + XDR *xdrs; + char **sp; +{ + return(xdr_string(xdrs, sp, ALEN)); +} +.DE +.ie t .DS +.el .DS L +.ft CW +bool_t +xdr_cmd(xdrs, cp) + XDR *xdrs; + struct cmd *cp; +{ + return(xdr_array(xdrs, &cp->c_argv, &cp->c_argc, NARGC, + sizeof (char *), xdr_wrap_string)); +} +.DE +.ie t .DS +.el .DS L +.ft CW +bool_t +xdr_history(xdrs, hp) + XDR *xdrs; + struct history *hp; +{ + return(xdr_array(xdrs, &hp->h_cmds, &hp->h_len, NCMDS, + sizeof (struct cmd), xdr_cmd)); +} +.DE +The most confusing part of this example is that the routine +.I xdr_wrap_string() +is needed to package the +.I xdr_string() +routine, because the implementation of +.I xdr_array() +only passes two parameters to the array element description routine; +.I xdr_wrap_string() +supplies the third parameter to +.I xdr_string (). +.LP +By now the recursive nature of the XDR library should be obvious. +Let's continue with more constructed data types. +.NH 3 +\&Opaque Data +.IX "XDR library" "opaque data" +.LP +In some protocols, handles are passed from a server to client. +The client passes the handle back to the server at some later time. +Handles are never inspected by clients; +they are obtained and submitted. +That is to say, handles are opaque. +The +.I xdr_opaque() +.IX xdr_opaque() "" \fIxdr_opaque()\fP +primitive is used for describing fixed sized, opaque bytes. +.DS +.ft CW +bool_t xdr_opaque(xdrs, p, len) + XDR *xdrs; + char *p; + u_int len; +.DE +The parameter +.I p +is the location of the bytes; +.I len +is the number of bytes in the opaque object. +By definition, the actual data +contained in the opaque object are not machine portable. +.NH 3 +\&Fixed Sized Arrays +.IX "XDR library" "fixed sized arrays" +.LP +The XDR library provides a primitive, +.I xdr_vector (), +for fixed-length arrays. +.ie t .DS +.el .DS L +.ft CW +#define NLEN 255 /* \fImachine names must be < 256 chars\fP */ +#define NGRPS 20 /* \fIuser belongs to exactly 20 groups\fP */ +.sp.5 +struct netuser { + char *nu_machinename; + int nu_uid; + int nu_gids[NGRPS]; +}; +.sp.5 +bool_t +xdr_netuser(xdrs, nup) + XDR *xdrs; + struct netuser *nup; +{ + int i; +.sp.5 + if (!xdr_string(xdrs, &nup->nu_machinename, NLEN)) + return(FALSE); + if (!xdr_int(xdrs, &nup->nu_uid)) + return(FALSE); + if (!xdr_vector(xdrs, nup->nu_gids, NGRPS, sizeof(int), + xdr_int)) { + return(FALSE); + } + return(TRUE); +} +.DE +.NH 3 +\&Discriminated Unions +.IX "XDR library" "discriminated unions" +.LP +The XDR library supports discriminated unions. +A discriminated union is a C union and an +.I enum_t +value that selects an \*Qarm\*U of the union. +.DS +.ft CW +struct xdr_discrim { + enum_t value; + bool_t (*proc)(); +}; +.sp.5 +bool_t xdr_union(xdrs, dscmp, unp, arms, defaultarm) + XDR *xdrs; + enum_t *dscmp; + char *unp; + struct xdr_discrim *arms; + bool_t (*defaultarm)(); /* \fImay equal NULL\fP */ +.DE +First the routine translates the discriminant of the union located at +.I *dscmp . +The discriminant is always an +.I enum_t . +Next the union located at +.I *unp +is translated. +The parameter +.I arms +is a pointer to an array of +.I xdr_discrim +structures. +Each structure contains an ordered pair of +.I [value,proc] . +If the union's discriminant is equal to the associated +.I value , +then the +.I proc +is called to translate the union. +The end of the +.I xdr_discrim +structure array is denoted by a routine of value +.I NULL +(0). If the discriminant is not found in the +.I arms +array, then the +.I defaultarm +procedure is called if it is non-null; +otherwise the routine returns +.I FALSE . +.LP +.I "Example D:" +Suppose the type of a union may be integer, +character pointer (a string), or a +.I gnumbers +structure. +Also, assume the union and its current type +are declared in a structure. +The declaration is: +.ie t .DS +.el .DS L +.ft CW +enum utype { INTEGER=1, STRING=2, GNUMBERS=3 }; +.sp.5 +struct u_tag { + enum utype utype; /* \fIthe union's discriminant\fP */ + union { + int ival; + char *pval; + struct gnumbers gn; + } uval; +}; +.DE +The following constructs and XDR procedure (de)serialize +the discriminated union: +.ie t .DS +.el .DS L +.ft CW +struct xdr_discrim u_tag_arms[4] = { + { INTEGER, xdr_int }, + { GNUMBERS, xdr_gnumbers } + { STRING, xdr_wrap_string }, + { __dontcare__, NULL } + /* \fIalways terminate arms with a NULL xdr_proc\fP */ +} +.sp.5 +bool_t +xdr_u_tag(xdrs, utp) + XDR *xdrs; + struct u_tag *utp; +{ + return(xdr_union(xdrs, &utp->utype, &utp->uval, + u_tag_arms, NULL)); +} +.DE +The routine +.I xdr_gnumbers() +was presented above in +.I "The XDR Library" +section. +.I xdr_wrap_string() +was presented in example C. +The default +.I arm +parameter to +.I xdr_union() +(the last parameter) is +.I NULL +in this example. Therefore the value of the union's discriminant +may legally take on only values listed in the +.I u_tag_arms +array. This example also demonstrates that +the elements of the arm's array do not need to be sorted. +.LP +It is worth pointing out that the values of the discriminant +may be sparse, though in this example they are not. +It is always good +practice to assign explicitly integer values to each element of the +discriminant's type. +This practice both documents the external +representation of the discriminant and guarantees that different +C compilers emit identical discriminant values. +.LP +Exercise: Implement +.I xdr_union() +using the other primitives in this section. +.NH 3 +\&Pointers +.IX "XDR library" "pointers" +.LP +In C it is often convenient to put pointers +to another structure within a structure. +The +.I xdr_reference() +.IX xdr_reference() "" \fIxdr_reference()\fP +primitive makes it easy to serialize, deserialize, and free +these referenced structures. +.DS +.ft CW +bool_t xdr_reference(xdrs, pp, size, proc) + XDR *xdrs; + char **pp; + u_int ssize; + bool_t (*proc)(); +.DE +.LP +Parameter +.I pp +is the address of +the pointer to the structure; +parameter +.I ssize +is the size in bytes of the structure (use the C function +.I sizeof() +to obtain this value); and +.I proc +is the XDR routine that describes the structure. +When decoding data, storage is allocated if +.I *pp +is +.I NULL . +.LP +There is no need for a primitive +.I xdr_struct() +to describe structures within structures, +because pointers are always sufficient. +.LP +Exercise: Implement +.I xdr_reference() +using +.I xdr_array (). +Warning: +.I xdr_reference() +and +.I xdr_array() +are NOT interchangeable external representations of data. +.LP +.I "Example E:" +Suppose there is a structure containing a person's name +and a pointer to a +.I gnumbers +structure containing the person's gross assets and liabilities. +The construct is: +.DS +.ft CW +struct pgn { + char *name; + struct gnumbers *gnp; +}; +.DE +The corresponding XDR routine for this structure is: +.DS +.ft CW +bool_t +xdr_pgn(xdrs, pp) + XDR *xdrs; + struct pgn *pp; +{ + if (xdr_string(xdrs, &pp->name, NLEN) && + xdr_reference(xdrs, &pp->gnp, + sizeof(struct gnumbers), xdr_gnumbers)) + return(TRUE); + return(FALSE); +} +.DE +.IX "pointer semantics and XDR" +.I "Pointer Semantics and XDR" +.LP +In many applications, C programmers attach double meaning to +the values of a pointer. Typically the value +.I NULL +(or zero) means data is not needed, +yet some application-specific interpretation applies. +In essence, the C programmer is encoding +a discriminated union efficiently +by overloading the interpretation of the value of a pointer. +For instance, in example E a +.I NULL +pointer value for +.I gnp +could indicate that +the person's assets and liabilities are unknown. +That is, the pointer value encodes two things: +whether or not the data is known; +and if it is known, where it is located in memory. +Linked lists are an extreme example of the use +of application-specific pointer interpretation. +.LP +The primitive +.I xdr_reference() +.IX xdr_reference() "" \fIxdr_reference()\fP +cannot and does not attach any special +meaning to a null-value pointer during serialization. +That is, passing an address of a pointer whose value is +.I NULL +to +.I xdr_reference() +when serialing data will most likely cause a memory fault and, on the UNIX +system, a core dump. +.LP +.I xdr_pointer() +correctly handles +.I NULL +pointers. For more information about its use, see +the +.I "Linked Lists" +topics below. +.LP +.I Exercise: +After reading the section on +.I "Linked Lists" , +return here and extend example E so that +it can correctly deal with +.I NULL +pointer values. +.LP +.I Exercise: +Using the +.I xdr_union (), +.I xdr_reference() +and +.I xdr_void() +primitives, implement a generic pointer handling primitive +that implicitly deals with +.I NULL +pointers. That is, implement +.I xdr_pointer (). +.NH 2 +\&Non-filter Primitives +.IX "XDR" "non-filter primitives" +.LP +XDR streams can be manipulated with +the primitives discussed in this section. +.DS +.ft CW +u_int xdr_getpos(xdrs) + XDR *xdrs; +.sp.5 +bool_t xdr_setpos(xdrs, pos) + XDR *xdrs; + u_int pos; +.sp.5 +xdr_destroy(xdrs) + XDR *xdrs; +.DE +The routine +.I xdr_getpos() +.IX xdr_getpos() "" \fIxdr_getpos()\fP +returns an unsigned integer +that describes the current position in the data stream. +Warning: In some XDR streams, the returned value of +.I xdr_getpos() +is meaningless; +the routine returns a \-1 in this case +(though \-1 should be a legitimate value). +.LP +The routine +.I xdr_setpos() +.IX xdr_setpos() "" \fIxdr_setpos()\fP +sets a stream position to +.I pos . +Warning: In some XDR streams, setting a position is impossible; +in such cases, +.I xdr_setpos() +will return +.I FALSE . +This routine will also fail if the requested position is out-of-bounds. +The definition of bounds varies from stream to stream. +.LP +The +.I xdr_destroy() +.IX xdr_destroy() "" \fIxdr_destroy()\fP +primitive destroys the XDR stream. +Usage of the stream +after calling this routine is undefined. +.NH 2 +\&XDR Operation Directions +.IX XDR "operation directions" +.IX "direction of XDR operations" +.LP +At times you may wish to optimize XDR routines by taking +advantage of the direction of the operation \(em +.I XDR_ENCODE +.I XDR_DECODE +or +.I XDR_FREE +The value +.I xdrs->x_op +always contains the direction of the XDR operation. +Programmers are not encouraged to take advantage of this information. +Therefore, no example is presented here. However, an example in the +.I "Linked Lists" +topic below, demonstrates the usefulness of the +.I xdrs->x_op +field. +.NH 2 +\&XDR Stream Access +.IX "XDR" "stream access" +.LP +An XDR stream is obtained by calling the appropriate creation routine. +These creation routines take arguments that are tailored to the +specific properties of the stream. +.LP +Streams currently exist for (de)serialization of data to or from +standard I/O +.I FILE +streams, TCP/IP connections and UNIX files, and memory. +.NH 3 +\&Standard I/O Streams +.IX "XDR" "standard I/O streams" +.LP +XDR streams can be interfaced to standard I/O using the +.I xdrstdio_create() +.IX xdrstdio_create() "" \fIxdrstdio_create()\fP +routine as follows: +.DS +.ft CW +#include +#include /* \fIxdr streams part of rpc\fP */ +.sp.5 +void +xdrstdio_create(xdrs, fp, x_op) + XDR *xdrs; + FILE *fp; + enum xdr_op x_op; +.DE +The routine +.I xdrstdio_create() +initializes an XDR stream pointed to by +.I xdrs . +The XDR stream interfaces to the standard I/O library. +Parameter +.I fp +is an open file, and +.I x_op +is an XDR direction. +.NH 3 +\&Memory Streams +.IX "XDR" "memory streams" +.LP +Memory streams allow the streaming of data into or out of +a specified area of memory: +.DS +.ft CW +#include +.sp.5 +void +xdrmem_create(xdrs, addr, len, x_op) + XDR *xdrs; + char *addr; + u_int len; + enum xdr_op x_op; +.DE +The routine +.I xdrmem_create() +.IX xdrmem_create() "" \fIxdrmem_create()\fP +initializes an XDR stream in local memory. +The memory is pointed to by parameter +.I addr ; +parameter +.I len +is the length in bytes of the memory. +The parameters +.I xdrs +and +.I x_op +are identical to the corresponding parameters of +.I xdrstdio_create (). +Currently, the UDP/IP implementation of RPC uses +.I xdrmem_create (). +Complete call or result messages are built in memory before calling the +.I sendto() +system routine. +.NH 3 +\&Record (TCP/IP) Streams +.IX "XDR" "record (TCP/IP) streams" +.LP +A record stream is an XDR stream built on top of +a record marking standard that is built on top of the +UNIX file or 4.2 BSD connection interface. +.DS +.ft CW +#include /* \fIxdr streams part of rpc\fP */ +.sp.5 +xdrrec_create(xdrs, + sendsize, recvsize, iohandle, readproc, writeproc) + XDR *xdrs; + u_int sendsize, recvsize; + char *iohandle; + int (*readproc)(), (*writeproc)(); +.DE +The routine +.I xdrrec_create() +provides an XDR stream interface that allows for a bidirectional, +arbitrarily long sequence of records. +The contents of the records are meant to be data in XDR form. +The stream's primary use is for interfacing RPC to TCP connections. +However, it can be used to stream data into or out of normal +UNIX files. +.LP +The parameter +.I xdrs +is similar to the corresponding parameter described above. +The stream does its own data buffering similar to that of standard I/O. +The parameters +.I sendsize +and +.I recvsize +determine the size in bytes of the output and input buffers, respectively; +if their values are zero (0), then predetermined defaults are used. +When a buffer needs to be filled or flushed, the routine +.I readproc() +or +.I writeproc() +is called, respectively. +The usage and behavior of these +routines are similar to the UNIX system calls +.I read() +and +.I write (). +However, +the first parameter to each of these routines is the opaque parameter +.I iohandle . +The other two parameters +.I buf "" +and +.I nbytes ) +and the results +(byte count) are identical to the system routines. +If +.I xxx +is +.I readproc() +or +.I writeproc (), +then it has the following form: +.DS +.ft CW +.ft I +/* + * returns the actual number of bytes transferred. + * -1 is an error + */ +.ft CW +int +xxx(iohandle, buf, len) + char *iohandle; + char *buf; + int nbytes; +.DE +The XDR stream provides means for delimiting records in the byte stream. +The implementation details of delimiting records in a stream are +discussed in the +.I "Advanced Topics" +topic below. +The primitives that are specific to record streams are as follows: +.DS +.ft CW +bool_t +xdrrec_endofrecord(xdrs, flushnow) + XDR *xdrs; + bool_t flushnow; +.sp.5 +bool_t +xdrrec_skiprecord(xdrs) + XDR *xdrs; +.sp.5 +bool_t +xdrrec_eof(xdrs) + XDR *xdrs; +.DE +The routine +.I xdrrec_endofrecord() +.IX xdrrec_endofrecord() "" \fIxdrrec_endofrecord()\fP +causes the current outgoing data to be marked as a record. +If the parameter +.I flushnow +is +.I TRUE , +then the stream's +.I writeproc +will be called; otherwise, +.I writeproc +will be called when the output buffer has been filled. +.LP +The routine +.I xdrrec_skiprecord() +.IX xdrrec_skiprecord() "" \fIxdrrec_skiprecord()\fP +causes an input stream's position to be moved past +the current record boundary and onto the +beginning of the next record in the stream. +.LP +If there is no more data in the stream's input buffer, +then the routine +.I xdrrec_eof() +.IX xdrrec_eof() "" \fIxdrrec_eof()\fP +returns +.I TRUE . +That is not to say that there is no more data +in the underlying file descriptor. +.NH 2 +\&XDR Stream Implementation +.IX "XDR" "stream implementation" +.IX "stream implementation in XDR" +.LP +This section provides the abstract data types needed +to implement new instances of XDR streams. +.NH 3 +\&The XDR Object +.IX "XDR" "object" +.LP +The following structure defines the interface to an XDR stream: +.ie t .DS +.el .DS L +.ft CW +enum xdr_op { XDR_ENCODE=0, XDR_DECODE=1, XDR_FREE=2 }; +.sp.5 +typedef struct { + enum xdr_op x_op; /* \fIoperation; fast added param\fP */ + struct xdr_ops { + bool_t (*x_getlong)(); /* \fIget long from stream\fP */ + bool_t (*x_putlong)(); /* \fIput long to stream\fP */ + bool_t (*x_getbytes)(); /* \fIget bytes from stream\fP */ + bool_t (*x_putbytes)(); /* \fIput bytes to stream\fP */ + u_int (*x_getpostn)(); /* \fIreturn stream offset\fP */ + bool_t (*x_setpostn)(); /* \fIreposition offset\fP */ + caddr_t (*x_inline)(); /* \fIptr to buffered data\fP */ + VOID (*x_destroy)(); /* \fIfree private area\fP */ + } *x_ops; + caddr_t x_public; /* \fIusers' data\fP */ + caddr_t x_private; /* \fIpointer to private data\fP */ + caddr_t x_base; /* \fIprivate for position info\fP */ + int x_handy; /* \fIextra private word\fP */ +} XDR; +.DE +The +.I x_op +field is the current operation being performed on the stream. +This field is important to the XDR primitives, +but should not affect a stream's implementation. +That is, a stream's implementation should not depend +on this value. +The fields +.I x_private , +.I x_base , +and +.I x_handy +are private to the particular +stream's implementation. +The field +.I x_public +is for the XDR client and should never be used by +the XDR stream implementations or the XDR primitives. +.I x_getpostn() , +.I x_setpostn() +and +.I x_destroy() +are macros for accessing operations. The operation +.I x_inline() +takes two parameters: +an XDR *, and an unsigned integer, which is a byte count. +The routine returns a pointer to a piece of +the stream's internal buffer. +The caller can then use the buffer segment for any purpose. +From the stream's point of view, the bytes in the +buffer segment have been consumed or put. +The routine may return +.I NULL +if it cannot return a buffer segment of the requested size. +(The +.I x_inline() +routine is for cycle squeezers. +Use of the resulting buffer is not data-portable. +Users are encouraged not to use this feature.) +.LP +The operations +.I x_getbytes() +and +.I x_putbytes() +blindly get and put sequences of bytes +from or to the underlying stream; +they return +.I TRUE +if they are successful, and +.I FALSE +otherwise. The routines have identical parameters (replace +.I xxx ): +.DS +.ft CW +bool_t +xxxbytes(xdrs, buf, bytecount) + XDR *xdrs; + char *buf; + u_int bytecount; +.DE +The operations +.I x_getlong() +and +.I x_putlong() +receive and put +long numbers from and to the data stream. +It is the responsibility of these routines +to translate the numbers between the machine representation +and the (standard) external representation. +The UNIX primitives +.I htonl() +and +.I ntohl() +can be helpful in accomplishing this. +The higher-level XDR implementation assumes that +signed and unsigned long integers contain the same number of bits, +and that nonnegative integers +have the same bit representations as unsigned integers. +The routines return +.I TRUE +if they succeed, and +.I FALSE +otherwise. They have identical parameters: +.DS +.ft CW +bool_t +xxxlong(xdrs, lp) + XDR *xdrs; + long *lp; +.DE +Implementors of new XDR streams must make an XDR structure +(with new operation routines) available to clients, +using some kind of create routine. +.NH 1 +\&Advanced Topics +.IX XDR "advanced topics" +.LP +This section describes techniques for passing data structures that +are not covered in the preceding sections. Such structures include +linked lists (of arbitrary lengths). Unlike the simpler examples +covered in the earlier sections, the following examples are written +using both the XDR C library routines and the XDR data description +language. +The +.I "External Data Representation Standard: Protocol Specification" +describes this +language in complete detail. +.NH 2 +\&Linked Lists +.IX XDR "linked lists" +.LP +The last example in the +.I Pointers +topic earlier in this chapter +presented a C data structure and its associated XDR +routines for a individual's gross assets and liabilities. +The example is duplicated below: +.ie t .DS +.el .DS L +.ft CW +struct gnumbers { + long g_assets; + long g_liabilities; +}; +.sp.5 +bool_t +xdr_gnumbers(xdrs, gp) + XDR *xdrs; + struct gnumbers *gp; +{ + if (xdr_long(xdrs, &(gp->g_assets))) + return(xdr_long(xdrs, &(gp->g_liabilities))); + return(FALSE); +} +.DE +.LP +Now assume that we wish to implement a linked list of such information. +A data structure could be constructed as follows: +.ie t .DS +.el .DS L +.ft CW +struct gnumbers_node { + struct gnumbers gn_numbers; + struct gnumbers_node *gn_next; +}; +.sp .5 +typedef struct gnumbers_node *gnumbers_list; +.DE +.LP +The head of the linked list can be thought of as the data object; +that is, the head is not merely a convenient shorthand for a +structure. Similarly the +.I gn_next +field is used to indicate whether or not the object has terminated. +Unfortunately, if the object continues, the +.I gn_next +field is also the address of where it continues. The link addresses +carry no useful information when the object is serialized. +.LP +The XDR data description of this linked list is described by the +recursive declaration of +.I gnumbers_list : +.ie t .DS +.el .DS L +.ft CW +struct gnumbers { + int g_assets; + int g_liabilities; +}; +.sp .5 +struct gnumbers_node { + gnumbers gn_numbers; + gnumbers_node *gn_next; +}; +.DE +.LP +In this description, the boolean indicates whether there is more data +following it. If the boolean is +.I FALSE , +then it is the last data field of the structure. If it is +.I TRUE , +then it is followed by a gnumbers structure and (recursively) by a +.I gnumbers_list . +Note that the C declaration has no boolean explicitly declared in it +(though the +.I gn_next +field implicitly carries the information), while the XDR data +description has no pointer explicitly declared in it. +.LP +Hints for writing the XDR routines for a +.I gnumbers_list +follow easily from the XDR description above. Note how the primitive +.I xdr_pointer() +is used to implement the XDR union above. +.ie t .DS +.el .DS L +.ft CW +bool_t +xdr_gnumbers_node(xdrs, gn) + XDR *xdrs; + gnumbers_node *gn; +{ + return(xdr_gnumbers(xdrs, &gn->gn_numbers) && + xdr_gnumbers_list(xdrs, &gp->gn_next)); +} +.sp .5 +bool_t +xdr_gnumbers_list(xdrs, gnp) + XDR *xdrs; + gnumbers_list *gnp; +{ + return(xdr_pointer(xdrs, gnp, + sizeof(struct gnumbers_node), + xdr_gnumbers_node)); +} +.DE +.LP +The unfortunate side effect of XDR'ing a list with these routines +is that the C stack grows linearly with respect to the number of +node in the list. This is due to the recursion. The following +routine collapses the above two mutually recursive into a single, +non-recursive one. +.ie t .DS +.el .DS L +.ft CW +bool_t +xdr_gnumbers_list(xdrs, gnp) + XDR *xdrs; + gnumbers_list *gnp; +{ + bool_t more_data; + gnumbers_list *nextp; +.sp .5 + for (;;) { + more_data = (*gnp != NULL); + if (!xdr_bool(xdrs, &more_data)) { + return(FALSE); + } + if (! more_data) { + break; + } + if (xdrs->x_op == XDR_FREE) { + nextp = &(*gnp)->gn_next; + } + if (!xdr_reference(xdrs, gnp, + sizeof(struct gnumbers_node), xdr_gnumbers)) { + + return(FALSE); + } + gnp = (xdrs->x_op == XDR_FREE) ? + nextp : &(*gnp)->gn_next; + } + *gnp = NULL; + return(TRUE); +} +.DE +.LP +The first task is to find out whether there is more data or not, +so that this boolean information can be serialized. Notice that +this statement is unnecessary in the +.I XDR_DECODE +case, since the value of more_data is not known until we +deserialize it in the next statement. +.LP +The next statement XDR's the more_data field of the XDR union. +Then if there is truly no more data, we set this last pointer to +.I NULL +to indicate the end of the list, and return +.I TRUE +because we are done. Note that setting the pointer to +.I NULL +is only important in the +.I XDR_DECODE +case, since it is already +.I NULL +in the +.I XDR_ENCODE +and +XDR_FREE +cases. +.LP +Next, if the direction is +.I XDR_FREE , +the value of +.I nextp +is set to indicate the location of the next pointer in the list. +We do this now because we need to dereference gnp to find the +location of the next item in the list, and after the next +statement the storage pointed to by +.I gnp +will be freed up and no be longer valid. We can't do this for all +directions though, because in the +.I XDR_DECODE +direction the value of +.I gnp +won't be set until the next statement. +.LP +Next, we XDR the data in the node using the primitive +.I xdr_reference (). +.I xdr_reference() +is like +.I xdr_pointer() +which we used before, but it does not +send over the boolean indicating whether there is more data. +We use it instead of +.I xdr_pointer() +because we have already XDR'd this information ourselves. Notice +that the xdr routine passed is not the same type as an element +in the list. The routine passed is +.I xdr_gnumbers (), +for XDR'ing gnumbers, but each element in the list is actually of +type +.I gnumbers_node . +We don't pass +.I xdr_gnumbers_node() +because it is recursive, and instead use +.I xdr_gnumbers() +which XDR's all of the non-recursive part. Note that this trick +will work only if the +.I gn_numbers +field is the first item in each element, so that their addresses +are identical when passed to +.I xdr_reference (). +.LP +Finally, we update +.I gnp +to point to the next item in the list. If the direction is +.I XDR_FREE , +we set it to the previously saved value, otherwise we can +dereference +.I gnp +to get the proper value. Though harder to understand than the +recursive version, this non-recursive routine is far less likely +to blow the C stack. It will also run more efficiently since +a lot of procedure call overhead has been removed. Most lists +are small though (in the hundreds of items or less) and the +recursive version should be sufficient for them. +.EQ +delim off +.EN diff --git a/lib/libc/rpc/PSD.doc/xdr.rfc.ms b/lib/libc/rpc/PSD.doc/xdr.rfc.ms new file mode 100644 index 000000000000..d4baff53915b --- /dev/null +++ b/lib/libc/rpc/PSD.doc/xdr.rfc.ms @@ -0,0 +1,1058 @@ +.\" +.\" Must use -- tbl -- with this one +.\" +.\" @(#)xdr.rfc.ms 2.2 88/08/05 4.0 RPCSRC +.de BT +.if \\n%=1 .tl ''- % -'' +.. +.ND +.\" prevent excess underlining in nroff +.if n .fp 2 R +.OH 'External Data Representation Standard''Page %' +.EH 'Page %''External Data Representation Standard' +.IX "External Data Representation" +.if \\n%=1 .bp +.SH +\&External Data Representation Standard: Protocol Specification +.IX XDR RFC +.IX XDR "protocol specification" +.LP +.NH 0 +\&Status of this Standard +.nr OF 1 +.IX XDR "RFC status" +.LP +Note: This chapter specifies a protocol that Sun Microsystems, Inc., and +others are using. It has been designated RFC1014 by the ARPA Network +Information Center. +.NH 1 +Introduction +\& +.LP +XDR is a standard for the description and encoding of data. It is +useful for transferring data between different computer +architectures, and has been used to communicate data between such +diverse machines as the Sun Workstation, VAX, IBM-PC, and Cray. +XDR fits into the ISO presentation layer, and is roughly analogous in +purpose to X.409, ISO Abstract Syntax Notation. The major difference +between these two is that XDR uses implicit typing, while X.409 uses +explicit typing. +.LP +XDR uses a language to describe data formats. The language can only +be used only to describe data; it is not a programming language. +This language allows one to describe intricate data formats in a +concise manner. The alternative of using graphical representations +(itself an informal language) quickly becomes incomprehensible when +faced with complexity. The XDR language itself is similar to the C +language [1], just as Courier [4] is similar to Mesa. Protocols such +as Sun RPC (Remote Procedure Call) and the NFS (Network File System) +use XDR to describe the format of their data. +.LP +The XDR standard makes the following assumption: that bytes (or +octets) are portable, where a byte is defined to be 8 bits of data. +A given hardware device should encode the bytes onto the various +media in such a way that other hardware devices may decode the bytes +without loss of meaning. For example, the Ethernet standard +suggests that bytes be encoded in "little-endian" style [2], or least +significant bit first. +.NH 2 +\&Basic Block Size +.IX XDR "basic block size" +.IX XDR "block size" +.LP +The representation of all items requires a multiple of four bytes (or +32 bits) of data. The bytes are numbered 0 through n-1. The bytes +are read or written to some byte stream such that byte m always +precedes byte m+1. If the n bytes needed to contain the data are not +a multiple of four, then the n bytes are followed by enough (0 to 3) +residual zero bytes, r, to make the total byte count a multiple of 4. +.LP +We include the familiar graphic box notation for illustration and +comparison. In most illustrations, each box (delimited by a plus +sign at the 4 corners and vertical bars and dashes) depicts a byte. +Ellipses (...) between boxes show zero or more additional bytes where +required. +.ie t .DS +.el .DS L +\fIA Block\fP + +\f(CW+--------+--------+...+--------+--------+...+--------+ +| byte 0 | byte 1 |...|byte n-1| 0 |...| 0 | ++--------+--------+...+--------+--------+...+--------+ +|<-----------n bytes---------->|<------r bytes------>| +|<-----------n+r (where (n+r) mod 4 = 0)>----------->|\fP + +.DE +.NH 1 +\&XDR Data Types +.IX XDR "data types" +.IX "XDR data types" +.LP +Each of the sections that follow describes a data type defined in the +XDR standard, shows how it is declared in the language, and includes +a graphic illustration of its encoding. +.LP +For each data type in the language we show a general paradigm +declaration. Note that angle brackets (< and >) denote +variable length sequences of data and square brackets ([ and ]) denote +fixed-length sequences of data. "n", "m" and "r" denote integers. +For the full language specification and more formal definitions of +terms such as "identifier" and "declaration", refer to +.I "The XDR Language Specification" , +below. +.LP +For some data types, more specific examples are included. +A more extensive example of a data description is in +.I "An Example of an XDR Data Description" +below. +.NH 2 +\&Integer +.IX XDR integer +.LP +An XDR signed integer is a 32-bit datum that encodes an integer in +the range [-2147483648,2147483647]. The integer is represented in +two's complement notation. The most and least significant bytes are +0 and 3, respectively. Integers are declared as follows: +.ie t .DS +.el .DS L +\fIInteger\fP + +\f(CW(MSB) (LSB) ++-------+-------+-------+-------+ +|byte 0 |byte 1 |byte 2 |byte 3 | ++-------+-------+-------+-------+ +<------------32 bits------------>\fP +.DE +.NH 2 +\&Unsigned Integer +.IX XDR "unsigned integer" +.IX XDR "integer, unsigned" +.LP +An XDR unsigned integer is a 32-bit datum that encodes a nonnegative +integer in the range [0,4294967295]. It is represented by an +unsigned binary number whose most and least significant bytes are 0 +and 3, respectively. An unsigned integer is declared as follows: +.ie t .DS +.el .DS L +\fIUnsigned Integer\fP + +\f(CW(MSB) (LSB) ++-------+-------+-------+-------+ +|byte 0 |byte 1 |byte 2 |byte 3 | ++-------+-------+-------+-------+ +<------------32 bits------------>\fP +.DE +.NH 2 +\&Enumeration +.IX XDR enumeration +.LP +Enumerations have the same representation as signed integers. +Enumerations are handy for describing subsets of the integers. +Enumerated data is declared as follows: +.ft CW +.DS +enum { name-identifier = constant, ... } identifier; +.DE +For example, the three colors red, yellow, and blue could be +described by an enumerated type: +.DS +.ft CW +enum { RED = 2, YELLOW = 3, BLUE = 5 } colors; +.DE +It is an error to encode as an enum any other integer than those that +have been given assignments in the enum declaration. +.NH 2 +\&Boolean +.IX XDR boolean +.LP +Booleans are important enough and occur frequently enough to warrant +their own explicit type in the standard. Booleans are declared as +follows: +.DS +.ft CW +bool identifier; +.DE +This is equivalent to: +.DS +.ft CW +enum { FALSE = 0, TRUE = 1 } identifier; +.DE +.NH 2 +\&Hyper Integer and Unsigned Hyper Integer +.IX XDR "hyper integer" +.IX XDR "integer, hyper" +.LP +The standard also defines 64-bit (8-byte) numbers called hyper +integer and unsigned hyper integer. Their representations are the +obvious extensions of integer and unsigned integer defined above. +They are represented in two's complement notation. The most and +least significant bytes are 0 and 7, respectively. Their +declarations: +.ie t .DS +.el .DS L +\fIHyper Integer\fP +\fIUnsigned Hyper Integer\fP + +\f(CW(MSB) (LSB) ++-------+-------+-------+-------+-------+-------+-------+-------+ +|byte 0 |byte 1 |byte 2 |byte 3 |byte 4 |byte 5 |byte 6 |byte 7 | ++-------+-------+-------+-------+-------+-------+-------+-------+ +<----------------------------64 bits---------------------------->\fP +.DE +.NH 2 +\&Floating-point +.IX XDR "integer, floating point" +.IX XDR "floating-point integer" +.LP +The standard defines the floating-point data type "float" (32 bits or +4 bytes). The encoding used is the IEEE standard for normalized +single-precision floating-point numbers [3]. The following three +fields describe the single-precision floating-point number: +.RS +.IP \fBS\fP: +The sign of the number. Values 0 and 1 represent positive and +negative, respectively. One bit. +.IP \fBE\fP: +The exponent of the number, base 2. 8 bits are devoted to this +field. The exponent is biased by 127. +.IP \fBF\fP: +The fractional part of the number's mantissa, base 2. 23 bits +are devoted to this field. +.RE +.LP +Therefore, the floating-point number is described by: +.DS +(-1)**S * 2**(E-Bias) * 1.F +.DE +It is declared as follows: +.ie t .DS +.el .DS L +\fISingle-Precision Floating-Point\fP + +\f(CW+-------+-------+-------+-------+ +|byte 0 |byte 1 |byte 2 |byte 3 | +S| E | F | ++-------+-------+-------+-------+ +1|<- 8 ->|<-------23 bits------>| +<------------32 bits------------>\fP +.DE +Just as the most and least significant bytes of a number are 0 and 3, +the most and least significant bits of a single-precision floating- +point number are 0 and 31. The beginning bit (and most significant +bit) offsets of S, E, and F are 0, 1, and 9, respectively. Note that +these numbers refer to the mathematical positions of the bits, and +NOT to their actual physical locations (which vary from medium to +medium). +.LP +The IEEE specifications should be consulted concerning the encoding +for signed zero, signed infinity (overflow), and denormalized numbers +(underflow) [3]. According to IEEE specifications, the "NaN" (not a +number) is system dependent and should not be used externally. +.NH 2 +\&Double-precision Floating-point +.IX XDR "integer, double-precision floating point" +.IX XDR "double-precision floating-point integer" +.LP +The standard defines the encoding for the double-precision floating- +point data type "double" (64 bits or 8 bytes). The encoding used is +the IEEE standard for normalized double-precision floating-point +numbers [3]. The standard encodes the following three fields, which +describe the double-precision floating-point number: +.RS +.IP \fBS\fP: +The sign of the number. Values 0 and 1 represent positive and +negative, respectively. One bit. +.IP \fBE\fP: +The exponent of the number, base 2. 11 bits are devoted to this +field. The exponent is biased by 1023. +.IP \fBF\fP: +The fractional part of the number's mantissa, base 2. 52 bits +are devoted to this field. +.RE +.LP +Therefore, the floating-point number is described by: +.DS +(-1)**S * 2**(E-Bias) * 1.F +.DE +It is declared as follows: +.ie t .DS +.el .DS L +\fIDouble-Precision Floating-Point\fP + +\f(CW+------+------+------+------+------+------+------+------+ +|byte 0|byte 1|byte 2|byte 3|byte 4|byte 5|byte 6|byte 7| +S| E | F | ++------+------+------+------+------+------+------+------+ +1|<--11-->|<-----------------52 bits------------------->| +<-----------------------64 bits------------------------->\fP +.DE +Just as the most and least significant bytes of a number are 0 and 3, +the most and least significant bits of a double-precision floating- +point number are 0 and 63. The beginning bit (and most significant +bit) offsets of S, E , and F are 0, 1, and 12, respectively. Note +that these numbers refer to the mathematical positions of the bits, +and NOT to their actual physical locations (which vary from medium to +medium). +.LP +The IEEE specifications should be consulted concerning the encoding +for signed zero, signed infinity (overflow), and denormalized numbers +(underflow) [3]. According to IEEE specifications, the "NaN" (not a +number) is system dependent and should not be used externally. +.NH 2 +\&Fixed-length Opaque Data +.IX XDR "fixed-length opaque data" +.IX XDR "opaque data, fixed length" +.LP +At times, fixed-length uninterpreted data needs to be passed among +machines. This data is called "opaque" and is declared as follows: +.DS +.ft CW +opaque identifier[n]; +.DE +where the constant n is the (static) number of bytes necessary to +contain the opaque data. If n is not a multiple of four, then the n +bytes are followed by enough (0 to 3) residual zero bytes, r, to make +the total byte count of the opaque object a multiple of four. +.ie t .DS +.el .DS L +\fIFixed-Length Opaque\fP + +\f(CW0 1 ... ++--------+--------+...+--------+--------+...+--------+ +| byte 0 | byte 1 |...|byte n-1| 0 |...| 0 | ++--------+--------+...+--------+--------+...+--------+ +|<-----------n bytes---------->|<------r bytes------>| +|<-----------n+r (where (n+r) mod 4 = 0)------------>|\fP +.DE +.NH 2 +\&Variable-length Opaque Data +.IX XDR "variable-length opaque data" +.IX XDR "opaque data, variable length" +.LP +The standard also provides for variable-length (counted) opaque data, +defined as a sequence of n (numbered 0 through n-1) arbitrary bytes +to be the number n encoded as an unsigned integer (as described +below), and followed by the n bytes of the sequence. +.LP +Byte m of the sequence always precedes byte m+1 of the sequence, and +byte 0 of the sequence always follows the sequence's length (count). +enough (0 to 3) residual zero bytes, r, to make the total byte count +a multiple of four. Variable-length opaque data is declared in the +following way: +.DS +.ft CW +opaque identifier; +.DE +or +.DS +.ft CW +opaque identifier<>; +.DE +The constant m denotes an upper bound of the number of bytes that the +sequence may contain. If m is not specified, as in the second +declaration, it is assumed to be (2**32) - 1, the maximum length. +The constant m would normally be found in a protocol specification. +For example, a filing protocol may state that the maximum data +transfer size is 8192 bytes, as follows: +.DS +.ft CW +opaque filedata<8192>; +.DE +This can be illustrated as follows: +.ie t .DS +.el .DS L +\fIVariable-Length Opaque\fP + +\f(CW0 1 2 3 4 5 ... ++-----+-----+-----+-----+-----+-----+...+-----+-----+...+-----+ +| length n |byte0|byte1|...| n-1 | 0 |...| 0 | ++-----+-----+-----+-----+-----+-----+...+-----+-----+...+-----+ +|<-------4 bytes------->|<------n bytes------>|<---r bytes--->| +|<----n+r (where (n+r) mod 4 = 0)---->|\fP +.DE +.LP +It is an error to encode a length greater than the maximum +described in the specification. +.NH 2 +\&String +.IX XDR string +.LP +The standard defines a string of n (numbered 0 through n-1) ASCII +bytes to be the number n encoded as an unsigned integer (as described +above), and followed by the n bytes of the string. Byte m of the +string always precedes byte m+1 of the string, and byte 0 of the +string always follows the string's length. If n is not a multiple of +four, then the n bytes are followed by enough (0 to 3) residual zero +bytes, r, to make the total byte count a multiple of four. Counted +byte strings are declared as follows: +.DS +.ft CW +string object; +.DE +or +.DS +.ft CW +string object<>; +.DE +The constant m denotes an upper bound of the number of bytes that a +string may contain. If m is not specified, as in the second +declaration, it is assumed to be (2**32) - 1, the maximum length. +The constant m would normally be found in a protocol specification. +For example, a filing protocol may state that a file name can be no +longer than 255 bytes, as follows: +.DS +.ft CW +string filename<255>; +.DE +Which can be illustrated as: +.ie t .DS +.el .DS L +\fIA String\fP + +\f(CW0 1 2 3 4 5 ... ++-----+-----+-----+-----+-----+-----+...+-----+-----+...+-----+ +| length n |byte0|byte1|...| n-1 | 0 |...| 0 | ++-----+-----+-----+-----+-----+-----+...+-----+-----+...+-----+ +|<-------4 bytes------->|<------n bytes------>|<---r bytes--->| +|<----n+r (where (n+r) mod 4 = 0)---->|\fP +.DE +.LP +It is an error to encode a length greater than the maximum +described in the specification. +.NH 2 +\&Fixed-length Array +.IX XDR "fixed-length array" +.IX XDR "array, fixed length" +.LP +Declarations for fixed-length arrays of homogeneous elements are in +the following form: +.DS +.ft CW +type-name identifier[n]; +.DE +Fixed-length arrays of elements numbered 0 through n-1 are encoded by +individually encoding the elements of the array in their natural +order, 0 through n-1. Each element's size is a multiple of four +bytes. Though all elements are of the same type, the elements may +have different sizes. For example, in a fixed-length array of +strings, all elements are of type "string", yet each element will +vary in its length. +.ie t .DS +.el .DS L +\fIFixed-Length Array\fP + +\f(CW+---+---+---+---+---+---+---+---+...+---+---+---+---+ +| element 0 | element 1 |...| element n-1 | ++---+---+---+---+---+---+---+---+...+---+---+---+---+ +|<--------------------n elements------------------->|\fP +.DE +.NH 2 +\&Variable-length Array +.IX XDR "variable-length array" +.IX XDR "array, variable length" +.LP +Counted arrays provide the ability to encode variable-length arrays +of homogeneous elements. The array is encoded as the element count n +(an unsigned integer) followed by the encoding of each of the array's +elements, starting with element 0 and progressing through element n- +1. The declaration for variable-length arrays follows this form: +.DS +.ft CW +type-name identifier; +.DE +or +.DS +.ft CW +type-name identifier<>; +.DE +The constant m specifies the maximum acceptable element count of an +array; if m is not specified, as in the second declaration, it is +assumed to be (2**32) - 1. +.ie t .DS +.el .DS L +\fICounted Array\fP + +\f(CW0 1 2 3 ++--+--+--+--+--+--+--+--+--+--+--+--+...+--+--+--+--+ +| n | element 0 | element 1 |...|element n-1| ++--+--+--+--+--+--+--+--+--+--+--+--+...+--+--+--+--+ +|<-4 bytes->|<--------------n elements------------->|\fP +.DE +It is an error to encode a value of n that is greater than the +maximum described in the specification. +.NH 2 +\&Structure +.IX XDR structure +.LP +Structures are declared as follows: +.DS +.ft CW +struct { + component-declaration-A; + component-declaration-B; + \&... +} identifier; +.DE +The components of the structure are encoded in the order of their +declaration in the structure. Each component's size is a multiple of +four bytes, though the components may be different sizes. +.ie t .DS +.el .DS L +\fIStructure\fP + +\f(CW+-------------+-------------+... +| component A | component B |... ++-------------+-------------+...\fP +.DE +.NH 2 +\&Discriminated Union +.IX XDR "discriminated union" +.IX XDR union discriminated +.LP +A discriminated union is a type composed of a discriminant followed +by a type selected from a set of prearranged types according to the +value of the discriminant. The type of discriminant is either "int", +"unsigned int", or an enumerated type, such as "bool". The component +types are called "arms" of the union, and are preceded by the value +of the discriminant which implies their encoding. Discriminated +unions are declared as follows: +.DS +.ft CW +union switch (discriminant-declaration) { + case discriminant-value-A: + arm-declaration-A; + case discriminant-value-B: + arm-declaration-B; + \&... + default: default-declaration; +} identifier; +.DE +Each "case" keyword is followed by a legal value of the discriminant. +The default arm is optional. If it is not specified, then a valid +encoding of the union cannot take on unspecified discriminant values. +The size of the implied arm is always a multiple of four bytes. +.LP +The discriminated union is encoded as its discriminant followed by +the encoding of the implied arm. +.ie t .DS +.el .DS L +\fIDiscriminated Union\fP + +\f(CW0 1 2 3 ++---+---+---+---+---+---+---+---+ +| discriminant | implied arm | ++---+---+---+---+---+---+---+---+ +|<---4 bytes--->|\fP +.DE +.NH 2 +\&Void +.IX XDR void +.LP +An XDR void is a 0-byte quantity. Voids are useful for describing +operations that take no data as input or no data as output. They are +also useful in unions, where some arms may contain data and others do +not. The declaration is simply as follows: +.DS +.ft CW +void; +.DE +Voids are illustrated as follows: +.ie t .DS +.el .DS L +\fIVoid\fP + +\f(CW ++ + || + ++ +--><-- 0 bytes\fP +.DE +.NH 2 +\&Constant +.IX XDR constant +.LP +The data declaration for a constant follows this form: +.DS +.ft CW +const name-identifier = n; +.DE +"const" is used to define a symbolic name for a constant; it does not +declare any data. The symbolic constant may be used anywhere a +regular constant may be used. For example, the following defines a +symbolic constant DOZEN, equal to 12. +.DS +.ft CW +const DOZEN = 12; +.DE +.NH 2 +\&Typedef +.IX XDR typedef +.LP +"typedef" does not declare any data either, but serves to define new +identifiers for declaring data. The syntax is: +.DS +.ft CW +typedef declaration; +.DE +The new type name is actually the variable name in the declaration +part of the typedef. For example, the following defines a new type +called "eggbox" using an existing type called "egg": +.DS +.ft CW +typedef egg eggbox[DOZEN]; +.DE +Variables declared using the new type name have the same type as the +new type name would have in the typedef, if it was considered a +variable. For example, the following two declarations are equivalent +in declaring the variable "fresheggs": +.DS +.ft CW +eggbox fresheggs; +egg fresheggs[DOZEN]; +.DE +When a typedef involves a struct, enum, or union definition, there is +another (preferred) syntax that may be used to define the same type. +In general, a typedef of the following form: +.DS +.ft CW +typedef <> identifier; +.DE +may be converted to the alternative form by removing the "typedef" +part and placing the identifier after the "struct", "union", or +"enum" keyword, instead of at the end. For example, here are the two +ways to define the type "bool": +.DS +.ft CW +typedef enum { /* \fIusing typedef\fP */ + FALSE = 0, + TRUE = 1 + } bool; + +enum bool { /* \fIpreferred alternative\fP */ + FALSE = 0, + TRUE = 1 + }; +.DE +The reason this syntax is preferred is one does not have to wait +until the end of a declaration to figure out the name of the new +type. +.NH 2 +\&Optional-data +.IX XDR "optional data" +.IX XDR "data, optional" +.LP +Optional-data is one kind of union that occurs so frequently that we +give it a special syntax of its own for declaring it. It is declared +as follows: +.DS +.ft CW +type-name *identifier; +.DE +This is equivalent to the following union: +.DS +.ft CW +union switch (bool opted) { + case TRUE: + type-name element; + case FALSE: + void; +} identifier; +.DE +It is also equivalent to the following variable-length array +declaration, since the boolean "opted" can be interpreted as the +length of the array: +.DS +.ft CW +type-name identifier<1>; +.DE +Optional-data is not so interesting in itself, but it is very useful +for describing recursive data-structures such as linked-lists and +trees. For example, the following defines a type "stringlist" that +encodes lists of arbitrary length strings: +.DS +.ft CW +struct *stringlist { + string item<>; + stringlist next; +}; +.DE +It could have been equivalently declared as the following union: +.DS +.ft CW +union stringlist switch (bool opted) { + case TRUE: + struct { + string item<>; + stringlist next; + } element; + case FALSE: + void; +}; +.DE +or as a variable-length array: +.DS +.ft CW +struct stringlist<1> { + string item<>; + stringlist next; +}; +.DE +Both of these declarations obscure the intention of the stringlist +type, so the optional-data declaration is preferred over both of +them. The optional-data type also has a close correlation to how +recursive data structures are represented in high-level languages +such as Pascal or C by use of pointers. In fact, the syntax is the +same as that of the C language for pointers. +.NH 2 +\&Areas for Future Enhancement +.IX XDR futures +.LP +The XDR standard lacks representations for bit fields and bitmaps, +since the standard is based on bytes. Also missing are packed (or +binary-coded) decimals. +.LP +The intent of the XDR standard was not to describe every kind of data +that people have ever sent or will ever want to send from machine to +machine. Rather, it only describes the most commonly used data-types +of high-level languages such as Pascal or C so that applications +written in these languages will be able to communicate easily over +some medium. +.LP +One could imagine extensions to XDR that would let it describe almost +any existing protocol, such as TCP. The minimum necessary for this +are support for different block sizes and byte-orders. The XDR +discussed here could then be considered the 4-byte big-endian member +of a larger XDR family. +.NH 1 +\&Discussion +.sp 2 +.NH 2 +\&Why a Language for Describing Data? +.IX XDR language +.LP +There are many advantages in using a data-description language such +as XDR versus using diagrams. Languages are more formal than +diagrams and lead to less ambiguous descriptions of data. +Languages are also easier to understand and allow one to think of +other issues instead of the low-level details of bit-encoding. +Also, there is a close analogy between the types of XDR and a +high-level language such as C or Pascal. This makes the +implementation of XDR encoding and decoding modules an easier task. +Finally, the language specification itself is an ASCII string that +can be passed from machine to machine to perform on-the-fly data +interpretation. +.NH 2 +\&Why Only one Byte-Order for an XDR Unit? +.IX XDR "byte order" +.LP +Supporting two byte-orderings requires a higher level protocol for +determining in which byte-order the data is encoded. Since XDR is +not a protocol, this can't be done. The advantage of this, though, +is that data in XDR format can be written to a magnetic tape, for +example, and any machine will be able to interpret it, since no +higher level protocol is necessary for determining the byte-order. +.NH 2 +\&Why does XDR use Big-Endian Byte-Order? +.LP +Yes, it is unfair, but having only one byte-order means you have to +be unfair to somebody. Many architectures, such as the Motorola +68000 and IBM 370, support the big-endian byte-order. +.NH 2 +\&Why is the XDR Unit Four Bytes Wide? +.LP +There is a tradeoff in choosing the XDR unit size. Choosing a small +size such as two makes the encoded data small, but causes alignment +problems for machines that aren't aligned on these boundaries. A +large size such as eight means the data will be aligned on virtually +every machine, but causes the encoded data to grow too big. We chose +four as a compromise. Four is big enough to support most +architectures efficiently, except for rare machines such as the +eight-byte aligned Cray. Four is also small enough to keep the +encoded data restricted to a reasonable size. +.NH 2 +\&Why must Variable-Length Data be Padded with Zeros? +.IX XDR "variable-length data" +.LP +It is desirable that the same data encode into the same thing on all +machines, so that encoded data can be meaningfully compared or +checksummed. Forcing the padded bytes to be zero ensures this. +.NH 2 +\&Why is there No Explicit Data-Typing? +.LP +Data-typing has a relatively high cost for what small advantages it +may have. One cost is the expansion of data due to the inserted type +fields. Another is the added cost of interpreting these type fields +and acting accordingly. And most protocols already know what type +they expect, so data-typing supplies only redundant information. +However, one can still get the benefits of data-typing using XDR. One +way is to encode two things: first a string which is the XDR data +description of the encoded data, and then the encoded data itself. +Another way is to assign a value to all the types in XDR, and then +define a universal type which takes this value as its discriminant +and for each value, describes the corresponding data type. +.NH 1 +\&The XDR Language Specification +.IX XDR language +.sp 1 +.NH 2 +\&Notational Conventions +.IX "XDR language" notation +.LP +This specification uses an extended Backus-Naur Form notation for +describing the XDR language. Here is a brief description of the +notation: +.IP 1. +The characters +.I | , +.I ( , +.I ) , +.I [ , +.I ] , +.I " , +and +.I * +are special. +.IP 2. +Terminal symbols are strings of any characters surrounded by +double quotes. +.IP 3. +Non-terminal symbols are strings of non-special characters. +.IP 4. +Alternative items are separated by a vertical bar ("\fI|\fP"). +.IP 5. +Optional items are enclosed in brackets. +.IP 6. +Items are grouped together by enclosing them in parentheses. +.IP 7. +A +.I * +following an item means 0 or more occurrences of that item. +.LP +For example, consider the following pattern: +.DS L +"a " "very" (", " " very")* [" cold " "and"] " rainy " ("day" | "night") +.DE +.LP +An infinite number of strings match this pattern. A few of them +are: +.DS +"a very rainy day" +"a very, very rainy day" +"a very cold and rainy day" +"a very, very, very cold and rainy night" +.DE +.NH 2 +\&Lexical Notes +.IP 1. +Comments begin with '/*' and terminate with '*/'. +.IP 2. +White space serves to separate items and is otherwise ignored. +.IP 3. +An identifier is a letter followed by an optional sequence of +letters, digits or underbar ('_'). The case of identifiers is +not ignored. +.IP 4. +A constant is a sequence of one or more decimal digits, +optionally preceded by a minus-sign ('-'). +.NH 2 +\&Syntax Information +.IX "XDR language" syntax +.DS +.ft CW +declaration: + type-specifier identifier + | type-specifier identifier "[" value "]" + | type-specifier identifier "<" [ value ] ">" + | "opaque" identifier "[" value "]" + | "opaque" identifier "<" [ value ] ">" + | "string" identifier "<" [ value ] ">" + | type-specifier "*" identifier + | "void" +.DE +.DS +.ft CW +value: + constant + | identifier + +type-specifier: + [ "unsigned" ] "int" + | [ "unsigned" ] "hyper" + | "float" + | "double" + | "bool" + | enum-type-spec + | struct-type-spec + | union-type-spec + | identifier +.DE +.DS +.ft CW +enum-type-spec: + "enum" enum-body + +enum-body: + "{" + ( identifier "=" value ) + ( "," identifier "=" value )* + "}" +.DE +.DS +.ft CW +struct-type-spec: + "struct" struct-body + +struct-body: + "{" + ( declaration ";" ) + ( declaration ";" )* + "}" +.DE +.DS +.ft CW +union-type-spec: + "union" union-body + +union-body: + "switch" "(" declaration ")" "{" + ( "case" value ":" declaration ";" ) + ( "case" value ":" declaration ";" )* + [ "default" ":" declaration ";" ] + "}" + +constant-def: + "const" identifier "=" constant ";" +.DE +.DS +.ft CW +type-def: + "typedef" declaration ";" + | "enum" identifier enum-body ";" + | "struct" identifier struct-body ";" + | "union" identifier union-body ";" + +definition: + type-def + | constant-def + +specification: + definition * +.DE +.NH 3 +\&Syntax Notes +.IX "XDR language" syntax +.LP +.IP 1. +The following are keywords and cannot be used as identifiers: +"bool", "case", "const", "default", "double", "enum", "float", +"hyper", "opaque", "string", "struct", "switch", "typedef", "union", +"unsigned" and "void". +.IP 2. +Only unsigned constants may be used as size specifications for +arrays. If an identifier is used, it must have been declared +previously as an unsigned constant in a "const" definition. +.IP 3. +Constant and type identifiers within the scope of a specification +are in the same name space and must be declared uniquely within this +scope. +.IP 4. +Similarly, variable names must be unique within the scope of +struct and union declarations. Nested struct and union declarations +create new scopes. +.IP 5. +The discriminant of a union must be of a type that evaluates to +an integer. That is, "int", "unsigned int", "bool", an enumerated +type or any typedefed type that evaluates to one of these is legal. +Also, the case values must be one of the legal values of the +discriminant. Finally, a case value may not be specified more than +once within the scope of a union declaration. +.NH 1 +\&An Example of an XDR Data Description +.LP +Here is a short XDR data description of a thing called a "file", +which might be used to transfer files from one machine to another. +.ie t .DS +.el .DS L +.ft CW + +const MAXUSERNAME = 32; /*\fI max length of a user name \fP*/ +const MAXFILELEN = 65535; /*\fI max length of a file \fP*/ +const MAXNAMELEN = 255; /*\fI max length of a file name \fP*/ + +.ft I +/* + * Types of files: + */ +.ft CW + +enum filekind { + TEXT = 0, /*\fI ascii data \fP*/ + DATA = 1, /*\fI raw data \fP*/ + EXEC = 2 /*\fI executable \fP*/ +}; + +.ft I +/* + * File information, per kind of file: + */ +.ft CW + +union filetype switch (filekind kind) { + case TEXT: + void; /*\fI no extra information \fP*/ + case DATA: + string creator; /*\fI data creator \fP*/ + case EXEC: + string interpretor; /*\fI program interpretor \fP*/ +}; + +.ft I +/* + * A complete file: + */ +.ft CW + +struct file { + string filename; /*\fI name of file \fP*/ + filetype type; /*\fI info about file \fP*/ + string owner; /*\fI owner of file \fP*/ + opaque data; /*\fI file data \fP*/ +}; +.DE +.LP +Suppose now that there is a user named "john" who wants to store +his lisp program "sillyprog" that contains just the data "(quit)". +His file would be encoded as follows: +.TS +box tab (&) ; +lfI lfI lfI lfI +rfL rfL rfL l . +Offset&Hex Bytes&ASCII&Description +_ +0&00 00 00 09&....&Length of filename = 9 +4&73 69 6c 6c&sill&Filename characters +8&79 70 72 6f&ypro& ... and more characters ... +12&67 00 00 00&g...& ... and 3 zero-bytes of fill +16&00 00 00 02&....&Filekind is EXEC = 2 +20&00 00 00 04&....&Length of interpretor = 4 +24&6c 69 73 70&lisp&Interpretor characters +28&00 00 00 04&....&Length of owner = 4 +32&6a 6f 68 6e&john&Owner characters +36&00 00 00 06&....&Length of file data = 6 +40&28 71 75 69&(qui&File data bytes ... +44&74 29 00 00&t)..& ... and 2 zero-bytes of fill +.TE +.NH 1 +\&References +.LP +[1] Brian W. Kernighan & Dennis M. Ritchie, "The C Programming +Language", Bell Laboratories, Murray Hill, New Jersey, 1978. +.LP +[2] Danny Cohen, "On Holy Wars and a Plea for Peace", IEEE Computer, +October 1981. +.LP +[3] "IEEE Standard for Binary Floating-Point Arithmetic", ANSI/IEEE +Standard 754-1985, Institute of Electrical and Electronics +Engineers, August 1985. +.LP +[4] "Courier: The Remote Procedure Call Protocol", XEROX +Corporation, XSIS 038112, December 1981.