/* 
 * sigMigrate.c --
 *
 *	Routines to handle signals for migrated procedures.
 *
 * Copyright (C) 1986 Regents of the University of California
 * All rights reserved.
 */

#ifndef lint
static char rcsid[] = "$Header: /cdrom/src/kernel/Cvsroot/kernel/sig/sigMigrate.c,v 9.10 92/01/06 15:17:15 kupfer Exp $ SPRITE (Berkeley)";
#endif not lint


#include <sprite.h>
#include <stdlib.h>
#include <sig.h>
#include <sync.h>
#include <list.h>
#include <proc.h>
#include <procMigrate.h>
#include <status.h>
#include <sched.h>
#include <sigInt.h>
#include <rpc.h>
#include <bstring.h>
#include <stdio.h>

/* 
 * Information sent when sending a signal.  Needed when doing a callback.
 */

typedef struct {
    Proc_PID 			processID;
    int				sigNum;
    int				code;
} DeferInfo;

static void DeferSignal _ARGS_((ClientData data, Proc_CallInfo *callInfoPtr));


/*
 *----------------------------------------------------------------------
 *
 * SigMigSend --
 *
 *	Send a signal to a migrated process.  The current host is found
 *	in the process control block for the process.  
 *
 * Results:
 *	SUCCESS or an error condition from the RPC or remote node.
 *
 * Side effects:
 *	A remote procedure call is performed and the process is signalled
 *	on its currently executing host.
 *
 *----------------------------------------------------------------------
 */

ReturnStatus
SigMigSend(procPtr, sigNum, code, addr)
    register Proc_ControlBlock 	*procPtr; /* The migrated process */
    int				  sigNum;
    int				  code;
    Address			  addr;
{
    ReturnStatus status;
    Proc_PID processID;
    Proc_PID remoteProcessID;
    int remoteHostID;
    Proc_ControlBlock 	*callerProcPtr; /* The calling process */
    Boolean locked;

    if (proc_MigDebugLevel > 4) {
	printf("SigMigSend(%x, %d, %d) entered.\n", procPtr->processID,
		   sigNum, code);
    }

again:
    processID = procPtr->processID;
    if (procPtr->genFlags & (PROC_MIG_PENDING | PROC_MIGRATING)) {
	/*
	 * If the current process is a user process, wait for the
	 * process to finish migrating before signalling it. If
	 * it's a kernel process, start a background process to
	 * wait for migration and deliver the signal asynchronously.
	 * When calling Proc_WaitForMigration, make sure the process isn't
	 * locked.
	 */
	callerProcPtr = Proc_GetActualProc();
	if (callerProcPtr->genFlags & PROC_KERNEL) {
	    DeferInfo *infoPtr;

	    infoPtr = (DeferInfo *) malloc(sizeof(*infoPtr));
	    infoPtr->processID = procPtr->processID;
	    infoPtr->sigNum = sigNum;
	    infoPtr->code = code;
	    Proc_CallFunc(DeferSignal, (ClientData) infoPtr, 0);
	    return(SUCCESS);
	}
        Proc_Unlock(procPtr);
	status = Proc_WaitForMigration(processID);
	Proc_Lock(procPtr);
	if ((procPtr->state != PROC_MIGRATED) ||
	    (procPtr->processID != processID)) {
	    /*
	     * Process is not now migrated.  Return PROC_INVALID_PID,
	     * which will make Sig_SendProc do a local send.
	     */
	    return(PROC_INVALID_PID);
	}
	if (status != SUCCESS) {
	    return(status);
	}
    }
    remoteProcessID = procPtr->peerProcessID;
    remoteHostID = procPtr->peerHostID;
    if (remoteHostID == (int) NIL) {
	printf("Warning: SigMigSend: process %x has no peer.\n", processID);
	return(PROC_INVALID_PID);
    }
	

    /*
     * It is necessary to unlock the process while sending the remote
     * signal, since the signal could cause the remote node to come back
     * and lock the process again.
     */
    Proc_Unlock(procPtr);
    locked = FALSE;
    status = SigSendRemoteSignal(remoteHostID, sigNum, code,
			      remoteProcessID, FALSE, addr);

    if (proc_MigDebugLevel > 4) {
	printf("SigMigSend returning %x.\n", status);
    }

    if (status != SUCCESS) {
	if (status == PROC_INVALID_PID) {
	    Proc_ControlBlock *newProcPtr;
	    newProcPtr = Proc_LockPID(processID);
	    if (newProcPtr == (Proc_ControlBlock *) NIL) {
		/*
		 * This is what we're hoping for: the process doesn't
		 * exist on either the remote host or the local host.
		 */
		goto done;
	    }
	    locked = TRUE;
	    if (procPtr != newProcPtr) {
		panic("SigMigSend: locked wrong process (continuable).\n");
		goto done;
	    }
	    /*
	     * Same process.
	     */
	    if (procPtr->state == PROC_MIGRATED &&
		procPtr->peerHostID != remoteHostID) {
		if (proc_MigDebugLevel > 1) {
		    printf("SigMigSend: process %x changed hosts during signal; retrying.\n",
			       processID);
		}
	    
		goto again;
	    }
	    if (procPtr->state != PROC_MIGRATED) {
		if (proc_MigDebugLevel > 1) {
		    printf("SigMigSend: process %x no longer migrated.\n",
			       processID);
		}
		goto done;
	    }
	}	    
	    
	    
	if (proc_MigDebugLevel > 0) {
	    printf("Warning: SigMigSend:Error trying to signal %d to process %x (%x on host %d):\n\t%s\n",
		   sigNum, processID, remoteProcessID, remoteHostID,
		   Stat_GetMsg(status));
	}
	if (sigNum == SIG_KILL || status == PROC_INVALID_PID) {
	    if (proc_MigDebugLevel > 0) {
		printf("SigMigSend: killing local copy of process %x.\n",
			   processID);
	    }
	    Proc_CallFunc(Proc_DestroyMigratedProc,
			  (ClientData) processID, 0);
	}
    }

    /*
     * Give back the procPtr in the same state we found it (locked).
     * Note that it may no longer refer to the same process (if the process
     * has been recycled while we had it unlocked) but the caller should
     * just unlock it and return.
     */
    done:
    if (!locked) {
	Proc_Lock(procPtr);
    }
	
    return(status);
}


