/***********************************************************************/
/* Open Visualization Data Explorer                                    */
/* (C) Copyright IBM Corp. 1989,1999                                   */
/* ALL RIGHTS RESERVED                                                 */
/* This code licensed under the                                        */
/*    "IBM PUBLIC LICENSE - Open Visualization Data Explorer"          */
/***********************************************************************/

#include <dxconfig.h>


#include <dx/dx.h>

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <ctype.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#ifdef DXD_WIN
#include <sys/timeb.h>
#include <time.h>
#else
#include <sys/time.h>
#endif
#if DXD_HAS_UNIX_SYS_INCLUDES
#include <sys/param.h>
#endif
#include <sys/types.h>
#if HAVE_SYS_FILIO_H
#include <sys/filio.h>
#endif
#ifdef DXD_HAS_WINSOCKETS
#include <winsock.h>
#else
#include <sys/socket.h>
#endif
#ifdef DXD_WIN
#include <io.h>
#include <winioctl.h>
#else
#include <sys/ioctl.h>
#endif
#ifndef DXD_HAS_WINSOCKETS
#include <netinet/in.h>
#endif
#if DXD_SOCKET_UNIXDOMAIN_OK
#include <sys/un.h>
#endif
#include <sys/stat.h>
#ifndef DXD_HAS_UNIX_SYS_INCLUDES
#define S_IXUSR S_IEXEC
#define S_IXGRP S_IEXEC
#define S_IXOTH S_IEXEC
#endif
#ifndef DXD_HAS_WINSOCKETS
#include <netdb.h>
#endif
#ifndef DXD_LACKS_UNIX_UID
#include <pwd.h>
#endif

#include "pmodflags.h"
#ifdef	DXD_WIN
#define	popen   _popen
#define pclose _pclose
#endif

#include "obmodule.h"
#include "config.h"
#include "utils.h"
#include "parse.h"
#include "distp.h"
#include "context.h"

#if DXD_NEEDS_SYS_SELECT_H
#include <sys/select.h>
#endif

extern Object _dxfExportBin_FP(Object o, int fd);
extern Object _dxfImportBin_FP(int fd);
extern int _dxfExRemoteExec(int dconnect, char *host, char *ruser, 
			    int r_argc, char **r_argv, int outboard);
extern Error _dxfExRemote(Object *in, Object *out);
extern void _dxfPrintConnectTimeOut(char *execname, char *hostname);

#if DXD_POPEN_OK && DXD_HAS_LIBIOP
#define popen popen_host
#define pclose pclose_host
#endif

#define MAX_STARTUP_ARGS 100

#define	BUFLEN	4096

/* This routine returns the pid of the forked child to communicate with (or
 * 0 if no waiting is required),
 * or, if failure, -1.  It (for now) also does a perror.  It also returns
 * remin, remout, and remerr, stdin,out,err for the created task.
 */
