/*static char *SCCSID = "@(#)tklink.c   6.4 92/03/13";*/
#define SCCSID  "@(#)tklink.c   6.4 92/03/13"
/*
 *      Tasking - Process/Thread Linkage Routines
 *
 *      IBM/Microsoft Confidential
 *
 *      Copyright (c) IBM Corporation 1987, 1989
 *      Copyright (c) Microsoft Corporation 1987, 1989
 *
 *      All Rights Reserved
 *
 *      Created: Jan 1989 - Jameel Hyder.
 */

#define INCL_TYPES                      // for basedef.h typedefs
#define INCL_ERROR_H                    // for error.h defines
#define INCL_ERROR2_H                   // for error2.h defines

#include <basemac.h>
#include <basedef.h>
#include <osconst.h>
#include <ostypes.h>
#define FNO_NUM FNO_TKLINK_C
#include <fnotask.h>
#include <osmac.h>
#include <bseerr.h>
#include <dossymc.h>
#include <sigdef.h>
#include <except.h>
#include <mi.h>
#include <infoseg.h>
#include <pgexport.h>
#include <vmexport.h>
#include <tkexport.h>
#include <proc.h>
#include <curdir.h>
#include <dirent.h>
#include <find.h>
#include <screen.h>
#include <sem.h>
#include <tkptda.h>
#include <ldrxport.h>
#include <kmode.h>
#include <seldesc.h>
#include <sched.h>
#include <bsetib.h>
#include <tklocal.h>

/***ED  Global ptda pointers */

PPTDA   pPTDAFirst;

// Moved to sdata.asm for System Anchor Block - f122427
extern PPTDA   pPTDAFirstZombie;
extern PPTDA   pptdaTKEvap;             /* List of dead PTDAs */


/***LD  ctkScanners - count of threads inside TKScanTasks */

extern ulong_t          ctkScanners;
extern ulong_t          ctkScannerOwner;


/***LT  Structure used by TKPidToPTDA to communicate with tkCheckPid */

typedef struct {
        USHORT  Wa_Pid;                 // Pid to look for
        PPTDA   Wa_pPTDA;               // Address of PTDA if found
} WorkerArg;

int     _stdcall tkCheckPid(PPTDA pPTDA, WorkerArg*pArg);
int     _stdcall tkCheckCsid(PPTDA pPTDA, USHORT Csid);


/***LP  tkStartScan - notification that TKScanTasks is starting
 *
 *      This procedure is called to increment the count of
 *      threads inside TKScanTasks.
 *
 *      tkStartScan ()
 *
 *      ENTRY   NONE
 *      RETURN  NONE
 *
 *      Increment ctkScanners by two.  The low-order bit of
 *      ctkScanners is used as the waiting bit by threads waiting
 *      for all scans to be finished.
 *
 *      CALLS   NONE
 *
 *      WARNING This procedure relies on non-preemptability of
 *              kernel code to prevent races in updating
 *              ctkScanners.
 *
 *      EFFECTS ctkScanners is incremented by two.
 */

void    tkStartScan()
{
        ctkScanners += 2;               /* Another thread is scanning */
        ctkScannerOwner = pTCBCur;
        tkAssert(ctkScanners > 1);      /* Must not overflow */
}



/***LP  tkStopScan - notification that TKScanTasks is stopping
 *
 *      This procedure is called to decrement the count of
 *      threads inside TKScanTasks.  If the count is one
 *      after decrementing, then waiting threads are run, and
 *      the count is set to zero.
 *
 *      tkStopScan ()
 *
 *      ENTRY   NONE
 *      RETURN  NONE
 *
 *      Decrement ctkScanners by two.  If the count equals one,
 *      then set the count to zero and run any threads waiting on
 *      the count.
 *
 *      CALLS   TKWakeup
 *
 *      WARNING This procedure relies on non-preemptability of
 *              kernel code to prevent races in updating
 *              ctkScanners.
 *
 *      EFFECTS ctkScanners is decremented by two.  Waiting threads,
 *              if any, will be made runnable.
 */

