/* 
 * procRemote.c --
 *
 *	Routines for a remote workstation to handle process migration.  This
 *	involves the following:
 *
 *		- Grant permission to another workstation to migrate to this
 *		  one.
 *		- Accept the process from the other workstation, creating a
 *		  local copy of it.
 *		- Execute the process, sending system calls back to the home
 *		  node for processing.
 *		- Transfer the process back to the home node upon termination.
 *
 * Copyright 1986, 1988 Regents of the University of California
 * Permission to use, copy, modify, and distribute this
 * software and its documentation for any purpose and without
 * fee is hereby granted, provided that the above copyright
 * notice appear in all copies.  The University of California
 * makes no representations about the suitability of this
 * software for any purpose.  It is provided "as is" without
 * express or implied warranty.
 */

#ifndef lint
static char rcsid[] = "$Header: /cdrom/src/kernel/Cvsroot/kernel/proc/procRemote.c,v 9.17 92/06/15 22:29:36 jhh Exp $ SPRITE (Berkeley)";
#endif /* not lint */


#include <sprite.h>
#include <mach.h>
#include <proc.h>
#include <sync.h>
#include <sched.h>
#include <procMigrate.h>
#include <procInt.h>
#include <migrate.h>
#include <fs.h>
#include <stdlib.h>
#include <string.h>
#include <sig.h>
#include <spriteTime.h>
#include <list.h>
#include <byte.h>
#include <vm.h>
#include <sys.h>
#include <rpc.h>
#include <sysSysCall.h>
#include <sysSysCallParam.h>
#include <dbg.h>
#include <stdio.h>
#include <procUnixStubs.h>
#include <bstring.h>
#include <recov.h>

extern int debugProcStubs;


/*
 * An address for copy-out or make accessible is reasonable if it is not NIL.
 */

#define ValidAddress(addr) (((Address) addr != (Address) NIL) && \
			    ((Address) addr != (Address) USER_NIL))

    

/*
 *----------------------------------------------------------------------
 *
 * ProcMigAcceptMigration --
 *
 *	Handle a request to start process migration.
 *	This could check things like the number of remote processes,
 *	load, or whatever. For now, just check against a global flag
 *	that says whether to refuse migrations, and compare architecture
 *	types and version numbers.  Allocate a process control block,
 *	or match up with an existing shadow copy.
 *
 * Results:
 *	SUCCESS is returned if permission is granted, and the local process
 *		id of the process is returned.
 *	PROC_MIGRATION_REFUSED is returned if the node is not accepting
 *		migrated processes, or there is a version mismatch.
 *	GEN_INVALID_ID is returned if some migrations are allowed but
 *		the user is not permitted to migrate (e.g., only root is
 * 		allowed).
 *
 * Side effects:
 *	A process may be allocated.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
ReturnStatus
ProcMigAcceptMigration(cmdPtr, procPtr, inBufPtr, outBufPtr)
    ProcMigCmd *cmdPtr;/* contains ID of process on this host,
				   or NIL */
    Proc_ControlBlock *procPtr; /* ptr to process control block, or NIL */
    Proc_MigBuffer *inBufPtr;	/* input buffer */
    Proc_MigBuffer *outBufPtr;	/* output buffer (default is empty) */
{
    ProcMigInitiateCmd *initPtr;
    char machType[32];
    int permMask;
    Proc_PID *pidPtr;
    int clientID;		/* Sprite ID of client host */

    if (proc_MigDebugLevel > 5) {
	printf("ProcMigAcceptMigration called.\n");
    }
    if (inBufPtr->size != sizeof(ProcMigInitiateCmd)) {
	/*
	 * Implicit version mismatch if they're not the same size.
	 */
	if (proc_MigDebugLevel > 0) {
	    printf("Migration version mismatch: size of initiation request");
	    printf(" is %d, not %d.\n", inBufPtr->size,
	           sizeof(ProcMigInitiateCmd));
        }
	return(PROC_MIGRATION_REFUSED);
    }

    initPtr = (ProcMigInitiateCmd *) inBufPtr->ptr;
    clientID = initPtr->clientID;
    if (initPtr->version != proc_MigrationVersion) {
	if (proc_MigDebugLevel > 1) {
	    printf("Migration version mismatch: we are level %d; client %d is level %d.\n",
		   proc_MigrationVersion, clientID, initPtr->version);
	}
	return(PROC_MIGRATION_REFUSED);
    }
    if (cmdPtr->remotePid == (Proc_PID) NIL) {
	/*
	 * Do various checks regarding whether we're accepting any migrations,
	 * then allocate a process.
	 */
	if ((proc_AllowMigrationState & PROC_MIG_IMPORT_ALL) ==
	    PROC_MIG_IMPORT_NEVER) {
	    if (proc_MigDebugLevel > 4) {
		printf("Warning: Proc_RpcMigInit: migration rejected because we are refusing migrations.\n");
	    }
	    return(PROC_MIGRATION_REFUSED);
	}
	if (initPtr->userID == PROC_SUPER_USER_ID) {
	    permMask = PROC_MIG_IMPORT_ROOT;
	} else {
	    permMask = PROC_MIG_IMPORT_ALL;
	}
 
	if ((proc_AllowMigrationState & permMask) != permMask) {
	    if (proc_MigDebugLevel > 2) {
		printf("Proc_Migrate: user does not have permission to migrate.\n");
	    }
	    return(GEN_NO_PERMISSION);
	}
    
	Net_SpriteIDToMachType(clientID, 32, machType);
	if (*machType == '\0') {
	    printf("Warning: Proc_RpcMigInit: couldn't get machine type for client %d.\n",
		   clientID);
	    return(PROC_MIGRATION_REFUSED);
	}
	if (strcmp(machType, mach_MachineType)) {
	    if (proc_MigDebugLevel > 0) {
		printf("Warning: Proc_RpcMigInit: client %d (%s) tried to migrate to this machine.\n",
		       clientID, machType);
	    }
	    return(PROC_MIGRATION_REFUSED);
	}
	/*
	 * Allocate a new process table entry for the migrating process.
	 */
	procPtr = ProcGetUnusedPCB();
	procPtr->peerProcessID = initPtr->processID;
	procPtr->peerHostID = clientID;
	procPtr->state = PROC_NEW;
	procPtr->genFlags |= PROC_FOREIGN;

	pidPtr = (Proc_PID *) malloc(sizeof(Proc_PID));
	*pidPtr = procPtr->processID;
	Proc_Unlock(procPtr);
	outBufPtr->ptr = (Address) pidPtr;
	outBufPtr->size = sizeof(Proc_PID);

	/*
	 * Remember the dependency on the other host.
	 */
	ProcMigAddDependency(procPtr->processID, procPtr->peerProcessID);
    } else {
	if (procPtr == (Proc_ControlBlock *) NIL) {
	    panic("ProcMigAcceptMigration: given null control block for existing process\n");
	    return(PROC_NO_PEER);
	}
    }
    if (proc_MigDebugLevel > 5) {
	printf("ProcMigAcceptMigration returning SUCCESS.\n");
    }
    return(SUCCESS);
}


