/* 
 *  procFork.c --
 *
 *	Routines to create new processes.  No monitor routines are required
 *	in this file.  Synchronization to proc table entries is by a call
 *	to the proc table monitor to get a PCB and calls to the family monitor
 *	to put a newly created process into a process family.
 *
 * Copyright (C) 1985, 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/procFork.c,v 9.11 91/11/15 21:06:46 kupfer Exp $ SPRITE (Berkeley)";
#endif /* not lint */

#include <sprite.h>
#include <mach.h>
#include <list.h>
#include <proc.h>
#include <procInt.h>
#include <sched.h>
#include <status.h>
#include <stdlib.h>
#include <string.h>
#include <sync.h>
#include <sys.h>
#include <timer.h>
#include <vm.h>
#include <prof.h>
#include <procUnixStubs.h>

/*
 * There is only one vfork sleep condition in the system.
 * When a child does a Sync_Broadcast, all sleeping parents
 * wake up, check the VFORKPARENT flag in their respective
 * PCBs and all but one (hopefully) go back to sleep.
 * If the contention level is too high we can put a condition lock
 * in each PCB, but this requires recompiling most of the world
 * so lets try the easy way first.
 */
static Sync_Condition vforkCondition;
static Sync_Lock vforkLock;
#define LOCKPTR &vforkLock

static ReturnStatus    InitUserProc _ARGS_((Proc_ControlBlock *procPtr,
			    Proc_ControlBlock *parentProcPtr,
			    Boolean shareHeap, Boolean vforkFlag));


/*
 *----------------------------------------------------------------------
 *
 * Proc_Fork --
 *
 *	Process the fork system call.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

int
Proc_Fork(shareHeap, pidPtr)
    Boolean	shareHeap;
    Proc_PID	*pidPtr;
{
    Proc_PID		*newPidPtr;
    int			numBytes;
    ReturnStatus	status;
    
    /*
     * Make the pointer to the process id that is to be returned accessible.
     */

    Vm_MakeAccessible(VM_OVERWRITE_ACCESS, 
		      sizeof(Proc_PID), (Address) pidPtr,
		      &numBytes, (Address *) &newPidPtr);
    if (numBytes < sizeof(Proc_PID)) {
	return(SYS_ARG_NOACCESS);
    }

    /*
     * Start up the new process.  The PC where to begin execution doesn't 
     * matter since it has already been stored in the proc table entry before
     * we were called.
     */

    status = Proc_NewProc((Address) 0, PROC_USER, shareHeap, newPidPtr,
			  (char *)NIL, FALSE);

    Vm_MakeUnaccessible((Address) newPidPtr, numBytes);

    return(status);
}


/*
 *----------------------------------------------------------------------
 *
 * Proc_Vfork --
 *
 *	Process the vfork system call.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

int
Proc_Vfork()
{
    ReturnStatus	status;
    Proc_PID newPid;

    status = Proc_NewProc((Address) 0, PROC_USER, FALSE, &newPid,
			  (char *) NIL, TRUE);
    if (status != SUCCESS) {
	Mach_SetErrno(Compat_MapCode(status));
	return -1;
    }
    return (int) newPid;
}


/*
 * ----------------------------------------------------------------------------
 *
 * Proc_VforkWakeup
 *
 *	Called by vfork'd child to wakeup waiting parent
 *      (when child dies or execs). Caller must hold a lock
 *      on the child's PCB entry (ie. must have called Proc_Lock()).
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Will make parent process runnable.
 *
 * ----------------------------------------------------------------------------
 */