int
ExConnectTo(char *host, char *user, char *cwd, int ac, char *av[], char *ep[], 
    char *spath, int *remin, int *remout, int *remerr)
{
    char s[BUFSIZ];
    char wd[BUFSIZ],script_name[500],cmd[1000];
    char cmdpvs[1000];
    char localhost[BUFSIZ];
    FILE *fp = NULL;
    int i, k;
    int in[2], out[2], err[2];
    char **rep;
    char *fargv[MAX_STARTUP_ARGS];
    int child;
    struct hostent *he;
    char *home;
    int findx;
    char *pathEnv;
    char *is;
    char *os;
    int  j;
    int ret;
    int found;

#if DXD_HAS_GETDTABLESIZE
    int  width = getdtablesize();
#else
#ifdef DXD_HAS_WINSOCKETS
    int  width = FD_SETSIZE;
#else
    int  width = MAXFUPLIM;
#endif
#endif
    char *dnum;
    char dstring[256];

#ifdef DXD_WIN   /*   AJ    */
    goto error_return;
#endif
    /*
     * Initialize return values (to default negative results).
     */
    if (remin)
	*remin  = -1;
    if (remout)
	*remout = -1;
    if (remerr)
	*remerr = -1;

    /*
     * Check to see if "host" is a valid hostname.
     */
    if (strcmp("unix", host) != 0)
    {
	he = gethostbyname (host);
	if (he == NULL)
	{
	    herror (host);
	    return (-1);
	}
    }

    for (pathEnv = NULL, i = 0; ep[i] && pathEnv == NULL; ++i)
	if (    strncmp (ep[i], "PATH=", strlen("PATH=")) == 0 ||
		strncmp (ep[i], "PATH =", strlen("PATH =")) == 0) 
	    pathEnv = ep[i];

    if (_dxfHostIsLocal(host)) {
	char *path;
	char *opath;
	struct stat sbuffer;

	if (user != NULL)
	    fprintf (stdout, "Different user on local machine ignored\n");

        if(ac > MAX_STARTUP_ARGS) {
            DXUIMessage("ERROR", "number of arguments exceeds max");
            goto error_return;
        }
	for (i = 0; i < ac; ++i) 
	    fargv[i] = av[i];
	fargv[i] = 0;
        findx = ac+1;
	rep = ep;
        /* First search directories in spath for specified command. Then
	 * scan through ep looking for "PATH".  If "PATH" is found, then
	 * look through path for the specified command, and if it's
	 * found and executable, replace it in fargv with the expanded 
	 * path name.
	 */
#if DXD_HAS_LIBIOP
	if (*fargv[0] != '.' && *fargv[0] != '/' && 
            strncmp(fargv[0], "os ", 3) != 0 ) {
#else 
	if (*fargv[0] != '.' && *fargv[0] != '/') {
#endif
            found = FALSE;
            for(k = 0; k < 2; k++) { 
                if(k == 0)
                    path = spath;
                else {
	            path = strchr (pathEnv, '=');
                    if(path == NULL)
                        path = ".";
                    else path++;
                }
	        while (*path) {
		    for (i = 0; *path != '\0' && *path != ':'; ++i, ++path) 
		        s[i] = *path;
	 	    if (*path == ':') 
		        ++path;
		    s[i] = '\0';
		    strcat (s, "/");
		    strcat (s, av[0]);
		    if (stat (s, &sbuffer) < 0) {
		        /* if the file doesn't exist, go on */
		        if (errno == ENOENT) 
			    /* We have no more paths to search */
			    if (*path == '\0' && k == 1)  {
                                DXUIMessage("ERROR", "can't find %s", av[0]);
			        goto error_return;
                            }
		        /* Ignore bad stats, we just can't find it */
		        continue;
		    }
		    /* If it's executable, and a regular file, we're done. */
		    if ((sbuffer.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)) != 0 &&
		        (sbuffer.st_mode & S_IFREG) != 0) {
                        found = TRUE;
		        break;	/* out of while(*path) */
                    }
                }
                if(found)
                    break;
	    }
	    fargv[0] = s;
	}
    }
    else {		/* Host is NOT local */
#if DXD_POPEN_OK
	/* The general thread here is to first create a script on the
	 * remote host using popen().  The script contains shell code to
	 *   1) set up a similar environment to the current one (PATH,...)
	 *   2) exec 'dx -...' 
	 *   3) remove the script from the remote file system.
	 * Then we set up fargv to contain the following: 
	 *    rsh host [ -l user ] /bin/sh /tmp/dx-$host:$pid 
	 */ 

        if(gethostname(localhost, BUFSIZ) < 0) {
            DXUIMessage("ERROR", "gethostname returned error");
            goto error_return;	
        }
	sprintf(script_name,"/tmp/dx-%s:%d",localhost,getpid());
	findx=0;
	fargv[findx++] = RSH;
	fargv[findx++] = host;

	if (user != NULL) {
	    fargv[findx++] = "-l";
	    fargv[findx++] = user;
	    sprintf(cmd,"%s %s -l %s 'cat > %s'",RSH, host, user, script_name);
	} else
	    sprintf(cmd,"%s %s 'cat > %s'",RSH,host,script_name);

	fargv[findx++] = "/bin/sh";
	fargv[findx++] = script_name; 
        fargv[findx++] = ">";
        fargv[findx++] = "/dev/null";
	fargv[findx++] = NULL ;
		
	fp = popen(cmd,"w");
	if (!fp) {
		perror("popen");
		goto error_return;
	}
	setbuf(fp,NULL); /* Unbuffered so file is 'immediately' updated */
	
	for (i = 0; ep[i]; ++i) {
	    /* Environment variables which are NOT passed to remote process */
	    static char *eignore[] = {"HOST=", "HOSTNAME=", "TZ=",
                                      "SHELL=", "DXHOST=", "ARCH=", 0}; 
	    int ignore;
	    
	    /* Look for and skip certain environment variables */
	    /* We could do this more quickly elsewhere, but ... */ 
	    for (ignore=j=0 ; !ignore && eignore[j] ; j++)
		if (!strncmp(ep[i], eignore[j], strlen(eignore[j])))
		   ignore = 1;
	    if (ignore)
		continue;

	    /* 
	     * Write the environment variable to the remote script file 
	     */
	    if ((strncmp(ep[i],"DISPLAY=unix:",strlen("DISPLAY=unix:"))==0 ||
		 strncmp(ep[i],"DISPLAY=:",    strlen("DISPLAY=:")) == 0) &&
		(dnum = strchr(ep[i], ':')) != NULL)
	    {
		fprintf(fp,"DISPLAY='%s%s';export DISPLAY\n",
							localhost,dnum);
	    } else {
		int k;
		char evar[256], c;
		char eval[1024];
	
		for (j=0 ; ep[i][j] && (ep[i][j] != '=') ; j++)
			evar[j] = ep[i][j];
		evar[j] = '\0';
		k = j + 1;	/* Skip the '=' sign */
		for (j=0 ; c = ep[i][k] ; k++, j++) {
			if (c == '\'') {		/* ' -> '\'' */
				/* Value contains a double quote */
				eval[j++] = '\'';
				eval[j++] = '\\';
				eval[j++] = '\'';
			} 
			eval[j] = c; 
		}
		eval[j] = '\0';
                if(strcmp(evar, "PATH")) 
	            fprintf(fp,"%s='%s'; export %s\n", evar, eval, evar);
                else
	            fprintf(fp,"%s=$PATH:'%s'; export %s\n", evar, eval, evar);
	    }
	}
        /* replace PATH with new search path for outboard module */
        if(spath)
	    fprintf(fp,"PATH='%s':$PATH; export PATH\n", spath);

        if(cwd != NULL) {
            fprintf(fp, "\nif [ -d %s ]; then\n", cwd);
            fprintf(fp, "cd %s\n", cwd);
            fprintf(fp, "else\n echo cannot change directory to %s\n", cwd);
            fprintf(fp, "fi \n");
        }

	fprintf(fp,"\n(sleep 15; rm -f %s) &\nexec ", script_name);

	for (i = 0; i < ac; ++i)
	    fprintf(fp, "%s ",av[i]);
	fprintf(fp,"\n");
	ret = pclose(fp);
	if (ret == -1) {
	    perror("popen/pclose");
	    goto error_return;
	}
        else if(ret != 0)
            goto error_return;
		
	rep = ep;
#else
      DXUIMessage("ERROR", "popen is unavailable on this platform");
      goto error_return;	
#endif
    }

#if DXD_HAS_LIBIOP
    strcpy(cmdpvs, fargv[0]);
    for(i = 1; i < findx-1; i++) {
        strcat(cmdpvs, " ");
        strcat(cmdpvs, fargv[i]);
    }
    strcat(cmdpvs, "&");
    ExDebug("*2", "cmd to run %s", cmdpvs);
    system(cmdpvs);
    return(1);
#elif sgi
    if(pcreateve(fargv[0], fargv, rep) < 0) {
        perror("pcreateve");
        exit(1);
    }
    return(1);

#else
    /* Set up three pipes */
    if (remin && pipe(in) < 0) {
	perror("pipe(in)");
	goto error_return;
    }
    if (remout && pipe(out) < 0) {
	perror("pipe(out)");
	goto error_return;
    }
    if (remerr && pipe(err) < 0) {
	perror("pipe(err)");
	goto error_return;
    }

#if DXD_HAS_VFORK
    child = vfork();
#else
    child = fork();
#endif
    if (child == 0) {
	if (remin)
	    close (in[1]);
	if (remout)
	    close (out[0]);
	if (remerr)
	    close (err[0]);
	if (remin && dup2(in[0], 0) < 0) {
	    perror("dup2");
	    exit(1);
	}
	if (remout && dup2(out[1], 1) < 0) {
	    perror("dup2");
	    exit(1);
	}
	if (remerr && dup2(err[1], 2) < 0) {
	    perror("dup2");
	    exit(1);
	}
	/* Close all other file descriptors */
	for (i = 3; i < width; ++i) 
	    close(i);

	if (cwd != NULL && chdir (cwd) < 0) {
	    perror ("chdir");
	    exit(1);
	}
	if (execve(fargv[0], fargv, rep) < 0) {
	    perror("execve");
	    exit(1);
	}
    }
    else if (child > 0) {
	if (remin) {
	    close (in[0]);
	    *remin = in[1];
	}
	if (remout) {
	    close (out[1]);
	    *remout = out[0];
	}
	if (remerr) {
	    close (err[1]);
	    *remerr = err[0];
	}
    }
    else {
	perror("fork");
	goto error_return;
    }

    return (child);
#endif

error_return:
    return (-1);
}

