/* rpcrts.c ** Remote Procedure Call Kernel Module ** =================================== ** ** ** History: ** See [rpc.rts.c.cms] history. ** This module is mastered in that CMS library. ** ** 19 May 89 Structures simplified. addr() disappears. (LTR/tbl) ** rpc_create_server takes POINTER to name now. ** 26 May 89 rpc_name is no longer a structure, but just an array. ** 12 May 89 caller_socket is guarranteed NULL outside an RPC, and ** works for nested RPCs. ** 13 Mar 90 rpc_put, rpc_begin, rpc_get added. ** Client field cli_local replaced by condidtion (cli_socket==0) ** 29 May 90 Bug fix: Ignore "no translation" error from cm_request. ** ** Preprocessor tokens: ** ** REGISTER Server packages are registered with a name server. ** ** AST The operating systems supports asynchonous procedures ** This is not essential, but provides useful added ** functionality. ** (not implemented on OS9 execpt under CATS) ** ** OSK OS9 on the M68k series (compiler defines automatically) ** ** __TURBOC__ Compilation (on IBM/PC?) under "Turbo C" ** ** NOHEAP Suppresses the use of the heap (not normally on) ** ** __MSDOS__ Operating system on IBM_PCs ** ** vms Run under VMS (VAX/VMS C compiler) ** ** unix Run under some unix, Ultrix, etc ** ** ** String Passing: ** ** To allow these routines to be called from Pascal (or, in some ** cases, FORTRAN), a maximum length is put on string parameters provided ** by users of this module: RPC_NAME_LENGTH. If the name is blank ** padded to this length it does not need to be zero terminated. ** */ #define TS /* Get visibility of TS layer structures, TS_READ etc */ #define RPCRTS /* External Definitions ** -------------------- */ # include # include /* The standard include files vary so much as to where most of these things ** are defined, that conditionally selecting a set of files is more complex ** than simply defining the things we need. */ extern int strcmp(); /* , */ extern char *strcpy(); extern char *strncpy(); extern int strlen(); #ifdef __STDC__ extern volatile int errno; /* Error number */ extern int errno; /* errno.h, stdio.h, netdb.h etc */ extern void abort(void); /* Abort program completely on error */ extern void exit(int status); /* Exit program with a status given */ /* malloc.h, alloc.h */ extern char *malloc(int size); /* Memory allocation */ extern void free(void * block); /* Memory deallocation */ #else #ifdef vms extern volatile noshare int errno; /* Error number as defined on VMS */ #else extern int errno; /* Error number */ #endif extern void abort(); /* Abort program completely on error */ extern void exit(); /* Exit program with status given */ extern char *malloc(); /* Memory allocation */ extern void free(); /* Memory deallocation */ #endif #ifndef vms #include "errmsg.h" /* Error message handling software */ extern void rpc_register_errors(); /* The rpc error message module */ #endif #include "syspec.h" /* Special options for this system */ #include "rpcrts.h" /* General RPC common definitions */ #include "rpc_code.h" /* Coding convention macros */ /* Module parameters ** ----------------- */ #define FIND_REMOTE_TIMEOUT 1000 /* Timeout on management rpc,units of 10ms*/ #ifdef NOHEAP # define MESSAGE_POOL_SIZE 14 #endif /* These timeouts are long in case the other side is running a trace. ** Normally, the exchange will take place with only network delays: no user ** code is invoked. */ # define RPC_GET_TIMEOUT 1500 /* In hundredths of a second */ # define RPC_PUT_TIMEOUT 1500 /* In hundredths of a second */ # define RPCM_PROGRAM 256 # define RPCM_VERSION 100 # define NO_TIMEOUT -1 # define RPC_REJECT_PROGRAM 0 # define RPC_REJECT_VERSION 1 # define RPC_REJECT_PROCEDURE 2 # define RPC_REJECT_INVALID_ARGUMENTS 3 # define RPC_REJECT_EXCEPTION 5 /* Internal RPCRTS structures ** -------------------------- */ /* Generic List Element ** -------------------- ** ** The list manipulation routines only handle the first field in each ** element, the link to the next element. They are defined in terms of ** a generic list element, and type coersion used to apply them to ** each of the list element types defined below. */ typedef struct list_struct * list_pointer; struct list_struct { list_pointer next; /* Link to next element or NIL */ }; /* Client descriptor ** ----------------- ** ** There is one of these for every successful call to rpc_open(). They are ** kept on a list. All strings in this record are zero terminated. */ typedef struct client_struct * client_pointer; struct client_struct { client_pointer cli_next; /* (link) */ rpc_long cli_program_number; /* Ref. for package on remote node */ socket_type cli_socket; /* Socket number for communication */ BOOLEAN cli_callback; /* Flag: call caller at call time */ rpc_name cli_logical; /* Original name quoted, zero term. */ int cli_version; /* Version number of client */ int cli_compatibility; /* Lowest version number of server */ rpc_name cli_peer_address; /* Network address, zero term. */ rpc_name cli_package_name; /* Name of package if known, z.t. */ }; /* Server stub descriptor ** ---------------------- ** ** There is one of these for every successful call to rpc_attach or ** rpc_specify. */ typedef struct program_struct * program_pointer; struct program_struct { program_pointer ser_next; /* Link to next on list */ rpc_pointer ser_stub_entry; /* Entry point of server stub module */ rpc_name ser_package_name; /* Logical name of package */ int ser_version; /* Version number of server */ int ser_compatibility; /* Lowest version number of client */ rpc_long ser_program; /* Allocated program number */ }; /* Active Server descriptor ** ------------------------ ** ** There is one of these for every server socket to be created. */ typedef struct active_server_struct * active_server_pointer; struct active_server_struct { active_server_pointer act_next; /* Link on list */ socket_type act_socket; /* Relevant socket */ }; /* Global variables ** ---------------- */ BOOLEAN rpc_trace; /* Flag: display diagnostics? */ FILE* tfp; /* The output file used */ client_pointer client_list; /* List of connected client stubs */ client_pointer free_client_list; /* List of spare blocks */ program_pointer program_list; /* List of attached server stubs */ program_index next_program_number;/* Next in sequential allocation */ program_pointer free_program_list; /* List of spare blocks */ active_server_pointer active_server_list; /* List of server sockets in use */ rpc_message_pointer free_messages; /* List of spare messages */ rpc_pointer user_error_handler; /* Pointer to user routine */ socket_type caller_socket=NULL; /* Socket on which last call arrived */ /* Module-wide variables */ PRIVATE BOOLEAN initialised=0; /* Has rpc_init been called? */ PRIVATE BOOLEAN trace_file_open=0; /* Has the trace file been opened? */ /* ** Global variables used by marshalling routines. */ rpc_long fix_pckl; rpc_short fix_pcks; /* External Routines ** ----------------- */ /* Transport Service ** ----------------- */ #ifdef __STDC__ extern rpc_status ts_init(void); /* General initialisation */ extern rpc_status ts_open( /* Open a socket */ socket_type *psocket, /* socket returned */ rpc_name service); /* communications address */ extern rpc_status ts_my_address( /* Return comms address of socket */ socket_type socket, /* IN Socket */ rpc_name addrstr); /* OUT address of this socket */ extern rpc_status ts_peer_address( /* Return comms address of peer */ socket_type socket, /* IN Socket */ rpc_name addrstr); /* OUT address of peer */ extern rpc_status ts_close( /* Close a socket */ socket_type socket); /* In Socket */ #else extern void ts_init(); /* General initialisation */ extern rpc_status ts_open(); /* Open a socket */ extern rpc_status ts_my_address(); /* Get my address */ extern rpc_status ts_peer_address();/* Get his address */ extern rpc_status ts_close(); /* Close a socket */ #endif /* Configuration Manager ** --------------------- */ #ifdef __STDC__ extern rpc_status cm_request( /* Request a service */ char * service, /* IN Service requested */ int version, /* IN Version of client */ int compatibility, /* IN Lowest server version acceptable */ char * address, /* OUT buffer */ int address_length); /* IN buffer size */ extern rpc_status cm_translate( /* Local translation of name */ char *initial, /* IN Initial string to be translated */ char *buffer, /* OUT Buffer for returned string */ int buffer_size); /* IN Maximum length of returned string */ extern rpc_status cm_register( /* Registration of service */ char * service, /* IN service name (zero terminated ) */ int version, /* IN Version of server */ int compatibility, /* IN Lowest client version acceptable */ char * address); /* IN address (zero terminated) */ extern rpc_status cm_unregister( /* Deregistration of service */ char * service, /* IN service name (zero terminated ) */ int version, /* IN Version of server */ char * address); /* IN address (zero terminated) */ #else extern rpc_status cm_request(); /* Request a service */ extern rpc_status cm_translate(); /* Local translation of name */ extern rpc_status cm_register(); /* Registration of service */ extern rpc_status cm_unregister(); /* Deregistration of service */ #endif /* ** Forward references. ** ------------------- */ void rpc_init(); #ifdef __STDC__ PRIVATE void rpcm_server(rpc_message_pointer *pcall); #else PRIVATE void rpcm_server(); #endif /* UTILITY ROUTINES ** */ /* Get Pascal or C string parameter ** -------------------------------- ** ** The original portable definition of the string parameter passing ** convention was a string of fixed length 40 characters, blank filled. ** Howver, it is very convenient to pass zero-terminated strings from C. ** Therefore, we keep both options open, allowing a maximum of 40 characters, ** either blank filled or zero-terminated. ** ** On entry, ** param is the address of the string parameter ** dest is the destination string in which it is to be stored ** ** On exit, ** *dest is a zero-terminated string. ** */ #ifdef __STDC__ void rpc_get_string(char *dest, char *param) #else void rpc_get_string(dest, param) char *param; char *dest; #endif { int i; for(i=0; i=0) && (dest[i]==' '); i--) dest[i]=0; /* Turn trailing spaces into zeroes */ } /* Set trace flag as specified by parameter rpc_set_trace() ** ---------------------------------------- ** **/ #ifdef __STDC__ void rpc_set_trace( BOOLEAN wanted) #else void rpc_set_trace(wanted) BOOLEAN wanted; #endif { rpc_trace = wanted; } /* check_trace() ** Set the rpc_trace flag according to environment ** ----------------------------------------------- ** ** void check_trace(void) ** ** Parameters: none ** ** Return: none ** ** On entry: trace_file_open contains an indication if the trace file has ** already been opened. ** ** On exit: rpc_trace indicates if tracing is wanted or not; ** ** trace_file_open if rpc_trace is true, so does trace_file_open, ** it is unchanged otherwise; ** ** tfp if rpc_trace is true it contains a valid file ** pointer for the trace file. ** ** Description: ** ** Attempts to translate the string 'RPC_TRACE_FLAG', turning on the ** trace if it is defined. If so, the translation is used for the ** trace file name. The procedure tries to open it in append mode, ** if open fails then stdout is used for the trace output. ** ** History: ** ** 04 Aug 89 Cleaned up. (RB) ** */ #ifdef __STDC__ PRIVATE void check_trace(void) #else PRIVATE void check_trace() #endif { rpc_name outstr; rpc_status status; status = cm_translate("RPC_TRACE_FLAG", outstr, RPC_NAME_LENGTH); rpc_trace = GOOD(status); if (rpc_trace) { if (!trace_file_open) { /* The trace file hasn't be opened yet, try now. */ /* Open to append, so we won't discard any previous one. */ if ((tfp = fopen(outstr, "a")) == NULL) { PERROR(stderr,"RPC: Can't open trace file `%s' (errno=%d)", outstr, errno); /* If can't open the trace file use stdout instead. */ tfp = stdout; } UTRACE(tfp, "\n\n New Trace Session Started\n\n"); trace_file_open = TRUE; } } } /* Allocate an RPC message rpc_new() ** ----------------------- ** ** This procedure must be fast, as it is used in many places in the rpcrts.c ** and ts.c modules. For this reason, it keeps a list of allocated messages, ** and does not return them to the heap when they have been used. ** ** On entry, ** p The address of the pointer to be filled in ** bytes is irrelevant. In principle this was to be allowed for a ** message size required, if we had variable sized messages. ** On exit, ** *p is either the address of a message, or zero if there is ** not enough memory. */ /*ARGSUSED*/ #ifdef __STDC__ void rpc_new( rpc_message_pointer *p, rpc_integer bytes) #else void rpc_new(p, bytes) rpc_message_pointer *p; rpc_integer bytes; #endif { if (free_messages == NULL) { *p = (rpc_message_pointer)malloc((unsigned)(sizeof(*(*p)))); } else { *p = free_messages; free_messages = (*p)->m_next; } } /* Dispose of one message rpc_dispose() ** ---------------------- ** ** On entry, ** p points to an rpc message previously allocated by rpc_new(). */ #ifdef __STDC__ void rpc_dispose(rpc_message_pointer p) #else void rpc_dispose(p) rpc_message_pointer p; #endif { p->m_next = free_messages; free_messages = p; } /* Report error message rpc_report_error() ** -------------------- ** ** If the status is good, then nothing is done at all. ** If the status is bad, then an error message is ** printed and the program exits. ** ** Under VAX/VMS, this uses the built-in error reporting mechanisms. ** Otherwise, this code contains the error messages for RPC errors. ** ** On entry, ** pstatus is A POINTER TO the VMS-style error code in question. ** ** 1 Dec 89 Takes argument by address every time to be FORTRAN compatibble ** 8 Dec 89 Uses the error_message module now, except on VMS (TBL) */ #ifdef __STDC__ void rpc_report_error(rpc_status *pstatus) #else void rpc_report_error(pstatus) rpc_status *pstatus; #endif { /* VMS version: relies on VMS error reporting ** ----------- */ #ifdef vms extern void sys$exit(); volatile rpc_status *dummy; #ifdef __GNUCC__ extern rpc_status econnrefused; /* We must pull in the msg file at link time */ extern rpc_status rpc_s_bad_procedure_number;/* and RPC error file */ dummy = &econnrefused; /* Force an access to ERRNO message module*/ dummy = &rpc_s_bad_procedure_number; /* Force an access to RPCMSG message module*/ #else globalvalue rpc_status econnrefused;/* Pull in the msg file at link time */ globalvalue rpc_status rpc_s_bad_procedure_number;/* same thing. */ dummy = econnrefused; /* Force an access to ERRNO message module*/ dummy = rpc_s_bad_procedure_number; /* access RPCMSG message module*/ #endif if (BAD(*pstatus)) sys$exit(*pstatus); /* Exit with error message number */ #else /* Normal Version uses error_message module ** ---------------------------------------- */ if (BAD(*pstatus)) { char temp[256]; /* Buffer for error message*/ (void) err_translate(*pstatus, temp, 256); PERROR(stderr, "%s\n", temp); /* Unix style output */ exit((int)*pstatus); /* Unix style error exit */ } /* if bad status */ #endif /* (not vax/vms) */ } /* end rpc_report_error */ /* Establish User error handler rpc_establish() ** ---------------------------- ** ** On entry, ** handler either: the address of a user error handler ** or: zero ** ** On exit, ** If the handler was non-zero on entry, then it is established as the ** entry point to be called whenever a fatal error is detected in ** handling a call for which there is no other method of signaling ** failure (i.e. no "call_status" parameter). Whenever such an error ** arises, the handler will be called, with a pointer to the rpc message ** in question as its one and only parameter. ** ** If the handler was non-zero on entry, then the error handler ** facility is disabled. ** ** See also: fatal_error() */ #ifdef __STDC__ void rpc_establish( rpc_pointer handler) #else void rpc_establish(handler) rpc_pointer handler; #endif { user_error_handler = handler; } /* Fatal error in stub. fatal_error() ** ------------------- ** ** This routine calls a user error handler is one has been ** established, and otherwise simply exits with an error message. ** It should ideally unwind the stack to exit the stub, if a user ** error handler has been called, and the status is still bad on ** return (not implemented). ** ** On entry, ** ** p_message points to the message which had the problem, fields: ** ** m_status gives the error code. ** */ #ifdef __STDC__ PRIVATE void fatal_error( rpc_message_pointer p_message) #else PRIVATE void fatal_error(p_message) rpc_message_pointer p_message; #endif { if (user_error_handler != 0) (*user_error_handler)(&p_message); /* call_local_stub */ else { rpc_report_error(&p_message->m_status); exit((int)(p_message->m_status)); } } /* Add element to tail of list. queue_add_tail() ** --------------------------- ** ** void queue_add_tail(list_pointer *list, list_pointer ptr) ** ** Parameters: list pointer to pointer to head of list; ** ** ptr pointer to element to be added to the list. ** ** Return: none ** ** On entry: ** list is the address of the head pointer of the queue ** ptr is the address of the record ** ** The record must not already be on the list. ** ** On exit: ** The record is on the list. ** ** Description: ** ** Add record pointed to by 'ptr' to the tail of the list whose ** head is pointed to by 'list'. The macro allows this to be done to ** any old record format. ** ** History: ** ** 14 Aug 89 Cleaned up. (RB) ** */ #ifdef __STDC__ PRIVATE void queue_add_tail(list_pointer *list, list_pointer ptr) #else PRIVATE void queue_add_tail(list, ptr) list_pointer *list; list_pointer ptr; #endif { list_pointer tmp; ptr->next = NULL; if (*list == NULL) *list = ptr; else { for(tmp = *list; tmp->next; tmp = tmp->next) /*find end*/; tmp->next = ptr; } } #define QUEUE_ADD_TAIL(l,e) queue_add_tail((list_pointer *)(l), \ (list_pointer) (e)) /* Remove record from list. queue_remove() ** ------------------------ ** ** void queue_remove(list_pointer *list, list_pointer ptr) ** ** Parameters: list pointer to pointer to head of list; ** ** ptr pointer to element be removed from the list. ** ** Return: none ** ** On entry The record may but does not have to be on the list ** ** On exit: The record is removed from the list if it was on it ** ** Description: ** ** Remove elemnt pointed to by 'ptr' from the list whose ** head is pointed to by 'list', if present. ** ** History: ** ** 14 Aug 89 Cleaned up. (RB) ** */ #ifdef __STDC__ PRIVATE void queue_remove(list_pointer *list, list_pointer ptr) #else PRIVATE void queue_remove(list, ptr) list_pointer *list; list_pointer ptr; #endif { list_pointer tmp; if (*list == ptr) *list = ptr->next; else { tmp = *list; while (tmp && (tmp->next != ptr)) tmp = tmp->next; if (tmp) tmp->next = ptr->next; } } #define QUEUE_REMOVE(l,e) queue_remove((list_pointer *)(l),(list_pointer)(e)) /* Wildcard character match, case insensitive character_match() ** ------------------------------------------ ** ** On entry, ** reference A character which `key' may be a match for ** key A character which may match 'reference' or be a wildcard. ** On exit, ** return TRUE iff the characater matches. */ #define UPPER(c) ((char) toupper((int) c)) BOOLEAN character_match(reference, key) char reference, key; { return (key == '?') ? TRUE : UPPER(reference) == UPPER(key); } /* Find local stub by name find_local() ** ----------------------- ** ** This routine searches the attached stubs for one with the given name. ** It may be called remotely (see find_remote()) or locally. ** ** On entry, ** ** name Points to an ASCII zero-terminated name which may conatin ** wildcard '?' characters, but must be correct length. ** p Points to a variable to hold the return program number ** ** On exit, ** ** returns a status indicating GOOD or BAD ** *p is the program number if the return value was GOOD. */ #ifdef __STDC__ rpc_status find_local( rpc_name name, program_index *p_prognum) #else rpc_status find_local(name, p_prognum) rpc_name name; program_index *p_prognum; #endif { register program_pointer ser; for(ser=program_list; ser; ser=ser->ser_next) { register int i; for(i=0;;i++) { if (!character_match(ser->ser_package_name[i], name[i])) break; /* Mismatch */ if (name[i]==0) { /* Match to end of name */ CTRACE(tfp, "RPC/Find_local: Local package number %ld for %40.40s\n", ser->ser_program, name); *p_prognum = ser->ser_program; return RPC_S_NORMAL; } if (ser->ser_package_name[i] == 0) break; /* Name too long */ } /* next i */ } /* next ser */ CTRACE(tfp, "RPC/Find_local: No local package called %40.40s\n", name); return RPC_S_NO_SUCH_PACKAGE; } /* Handle an Incoming (or local) Call rpc_handle_call() ** ---------------------------------- ** ** This routine is responsible for calling the server stub, and replying to ** the client if necessary. ** ** On entry, ** pcall points to the call message. ** local_client is set if the call is not local. ** On exit, ** The routine assumes full responsability: no further action is ** necessary. There is no return status. */ #ifdef __STDC__ void rpc_handle_call( rpc_message_pointer *ppmes, BOOLEAN local_client) #else void rpc_handle_call(ppmes, local_client) rpc_message_pointer *ppmes; BOOLEAN local_client; #endif { program_pointer p; program_index number; rpc_status status = RPC_S_NORMAL; register rpc_message *mes1 = *ppmes; mes1->m_status = RPC_S_NO_RESULTS_RETURNED; /* Yet */ mes1->m_index = RPC_PROGRAM_NUMBER_OFFSET; /* skip type, tid */ upk_long(mes1, number); /* Get program number */ if (number == RPCM_PROGRAM) rpcm_server(ppmes); /* Call internal server */ else { p = program_list; while (p && (p->ser_program != number)) p = p->ser_next; if (p == NULL) mes1->m_status = RPC_S_BAD_PROGRAM_NUMBER; else { /* Call the server stub: */ socket_type previous_caller = caller_socket; /* Save */ caller_socket = (*ppmes)->m_socket; CTRACE(tfp, "RPC: Calling stub at %lx (hex)\n", (unsigned long)(p->ser_stub_entry)); (*(p->ser_stub_entry))(ppmes); /* Call local stub */ caller_socket = previous_caller; /* Restore previous */ } /* if program number ok */ } /* if not internal server */ /* We now assume that the server stub may have moved the message pointer ** from mes1 to mes2. ** "No results returned" is taken as a good status, but requiring a reply. ** Any other good status imples that the reply is not required. */ { register rpc_message *mes2 = *ppmes; if (BAD(mes2->m_status) || (mes2->m_status == RPC_S_NO_RESULTS_RETURNED)) { rpc_long byte_count = mes2->m_index; /* Save byte count */ mes2->m_index = 0; if (mes2->m_status == RPC_S_NO_RESULTS_RETURNED) { pck_integer(mes2, RPC_RETURN_MESSAGE); mes2->m_status = RPC_S_NORMAL; mes2->m_index = byte_count; /* Restore byte count */ } else { pck_integer(mes2, RPC_REJECT_MESSAGE); /* Message type */ pck_integer(mes2,0); /* TID */ switch ((int)(mes2->m_status & 0xffff) / 8) { case 5: pck_integer(mes2, RPC_REJECT_PROGRAM); break ; case 1: pck_integer(mes2, RPC_REJECT_VERSION); break ; case 2: pck_integer(mes2, RPC_REJECT_PROCEDURE); break ; case 3: pck_integer(mes2, RPC_REJECT_INVALID_ARGUMENTS); break ; default: pck_integer(mes2, RPC_REJECT_EXCEPTION); } pck_long(mes2, mes2->m_status); if (local_client) fatal_error(*ppmes); } /* Send reply if necessary, ie if the caller is remote, and if the reply ** has not already been sent. Note that TS_WRITE has the option of swapping ** the pointer again. */ if (!local_client) status = TS_WRITE(ppmes); } /* end if bad status */ } /* end with mes2 */ } /* end rpc_handle_call */ /* Decode Status of REJECT message reject_status() ** ------------------------------- ** ** On entry, ** mes Points to a REJECTmessage, fields as follows: ** ** m_index is just before the reason code. ** ** On exit, ** returns the status value representing the problem. */ #ifdef __STDC__ rpc_status reject_status(rpc_message *mes) #else rpc_status reject_status(mes) rpc_message *mes; #endif { int reason; rpc_status status; upk_short(mes, reason); /* Courier reason code*/ switch (reason) { case 0: return RPC_S_BAD_PROGRAM_NUMBER; case 1: return RPC_S_UNSUPPORTED_VERSION; case 2: return RPC_S_BAD_PROCEDURE_NUMBER; case 3: return RPC_S_IVPARAM; case 5: upk_long(mes, status); /* Our reason code */ return status; default: return RPC_S_BAD_REJECT_CODE; } /* switch */ } /* reject_status */ /* Begin a Call rpc_begin() ** ------------ ** ** This routine is called by a the stub in order to allocate a buffer for ** the call, and to fill in the initial fields. ** ** On entry, ** handle Must be a valid handle returned by a call to rpc_open(). ** ppmes Must point to the location to receive the message pointer. ** ** On exit, ** *ppmes is a buffer pointer which may be used by a stub ** with the marshalling routines/macros and rpc_call, etc. Fields:- ** ** m_next Junk ** m_status RPC_S_NORMAL ** m_index RPC_CALL_HEADER_LENGTH ** m_socket valid (zero for a local call) ** ** program number valid ** version number valid ** procedure number valid ** */ #ifdef __STDC__ void rpc_begin( rpc_message_pointer *ppmes, client_pointer handle, rpc_integer version, rpc_integer proc_num) #else void rpc_begin(ppmes, handle, version, proc_num) rpc_message_pointer *ppmes; client_pointer handle; rpc_integer version; rpc_integer proc_num; #endif { rpc_new(ppmes, RPC_BUFFER_SIZE); /* Allocate the buffer */ if (handle->cli_callback) { handle->cli_socket = caller_socket; } { register rpc_message * mes = *ppmes; mes->m_socket = handle->cli_socket; mes->m_index = 0; mes->m_status = RPC_S_NORMAL; pck_integer(mes, RPC_CALL_MESSAGE); /* Message type */ pck_integer(mes, 0); /* TID */ pck_long(mes,handle->cli_program_number); /* program number */ pck_integer(mes, version); /* Package version */ pck_integer(mes, proc_num); /* Procedure number */ } } /* rpc_begin*/ /* Turn around a call message into a Return rpc_turn() ** ---------------------------------------- ** ** (See rpcrts.h for the macro) */ /* Send a message to the remote side rpc_put() ** --------------------------------- ** ** ** This procedure is called by the server OR client stub code. It does an ** exchange to send an extra message. The exchange could be supressed over ** reliable media, to just send the message. A running status is kept, in that ** any bad status is left in the message, and no I/O is done. This is to allow ** the stub to deal with the status as it wants. ** ** Because the initial call will have been dealt with, the index is left at 4, ** as the continuation message ** will be packed starting at byte 4, unlike normal call packets. This is just ** more efficient. ** ** A good status will be preserved (failing errors) so that the ** NO_RESULTS_RETURNED condidtion is not lost. ** ** Restrictions: ** ** Will not work with local calls. ** ** Will not work with multiclient raw ethernet servers. ** ** See also: RPC_Call, RPC_Begin, RPC_Get. ** ** On entry, ** *ppmes must point to a record with fields: ** m_next don't care ** m_index Amount of data to be sent ** m_socket VALID (For example from RPC_BEGIN) ** m_status VALID ** which MUST BE SET UP -- eg by init_return ** rpc_b Set up for first fragment. ** ** On entry, ** *ppmes must point to a record with fields: ** m_next corrupted ** m_index Set to RETURN_HEADER_LENGTH in all cases ** m_socket valid ** m_status changed iff bad status generated. ** which valid, same as on entry ** rpc_b corrupted. ** */ #ifdef __STDC__ void rpc_put(rpc_message_pointer *ppmes) #else void rpc_put(ppmes) rpc_message_pointer *ppmes; #endif { rpc_integer which1, which2; rpc_long len; rpc_status status; rpc_status original_status; /* Must be saved as message may be swapped*/ register rpc_message_pointer mes = *ppmes; if (GOOD(original_status=mes->m_status)) { len = mes->m_index; /* Save length */ mes->m_index = 0; upk_integer(mes, which1); /* Find out whether client or server */ mes->m_index = len; /* Restore length */ if (mes->m_socket) { status = TS_WRITE(ppmes); if (GOOD(status)) { status = TS_READ(ppmes, RPC_PUT_TIMEOUT); if (GOOD(status)) { mes = *ppmes; /* Pointer could have moved */ len = mes->m_index; mes->m_index = 0; upk_integer(mes, which2); if (which1==RPC_CALL_MESSAGE) { if (which2 != RPC_RETURN_MESSAGE) { if (which2 == RPC_REJECT_MESSAGE) status = reject_status(mes); else status = RPC_S_BAD_MSG_TYPE; } } else { /* orginal sent was a return */ if (which2 != RPC_CALL_MESSAGE) status = RPC_S_BAD_MSG_TYPE; } /* original was return */ mes->m_index = 0; /* Discard received data */ pck_integer(mes, which1); /* restore original message type */ } /*if good receive*/ } /*if good send*/ } else { /* socket is zero */ status = RPC_S_NOT_IMPLEMENTED; /* fragmented local call */ } if (BAD(status)) { (*ppmes)->m_status = status; CTRACE(tfp, "RPC_put: Bad status %lx (hex)\n", status); } else { (*ppmes)->m_status = original_status; } } /* if good status */ (*ppmes)->m_index = RETURN_HEADER_LENGTH; /* Whatever the msg type */ } /* rpc_put */ /* Receive a new message rpc_get() ** --------------------- ** ** ** This procedure is called by the server OR client stub code. It does an exchange ** to get an extra message. The exchange could be supressed over reliable media, ** to just get the message. A running status is kept. ** ** Because the initial call will have been dealt with, the index is left at 4, ** whatever the message type. This means that extension fragments to a call will be ** packed starting at byte 4, unlike normal call packets. This is just more ** efficient. ** ** Restrictions: ** ** Will not work with local calls. ** ** Will not work with multiclient raw ethernet servers. ** ** See also: RPC_Call, RPC_Begin, RPC_Put. ** ** On entry, ** *ppmes must point to a record with fields: ** m_next don't care ** m_index don't care ** m_socket VALID (For example from RPC_BEGIN) ** m_status VALID ** which The message type required to be received ** rpc_b don't care ** ** On entry, ** *ppmes must point to a record with fields: ** m_next corrupted ** m_index Set to RETURN_HEADER_LENGTH in all cases ** m_socket valid ** m_status changed iff bad status generated. ** which valid, same as on entry ** rpc_b Parameter values received ** */ #ifdef __STDC__ void rpc_get(rpc_message_pointer *ppmes) #else void rpc_get(ppmes) rpc_message_pointer *ppmes; #endif { rpc_integer which1; /* Type of the message we expect */ rpc_integer which2; /* Type of the message we got */ rpc_long len; rpc_status status; rpc_status original_status; /* Must be saved as message may be swapped*/ register rpc_message_pointer mes = *ppmes; if (GOOD(original_status=mes->m_status)) { len = mes->m_index; /* Save length */ mes->m_index = 0; upk_integer(mes, which1); /* Find out whether client or server */ mes->m_index = len; if (mes->m_socket) { /* Now we must send a dummy message in order to provoke the next message ** being sent back to us. The dummy message will have the opposite type to ** the message we will recieve. */ mes->m_index = 0; /* Discard any data in this message */ if (which1==RPC_CALL_MESSAGE) { /* Change the message type */ pck_integer(mes, RPC_RETURN_MESSAGE); } else { /* not call */ pck_integer(mes, RPC_CALL_MESSAGE); } mes->m_index = RETURN_HEADER_LENGTH; /* in either case*/ status = TS_WRITE(ppmes); if (GOOD(status)) { status = TS_READ(ppmes, RPC_GET_TIMEOUT); if (GOOD(status)) { mes = *ppmes; /* Pointer could have moved */ len = mes->m_index; mes->m_index = 0; upk_integer(mes, which2); if (which2 != which1) { if (which2 == RPC_REJECT_MESSAGE) status = reject_status(mes); else status = RPC_S_BAD_MSG_TYPE; } mes->m_index = 0; pck_integer(mes, which1); /* restore original message type */ } /*if good receive*/ } /*if good send*/ } else { /* socket is zero */ status = RPC_S_NOT_IMPLEMENTED; /* fragmented local call */ } if (BAD(status)) { (*ppmes)->m_status = status; CTRACE(tfp, "RPC_get: Bad status %lx (hex) ***\n", status); } else { (*ppmes)->m_status = original_status; } } /* if good initial status */ (*ppmes)->m_index = RETURN_HEADER_LENGTH; /* Whatever the msg type */ /* Ready for packing/unpacking */ } /*rpc_get*/ /* Make a Remote (or local) Call rpc_call_status() ** ----------------------------- ** ** This routine is called by the client stub, directly or via ** rpc_call(), in order to have the call executed on the remote ** (or local as appropriate) node. ** ** On entry, ** ** handle points to a valid client descriptor. ** pp_message points to A POINTER TO a message, with fields: ** ** m_next (don't care) ** m_status Valid. If bad, operation skipped. ** m_socket (don't care) ** m_index The number of bytes to be sent in rpc_b. ** protocol_header (don't care) ** which (don't care) ** T.I.D. (don't care) ** procedure_number is set up. ** version_number is set up. ** rpc_b has the parameters marshalled. ** ** On exit, ** returns A status value indicating success or otherwise of the call. ** ** *pp_message may have changed, as messages are swapped around. ** The new value of *pp_message, however, will be the return ** message, ready to be unpacked, fields:- ** ** m_next (corrupted) ** m_status (corrupted) ** m_socket valid. ** m_index Offset to start of parameters. ** protocol_header (corrupted) ** which RPC_RETURN_MESSAGE ** T.I.D. (corrupted) ** rpc_b has the parameters marshalled. */ #ifdef __STDC__ rpc_status rpc_call_status( client_pointer *handle, rpc_message_pointer *pp_message, rpc_integer timeout) #else rpc_status rpc_call_status(handle, pp_message, timeout) client_pointer *handle; rpc_message_pointer *pp_message; rpc_integer timeout; #endif { message_type which1; rpc_long tx_size; rpc_status status; register client_pointer cli = *handle; register rpc_message *mes = *pp_message; if (BAD(mes->m_status)) return mes->m_status; if (!cli->cli_socket) { rpc_handle_call(pp_message, TRUE); return RPC_S_NORMAL; } else { status = TS_WRITE(pp_message); if (BAD(status)) return status; mes = *pp_message; /* Note pointer may have been swapped */ /* Now, in the remote case, we allow the server to call us back instead of ** replying. The server can also send a "reject" message, indicating ** that he couldn't process the call. */ for (;;) { register rpc_message_pointer mes2; status = TS_READ(pp_message, timeout); if (BAD(status)) return status; mes2 = *pp_message; /* Now point to received message */ mes2->m_index = 0; /* Start unmarshalling header */ upk_integer(mes2, which1); /* Message type */ mes2->m_index = RETURN_HEADER_LENGTH; /* Skip TID */ switch (which1) { case RPC_RETURN_MESSAGE: return status; /* Good: we have a reply */ case RPC_CALL_MESSAGE: rpc_handle_call(pp_message, FALSE); break ; /* Go wait for another callback or return */ case RPC_REJECT_MESSAGE: return reject_status(mes2); /*NOTREACHED*/ default: return RPC_S_BAD_MSG_TYPE; } /* end switch */ } /* end for */ /*NOTREACHED*/ } /* End if remote */ /*NOTREACHED*/ } /* end rpc_call_status */ /* Make a Remote (or local) Call rpc_call() ** ----------------------------- ** ** This routine is called by the client stub, ahas the call executed ** on the remote (or local as appropriate) node. If it finds an error, ** it takes default emergency action -- like printing a message and exiting. ** ** See also rpc_call_status ** ** On entry, ** ** handle points to a valid client descriptor. ** pp_message points to A POINTER TO a message, with fields: ** ** m_next (don't care) ** m_status (don't care) ** m_socket (don't care) ** m_index The number of bytes to be sent in rpc_b. ** protocol_header (don't care) ** which (don't care) ** T.I.D. (don't care) ** procedure_number is set up. ** version_number is set up. ** rpc_b has the parameters marshalled. ** ** On exit, ** ** *pp_message may have changed, as messages are swapped around. ** The new value of *pp_message, however, will be the return ** message, ready to be unpacked, fields:- ** ** m_next (corrupted) ** m_status valid. ** m_socket valid. ** m_index Offset to start of parameters. ** protocol_header (corrupted) ** which RPC_RETURN_MESSAGE ** T.I.D. (junk) ** rpc_b has the parameters marshalled. */ #ifdef __STDC__ void rpc_call( client_pointer *handle, rpc_message_pointer *pp_message, rpc_integer timeout) #else void rpc_call(handle, pp_message, timeout) client_pointer *handle; rpc_message_pointer *pp_message; rpc_integer timeout; #endif { rpc_status status = rpc_call_status(handle, pp_message, timeout); if (BAD(status)) { (*pp_message)->m_status = status; fatal_error(*pp_message); } } /* Make a Remote (or local) Cast rpc_cast() ** ----------------------------- ** ** This routine is called by the client stub, when no return message is ** required. ** ** On entry, ** ** handle points to a valid client descriptor. ** pp_message points to A POINTER TO a message, with fields: ** ** m_next (don't care) ** m_status Valid. If bad, operation skipped. ** m_socket (don't care) ** m_index The number of bytes to be sent in rpc_b. ** protocol_header (don't care) ** which (don't care) ** T.I.D. (don't care) ** procedure_number is set up. ** version_number is set up. ** rpc_b has the parameters marshalled. ** ** On exit, ** returns A status value indicating success or otherwise of the cast. ** ** *pp_message may have changed, as messages are swapped around. ** Fields:- ** ** m_next (corrupted) ** m_status (corrupted) ** m_socket valid. ** m_index (corrupted) ** protocol_header (corrupted) ** which RPC_RETURN_MESSAGE ** T.I.D. (corrupted) ** rpc_b (corrupted) ** ** ** Bugs: ** @@@ No error handling in. */ #ifdef __STDC__ rpc_status rpc_cast( client_pointer *handle, rpc_message_pointer *pp_message, rpc_integer timeout) #else rpc_status rpc_cast(handle, pp_message, timeout) client_pointer *handle; rpc_message_pointer *pp_message; rpc_integer timeout; #endif { message_type which1; rpc_long tx_size; rpc_status status; register client_pointer cli = *handle; register rpc_message *mes = *pp_message; if (BAD(mes->m_status)) return mes->m_status; if (!cli->cli_socket) { rpc_handle_call(pp_message, TRUE); return RPC_S_NORMAL; } else { status = TS_WRITE(pp_message); return status; } /* End if remote */ /*NOTREACHED*/ } /* end rpc_cast */ /* Client stub for RPC Manager Function find_remote() ** ------------------------------------ ** ** This routine is a client stub for the find_local procedure. It takes ** its information directly from a client record. It assumes that the ** communications path is set up, and so only the package number is ** missing. ** ** The corresponding server stub follows the client stub. ** ** On entry, ** cli points to a valid client descriptor, fields: ** cli_package_name is valid logical package name. ** cli_socket is set up ** On exit, ** status is as returned from remote node ** cli->cli_program_number ** is valid if status is GOOD. ** ** History ** 2 Oct 89 Cleaned up, zero terminated strings. (TBL) ** 7 Oct 89 Status returned on error (instead of exit!) (TBL) ** 4 Dec 89 Bug fix: used cli_logical instead of cli_package_name (TBL) ** 4 Apr 90 Modify to use rpc_begin */ #ifdef __STDC__ rpc_status find_remote( client_pointer cli) #else rpc_status find_remote(cli) client_pointer cli; #endif { rpc_message_pointer rpc_p_buf; client_pointer handle = cli; /* Something we can take address of */ rpc_status status; register int i; /* String index */ int len; /* String length */ cli->cli_program_number = RPCM_PROGRAM; /* First, overwrite prog no */ rpc_begin(&rpc_p_buf, handle, RPCM_VERSION, 1); len = strlen(cli->cli_package_name); pck_short(rpc_p_buf, len); for (i = 0; i<=len-1; i++) pck_char(rpc_p_buf, cli->cli_package_name[i]); status = rpc_call_status(&handle, &rpc_p_buf, FIND_REMOTE_TIMEOUT); if (GOOD(status)) { rpc_p_buf->m_index = RETURN_HEADER_LENGTH; upk_long(rpc_p_buf, status); upk_long(rpc_p_buf, cli->cli_program_number); } rpc_dispose(rpc_p_buf); if (BAD(status)) cli->cli_program_number = -1; CTRACE(tfp, "RPC/Find_remote: Status %lx. hex\n Remote package number = %ld for %4s\n", status, cli->cli_program_number, cli->cli_package_name); return status; } /* end find_remote */ /* Server stub for RPC Manager rpcm_server() ** --------------------------- ** ** This is a standard server stub. See the RPC internals manual for ** detailed entry and exit conditions. This stub is for the "rpcm" package ** which at present contains only find_local(). ** ** On entry, ** *pcall Points to an RPC call message for this package. Fields: ** ** m_status is RPC_S_NO_RESULTS_RETURNED. ** m_index (initial value not needed by this routine) ** m_socket is valid. ** m_next may be corrupted ** ** On exit, ** *pcall Points to an RPC message. Within the message, the fields are ** m_status RPC_S_NO_RESULTS_RETURNED means the reply is yet to go; ** GOOD status indicates reply is not needed; ** BAD status indicates reply should be rejection. ** m_index If a reply is required, the data length. ** m_socket valid. ** m_next may be corrupted */ #ifdef __STDC__ PRIVATE void rpcm_server( rpc_message_pointer *pcall) #else PRIVATE void rpcm_server(pcall) rpc_message_pointer *pcall; #endif { rpc_integer proc_no; rpc_integer len; rpc_name name; program_index returned_program_number; (*pcall)->m_index = CALL_HEADER_LENGTH - 2; upk_integer((*pcall), proc_no); if (proc_no == 1) { register int a; upk_integer((*pcall), len); for (a =0; am_index = RETURN_HEADER_LENGTH; pck_long((*pcall), find_local(name, &returned_program_number)); pck_long((*pcall), returned_program_number); } else { (*pcall)->m_status = RPC_S_BAD_PROCEDURE_NUMBER; } } /* Close Down a Handle close_client() ** ------------------- ** ** This routine checks to see whether the handle is sharing a socket ** with annother client connection. If so, it leaves the socket, bit if not, ** it closes the socket. ** ** On entry, ** cli points to a valid client descriptor. ** On exit, ** cli->cli_socket variable is no longer valid ** returns The status of the close, or "normal" if not needed. */ #ifdef __STDC__ PRIVATE rpc_status close_client( client_pointer cli) #else PRIVATE rpc_status close_client(cli) client_pointer cli; #endif { register client_pointer hand; if (!cli->cli_socket || cli->cli_callback) return RPC_S_NORMAL; for(hand = client_list; hand; hand=hand->cli_next) if ((strcmp(hand->cli_peer_address, cli->cli_peer_address) == 0) && (hand != cli)) return RPC_S_NORMAL; /* Don't have to close -- still in use */ return ts_close(cli->cli_socket); } /* close_client */ /* Is an address a physical address? physical() ** -------------------------------- ** ** On entry, ** p points to an RPC address string ** ** On exit, ** returns true iff the string contains an "@" or a ".". */ #ifdef __STDC__ PRIVATE BOOLEAN physical(char * p) #else PRIVATE BOOLEAN physical(p) char * p; #endif { for(;;p++) { if (*p == '@') return TRUE; if (*p == '.') return TRUE; if (*p == 0) return FALSE; } } /* Set up a Handle setup() ** --------------- ** ** This procedure opens the transport connection as necessary, ** and resolves the program number. ** ** On entry, ** ** handle has been allocated and field: ** cli_logical is valid. ** ** On exit (if good status), ** ** cli_package_name is valid. ** cli_program_number is set up or left at -1. ** cli_socket is valid, or NULL if LOCAL or CALLBACK. ** ** On exit (bad status) ** ** cli_socket is invalid, no socket is open. ** ** The address formats accepted are ** ** [[ nnnn ] @] xxxx % mmmm remote, address xxxx, medium mmmm ** [[ nnnn ] @] LOCAL local ** [[ nnnn ] @] CALLER caller current at open ** [[ nnnn ] @] CALLBACK caller current at time of call ** pppp local, package name pppp ** ** If nnnn is specified and is numeric, it is taken as the program number. ** Otherwise, a look-up is done later, the program number being left at -1. ** If nnnn is specified as non-numeric, then it is taken as the name of the ** package to be looked up. ** */ #ifdef __STDC__ PRIVATE rpc_status setup( client_pointer handle) #else PRIVATE rpc_status setup(handle) client_pointer handle; #endif { rpc_status status; rpc_name s; client_pointer hand; register client_pointer cli = handle; char *tsap; /* pointer to start of network address */ char *p; /* pointer into strings */ cli->cli_callback = FALSE; /* (by default) */ cli->cli_socket = NULL; /* (by default) */ cli->cli_program_number = -1; /* (by default) Means "Undefined" */ /* By default, assume we have a logical name: */ strncpy(cli->cli_package_name, cli->cli_logical, RPC_NAME_LENGTH+1); /* Try requesting a physical name from the name server: ** (If the name is a physical address, don't bother.) */ strncpy(s, cli->cli_logical, RPC_NAME_LENGTH+1); /* By default */ cli->cli_version = 0; cli->cli_compatibility = 0; if (!physical(cli->cli_logical)) { status = cm_request(cli->cli_logical, cli->cli_version, cli->cli_compatibility, s, RPC_NAME_LENGTH); if (BAD(status) & (status == RPC_S_NO_TRANSLATION)) return status; } /* Now, get program number or name if any: */ for(p=s; *p && (*p!='@'); p++); /* search for '@' */ if (*p) { /* string contains an "@" */ tsap = p+1; /* The TSAP is after the "@" */ *p=0; /* Terminate the package part */ if (p==s) { /* @xxxxx */ /* (leave cli_package_name as valid) */ } else { /* xxxx@xxxx */ cli->cli_program_number = 0; for (p=s; (*p >= '0') && (*p <= '9'); p++) cli->cli_program_number = 10 * cli->cli_program_number + (unsigned)(*p) - (unsigned)('0'); if (p==s) { /* Not numeric: aaa@xxxxxx */ strcpy(cli->cli_package_name, s); /* Explicit package name */ cli->cli_program_number = -1; /* "Undefined" */ } else { /* Numeric */ if (*p) return RPC_S_SYNTAX_1; /* nnn???@xxxxx */ } } } else { /* No "@" */ tsap = s; } strcpy(cli->cli_peer_address, tsap); /* save for comparisons */ /* Check for special addressing forms ** ** Now, cli_program_number is -1 (for undefined) or valid; ** cli_package_name is valid if we know it ** tsap points to what's left. ** cli_peer_address is a copy of tsap. */ if (!strcmp(tsap, "CALLER")) { cli->cli_socket = caller_socket; } else if (!strcmp(tsap, "CALLBACK")) { /* cli->cli_socket = NULL; already */ cli->cli_callback = TRUE; return RPC_S_NORMAL; } else if (!strcmp(tsap, "LOCAL")) { /* cli->cli_socket = NULL; already */ } else { for(p=tsap; *p && *p!='.'; p++);/* Look for dot (No dot => LOCAL) */ if (*p) { /* Dot exists -- probably a TSAP */ /* Check for somebody else using the same address: */ for(hand = client_list; hand; hand=hand->cli_next) if ((strcmp(hand->cli_peer_address, cli->cli_peer_address) == 0) && (hand != handle)) break; /* found one */ if (hand) cli->cli_socket = hand->cli_socket; else { status = ts_open(&cli->cli_socket, /* Open new socket */ cli->cli_peer_address); if (BAD(status)) return status; } } /* dot */ } /* not special address */ /* Now, cli_socket is valid or NULL as apropriate ** cli_callback is valid */ /* Find remote program number if undefined: */ if (cli->cli_program_number == -1) { status = find_remote(cli); /* ask remote node */ if (BAD(status)) { if (cli->cli_socket) (void) close_client(cli); /* Close socket unless shared */ return status; } } return RPC_S_NORMAL; } /* setup */ /* Establish a "handle" for a client stub rpc_open() ** -------------------------------------- ** ** This routine must be called by the client stub or client application ** before any remote calls are made for a given package. The resulting ** handle is quoted by the stub in all remote calls. ** ** On entry, ** ** service_name points to a pascal or C string giving EITHER the ** logical name of the package required, OR the ** RPC address of the server stub. See the RPC manual ** for full details of addressing formats. ** ** On exit, ** ** *status is the return status. ** ** *phandle is a valid handle if the status was good, and ** junk if it was bad. ** ** If the open was succesful, the client record is left on the client_list. ** */ #ifdef __STDC__ void rpc_open( rpc_status *status, client_pointer *phandle, rpc_name service_name) #else void rpc_open(status, phandle, service_name) rpc_status *status; client_pointer *phandle; rpc_name service_name; #endif { if (!(initialised)) rpc_init(); if (free_client_list == NULL) (*phandle) = (client_pointer)malloc((unsigned)(sizeof(*(*phandle)))); else { (*phandle) = free_client_list; QUEUE_REMOVE(&free_client_list, *phandle); } if (*phandle == (client_pointer)0) { *status = RPC_S_OPERATING_SYSTEM; /* No memory left! */ return; } rpc_get_string((*phandle)->cli_logical, service_name); *status = setup(*phandle); /* Parse, and set up communications */ if (GOOD(*status)) { QUEUE_ADD_TAIL(&client_list, (*phandle)); } else { QUEUE_ADD_TAIL(&free_client_list, (*phandle)); *phandle = NULL; } } /* Disestablish a "handle" rpc_close() ** ----------------------- ** ** This is the reverse of rpc_open(). ** ** On entry, ** ** pstatus points to a variable to contain the returned status ** handle is a valid value returned by a successfull cal to ** rpc_open() ** On exit, ** ** handle may no longer be used. ** ** See also: rpc_open(), rpc_switch() */ #ifdef __STDC__ void rpc_close(rpc_status *status, client_pointer handle) #else void rpc_close(status, handle) rpc_status *status; client_pointer handle; #endif { CTRACE(tfp, "RPC: Closing package `%s'\n", handle->cli_logical); QUEUE_REMOVE(&client_list, handle); *status = close_client(handle); /* Close socket unless shared */ QUEUE_ADD_TAIL(&free_client_list, handle); } /* Reestablish a handle in case addressing changed rpc_switch() ** ----------------------------------------------- ** ** This is equivalent to doing an rpc_close on an open channel, followed by ** an rpc_open with the same logical address, except that the handle remains ** the same. One might want to do this to ensure that the server addressed ** is the one defined by the current state of the database, etc. ** ** On entry, ** ** pstatus points to a variable to contain the returned status ** ** phandle points to a pointer to the client data structure (NOTE) ** *phandle must be a valid handle returned by a successful ** call to rpc_open(). ** ** On exit, ** ** *pstatus is a standard status value. ** ** *phandle If good status, a handle which must eventually ** by closed by RPC_CLOSE. If bad status, RPC_CLOSE must ** still be called, although the handle is unusable for ** remote calls. */ #ifdef __STDC__ void rpc_switch( rpc_status *pstatus, client_pointer *phandle) #else void rpc_switch(pstatus, phandle) rpc_status *pstatus; client_pointer *phandle; #endif { *pstatus = close_client(*phandle); /* Close socket unless shared */ *pstatus = setup(*phandle); } /* Switch all open clients rpc_configure() ** ----------------------- ** ** This routine performs an rpc_switch on each open client. ** It uses the recursive procedure switch_tail because rpc_switch ** changes the order of things on the client list. ** ** On entry, ** ** pstatus points to a variable to contain the returned status ** ** On exit, ** ** *pstatus is the first bad status to arise, or RPC_S_NORMAL is ** all went well. The routine does not stop if it encounters ** a problem with one handle, but continues with the rest. ** ** Bugs: ** ** If one of the handles cannot be reopened, a bad status is returned. ** In this case, the use has no way of knowing which handles are good and ** which are bad. This procedure is not, therefore, recommended. */ #ifdef __STDC__ void rpc_configure(rpc_status *pstatus) #else void rpc_configure(pstatus) rpc_status *pstatus; #endif { rpc_status status1; client_pointer cli; *pstatus = RPC_S_NORMAL; for (cli=client_list; cli; cli=cli->cli_next) { rpc_switch(&status1, &cli); if (BAD(status1) && GOOD(*pstatus)) *pstatus = status1; /* keep the first bad status */ } /*for*/ } /* Server Routines ** _______________ */ /* Register the fact that we provide a service set_register() ** ------------------------------------------- ** ** This routine will register or deregister according to the flag. ** ** For a given pair of active server, and server package one registration ** is required. This may mean that if we have a large number of active ** servers and a large number of packages, then the number of registrations ** becomes very large. However, neither of these numbers are intended to ** grow without limit, and both will in most cases be 1. ** ** On entry, ** ** flag TRUE to register, FALSE to deregister ** act A valid active server pointer ** ser A valid program pointer ** ** On exit, ** ** returns A standard status. For registeration: ** ** If GOOD, the service has been registered and should ** be deregistered eventually. ** If BAD, the server has not been registered for some reason. ** ** For deregistration, one can make no assumption about ** the registration state afterward of the status is bad. */ #ifdef REGISTER #ifdef __STDC__ PRIVATE rpc_status set_register( BOOLEAN flag, active_server_pointer act, program_pointer ser) #else PRIVATE rpc_status set_register(flag, act, ser) BOOLEAN flag; active_server_pointer act; program_pointer ser; #endif { rpc_name transport_address; /* of the active server */ rpc_status status; /* of the attept to get the address */ status = ts_my_address(act->act_socket, transport_address); if (BAD(status)) return status; { int i; for(i=RPC_NAME_LENGTH-1; (i>=0) && (transport_address[i]==' '); i--) transport_address[i]=0; /* Strip blanks */ } return flag ? cm_register(ser->ser_package_name, ser->ser_version, ser->ser_compatibility, transport_address) : cm_unregister(ser->ser_package_name, ser->ser_version, transport_address); } /* set_register */ #endif /* Specify a stub number explicitly rpc_specify_stub() ** -------------------------------- ** ** This records the entry point and program number of a new server stub ** into the rpc system's list of server stubs. ** ** On entry, ** my_entry is the entry point of the server stub procedure ** my_name is a 40-character or zero terminated package name ** my_number is the required stub number ** p is the address for the return of the program pointer ** ** On exit, ** *status is the completion status. ** *p is a valid program pointer if status is good. ** ** See also: rpc_attach_stub, rpc_detach_stub */ #ifdef __STDC__ void rpc_specify_stub( rpc_status *status, rpc_pointer my_entry, rpc_name my_name, rpc_long my_number, program_pointer *p) #else void rpc_specify_stub(status, my_entry, my_name, my_number, p) rpc_status *status; rpc_pointer my_entry; rpc_name my_name; rpc_long my_number; program_pointer *p; #endif { if (!(initialised)) rpc_init(); if (free_program_list == NULL) *p = (program_pointer)malloc((unsigned)(sizeof(*(*p)))); else { *p = free_program_list; QUEUE_REMOVE(&free_program_list, (*p)); } if (*p == 0) { CTRACE(tfp, "RPC: No space to specify server stub!\n"); *status = RPC_S_OPERATING_SYSTEM; /* No memory */ } else { register program_pointer ser = *p; ser->ser_stub_entry = my_entry; rpc_get_string(ser->ser_package_name, my_name); ser->ser_program = my_number; QUEUE_ADD_TAIL(&program_list, ser); CTRACE(tfp, "RPC: Server stub number %ld for `%s'\n", my_number, ser->ser_package_name); *status = RPC_S_NORMAL; /* Register the package for each active server: */ #ifdef REGISTER { active_server_pointer act; for(act=active_server_list; act; act = act->act_next) { *status = set_register(TRUE, act, ser); } } #endif } } /* rpc_specify_stub */ /* Attach a stub rpc_attach_stub() ** ------------- ** ** This routine is called by the server stub's default initialisation ** procedure. A program number is allocated to the stub, and the stub ** is registered in the rpc system's list of server stubs. ** ** On entry, ** ** status points to an area to receive the status ** e is the entry point of the server stub routine ** name points to a 40 character or zero-terminated package name ** p points to an area to receive the program pointer ** ** On exit, ** ** *status is a standard status code ** *p is a valid program pointer iff the status was good. ** ** See also: rpc_specify_stub, rpc_detach_stub */ #ifdef __STDC__ void rpc_attach_stub(status, e, name, p) rpc_status *status; rpc_pointer e; rpc_name name; program_pointer *p; #else void rpc_attach_stub(status, e, name, p) rpc_status *status; rpc_pointer e; rpc_name name; program_pointer *p; #endif { program_index number; if (!(initialised)) rpc_init(); number = next_program_number; next_program_number = (next_program_number + 1) & 0xff; rpc_specify_stub(status, e, name, number, p); } /* Detach stub rpc_detach_stub() ** ----------- ** ** This removes the registration of a server stub. ** ** On entry, ** ** p must be a program pointer previously obtained by a succesful ** call to rpc_attach_stub or rpc_specify_stub ** ** See also: rpc_specify_stub, rpc_specify_stub */ #ifdef __STDC__ void rpc_detach_stub(program_pointer ser) #else void rpc_detach_stub(ser) program_pointer ser; #endif { CTRACE(tfp, "RPC: Detaching stub %s\n", ser->ser_package_name); /* Deregister the package for each active server: */ #ifdef REGISTER { active_server_pointer act; for(act=active_server_list; act; act = act->act_next) { (void)set_register(FALSE, act, ser); } } #endif QUEUE_REMOVE(&program_list, ser); QUEUE_ADD_TAIL(&free_program_list, ser); } /* Service a call message rpc_service() ** ---------------------- ** ** This procedure may be called by user-written server code. It takes ** a pointer to a call message, services the call, and then disposes of ** the message. ** ** On entry, ** ** p_message points to a message containing a call. ** ** On exit, ** ** The message has been returned to the pool. */ #ifdef __STDC__ void rpc_service(rpc_message_pointer p_message) #else void rpc_service(p_message) rpc_message_pointer p_message; #endif { rpc_message_pointer p_message_1 = p_message; /* A copy to take address of */ rpc_handle_call(&p_message_1, FALSE); rpc_dispose(p_message_1); } #ifdef AST /* Queue a read on a created server rpc_queue_server() ** -------------------------------- ** ** This allows the user writing his own server routine to have ** his own routine invoked whenever a call comes in from a remote client, ** The routine will be invoked at AST level: the user program will be ** interrupted, the user handler called, and then control returned to ** the user program. */ #ifdef __STDC__ void rpc_queue_server( rpc_status *status, socket_type socket, rpc_pointer action, rpc_integer user_1) #else void rpc_queue_server(status, socket, action, user_1) rpc_status *status; socket_type socket; rpc_pointer action; rpc_integer user_1; #endif { rpc_message_pointer p_call; rpc_long rx_size; rpc_new(&p_call, RPC_BUFFER_SIZE); p_call->m_socket = socket; rx_size = RPC_BUFFER_SIZE; *status = TS_AREAD(p_call, action, user_1); } /* Service a call message and queue a new read rpc_server_ast() ** ------------------------------------------- ** ** This procedure is one suitable to be handed to rpc_queue_server(). It ** handles one call, and then queues a new read on the same socket. ** ** On entry, ** ** pmessage points to the message which has been received, ** with fields: ** m_next don't care ** m_status don't care ** m_socket valid ** m_index don't care ** body contains the call. */ #ifdef __STDC__ void rpc_server_ast(rpc_message_pointer pmessage) #else void rpc_server_ast(pmessage) rpc_message *pmessage; #endif #ifndef PIPELINE { rpc_message_pointer this_call = pmessage; rpc_handle_call(&this_call, FALSE); (void) TS_AREAD(this_call, rpc_server_ast, 0); } #else /* The following (not preferred) version queues the read before servicing the ** call. The disadvantages are that ** it will not work if the stub itself does anything to the communications ** channel. For example, if the stub tries to do a direct read from the ** socket, the messages will be read in the wrong order. This will not work ** with fragmentation, for example. */ { rpc_status status; rpc_long rx_size; rpc_message_pointer *next_call; rpc_message_pointer *this_call; this_call = &pmessage; rpc_new(&next_call, RPC_BUFFER_SIZE); next_call->m_socket = this_call->m_socket; status = TS_AREAD(next_call, rpc_server_ast, 0); rpc_handle_call(&this_call, FALSE); rpc_dispose(this_call); } #endif #endif /*AST*/ /* Service the next call rpc_accept() ** --------------------- ** ** This function allows a server program to poll for requests from other ** processes, and to do something else if there aren't any. ** ** On entry, ** ** pstatus points to a variable to receive the completion status. ** ** server_socket is a value returned by a previous successful call to ** rpc_create_server() ** ** timeout A value in units of 10ms (NOTE!), or ** NO_TIMEOUT meaning an indefinite wait, or ** zero indicating action only if possible immediately. ** On exit, ** ** *pstatus is the completion status. A value of RPC_S_TIMEOUT ** indicates that no procedure was serviced. A good ** value indicates that one procedure call was serviced. ** Any other bad value indicates an error occured. ** */ #ifdef __STDC void rpc_accept( rpc_status *pstatus, socket_type server_socket, rpc_integer timeout) #else void rpc_accept(pstatus, server_socket, timeout) rpc_status *pstatus; socket_type server_socket; rpc_integer timeout; #endif { rpc_message_pointer p_call; rpc_new(&p_call, RPC_BUFFER_SIZE); p_call->m_socket = server_socket; *pstatus = TS_READ(&p_call, timeout); if (GOOD(*pstatus)) rpc_handle_call(&p_call, FALSE); rpc_dispose(p_call); } /* Create a server socket rpc_create_server() ** ---------------------- ** ** This routine is used both internally and externally to set up a ** server. Any errors occuring during registration are ignored. ** ** On entry, ** ** pstatus points to a variable to receive the return status ** ** client_name points to a 40-character or zero-terminated string giving ** the network address for the server to serve on, or a logical ** name which will translate to such an address. ** ** On exit, ** ** *pstatus is a return status, standard RPC format. ** ** *psocket If *pstatus is good, a server valid socket: otherwise, ** undefined. ** ** The server socket is put onto a list of active servers. ** ** See also: rpc_delete_server() ** ** History: ** ** 24 Oct 89 Bug fix: would not accept a physical address. (TBL) */ #ifdef __STDC__ void rpc_create_server(pstatus, client_address, psocket) rpc_status *pstatus; rpc_name client_address; socket_type *psocket; #else void rpc_create_server(pstatus, client_address, psocket) rpc_status *pstatus; rpc_name client_address; socket_type *psocket; #endif { rpc_name local_copy, physical_address; int l; if (!(initialised)) rpc_init(); rpc_get_string(local_copy, client_address); /* Allow a logical name to be used for the server address. */ if (BAD(cm_translate(local_copy, physical_address, sizeof(physical_address)))) *pstatus = ts_open(psocket, local_copy); else *pstatus = ts_open(psocket, physical_address); if(BAD(*pstatus)) return; /* Generate an active server record: */ { active_server_pointer act; act = (active_server_pointer) malloc(sizeof(*act)); if (act == 0) { *pstatus = RPC_S_OPERATING_SYSTEM; return; } act->act_socket = *psocket; /* Save socket number */ QUEUE_ADD_TAIL(&active_server_list, act); CTRACE(tfp, "RPC: Created server %lx at `%s'\n", *psocket, local_copy); /* Register the active server for each package: */ #ifdef REGISTER { program_pointer ser; for(ser=program_list; ser; ser = ser->ser_next) { (void) set_register(TRUE, act, ser); } } #endif } } /* rpc_create_server */ /* Delete an Active Server rpc_delete_server() ** ----------------------- ** ** ** This routine is the opposite of rpc_create_server(). Note that the ** parameter it takes is a server socket. In fact, it would be possible ** to take an ective server pointer, and to return the same from ** rpc_create_server() to the user. This would be transparent to the user ** so long as he never calls ts level routines himself, but some of our ** users do. ** ** On entry, ** ** pstatus points to a variable to receive the returned status ** ** psocket points to a socket number which has previously been ** returned by a successful call to rpc_create_server(). ** Note call by address -- more FORTRAN compatipble. ** ** On exit, ** ** *pstatus is a status code indicating whether there was a problem ** ** *psocket may not be used as a socket for any futher calls. */ #ifdef __STDC__ void rpc_delete_server(rpc_status *pstatus, socket_type *psocket) #else void rpc_delete_server(pstatus, psocket) rpc_status *pstatus; socket_type *psocket; #endif { active_server_pointer act; CTRACE(tfp, "RPC: Deleting server %lx\n", (long int)*psocket); /* Search active server list for this socket: */ for(act=active_server_list; act; act = act->act_next) { if (act->act_socket == *psocket) { /* Deregister the activer server for each package: ** (errors are ignored in this step) */ #ifdef REGISTER { program_pointer ser; for(ser=program_list; ser; ser = ser->ser_next) { (void) set_register(FALSE, act, ser); } } #endif /* Delete all record of the active server: */ *pstatus = ts_close(act->act_socket); QUEUE_REMOVE(&active_server_list, act); free(act); return; } } /*for*/ CTRACE(tfp, "RPC: No record of server %lx !\n", (long int)*psocket); } /* rpc_delete_server */ /* Function as a server indefinitely rpc_loop_server() ** --------------------------------- ** ** This routine is the simplest form of server: a synchronous server. It ** waits for calls, executes them one at a time, and then loops to wait ** for the next. It will only exit on communications error. ** ** On entry, ** ** pstatus points to a variable to receive the return status ** ** client_name points to a 40-character or zero-terminated string giving ** the network address for the server to serve on, or a logical ** name which will translate to such an address. ** ** On exit, ** ** *pstatus is a return status, standard RPC format. This will be bad ** on return, as the routine only exits on error. ** ** See also rpc_start_server() starts an asynchronous server. */ #ifdef __STDC__ void rpc_loop_server( rpc_status *pstatus, rpc_name client_address) #else void rpc_loop_server(pstatus, client_address) rpc_status *pstatus; rpc_name client_address; #endif { rpc_message_pointer p_call; socket_type server_socket; rpc_create_server(pstatus, client_address, &server_socket); if (GOOD(*pstatus)) { rpc_new(&p_call, RPC_BUFFER_SIZE); while (GOOD(*pstatus)) { p_call->m_socket = server_socket; *pstatus = TS_READ(&p_call, NO_TIMEOUT); if (GOOD(*pstatus)) rpc_handle_call(&p_call, FALSE); } (void) ts_close(server_socket); rpc_dispose(p_call); } } /* Start an asynchronous server rpc_start_server() ** ---------------------------- ** ** This routine is called by a program which wishes to behave as a server at ** AST level. If the return status is good, then a server will have been ** created in such a way that when a remote client makes a call, the server ** program will be interrupted (if and when ASTs are enabled), the server ** procedure will be called as though the server program had called it, but ** with the client's parameters, and then control will return to the server ** program. This allows a server task to do other things, whilst still in ** communication with the client. ** ** On entry, ** ** pstatus points to a variable to receive the return status ** ** client_name points to a 40-character or zero-terminated string giving ** the network address for the server to serve on, or a logical ** name which will translate to such an address. ** ** On exit, ** ** *pstatus is a return status, standard RPC format. If good, the ** server is started successfully. ** ** Known Deficiencies: ** ** The server socket is not returned, making it impossible for the caller ** to stop the server. */ #ifdef AST #ifdef __STDC__ void rpc_start_server(rpc_status *pstatus, rpc_name client_name) #else void rpc_start_server(pstatus, client_name) rpc_status *pstatus; rpc_name client_name; #endif { rpc_long rx_size; rpc_integer user_1; rpc_message_pointer p_call; rpc_new(&p_call, RPC_BUFFER_SIZE); user_1 = 0; rx_size = RPC_BUFFER_SIZE; rpc_create_server(pstatus, client_name, &p_call->m_socket); if (GOOD(*pstatus)) *pstatus = ts_when_receive(p_call->m_socket, p_call, rx_size, rpc_server_ast, user_1); } #endif /* Reply to a message early rpc_early_reply() ** ------------------------ ** ** ** This routine is called by the server stub module if it wishes to reply ** immediatly to the client. See the PRAGMA CONCURRENT feature of the ** compiler. (Normally, the rpc_handle_call() function will ** reply after the stub has executed, but it checks the m_status field ** of the message, and will only reply if that is still ** RPC_S_NO_RETULTS_RETURNED.) ** ** On entry, ** ** return_msg_p points to A POINTER TO the message. ** ** On return ** ** *return_msg_p may have been changed to point to a different message ** if necessary. The socket and status fields of the ** new message will be valid. ** ** See also: rpc_handle_call() */ #ifdef __STDC__ void rpc_early_return( rpc_message_pointer *return_msg_p) #else void rpc_early_return(return_msg_p) rpc_message_pointer *return_msg_p; #endif { rpc_message_pointer temp; rpc_long message_length; { register rpc_message_pointer mes = *return_msg_p; if (mes->m_status == RPC_S_NO_RESULTS_RETURNED) { mes->m_status = RPC_S_NORMAL; rpc_new(&temp, RPC_BUFFER_SIZE); *temp = *mes; /* Copy the message! */ message_length = temp->m_index; /* Save the length */ temp->m_index = 0; /* Write a header in */ pck_integer(temp, RPC_RETURN_MESSAGE); /* Message type field */ temp->m_index = message_length; /* Restore the length */ if (temp->m_socket) (void) TS_WRITE(&temp); rpc_dispose(temp); } } } /* Clean up entire RPC system rpc_exit() ** -------------------------- ** ** This may be done before exiting an image to ensure that all the ** transport services used by the RPC system have been closed. ** On VMS, this is not necessary, as VMS cleans up very well itself, ** but it is included for testing. ** ** This procedure may be registered to be called on image exit where this ** facility is provided. ** ** Any errors are ignored. ** ** On entry, ** ** rpc_init() must have been called. */ void rpc_exit() { rpc_status status; while (client_list) rpc_close(&status, client_list); while (program_list) rpc_detach_stub(program_list); while (active_server_list) rpc_delete_server(&status, &active_server_list->act_socket); CTRACE(tfp, "RPC: Exit completed, end of trace.\n\n"); if (trace_file_open) { fclose(tfp); trace_file_open = FALSE; } } /* end rpc_exit */ /* Initialize the entire RPC system rpc_init() ** -------------------------------- ** ** On entry, ** No constraints. ** ** On exit, ** other rpc_xxx procedures may be called. */ #ifdef __TURBOC__ extern int atexit(); /* Register our exit procedure */ #endif #ifdef __STDC__ void rpc_init(void) #else void rpc_init() #endif { if (!(initialised)) { initialised = TRUE; /* Set flag so only do once */ user_error_handler = 0; rpc_trace = FALSE; free_messages = NULL; #ifndef vms rpc_register_errors(); /* Force inclusion of error messages */ #endif #ifdef NOHEAP { register int i; for (i = 1; i <= MESSAGE_POOL_SIZE; i++) /* Allocate messages */ rpc_dispose((rpc_message *)malloc((unsigned)(sizeof(rpc_message)))); } #endif free_client_list = NULL; client_list = NULL; free_program_list = NULL; next_program_number = 0; program_list = NULL; active_server_list = (active_server_pointer)0; cm_init(); /* Initialise configuation manager */ ts_init(); /* Initialise transport services */ #ifdef __TURBOC__ (void) atexit(rpc_exit); /* Register our exit procedure */ #endif } /* end if */ check_trace(); /* Turn trace on if required */ }