void    tkStopScan()
{
        tkAssert(ctkScanners > 1);      /* Must not underflow */
        ctkScannerOwner = 0;
        if((ctkScanners -= 2) == 1) {   /* If time to wake waiters */
            ctkScanners = 0;            /* Reset the count */
            TKWakeup(&ctkScanners, 0, NULL);
                                        /* Wake up waiting threads */
        }
}



/***LP  tkWaitScan - wait for TKScanTasks to complete
 *
 *      This procedure blocks the current thread until the count
 *      of threads executing in TKScanTasks goes to zero.
 *
 *      tkWaitScan ()
 *
 *      ENTRY   NONE
 *      RETURN  NONE
 *
 *      While ctkScanners is not zero, set the waiting bit (bit 0),
 *      and block on the address of ctkScanners.
 *
 *      CALLS   TKSleep
 *
 *      WARNING This procedure relies on non-preemptability of
 *              kernel code to prevent races in updating
 *              ctkScanners.  It is assumed that users of this
 *              procedure will not yield the CPU until they
 *              have completed their critical activity.
 *
 *      EFFECTS May set the low-order bit of ctkScanners and block
 *              the current thread.
 */

void    tkWaitScan()
{
        while(ctkScanners != 0) {       /* While TKScanTasks is busy */
            tkAssert(ctkScanners != 1); /* Error if only waiting bit set */
            ctkScanners |= 1;           /* Set the waiting bit */
            TKSleep(&ctkScanners, -1L, 1, 0);
                                        /* Block *UNINTERRUPTIBLY* */
        }
}



/***LP  tkCheckPid - Worker Routine for TKPidToPTDA
 *
 *      This routine is used by TKPidToPTDA to scan each task in the
 *      task tree using TKScanTasks.  TKScanTasks selects each PTDA in turn
 *      calls the worker.  The worker compares the Pid field in the target
 *      PTDA with the supplied Pid in the worker's data area.  If the two
 *      match, the address of the target PTDA is stored in the worker's
 *      data area and the worker returns the code ERROR_SCAN_TERMINATED
 *      to cause TKScanTasks to terminate the scan.
 *
 *      ENTRY  pPTDA =  address of a target PTDA selected by ScanTasks
 *                pArg  =  address of worker's data area.  This has the
 *                      format of WorkerArg (see at top of the file).
 *
 *      EXIT:   NO_ERROR - continue scan - no match
 *
 *              ERROR_SCAN_TERMINATED - match found
 *              pArg->Wa_pPTDA = address of PTDA with matching Pid
 *
 */

int     _stdcall tkCheckPid(PPTDA pPTDA, WorkerArg*pArg)
{
        if (pPTDA->ptda_Pid == pArg->Wa_Pid) {
                pArg->Wa_pPTDA = pPTDA;
                return (ERROR_SCAN_TERMINATED);
        }
        return(NO_ERROR);
}



/***EP  TKPidToPTDA - Convert Process Id to PTDA address
 *
 *      TKPidToPTDA returns the address of the PTDA for the task
 *      having the specified Process Id.
 *
 *      ENTRY   Pid = Process Id of the PTDA whose address is to be returned
 *              ppPTDA = pointer to where the address of PTDA is to be returned
 *
 *      EXIT    NO_ERROR, If successful
 *                      *ppPTDA contains the address of the PTDA of the
 *                      task having the specifid Pid.
 *              ERROR_INVALID_PROCID
 *                      There is no task having the specified Process Id
 *
 */

int TKPidToPTDA(Pid, ppPTDA)
USHORT  Pid;
PPTDA   *ppPTDA;
{
        WorkerArg arg;
        int       error;

        if (Pid == 0)
                return (ERROR_INVALID_PROCID);
        if (Pid == pPTDACur->ptda_Pid) {        // looking for this process
                *ppPTDA = pPTDACur;
                return (NO_ERROR);
        }
        arg.Wa_pPTDA = NULL;
        arg.Wa_Pid   = Pid;
        if ((error = TKScanTasks(TK_TG_ALL | TK_SF_WORKER, 0,
                                tkCheckPid, SSToDS(&arg))) != NO_ERROR)
                return(error);
        if ((*ppPTDA = arg.Wa_pPTDA) == NULL)
                return (ERROR_INVALID_PROCID);
        return (NO_ERROR);
}