Error ChildOutput(int fd, Pointer in)
{
    char buffer[512];
    int nbytes;

    nbytes = read(fd, buffer, 512);
    ExDebug("*1", "ChildOutput %d %d", fd, nbytes);
    if(nbytes <= 0) {
        /* EOF or terminate */
        DXRegisterInputHandler (NULL, fd, NULL);
        close(fd);
        return(ERROR);
    }
    else {
        buffer[nbytes] = '\0';
        DXMessage("%s: %s", (char *)in, buffer);
    }
    return(OK);
}

int _dxfExRemoteExec(int dconnect, char *host, char *ruser, int r_argc, 
                     char **r_argv, int outboard)
{
#ifdef DXD_WIN    /*   AJ   */
    return 0;
#else
    char                myhost[MAXHOSTNAMELEN + 2];
    char		cwd[MAXPATHLEN];
    char		*spath	= NULL;
    int                 dxport, dxsock;
    int                 dxfd    = -1;
    struct sockaddr_in  dxserver;
#if DXD_SOCKET_UNIXDOMAIN_OK
    struct sockaddr_un  dxuserver;
    int			dxusock;
#endif
    int                 i, len;
    extern char         **environ;
    int                 child = 0;
    int                 in = -1, out = -1, error = -1;
    char                **nargv = NULL;
    int                 nargc;

 
    if (gethostname (myhost, sizeof(myhost)) < 0)
        goto error;
    if ((unsigned int)(getcwd (cwd, sizeof (cwd))) == (unsigned int)NULL)
	goto error;
    if (spath == NULL)
    {
	spath = getenv ("DXMODULES");
	if (spath == NULL)
	    spath = ".";
    }

    /* create the socket that will remain open between dx and module */
#if DXD_SOCKET_UNIXDOMAIN_OK
    dxport = _dxfSetupServer(OBPORT, &dxsock, &dxserver, &dxusock, &dxuserver);
#else
    dxport = _dxfSetupServer(OBPORT, &dxsock, &dxserver);
#endif
    if (dxport < 0)
        goto error;

    /* add hostname, port number and trailing NULL to arguments */   
    nargc = r_argc + 2;
 
    nargv = (char **)DXAllocateLocalZero((nargc + 1) * sizeof(char *));
    if(nargv == NULL) {
        DXUIMessage("ERROR", "Could not allocate memory for argument list");
        goto error;
    }
    for(i = 0; i < r_argc; i++)
        nargv[i] = r_argv[i];

    if(outboard) {
        nargv[r_argc] = (char *)DXAllocateLocal(
                                     (strlen(myhost)+1)*sizeof(char));
        if(nargv[r_argc] == NULL) {
            DXUIMessage("ERROR", 
                        "Could not allocate memory for argument list");
            goto error;
        }
        strcpy(nargv[r_argc], myhost);
        nargv[r_argc+1] = (char *)DXAllocateLocal(5*sizeof(char));
        if(nargv[r_argc+1] == NULL) {
            DXUIMessage("ERROR", 
                        "Could not allocate memory for argument list");
            goto error;
        }
        sprintf(nargv[r_argc+1], "%4d", dxport);
        nargv[r_argc+2] = NULL;
    }
    else {
        nargv[r_argc] = (char *)DXAllocateLocal(
                                     (strlen("-connect")+1)*sizeof(char));
        if(nargv[r_argc] == NULL) {
            DXUIMessage("ERROR", 
                        "Could not allocate memory for argument list");
            goto error;
        }
        strcpy(nargv[r_argc], "-connect");
        nargv[r_argc+1] = (char *)DXAllocateLocal(
                                  (strlen(myhost) + 6)*sizeof(char));
        if(nargv[r_argc+1] == NULL) {
            DXUIMessage("ERROR", 
                        "Could not allocate memory for argument list");
            goto error;
        }
        strcpy(nargv[r_argc+1], myhost);
        sprintf(nargv[r_argc+1]+strlen(myhost), ":%4d", dxport);
        nargv[r_argc+2] = NULL;
    }

    if(dconnect) {
        fprintf(stderr, "start on %s: ", host);
        for(i = 0; i < nargc; i++)
            fprintf(stderr, "%s ", nargv[i]);
        fprintf(stderr, "\n");    
        fflush(stderr);
    }
    else {
        if(outboard) {
            _dxfPrintConnectTimeOut(nargv[0], host);
            child = ExConnectTo(host, ruser, cwd, nargc, nargv,
                        environ, spath, &in, NULL, NULL);
        }
        else {
            _dxfPrintConnectTimeOut("Distributed DX", host);
            child = ExConnectTo(host, ruser, cwd, nargc, nargv,
                        environ, NULL, &in, &out, &error);
        }

        /* we don't need child's stdin */
        if(in >= 0)
            close(in);
        if(out >= 0) {
            ExDebug("*1", "register %d output for %s", out, host);
	    DXRegisterInputHandler (ChildOutput, out, (Pointer)host);
        }
        if(error >= 0) {
            ExDebug("*1", "register %d stderr for %s", error, host);
	    DXRegisterInputHandler (ChildOutput, error, (Pointer)host);
        }
    }

    if(child > 0 || dconnect) {
#if DXD_SOCKET_UNIXDOMAIN_OK
        /* if we are debugging do not set a timeout on connection */
        dxfd = _dxfCompleteServer(dxsock, dxserver,
                                  dxusock, dxuserver,
                                  !dconnect);
#else
        /* if we are debugging do not set a timeout on connection */
        dxfd = _dxfCompleteServer(dxsock, dxserver,
                                  !dconnect);
#endif
        if (dxfd < 0)
            goto error;
    }
    if(child == -1)
        goto error;

done:
    DXFree(nargv[r_argc]);
    DXFree(nargv[r_argc+1]);
    DXFree(nargv);
    return (dxfd);

error:
    if (dxfd >= 0)
	close (dxfd);
    goto done;

#endif
}

