.fo ''%'' .(l C .ps +4 .b Writing Distributed Programs with Courier .ps -4 .sp 2 .i Eric C. Cooper .sp 2 .r Computer Science Division \(em EECS University of California Berkeley, CA 94720 .sp 2 March, 1982 .)l .sh 1 "Introduction" .pp This paper describes the implementation and use of the Courier remote procedure call protocol for Berkeley UNIX (version 4.2). Courier is both a protocol and a specification language. The protocol specifies 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. This document assumes familiarity with the Courier language, details of which may be found in the Courier protocol definition.\** .r .(f \** ``Courier: The Remote Procedure Call Protocol.'' Xerox System Integration Standard 038112, December 1981. .)f However, for reference purposes, a grammar for the Courier language may be found in the appendix to this document. .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. .sh 1 "The mapping between C and Courier" .pp The Courier specification language includes facilities for declaring new types and constants of given types. Certain of these types presuppose a programming language environment capable of supporting them: for example, there are error types that procedures may report in lieu of returning a result, and procedures may return multiple results. Because of the lack of support for these features in the UNIX C environment, they are not supported in this implementation. .sh 2 "Predefined types" .pp The following typedefs correspond to predefined Courier types: .(b .TS c c l l. C typedef Courier type Boolean BOOLEAN Cardinal CARDINAL LongCardinal LONG CARDINAL Integer INTEGER LongInteger LONG INTEGER String STRING Unspecified UNSPECIFIED LongUnspecified LONG UNSPECIFIED .TE .)b .sh 2 "Constructed types" .pp The Courier enumeration, array, and record types correspond to C enumerations, arrays, and structures. .pp Procedures correspond to C functions, except that error reports and multiple results are not supported. Error types are not supported. .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 Although the Courier language allows constants of any type to be declared, it is difficult to define constants of constructed types in C programs. Consequently, this implementation restricts constants to be numeric. .sh 1 "How to build a Courier program" .pp The specification file is expected to have the extension .i ".cr" . Consider the following skeletal Courier program definition: .sp Example : PROGRAM = BEGIN ... END. .sp The name of this Courier program is .i "Example" ; by convention, this specification would stored in the file .i "Example.cr" . .pp The first step is to use the Courier compiler on the specification, by doing either .sp courier Example.cr .sp or .sp courier -x Example.cr .sp (The .i "-x" option is explained below.) Assuming there are no errors in compilation, the following files will have been produced: .(b .TS c c l l. File Contents Example.h definitions and typedefs Example_stubs.c routines to map between C and Courier Example_server.c server routines Example_client.c client routines .TE .)b .pp The header file .i "Example.h" should be included via .sp #include ``Example.h'' .sp in all user-written parts of the Courier program (i.e., the implementations of the client program and server procedures.) It is intended to be readable, so that the user can refer to it directly rather than memorize the correspondence between Courier types and C types discussed above. .sh 2 "Calling remote procedures" .pp The current implementation of Courier supports two styles of .i binding remote program interfaces to the user program: a per-session mechanism (the default) and a per-call mechanism, which is requested by giving the Courier compiler the .i "-x" option (for .i explicit binding.) .pp If the per-session mechanism is used, only one instance of each remote interface may be active at a time (although several different remote programs may be used simultaneously.) The remote interface for the ``Example'' program above is activated by the call .sp BindExampleToMachine(machine_name); .sp (Note that the name of the function to be called depends on the Courier program name; it is generated automatically by the Courier compiler.) Subsequent calls will deactivate any existing interface for the program before activating a new one. Once an interface is activated, the remote procedures in it may be called exactly as if they were local C functions. (The compiler produces stubs which invoke the corresponding procedures on the remote machine.) .pp To use the explicit, or per-call, binding mechanism, the .i "-x" option must be given to the compiler, as mentioned above. No binding call need be given in the user program; instead, each remote procedure to be called must be passed .i "an additional parameter" . This parameter, a string, is the machine name on which the procedure should be executed; it precedes any other parameters declared in the Courier specification. (Again, the compiler produces the appropriate stubs, this time with the additional parameter, which invoke the corresponding procedures on the specified remote machine.) .pp This style of binding allows any number of instances of the same remote interface to be used. For instance, one could (inefficiently) implement a third-party file transfer by reading from one file server and writing to a second file server. Since both the read and write procedures would presumably be defined in the same Courier specification, per-session binding could not be used.\** .(f \** I am indebted to Jeff Mogul for this example. .)f .pp The client program should be loaded with Example_client.o and -lcr (the Courier library) to produce an executable program. The server procedures should be loaded with Example_server.o and -lcr to produce the server process that will be invoked whenever an activation request arrives. .sh 1 "The Courier Daemon" .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, both of which have been somewhat simplified for the UNIX environment. .pp There is a single Courier Daemon per machine whose function is to listen on a well-known port for Courier interface activation requests. A request contains the name of the Courier program whose server is to be activated. The executable file for this program must reside in a special directory (/usr/lib/courier) and have the same name as the Courier program it contains. The daemon either spawns this executable file or replies with an error message. .pp The format for messages has been simplified somewhat because reject and abort messages are not used. .sh 1 "An example" .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.) .bp .sh 2 "The specification (PasswordLookup.cr)" .sp 2 .nf PasswordLookup : PROGRAM = BEGIN -- This is a translation of the passwd structure in 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 entry points. -- Given a user name, return a Passwd record. LookupUser : PROCEDURE [user : STRING] RETURNS [passwd : Passwd] = 0; -- Given a user id, return a Passwd record. LookupUid : PROCEDURE [uid : CARDINAL] RETURNS [passwd : Passwd] = 1; END. .fi .bp .sh 2 "The server procedures (PasswordLookup.c)" .sp 2 .nf #include #include "PasswordLookup.h" extern Passwd *getpwnam(), *getpwuid(); Passwd empty = { "", "", 0, 0, 0, "", "" }; Passwd LookupUser(user) String user; { Passwd *pw; pw = getpwnam(user); if (pw == 0) return (empty); else return (*pw); } Passwd LookupUid(uid) Cardinal uid; { Passwd *pw; pw = getpwuid(uid); if (pw == 0) return (empty); else return (*pw); } .fi .bp .sh 2 "The user program (lookup.c)" .sp 2 .nf /* * Sample program to access remote password lookup. * * Usage: lookup machine username */ #include #include "PasswordLookup.h" main(argc, argv) int argc; char **argv; { Passwd passwd; if (argc != 3) { fprintf(stderr, "Usage: %s machine username\en", argv[0]); exit(1); } BindPasswordLookupToMachine(argv[1]); passwd = LookupUser(argv[2]); if (strcmp(passwd.pw_name, argv[2]) != 0) printf("User %s is unknown on %s.\en", argv[2], argv[1]); else display(&passwd); } display(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); } .fi .bp .sh 2 "Makefile" .sp 2 .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.h PasswordLookup.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.h .fi .sh 1 "Final notes" .lp The program number and version number fields in a Courier program specification are ignored, and instead the program name is used to activate the remote interface. It was felt that this more dynamic form of binding was more in keeping with the UNIX environment than centrally administered program numbers. .lp The DEPENDS UPON facility of the Courier language is not supported. .lp An approximation to Courier error reporting has been designed, using a combination of UNIX signals and the C non-local return mechanism (a stack frame unwinding mechanism), but this has not yet been implemented. .lp The issues of authentication and protection are difficult. They are only touched upon in this implementation, by making the Courier daemon spawn each server process with privileges equivalent to the protection of the corresponding executable file in /usr/lib/courier. Currently, each Courier program must perform any further authentification if this is desired. .sh 1 "Appendix" .pp This appendix contains the grammar for the Courier language. It is essentially identical 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