/***LP  tkCheckCsid - TKScanTasks worker to check if the given process
 *                        has the same Csid
 *
 *      ENTRY:  pPTDA - pointer to ptda
 *              pCsid - pointer to Csid
 *
 *      EXIT:   NO_ERROR
 *                      If Csid does not match
 *              ERROR_NOT_DESCENDANT, otherwise
 */

int     _stdcall        tkCheckCsid(PPTDA pPTDA, USHORT Csid)
{
        return((pPTDA->Csid != Csid)? NO_ERROR: ERROR_NOT_DESCENDANT);
}



/***EP  TKScanTasks - Scan a group of Tasks using a kernel worker routine
 *
 *      The caller supplies the address of a worker routine in the
 *      kernel ring 0 code.     The worker is called once for each task that
 *      belongs to the specified group, and is presented with the address
 *      of the Per-Task Data Area (PTDA) of a task in the group, and the
 *      address of a read/write data area.  The data area may be viewed
 *      in any format desired by the worker, and data is not accessed by
 *      TKScanTasks.  The scan can optionally be terminated if the worker
 *      returns with non-zero.  The worker routine is called as an int
 *      function as follows
 *
 *      error = (*pWorker)(pPTDA, pArg)
 *
 *      The order in which the processes are presented to the worker routine
 *      is not exported, except that a parent process is always accessed before
 *      any of its children.
 *
 *      TKScanTasks will never reference a zombie process, nor will it
 *      invoke the worker on a process whose PS_DYING bit is set in the
 *      ptda_ProcStatus flags.
 *
 *      ENTRY   ScanFlags - Type of Task Group to be scanned (low order byte)
 *                TK_TG_ONE  -  Individual process
 *                TK_TG_CSUB -  Command Subtree of current process
 *                TK_TG_TREE -  A process and all of its descendants.
 *                TK_TG_SCRN -  All processes in the current screen group
 *                TK_TG_ALL  -  All processes
 *
 *              ScanFlags - high order byte
 *                TK_SF_WORKER   - Stop the scan if the worker returns error.
 *                TK_SF_WRETCODE - Return the worker's error code if scan
 *                                       stopped by worker when TK_SF_WORKER.
 *                TK_SF_EXEC     - Scan children being exec'ed even if not
 *                                       linked into the task tree.
 *                TK_SF_DYING    - Scan dying processes as well.
 *
 *              GroupId = Id of task group to be scanned.
 *                Process id if ScanType = TK_TG_ONE
 *                Command Subtree Id if ScanType = TK_TG_CSUB.  The
 *                      command subtree id must be the Process Id for a process
 *                      that was exec'ed by the current process.
 *                Process Id for root process
 *                Screen Group Number if ScanType = TK_TG_SCRN
 *                Not used if ScanType = TK_TG_ALL
 *
 *              pWorker = Address of worker routine
 *
 *              pArg = Pointer to a data area that is to be accessible
 *                        on each call.
 *
 *      EXIT    NO_ERROR, If successful
 *                      pArg array contains data left by last worker call
 *
 *              ERROR_INVALID_PROCID
 *                      The specified id is not a valid process id
 *                      or command tree id
 *
 *              ERROR_NOT_DESCENDANT
 *                      The specified id is a valid command subtree id,
 *                      but the  command subtree is not a command subtree
 *                      of the current process.
 *
 *              ERROR_INVALID_FUNCTION
 *                      Illegal ScanFlags
 *
 */