/*
 *----------------------------------------------------------------------
 *
 * ProcMigDestroyCmd --
 *
 *	Handle a request to destroy a migrated process, possibly
 *	one that has not completed migration quite yet.
 *
 * Results:
 *	SUCCESS.
 *
 * Side effects:
 *	A process may be allocated.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
ReturnStatus
ProcMigDestroyCmd(cmdPtr, procPtr, inBufPtr, outBufPtr)
    ProcMigCmd *cmdPtr;/* contains ID of process on this host */
    Proc_ControlBlock *procPtr; /* ptr to process control block */
    Proc_MigBuffer *inBufPtr;	/* input buffer */
    Proc_MigBuffer *outBufPtr;	/* output buffer (stays empty) */
{
    Proc_DestroyMigratedProc((ClientData) procPtr->processID, 
				(Proc_CallInfo *) NIL);
    return(SUCCESS);
}


/*
 *----------------------------------------------------------------------
 *
 * ProcMigContinueProcess --
 *
 *	Restore the state of a migrated process to its state prior to
 *	migration.  If it is migrating home, remove the link between
 *	the foreign copy of the process and this copy.  Add it to the
 *	ready queue.
 *
 *
 * Results:
 *      A ReturnStatus.  (SUCCESS for now.)
 *
 * Side effects:
 *	The process may be made runnable.
 *
 *----------------------------------------------------------------------
 */

/*ARGSUSED*/
ReturnStatus
ProcMigContinueProcess(cmdPtr, procPtr, inBufPtr, outBufPtr)
    ProcMigCmd *cmdPtr;/* contains ID of process on this host */
    Proc_ControlBlock *procPtr; /* ptr to process control block */
    Proc_MigBuffer *inBufPtr;	/* input buffer */
    Proc_MigBuffer *outBufPtr;	/* output buffer (stays empty) */
{
    if (proc_MigDebugLevel > 10) {
	printf(">> Entering debugger before continuing process %x.\n", procPtr->processID);
	DBG_CALL;
    }

    Proc_Lock(procPtr);
    if (!(procPtr->genFlags & PROC_FOREIGN)) {
	procPtr->peerProcessID = (Proc_PID) NIL;
	procPtr->peerHostID = NIL;
    }
    procPtr->genFlags |= PROC_MIGRATION_DONE;
    Proc_Unlock(procPtr);
    Sched_MakeReady(procPtr);

    return(SUCCESS);
}


/*
 *----------------------------------------------------------------------
 *
 * Proc_ResumeMigProc --
 *
 *	Resume a migrated user process.  This is the first thing that is
 * 	called when a migrated process continues execution.
 *	If the process is actually performing a remote exec, then
 * 	call the routine to perform the exec, which won't return.
 *
 * Results:
 *	Does not return.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

void
Proc_ResumeMigProc(pc)
    int pc;		/* program counter to resume execution */
{
    register     	Proc_ControlBlock *procPtr;


    MASTER_UNLOCK(sched_MutexPtr);

    procPtr = Proc_GetCurrentProc();
    Proc_Lock(procPtr);
    if (procPtr->migFlags & PROC_EVICTING) {
	/*
	 * Just to make sure we migrate back home ASAP...
	 */
	Sig_SendProc(procPtr, SIG_MIGRATE_HOME, 0, (Address)0);
    }
    if (procPtr->genFlags & PROC_REMOTE_EXEC_PENDING) {
	ProcDoRemoteExec(procPtr);
	/*
	 * NOTREACHED
	 */
    }
    procPtr->genFlags &= ~PROC_NO_VM;
    VmMach_ReinitContext(procPtr);
    Proc_Unlock(procPtr);

    if (procPtr->unixProgress != PROC_PROGRESS_NOT_UNIX &&
	    procPtr->unixProgress != PROC_PROGRESS_UNIX) {
	if (debugProcStubs) {
	    printf("Unix progress = %d (mig)\n", procPtr->unixProgress);
	}
    }
    if (procPtr->unixProgress == PROC_PROGRESS_MIG_RESTART) {
	if (debugProcStubs) {
	    printf("Restarting migrated system call\n");
	}
	procPtr->unixProgress = PROC_PROGRESS_RESTART;
    }

    /*
     * Start the process running.  This does not return.  
     */

    Mach_StartUserProc(procPtr, (Address) pc);
    panic("Proc_ResumeMigProc: Mach_StartUserProc returned.\n");
}