void
Proc_VforkWakeup(procPtr)
Proc_ControlBlock 	*procPtr;
{
    Proc_ControlBlock 	*parentProcPtr;

    parentProcPtr = Proc_GetPCB(procPtr->parentID);
    Proc_Lock(parentProcPtr);
    if (!(parentProcPtr->genFlags & PROC_VFORKPARENT)) {
	panic("VForkWakeup called but VFORKPARENT flag == 0.");
    }
    parentProcPtr->genFlags &= ~PROC_VFORKPARENT;
    Proc_Unlock(parentProcPtr);

    LOCK_MONITOR;
    Sync_Broadcast(&vforkCondition);
    UNLOCK_MONITOR;

    procPtr->genFlags &= ~PROC_VFORKCHILD;

}


/*
 * ----------------------------------------------------------------------------
 *
 * Proc_NewProc --
 *
 *	Allocates a PCB and initializes it.
 *
 * Results:
 *	Pointer to process control block for created process.
 *
 * Side effects:
 *	PCB initialized and made runnable.
 *
 * ----------------------------------------------------------------------------
 */

ReturnStatus
Proc_NewProc(PC, procType, shareHeap, pidPtr, procName, vforkFlag)
    Address 	PC;		/* The program counter where to start. */
    int		procType;	/* One of PROC_KERNEL or PROC_USER. */
    Boolean	shareHeap;	/* TRUE if share heap, FALSE if not. */
    Proc_PID	*pidPtr;	/* A pointer to where to return the process
				   ID in. */
    char	*procName;	/* Name for process control block */
    Boolean     vforkFlag;      /* Added for vfork */
{
    ReturnStatus	status;
    Proc_ControlBlock 	*procPtr;	/* The new process being created */
    Proc_ControlBlock 	*parentProcPtr;	/* The parent of the new process,
					 * the one that is making this call */
    Boolean		migrated = FALSE;

    parentProcPtr = Proc_GetActualProc();

    if (parentProcPtr->genFlags & PROC_FOREIGN) {
	migrated = TRUE;
    }

    procPtr = ProcGetUnusedPCB();
    if (pidPtr != (Proc_PID *) NIL) {
	*pidPtr		= procPtr->processID;
    }

    procPtr->Prof_Scale = 0;
    Prof_Enable(procPtr, parentProcPtr->Prof_Buffer, 
        parentProcPtr->Prof_BufferSize, parentProcPtr->Prof_Offset,
	parentProcPtr->Prof_Scale);

    procPtr->processor		= parentProcPtr->processor;
    procPtr->genFlags 		|= procType;
    if (vforkFlag) {
	procPtr->genFlags |= PROC_VFORKCHILD;
    }
    procPtr->syncFlags		= 0;
    procPtr->schedFlags		= 0;
    procPtr->exitFlags		= 0;

    if (!migrated) {
	procPtr->parentID 	= parentProcPtr->processID;
    } else {
	procPtr->parentID 	= parentProcPtr->peerProcessID;
    }
    procPtr->familyID 		= parentProcPtr->familyID;
    procPtr->userID 		= parentProcPtr->userID;
    procPtr->effectiveUserID 	= parentProcPtr->effectiveUserID;

    procPtr->billingRate 	= parentProcPtr->billingRate;
    procPtr->recentUsage 	= 0;
    procPtr->weightedUsage 	= 0;
    procPtr->unweightedUsage 	= 0;

    procPtr->kernelCpuUsage.ticks 	= timer_TicksZeroSeconds;
    procPtr->userCpuUsage.ticks 	= timer_TicksZeroSeconds;
    procPtr->childKernelCpuUsage.ticks = timer_TicksZeroSeconds;
    procPtr->childUserCpuUsage.ticks 	= timer_TicksZeroSeconds;
    procPtr->numQuantumEnds	= 0;
    procPtr->numWaitEvents	= 0;
    procPtr->event		= NIL;

    procPtr->kcallTable		= mach_NormalHandlers;
    procPtr->unixProgress	= parentProcPtr->unixProgress;

    /* 
     * Free up the old argument list, if any.  Note, this could be put
     * in Proc_Exit, but is put here for consistency with the other
     * reinitializations of control block fields.  
p     */

    if (procPtr->argString != (Address) NIL) {
	free((Address) procPtr->argString);
	procPtr->argString = (Address) NIL;
    }

    /*
     * Create the argument list for the child.  If no name specified, take
     * the list from the parent.  If one is specified, just make a one-element
     * list containing that name.
     */
    if (procName != (char *)NIL) {
	procPtr->argString = (char *) malloc(strlen(procName) + 1);
	(void) strcpy(procPtr->argString, procName);
    } else if (parentProcPtr->argString != (Address) NIL) {
	procPtr->argString =
		(char *) malloc(strlen(parentProcPtr->argString) + 1);
	(void) strcpy(procPtr->argString, parentProcPtr->argString);
    }

    if (!migrated) {
	if (ProcFamilyInsert(procPtr, procPtr->familyID) != SUCCESS) {
	    panic("Proc_NewProc: ProcFamilyInsert failed\n");
	}
    }

    /*
     *  Initialize our child list to remove any old links.
     *  If not migrated, insert this PCB entry into the list
     *  of children of our parent.
     */
    List_Init((List_Links *) procPtr->childList);
    if (!migrated) {
	List_Insert((List_Links *) &(procPtr->siblingElement), 
		    LIST_ATREAR(parentProcPtr->childList));
    }
    Sig_Fork(parentProcPtr, procPtr);

    Vm_ProcInit(procPtr);

    /*
     * If the process is migrated, setup its process state on the home node.
     */
    if (migrated) {
	status = ProcRemoteFork(parentProcPtr, procPtr);
	if (status != SUCCESS) {
	    /*
	     * We couldn't fork on the home node, so free up the new
	     * process that we were in the process of allocating.
	     */

	    Proc_Unlock(procPtr);
	    ProcFreePCB(procPtr);

	    return(status);
	}

	/*
	 * Change the returned process ID to be the process ID on the home
	 * node.
	 */
	if (pidPtr != (Proc_PID *) NIL) {
	    *pidPtr = procPtr->peerProcessID;
	}
    } else {
	procPtr->peerHostID = NIL;
	procPtr->peerProcessID = NIL;
    }

    /*
     * Set up the virtual memory of the new process.
     */

    if (procType == PROC_KERNEL) {
	status = Mach_SetupNewState(procPtr, (Mach_State *)NIL,
				    Sched_StartKernProc, PC, FALSE);
	if (status != SUCCESS) {
	    /*
	     * We are out of kernel stacks.
	     */
	    Proc_Unlock(procPtr);
	    ProcFreePCB(procPtr);
	    return(status);
	}
    } else {
	status = InitUserProc(procPtr, parentProcPtr, shareHeap, vforkFlag);
	if (status != SUCCESS) {
	    /*
	     * We couldn't allocate virtual memory, so free up the new
	     * process that we were in the process of allocating.
	     */

	    if (!migrated) {
		ProcFamilyRemove(procPtr);
		List_Remove((List_Links *) &(procPtr->siblingElement));
	    }
	    Proc_Unlock(procPtr);
	    ProcFreePCB(procPtr);

	    return(status);
	}
    }

    /*
     * Mark ourselves waiting, if necessary
     */
    if (vforkFlag) {
	Proc_Lock(parentProcPtr);
	parentProcPtr->genFlags |= PROC_VFORKPARENT;
	Proc_Unlock(parentProcPtr);
    }

    /*
     * Set up the environment of the process.
     */

    if (!migrated) {
	ProcSetupEnviron(procPtr);
    }
    
    /*
     * Have the new process inherit filesystem state.
     */
    Fs_InheritState(parentProcPtr, procPtr);

    /*
     * Return PROC_CHILD_PROC to the newly created process.
     */
    if (procPtr->unixProgress == PROC_PROGRESS_NOT_UNIX) {
	Mach_SetReturnVal(procPtr, (vforkFlag ? 0 : (int) PROC_CHILD_PROC), 1);
    } else {
	Mach_SetReturnVal(procPtr, 0, 1);
    }

    /* 
     * Now that we're done messing with the PCB, unlock it.  Maybe this 
     * could get moved up to happen earlier in the function, but there's 
     * probably no harm to delaying until now.
     */
    Proc_Unlock(procPtr);

    /*
     * Put the process on the ready queue.
     */
    Sched_MakeReady(procPtr);

    /*
     * Now make the parent wait until child exec`s or exits
     */
    if (vforkFlag) {
	LOCK_MONITOR;
	while (parentProcPtr->genFlags & PROC_VFORKPARENT) {
	    Sync_Wait(&vforkCondition, FALSE);
	}
	UNLOCK_MONITOR;
    }

    return(SUCCESS);
}