int TKScanTasks(ScanFlags, GroupId, pWorker, pArg)
USHORT          ScanFlags;
USHORT          GroupId;
register int    (*pWorker)(PPTDA,PVOID);
void            *pArg;
{
        register PPTDA  pPTDA;                  // working ptda
        PPTDA           pPTDAStop;              // ptda we stop at
        register int    error = NO_ERROR;       // TKScanTasks error code
        int             werror;                 // worker error code
        int             fWorkerCalled = FALSE;  // worker not called yet
        int             ScanType;

        tkStartScan();                          // Starting scan
        ScanType = ScanFlags & 0xFF;            // extract flags and scantype
        ScanFlags &= 0xFF00;
        switch (ScanType) {
          case TK_TG_ALL:
          case TK_TG_SCRN:
            pPTDA = pPTDAFirst;                 // stop with First
            pPTDAStop = NULL;                   // stop at First's parent
            break;

          case TK_TG_ONE:
          case TK_TG_TREE:
            pPTDA = pPTDACur;
            if (pPTDA->ptda_Pid != GroupId) {
                error = TKPidToPTDA(GroupId, SSToDS(&pPTDAStop));
                pPTDA = pPTDAStop;
            }
            else if(pPTDA->ptda_ProcStatus & PS_DYING)
                error = ERROR_INVALID_PROCID;   // Cannot touch a dying task
            if (ScanType == TK_TG_ONE && error == NO_ERROR) {
                werror = (*pWorker)(pPTDA, pArg);
                tkStopScan();                   // Stop scan
                if ((werror != NO_ERROR) && (ScanFlags & TK_SF_WORKER))
                        return ((ScanFlags & TK_SF_WORKER) ? werror : NO_ERROR);
                return (NO_ERROR);
            }
            pPTDAStop = pPTDA;
            break;

          case TK_TG_CSUB:
            pPTDA = pPTDACur;
            if (pPTDA->ptda_Pid != GroupId) {
                pPTDA = pPTDA->pPTDAFirstChild; // start with first child
                while (pPTDA != NULL) { // search the top-level ptdas for Csid
                    if (pPTDA->Csid == GroupId) // stop if found
                        break;
                    pPTDA = pPTDA->pPTDANextSibling; // try next sibling
                }
                if (pPTDA == NULL) {    // not found

                // We must distinguish between the command subtree existing
                // but not belonging to the current process, and the command
                // subtree not existing at all. In the former case, return
                // ERROR_NOT_DESCENDANT. In the latter, ERROR_INVALID_PROCID

                    if ((error = TKScanTasks(TK_TG_ALL | TK_SF_WORKER |
                      TK_SF_WRETCODE, 0, tkCheckCsid,
                      (void *)GroupId)) == NO_ERROR)
                        error = ERROR_INVALID_PROCID;
                }
            }
            else ScanType = TK_TG_TREE;         // if current process
            pPTDAStop = pPTDA;
            break;

          default:
            error = ERROR_INVALID_FUNCTION;
            break;
        }

        // return if we error'd
        if (error != NO_ERROR) {
                tkStopScan();                   // Stop scan
                return (error);
        }

        while (TRUE) {          // main scan loop
            if (((ScanFlags & TK_SF_DYING) || !(pPTDA->ptda_ProcStatus & PS_DYING))
                && (ScanType != TK_TG_SCRN
                    || pPTDA->Cons_Loc.Scrn_Num == (UCHAR) GroupId)) {
                fWorkerCalled = TRUE;
                werror = (*pWorker)(pPTDA, pArg);
                if (werror != NO_ERROR && (ScanFlags & TK_SF_WORKER)) {
                        tkStopScan();           // Stop scan
                        return ((ScanFlags & TK_SF_WRETCODE)?werror:NO_ERROR);
                }
                if ((ScanFlags & TK_SF_EXEC) && pPTDA->pPTDAExecChild != NULL) {
                    werror = (*pWorker)(pPTDA->pPTDAExecChild, pArg);
                    if (werror != NO_ERROR && (ScanFlags & TK_SF_WORKER)) {
                        tkStopScan();           // Stop scan
                        return ((ScanFlags & TK_SF_WRETCODE)?werror:NO_ERROR);
                    }
                }
            }
            // If the PTDA just referenced has children, then select the child
            // at the head of the list.

            if (pPTDA->pPTDAFirstChild != NULL) {
                pPTDA = pPTDA->pPTDAFirstChild;
                continue;
            }

            // There are no children. If we are at the Start PTDA, then we must
            // check that the sibling tree meets scan condition. If we are not
            // at start, the sibling tree ALWAYS meets scan condition.
          next_higher_level:
            if (pPTDA == pPTDAStop) {
                if (ScanType == TK_TG_CSUB) {
                    while ((pPTDA = pPTDA->pPTDANextSibling) != NULL) {
                        if (pPTDA->Csid==GroupId) {
                            pPTDAStop = pPTDA;  // mark new stop point
                            break;
                        }
                    }
                    if (pPTDA != NULL)  // continue if we found a process in
                        continue;       // sub-group
                }
                break;
            }

            // if there are siblings then do their sub-trees
            else if (pPTDA->pPTDANextSibling != NULL)
                    pPTDA = pPTDA->pPTDANextSibling;

            // there are neither children nor siblings. Go up a level and
            // continue scan.
            else {
                pPTDA = pPTDA->pPTDAParent;
                goto next_higher_level;
            }
        }
        // check for TK_TG_SCRN error
        if (!fWorkerCalled && ScanType == TK_TG_SCRN)
            error = ERROR_INVALID_SCREEN_GROUP;
        tkStopScan();                   // Stop scan
        return (error);
}