/*
 *----------------------------------------------------------------------
 *
 * Proc_DoRemoteCall --
 *
 *	Generic system call handler for migrated processes.  This routine
 *	takes a specification for the system call to invoke and the usage
 *	of its parameters and sends them to the home node for processing.
 *      Each parameter has a type and a disposition.  The type indicates
 *	whether the parameter is an int, string, or other specialized
 * 	structure.  The disposition is "in", "out", or "in/out", as well
 *	as whether the parameter should be accessed using Vm_Copy{In,Out}
 *	or Vm_MakeAccessible.  The specific defined constants are documented
 *	in sysSysCallParam.h.
 *
 *	All "in" or "in/out" parameters are sent in the RPC in the data
 *	buffer.  All "out" or "in/out" parameters have space allocated for
 *	them in the reply data buffer.  Strings and characters are padded
 *	so that each field is aligned on an even address, so the actual
 *	addresses within the buffers may be cast as integers and other types
 *	without additional copying.
 *
 * Results:
 *	A ReturnStatus is returned.  Values may be returned in the areas
 *	referenced by pointers that are passed into the routine, but they
 *	are dependent upon the system call invoked.
 *
 * Side effects:
 *	A remote procedure call is performed.  
 *
 *----------------------------------------------------------------------
 */

ReturnStatus
Proc_DoRemoteCall(callNumber, numWords, argsPtr, specsPtr)
    int callNumber;			/* number of system call to invoke */
    int numWords;			/* number of words passed in */
    ClientData *argsPtr;		/* pointer to data passed in */
    Sys_CallParam *specsPtr;		/* array of information about the
					 * parameters to this call */
{
    Proc_ControlBlock 	*procPtr;	/* The migrated process */
    int dataSize = 0;			/* size of the data buffer */
    int replyDataSize = 0;		/* size of the reply buffer */
    Address ptr;			/* place holder within buffer */
    int *intArgsArray;			/* to treat argsPtr as array of ints */
    int disp;				/* "disposition" of a parameter */
    int type;				/* type of param (int, string, ...) */
    int size;				/* number of bytes of a param */
    int paddedSize;			/* likewise, padded to be aligned */
    Proc_RemoteCall call;		/* parameter information for RPC */
    Address pointerArray[SYS_MAX_ARGS]; /* array of made accessible pointers */
    int numBytesAccessible[SYS_MAX_ARGS];/* number of bytes made accessible,
					  * or zero if this variable has not
					  * been made accessible */
    Rpc_Storage storage;		
    Address dataBuffer = (Address) NIL;
    Address replyDataBuffer = (Address) NIL;
    int i;
    register ReturnStatus status;	/* returned by assorted procedures */
    ReturnStatus remoteCallStatus;	/* status returned by system call */
    Proc_TraceRecord record;		/* used to store trace data */
    int lastArraySize = -1;			/* size of last array found */
    int numTries;			/* number of times trying RPC */

    /*
     * Create a synonym for argsPtr so that integer arguments can be referred
     * to without casting them all the time.
     */
    
    intArgsArray = (int *) argsPtr;
    
    
    procPtr = Proc_GetActualProc();

    /*
     * Check to make sure the home node is up, and kill the process if
     * it isn't.  The call to exit never returns.
     */
    status = Recov_IsHostDown(procPtr->peerHostID);
    if (status != SUCCESS) {
	if (proc_MigDebugLevel > 0) {
	    printf("Proc_DoRemoteCall: host %d is down; killing process %x.\n",
		       procPtr->peerHostID, procPtr->processID);
	}
	Proc_ExitInt(PROC_TERM_DESTROYED, (int) PROC_NO_PEER, 0);
	/*
	 * This point should not be reached, but the N-O-T-R-E-A-C-H-E-D
	 * directive causes a complaint when there's code after it.
	 */
	panic("Proc_DoRemoteCall: Proc_ExitInt returned.\n");
	return(PROC_NO_PEER);
    }

    /*
     * Set up the RPC parameters: pass the home processID, the system call
     * number, and parameter information.  Additional parameter information
     * (type and disposition) is copied below.
     */

    call.processID = procPtr->peerProcessID;
    call.callNumber = callNumber;
    call.numArgs = numWords;
    call.parseArgs = TRUE;

#ifndef CLEAN
    if (proc_DoTrace && proc_DoCallTrace) {
	record.processID = call.processID;
	record.flags = PROC_MIGTRACE_START;
	record.info.call.callNumber = callNumber;
	Trace_Insert(proc_TraceHdrPtr, PROC_MIGTRACE_CALL,
		     (ClientData) &record);
    }
#endif /* CLEAN */   

    for (i = 0; i < numWords; i++) {
	numBytesAccessible[i] = 0;
    }

    /*
     * Determine the type and size of each argument.  If the argument is
     * a string, make it accessible now in order to find its size.
     */

    for (i = 0; i < numWords; i++) {
	disp = specsPtr[i].disposition;
	type = specsPtr[i].type;
	if ((disp & SYS_PARAM_OUT) &&
	    !(disp & (SYS_PARAM_ACC | SYS_PARAM_COPY))) {
	    panic("Proc_DoRemoteCall: Illegal parameter information for call %d for output parameter %d",
		      callNumber, i);
	    return(GEN_INVALID_ARG);
	}
	switch(type) {
	    case SYS_PARAM_INT:
	    case SYS_PARAM_CHAR: 
	    case SYS_PARAM_PROC_PID:
	    case SYS_PARAM_PROC_RES:
	    case SYS_PARAM_SYNC_LOCK:
	    case SYS_PARAM_FS_ATT:
	    case SYS_PARAM_TIMEPTR: 
	    case SYS_PARAM_TIME1:
	    case SYS_PARAM_TIME2:
	    case SYS_PARAM_RANGE1:
	    case SYS_PARAM_RANGE2:
	    case SYS_PARAM_PCB:
	    case SYS_PARAM_PCBARG:
	    case SYS_PARAM_VM_CMD:
	    case SYS_PARAM_DUMMY:
	        /*
		 * For typical arguments, the size may be found in a global
		 * array that is subscripted by the type.  If the argument
		 * is really an array, multiply by the number of arguments.
		 *
		 * Special case Proc_GetPCBInfo because it can allow
		 * a range of values or a PROC_MY_PID to specify one
		 * value.
		 */
	        if ((! (disp & SYS_PARAM_ARRAY)) ||
		        (callNumber == SYS_PROC_GETPCBINFO &&
		        intArgsArray[0] == PROC_MY_PID)) {
		    size = sys_ParamSizes[type];
		} else {
		    /*
		     * The argument is actually a pointer to an array of
		     * the given type.  The size of the array is either
		     * the parameter just before the array pointer (an INT),
		     * or the difference between the two preceding arguments
		     * (RANGE1 and RANGE2).  Remember the size of the array,
		     * since a more arrays of the same size may follow.
		     *
		     */
		    if (i > 0 && specsPtr[i-1].type == SYS_PARAM_INT) {
			size = (intArgsArray[i-1]) *
				sys_ParamSizes[type];
		    } else if (i > 1 && specsPtr[i-2].type ==
			       SYS_PARAM_RANGE1 &&
			       specsPtr[i-1].type == SYS_PARAM_RANGE2) {
			 if (type == SYS_PARAM_PCB) {
			    intArgsArray[i-1] = intArgsArray[i-1] &
				    PROC_INDEX_MASK;
			    intArgsArray[i-2] = intArgsArray[i-2]  &
				    PROC_INDEX_MASK;
			}
			size = (intArgsArray[i-1] - intArgsArray[i-2] + 1) *
				sys_ParamSizes[type];
		    } else if (i > 1 && (specsPtr[i-1].disposition
					 & (SYS_PARAM_ARRAY))) {
			size = lastArraySize;
		    } else {
			panic("Proc_DoRemoteCall: bad parameter list.\n");
			status = FAILURE;
			goto failure;
		    }
		    lastArraySize = size;
		}
		break;
	    case SYS_PARAM_FS_NAME: 
	    case SYS_PARAM_HOSTNAME:
                /*
		 * The argument is a string.  If copying a string in, make
		 * it accessible and figure out its size dynamically.  If
		 * copying it out, just allocate space for the maximum number 
		 * of bytes for that type.
		 */
		if ((disp & SYS_PARAM_ACC) && (disp & SYS_PARAM_IN)) {
		    status = Proc_MakeStringAccessible(sys_ParamSizes[type],
 		        (char **) &argsPtr[i], &numBytesAccessible[i], &size);
		    if (status != SUCCESS) {
			if (proc_MigDebugLevel > 6) {
			    panic("Proc_DoRemoteCall: status %x returned by Proc_MakeStringAccessible.\n", status);
			}
			goto failure;
		    }
		    /*
 		     * Send the null byte as well.
		     */
		    size += 1;
		} else if ((disp & SYS_PARAM_COPY) && (disp & SYS_PARAM_OUT)) {
		    size = sys_ParamSizes[type];
		} else {
		    panic("Proc_DoRemoteCall: can't handle string parameter combination.\n");
		    status = FAILURE;
		    goto failure;
		}
		pointerArray[i] = (Address) argsPtr[i];
		break;				    
	    default:
		panic("Proc_DoRemoteCall: can't handle argument type.\n");
		status = FAILURE;
		goto failure;
	}

	/*
	 * Store the information about this parameter.  If the argument is
	 * a pointer and it is USER_NIL, then store the fact that the
	 * parameter is NIL so that we won't try to copy out to that address
	 * later on, or to make it accessible.  Keep track of the sizes of the
	 * input and output buffers.  Make accessible anything that isn't
	 * already accessible.
	 */
	call.info[i].size = size;
	if ((disp & (SYS_PARAM_COPY | SYS_PARAM_ACC))
	        && !ValidAddress(argsPtr[i])) {
	    disp |= SYS_PARAM_NIL;
	} else {
	    paddedSize = Byte_AlignAddr(size);
	    if (disp & SYS_PARAM_IN) {
		dataSize += paddedSize;
	    }
	    if (disp & SYS_PARAM_OUT) {
		replyDataSize += paddedSize;
	    }
	}
	call.info[i].disposition = disp;
	if ((disp & SYS_PARAM_ACC) && numBytesAccessible[i] == 0) {
	    if (! (disp & SYS_PARAM_NIL)) {
		int accessType; 

		if ((disp & SYS_PARAM_IN) && !(disp & SYS_PARAM_OUT)) {
		    accessType = VM_READONLY_ACCESS;
		} else if (!(disp & SYS_PARAM_IN) && (disp & SYS_PARAM_OUT)) {
		    accessType = VM_OVERWRITE_ACCESS;
		} else {
		    accessType = VM_READWRITE_ACCESS;
		}
		Vm_MakeAccessible(accessType, size, (Address) argsPtr[i],
				  &numBytesAccessible[i], &pointerArray[i]);
		if (numBytesAccessible[i] != size ||
		        pointerArray[i] == (Address) NIL) {
		    status = SYS_ARG_NOACCESS;
		    goto failure;
		}
	    } else {
		pointerArray[i] = (Address) NIL;
	    }
	}
    }

    /*
     * Now that the total sizes are known, allocate space and save the
     * arguments in the data buffer.  While the RPC system and network
     * driver are confused and screw up on buffers that are too small,
     * pad the sizes accordingly.
     */

#define RPC_MIN_BUFFER_SIZE 12
#ifdef RPC_MIN_BUFFER_SIZE
    if (dataSize < RPC_MIN_BUFFER_SIZE) {
	dataSize = RPC_MIN_BUFFER_SIZE;
    }
    if (replyDataSize < RPC_MIN_BUFFER_SIZE) {
	replyDataSize = RPC_MIN_BUFFER_SIZE;
    }
#endif /* RPC_MIN_BUFFER_SIZE */
	
    dataBuffer = (Address) malloc(dataSize);
    ptr = dataBuffer;
    replyDataBuffer = (Address) malloc(replyDataSize);
    call.replySize = replyDataSize;

    for (i = 0; i < numWords; i++) {
	disp = call.info[i].disposition;
	if ((disp & SYS_PARAM_IN) && ! (disp & SYS_PARAM_NIL)) {
	    if (disp & SYS_PARAM_ACC) {
		bcopy(pointerArray[i], ptr, call.info[i].size);
	    } else if (disp & SYS_PARAM_COPY) {
		status = Vm_CopyIn(call.info[i].size, (Address) argsPtr[i],
				   ptr);
		if (status != SUCCESS) {
		    goto failure;
		}
	    } else {
		bcopy((Address) &argsPtr[i], ptr, call.info[i].size);
	    }
	ptr += Byte_AlignAddr(call.info[i].size);
	}
    }

    /*
     * Set up for the RPC.
     */
    storage.requestParamPtr = (Address) &call;
    storage.requestParamSize = sizeof(Proc_RemoteCall);

    storage.requestDataPtr = dataBuffer;
    storage.requestDataSize = dataSize;

    storage.replyParamPtr = (Address) NIL;
    storage.replyParamSize = 0;
    storage.replyDataPtr = replyDataBuffer;
    storage.replyDataSize = replyDataSize;

    if (proc_MigDebugLevel > 4) {
	printf("Proc_DoRemoteCall: sending call %d home.\n", callNumber); 
    }

    for (numTries = 0; numTries < PROC_MAX_RPC_RETRIES; numTries++) {
	remoteCallStatus = Rpc_Call(procPtr->peerHostID, RPC_PROC_REMOTE_CALL,
				    &storage);
	if (remoteCallStatus != RPC_TIMEOUT) {
	    break;
	}
	status = Proc_WaitForHost(procPtr->peerHostID);
	if (status != SUCCESS) {
	    break;
	}
    }
    if (proc_MigDebugLevel > 4) {
	printf("Proc_DoRemoteCall: status %x returned by Rpc_Call.\n",
		   remoteCallStatus);
    }
#ifndef CLEAN
    if (proc_DoTrace && proc_DoCallTrace) {
	record.flags &= ~PROC_MIGTRACE_START;
	record.info.call.status = remoteCallStatus;
	Trace_Insert(proc_TraceHdrPtr, PROC_MIGTRACE_CALL,
		     (ClientData) &record);
    }
#endif /* CLEAN */   

    /*
     * If we were told the process no longer exists on the home node,
     * then blow it away.
     */
    if (remoteCallStatus == PROC_NO_PEER) {
	if (proc_MigDebugLevel > 1) {
	    printf("Proc_DoRemoteCall: peer process %x is gone, killing %x.\n",
		   procPtr->peerProcessID, procPtr->processID);
	}
	status = PROC_NO_PEER;
	(void) Sig_Send(SIG_KILL, (int) PROC_NO_PEER, procPtr->processID,
			FALSE, (Address)0); 
	goto failure;
    }
    /*
     * Step through the reply data buffer and copy out any arguments that
     * were output parameters.
     */

    ptr = replyDataBuffer;
    for (i = 0; i < numWords; i++) {
	disp = call.info[i].disposition;
	if (disp & SYS_PARAM_ACC) {
	    if ((disp & SYS_PARAM_OUT) && !(disp & SYS_PARAM_NIL)) {
		bcopy(ptr, pointerArray[i], call.info[i].size);
		ptr += Byte_AlignAddr(call.info[i].size);
	    }
	    if (numBytesAccessible[i] > 0) {
		Vm_MakeUnaccessible(pointerArray[i], numBytesAccessible[i]);
		numBytesAccessible[i] = 0;
	    }
	} else if ((disp & SYS_PARAM_COPY) && (disp & SYS_PARAM_OUT)
		   && !(disp & SYS_PARAM_NIL)) {
	    size = call.info[i].size;
	    status = Vm_CopyOut(size, ptr, (Address) argsPtr[i]);
	    if (status != SUCCESS) {
		if (proc_MigDebugLevel > 6) {
		    panic("Proc_DoRemoteCall: status %x returned by Vm_CopyOut.\n",
			       status);
		}

		status = SYS_ARG_NOACCESS;
		goto failure;
	    }
	    ptr += Byte_AlignAddr(call.info[i].size);
	}
    }

    free(dataBuffer);
    free(replyDataBuffer);
    
    return(remoteCallStatus);


    /*
     * Try to unwind after an error by making everything unaccessible
     * again and freeing memory before returning the error condition.
     * Note: if the procedure has progressed far enough for dataBuffer 
     * to be non_NIL, then replyDataBuffer should be non-NIL then as well.
     */

failure:

    for (i = 0; i < numWords; i++) {
	if (numBytesAccessible[i] > 0) {
	    Vm_MakeUnaccessible(pointerArray[i], numBytesAccessible[i]);
	}
    }
    if (dataBuffer != (Address) NIL) {
	free(dataBuffer);
	free(replyDataBuffer);
    }
    return(status);
}