/*
 *----------------------------------------------------------------------
 *
 * DeferSignal --
 *
 *	Wait for a process to migrate, then send it a signal. This
 *	is done using the callback queue so the sender of the signal,
 *	if a kernel process, doesn't block.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The process doing the callback goes to sleep until the process
 *	being signalled has migrated or been killed.
 *
 *----------------------------------------------------------------------
 */

static void 
DeferSignal(data, callInfoPtr)
    ClientData data;
    Proc_CallInfo *callInfoPtr;         /* Not used. */
{
    DeferInfo *infoPtr = (DeferInfo *) data;
    ReturnStatus status;
    Proc_ControlBlock *procPtr;

    status = Proc_WaitForMigration(infoPtr->processID);
    if (status != SUCCESS) {
	goto failure;
    }
    procPtr = Proc_LockPID(infoPtr->processID);
    if (procPtr == (Proc_ControlBlock *) NIL) {
	goto failure;
    }
    (void) SigMigSend(procPtr, infoPtr->sigNum, infoPtr->code, (Address) 0);
    Proc_Unlock(procPtr);
    return;

    failure:
    if (proc_MigDebugLevel > 2) {
	printf("DeferSignal: unable to send delayed signal to migrated process %x\n",
	       infoPtr->processID);
    }
}
    

typedef struct {
    int		sigHoldMask;
    int		sigPendingMask;
    int		sigActions[SIG_NUM_SIGNALS];
    int		sigMasks[SIG_NUM_SIGNALS];
    int		sigCodes[SIG_NUM_SIGNALS];
    int		sigFlags;
} EncapState;

#define COPY_STATE(from, to, field) to->field = from->field