/***EP  TKLinkTask - Link new task's PTDA to sibling and parent PTDA's
 *
 *      This routine is called during the exec of a new process to link the
 *      PTDA to its sibling and parent PTDAs.  In the case of a detached
 *      process, the PTDA is linked to the PTDA of the null process as
 *      parent.
 *
 *      ENTRY   pPTDA - address of PTDA to be linked
 *
 *      EXIT    None
 *
 */

void TKLinkTask(pPTDA)
register PPTDA  pPTDA;
{
        tkWaitScan();                   // Wait until TKScanTasks is idle
        if (pPTDA->ptda_Ppid != 0) {    // not detached
            pPTDA->pPTDAParent = pPTDACur;
            pPTDA->pPTDANextSibling = pPTDACur->pPTDAFirstChild;
            pPTDACur->pPTDAFirstChild   = pPTDA;
            if (pPTDA->pPTDANextSibling)
                pPTDA->pPTDANextSibling->pPTDAPrevSibling = pPTDA;
        }
        else {                          // detached
            pPTDA->pPTDANextSibling = pPTDAFirst;
            pPTDAFirst->pPTDAPrevSibling = pPTDA;
            pPTDAFirst = pPTDA;
            pPTDA->pPTDAParent = NULL;
        }
        pPTDA->pPTDAPrevSibling = NULL;
}



/***EP  TKUnlinkTask - Unlink PTDA from sibling and parent PTDAs
 *
 *      This routine is called just before a task finally dies to unlink
 *      its PTDA from that of its sibling tasks and its parent.  Each
 *      direct child becomes a direct child of the parent task (if any)
 *      and these children are joined onto the head of the parent's chain
 *      of existing direct children.  The command subtree to which the dying
 *      task belongs is remembered by each of the direct children; the Csid
 *      field in the dying task's PTDA is copied to the PTDAs of the
 *      direct children.
 *
 *      If the process is not an orphan and its original parent is living,
 *      the PTDA will be added to the zombie list.
 *
 *      ENTRY   pPTDA - ptda pointer of the dying task
 *
 *      EXIT    None
 *
 */