Error OBDelete(Pointer in)
{
    ObInfo *ob_info = (ObInfo *)in;

    close(ob_info->fd);
#ifndef DXD_HAS_LIBIOP
    if (!_dxd_exDebugConnect)
        wait(0);
#endif
    DXRegisterInputHandler (NULL, ob_info->fd, NULL);

    DXFree((Pointer)ob_info->modid);

    DXFree((Pointer)ob_info);
    return(OK);
}

Error OBAsync(int fd, Pointer in)
{
    int msb_signature = 0xEF33AB88;
    int lsb_signature = 0x88AB33EF;
    ObInfo *ob_info = (ObInfo *)in;
    int i;

    /* make sure this is from an outboard ReadyToRun routine,
     * and handle errors.
     */
    if (read(fd, &i, sizeof(i)) <= 0) {
	/* put an ioctl here to be sure?  this should be ok */
	DXRegisterInputHandler (NULL, ob_info->fd, NULL);
	ob_info->deleted = 1;
	return ERROR;
    }

    if (i == msb_signature || i == lsb_signature)
	DXReadyToRun(ob_info->modid);

    return OK;
}

#define HIDDEN_INPUTS 5    /* exec name, host name, flags, #inputs, #outputs */
#define PREDEFINED_EXP_GRP_MEMBERS 2  /* sent to outboard: #in, inlist, #out */
#define PREDEFINED_IMP_GRP_MEMBERS 3  /* returned: rc, rc_string, #out */

