BSD 4_4_Lite2 development
authorCSRG <csrg@ucbvax.Berkeley.EDU>
Mon, 16 Mar 1987 23:33:51 +0000 (15:33 -0800)
committerCSRG <csrg@ucbvax.Berkeley.EDU>
Mon, 16 Mar 1987 23:33:51 +0000 (15:33 -0800)
Work on file usr/src/contrib/xns/doc/courier.tbl.me

Synthesized-from: CSRG/cd3/4.4BSD-Lite2

usr/src/contrib/xns/doc/courier.tbl.me [new file with mode: 0644]

diff --git a/usr/src/contrib/xns/doc/courier.tbl.me b/usr/src/contrib/xns/doc/courier.tbl.me
new file mode 100644 (file)
index 0000000..5bbcd9c
--- /dev/null
@@ -0,0 +1,1759 @@
+\" $Header: courier.tbl.me,v 1.8 87/03/17 09:32:52 ed Exp $
+\" $Header: courier.tbl.me,v 1.8 87/03/17 09:32:52 ed Exp $
+\" $Log:       courier.tbl.me,v $
+\" Revision 1.8  87/03/17  09:32:52  ed
+\" Added -I switch to establish search path for DEPENDS UPON files.
+\" 
+\" Revision 1.7  86/11/22  07:12:05  jqj
+\" small changes reflecting differences between 4.3bsd and 4.2 Maryland XNS.
+\" 
+\" Revision 1.6  86/05/12  09:02:43  jqj
+\" minor revisions
+\" 
+\" Revision 1.5  85/03/26  06:30:58  jqj
+\" Revised public alpha-test version, released 26 March 1985
+\" 
+\" Revision 1.4  85/03/11  16:46:01  jqj
+\" Public alpha-test version, released 11 March 1985
+\" 
+.lg 0
+.fo ''%''
+.(l C
+.ps +4
+.b
+XNS Courier under UNIX (4.3BSD)
+.ps -4
+.sp 2
+.r
+.he $ XNS Courier $ $Date: 87/03/17 09:32:52 $
+J.Q. Johnson
+Computer Science Department
+Cornell University
+405 Upson Hall
+Ithaca, NY  14853
+.)l
+.sh 1 "Introduction"
+.pp
+This document describes the implementation and use
+of the Courier remote procedure call protocol for Berkeley UNIX
+(version 4.3), with XNS.
+.sp 1
+.nf
+.b
+.(c
+********************************************************
+Warning:  this document is a DRAFT design specification.
+It has not yet been fully implemented, and the details of
+the interface it specifies (particularly with respect to 
+Courier servers) are subject to change.  Send design
+comments to jqj@cornell.ARPA or jqj@cornell.UUCP.
+********************************************************
+.)c
+.r
+.fi
+.sp 1
+.uh "Changes since previous revision:"
+.np
+Added discussion of encapsulated protocols.
+.np
+Added discussion of Clearinghouse library calls for finding a connection,
+and relationship of connections to outstanding RPCs.
+.uh "Earlier changes:"
+.np
+Changed names of generated file names to include version number.
+Added discussion on scope of names vs. DEPENDS UPON modules.
+.np
+Users must now link Courier program ``Foo'' with Foo1_support.o.
+.np
+Added some discussion of freeing dynamic storage via clear_Foo.
+.np
+Changed
+.i CourierOpen()
+to take ns_addr structure rather than a string host name.  Routines for
+converting from name to address (using Clearinghouse) have not yet been
+designed. 
+.np
+Changed ERROR discussion to indicate that an ERROR is mapped to a long
+with value > 65535.
+.np
+Changed description of BDT to reflect use of 
+.i BDTread()
+and
+.i BDTwrite() .
+.np
+Added SPPMAXDATA.
+.np
+Changed discussion of BulkData constants to indicate that things now
+work correctly.
+.sh 2 "General Description"
+.pp
+Courier is both a protocol and a specification language.
+The protocol describes how remote procedures are invoked and
+how parameters and results of various data types are transmitted.
+The specification language, somewhat reminiscent of Mesa,
+provides a simple way of defining the remote interfaces of distributed
+programs.
+.pp
+This implementation is an attempt to allow UNIX C programmers to
+construct distributed Courier programs to communicate with other
+Courier implementations layered on XNS.  It thus requires kernel
+support for XNS Sequenced Packet Protocol sockets.
+.pp
+The simplest form of distributed program using Courier
+consists of three parts.
+The first is the specification,
+written in the Courier language.
+The specification must declare all procedures of
+the program that will be called remotely (the
+.i "server procedures" ).
+The next part is the implementation, in C, of the server procedures.
+Finally, there is the client portion of the program, also in C, which calls
+the server procedures.
+Note that either the client or server portion of the program may be
+omitted if the UNIX program is designed to act with a non-UNIX
+partner; for example, a simple printing client under UNIX might send
+files to a Xerox laser printer using the Printing protocol published
+and supported by Xerox.
+.sh 2 "Acknowledgements"
+.pp
+Contributors to the current implementation have included
+JQ Johnson, the primary implementor of the current version of the
+Courier compiler under UNIX,
+Eric Cooper, the author of the original UNIX Courier/TCP compiler,
+Jeff Mogul, the author of the exception signalling mechanism used
+here to handle abort and reject messages,
+James O'Toole, Chris Torek, and Mark Weiser,
+the authors of the 4.2BSD version of UNIX kernel support for XNS,
+Keith Sklower, the author of the present (4.3BSD) version of UNIX kernel support,
+Lee Moore, and Bill Nesheim.
+.pp
+Some of the software is copyright 1984 by Eric Cooper, and is for
+research use only; it may be freely distributed but may not be
+sold as a commercial product.
+.sh 2 "References"
+.pp
+This document assumes familiarity with the Courier language,
+details of which may be found in
+the Courier protocol definition,
+.i "Courier: The Remote Procedure Call Protocol" ,
+Xerox System Integration Standard 038112,
+December 1981, and ``Appendix F, Bulk Data Transfer,'' Xerox System
+Integration Standard 038112 Add. 1, October 1982.
+However, for reference purposes, a grammar for the Courier language
+may be found in the appendix to this document.
+.pp
+Users of Bulk Data Transfer will need to interact with SPP 
+(Sequenced Packet Protocol)
+streams, and may wish to see
+.i "Internet Transport Protocols" ,
+Xerox System Integration Standard 028112, December 1981.
+.pp
+Those interested in the workings of UNIX networking might consult
+``A 4.2bsd Interprocess Communications Primer,''
+(DRAFT of March 11, 1983), by Samuel J. Leffler, Robert S. Fabry, and
+William N. Joy, and the 
+``4.2BSD System Manual,''
+(Revised July, 1983), by William Joy 
+.i "et al" .
+.pp
+This document is also supplemented by various UNIX manual pages, notably
+.i "xnscourier(3) " ,
+and
+.i "except(3)" .
+.sh 2 "Software and Hardware Dependencies"
+.pp
+This package requires the following software to operate:
+.(l
+4.3BSD (currently tested only for VAX and Gould versions)
+PCC, 4.3BSD version (probably\-limited testing has been done with Tartan C)
+.)l
+.pp
+To communicate with Xerox products you will need an Ethernet
+interface.  Most of the existing example applications depend heavily on
+the existence of a Xerox Clearinghouse somewhere on the local network.
+.sh 1 "The Courier Specification Language"
+.pp
+The Courier specification language is documented (loosely) in the Xerox
+``Courier: The Remote Procedure Call Protocol.''  Our compiler
+implements a large subset of the specification.  Note that the embedding
+of Courier constructs in a message or bit stream is usually irrelevant to the
+applications programmer, who need only be concerned with the semantics
+of the Courier datatypes.  The courier compiler and runtimes translate
+Courier datatypes into more easily managed C constructs and handle the
+protocol of communication with the remote system.
+.pp
+This implementation places several restrictions on the specifications
+it will accept:
+.np
+No forward references are permitted in the courier
+specification.  The user must reorder type specifications so that all
+types and constants are defined before they are used.
+Special provisions are made for recursive type declarations of the form
+typically found in Bulk Data ``StreamOf'' declarations; see the
+discussion of BDT below for details.  General recursive declarations,
+e.g. that of the Filing ``Filter,'' are not supported.
+.np
+The Courier specification is unclear as to lexical issues, 
+name scope, and name overloading.
+In the current compiler, names should contain only upper and lower case
+letters or digits.  Also, all constants, types, and
+enumeration tags within a module should have distinct names.  Enumeration
+tag names are currently global; thus, different enumeration types, even
+in different modules, may not have the same tags.
+.np
+Constants of type CHOICE are not fully supported.  In particular, they
+are allowed only if the active arm of the choice is a nill record.
+.np
+Constants of type SEQUENCE are not supported.  Constants of type ARRAY
+(which have the same syntax as SEQUENCE constants) are supported, but are
+not rangechecked; ``Vect: ARRAY 10 OF INTEGER = {1,2,3}'' is accepted,
+though illegal.
+.np
+This implementation supports DEPENDS UPON and
+referenced types and constants.  However, the semantics of referenced
+enumeration constants is unclear.  Also, this implementation
+does not support recursive dependencies.
+.lp
+We think we've eliminated all other restrictions, but we're probably wrong.
+.sh 2 "Predefined types"
+.pp
+The following typedefs correspond to predefined Courier types:
+.(b
+.TS
+c c c
+l l l.
+Courier type   C typedef       Implemented (on VAX) as
+
+BOOLEAN        Boolean char (0==TRUE, 1==FALSE)
+CARDINAL       Cardinal        unsigned short
+LONG CARDINAL  LongCardinal    unsigned long
+INTEGER        Integer short
+LONG INTEGER   LongInteger     long
+STRING String  char*
+UNSPECIFIED    Unspecified     unsigned short
+LONG UNSPECIFIED       LongUnspecified unsigned long
+.TE
+.)b
+Note that the representations of these types as seen by the applications
+programmer are quite different from those sent over a Courier SPP connection.
+For example, on the VAX a Cardinal's bytes are swapped before being
+sent out in network byte order.  A UNIX String corresponds to a pointer to
+a null-terminated array of characters while a Courier serialized STRING
+contains a count directly followed by an array of characters.  Note
+in particular that this implementation does
+not support Courier STRINGs containing the ascii character NUL.
+.sh 2 "Constructed types"
+.pp
+The Courier enumeration, array, and (non-null) record types
+correspond to C enumerations, arrays, and structures respectively.
+The Courier null record corresponds to an int.
+.pp
+The Courier sequence and choice types
+pose some problems when they are mapped into C.
+This is because an object of one of these types
+must contain run-time information (the length of the sequence
+or which choice is present)
+that is implicit in Courier, but must be made explicit
+in C.
+Furthermore, the C programmer must bear the responsibility of keeping
+this information consistent.
+.pp
+A sequence type is mapped into a structure consisting
+of a Cardinal called
+.i "length" ,
+and a pointer to the sequence elements called
+.i "sequence" .
+The consistency requirement is that
+.i "length"
+indicate the number of elements in the array pointed to
+by
+.i "sequence" .
+.pp
+A choice type is mapped into a structure consisting
+of an enumeration element called
+.i "designator" ,
+and a union of all the possible choices.
+The designator is of the enumeration type defined
+(implicitly or explicitly)
+in the declaration of the choice type.
+If this enumeration consists of elements
+.i A ,
+.i B ,
+and
+.i C ,
+then the choices are accessible as
+.i A_case ,
+.i B_case ,
+and
+.i C_case .
+The consistency requirement is that
+.i "designator"
+contain the enumeration value corresponding to
+which choice currently occupies the union.
+.sh 2 "Constants"
+.pp
+The Courier specification is silent on the precise representations of
+numbers and strings.  In this implementation, numbers may be represented
+as an optional minus sign followed by a
+sequence of decimal digits or a sequence of octal digits followed
+by ``B''.  A constant of type STRING 
+is represented by a sequence of characters enclosed in
+double quotation marks; it has the same restrictions as a C literal string (no
+embedded newlines, use ``\e'' as a prefix character, etc.) except that double
+double quotes are permitted to indicate quotations inside the string.
+.sp 1
+.nf
+minInt: INTEGER = -2147483648;         -- or 20000000000B --
+lastCard: CARDINAL = 177777B;          -- or 65535 --
+quotedName: STRING = "my name is \"jqj""\en";
+.fi
+.sp 1
+.pp
+Although the Courier language allows constants of
+any type to be declared,
+it is difficult to define constants of constructed types
+in C programs.
+This implementation normally handles constants by creating
+static variables initialized to the values of the (possibly structured)
+constants.  The technique is not in general possible for structured 
+constants of type CHOICE
+since they are implemented as C ``unions'', so this implementation does not
+support constants which contain a non-null CHOICE.  As a special case,
+constants whose selected variant is a null record are allowed, e.g.
+.sp 1
+       myImmediate: BulkData.Immediate = immediate [];
+.sp 1
+.sh 2 "Procedures"
+.pp
+PROCEDURE constants correspond to C functions plus a numeric jump table
+index used in serialization of procedure calls on the network.
+Procedure declarations may include argument lists, result lists, and
+error lists.  The argument list of the corresponding C function 
+contains one parameter for each parameter specified in the declaration,
+but is
+expanded by the addition of 2 parameters (see below).  The result list
+specifies a C structure to be returned by the C function containing the
+results; for a procedure, Foo, this result list is of type FooResults.
+Thus, a procedure declared
+.sp 1
+       Example: PROCEDURE [ ] RETURNS [ a: Foo, b: Baz ] = 0;
+.sp 1
+would be implemented by a C function with no arguments
+returning a structure of type ExampleResults with definition
+something like
+.sp 1
+.nf
+       typedef struct {
+               Foo a;
+               Baz b;
+       } ExampleResults
+.fi
+.sp 1
+Note that Foo and Baz might be arbitrarily complex structures;
+see ``Constructed Types'' above.
+.pp
+Importing a PROCEDURE from a DEPENDS UPON module imports only its jump
+table index.
+.sh 2 "ERROR constants"
+.pp
+An ERROR constant corresponds to a defined numeric value, and possibly
+to a C structure describing its argument list.  For example,
+.sp 1
+       OtherError: ERROR [ errorstring: STRING ] = 1;
+.sp 1
+corresponds approximately to the C declarations:
+.sp 1
+.nf
+       #define OtherError (1+ERROR_OFFSET)
+       typedef struct {
+               String errorstring;
+       } OtherErrorArgs;
+.fi
+.sp 1
+Note that ERROR values must be specified in the range 0 to 65535, but
+that they produce a symbol which appears to the C programmer
+with values of type int
+in the range 65536 to 131171 (in some contexts, the user may wish to
+treat C error values in the range 0 to 65535 as Unix error numbers).
+The structure corresponding to the error
+may be useful in creating and referencing error arguments
+in C functions.
+.sh 2 "Scope of Names"
+.pp
+Given a courier program beginning
+.sp
+       Example : PROGRAM 537 VERSION 1 =
+.sp
+types and constants declared in this program belong to the module ``Example1'';
+Their full names have prefixed to them the program name and version number.
+Given the declaration of ``minInt'' above, the XNS Courier compiler actually
+produces a header file (here
+.i Example1.h )
+containing the C declaration:
+.sp 1
+.nf
+static Integer Example1_minInt = {-2147483648};
+.fi
+.sp 1
+.pp
+This prefixing implies that a C programmer may refer to several different
+remote Courier programs without risk of name collision.
+For programmer convenience a second header file (here
+.i Example1_defs.h )
+is also created by the compiler containing macro definitions which widen
+the scope to ``Example1'' and allow the programmer to refer to types and
+constants without explicit qualification.  The programmer may include
+whichever header file he prefers.
+.pp
+Note that types and constants obtained from a DEPENDS UPON inclusion must
+be referenced using fully qualified form.  Note also that structure members,
+procedure arguments, and enum tags are not currently qualified; beware
+potential name conflicts!
+.sh 1 "Running the Courier Compiler"
+.pp
+The specification file is expected to have the extension
+.i ".cr" .
+Consider the following skeletal Courier program definition:
+.sp
+       Example : PROGRAM 537 VERSION 1 = BEGIN ... END.
+.sp
+The name of this Courier program is ``Example''; it is version 1.
+By convention, this specification would stored in the file
+.i "Example1.cr" .
+The ``537'' in this example is illustrative only;
+the program number is uniquely allocated from the range of LongCardinals
+by Xerox.  See Appendix B of the Courier standard for program number
+assignment procedures.
+.pp
+The first step is to use the Courier compiler on the specification.
+Assuming
+.i "xnscourier"
+is in your path (it is 
+.i "/usr/new/xnscourier"
+on the Berkeley distribution), type
+.sp 1
+       xnscourier Example1.cr
+.sp 1
+If there are no errors in compilation,
+the following files will be produced:
+.(b
+.TS
+c c
+l l.
+File   Contents
+
+Example1_defs.h        scope widening macros (includes Example1.h)
+Example1.h     definitions, typedefs, and constant declarations
+Example1_support.c     routines to map between C and Courier
+Example1_server.c      server main program and support routines
+Example1_client.c      client routines to support applications calling Example
+.TE
+.)b
+.pp
+The header file
+.i "Example1_defs.h"
+should be included via
+.sp
+       #include ``Example1_defs.h''
+.sp
+in all user-written parts of the Courier program
+(i.e., the implementations of the client program and server procedures.)
+.pp
+The 
+.i "\fB-I\fIinclude-directory"
+switch may be used on the command line of
+.i xnscourier
+to establish a search path for Courier specification files named in a
+DEPENDS UPON clause. By default, the compiler looks first in the current
+directory followed by the courier-description-file named in the 
+.i /etc/Courierservices
+file.
+.sh 2 "Calling Remote Procedures \- Client Implementation"
+.pp
+A remote procedure appears to the C programmer to look almost like
+a local procedure (although it typically makes use of resources not
+available on the local machine or else runs many times slower
+than would a local procedure).  A C client program (i.e. the caller
+of a remote procedure) actually calls a stub function generated by
+the courier compiler, which in turn executes the code necessary to
+invoke the corresponding procedure on the remote machine.  
+.pp
+Before calling the remote procedure it is necessary to establish a
+Courier SPP connection (in the process binding a local socket to
+a remote machine).  This may be done by the calling the function
+CourierOpen(), passing it as argument a structure identifying the remote
+host.  This structure
+is of type ``ns_addr''
+as defined in the include file
+.i "#include <netns/ns.h>" .
+CourierOpen() returns an anonymous pointer
+(of type CourierConnection*) to a structure containing
+the socket to be used for this courier call, which should be passed
+to each remote procedure to be executed on the particular remote host.
+CourierOpen() returns NULL if there is a problem opening an SPP connection
+to the host specified.
+.pp
+For low-level access to ns_addr structures,
+the procedure getXNSaddr() is available, taking a string in the form
+``network#a1.a2.a3.a4.a5.a6#socket'' (where all numbers are hex) or 
+``networkhigh-networklow#d1-d2-d3-d4#socket'' (where all numbers are
+3-digit decimal numbers)
+and returning a pointer to the
+corresponding ns_addr structure.  This result may then be passed as the
+argument to CourierOpen().
+More normally, the user will have a string in the form of an NS address,
+e.g. ``jqj:computer\ science:cornell-univ''.  This string can be
+coerced into a Clearinghouse ObjectName, then used in a Clearinghouse-based
+lookup to find the corresponding address:
+.sp 1
+.nf
+       Clearinghouse2_ObjectName hobj, hdefault;
+       struct ns_addr * host;
+       char * string;
+
+       CH_NameDefault(&hdefault);      /* get defaults */
+       hobj = CH_StringToName(string, &hdefault);
+       host = CH_LookupAddr(hobj, 0);  
+.fi
+.sp 1
+.pp
+The SPP connection may be reused by multiple RPCs destined for the same
+host, even if the procedures are part of different remote programs.  However,
+the SPP connection may not be reused while an RPC is outstanding; thus,
+if you need to issue a Courier call during a BDT transfer, you will need
+to obtain a second CourierConnection.
+.pp
+The client then calls the compiler-generated stub function to invoke
+the remote procedure.  This stub
+function takes at least 2 arguments:
+.ip  (1)
+The pointer returned from CourierOpen().
+If the SPP connection does not
+currently exist (i.e. has been closed by the server)
+it is reopened for this call.
+.ip (2)
+A pointer to a user-supplied function which will be used if a Bulk Data
+Transfer is required (see the section on BDT below); if this remote
+procedure does not involve Bulk Data Transfer, then the function pointer
+should be 0 or NULL.
+.ip (3...)
+One additional argument corresponding to each parameter in the Courier
+language description of this procedure.
+.pp
+The remote procedure returns in one of 2 ways:  either it returns
+normally, passing back a structure corresponding to the RETURNS parameters
+specified in the procedure description, or it signals an error which
+may be caught using the DURING ... HANDLER mechanism described in
+.i except(1) .
+.pp
+A Courier procedure is allowed to return multiple results, a language feature
+not easily mapped into C.  To provide this functionality, all UNIX
+courier remote procedures actually return a structure
+containing the procedure results.  Thus, for a procedure named
+Foo, declared
+.sp 1
+       Foo: PROCEDURE [ ] RETURNS [str: STRING];
+.sp 1
+the C function Foo would return a structure of type FooResults, declared
+.sp 1
+.nf
+       typedef struct {
+               String str;
+       } FooResults;
+.fi
+.fi
+.pp
+Instead of returning a value of type determined by the RESULTS clause
+of a PROCEDURE declaration, a Courier remote procedure call may return
+a reject message indicating the remote system's inability to even
+attempt a remote operation, or an abort message raising a remote error,
+i.e. reporting the operation's failure.  Reject and abort messages are
+handled by a signalling mechanism  or if uncaught cause termination of
+the client program.  For example, a client calling the remote procedure
+defined by
+.sp 1
+.nf
+       NoSuchFile: ERROR = 0;
+       OtherError: ERROR [ errorstring: STRING ] = 1;
+       Example: PROCEDURE [ ] REPORTS [ NoSuchFile, OtherError ];
+.fi
+.sp 1
+might be coded as
+.sp 1
+.nf
+       conn = CourierOpen(destaddr);
+       DURING Example(conn,NULL)
+       HANDLER switch(Exception.Code) {
+       case REJECT_ERROR:
+               fprintf(stderr,"Remote reject.\en");
+               exit(1);
+       case NoSuchFile:
+               fprintf(stderr,"No such file\en");
+               break;
+       case OtherError:
+               fprintf(stderr,"Remote error %s\en",
+                       CourierErrArgs(OtherErrorArgs,errorstring) );
+               break;
+       case PROTOCOL_VIOLATION:
+       case INTERNAL_ERROR:
+               fprintf(stderr,"Local internal error %s\en",
+                       Exception.Message);
+               break;
+       default:
+               fprintf(stderr,"Unknown error, type %d\en",
+                       Exception.Code);
+       }       
+       END_HANDLER
+.fi
+.sp 1
+The error value, Exception.code, will be one of (1) ``REJECT_ERROR'',
+which indicates a REJECT message from the remote server, (2) a program-defined
+ERROR value (offset by ERROR_OFFSET), (3) or ``INTERNAL_ERROR'' 
+or ``PROTOCOL_VIOLATION''
+indicating
+a serious internal error in the Unix courier implementation; in the third
+case, a string is available as Exception.Message.
+Both errors and rejections may have arguments, which can be accessed
+within a handler as a struct pointed to by Exception.Message.  For
+programmer convenience a macro, CourierErrArgs, is defined, taking
+as arguments the typedef defining the current error's arguments 
+and the name of the argument to be accessed; this macro may be used
+only within an error handler.
+For rejection messages, the type should be ``rejectionDetails,''
+as defined on page 25 of the Courier spec.
+.pp
+When done with a particular remote connection, the client should call
+CourierClose() with argument the pointer returned from CourierOpen()
+to free the socket and cleanly close the connection.
+.pp
+The client program should be loaded with 
+.i "Example1_client.o" ,
+.i "Example1_support.o" ,
+and -lcourier (the XNS Courier library)
+to produce an executable program.
+.sh 2 "Writing a Courier Server"
+.pp
+The Courier protocol specifies a standard for communicating
+parameters and results which has been adhered to in this
+implementation.
+It also specifies an initial connection protocol
+and a format for messages.
+.pp
+There is a single Courier Daemon per UNIX machine whose function
+is to listen on a well-known XNS port for Courier interface activation
+requests.
+A request contains the number of the Courier program
+whose server is to be activated, and its version number.  These two
+numbers are used as a key in the file 
+.i /etc/Courierservices
+to identify
+the executable file associated with the particular courier program.
+.i /etc/Courierservices
+consists of a sequence of lines of the form
+.sp 1
+       program-name  program-number  version  courier-description-file  server-binary-file
+.sp 1
+The daemon
+either spawns the executable file
+or replies with a reject message.  Note that each incoming remote procedure
+call may be serviced by a separate UNIX process.
+This implies that connection-oriented services (services which consist of a
+sequence of related procedure calls) must be implemented by maintaining
+the state of connections in a file.
+.pp
+The executable file containing the server for a particular remote
+program consists of a main program generated by the courier compiler
+which interprets CALL messages from the remote client and calls
+one or more user-written functions to implement the
+courier procedures.
+.pp
+Given a courier specification for the remote program Example containing
+remote procedure Example,
+the server functions (including the C function Example and any support
+routines needed)
+should be loaded with 
+.i "Example1_server.o" ,
+.i "Example1_support.o" ,
+and -lcourier
+to produce the server process that will be invoked whenever
+an activation request arrives.
+.pp
+A server function receives an argument list similar to that passed by
+a client to a remote procedure.  The first two arguments in the
+parameter list should be ignored; each succeeding argument corresponds
+to one parameter in the courier declaration.
+.pp
+To return from a server function you must return a structure containing
+the results of the procedure.  If this structure contains pointers (e.g.
+Strings) you should insure that the data pointed to is allocated
+staticly or on the heap rather than on the stack.
+You must not call exit(3) from within the server function; doing so will
+cause the client (remote caller) to hang indefinitely waiting for a
+reply.
+.pp
+To abort a server function, returning an error code, use the
+``raise(code, msg)''
+library function, with arguments the error code and either NULL (if the
+error takes no arguments) or a pointer to a structure containing the
+arguments to the error.  Raise causes a longjump out of the function
+directly to the handler, and thence to the remote procedure caller.
+For example, to return the OtherError message defined above one might code
+.sp 1
+.nf
+       OtherErrorArg randomerr;
+       . . .
+       randomerr.errorstring = sys_errlist[errno]);
+       raise(OtherError,(char*) &randomerr);
+.fi
+.sp 1
+.pp
+When a server function returns to its main program caller the main()
+sends the appropriate RETURN or ABORT message to the remote client,
+and does an exit(0).  The server then holds the connection for up to 90 
+seconds waiting for another Courier call to arrive on the same SPP
+stream.
+.sh 2 "Dynamic Allocation"
+.pp
+One additional 
+.i caveat 
+is necessary when using Courier remote procedure calls from C:  C does not
+have garbage collection, so you should free any dynamically allocated storage
+when you are done with it.  In general, top level Courier objects are
+always allocated from the stack or statically; however, strings and sequences
+within an object are generally allocated from the heap using malloc().
+.pp
+A client program which calls the remote procedure Foo defined above,
+returning a STRING,
+actually has returned to it a C struct containing a pointer to an array
+of malloced characters.  When done with this string, the program may free
+it by calling clear_FooResults() with argument the address of the
+result returned by the call to Foo.  Thus:
+.sp 1
+.nf
+       FooResults result;
+       . . .
+       result = Foo(conn,NULL);
+       . . .
+       printf("result was %s\n",result.str);
+       clear_FooResults(&result);
+.fi
+.sp 1
+.sh 1 "Using Bulk Data Transfer"
+.pp
+.sh 2 "Overview"
+.pp
+When a Courier program needs to transfer an arbitrary amount of
+information as an argument or result of a Courier procedure, the
+procedure is usually defined to have an argument of type ``BulkData.Sink''
+or ``BulkData.Source'' (and a ``DEPENDS UPON BulkData''
+is included in the program).
+The argument is a ``source'' if it is information transferred from caller
+to server (as though a procedure argument), a ``sink'' if it is
+information transferred from server to caller (as though a procedure
+result).  In this
+implementation, a Courier procedure may have at most one such argument,
+which must have the value null or immediate.
+In a Courier call, the bulk data is transmitted in a special way,
+multiplexed into the same data path as control messages
+between the arguments and the results.
+.pp
+The BDT user should include in the 
+.i ".cr"
+file a ``DEPENDS UPON BulkData (0) VERSION 1'', then reference these
+arguments as BulkData.Source and BulkData.Sink in PROCEDURE
+declarations.
+The declaration of a Courier PROCEDURE can then indicate a bulk
+data transfer by including in its formal argument list a variable
+of type BulkData.Source or BulkData.Sink
+as appropriate.
+The corresponding parameter to the C procedure will be of type
+BulkData1_Descriptor.
+.sh 2 "Coding a Client"
+.pp
+To use Bulk Data in a client program, the Courier definition of the
+procedure must include one BulkData.Source or BulkData.Sink parameter.
+As the actual argument to the call, the C client passes a constant,
+either BulkData1_immediateSource, BulkData1_immediateSink, BulkData1_nullSource, 
+or BulkData1_nullSink as appropriate.  The client
+also specifies a pointer to a user-supplied function as the second
+argument to the remote procedure.
+Courier sets up the transaction, then calls the supplied function with
+one argument, the pointer returned by CourierOpen() describing the
+SPP socket on which to write (if a source argument) or read
+(if a sink) the bulk data. 
+.pp
+To complete the transaction normally, the
+function should send packets of SPP data terminated by
+an end-of-message (if writing) or read up to but not beyond an
+end-of-message (if reading), and should return normally to its caller.
+To do so, it should call the procedures BDTread(), BDTwrite(), or 
+BDTclosewrite()
+as appropriate.  BDTread() and BDTwrite() take arguments analogous to read()
+and write() except that instead of a file descriptor they accept the SPP
+descriptor returned by CourierOpen(); their arguments are thus
+(1) the pointer to the SPP data structure,
+(2) a pointer to an array of characters (the IO buffer),
+and (3) a count;
+they return the number of characters actually read or written.
+.pp
+The UNIX program can finish a write transfer by calling BDTclosewrite() or
+BDTabort(), each with the SPP descriptor as argument.
+BDTclosewrite() produces an SPP end-of-message, while BDTabort() instructs
+Courier to discard any buffered data and
+send a Bulk Data Abort to abort the transaction.  A read transfer may also be
+prematurely ended by calling BDTabort().
+.sh 2 "Coding a Server"
+.pp
+Writing a server that uses BDT
+is similar to writing the BDT function in a caller.
+The server is passed a BulkData.Source or BulkData.Sink (the two are
+indistinguishable at run time).
+The server routine should check to make sure that the source or sink
+is of type null or immediate (active and passive descriptors are not
+supported in this implementation) by means of code such as:
+.sp 1
+.nf
+Foo(bdtconnection,ignoredarg,..., s, ...)
+       CourierConnection *bdtconnection;
+       BulkData1_Sink s;
+       . . .
+       switch (s.designator) {
+       case active:
+       case passive:
+               /* can't raise BulkData1_InvalidDescriptor, so */
+               raise(someerror);
+       case null:
+               /* handle null transfer */
+               break;
+       case immediate:
+               /* handle normal, i.e. immediate, transfer */
+               break;
+       }
+.fi
+.sp 1
+Note that the BDT error InvalidDescriptor cannot be used unless the
+procedure declaration explicitly notes that this is a valid REPORTS error
+type.  Normally, each courier program defines its own error to be reported
+on BDT problems.
+.pp
+Use the first argument to the server procedure as the handle on the
+BDT connection.
+As with a client BDTabort() may
+be used to send abort messages.
+.sh 2 "Sending Bulk Data"
+.pp
+Sending bulk data, either as client or server, is quite
+straightforward.  Use the supplied procedure BDTwrite(), whose
+semantics are similar to write().  BDTwrite() will return -1
+if an error occurs or an abort from the listener is received; in this
+case, the sender should immediately cease transmitting BDT data, and
+should instead call BDTabort(). 
+Typical code to send data on ``bdtconnection'',
+in this case read from a buffered file open
+as ``source,'' might appear as:
+.sp 1
+.nf
+       CourierConnection *bdtconnection;
+       FILE *source;
+       int count;
+       char buffer[SPPMAXDATA];
+       ...
+       while ( (count = fread(buffer,1,SPPMAXDATA,source)) > 0 &&
+               BDTwrite(bdtconnection,buffer,count) >= 0 )
+               ;
+       if (count <= 0) /* succsfull transfer */
+               BDTclosewrite(bdtconnection);
+       else
+               BDTabort(bdtconnection);
+.fi
+.sp 1
+.pp
+Each BDTwrite() transmits one packet after checking for an abort message
+from the receiver.
+A sender which gets an abort from a remote receiver must immediately
+stop transferring data and instead must echo an abort via BDTabort()
+to acknowledge the end of the transfer.
+.pp
+Also, for efficiency's
+sake it is desirable to write packets as near as possible to the
+nominal maximum length of an SPP packet; this suggests a buffer length of
+534 bytes.  For programmer convenience, the file
+.i courier.h
+defines the symbol SPPMAXDATA as 534.
+.sh 2 "Receiving Bulk Data"
+.pp
+To make receiving bulk data simple, the routine BDTread() performs checks for
+various conditions on receipt of each packet.  BDTread() will return the
+count of the number of bytes actually read in each packet (skipping empty
+packets), or 0 to indicate end of data.  If an error or BDT abort message
+occurs, BDTread() will return -1; if this occurs, the receiver should 
+immediately stop reading BDT data.
+.pp
+Typical code for receiving bulk data on the socket described by 
+``bdtconnection'' and
+depositing it in the file opened for buffered ouput as ``destfile''
+might be:
+.sp 1
+.nf
+       CourierConnection *bdtconnection;       /* BDT source descriptor */
+       FILE *destfile;                         /* output file */
+       char sppdata[SPPMAXDATA];
+       ...
+               count = BDTread(bdtconnection, sppdata, SPPMAXDATA);
+               while (count > 0) {     /* actually read data */
+                       if (fwrite(sppdata, 1, count, destfile) == 0) {
+                                       /* error while writing */
+                               BDTabort(bdtconnection);
+                               break;
+                       }
+                       count = BDTread(bdtconnection, sppdata, SPPMAXDATA);
+               }
+               if (count == 0) {
+                       /* successful */
+                       ...
+.fi
+.sp 1
+.sh 2 "StreamOf declarations"
+.pp
+Frequently, data to be sent on a Bulk Data connection will be described
+by a StreamOfSomething declaration in the courier specification.  For
+example, in Clearinghouse several routines are documented as returning
+a StreamOfObjectName; unfortunately, a StreamOf declaration is recursive,
+and so is not immediately translatable into automatic packing and unpacking
+routines which need to know in advance how much space to allocate for the
+result.  As a kludge, we translate such types as if the recursive
+part of the declaration were a null record.  
+Consider:
+.sp 1
+.nf
+StreamOfObjectName: TYPE = CHOICE OF {
+       nextSegment (0) => RECORD [
+               segment: SEQUENCE OF ObjectName,
+               restOfStream: StreamOfObjectName ],
+       lastSegment (1) => SEQUENCE OF ObjectName};
+.fi
+.sp 1
+.lp
+In the above declaration, the ``restOfStream is effectively ignored.
+Given this declaration, the following routine may be passed to a
+remote procedure as the Bulk Data actor.
+.sp 1
+.nf
+#include "Clearinghouse_support.c"     /* get internalize_* routines */
+#define MAXPACKS 5
+
+GetData(conn)
+       CourierConnection *conn;
+{
+       int count, i;
+       Unspecified buffer[MAXWORDS*MAXPACKS], *bp, *bufend;
+       StreamOfObjectName obnames;
+       
+       bufend = buffer;
+       bp = buffer+MAXWORDS*(MAXPACKS-1);    /* end of available space */
+       while (count = BDTread(conn, (char*)bufend, 
+                               MAXWORDS*sizeof(Unspecified))
+               ) {
+               bufend += count/sizeof(Unspecified);
+               if (bufend > bp) {
+                       fprintf(stderr,"BDT read too big to fit\en");
+                       BDTabort(conn);
+                       /* should clear out stuff here if we knew how much */
+               }
+       }
+       bp = buffer;
+       while (bp < bufend) {
+               bp += internalize_StreamOfObjectName(&obnames,bp);
+               if (0 == (int) obnames.designator)
+                  for (i = 0; i < obnames.nextSegment_case.segment.length; i++)
+                       ProcessObjectName(
+                               obnames.nextSegment_case.segment.sequence[i]);
+               else {
+                  for (i = 0; i < obnames.lastSegment_case.length; i++)
+                       ProcessObjectName(
+                               obnames.lastSegment_case.sequence[i]);
+                  return;
+               }
+       }
+}
+.fi
+.sp 1
+.lp
+Note that this code is very awkward, and that it requires that the whole
+bulk data transfer be stored in memory before it is unpacked.  Stay
+tuned; we intend to modify the compiler to make the handling of such
+streams much easier, at the cost of incompatibility with the existing
+scheme, of course!
+.sh 2 "Encapsulated Protocols"
+.pp
+Some courier programs use what might be termed ``encapsulated protocols''
+as a method of type punning to escape from the restrictions of the Courier
+language.  For example, in Filing
+.sp 1
+.nf
+       Attribute: RECORD [
+               type: AttributeType, value: SEQUENCE OF UNSPECIFIED ];
+       checksum: AttributeType = 0;
+       Checksum: TYPE = CARDINAL;
+       createdBy: AttributeType = 1;
+       CreatedBy: TYPE = User;
+.fi
+.sp 1
+Depending on the value of the type subfield, the data in the value field
+is intended to be interpreted as either a CARDINAL or a User name.
+.pp
+Such encapsulation is in general bad design unless a very compelling
+need for it exists.  It is also awkward to support in a C program.  The
+best way to handle such types is to write functions to code and decode
+the encapsulated value using the low level packing and unpacking routines
+provided by the Courier compiler.  For example:
+.sp 1
+.nf
+       Filing4_User
+       AttrToUser(attr)
+               Filing4_Attribute *attr;
+       {
+               Unspecified buf[2049], *bp; /* space for biggest attribute */
+               Cardinal len;
+               Filing4_User retval;
+
+               /* useful fact:   Item: TYPE = SEQUENCE OF UNSPECIFIED; */
+               externalize_Clearinghouse2_Item(&(attr->value), buf);
+               bp = buf;
+               bp += internalize_Cardinal(&len, bp);
+               bp += internalize_Filing4_User(&retval, bp);  
+               return(retval);
+       }
+
+       UserToAttr(id, attr)
+               Filing4_User id;
+               Filing4_Attribute *attr;
+       {
+               Unspecified buf[2049], *bp;
+               Cardinal len;
+
+               /* don't know length yet, so leave space for it */
+               bp = buf + sizeof_Cardinal(len);
+               len = externalize_Filing4_User(&id, bp);
+               (void) externalize_Cardinal(&len, buf);
+               internalize_Clearinghouse2_Item(&(attr->value), buf);
+               return;
+       }
+
+.fi
+.sp 1
+Since the SEQUENCE OF UNSPECIFIED may have undergone arbitrary
+transformations
+during deserialization, translating it to a User record consists of first
+reserializing it into an array of char, then deserializing it using the
+low-level routine appropriate to the encapsulated type.  Translating to
+an encapsulated type is an almost precise inverse.
+.sh 1 "Example I:  Passwd.cr"
+.pp
+This section contains a Courier program which implements remote lookup in
+.i "/etc/passwd"
+(the UNIX database of user names, passwords, home directories, and so
+on).
+It does not utilize Bulk Data Transfer, but does illustrate most other
+features of this courier implementation.
+The applications programmer would first write 
+.i PasswordLookup.cr ,
+the description of the courier interface for the service, then might
+write
+.i PasswordLookup.c ,
+containing the routines needed to implement a server for this courier
+program, or might write
+.i lookup.c ,
+a typical client of this service.
+.bp
+.sh 2 "The specification (PasswordLookup.cr)"
+.sp 1
+.nf
+PasswordLookup : PROGRAM 754 VERSION 1 =
+
+BEGIN
+
+    -- This is a translation of the passwd structure in <pwd.h>
+
+    Passwd : TYPE = RECORD [
+       pw_name, pw_passwd : STRING,
+       pw_uid, pw_gid, pw_quota : LONG CARDINAL,
+       pw_comment, pw_gecos, pw_dir, pw_shell : STRING
+    ];
+
+    -- Remote Errors
+
+    NoSuchUser : ERROR = 0;
+    OtherError : ERROR [ errorstring: STRING ] = 1;
+
+    -- Remote entry points.
+
+    LookupUid : PROCEDURE [ uid : CARDINAL ] RETURNS [ passwd : Passwd ]
+                   REPORTS [ NoSuchUser ]
+                   = 0;
+
+    LookupUser : PROCEDURE [ user : STRING ]
+                   RETURNS [ passwd : Passwd, forward : STRING ]
+                   REPORTS [ NoSuchUser, OtherError ]
+                   = 1;
+
+END.
+.fi
+.sp 2
+.sh 2 "PasswordLookup_defs.h"
+.sp 1
+.nf
+/*
+ * Declarations for Courier program PasswordLookup.
+ */
+#include <courier.h>
+#include <except.h>
+
+typedef struct {
+       String pw_name;
+       String pw_passwd;
+       LongCardinal pw_uid;
+       LongCardinal pw_gid;
+       LongCardinal pw_quota;
+       String pw_comment;
+       String pw_gecos;
+       String pw_dir;
+       String pw_shell;
+} Passwd;
+
+#define NoSuchUser 0
+
+#define OtherError 1
+typedef struct {
+       String errorstring;
+} OtherErrorArg;
+
+typedef struct {
+       Passwd passwd;
+} LookupUidResult;
+
+typedef struct {
+       Passwd passwd;
+       String forward;
+} LookupUserResult;
+
+extern LookupUidResult LookupUid();
+
+extern LookupUserResult LookupUser();
+.fi
+.sp 2
+.sh 2 "Makefile"
+.sp 1
+.nf
+CFLAGS = -O
+USEROBJS = lookup.o PasswordLookup_client.o
+SRVROBJS = PasswordLookup.o PasswordLookup_server.o
+LIBS = -lcr
+DESTDIR = /usr/lib/courier
+
+all:   lookup PasswordLookup
+
+lookup:        $(USEROBJS)
+       cc -o lookup $(USEROBJS) $(LIBS)
+
+PasswordLookup:        $(SRVROBJS)
+       cc -o PasswordLookup $(SRVROBJS) $(LIBS)
+
+$(USEROBJS) $(SRVROBJS):       PasswordLookup_defs.h
+
+PasswordLookup_defs.h \e
+PasswordLookup_server.c \e
+PasswordLookup_client.c:       PasswordLookup.cr
+       courier PasswordLookup.cr
+
+install:       all
+       install -s PasswordLookup $(DESTDIR)
+
+clean:
+       -rm -f *.o PasswordLookup_*.c PasswordLookup_defs.h
+.fi
+.bp
+.sh 2 "The server procedures (PasswordLookup.c)"
+.sp 1
+.nf
+#include <stdio.h>
+#include "PasswordLookup_defs.h"
+
+extern Passwd *getpwnam(), *getpwuid();
+
+LookupUidResult
+LookupUid(CourierConnection,CourierBDTProc,uid)
+       CourierConnection *CourierConnection, CourierBDTProc;
+       Cardinal uid;
+{
+       Passwd *pw;
+
+       pw = getpwuid(uid);
+       if (pw == NULL)
+               raise(NoSuchUser,NULL);
+       else {
+               return (*(LookupUidResult*) &pw);
+       }
+}
+
+LookupUserResult
+LookupUser(CourierConnection,CourierBDTProc,user)
+       CourierConnection *CourierConnection, CourierBDTProc;
+       String user;
+{
+       LookupUserResult result;
+       Passwd *pw;
+       FILE *fwdfd;
+       static char forward[100];
+
+       pw = getpwnam(user);
+       if (pw == NULL)
+               raise (NoSuchUser,NULL);
+       else {
+               sprintf(forward,"%s/.forward",pw->pw_dir);
+               if ((fwdfd = fopen(forward,"r")) == NULL) {
+                       forward[0] = '\e0';
+               else {
+                       fgets(forward,100,fwdfd);
+                       fclose(fwdfd);
+                       if (strlen(forward) < 2) (
+                               static OtherErrorArg randomerr = {
+                                       "invalid forwarding file"};
+                               raise(OtherError,&randomerr));
+               }
+               result.password = *pw;
+               result.forward = forward;
+               return (result);
+       }
+}
+.fi
+.bp
+.sh 2 "The user program (lookup.c)"
+.sp 1
+.nf
+/*
+ * Sample program to access remote password lookup.
+ * Usage: lookup machine username
+ */
+#include <stdio.h>
+#include "PasswordLookup_defs.h"
+
+main(argc, argv)
+       int argc;  char **argv;
+{
+       LookupUserResult result;
+       Passwd passwd;
+       CourierConnection *connection;
+
+       if (argc < 3 || (destaddr = getXNSaddr(argv[1])) == NULL)) {
+               fprintf(stderr,"Usage:  %s machine file ...\en",argv[0]);
+               exit(1);
+       }
+       if ((connection = CourierOpen(destaddr)) == NULL) {
+               fprintf(stderr,"Can't open connection to %s\en",argv[1]);
+               exit(1);
+       }
+       DURING
+               result = LookupUser(connection,NULL,argv[2]);
+       HANDLER
+               switch(Exception.Code) {
+       case Courier_reject:
+               fprintf("Connection rejected, code = %d\en",
+                       CourierErrArgs(rejectionDetails,designator) );
+               exit(1);
+       case NoSuchUser:
+               printf("User %s unknown on %s.\en", argv[2], argv[1]);
+               exit(0);
+       case OtherError:
+               fprintf(stderr,"Remote error %s\en",
+                       CourierErrArgs(OtherErrorArg,errorstring) );
+               exit(1);
+       }
+       END_HANDLER;
+
+       displaypwd(& result.passwd);
+       displayfwd(result.forward);
+       CourierClose(connection);
+}
+
+displaypwd(p)
+       Passwd *p;
+{
+       printf("%s:%s:%d:%d:%s:%s:%s\en",
+               p->pw_name, p->pw_passwd, p->pw_uid, p->pw_gid,
+               p->pw_gecos, p->pw_dir, p->pw_shell);
+}
+
+displayfwd(s)
+       String s;
+{
+       if (*s) printf("Mail forwarding to %s\en",s);
+       else    printf("Mail is not forwarded\en");
+}
+.fi
+.bp
+.sh 1 "Example II: PrintFile"
+.pp
+This example is a very simple application of Bulk Data Transfer.
+.sh 2 "PrintFile.cr"
+.sp 1
+.nf
+PrintFile: PROGRAM 756 VERSION 1 =
+BEGIN
+
+    DEPENDS UPON BulkData (0) VERSION 1;
+
+    -- Remote errors.
+
+    CantPrint: ERROR = 0;
+
+    -- Remote entry points.
+
+    RPrint: PROCEDURE [ s: BulkData.Source ]
+               REPORTS [ CantPrint ];
+
+END.
+.fi
+.sp 1
+.sh 2 "A typical client (remoteprint.c)"
+.lp
+.sp 1
+.nf
+/*
+ * Sample proram to print a file remotely using trivial remote print
+ * protocol.
+ * Usage:  remoteprint filename
+ *
+ */
+#include <stdio.h>
+#include <sys/types.h>
+#include <netns/ns.h>          /* for XNS addresses */
+#include "PrintFile_defs.h"
+
+static FILE * source;  /* communicate from main to SendSource */
+
+main(argc, argv)
+       int argc;
+       char *argv[];
+{
+       CourierConnection *connection;
+       struct ns_addr *destaddr;
+
+       if (argc < 3 || (destaddr = getXNSaddr(argv[1])) == NULL)) {
+               fprintf(stderr,"Usage:  %s machine file ...\en",argv[0]);
+               exit(1);
+       }
+       if ((connection = CourierOpen(destaddr)) == NULL) {
+               fprintf(stderr,"Can't open connection to %s\en",argv[1]);
+               exit(1);
+       }
+       argv++;
+       while (argc-- > 2) {
+               argv++;
+               if (strcmp(argv[0],"-") == 0) source = stdin;
+               else source = fopen(argv[0],"r");
+               if (source == NULL)
+                       fprintf(stderr,"Can't open %s\en",argv[0]);
+               else DURING
+                       RPrint(connection,SendSource,immediateSource);
+                   HANDLER
+                       fprintf(stderr,"Call to RPrint failed.\en");
+                   END_HANDLER;
+               fclose(source);
+       }
+       CourierClose(connection);
+}
+
+SendSource(bdtconnection)
+CourierConnection *bdtconnection;
+{
+       int count;
+       char buffer[SPPMAXDATA];
+
+       while ( (count = fread(buffer,1,SPPMAXDATA,source)) > 0 &&
+               BDTwrite(bdtconnection,buffer,count) >= 0 )
+               ;
+       if (count <= 0)
+               BDTclosewrite(bdtconnection);   /* last packet with EOM set */
+       else
+               BDTabort(bdtconnection);
+}
+.fi
+.sp 2
+.sh 2 "Server (PrintFile.c)"
+.sp 1
+.nf
+#include <stdio.h>
+#include <sys/types.h>
+#include <netxns/ns.h>
+#include "PrintFile_defs.h"
+
+RPrintResult
+Rprint(source,CourierBDTProc,s)
+       CourierConnection *source, CourierBDTProc;
+       BulkData1_Source s;
+{
+       FILE *printpipe;
+       char sppdata[SPPMAXDATA];
+
+       switch (s.designator)
+       case active:
+       case passive:
+               raise(CantPrint);
+               /*NOTREACHED*/
+       case null:
+               system("print /dev/null");      /* print a null file */
+               return;
+       case immediate:
+               if ((printpipe = popen("print","w")) == NULL) {
+                       raise(CantPrint);
+                       /*NOTREACHED*/
+               }
+               count = BDTread(source, sppdata, SPPMAXDATA);
+               while (count > 0) {     /* actually read data */
+                       if (fwrite(sppdata, 1, count, printpipe) == 0) {
+                               BDTabort(source);
+                               break;
+                       }
+                       count = BDTread(source, sppdata, SPPMAXDATA);
+               }
+               if (pclose(printpipe) == 0 && count == 0)
+                       return;
+               else raise(CantPrint);
+}
+.fi
+.sh 1 "One Final Example"
+.pp
+Finally, we present a slightly 
+more useful example, a program to print an Interpress
+file on a Services-8 printer.  It depends on the standard Xerox Printing
+specification, program 4 version 3.  For an even better example, look
+at the printing client, xnsprint, provided with the XNS Courier distribution
+as
+.i "./examples/print/xnsprint.c" .
+.sp 2
+.nf
+#include <stdio.h>
+#include <sys/types.h>
+#include <netns/ns.h>
+#include "Printing_defs.h"
+#include <except.h>
+
+static FILE *ipfile = NULL;
+
+SendSource(bdtconnection)
+CourierConnection *bdtconnection;
+{
+       int count;
+       char buffer[SPPMAXDATA];
+
+       while ( (count = fread(buffer,1,SPPMAXDATA,ipfile)) > 0 &&
+               BDTwrite(bdtconnection,buffer,count) >= 0 )
+               ;
+       if (count <= 0)
+               BDTclosewrite(bdtconnection);   /* last packet with EOM set */
+       else
+               BDTabort(bdtconnection);
+}
+
+main(argc, argv)
+       int argc;
+       char *argv[];
+{
+       PrintResults result;
+       struct ns_addr *destaddr;
+       CourierConnection *conn;
+       extern struct ns_addr *getXNSaddr();
+       PrintAttributes attributes;
+       PrintOptions options;
+
+       /* use Cornell print server, CornellS1 (slander) */
+       destaddr = getXNSaddr("2-273#2-852-159-207");
+       attributes.length = 0;
+       options.length = 0;
+       if (argc != 2 || ((ipfile = fopen(argv[1],"r")) == NULL)) {
+               fprintf(stderr,"Usage: %s file\en",argv[0]);
+               exit(1);
+       }
+       if ((conn = CourierOpen(destaddr)) == NULL) {
+               fprintf(stderr,"Can't open connection to %s\en",xnshost);
+               exit(1);
+       }
+
+       DURING
+               result = Print(conn, SendSource, BulkData1_immediateSource,
+                                       attributes, options);
+       HANDLER {
+               switch (Exception.Code) {
+               case Busy:
+                       fprintf(stderr,"Busy\en");
+                       break;
+               case ConnectionError:
+                       fprintf(stderr,"Connection error, %d\en",
+                               CourierErrArgs(ConnectionErrorArgs,problem));
+                       break;
+               case InsufficientSpoolSpace:
+               case SpoolingQueueFull:
+                       fprintf(stderr,"Insufficient spool space\en");
+                       break;
+               case SpoolingDisabled:
+                       fprintf(stderr,"Spooling disabled\en");
+                       break;
+               case MasterTooLarge:
+               case TooManyClients:
+               case ServiceUnavailable:
+               case SystemError:
+               case InvalidPrintParameters:
+               case MediumUnavailable:
+               case TransferError:
+                       fprintf(stderr,"Some Printing error, number %d\en",
+                               Exception.code-ERROR_OFFSET);
+                       break;
+               case Undefined:
+                       fprintf(stderr,"Undefined error, number %d\en",
+                               CourierErrArgs(UndefinedArgs,problem));
+                       break;
+               case REJECT_ERROR:
+                       fprintf(stderr,"REJECT:  type = %d\en",
+                               CourierErrArgs(rejectionDetails, designator));
+                       break;
+               default:
+                       fprintf(stderr,"Some random error, code %d\en",
+                               Exception.Code);
+                       break;
+               }
+       exit(1);
+       } END_HANDLER;
+
+       CourierClose(conn);
+       printf("Done.  Request ID %x %x %x %x %x\en",
+               result.printRequestID[0], result.printRequestID[1],
+               result.printRequestID[2], result.printRequestID[3],
+               result.printRequestID[4]);
+}
+.fi
+.bp
+.sh 1 "Final Notes"
+.pp
+The issues of authentication and protection are
+difficult.
+They are only touched upon in this implementation.
+Currently, each Courier program must perform any authentication
+(presumably using the Authentication protocol)
+if this is desired.  A few routines exist to allow cleints and servers
+to perform simple Authentication; no support for strong Authentication
+has yet been written.
+.pp
+This implementation is fairly inefficient, especially in
+the implementation of servers.  Courier calls require substantial
+extra copying of courier arguments and results.  More significantly,
+the requirement that each call spawn a unique process is very expensive
+in UNIX, and should be reconsidered.
+A sequence of RPCs on the same SPP stream that specifies the same program
+and version number uses the same process, but in all other cases a new
+process must be spawned for each individual RPC.
+.pp
+This implementation defines a number of reserved identifiers.
+.b ">>>should list them here<<<"
+.sh 1 "Appendix"
+.pp
+This appendix contains the grammar for the Courier language.
+It is similar to the YACC specification used
+by the Courier compiler.
+.sp
+.ps -2p
+.vs -2p
+.nf
+.TS
+l l l l l.
+%token identifier      number  string  
+       ARRAY   BEGIN   BOOLEAN CARDINAL
+       CHOICE  DEPENDS END     ERROR
+       FALSE   INTEGER LONG    OF
+       PROCEDURE       PROGRAM RECORD  REPORTS
+       RETURNS SEQUENCE        STRING  TRUE
+       TYPE    UNSPECIFIED     UPON    VERSION
+.TE
+%%
+
+Program :
+               identifier ':' PROGRAM number VERSION number '='
+               BEGIN DependencyList DeclarationList END '.'
+       |
+               identifier ':' PROGRAM '='
+               BEGIN DependencyList DeclarationList END '.'
+       ;
+
+DependencyList :
+               /* empty */
+       |       DEPENDS UPON ReferencedProgramList ';'
+       ;
+
+ReferencedProgramList :
+               ReferencedProgram
+       |       ReferencedProgramList ',' ReferencedProgram
+       ;
+
+ReferencedProgram :
+               identifier '(' number ')' VERSION number
+       ;
+
+DeclarationList :
+               /* empty */
+       |       DeclarationList Declaration
+       ;
+
+Declaration :
+               identifier ':' TYPE '=' Type ';'
+       |       identifier ':' Type '=' Constant ';'
+       ;
+
+Type :
+               PredefinedType
+       |       ConstructedType
+       |       ReferencedType
+       ;
+
+PredefinedType :
+               BOOLEAN
+       |       CARDINAL
+       |       LONG CARDINAL
+       |       INTEGER
+       |       LONG INTEGER
+       |       STRING
+       |       UNSPECIFIED
+       |       LONG UNSPECIFIED
+       ;
+
+ConstructedType :
+               '{' CorrespondenceList '}'
+       |       ARRAY NumericValue OF Type
+       |       SEQUENCE MaximumNumber OF Type
+       |       RECORD '[' FieldList ']'
+       |       RECORD '[' ']'
+       |       CHOICE DesignatorType OF '{' CandidateList '}'
+       |       PROCEDURE ArgumentList ResultList ErrorList
+       |       ERROR ArgumentList
+       ;
+
+ReferencedType :
+               identifier
+       |       identifier '.' identifier
+       ;
+
+CorrespondenceList :
+               Correspondence
+       |       CorrespondenceList ',' Correspondence
+       ;
+
+Correspondence :
+               identifier '(' NumericValue ')'
+       ;
+
+MaximumNumber :
+               NumericValue
+       |       /* empty */
+       ;
+
+NumericValue :
+               number
+       |       ReferencedConstant
+       ;
+
+DesignatorType :
+               /* empty */
+       |       ReferencedType
+       ;
+
+CandidateList :
+               Candidate
+       |       CandidateList ',' Candidate
+       ;
+
+Candidate :
+               DesignatorList '=''>' Type
+       ;
+
+DesignatorList :
+               Designator
+       |       DesignatorList ',' Designator
+       ;
+
+Designator :
+               identifier
+       |       Correspondence
+       ;
+
+ArgumentList :
+               /* empty */
+       |       '[' FieldList ']'
+       ;
+
+ResultList :
+               /* empty */
+       |       RETURNS '[' FieldList ']'
+       ;
+
+ErrorList :
+               /* empty */
+       |       REPORTS '[' NameList ']'
+       ;
+
+FieldList :
+               Field
+       |       FieldList ',' Field
+       ;
+
+Field :
+               NameList ':' Type
+       ;
+
+Constant :
+               PredefinedConstant
+       |       ConstructedConstant
+       |       ReferencedConstant
+       ;
+
+PredefinedConstant :
+               TRUE
+       |       FALSE
+       |       number
+       |       '-' number
+       |       '"' string '"'
+       ;
+
+ConstructedConstant :
+               identifier
+       |       '[' ElementList ']'
+       |       '[' ComponentList ']'
+       |       '['']'
+       |       identifier Constant
+       |       number
+       ;
+
+ReferencedConstant :
+               identifier
+       |       identifier '.' identifier
+       ;
+
+ElementList :
+               Constant
+       |       ElementList ',' Constant
+       ;
+
+ComponentList :
+               Component
+       |       ComponentList ',' Component
+       ;
+
+Component :
+               NameList ':' Constant
+       ;
+
+NameList :
+               identifier
+       |       NameList ',' identifier
+       ;
+.fi
+.vs
+.ps