/*
 *----------------------------------------------------------------------
 *
 * Sig_GetEncapSize --
 *
 *	Determine the size of the encapsulated signal state.
 *
 * Results:
 *	SUCCESS is returned directly; the size of the encapsulated state
 *	is returned in infoPtr->size.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

/* ARGSUSED */
ReturnStatus
Sig_GetEncapSize(procPtr, hostID, infoPtr)
    Proc_ControlBlock *procPtr;			/* process being migrated */
    int hostID;					/* host to which it migrates */
    Proc_EncapInfo *infoPtr;			/* area w/ information about
						 * encapsulated state */
{
    infoPtr->size = sizeof(EncapState);
    return(SUCCESS);	
}


/*
 *----------------------------------------------------------------------
 *
 * Sig_EncapState --
 *
 *	Encapsulate the signal state of a process from the Proc_ControlBlock
 *	and return it in the buffer provided.
 *
 * Results:
 *	SUCCESS.  The buffer is filled.
 *
 * Side effects:
 *	None.
 *----------------------------------------------------------------------
 */

/* ARGSUSED */
ReturnStatus
Sig_EncapState(procPtr, hostID, infoPtr, bufPtr)
    register Proc_ControlBlock 	*procPtr;  /* The process being migrated */
    int hostID;				   /* host to which it migrates */
    Proc_EncapInfo *infoPtr;		   /* area w/ information about
					    * encapsulated state */
    Address bufPtr;			   /* Pointer to allocated buffer */
{
    EncapState *encapPtr = (EncapState *) bufPtr;

    COPY_STATE(procPtr, encapPtr, sigHoldMask);
    COPY_STATE(procPtr, encapPtr, sigPendingMask);
    COPY_STATE(procPtr, encapPtr, sigFlags);
    bcopy((Address) procPtr->sigActions, (Address) encapPtr->sigActions,
	  SIG_NUM_SIGNALS * sizeof(int));
    bcopy((Address) procPtr->sigMasks, (Address) encapPtr->sigMasks,
	  SIG_NUM_SIGNALS * sizeof(int));
    bcopy((Address) procPtr->sigCodes, (Address) encapPtr->sigCodes,
	  SIG_NUM_SIGNALS * sizeof(int));
    return(SUCCESS);
}


/*
 *----------------------------------------------------------------------
 *
 * Sig_DeencapState --
 *
 *	Get signal information from a Proc_ControlBlock from another host.
 *	The information is contained in the parameter ``buffer''.
 *
 * Results:
 *	SUCCESS.
 *
 * Side effects:
 *	None.
 *----------------------------------------------------------------------
 */

/* ARGSUSED */
ReturnStatus
Sig_DeencapState(procPtr, infoPtr, bufPtr)
    register Proc_ControlBlock 	*procPtr; /* The process being migrated */
    Proc_EncapInfo *infoPtr;		  /* information about the buffer */
    Address bufPtr;			  /* buffer containing data */
{
    EncapState *encapPtr = (EncapState *) bufPtr;

    if (infoPtr->size != sizeof(EncapState)) {
	if (proc_MigDebugLevel > 0) {
	    printf("Sig_DeencapState: warning: host %d tried to migrate onto this host with wrong structure size.  Ours is %d, theirs is %d.\n",
		   procPtr->peerHostID, sizeof(EncapState),
		   infoPtr->size);
	}
	return(PROC_MIGRATION_REFUSED);
    }
    COPY_STATE(encapPtr, procPtr, sigHoldMask);
    COPY_STATE(encapPtr, procPtr, sigPendingMask);
    procPtr->sigPendingMask &=
	~(Sig_NumberToMask(SIG_MIGRATE_TRAP) |
	  Sig_NumberToMask(SIG_MIGRATE_HOME));
    COPY_STATE(encapPtr, procPtr, sigFlags);
    bcopy((Address) encapPtr->sigActions, (Address) procPtr->sigActions,
	  SIG_NUM_SIGNALS * sizeof(int));
    bcopy((Address) encapPtr->sigMasks, (Address) procPtr->sigMasks,
	  SIG_NUM_SIGNALS * sizeof(int));
    bcopy((Address) encapPtr->sigCodes, (Address) procPtr->sigCodes,
	  SIG_NUM_SIGNALS * sizeof(int));
    return(SUCCESS);
}