#define OUTERRMSG  "connection to outboard module failed"

/* all outboards call this as their module entry point.
 */
Error DXOutboard (Object *in, Object *out)
{
    return _dxfExRemote(in, out);
}

Error
_dxfExRemote (Object *in, Object *out)
{
    extern      int _dxf_ExGetCurrentInstance();

    Error	ret	= ERROR;
    char	*cmd;
    char	*hostp;
    char	*userp;
    int		*iptr;
    int		i;
    int		nin;
    int		nout;
    int		flags;
    int		cnt;
    int		memcount;
    int		persistent = 0;
    int		async = 0;
    char	*asyvarname;
    Array	parmlist = NULL;
    Group	g	 = NULL;
    char	buff[BUFLEN];
    int		fd = -1;
    int		c;
    int		instance = 0;
    Object	obj;
    ErrorCode	rc;
    char	*rs	= NULL;
    ObInfo      *ob_info = NULL;
    char        *av[2];
    Object	attr;
    int		isMsg;
    char	*msg;
    Object      cachelist[2];
    char	cachetag[32];
    int 	one = 1; 
    int 	zero = 0;


    /* 
     * extract info from hidden parms.
     */
    if (! DXExtractString (in[0], &cmd))
	goto cleanup;
    if (! DXExtractString (in[1], &hostp))
	goto cleanup;
    if (! DXExtractInteger (in[2], &flags))
	goto cleanup;
    if (! DXExtractInteger (in[3], &nin))
	goto cleanup;
    if (! DXExtractInteger (in[4], &nout))
	goto cleanup;

    ExDebug("*2","DXOutboard hidden inputs: %s, %s, %d, %d, %d", 
	    cmd, hostp, flags, nin, nout);

    
    strcpy (buff, hostp);
    hostp = buff;
    userp = strchr (buff, ',');
    if (userp)
	*userp++ = '\000';

    persistent = flags & MODULE_PERSISTENT;
    async = flags & (MODULE_ASYNC | MODULE_ASYNCLOCAL);

    instance = _dxf_ExGetCurrentInstance();
    if (async)
	DXExtractString(in[nin-2], &asyvarname);

    nin -= HIDDEN_INPUTS;

    g = DXNewGroup ();
    if (g == NULL)
	goto cleanup;
    memcount = 0;

    /* 
     * package remaining parms into a single group object for transmission
     * to remote process.  members are: input parm count, input object list
     * (so remote process knows which are null and which are being sent),
     * the maximum number of outputs the module will produce, and then all
     * inputs which aren't null.
     */
    obj = (Object)_dxfExNewInteger (nin);

    if (! DXSetEnumeratedMember (g, memcount++, obj))
	goto cleanup;

    parmlist = DXNewArray(TYPE_INT, CATEGORY_REAL, 0);
    if (!parmlist)
	goto cleanup;

    for (i = 0; i < nin; i++) {
	if (! DXAddArrayData(parmlist, i, 1, in[HIDDEN_INPUTS+i] ? 
			     (Pointer)&one : (Pointer)&zero))
	    goto cleanup;
    }

    if (! DXSetEnumeratedMember(g, memcount++, (Object)parmlist))
	goto cleanup;

    if (! DXSetEnumeratedMember (g, memcount++, in[4]))
	goto cleanup;
    
    for (i = HIDDEN_INPUTS; i < nin+HIDDEN_INPUTS; i++)
    {
	if (! in[i])
	    continue;
	
	if (! DXSetEnumeratedMember (g, memcount++, in[i]))
	    goto cleanup;
    }

    cachelist[0] = in[0];
    cachelist[1] = in[1];
    sprintf(cachetag, "RemoteConnect%d", instance);

    /*
     * if this is a persistent module and you have already opened the
     * socket connection, get the open file descriptor from the cache.
     * make sure the socket is still alive.
     */
    if (persistent &&
	((obj = DXGetCacheEntryV(cachetag, 0, 2, cachelist)) != NULL))
    {
        ob_info = (ObInfo *) DXGetPrivateData ((Private) obj);
        fd = ob_info->fd;
        DXDelete ((Object) obj);  /* get rid of our extra reference here */
	if (ob_info->deleted) {
	    DXSetError (ERROR_INVALID_DATA, OUTERRMSG);
	    DXSetCacheEntryV(NULL, 0, cachetag, 0, 2, cachelist);
	    goto cleanup;
	}
    }
    else 
    {
	av[0] = cmd;
	av[1] = NULL;
	
	fd = _dxfExRemoteExec (_dxd_exDebugConnect, hostp, userp, 1, av, 1);
	
        if (fd < 0)
        {
	    DXSetError (ERROR_BAD_PARAMETER,
			"connection to host %s failed", hostp);
	    goto cleanup;
        }
        ExDebug("*2", "Connected to Outboard module");

	if (persistent)
	{
	    ob_info = (ObInfo *) DXAllocateZero (sizeof(ObInfo));
	    if (!ob_info)
	    {
		close (fd);
		goto cleanup;
	    }
	    ob_info->fd = fd;
	    if (async)
		ob_info->modid = (char *)DXGetModuleId();

	    obj = (Object) DXNewPrivate ((Pointer) ob_info, OBDelete);
	    if (!obj) 
	    {
		close (fd);
		DXFree (ob_info->modid);
		DXFree (ob_info);
		goto cleanup;
	    }

	    DXSetCacheEntryV((Object)obj, CACHE_PERMANENT, 
			     cachetag, 0, 2, cachelist);
	}
    }

    /*
     * Send the data to the remote module and get rid of the packaging.
     */
    if (_dxfExportBin_FP ((Object) g, fd) == ERROR) {
	DXSetError(ERROR_INVALID_DATA, "Failed to send data to outboard module");
	goto cleanup;
    }
    ExDebug("*2", "Sending Input Objects to Outboard Module");

    DXDelete ((Object) g);
    g = NULL;


    /*
     * Import objects until you get one that's not a message or 
     * an async request to run.
     *
     */
    do {
        int b;
	isMsg = 0;
	if ((obj = _dxfImportBin_FP (fd)) == NULL) {
            fd_set fdset;
            struct timeval tv;
            int nsel;
	    DXSetError(ERROR_INVALID_DATA, "Failed to receive data from outboard module");
            FD_ZERO(&fdset);
            FD_SET(fd, &fdset);
            tv.tv_sec = 0;
            tv.tv_usec = 0;
            nsel = select(fd + 1, (SelectPtr) &fdset, NULL, NULL, &tv);
            if(nsel > 0) {
                if((IOCTL(fd, FIONREAD, (char *)&b) < 0) || b <= 0)  {
                    DXSetError(ERROR_INVALID_DATA, 
                               "Connection to outboard module has been broken");
                    if(persistent)
                        DXSetCacheEntryV(NULL, 0, cachetag, 0, 2, cachelist);
                }
            }
	    goto cleanup;
	}
        ExDebug("*2", "Received Object from Outboard Module");
	attr = DXGetAttribute(obj, "message");
	if (attr != NULL && DXExtractInteger(attr, &isMsg) != NULL && isMsg)
	{
	    /* message */
	    if (isMsg == 1)
	    {
		Object whoObj = NULL;
		Object msgObj = NULL;
		char *who, *msg;
		va_list nolist = { NULL };
		
		whoObj = DXGetEnumeratedMember((Group)obj, 0, NULL);
		if (!whoObj || !DXExtractString(whoObj, &who))
		    goto message_cleanup;
		
		msgObj = DXGetEnumeratedMember((Group)obj, 1, NULL);
		if (!msgObj || !DXExtractString(msgObj, &msg))
		    goto message_cleanup;
		
		DXqmessage (who, msg, nolist);
	    }
	    /* async request */
	    if (isMsg == 2)
	    {
		if (async)
		    DXReadyToRun((Pointer)asyvarname);
	    }
 message_cleanup: ;
	}
	if (isMsg)
	    DXDelete(obj);
    } while(isMsg);
    g = (Group)obj;

    /* turn on input handler here */
    if (persistent && async) {
	if (ob_info->async_handler == 0) {
	    DXRegisterInputHandler (OBAsync, ob_info->fd, ob_info);
	    ob_info->async_handler = 1;
	}
    }

    /* return from outboard.  values are packaged into a single group
     * for transmission.  members are: return code, return message string,
     * output object list (so this routine knows how many members aren't
     * null) and the actual output objects.
     */

    /*
     * Check the error code.  If it is not a good one then get the
     * error message and set the error status and return.
     */

    obj = DXGetEnumeratedMember (g, 0, NULL);
    if (obj == NULL)
	goto cleanup;
    if (! DXExtractInteger (obj, (int *) &rc))
	goto cleanup;
    if (rc != ERROR_NONE)
    {
	obj = DXGetEnumeratedMember (g, 1, NULL);
	if (obj == NULL)
	    goto cleanup;
	if (! DXExtractString (obj, &rs))
	    goto cleanup;
        ExDebug("*2", "Outboard Module returning with error %s", rs);
	DXSetError (rc, rs);
	goto cleanup;
    }

    /*
     * Find out how many outputs were generated and extract them.
     * Here we reference the returned objects so that they don't get
     * deleted when we get rid of the packaging.  BUT, before we
     * return we need to unreference them so that we are handing
     * out objects with a reference count of zero.
     */

    obj = DXGetEnumeratedMember (g, 2, NULL);
    if (obj == NULL)
	goto cleanup;

    iptr = (int *) DXGetArrayData ((Array) obj);
    if (iptr == NULL)
	goto cleanup;

    for (i = 0, cnt = 3; i < nout; i++)
    {
	if (*iptr++)
	{
	    obj = DXGetEnumeratedMember (g, cnt++, NULL);
	    if (obj == NULL)
		goto cleanup;
	    out[i] = DXReference (obj);
            ExDebug("*2", "Setting output %d for Outboard Module", i);
	}
    }

    DXDelete ((Object) g);
    g = NULL;
    for (i = 0; i < nout; i++)
	DXUnreference (out[i]);

    ret = OK;
    /* fall thru */

  cleanup:
    DXDelete ((Object) g);

    /* fall thru */

  close:
    if (!persistent && fd >= 0)
    {
	close (fd);
	/* If we have to, wait for the child. */
#ifndef DXD_HAS_LIBIOP
	if (!_dxd_exDebugConnect)
	    wait(0);
#endif
    }

    return (ret);
}