/*
 *----------------------------------------------------------------------
 *
 * InitUserProc --
 *
 *	Initalize the state for a user process.  This involves allocating
 *	the segments for the new process.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static ReturnStatus
InitUserProc(procPtr, parentProcPtr, shareHeap, vforkFlag)
    register	Proc_ControlBlock	*procPtr;	/* PCB to initialized.*/
    register	Proc_ControlBlock	*parentProcPtr;	/* Parent's PCB. */
    Boolean				shareHeap;	/* TRUE => share heap
							 * with parent. */
    Boolean				vforkFlag;	/* TRUE => share all
							 * segs with parent. */
{
    ReturnStatus	status;

    /*
     * Set up a kernel stack for the process.
     */
    status = Mach_SetupNewState(procPtr, parentProcPtr->machStatePtr,
				Sched_StartUserProc, (Address)NIL, TRUE);
    if (status != SUCCESS) {
	return(status);
    }

    /*
     * Initialize all of the segments.  The system segment is the standard one.
     * The code segment is the same as the parent process.  The stack segment
     * is a copy of the parents, unless vforkFlag == TRUE in which case
     * the ref count is boosted.  Finally the heap segment is either a copy
     * or the same as the parent depending on the share heap flag.
     */

    procPtr->vmPtr->segPtrArray[VM_SYSTEM] = (Vm_Segment *) NIL;

    if (vforkFlag) {
	Vm_SegmentIncRef(parentProcPtr->vmPtr->segPtrArray[VM_STACK], procPtr);
	procPtr->vmPtr->segPtrArray[VM_STACK] = 
				parentProcPtr->vmPtr->segPtrArray[VM_STACK];
    } else {
	status = Vm_SegmentDup(parentProcPtr->vmPtr->segPtrArray[VM_STACK],
			procPtr, &(procPtr->vmPtr->segPtrArray[VM_STACK]));
	if (status != SUCCESS) {
	    Mach_FreeState(procPtr);
	    return(status);
	}
    }

    if (shareHeap || vforkFlag) {
	Vm_SegmentIncRef(parentProcPtr->vmPtr->segPtrArray[VM_HEAP], procPtr);
	procPtr->vmPtr->segPtrArray[VM_HEAP] = 
				parentProcPtr->vmPtr->segPtrArray[VM_HEAP];
    } else {
	status = Vm_SegmentDup(parentProcPtr->vmPtr->segPtrArray[VM_HEAP],
			   procPtr, &(procPtr->vmPtr->segPtrArray[VM_HEAP]));
	if (status != SUCCESS) {
	    Vm_SegmentDelete(procPtr->vmPtr->segPtrArray[VM_STACK], procPtr);
	    Mach_FreeState(procPtr);
	    return(status);
	}
    }

    if (parentProcPtr->vmPtr->sharedSegs != (List_Links *)NIL) {
	Vm_CopySharedMem(parentProcPtr, procPtr);
    }

    Vm_SegmentIncRef(parentProcPtr->vmPtr->segPtrArray[VM_CODE], procPtr);
    procPtr->vmPtr->segPtrArray[VM_CODE] =
				parentProcPtr->vmPtr->segPtrArray[VM_CODE];

    return(SUCCESS);
}