/*
 *----------------------------------------------------------------------
 *
 * Proc_RemoteDummy --
 *
 *	Dummy routine to return FAILURE for any system call not yet
 *	implemented.
 *
 * Results:
 *	FAILURE.
 *
 * Side effects:
 *	None.  
 *
 *----------------------------------------------------------------------
 */

/* ARGSUSED */
ReturnStatus
Proc_RemoteDummy(callNumber, numWords, argsPtr, specsPtr)
    int callNumber;
    int numWords;
    Sys_ArgArray *argsPtr;
    Sys_CallParam *specsPtr;
{
    printf("Warning: Call %d not yet implemented.\n", callNumber);
    return(FAILURE);
}


/*
 * ----------------------------------------------------------------------------
 *
 * ProcRemoteFork --
 *
 *	Perform an RPC to execute a fork on behalf of a migrated process.
 *
 * Results:
 *	The status returned by the RPC is returned.
 *
 * Side effects:
 *	An RPC is performed.  A skeletal process is created on the home node
 *	to be the "real" process corresponding to the migrated forked process
 *	that is in the process of being created.  The ID of the child on
 *	the home node is stored in the PCB of the remote process.
 *
 * ----------------------------------------------------------------------------
 */

ReturnStatus
ProcRemoteFork(parentProcPtr, childProcPtr)
    Proc_ControlBlock *parentProcPtr;	/* PCB for parent */
    Proc_ControlBlock *childProcPtr;		/* PCB for child */
{
    Rpc_Storage storage;
    Proc_RemoteCall call;
    ReturnStatus status;
    Proc_TraceRecord record;		/* used to store trace data */
    int numTries;			/* number of times trying RPC */

    if (proc_MigDebugLevel > 3) {
	printf("ProcRemoteFork called.\n");
    }

    status = Recov_IsHostDown(parentProcPtr->peerHostID);
    if (status != SUCCESS) {
	if (proc_MigDebugLevel > 0) {
	    printf("ProcRemoteFork: host %d is down; killing process %x.\n",
		       parentProcPtr->peerHostID, parentProcPtr->processID);
	}
	Proc_ExitInt(PROC_TERM_DESTROYED, (int) PROC_NO_PEER, 0);
	/*
	 * This point should not be reached, but the N-O-T-R-E-A-C-H-E-D
	 * directive causes a complaint when there's code after it.
	 */
	panic("ProcRemoteFork: Proc_ExitInt returned.\n");
	return(PROC_NO_PEER);
    }

#ifndef CLEAN
    if (proc_DoTrace && proc_DoCallTrace) {
	record.processID = parentProcPtr->peerProcessID;
	record.flags = PROC_MIGTRACE_START;
	record.info.call.callNumber = SYS_PROC_FORK;
	Trace_Insert(proc_TraceHdrPtr, PROC_MIGTRACE_CALL,
		     (ClientData) &record);
    }
#endif /* CLEAN */   

    /*
     * Set up for the RPC.
     */
    call.processID = parentProcPtr->peerProcessID;
    call.callNumber = SYS_PROC_FORK;
    call.parseArgs = FALSE;
    storage.requestParamPtr = (Address) &call;
    storage.requestParamSize = sizeof(Proc_RemoteCall);

    storage.requestDataPtr = (Address) &childProcPtr->processID;
    storage.requestDataSize = sizeof(int);

    storage.replyParamPtr = (Address) NIL;
    storage.replyParamSize = 0;
    storage.replyDataPtr = (Address) &childProcPtr->peerProcessID;
    storage.replyDataSize = sizeof(Proc_PID);

    for (numTries = 0; numTries < PROC_MAX_RPC_RETRIES; numTries++) {
	status = Rpc_Call(parentProcPtr->peerHostID, RPC_PROC_REMOTE_CALL,
			   &storage);
	if (status != RPC_TIMEOUT) {
	    break;
	}
	status = Proc_WaitForHost(parentProcPtr->peerHostID);
	if (status != SUCCESS) {
	    break;
	}
    }

#ifndef CLEAN
    if (proc_DoTrace && proc_DoCallTrace) {
	record.flags &= ~PROC_MIGTRACE_START;
	record.info.call.status = status;
	Trace_Insert(proc_TraceHdrPtr, PROC_MIGTRACE_CALL,
		     (ClientData) &record);
    }
#endif /* CLEAN */   

    /*
     * If we were told the process no longer exists on the home node,
     * then blow it away.  Any other return status just means we didn't
     * initialize the child properly.
     */
    if (status != SUCCESS) {
	if (status == PROC_NO_PEER) {
	    (void) Sig_Send(SIG_KILL, (int) PROC_NO_PEER,
			    parentProcPtr->processID, FALSE, (Address)0); 
	}
	if (proc_MigDebugLevel > 0) {
	    printf("Warning: ProcRemoteFork returning status %x.\n",
		       status);
	}
	return(status);
    }
    childProcPtr->peerHostID = parentProcPtr->peerHostID;
    childProcPtr->genFlags |= PROC_FOREIGN;
    childProcPtr->kcallTable = mach_MigratedHandlers;

    /*
     * Note the dependency of the new process on the other host.
     */
    ProcMigAddDependency(childProcPtr->processID, childProcPtr->peerProcessID);

    /*
     * Update statistics.
     */
    PROC_MIG_INC_STAT(foreign);

    if (proc_MigDebugLevel > 3) {
	printf("ProcRemoteFork returning status %x.\n", status);
    }
    return(status);
}


