/*								       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 <stdio.h>
# include <ctype.h>

/*  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();	    /* <string.h>, <strings.h> */
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<RPC_NAME_LENGTH ;i++) {
	dest[i] = param[i];	    /* Copy characters		    */
	if (dest[i]==0) return;	    /* until a zero has been copied */
    }				    /* or 40 characters copied	    */

    for (i=RPC_NAME_LENGTH-1; (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; a<len ; a++) upk_char(*pcall, name[a]);
	name[len] = 0;

	(*pcall)->m_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 */
}