void TKUnlinkTask(pPTDA)
register PPTDA  pPTDA;
{
        register PPTDA  pptdafirstorphan;
        register PPTDA  pptdalastorphan;

        tkAssert(pPTDA->ptda_ProcStatus & PS_DYING);
                                        // Process must be dead
        tkWaitScan();                   // Wait until TKScanTasks is idle
        if (pPTDA->pPTDAPrevSibling)    // if there is a previous sibling
            pPTDA->pPTDAPrevSibling->pPTDANextSibling = pPTDA->pPTDANextSibling;
        else {
            if (pPTDA == pPTDAFirst)    // dying precess is the 1st
                                        // in the top level chain
                pPTDAFirst = pPTDA->pPTDANextSibling;
            else {
                pPTDA->pPTDAParent->pPTDAFirstChild = pPTDA->pPTDANextSibling;
                pPTDA->pPTDAPrevSibling = NULL;
            }
        }
        if (pPTDA->pPTDANextSibling)            // if there is a next sibling
            pPTDA->pPTDANextSibling->pPTDAPrevSibling = pPTDA->pPTDAPrevSibling;

//      Link the dying proc's direct children to the parent and make the direct
//      children inherit the CSId of the dying process.

        if (pPTDA->pPTDAFirstChild != NULL) {
                                        // if process has direct children
            pptdafirstorphan = pptdalastorphan = pPTDA->pPTDAFirstChild;
            for(;;) {                   /* Loop to update each child */
                pptdalastorphan->ptda_Ppid      = pPTDA->ptda_Ppid;
                pptdalastorphan->Csid           = pPTDA->Csid;
                pptdalastorphan->pPTDAParent    = pPTDA->pPTDAParent;
                tkUpdatePIB(pptdalastorphan);
                                        /* Update PIB for Ppid change */
                if(pptdalastorphan->pPTDANextSibling == NULL) break;
                                        /* Break if no more children */
                pptdalastorphan = pptdalastorphan->pPTDANextSibling;
            }

            if(pPTDA->pPTDAParent != NULL) {
                                        /* If there is a parent */
                tkAssert(pptdalastorphan->pPTDANextSibling == NULL);
                                        /* Last orphan is last in list */
                if((pptdalastorphan->pPTDANextSibling =
                    pPTDA->pPTDAParent->pPTDAFirstChild) != NULL)
                    pPTDA->pPTDAParent->pPTDAFirstChild->pPTDAPrevSibling =
                    pptdalastorphan;    /* Attach old list to tail of new */
                pPTDA->pPTDAParent->pPTDAFirstChild = pptdafirstorphan;
                                        /* New parent points to new kids */
                tkAssert(pptdafirstorphan->pPTDAPrevSibling == NULL);
                                        /* First must be first in list */
            }
            else {                      /* Else true orphans */
                tkAssert(pPTDAFirst != NULL);
                                        /* Must have at least one orphan */
                pptdalastorphan->pPTDANextSibling = pPTDAFirst;
                pPTDAFirst->pPTDAPrevSibling = pptdalastorphan;
                                        /* Put new orphans at front of list */
                pPTDAFirst = pptdafirstorphan;
            }
        }

        // If the unlinkee should be a zombie, then link it to the zombie tree

        if ((pPTDA->ptda_ProcStatus & (PS_WAITPARENT | PS_SYNCPARENT)) &&
          pPTDA->ptda_Pid == pPTDA->Csid) {
            pPTDA->pPTDANextZombie = pPTDAFirstZombie;
            pPTDA->pPTDAPrevZombie = NULL;
            pPTDAFirstZombie = pPTDA;
            if (pPTDA->pPTDANextZombie)
                pPTDA->pPTDANextZombie->pPTDAPrevZombie = pPTDA;
        } else {                        /* Not a zombie, put on evap list */
            pPTDA->pPTDANextSibling = pptdaTKEvap;
            pptdaTKEvap = pPTDA;
            flGlobalActions |= KM_FF_EVAPALL;
                                        /* Set force flag */
        }
}



/***EP  TKAdoptTask - Link process into adopter's PTDA chain.
 *
 *      This routine is called by SYSServSM to move a process from one
 *      PTDA chain into the adopter's PTDA chain.
 *
 *      Just in case the original parent is doing a CWait on us, we
 *      give it the parent's CWait a ProcRun, to keep it from hanging.
 *      It is invalid to adopt a task that was started via a Synchronous Exec.
 *
 *      ENTRY   Pid - Id of process to be adopted
 *
 *      EXIT    NO_ERROR
 *              ERROR_INVALID_PROCID
 *                      There is no process having the given process id
 */