/*
 *----------------------------------------------------------------------
 *
 * ProcRemoteExit --
 *
 *	Cause a migrated process to exit.  Throw away its address space, but
 * 	migrate runtime information back to the home node.  
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	A remote procedure call is performed and the part of the
 * 	process state that survives past Proc_Exit is transferred.
 *
 *----------------------------------------------------------------------
 */

void
ProcRemoteExit(procPtr, reason, exitStatus, code)
    register Proc_ControlBlock 	*procPtr;  /* process that is exiting */
    int reason;	/* Why the process is dying: EXITED, SIGNALED, DESTROYED  */
    int	exitStatus;	/* Exit status or signal # or destroy status */
    int code;	/* Signal sub-status */
{
    Address buffer;
    Address ptr;
    int bufferSize;
    Rpc_Storage storage;
    Proc_RemoteCall call;
    ReturnStatus status;
    Proc_TraceRecord record;		/* used to store trace data */
    int numTries;			/* number of times trying RPC */

    if (proc_MigDebugLevel > 4) {
	printf("ProcRemoteExit(%x) called.\n", exitStatus);
    }

    /*
     * Update statistics.
     */
    if (!(procPtr->genFlags & PROC_DONT_MIGRATE)) {
	PROC_MIG_DEC_STAT(foreign);
    } else {
	if (proc_MigDebugLevel > 3) {
	    printf("ProcRemoteExit: process %x is foreign but unmigratable.\n",
		   procPtr->processID);
	}
    }
    if ((procPtr->migFlags & PROC_EVICTING) ||
	(proc_MigStats.foreign == 0 &&
	 proc_MigStats.evictionsInProgress != 0)) {
	ProcMigEvictionComplete();
    }


    status = Recov_IsHostDown(procPtr->peerHostID);
    if (status != SUCCESS) {
	if (proc_MigDebugLevel > 0) {
	    printf("ProcRemoteExit: host %d is down; ignoring exit for process %x.\n",
		       procPtr->peerHostID, procPtr->processID);
	}
	/*
	 * Remove the dependency on the other host, but note that the host
	 * is down now.
	 */
	ProcMigRemoveDependency(procPtr->processID, FALSE);
	return;
    }

    ProcMigRemoveDependency(procPtr->processID, TRUE);
#ifndef CLEAN
    if (proc_DoTrace && proc_DoCallTrace) {
	record.processID = procPtr->processID;
	record.flags = PROC_MIGTRACE_START;
	record.info.call.callNumber = SYS_PROC_EXIT;
	Trace_Insert(proc_TraceHdrPtr, PROC_MIGTRACE_CALL,
		     (ClientData) &record);
    }
#endif /* CLEAN */   


    bufferSize = 2 * sizeof(Timer_Ticks) + 5 * sizeof(int);
    buffer = (Address) malloc(bufferSize);

    ptr = buffer;
    Byte_FillBuffer(ptr, Timer_Ticks,  procPtr->kernelCpuUsage.ticks);
    Byte_FillBuffer(ptr, Timer_Ticks,  procPtr->userCpuUsage.ticks);
    Byte_FillBuffer(ptr, int,  procPtr->numQuantumEnds);
    Byte_FillBuffer(ptr, int,  procPtr->numWaitEvents);
    Byte_FillBuffer(ptr, int, reason);
    Byte_FillBuffer(ptr, int, exitStatus);
    Byte_FillBuffer(ptr, int, code);


    /*
     * Set up for the RPC.
     */
    call.processID = procPtr->peerProcessID;
    call.callNumber = SYS_PROC_EXIT;
    call.parseArgs = FALSE;
    storage.requestParamPtr = (Address) &call;
    storage.requestParamSize = sizeof(Proc_RemoteCall);

    storage.requestDataPtr = buffer;
    storage.requestDataSize = bufferSize;

    storage.replyParamPtr = (Address) NIL;
    storage.replyParamSize = 0;
    storage.replyDataPtr = (Address) NIL;
    storage.replyDataSize = 0;


    for (numTries = 0; numTries < PROC_MAX_RPC_RETRIES; numTries++) {
	status = Rpc_Call(procPtr->peerHostID, RPC_PROC_REMOTE_CALL, &storage);
	if (status != RPC_TIMEOUT) {
	    break;
	}
	status = Proc_WaitForHost(procPtr->peerHostID);
	if (status != SUCCESS) {
	    break;
	}
    }

#ifndef CLEAN
    if (proc_DoTrace && proc_DoCallTrace) {
	record.flags &= ~PROC_MIGTRACE_START;
	record.info.call.status = status;
	Trace_Insert(proc_TraceHdrPtr, PROC_MIGTRACE_CALL,
		     (ClientData) &record);
    }
#endif /* CLEAN */   

    free(buffer);

    if ((status != SUCCESS) && (proc_MigDebugLevel > 0)) {
	printf("Warning: ProcRemoteExit received status %x.\n", status);
    }
}


