/* (C) Copyright International Business Machines Corporation 23 January */
/* 1990.  All Rights Reserved. */
/*  */
/* See the file USERAGREEMENT distributed with this software for full */
/* terms and conditions of use. */
/* File: asyncrpc.c */
/* Author: David F. Bacon */
#ifndef lint
static char sccsinfo[] = "@(#)asyncrpc.c	1.6 3/14/90";
#endif

#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <netdb.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>

#ifdef _AIX
#  include <sys/select.h>
#endif

#include <rpc/rpc.h>
#include <rpc/pmap_clnt.h>
#include <netinet/tcp.h>

#ifndef _AIX
#  include <netinet/in.h>
#endif

extern int errno;


enum clnt_stat
clnt_send(h, proc, xdr_args, args_ptr)
CLIENT *h;
u_long proc;
xdrproc_t xdr_args;
caddr_t args_ptr;
{
    enum clnt_stat stat;
    struct timeval timeout;

    timeout.tv_sec = 0;		/* no automatic aggregate initialization */
    timeout.tv_usec = 0;

    stat = (* h->cl_ops->cl_call)(h, proc, xdr_args, args_ptr, xdr_void, NULL, 
				  timeout);
    if (stat == RPC_TIMEDOUT) 
      return(RPC_SUCCESS);
    else
      return(stat);
}


/*****************************************************************************/
/*               Client-Side TCP Asynchronous RPC Support                    */
/*****************************************************************************/

static struct protoent tcpproto, *tcpp = NULL;
static int true = TRUE;
static enum clnt_stat (*clnttcp_call)() = NULL;


struct ct_data_prefix {
    int ct_sock;
    struct timeval ct_wait;
    struct rpc_err ct_error;
};