Error
_dxf_ExDeleteRemote (char *name, int instance)
{
    node	*module;
    node	*id;
    int		flags;
    int		both;
    Object	in[2];
    char	cachetag[32];


    module = (node*)_dxf_ExMacroSearch(name);

    if (module == NULL)
    {
	DXSetError(ERROR_BAD_PARAMETER, "#8370", name);
	return ERROR;
    }

    flags = module->v.function.flags;
    both = MODULE_OUTBOARD | MODULE_PERSISTENT;
    if ((flags & both) != both)
    {
	return OK;
    }

    id = module->v.module.in;
    in[0] = ((Object)id->v.id.dflt->v.constant.obj);
    id = id->next;
    in[1] = ((Object)id->v.id.dflt->v.constant.obj);
    sprintf(cachetag, "RemoteConnect%d", instance);

    DXSetCacheEntryV(NULL, 0, cachetag, 0, 2, in);

    return (OK);
}

Error
_dxf_ExDeleteReallyRemote (char *procgroup, char *name, int instance)
{
    node	*module;
    node	*id;
    int		flags;
    int		both;
    int		fd;
    DelRemote   d;

    module = (node *)_dxf_ExMacroSearch(name);

    if (module == NULL)
    {
	DXSetError(ERROR_BAD_PARAMETER, "#8370", name);
	return ERROR;
    }

    flags = module->v.function.flags;
    both = MODULE_OUTBOARD | MODULE_PERSISTENT;
    if ((flags & both) != both)
    {
	return OK;
    }

    d.del_namelen = strlen(name) + 1;
    d.del_name = name;
    d.del_instance = instance;

    /* this has to happen on the current host for the process group */
    fd = _dxf_ExGetSocketId(procgroup);
    if (fd < 0) {
	DXSetError(ERROR_INVALID_DATA, 
		   "can't find connection to host for process group '%s'",
		   procgroup);
	return ERROR;
    }

    _dxf_ExDistMsgfd(DM_PERSISTDELETE, (Pointer)&d, fd);

    return (OK);
}