int TKAdoptTask(Pid)
ushort_t Pid;
{
        register PPTDA  pPTDA;
        PPTDA           pPTDA1;
        int             error;


        if ((error = TKPidToPTDA(Pid, SSToDS(&pPTDA1))) != NO_ERROR ||
                                (pPTDA1->ptda_ProcStatus & PS_WAITPARENT)) {
                return(ERROR_INVALID_PROCID);
        }
        tkWaitScan();                   // Wait until TKScanTasks is idle
        pPTDA = pPTDA1;
        TKWakeup((void *)(Pid + (FIELDOFFSET(PTDA, ptda_Pid) << 16)), 0, NULL);

        if (pPTDA->pPTDANextSibling)
            pPTDA->pPTDANextSibling->pPTDAPrevSibling = pPTDA->pPTDAPrevSibling;

        if (pPTDA->pPTDAPrevSibling)
            pPTDA->pPTDAPrevSibling->pPTDANextSibling = pPTDA->pPTDANextSibling;
        else {
            if (pPTDA->pPTDAParent)
                pPTDA->pPTDAParent->pPTDAFirstChild = pPTDA->pPTDANextSibling;
            else
                pPTDAFirst = pPTDA->pPTDANextSibling;
        }

//      We have unlinked adoptee from old linkages. Now link the adoptee into
//      the new parents chain.

        if (pPTDACur->pPTDAFirstChild)
            pPTDACur->pPTDAFirstChild->pPTDAPrevSibling = pPTDA;
        pPTDA->pPTDANextSibling = pPTDACur->pPTDAFirstChild;
        pPTDACur->pPTDAFirstChild = pPTDA;
        pPTDA->pPTDAParent = pPTDACur;

        pPTDA->pPTDAPrevSibling = NULL;
        pPTDA->ptda_Ppid = pPTDACur->ptda_Pid;
        tkUpdatePIB(pPTDA);             /* Force PIB update */
        return (NO_ERROR);
}



/***EP  TKDescendant - Check if a given process id is the process id of a
 *                      descendant of the current process.
 *
 *      This routine compares a given Pid with the Pid of the current process
 *      and all of its descendant processes and indicates whether the Pid
 *      matches the Pid of any descendant.
 *
 *      ENTRY   Pid - Id of the process to be checked
 *
 *      EXIT    NO_ERROR, if successful
 *              ERROR_INVALID_PROCID
 *                      There is no process having the given process id
 *              ERROR_NOT_DESCENDANT
 *                      The given process is not a descendant of the current
 *                      process
 */

int TKDescendant(Pid)
ushort_t Pid;
{
        WorkerArg       arg;

        arg.Wa_Pid = Pid;
        if (TKScanTasks(TK_TG_TREE | TK_SF_WORKER | TK_SF_WRETCODE,
                        pPTDACur->ptda_Pid,
                        tkCheckPid, SSToDS(&arg)) != NO_ERROR)
            return (NO_ERROR);
        else if (TKScanTasks(TK_TG_ALL | TK_SF_WORKER | TK_SF_WRETCODE,
                                0, tkCheckPid, SSToDS(&arg)) != NO_ERROR)
            return (ERROR_NOT_DESCENDANT);
        return (ERROR_INVALID_PROCID);
}



/***EP  TKPidGetSem - get PTDA semaphore given a Pid
 *
 *      This function obtains the PTDA semaphore for the process
 *      specified by the given Pid.
 *
 *      TKPidGetSem(uspid, ppptda)
 *
 *      ENTRY   uspid           - process i.d.
 *              ppptda          - pointer to place to put ptda pointer
 *      RETURN  int             - return code (NO_ERROR if successful)
 *
 *      This function revalidates the Pid after each blocking call
 *      to VMGetSem because the process could have died while we
 *      were blocked waiting for the semaphore.
 *
 *      CALLS   TKPidToPTDA, VMGetSem
 *
 *      WARNING NONE
 *
 *      EFFECTS The caller owns the given process' PTDA semaphore
 *              on a successful return.
 */

int     TKPidGetSem(uspid, ppptda)
USHORT  uspid;                          /* Process i.d. */
PPTDA   *ppptda;                        /* Place to put PTDA pointer */
{
        int     rc;                     /* Return code */

        do {                                    /* Loop to get semaphore */
            if((rc = TKPidToPTDA(uspid,ppptda)) != NO_ERROR)
                break;                          /* Get PTDA pointer */
        } while((rc = VMGetSem((*ppptda)->ptda_handle,
                VMGS_PSEUDO+VMGS_TAKESEM+VMGS_ERRBLOCK,NULL)) == ERROR_BLOCKED);
                                                /* Loop if we blocked */
        return(rc);                             /* Return */
}