CLIENT *
clntatcp_create(raddr, prog, vers, sockp, sendsz, recvsz)
struct sockaddr_in *raddr;
u_long prog;
u_long vers;
int *sockp;
u_int sendsz;
u_int recvsz;
{
    CLIENT *h;
    struct ct_data_prefix *ct;
    u_short port;


    /* supply port if none given (needed for connect) */

    if (raddr->sin_port == 0) {
	if ((port = pmap_getport(raddr, prog, vers, IPPROTO_TCP)) == 0)
	  return (NULL);
	raddr->sin_port = htons(port);
    }

    /* for now, block on connect but deal with interrupts.  eventually, we */
    /* should have the connects fully asynchronous with some sort of client */
    /* structure that indicates that it isn't yet connected, like the */
    /* rendezvouser on the server side. */

    if (*sockp < 0) {
	if ((*sockp = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) {
	    rpc_createerr.cf_stat = RPC_SYSTEMERROR;
	    rpc_createerr.cf_error.re_errno = errno;
	    return(NULL);
	}

	while (TRUE) {		/* structured programming... what a concept. */
	    if (connect(*sockp, (struct sockaddr *) raddr, sizeof(*raddr))
		== 0) 
	      break;		/* got it; do the clnttcp_create now */
	    if (errno == EINTR) {
		(void) fprintf(stderr, "Connect was interruped; retrying.\n");
		continue;
	    }
	    rpc_createerr.cf_stat = RPC_SYSTEMERROR;
	    rpc_createerr.cf_error.re_errno = errno;
	    return(NULL);
	}
    }


    h = clnttcp_create(raddr, prog, vers, sockp, sendsz, recvsz);
    if (h == NULL)
      return(NULL);		/* failure; bail out */

    if (tcpp == NULL) {		/* if tcpproto is uninit, initialize it.... */
	if ((tcpp = getprotobyname("tcp")) == NULL) {
	    clnt_destroy(h);
	    return(NULL);
	}
	tcpproto = *tcpp;
	tcpp = &tcpproto;
    }

    if (clnttcp_call == NULL)	/* if default call is uninint, copy it */
      clnttcp_call = h->cl_ops->cl_call;

    ct = (struct ct_data_prefix *) h->cl_private;
    if (setsockopt(ct->ct_sock, tcpproto.p_proto, TCP_NODELAY, &true, 
		   sizeof(true))) {
	clnt_destroy(h);
	return(NULL);
    }

    return(h);
}


/*ARGSUSED*/
static enum clnt_stat
clntatcp_send(h, proc, xdr_args, args_ptr, xdr_results, results_ptr, timeout)
CLIENT *h;
u_long proc;
xdrproc_t xdr_args;
caddr_t args_ptr;
xdrproc_t xdr_results;
caddr_t results_ptr;
struct timeval timeout;
{
    struct timeval sendtimeout;
    struct ct_data_prefix *ct = (struct ct_data_prefix *) h->cl_private;
    enum clnt_stat stat;


    sendtimeout.tv_sec = 0;	/* no automatic aggregate initialization */
    sendtimeout.tv_usec = 0;

    stat = (*clnttcp_call)(h, proc, xdr_args, args_ptr, xdr_void, NULL, 
			   sendtimeout);

    if (stat == RPC_TIMEDOUT) 
      return(ct->ct_error.re_status = RPC_SUCCESS);
    else
      return(stat);
}

/*****************************************************************************/
/*               Client-Side Caching of Connections                          */
/*****************************************************************************/

typedef struct cl_cache {
    struct cl_cache *next;

    struct sockaddr_in addr;
    u_long prog;
    u_long vers;

    CLIENT *cl;
} cl_cache_t;

static cl_cache_t *cache = NULL;


CLIENT *
clntatcp_cachedcreate(raddr, prog, vers, sockp, sendsz, recvsz)
struct sockaddr_in *raddr;
u_long prog;
u_long vers;
int *sockp;
u_int sendsz;
u_int recvsz;
{
    cl_cache_t *cc;


    cc = cache;
    while (cc) {
	if (cc->prog == prog && cc->vers == vers && 
	    raddr->sin_family == cc->addr.sin_family &&
	    bcmp((caddr_t) & raddr->sin_addr, (caddr_t) & cc->addr.sin_addr,
		 sizeof(struct in_addr)) == 0) 
	  return(cc->cl);
	cc = cc->next;
    }

    if ((cc = (cl_cache_t *) mem_alloc(sizeof(cl_cache_t))) == NULL) {
	return(NULL);
    }

    if ((cc->cl = clntatcp_create(raddr, prog, vers, sockp, sendsz, recvsz))
	== NULL) {
	mem_free((caddr_t) cc, sizeof(cl_cache_t));
	return(NULL);
    }

    cc->addr = *raddr;
    cc->prog = prog;
    cc->vers = vers;

    cc->next = cache;
    cache = cc;

    return(cc->cl);    
}


/*****************************************************************************/
/*               Server-Side TCP Asynchronous RPC Support                    */
/*****************************************************************************/

static bool_t (*rendezvous_request)() = NULL;

static struct xp_ops svcatcp_rendezvous_op = {
    NULL, NULL, NULL, NULL, NULL, NULL
};


typedef struct { 
    u_int sendsize;
    u_int recvsize;
} tcp_rendezvous;


static void
async_fd(fd)
int fd;
{
    if (fcntl(fd, F_SETFL, FASYNC | fcntl(fd, F_GETFL, 0)) == -1)
      (void) fprintf(stderr,
		     "asyncrpc: async_fd: can't set async on socket.\n");
    if (fcntl(fd, F_SETOWN, getpid()) == -1)
      (void) fprintf(stderr,
		     "asyncrpc: async_fd: can't set ownership of self.\n");
}


SVCXPRT *
svcatcp_create(sock, sendsize, recvsize)
int sock;
u_int sendsize;
u_int recvsize;
{
    bool_t async_rendezvous_request();
    SVCXPRT *xp;


    xp = svctcp_create(sock, sendsize, recvsize);
    if (xp == NULL)
      return(NULL);

    if (rendezvous_request == NULL) { /* initialize our copy of normal funcs */
	rendezvous_request = xp->xp_ops->xp_recv;
	svcatcp_rendezvous_op = *xp->xp_ops;
	svcatcp_rendezvous_op.xp_recv = async_rendezvous_request;
    }

    xp->xp_ops = & svcatcp_rendezvous_op;

    async_fd(xp->xp_sock);
    return(xp);
}
    

static bool_t
async_rendezvous_request(xprt)
SVCXPRT *xprt;
{
    SVCXPRT *svcfd_create();

    int sock;
    tcp_rendezvous *rinfo;
    struct sockaddr_in addr;
    int len;
    SVCXPRT *svc;

  retry:
    len = sizeof(struct sockaddr_in);
    if ((sock = accept(xprt->xp_sock, (struct sockaddr *) & addr, & len)) 
	== -1) {
	if (errno == EINTR)	/* retry on interrupted system call */
	  goto retry;
	return (FALSE);		/* other failure; die silently */
    }

    async_fd(sock);

    rinfo = (tcp_rendezvous *) xprt->xp_p1;
    svc = svcfd_create(sock, rinfo->sendsize, rinfo->recvsize);
    if (svc == NULL)
      return(FALSE);

    svc->xp_raddr = addr;
    svc->xp_addrlen = len;
    return(FALSE);
}