int
_dxf_ExInitRemote (void)
{
    return (OK);
}

static void ExIncrementGlobalVariable (char *name, int cause_execution)
{
    int val;

    if(!DXGetIntVariable(name, &val))
        val = 0;

    DXSetIntVariable(name, val+1, 1, cause_execution);
}

/* the point of making the module id routines is to hide
 * exactly what a module id is.  right now it's the
 * fully qualified pathname down to the module, plus an
 * instance number, all in string form; but at some time
 * we might want to convert module ids over to simple numbers.
 * hopefully having these cover routines will mean that
 * people's code won't break if we do this.
 */

/* allocate space for the cpath of the current module
 *  and return it.
 */
Pointer DXGetModuleId()
{
    Pointer id;
    int len;
    char *cpath;

    len = DXGetModulePathLen() + 1;
    
    id = DXAllocate(len);
    if (!id)
	return NULL;

    DXGetModulePath(id);
    return id;
}

/* copy a module id
 */
Pointer DXCopyModuleId(Pointer id)
{
    Pointer nid;
    int len;

    if (id == NULL || (char *)id == '\0')
	return NULL;

    nid = DXAllocate(strlen((char *)id) + 1);
    if (!nid)
	return NULL;

    strcpy(nid, id);
    return nid;
}

/* compare two module ids
 */
Error DXCompareModuleId(Pointer id1, Pointer id2)
{
    if (id1 == NULL || id2 == NULL)
	return ERROR;

    return ((strcmp(id1, id2) == 0) ? OK : ERROR);
}

/* get the space back from a module id string
 */
Error DXFreeModuleId(Pointer id)
{
    if (!id)
	return OK;

    DXFree(id);
    return OK;
}

Error DXReadyToRun(Pointer id)
{
    if (!id)
	return ERROR;

    ExIncrementGlobalVariable (id, 1);
    return OK;
}

Error DXReadyToRunNoExecute(Pointer id)
{
    if (!id)
	return ERROR;

    ExIncrementGlobalVariable (id, 0);
    return OK;
}