/*
 *----------------------------------------------------------------------
 *
 * ProcRemoteExec --
 *
 *	Tell the home node of a process that it has done an exec, and to
 * 	change any information it might have about effective IDs.  
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	A remote procedure call is performed.
 *
 *----------------------------------------------------------------------
 */

void
ProcRemoteExec(procPtr, uid)
    register Proc_ControlBlock 	*procPtr;  /* process that is doing exec */
    int uid;				   /* new effective user ID, or -1 */
{
    Address buffer;
    Address ptr;
    int bufferSize;
    Rpc_Storage storage;
    Proc_RemoteCall call;
    ReturnStatus status;
    Proc_TraceRecord record;		/* used to store trace data */
    int numTries;			/* number of times trying RPC */

    if (proc_MigDebugLevel > 4) {
	printf("ProcRemoteExec(%s) called.\n", procPtr->argString);
    }

    status = Recov_IsHostDown(procPtr->peerHostID);
    if (status != SUCCESS) {
	if (proc_MigDebugLevel > 0) {
	    printf("ProcRemoteExec: host %d is down; killing process %x.\n",
		       procPtr->peerHostID, procPtr->processID);
	}
	Proc_Unlock(procPtr);
	Proc_ExitInt(PROC_TERM_DESTROYED, (int) PROC_NO_PEER, 0);
	/*
	 * This point should not be reached, but the N-O-T-R-E-A-C-H-E-D
	 * directive causes a complaint when there's code after it.
	 */
	panic("ProcRemoteExec: Proc_ExitInt returned.\n");
	return;
    }


#ifndef CLEAN
    if (proc_DoTrace && proc_DoCallTrace) {
	record.processID = procPtr->processID;
	record.flags = PROC_MIGTRACE_START;
	record.info.call.callNumber = SYS_PROC_EXEC;
	Trace_Insert(proc_TraceHdrPtr, PROC_MIGTRACE_CALL,
		     (ClientData) &record);
    }
#endif /* CLEAN */   



    bufferSize = sizeof(int) + strlen(procPtr->argString) + 1;
    buffer = (Address) malloc(bufferSize);

    ptr = buffer;
    Byte_FillBuffer(ptr, int,  uid);
    (void) strcpy(ptr,  procPtr->argString);


    /*
     * Set up for the RPC.
     */
    call.processID = procPtr->peerProcessID;
    call.callNumber = SYS_PROC_EXEC;
    call.parseArgs = FALSE;
    storage.requestParamPtr = (Address) &call;
    storage.requestParamSize = sizeof(Proc_RemoteCall);

    storage.requestDataPtr = buffer;
    storage.requestDataSize = bufferSize;

    storage.replyParamPtr = (Address) NIL;
    storage.replyParamSize = 0;
    storage.replyDataPtr = (Address) NIL;
    storage.replyDataSize = 0;


    /*
     * Unlock the process while we're doing the RPC.
     */
    Proc_Unlock(procPtr);
    
    for (numTries = 0; numTries < PROC_MAX_RPC_RETRIES; numTries++) {
	status = Rpc_Call(procPtr->peerHostID, RPC_PROC_REMOTE_CALL, &storage);
	if (status != RPC_TIMEOUT) {
	    break;
	}
	status = Proc_WaitForHost(procPtr->peerHostID);
	if (status != SUCCESS) {
	    break;
	}
    }

    /*
     * Give the process back the way it was handed to us (locked).
     */
    Proc_Lock(procPtr);

#ifndef CLEAN
    if (proc_DoTrace && proc_DoCallTrace) {
	record.flags &= ~PROC_MIGTRACE_START;
	record.info.call.status = status;
	Trace_Insert(proc_TraceHdrPtr, PROC_MIGTRACE_CALL,
		     (ClientData) &record);
    }
#endif /* CLEAN */   


    free(buffer);

    if ((status != SUCCESS) && (proc_MigDebugLevel > 0)) {
	printf("Warning: ProcRemoteExec received status %x.\n", status);
    }
}


