/* $Id: shinstance.c 3570 2022-07-09 14:42:02Z bird $ */
/** @file
 * The shell instance methods.
 */

/*
 * Copyright (c) 2007-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
 *
 *
 * This file is part of kBuild.
 *
 * kBuild is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * kBuild is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with kBuild; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */


/*********************************************************************************************************************************
*   Header Files                                                                                                                 *
*********************************************************************************************************************************/
#include <string.h>
#include <stdlib.h>
#ifdef _MSC_VER
# include <process.h>
#else
# include <unistd.h>
# include <pwd.h>
#endif
#include "shinstance.h"

#include "alias.h"
#include "error.h"
#include "input.h"
#include "jobs.h"
#include "memalloc.h"
#include "nodes.h"
#include "redir.h"
#include "shell.h"
#include "trap.h"

#if K_OS == K_OS_WINDOWS
# include <Windows.h>
# include "nt/nt_child_inject_standard_handles.h"
# ifdef SH_FORKED_MODE
extern pid_t shfork_do(shinstance *psh); /* shforkA-win.asm */
# endif
#endif


/*********************************************************************************************************************************
*   Defined Constants And Macros                                                                                                 *
*********************************************************************************************************************************/
#ifndef SH_FORKED_MODE
/** Used by sh__exit/sh_thread_wrapper for passing zero via longjmp.  */
# define SH_EXIT_ZERO    0x0d15ea5e
#endif


/*********************************************************************************************************************************
*   Global Variables                                                                                                             *
*********************************************************************************************************************************/
#ifndef SH_FORKED_MODE
/** Mutex serializing exec/spawn to prevent unwanted file inherting. */
shmtx               g_sh_exec_inherit_mtx;
/** Mutex protecting g_sh_sts_free. */
static shmtx        g_sh_sts_mtx;
/** List of free subshell status structure (saves CreateEvent calls).  */
static shsubshellstatus * volatile g_sh_sts_free = NULL;
#endif
/** The mutex protecting the the globals and some shell instance members (sigs). */
static shmtx        g_sh_mtx;
/** The root shell instance. */
static shinstance  *g_sh_root;
/** The first shell instance. */
static shinstance  *g_sh_head;
/** The last shell instance. */
static shinstance  *g_sh_tail;
/** The number of shells. */
static int volatile g_num_shells;
/* Statistics: Number of subshells spawned. */
static KU64             g_stat_subshells = 0;
/* Statistics: Number of program exec'ed. */
static KU64 volatile    g_stat_execs = 0;
#if K_OS == K_OS_WINDOWS
/* Statistics: Number of serialized exec calls. */
static KU64 volatile    g_stat_execs_serialized = 0;
#endif
/** Per signal state for determining a common denominator.
 * @remarks defaults and unmasked actions aren't counted. */
struct shsigstate
{
    /** The current signal action. */
#ifndef _MSC_VER
    struct sigaction sa;
#else
    struct
    {
        void      (*sa_handler)(int);
        int         sa_flags;
        shsigset_t  sa_mask;
    } sa;
#endif
    /** The number of restarts (siginterrupt / SA_RESTART). */
    int num_restart;
    /** The number of ignore handlers. */
    int num_ignore;
    /** The number of specific handlers. */
    int num_specific;
    /** The number of threads masking it. */
    int num_masked;
}                   g_sig_state[NSIG];

/*********************************************************************************************************************************
*   Internal Functions                                                                                                           *
*********************************************************************************************************************************/
#ifndef SH_FORKED_MODE
static void shsubshellstatus_signal_and_release(shinstance *psh, int iExit);
#endif



int shmtx_init(shmtx *pmtx)
{
#if K_OS == K_OS_WINDOWS
    typedef int mtxsizecheck[sizeof(CRITICAL_SECTION) + sizeof(KU64) <= sizeof(*pmtx) ? 2 : 0];
    InitializeCriticalSection((CRITICAL_SECTION *)pmtx);
#else
    pmtx->b[0] = 0;
#endif
    pmtx->au64[SHMTX_MAGIC_IDX] = SHMTX_MAGIC;
    return 0;
}

/**
 * Safe to call more than once.
 */
void shmtx_delete(shmtx *pmtx)
{
    if (pmtx->au64[SHMTX_MAGIC_IDX] != SHMTX_MAGIC)
    {
#if K_OS == K_OS_WINDOWS
        DeleteCriticalSection((CRITICAL_SECTION *)pmtx);
#else
        pmtx->b[0] = 0;
#endif
        pmtx->au64[SHMTX_MAGIC_IDX] = ~SHMTX_MAGIC;
    }
}

void shmtx_enter(shmtx *pmtx, shmtxtmp *ptmp)
{
#if K_OS == K_OS_WINDOWS
    EnterCriticalSection((CRITICAL_SECTION *)pmtx);
    ptmp->i = 0x42;
#else
    pmtx->b[0] = 0;
    ptmp->i = 0;
#endif
}

void shmtx_leave(shmtx *pmtx, shmtxtmp *ptmp)
{
#if K_OS == K_OS_WINDOWS
    kHlpAssert(ptmp->i == 0x42);
    LeaveCriticalSection((CRITICAL_SECTION *)pmtx);
    ptmp->i = 0x21;
#else
    pmtx->b[0] = 0;
    ptmp->i = 432;
#endif
}

/**
 * Initialize globals in shinstance.c.
 *
 * Called when creating the rootshell and on windows after forking.
 */
void sh_init_globals(void)
{
    kHlpAssert(g_sh_mtx.au64[SHMTX_MAGIC_IDX] != SHMTX_MAGIC);
    shmtx_init(&g_sh_mtx);
#ifndef SH_FORKED_MODE
    shmtx_init(&g_sh_exec_inherit_mtx);
    shmtx_init(&g_sh_sts_mtx);
#endif
}


/**
 * Links the shell instance.
 *
 * @param   psh     The shell.
 */
static void sh_int_link(shinstance *psh)
{
    shmtxtmp tmp;
    shmtx_enter(&g_sh_mtx, &tmp);

    if (psh->rootshell)
        g_sh_root = psh;
    else
        g_stat_subshells++;

    psh->next = NULL;
    psh->prev = g_sh_tail;
    if (g_sh_tail)
        g_sh_tail->next = psh;
    else
        g_sh_tail = g_sh_head = psh;
    g_sh_tail = psh;

    g_num_shells++;

    psh->linked = 1;

    shmtx_leave(&g_sh_mtx, &tmp);
}

/**
 * Unlink the shell instance.
 *
 * @param   psh     The shell.
 */
static void sh_int_unlink(shinstance *psh)
{
    if (psh->linked)
    {
        shinstance *pshcur;
        shmtxtmp tmp;
        shmtx_enter(&g_sh_mtx, &tmp);

        g_num_shells--;

        if (g_sh_tail == psh)
            g_sh_tail = psh->prev;
        else
            psh->next->prev = psh->prev;

        if (g_sh_head == psh)
            g_sh_head = psh->next;
        else
            psh->prev->next = psh->next;

        if (g_sh_root == psh)
            g_sh_root = NULL;

        /* Orphan children: */
        for (pshcur = g_sh_head; pshcur; pshcur = pshcur->next)
            if (pshcur->parent == psh)
                pshcur->parent = NULL;

        shmtx_leave(&g_sh_mtx, &tmp);
    }
}

/**
 * Frees a string vector like environ or argv.
 *
 * @param   psh     The shell to associate the deallocations with.
 * @param   vecp    Pointer to the vector pointer.
 */
static void sh_free_string_vector(shinstance *psh, char ***vecp)
{
    char **vec = *vecp;
    if (vec)
    {
        char *str;
        size_t i = 0;
        while ((str = vec[i]) != NULL)
        {
            sh_free(psh, str);
            vec[i] = NULL;
            i++;
        }

        sh_free(psh, vec);
        *vecp = NULL;
    }
}


/**
 * Destroys the shell instance.
 *
 * This will work on partially initialized instances (because I'm lazy).
 *
 * @param   psh     The shell instance to be destroyed.
 * @note    invalidate thread arguments.
 */
static void sh_destroy(shinstance *psh)
{
    unsigned left, i;

    INTOFF;

    sh_int_unlink(psh);

    /* shinstance stuff: */
    shfile_uninit(&psh->fdtab, psh->tracefd);
    sh_free_string_vector(psh, &psh->shenviron);
    sh_free(psh, psh->children);
    psh->children = NULL;
#ifndef SH_FORKED_MODE
    /** @todo children. */
    sh_free(psh, psh->threadarg);
    psh->threadarg = NULL;
    kHlpAssert(!psh->subshellstatus);
    if (psh->subshellstatus)
    {
        shsubshellstatus_signal_and_release(psh, psh->exitstatus);
        psh->subshellstatus = NULL;
    }
#endif

    /* alias.c */
    left = psh->aliases;
    if (left > 0)
        for (i = 0; i < K_ELEMENTS(psh->atab); i++)
        {
            struct alias *cur = psh->atab[i];
            if (cur)
            {
                do
                {
                    struct alias *next = cur->next;
                    sh_free(psh, cur->val);
                    sh_free(psh, cur->name);
                    sh_free(psh, cur);
                    cur = next;
                    left--;
                } while (cur);
                psh->atab[i] = NULL;
                if (!left)
                    break;
            }
        }

    /* cd.c */
    sh_free(psh, psh->curdir);
    psh->curdir = NULL;
    sh_free(psh, psh->prevdir);
    psh->prevdir = NULL;
    psh->cdcomppath = NULL; /* stalloc */

    /* eval.h */
    if (psh->commandnamemalloc)
        sh_free(psh, psh->commandname);
    psh->commandname = NULL;
    psh->cmdenviron = NULL;

    /* expand.c */
    if (psh->ifsfirst.next)
    {
        struct ifsregion *ifsrgn = psh->ifsfirst.next;
        psh->ifsfirst.next = NULL;
        do
        {
            struct ifsregion *next = ifsrgn->next;
            sh_free(psh, ifsrgn);
            ifsrgn = next;
        } while (ifsrgn);
    }
    psh->ifslastp = NULL;
    sh_free(psh, psh->expdir);
    psh->expdir = NULL;

    /* exec.h/exec.c */
    psh->pathopt = NULL;
    for (i = 0; i < CMDTABLESIZE; i++)
    {
        struct tblentry *cur = psh->cmdtable[i];
        if (cur)
        {
            do
            {
                struct tblentry *next = cur->next;
                if (cur->cmdtype == CMDFUNCTION)
                {
                    freefunc(psh, cur->param.func);
                    cur->param.func = NULL;
                }
                sh_free(psh, cur);
                cur = next;
            } while (cur);
            psh->cmdtable[i] = NULL;
        }
    }

    /* input.h/c */
    if (psh->parsefile != NULL)
    {
        popallfiles(psh);
        while (psh->basepf.strpush)
            popstring(psh);
    }

    /* jobs.h/c */
    if (psh->jobtab)
    {
        int j = psh->njobs;
        while (j-- > 0)
            if (psh->jobtab[j].used && psh->jobtab[j].ps != &psh->jobtab[j].ps0)
            {
                sh_free(psh, psh->jobtab[j].ps);
                psh->jobtab[j].ps = &psh->jobtab[j].ps0;
            }
        sh_free(psh, psh->jobtab);
        psh->jobtab = NULL;
        psh->njobs = 0;
    }

    /* myhistedit.h */
#ifndef SMALL
# error FIXME
    History            *hist;
    EditLine           *el;
#endif

    /* output.h */
    if (psh->output.buf != NULL)
    {
        ckfree(psh, psh->output.buf);
        psh->output.buf = NULL;
    }
    if (psh->errout.buf != NULL)
    {
        ckfree(psh, psh->errout.buf);
        psh->errout.buf = NULL;
    }
    if (psh->memout.buf != NULL)
    {
        ckfree(psh, psh->memout.buf);
        psh->memout.buf = NULL;
    }

    /* options.h */
    if (psh->arg0malloc)
    {
        sh_free(psh, psh->arg0);
        psh->arg0 = NULL;
    }
    if (psh->shellparam.malloc)
        sh_free_string_vector(psh, &psh->shellparam.p);
    sh_free_string_vector(psh, &psh->orgargv);
    psh->argptr = NULL;
    psh->minusc = NULL;

    /* redir.c */
    if (psh->redirlist)
    {
        struct redirtab *redir = psh->redirlist;
        psh->redirlist = NULL;
        do
        {
            struct redirtab *next = redir->next;
            sh_free(psh, redir);
            redir = next;
        } while (redir);
    }
    psh->expfnames = NULL; /* stack alloc */

    /* trap.c */
    for (i = 0; i < K_ELEMENTS(psh->trap); i++)
        if (!psh->trap[i])
        { /* likely */ }
        else
        {
            sh_free(psh, psh->trap[i]);
            psh->trap[i] = NULL;
        }

    /* var.h */
    if (psh->localvars)
    {
        struct localvar *lvar = psh->localvars;
        psh->localvars = NULL;
        do
        {
            struct localvar *next = lvar->next;
            if (!(lvar->flags & VTEXTFIXED))
                sh_free(psh, lvar->text);
            sh_free(psh, lvar);
            lvar = next;
        } while (lvar);
    }

    for (i = 0; i < K_ELEMENTS(psh->vartab); i++)
    {
        struct var *var = psh->vartab[i];
        if (!var)
        { /* likely */ }
        else
        {
            psh->vartab[i] = NULL;
            do
            {
                struct var *next = var->next;
                if (!(var->flags & (VTEXTFIXED | VSTACK)))
                    sh_free(psh, var->text);
                if (!(var->flags & (VSTRFIXED | VSTRFIXED2)))
                    sh_free(psh, var);
                var = next;
            } while (var);
        }
    }

    /*
     * memalloc.c: Make sure we've gotten rid of all the stack memory.
     */
    if (psh->stackp != &psh->stackbase && psh->stackp)
    {
        struct stack_block *stackp = psh->stackp;
        do
        {
            psh->stackp = stackp->prev;
            sh_free(psh, stackp);
        } while ((stackp = psh->stackp) != &psh->stackbase && stackp);
    }
#ifdef KASH_SEPARATE_PARSER_ALLOCATOR //bp msvcr100!_wassert
    if (psh->pstack)
    {
        if (psh->pstacksize > 0)
            pstackpop(psh, 0);
        sh_free(psh, psh->pstack);
        psh->pstack = NULL;
    }
    sh_free(psh, psh->freepstack);
    psh->freepstack = NULL;
#endif
    psh->markp = NULL;

    /*
     * Finally get rid of tracefd and then free the shell:
     */
    shfile_uninit(&psh->fdtab, -1);

    memset(psh, 0, sizeof(*psh));
    sh_free(NULL, psh);
}

/**
 * Clones a string vector like environ or argv.
 *
 * @returns 0 on success, -1 and errno on failure.
 * @param   psh     The shell to associate the allocations with.
 * @param   dstp    Where to store the clone.
 * @param   src     The vector to be cloned.
 */
static int sh_clone_string_vector(shinstance *psh, char ***dstp, char **src)
{
    char **dst;
    size_t items;

    /* count first */
    items = 0;
    while (src[items])
        items++;

    /* alloc clone array. */
    *dstp = dst = sh_malloc(psh, sizeof(*dst) * (items + 1));
    if (!dst)
        return -1;

    /* copy the items */
    dst[items] = NULL;
    while (items-- > 0)
    {
        dst[items] = sh_strdup(psh, src[items]);
        if (!dst[items])
        {
            /* allocation error, clean up. */
            while (dst[++items])
                sh_free(psh, dst[items]);
            sh_free(psh, dst);
            errno = ENOMEM;
            return -1;
        }
    }

    return 0;
}

/**
 * Creates a shell instance, caller must link it.
 *
 * @param   inherit     The shell to inherit from, or NULL if root.
 * @param   argv        The argument vector.
 * @param   envp        The environment vector.
 * @param   parentfdtab File table to inherit from, NULL if root.
 *
 * @returns pointer to root shell on success, NULL on failure.
 */
static shinstance *sh_create_shell_common(char **argv, char **envp, shfdtab *parentfdtab)
{
    shinstance *psh;

    /*
     * The allocations.
     */
    psh = sh_calloc(NULL, sizeof(*psh), 1);
    if (psh)
    {
        /* Init it enough for sh_destroy() to not get upset: */
        /* ... */

        /* Call the basic initializers. */
        if (    !sh_clone_string_vector(psh, &psh->shenviron, envp)
            &&  !sh_clone_string_vector(psh, &psh->orgargv, argv)
            &&  !shfile_init(&psh->fdtab, parentfdtab))
        {
            unsigned i;

            /*
             * The special stuff.
             */
#ifdef _MSC_VER
            psh->pgid = psh->pid = _getpid();
#else
            psh->pid = getpid();
            psh->pgid = getpgid(0);
#endif

            /*sh_sigemptyset(&psh->sigrestartset);*/
            for (i = 0; i < K_ELEMENTS(psh->sigactions); i++)
                psh->sigactions[i].sh_handler = SH_SIG_UNK;
#if defined(_MSC_VER)
            sh_sigemptyset(&psh->sigmask);
#else
            sigprocmask(SIG_SETMASK, NULL, &psh->sigmask);
#endif

            /*
             * State initialization.
             */
            /* cd.c */
            psh->getpwd_first = 1;

            /* exec */
            psh->builtinloc = -1;

            /* memalloc.c */
            psh->stacknleft = MINSIZE;
            psh->herefd = -1;
            psh->stackp = &psh->stackbase;
            psh->stacknxt = psh->stackbase.space;

            /* input.c */
            psh->plinno = 1;
            psh->init_editline = 0;
            psh->parsefile = &psh->basepf;

            /* output.c */
            psh->output.bufsize = OUTBUFSIZ;
            psh->output.fd = 1;
            psh->output.psh = psh;
            psh->errout.bufsize = 100;
            psh->errout.fd = 2;
            psh->errout.psh = psh;
            psh->memout.fd = MEM_OUT;
            psh->memout.psh = psh;
            psh->out1 = &psh->output;
            psh->out2 = &psh->errout;

            /* jobs.c */
            psh->backgndpid = -1;
#if JOBS
            psh->curjob = -1;
#else
# error asdf
#endif
            psh->ttyfd = -1;

            /* show.c */
            psh->tracefd = -1;
            return psh;
        }

        sh_destroy(psh);
    }
    return NULL;
}

/**
 * Creates the root shell instance.
 *
 * @param   argv        The argument vector.
 * @param   envp        The environment vector.
 *
 * @returns pointer to root shell on success, NULL on failure.
 */
shinstance *sh_create_root_shell(char **argv, char **envp)
{
    shinstance *psh;

    sh_init_globals();

    psh = sh_create_shell_common(argv, envp, NULL /*parentfdtab*/);
    if (psh)
    {
        sh_int_link(psh);
        return psh;
    }
    return NULL;
}

#ifndef SH_FORKED_MODE

/**
 * Does the inherting from the parent shell instance.
 */
static void sh_inherit_from_parent(shinstance *psh, shinstance *inherit)
{
    /*
     * Make sure we can use TRACE/TRACE2 for logging here.
     */
#ifdef DEBUG
    /* show.c */
    psh->tracefd = inherit->tracefd;
    /* options.c */
    debug(psh) = debug(inherit);
#endif

    /*
     * Do the rest of the inheriting.
     */
    psh->parent = inherit;
    psh->pgid = inherit->pgid;

    psh->sigmask = psh->sigmask;
    /** @todo sigactions?   */
    /// @todo suppressint?

    /* alises: */
    subshellinitalias(psh, inherit);

    /* cd.c */
    psh->getpwd_first = inherit->getpwd_first;
    if (inherit->curdir)
        psh->curdir = savestr(psh, inherit->curdir);
    if (inherit->prevdir)
        psh->prevdir = savestr(psh, inherit->prevdir);

    /* eval.h */
    /* psh->commandname - see subshellinitoptions */
    psh->exitstatus  = inherit->exitstatus;          /// @todo ??
    psh->back_exitstatus = inherit->back_exitstatus; /// @todo ??
    psh->funcnest = inherit->funcnest;
    psh->evalskip = inherit->evalskip;               /// @todo ??
    psh->skipcount = inherit->skipcount;             /// @todo ??

    /* exec.c */
    subshellinitexec(psh, inherit);

    /* input.h/input.c - only for the parser and anyway forkchild calls closescript(). */

    /* jobs.h - should backgndpid be -1 in subshells? */

    /* jobs.c -    */
    psh->jobctl = inherit->jobctl;  /// @todo ??
    psh->initialpgrp = inherit->initialpgrp;
    psh->ttyfd = inherit->ttyfd;
    /** @todo copy jobtab so the 'jobs' command can be run in a subshell.
     *  Better, make it follow the parent chain and skip the copying.  Will
     *  require some kind of job locking. */

    /* mail.c - nothing (for now at least) */

    /* main.h */
    psh->rootpid = inherit->rootpid;
    psh->psh_rootshell = inherit->psh_rootshell;

    /* memalloc.h / memalloc.c - nothing. */

    /* myhistedit.h  */ /** @todo copy history? Do we need to care? */

    /* output.h */ /** @todo not sure this is possible/relevant for subshells */
    psh->output.fd = inherit->output.fd;
    psh->errout.fd = inherit->errout.fd;
    if (inherit->out1 == &inherit->memout)
        psh->out1 = &psh->memout;
    if (inherit->out2 == &inherit->memout)
        psh->out2 = &psh->memout;

    /* options.h */
    subshellinitoptions(psh, inherit);

    /* parse.h/parse.c */
    psh->whichprompt = inherit->whichprompt;
    /* tokpushback, doprompt and needprompt shouldn't really matter, parsecmd resets thems. */
    /* The rest are internal to the parser, as I see them, and can be ignored. */

    /* redir.c */
    subshellinitredir(psh, inherit);

    /* trap.h / trap.c */ /** @todo we don't carry pendingsigs to the subshell, right? */
    subshellinittrap(psh, inherit);

    /* var.h */
    subshellinitvar(psh, inherit);
}

/**
 * Creates a child shell instance.
 *
 * @param   inherit     The shell to inherit from.
 *
 * @returns pointer to root shell on success, NULL on failure.
 */
shinstance *sh_create_child_shell(shinstance *inherit)
{
    shinstance *psh = sh_create_shell_common(inherit->orgargv, inherit->shenviron, &inherit->fdtab);
    if (psh)
    {
        /* Fake a pid for the child: */
        static unsigned volatile s_cShells = 0;
        int const iSubShell = ++s_cShells;
        psh->pid = SHPID_MAKE(SHPID_GET_PID(inherit->pid), iSubShell);

        sh_inherit_from_parent(psh, inherit);

        /* link it */
        sh_int_link(psh);
        return psh;
    }
    return NULL;
}

#endif /* !SH_FORKED_MODE */

/** getenv() */
char *sh_getenv(shinstance *psh, const char *var)
{
    size_t  len;
    int     i = 0;

    if (!var)
        return NULL;

    len = strlen(var);
    i = 0;
    while (psh->shenviron[i])
    {
        const char *item = psh->shenviron[i];
        if (    !strncmp(item, var, len)
            &&  item[len] == '=')
            return (char *)item + len + 1;
        i++;
    }

    return NULL;
}

char **sh_environ(shinstance *psh)
{
    return psh->shenviron;
}

const char *sh_gethomedir(shinstance *psh, const char *user)
{
    const char *ret = NULL;

#ifdef _MSC_VER
    ret = sh_getenv(psh, "HOME");
    if (!ret)
        ret = sh_getenv(psh, "USERPROFILE");
#else
    struct passwd *pwd = getpwnam(user); /** @todo use getpwdnam_r */
    (void)psh;
    ret = pwd ? pwd->pw_dir : NULL;
#endif

    return ret;
}

/**
 * Lazy initialization of a signal state, globally.
 *
 * @param   psh         The shell doing the lazy work.
 * @param   signo       The signal (valid).
 */
static void sh_int_lazy_init_sigaction(shinstance *psh, int signo)
{
    if (psh->sigactions[signo].sh_handler == SH_SIG_UNK)
    {
        shmtxtmp tmp;
        shmtx_enter(&g_sh_mtx, &tmp);

        if (psh->sigactions[signo].sh_handler == SH_SIG_UNK)
        {
            shsigaction_t shold;
            shinstance *cur;
#ifndef _MSC_VER
            struct sigaction old;
            if (!sigaction(signo, NULL, &old))
            {
                /* convert */
                shold.sh_flags = old.sa_flags;
                shold.sh_mask = old.sa_mask;
                if (old.sa_handler == SIG_DFL)
                    shold.sh_handler = SH_SIG_DFL;
                else
                {
                    kHlpAssert(old.sa_handler == SIG_IGN);
                    shold.sh_handler = SH_SIG_IGN;
                }
            }
            else
#endif
            {
                /* fake */
#ifndef _MSC_VER
                kHlpAssert(0);
                old.sa_handler = SIG_DFL;
                old.sa_flags = 0;
                sigemptyset(&shold.sh_mask);
                sigaddset(&shold.sh_mask, signo);
#endif
                shold.sh_flags = 0;
                sh_sigemptyset(&shold.sh_mask);
                sh_sigaddset(&shold.sh_mask, signo);
                shold.sh_handler = SH_SIG_DFL;
            }

            /* update globals */
#ifndef _MSC_VER
            g_sig_state[signo].sa = old;
#else
            g_sig_state[signo].sa.sa_handler = SIG_DFL;
            g_sig_state[signo].sa.sa_flags = 0;
            g_sig_state[signo].sa.sa_mask = shold.sh_mask;
#endif
            TRACE2((psh, "sh_int_lazy_init_sigaction: signo=%d:%s sa_handler=%p sa_flags=%#x\n",
                    signo, sys_signame[signo], g_sig_state[signo].sa.sa_handler, g_sig_state[signo].sa.sa_flags));

            /* update all shells */
            for (cur = g_sh_head; cur; cur = cur->next)
            {
                kHlpAssert(cur->sigactions[signo].sh_handler == SH_SIG_UNK);
                cur->sigactions[signo] = shold;
            }
        }

        shmtx_leave(&g_sh_mtx, &tmp);
    }
}

/**
 * Perform the default signal action on the shell.
 *
 * @param   psh         The shell instance.
 * @param   signo       The signal.
 */
static void sh_sig_do_default(shinstance *psh, int signo)
{
    /** @todo */
}

/**
 * Deliver a signal to a shell.
 *
 * @param   psh         The shell instance.
 * @param   pshDst      The shell instance to signal.
 * @param   signo       The signal.
 * @param   locked      Whether we're owning the lock or not.
 */
static void sh_sig_do_signal(shinstance *psh, shinstance *pshDst, int signo, int locked)
{
    shsig_t pfn = pshDst->sigactions[signo].sh_handler;
    if (pfn == SH_SIG_UNK)
    {
        sh_int_lazy_init_sigaction(pshDst, signo);
        pfn = pshDst->sigactions[signo].sh_handler;
    }

    if (pfn == SH_SIG_DFL)
        sh_sig_do_default(pshDst, signo);
    else if (pfn == SH_SIG_IGN)
        /* ignore it */;
    else
    {
        kHlpAssert(pfn != SH_SIG_ERR);
        pfn(pshDst, signo);
    }
    (void)locked;
}

/**
 * Handler for external signals.
 *
 * @param   signo       The signal.
 */
static void sh_sig_common_handler(int signo)
{
    shinstance *psh;

/*    fprintf(stderr, "sh_sig_common_handler: signo=%d:%s\n", signo, sys_signame[signo]); */

#ifdef _MSC_VER
    /* We're treating SIGBREAK as if it was SIGINT for now: */
    if (signo == SIGBREAK)
        signo = SIGINT;
#endif

    /*
     * No need to take locks if there is only one shell.
     * Since this will be the initial case, just avoid the deadlock
     * hell for a litte while...
     */
    if (g_num_shells <= 1)
    {
        psh = g_sh_head;
        if (psh)
            sh_sig_do_signal(NULL, psh, signo, 0 /* no lock */);
    }
    else
    {
        shmtxtmp tmp;
        shmtx_enter(&g_sh_mtx, &tmp);

        /** @todo signal focus chain or something? Atm there will only be one shell,
         *        so it's not really important until we go threaded for real... */
        psh = g_sh_tail;
        while (psh != NULL)
        {
            sh_sig_do_signal(NULL, psh, signo, 1 /* locked */);
            psh = psh->prev;
        }

        shmtx_leave(&g_sh_mtx, &tmp);
    }
}

int sh_sigaction(shinstance *psh, int signo, const struct shsigaction *newp, struct shsigaction *oldp)
{
    if (newp)
        TRACE2((psh, "sh_sigaction: signo=%d:%s newp=%p:{.sh_handler=%p, .sh_flags=%#x} oldp=%p\n",
                signo, sys_signame[signo], newp, newp->sh_handler, newp->sh_flags, oldp));
    else
        TRACE2((psh, "sh_sigaction: signo=%d:%s newp=NULL oldp=%p\n", signo, sys_signame[signo], oldp));

    /*
     * Input validation.
     */
    if (signo >= NSIG || signo <= 0)
    {
        errno = EINVAL;
        return -1;
    }

    /*
     * Make sure our data is correct.
     */
    sh_int_lazy_init_sigaction(psh, signo);

    /*
     * Get the old one if requested.
     */
    if (oldp)
        *oldp = psh->sigactions[signo];

    /*
     * Set the new one if it has changed.
     *
     * This will be attempted coordinated with the other signal handlers so
     * that we can arrive at a common denominator.
     */
    if (    newp
        &&  memcmp(&psh->sigactions[signo], newp, sizeof(*newp)))
    {
        shmtxtmp tmp;
        shmtx_enter(&g_sh_mtx, &tmp);

        /* Undo the accounting for the current entry. */
        if (psh->sigactions[signo].sh_handler == SH_SIG_IGN)
            g_sig_state[signo].num_ignore--;
        else if (psh->sigactions[signo].sh_handler != SH_SIG_DFL)
            g_sig_state[signo].num_specific--;
        if (psh->sigactions[signo].sh_flags & SA_RESTART)
            g_sig_state[signo].num_restart--;

        /* Set the new entry. */
        psh->sigactions[signo] = *newp;

        /* Add the bits for the new action entry. */
        if (psh->sigactions[signo].sh_handler == SH_SIG_IGN)
            g_sig_state[signo].num_ignore++;
        else if (psh->sigactions[signo].sh_handler != SH_SIG_DFL)
            g_sig_state[signo].num_specific++;
        if (psh->sigactions[signo].sh_flags & SA_RESTART)
            g_sig_state[signo].num_restart++;

        /*
         * Calc new common action.
         *
         * This is quit a bit ASSUMPTIVE about the limited use. We will not
         * bother synching the mask, and we pretend to care about SA_RESTART.
         * The only thing we really actually care about is the sh_handler.
         *
         * On second though, it's possible we should just tie this to the root
         * shell since it only really applies to external signal ...
         */
        if (    g_sig_state[signo].num_specific
            ||  g_sig_state[signo].num_ignore != g_num_shells)
            g_sig_state[signo].sa.sa_handler = sh_sig_common_handler;
        else if (g_sig_state[signo].num_ignore)
            g_sig_state[signo].sa.sa_handler = SIG_IGN;
        else
            g_sig_state[signo].sa.sa_handler = SIG_DFL;
        g_sig_state[signo].sa.sa_flags = psh->sigactions[signo].sh_flags & SA_RESTART;

        TRACE2((psh, "sh_sigaction: setting signo=%d:%s to {.sa_handler=%p, .sa_flags=%#x}\n",
                signo, sys_signame[signo], g_sig_state[signo].sa.sa_handler, g_sig_state[signo].sa.sa_flags));
#ifdef _MSC_VER
        /* Throw SIGBREAK in with SIGINT for now. */
        if (signo == SIGINT)
            signal(SIGBREAK, g_sig_state[signo].sa.sa_handler);

        if (signal(signo, g_sig_state[signo].sa.sa_handler) == SIG_ERR)
        {
            TRACE2((psh, "sh_sigaction: SIG_ERR, errno=%d signo=%d\n", errno, signo));
            if (   signo != SIGHUP   /* whatever */
                && signo != SIGQUIT
                && signo != SIGPIPE
                && signo != SIGTTIN
                && signo != SIGTSTP
                && signo != SIGTTOU
                && signo != SIGCONT)
                kHlpAssert(0);
        }
#else
        if (sigaction(signo, &g_sig_state[signo].sa, NULL))
            kHlpAssert(0);
#endif

        shmtx_leave(&g_sh_mtx, &tmp);
    }

    return 0;
}

shsig_t sh_signal(shinstance *psh, int signo, shsig_t handler)
{
    shsigaction_t sa;
    shsig_t ret;

    /*
     * Implementation using sh_sigaction.
     */
    if (sh_sigaction(psh, signo, NULL, &sa))
        return SH_SIG_ERR;

    ret = sa.sh_handler;
    sa.sh_flags &= SA_RESTART;
    sa.sh_handler = handler;
    sh_sigemptyset(&sa.sh_mask);
    sh_sigaddset(&sa.sh_mask, signo); /* ?? */
    if (sh_sigaction(psh, signo, &sa, NULL))
        return SH_SIG_ERR;

    return ret;
}

int sh_siginterrupt(shinstance *psh, int signo, int interrupt)
{
    shsigaction_t sa;
    int oldflags = 0;

    /*
     * Implementation using sh_sigaction.
     */
    if (sh_sigaction(psh, signo, NULL, &sa))
        return -1;
    oldflags = sa.sh_flags;
    if (interrupt)
        sa.sh_flags &= ~SA_RESTART;
    else
        sa.sh_flags |= ~SA_RESTART;
    if (!((oldflags ^ sa.sh_flags) & SA_RESTART))
        return 0; /* unchanged. */

    return sh_sigaction(psh, signo, &sa, NULL);
}

void sh_sigemptyset(shsigset_t *setp)
{
    memset(setp, 0, sizeof(*setp));
}

void sh_sigfillset(shsigset_t *setp)
{
    memset(setp, 0xff, sizeof(*setp));
}

void sh_sigaddset(shsigset_t *setp, int signo)
{
#ifdef _MSC_VER
    *setp |= 1U << signo;
#else
    sigaddset(setp, signo);
#endif
}

void sh_sigdelset(shsigset_t *setp, int signo)
{
#ifdef _MSC_VER
    *setp &= ~(1U << signo);
#else
    sigdelset(setp, signo);
#endif
}

int sh_sigismember(shsigset_t const *setp, int signo)
{
#ifdef _MSC_VER
    return !!(*setp & (1U << signo));
#else
    return !!sigismember(setp, signo);
#endif
}

int sh_sigprocmask(shinstance *psh, int operation, shsigset_t const *newp, shsigset_t *oldp)
{
    int rc;

    if (    operation != SIG_BLOCK
        &&  operation != SIG_UNBLOCK
        &&  operation != SIG_SETMASK)
    {
        errno = EINVAL;
        return -1;
    }

#if defined(SH_FORKED_MODE) && !defined(_MSC_VER)
    rc = sigprocmask(operation, newp, oldp);
    if (!rc && newp)
        psh->sigmask = *newp;

#else
    if (oldp)
        *oldp = psh->sigmask;
    if (newp)
    {
        /* calc the new mask */
        shsigset_t mask = psh->sigmask;
        switch (operation)
        {
            case SIG_BLOCK:
                for (rc = 0; rc < NSIG; rc++)
                    if (sh_sigismember(newp, rc))
                        sh_sigaddset(&mask, rc);
                break;
            case SIG_UNBLOCK:
                for (rc = 0; rc < NSIG; rc++)
                    if (sh_sigismember(newp, rc))
                        sh_sigdelset(&mask, rc);
                break;
            case SIG_SETMASK:
                mask = *newp;
                break;
        }

# if defined(_MSC_VER)
        rc = 0;
# else
        rc = sigprocmask(operation, &mask, NULL);
        if (!rc)
# endif
            psh->sigmask = mask;
    }

#endif
    return rc;
}

SH_NORETURN_1 void sh_abort(shinstance *psh)
{
    shsigset_t set;
    TRACE2((psh, "sh_abort\n"));

    /* block other async signals */
    sh_sigfillset(&set);
    sh_sigdelset(&set, SIGABRT);
    sh_sigprocmask(psh, SIG_SETMASK, &set, NULL);

    sh_sig_do_signal(psh, psh, SIGABRT, 0 /* no lock */);

    /** @todo die in a nicer manner. */
    *(char *)1 = 3;

    TRACE2((psh, "sh_abort returns!\n"));
    (void)psh;
    abort();
}

void sh_raise_sigint(shinstance *psh)
{
    TRACE2((psh, "sh_raise(SIGINT)\n"));

    sh_sig_do_signal(psh, psh, SIGINT, 0 /* no lock */);

    TRACE2((psh, "sh_raise(SIGINT) returns\n"));
}

int sh_kill(shinstance *psh, shpid pid, int signo)
{
    shinstance *pshDst;
    shmtxtmp tmp;
    int rc;

    /*
     * Self or any of the subshells?
     */
    shmtx_enter(&g_sh_mtx, &tmp);

    pshDst = g_sh_tail;
    while (pshDst != NULL)
    {
        if (pshDst->pid == pid)
        {
            TRACE2((psh, "sh_kill(%" SHPID_PRI ", %d): pshDst=%p\n", pid, signo, pshDst));
            sh_sig_do_signal(psh, pshDst, signo, 1 /* locked */);

            shmtx_leave(&g_sh_mtx, &tmp);
            return 0;
        }
        pshDst = pshDst->prev;
    }

    shmtx_leave(&g_sh_mtx, &tmp);

    /*
     * Some other process, call kill where possible
     */
#ifdef _MSC_VER
    errno = ENOSYS;
    rc = -1;
#elif defined(SH_FORKED_MODE)
/*    fprintf(stderr, "kill(%d, %d)\n", pid, signo);*/
    rc = kill(pid, signo);
#else
# error "PORT ME?"
#endif

    TRACE2((psh, "sh_kill(%d, %d) -> %d [%d]\n", pid, signo, rc, errno));
    return rc;
}

int sh_killpg(shinstance *psh, shpid pgid, int signo)
{
    shinstance *pshDst;
    shmtxtmp tmp;
    int rc;

    /*
     * Self or any of the subshells?
     */
    shmtx_enter(&g_sh_mtx, &tmp);

    pshDst = g_sh_tail;
    while (pshDst != NULL)
    {
        if (pshDst->pgid == pgid)
        {
            TRACE2((psh, "sh_killpg(%" SHPID_PRI ", %d): pshDst=%p\n", pgid, signo, pshDst));
            sh_sig_do_signal(psh, pshDst, signo, 1 /* locked */);

            shmtx_leave(&g_sh_mtx, &tmp);
            return 0;
        }
        pshDst = pshDst->prev;
    }

    shmtx_leave(&g_sh_mtx, &tmp);

#ifdef _MSC_VER
    errno = ENOSYS;
    rc = -1;
#elif defined(SH_FORKED_MODE)
    //fprintf(stderr, "killpg(%d, %d)\n", pgid, signo);
    rc = killpg(pgid, signo);
#else
# error "PORTME?"
#endif

    TRACE2((psh, "sh_killpg(%" SHPID_PRI ", %d) -> %d [%d]\n", pgid, signo, rc, errno));
    (void)psh;
    return rc;
}

clock_t sh_times(shinstance *psh, shtms *tmsp)
{
#ifdef _MSC_VER
    errno = ENOSYS;
    return (clock_t)-1;
#elif defined(SH_FORKED_MODE)
    (void)psh;
    return times(tmsp);
#else
# error "PORTME"
#endif
}

int sh_sysconf_clk_tck(void)
{
#ifdef _MSC_VER
    return CLK_TCK;
#else
    return sysconf(_SC_CLK_TCK);
#endif
}

#ifndef SH_FORKED_MODE

/**
 * Retains a reference to a subshell status structure.
 */
static unsigned shsubshellstatus_retain(shsubshellstatus *sts)
{
    unsigned refs = sh_atomic_dec(&sts->refs);
    kHlpAssert(refs > 1);
    kHlpAssert(refs < 16);
    return refs;
}

/**
 * Releases a reference to a subshell status structure.
 */
static unsigned shsubshellstatus_release(shinstance *psh, shsubshellstatus *sts)
{
    unsigned refs = sh_atomic_dec(&sts->refs);
    kHlpAssert(refs < ~(unsigned)0/4);
    if (refs == 0)
    {
        shmtxtmp tmp;
        shmtx_enter(&g_sh_sts_mtx, &tmp);
        sts->next = g_sh_sts_free;
        g_sh_sts_free = sts;
        shmtx_leave(&g_sh_sts_mtx, &tmp);
    }
    return refs;
}

/**
 * Creates a subshell status structure.
 */
static shsubshellstatus *shsubshellstatus_create(shinstance *psh, int refs)
{
    shsubshellstatus *sts;

    /* Check the free list: */
    if (g_sh_sts_free)
    {
        shmtxtmp tmp;
        shmtx_enter(&g_sh_sts_mtx, &tmp);
        sts = g_sh_sts_free;
        if (sts)
            g_sh_sts_free = sts->next;
        shmtx_leave(&g_sh_sts_mtx, &tmp);
    }
    else
        sts = NULL;
    if (sts)
    {
# if K_OS == K_OS_WINDOWS
        BOOL rc = ResetEvent((HANDLE)sts->towaiton);
        kHlpAssert(rc); K_NOREF(rc);
# endif
    }
    else
    {
        /* Create a new one: */
        sts = (shsubshellstatus *)sh_malloc(psh, sizeof(*sts));
        if (!sts)
            return NULL;
# if K_OS == K_OS_WINDOWS
        sts->towaiton = (void *)CreateEventW(NULL /*noinherit*/, TRUE /*fManualReset*/,
                                             FALSE /*fInitialState*/, NULL /*pszName*/);
        if (!sts->towaiton)
        {
            kHlpAssert(0);
            sh_free(psh, sts);
            return NULL;
        }
# endif
    }

    /* Initialize it: */
    sts->refs     = refs;
    sts->status   = 999999;
    sts->done     = 0;
    sts->next     = NULL;
# if K_OS == K_OS_WINDOWS
    sts->hThread  = 0;
# endif
    return sts;
}

/**
 * If we have a subshell status structure, signal and release it.
 */
static void shsubshellstatus_signal_and_release(shinstance *psh, int iExit)
{
    shsubshellstatus *sts = psh->subshellstatus;
    if (sts)
    {
        BOOL rc;
        HANDLE hThread;

        sts->status = W_EXITCODE(iExit, 0);
        sts->done   = K_TRUE;
        rc = SetEvent((HANDLE)sts->towaiton); kHlpAssert(rc); K_NOREF(rc);

        hThread = (HANDLE)sts->hThread;
        sts->hThread = 0;
        rc = CloseHandle(hThread); kHlpAssert(rc);

        shsubshellstatus_release(psh, sts);
        psh->subshellstatus = NULL;
    }
}


#endif /* !SH_FORKED_MODE */

/**
 * Adds a child to the shell
 *
 * @returns 0 on success, on failure -1 and errno set to ENOMEM.
 *
 * @param   psh         The shell instance.
 * @param   pid         The child pid.
 * @param   hChild      Windows child wait handle (process if sts is NULL).
 * @param   sts         Subshell status structure, NULL if progress.
 */
int sh_add_child(shinstance *psh, shpid pid, void *hChild, struct shsubshellstatus *sts)
{
    /* get a free table entry. */
    unsigned i = psh->num_children++;
    if (!(i % 32))
    {
        void *ptr = sh_realloc(psh, psh->children, sizeof(*psh->children) * (i + 32));
        if (!ptr)
        {
            psh->num_children--;
            errno = ENOMEM;
            return -1;
        }
        psh->children = ptr;
    }

    /* add it */
    psh->children[i].pid = pid;
#if K_OS == K_OS_WINDOWS
    psh->children[i].hChild = hChild;
#endif
#ifndef SH_FORKED_MODE
    psh->children[i].subshellstatus = sts;
#endif
    (void)hChild; (void)sts;
    return 0;
}

#ifdef SH_FORKED_MODE

pid_t sh_fork(shinstance *psh)
{
    pid_t pid;
    TRACE2((psh, "sh_fork\n"));

#if K_OS == K_OS_WINDOWS //&& defined(SH_FORKED_MODE)
    pid = shfork_do(psh);

#elif defined(SH_FORKED_MODE)
# ifdef _MSC_VER
    pid = -1;
    errno = ENOSYS;
# else
    pid = fork();
# endif

#else

#endif

    /* child: update the pid and zap the children array */
    if (!pid)
    {
# ifdef _MSC_VER
        psh->pid = _getpid();
# else
        psh->pid = getpid();
# endif
        psh->num_children = 0;
    }

    TRACE2((psh, "sh_fork -> %d [%d]\n", pid, errno));
    (void)psh;
    return pid;
}

#else  /* !SH_FORKED_MODE */

# ifdef _MSC_VER
/** Thread wrapper procedure. */
static unsigned __stdcall sh_thread_wrapper(void *user)
{
    shinstance * volatile volpsh = (shinstance *)user;
    shinstance *psh = (shinstance *)user;
    struct jmploc exitjmp;
    int iExit;

    /* Update the TID and PID (racing sh_thread_start) */
    DWORD tid = GetCurrentThreadId();
    shpid pid = GetCurrentProcessId();

    pid = SHPID_MAKE(pid, tid);
    psh->pid = pid;
    psh->tid = tid;

    /* Set the TLS entry before we try TRACE or TRACE2. */
    shthread_set_shell(psh);

    TRACE2((psh, "sh_thread_wrapper: enter\n"));
    if ((iExit = setjmp(exitjmp.loc)) == 0)
    {
        psh->exitjmp = &exitjmp;
        iExit = psh->thread(psh, psh->threadarg);
        TRACE2((psh, "sh_thread_wrapper: thread proc returns %d (%#x)\n", iExit, iExit));
    }
    else
    {
        psh = volpsh; /* paranoia */
        psh->exitjmp = NULL;
        TRACE2((psh, "sh_thread_wrapper: longjmp: iExit=%d (%#x)\n", iExit, iExit));
        if (iExit == SH_EXIT_ZERO)
            iExit = 0;
    }

    /* Signal parent. */
    shsubshellstatus_signal_and_release(psh, iExit);

    /* destroy the shell instance and exit the thread. */
    TRACE2((psh, "sh_thread_wrapper: quits - iExit=%d\n", iExit));
    sh_destroy(psh);
    shthread_set_shell(NULL);
    _endthreadex(iExit);
    return iExit;
}
# else
#  error "PORTME"
# endif

/**
 * Starts a sub-shell thread.
 */
shpid sh_thread_start(shinstance *pshparent, shinstance *pshchild, int (*thread)(shinstance *, void *), void *arg)
{
# ifdef _MSC_VER
    shpid pid;

    shsubshellstatus *sts = shsubshellstatus_create(pshparent, 2);
    pshchild->subshellstatus = sts;
    if (sts)
    {
        unsigned tid = 0;
        uintptr_t hThread;

        pshchild->thread    = thread;
        pshchild->threadarg = arg;

        hThread = _beginthreadex(NULL /*security*/, 0 /*stack_size*/, sh_thread_wrapper, pshchild, 0 /*initflags*/, &tid);
        sts->hThread = hThread;
        if (hThread != -1)
        {
            pid = SHPID_MAKE(SHPID_GET_PID(pshparent->pid), tid);
            pshchild->pid = pid;
            pshchild->tid = tid;

            if (sh_add_child(pshparent, pid, sts->towaiton, sts) == 0)
            {
                return pid;
            }

            shsubshellstatus_retain(sts);
            pid = -ENOMEM;
        }
        else
            pid = -errno;
        shsubshellstatus_release(pshparent, sts);
        shsubshellstatus_release(pshparent, sts);
    }
    else
        pid = -ENOMEM;
    return pid;

# else
#  error "PORTME"
# endif
}

#endif /* !SH_FORKED_MODE */

/** waitpid() */
shpid sh_waitpid(shinstance *psh, shpid pid, int *statusp, int flags)
{
    shpid       pidret;
#if K_OS == K_OS_WINDOWS //&& defined(SH_FORKED_MODE)
    DWORD       dwRet;
    HANDLE      hChild = INVALID_HANDLE_VALUE;
    unsigned    i;

    *statusp = 0;
    pidret = -1;
    if (pid != -1)
    {
        /*
         * A specific child, try look it up in the child process table
         * and wait for it.
         */
        for (i = 0; i < psh->num_children; i++)
            if (psh->children[i].pid == pid)
                break;
        if (i < psh->num_children)
        {
            dwRet = WaitForSingleObject(psh->children[i].hChild,
                                        flags & WNOHANG ? 0 : INFINITE);
            if (dwRet == WAIT_OBJECT_0)
                hChild = psh->children[i].hChild;
            else if (dwRet == WAIT_TIMEOUT)
            {
                i = ~0; /* don't try close anything */
                pidret = 0;
            }
            else
                errno = ECHILD;
        }
        else
            errno = ECHILD;
    }
    else if (psh->num_children <= MAXIMUM_WAIT_OBJECTS)
    {
        HANDLE ahChildren[MAXIMUM_WAIT_OBJECTS];
        for (i = 0; i < psh->num_children; i++)
            ahChildren[i] = psh->children[i].hChild;
        dwRet = WaitForMultipleObjects(psh->num_children, &ahChildren[0],
                                       FALSE,
                                       flags & WNOHANG ? 0 : INFINITE);
        i = dwRet - WAIT_OBJECT_0;
        if (i < psh->num_children)
        {
            hChild = psh->children[i].hChild;
        }
        else if (dwRet == WAIT_TIMEOUT)
        {
            i = ~0; /* don't try close anything */
            pidret = 0;
        }
        else
        {
            i = ~0; /* don't try close anything */
            errno = EINVAL;
        }
    }
    else
    {
        fprintf(stderr, "panic! too many children!\n");
        i = ~0;
        *(char *)1 = '\0'; /** @todo implement this! */
    }

    /*
     * Close the handle, and if we succeeded collect the exit code first.
     */
    if (i < psh->num_children)
    {
        BOOL rc;
        if (hChild != INVALID_HANDLE_VALUE)
        {
            DWORD dwExitCode = 127;
# ifndef SH_FORKED_MODE
            if (psh->children[i].subshellstatus)
            {
                rc = psh->children[i].subshellstatus->done;
                kHlpAssert(rc);
                if (rc)
                {
                    *statusp = psh->children[i].subshellstatus->status;
                    pidret = psh->children[i].pid;
                }
            }
            else
# endif
            if (GetExitCodeProcess(hChild, &dwExitCode))
            {
                pidret = psh->children[i].pid;
                if (dwExitCode && !W_EXITCODE(dwExitCode, 0))
                    dwExitCode |= 16;
                *statusp = W_EXITCODE(dwExitCode, 0);
            }
            else
                errno = EINVAL;
        }

        /* close and remove */
# ifndef SH_FORKED_MODE
        if (psh->children[i].subshellstatus)
        {
            shsubshellstatus_release(psh, psh->children[i].subshellstatus);
            psh->children[i].subshellstatus = NULL;
        }
        else
# endif
        {
            rc = CloseHandle(psh->children[i].hChild);
            kHlpAssert(rc);
        }

        psh->num_children--;
        if (i < psh->num_children)
            psh->children[i] = psh->children[psh->num_children];
        psh->children[psh->num_children].hChild = NULL;
# ifndef SH_FORKED_MODE
        psh->children[psh->num_children].subshellstatus = NULL;
# endif
    }

#elif defined(SH_FORKED_MODE)
    *statusp = 0;
# ifdef _MSC_VER
    pidret = -1;
    errno = ENOSYS;
# else
    pidret = waitpid(pid, statusp, flags);
# endif

#else
#endif

    TRACE2((psh, "waitpid(%" SHPID_PRI ", %p, %#x) -> %" SHPID_PRI " [%d] *statusp=%#x (rc=%d)\n", pid, statusp, flags,
            pidret, errno, *statusp, WEXITSTATUS(*statusp)));
    (void)psh;
    return pidret;
}

SH_NORETURN_1 void sh__exit(shinstance *psh, int iExit)
{
    TRACE2((psh, "sh__exit(%d)\n", iExit));

#if defined(SH_FORKED_MODE)
    _exit(iExit);
    (void)psh;

#else
    psh->exitstatus = iExit;

    /*
     * If we're a thread, jump to the sh_thread_wrapper and make a clean exit.
     */
    if (psh->thread)
    {
        shsubshellstatus_signal_and_release(psh, iExit);
        if (psh->exitjmp)
            longjmp(psh->exitjmp->loc, !iExit ? SH_EXIT_ZERO : iExit);
        else
        {
            static char const s_msg[] = "fatal error in sh__exit: exitjmp is NULL!\n";
            shfile_write(&psh->fdtab, 2, s_msg, sizeof(s_msg) - 1);
            _exit(iExit);
        }
    }

    /*
     * The main thread will typically have to stick around till all subshell
     * threads have been stopped.  We must tear down this shell instance as
     * much as possible before doing this, though, as subshells could be
     * waiting for pipes and such to be closed before they're willing to exit.
     */
    if (g_num_shells > 1)
    {
        TRACE2((psh, "sh__exit: %u shells around, must wait...\n", g_num_shells));
        shfile_uninit(&psh->fdtab, psh->tracefd);
        sh_int_unlink(psh);
        /** @todo    */
    }

    _exit(iExit);
#endif
}

int sh_execve(shinstance *psh, const char *exe, const char * const *argv, const char * const *envp)
{
    int rc;

    g_stat_execs++;

#ifdef DEBUG
    /* log it all */
    TRACE2((psh, "sh_execve(%p:{%s}, %p, %p}\n", exe, exe, argv, envp));
    for (rc = 0; argv[rc]; rc++)
        TRACE2((psh, "  argv[%d]=%p:{%s}\n", rc, argv[rc], argv[rc]));
#endif

    if (!envp)
        envp = (const char * const *)sh_environ(psh);

#if defined(SH_FORKED_MODE) && K_OS != K_OS_WINDOWS
# ifdef _MSC_VER
    errno = 0;
    {
        intptr_t rc2 = _spawnve(_P_WAIT, exe, (char **)argv, (char **)envp);
        if (rc2 != -1)
        {
            TRACE2((psh, "sh_execve: child exited, rc=%d. (errno=%d)\n", rc, errno));
            rc = (int)rc2;
            if (!rc && rc2)
                rc = 16;
            exit(rc);
        }
    }
    rc = -1;

# else
    rc = shfile_exec_unix(&psh->fdtab);
    if (!rc)
        rc = execve(exe, (char **)argv, (char **)envp);
# endif

#else
# if K_OS == K_OS_WINDOWS
    {
        /*
         * This ain't quite straight forward on Windows...
         */
        PROCESS_INFORMATION ProcInfo;
        STARTUPINFO StrtInfo;
        shfdexecwin fdinfo;
        char *cwd = shfile_getcwd(&psh->fdtab, NULL, 0);
        char *cmdline;
        size_t cmdline_size;
        char *envblock;
        size_t env_size;
        char *p;
        int i;

        /* Create the environment block. */
        if (!envp)
            envp = sh_environ(psh);
        env_size = 2;
        for (i = 0; envp[i]; i++)
            env_size += strlen(envp[i]) + 1;
        envblock = p = sh_malloc(psh, env_size);
        for (i = 0; envp[i]; i++)
        {
            size_t len = strlen(envp[i]) + 1;
            memcpy(p, envp[i], len);
            p += len;
        }
        *p = '\0';

        /* Figure the size of the command line. Double quotes makes this
           tedious and we overestimate to simplify. */
        cmdline_size = 2;
        for (i = 0; argv[i]; i++)
        {
            const char *arg = argv[i];
            cmdline_size += strlen(arg) + 3;
            arg = strchr(arg, '"');
            if (arg)
            {
                do
                    cmdline_size++;
                while ((arg = strchr(arg + 1, '"')) != NULL);
                arg = argv[i] - 1;
                while ((arg = strchr(arg + 1, '\\')) != NULL);
                    cmdline_size++;
            }
        }

        /* Create the command line. */
        cmdline = p = sh_malloc(psh, cmdline_size);
        for (i = 0; argv[i]; i++)
        {
            const char *arg = argv[i];
            const char *cur = arg;
            size_t len = strlen(arg);
            int quoted = 0;
            char ch;
            while ((ch = *cur++) != '\0')
                if (ch <= 0x20 || strchr("&><|%", ch) != NULL)
                {
                    quoted = 1;
                    break;
                }

            if (i != 0)
                *(p++) = ' ';
            if (quoted)
                *(p++) = '"';
            if (memchr(arg, '"', len) == NULL)
            {
                memcpy(p, arg, len);
                p += len;
            }
            else
            {   /* MS CRT style: double quotes must be escaped; backslashes
                   must be escaped if followed by double quotes. */
                while ((ch = *arg++) != '\0')
                    if (ch != '\\' && ch != '"')
                        *p++ = ch;
                    else if (ch == '"')
                    {
                        *p++ = '\\';
                        *p++ = '"';
                    }
                    else
                    {
                        unsigned slashes = 1;
                        *p++ = '\\';
                        while (*arg == '\\')
                        {
                            *p++ = '\\';
                            slashes++;
                            arg++;
                        }
                        if (*arg == '"')
                        {
                            while (slashes-- > 0)
                                *p++ = '\\';
                            *p++ = '\\';
                            *p++ = '"';
                            arg++;
                        }
                    }
            }
            if (quoted)
                *(p++) = '"';
        }
        p[0] = p[1] = '\0';

        /* Init the info structure */
        memset(&StrtInfo, '\0', sizeof(StrtInfo));
        StrtInfo.cb = sizeof(StrtInfo);

        /* File handles. */
        fdinfo.strtinfo = &StrtInfo;
        shfile_exec_win(&psh->fdtab, 1 /* prepare */, &fdinfo);
        TRACE2((psh, "sh_execve: inherithandles=%d replacehandles={%d,%d,%d} handles={%p,%p,%p} suspended=%d Reserved2=%p LB %#x\n",
                fdinfo.inherithandles, fdinfo.replacehandles[0], fdinfo.replacehandles[1], fdinfo.replacehandles[2],
                fdinfo.handles[0], fdinfo.handles[1], fdinfo.handles[3], fdinfo.startsuspended,
                StrtInfo.lpReserved2, StrtInfo.cbReserved2));
        if (!fdinfo.inherithandles)
        {
            StrtInfo.dwFlags |= STARTF_USESTDHANDLES;
            StrtInfo.hStdInput  = INVALID_HANDLE_VALUE;
            StrtInfo.hStdOutput = INVALID_HANDLE_VALUE;
            StrtInfo.hStdError  = INVALID_HANDLE_VALUE;
        }
        else
        {
            StrtInfo.dwFlags |= STARTF_USESTDHANDLES;
            StrtInfo.hStdInput  = (HANDLE)fdinfo.handles[0];
            StrtInfo.hStdOutput = (HANDLE)fdinfo.handles[1];
            StrtInfo.hStdError  = (HANDLE)fdinfo.handles[2];
            g_stat_execs_serialized++;
        }

        /* Get going... */
        rc = CreateProcessA(exe,
                            cmdline,
                            NULL,         /* pProcessAttributes */
                            NULL,         /* pThreadAttributes */
                            fdinfo.inherithandles,
                            fdinfo.startsuspended ? CREATE_SUSPENDED : 0,
                            envblock,
                            cwd,
                            &StrtInfo,
                            &ProcInfo);
        if (rc)
        {
            DWORD dwErr;
            DWORD dwExitCode;

            if (fdinfo.startsuspended)
            {
                char errmsg[512];
                if (!fdinfo.inherithandles)
                    rc = nt_child_inject_standard_handles(ProcInfo.hProcess, fdinfo.replacehandles,
                                                          (HANDLE *)&fdinfo.handles[0], errmsg, sizeof(errmsg));
                else
                    rc = 0;
                if (!rc)
                {
#  ifdef KASH_ASYNC_CLOSE_HANDLE
                    shfile_async_close_sync();
#  endif
                    rc = ResumeThread(ProcInfo.hThread);
                    if (!rc)
                        TRACE2((psh, "sh_execve: ResumeThread failed: %u -> errno=ENXIO\n", GetLastError()));
                }
                else
                {
                    TRACE2((psh, "sh_execve: nt_child_inject_standard_handles failed: %d -> errno=ENXIO; %s\n", rc, errmsg));
                    rc = FALSE;
                }
                errno = ENXIO;
            }

            shfile_exec_win(&psh->fdtab, rc ? 0 /* done */ : -1 /* done but failed */, &fdinfo);

            CloseHandle(ProcInfo.hThread);
            ProcInfo.hThread = INVALID_HANDLE_VALUE;
            if (rc)
            {
                /*
                 * Wait for it and forward the exit code.
                 */
                dwErr = WaitForSingleObject(ProcInfo.hProcess, INFINITE);
                kHlpAssert(dwErr == WAIT_OBJECT_0);

                if (GetExitCodeProcess(ProcInfo.hProcess, &dwExitCode))
                {
#  ifndef SH_FORKED_MODE
                    shsubshellstatus_signal_and_release(psh, (int)dwExitCode);
#  endif
                    CloseHandle(ProcInfo.hProcess);
                    ProcInfo.hProcess = INVALID_HANDLE_VALUE;
                    sh__exit(psh, dwExitCode);
                }

                /* this shouldn't happen... */
                TRACE2((psh, "sh_execve: GetExitCodeProcess failed: %u\n", GetLastError()));
                kHlpAssert(0);
                errno = EINVAL;
            }
            TerminateProcess(ProcInfo.hProcess, 0x40000015);
            CloseHandle(ProcInfo.hProcess);
        }
        else
        {
            DWORD dwErr = GetLastError();

            shfile_exec_win(&psh->fdtab, -1 /* done but failed */, &fdinfo);

            switch (dwErr)
            {
                case ERROR_FILE_NOT_FOUND:          errno = ENOENT; break;
                case ERROR_PATH_NOT_FOUND:          errno = ENOENT; break;
                case ERROR_BAD_EXE_FORMAT:          errno = ENOEXEC; break;
                case ERROR_INVALID_EXE_SIGNATURE:   errno = ENOEXEC; break;
                default:                            errno = EINVAL; break;
            }
            TRACE2((psh, "sh_execve: dwErr=%d -> errno=%d\n", dwErr, errno));
        }
    }
    rc = -1;

# else
    errno = ENOSYS;
    rc = -1;
# endif
#endif

    TRACE2((psh, "sh_execve -> %d [%d]\n", rc, errno));
    (void)psh;
    return (int)rc;
}

uid_t sh_getuid(shinstance *psh)
{
#ifdef _MSC_VER
    uid_t uid = 0;
#else
    uid_t uid = getuid();
#endif

    TRACE2((psh, "sh_getuid() -> %d [%d]\n", uid, errno));
    (void)psh;
    return uid;
}

uid_t sh_geteuid(shinstance *psh)
{
#ifdef _MSC_VER
    uid_t euid = 0;
#else
    uid_t euid = geteuid();
#endif

    TRACE2((psh, "sh_geteuid() -> %d [%d]\n", euid, errno));
    (void)psh;
    return euid;
}

gid_t sh_getgid(shinstance *psh)
{
#ifdef _MSC_VER
    gid_t gid = 0;
#else
    gid_t gid = getgid();
#endif

    TRACE2((psh, "sh_getgid() -> %d [%d]\n", gid, errno));
    (void)psh;
    return gid;
}

gid_t sh_getegid(shinstance *psh)
{
#ifdef _MSC_VER
    gid_t egid = 0;
#else
    gid_t egid = getegid();
#endif

    TRACE2((psh, "sh_getegid() -> %d [%d]\n", egid, errno));
    (void)psh;
    return egid;
}

shpid sh_getpid(shinstance *psh)
{
    return psh->pid;
}

shpid sh_getpgrp(shinstance *psh)
{
    shpid pgid = psh->pgid;
#ifndef _MSC_VER
    kHlpAssert(pgid == getpgrp());
#endif

    TRACE2((psh, "sh_getpgrp() -> %" SHPID_PRI " [%d]\n", pgid, errno));
    return pgid;
}

/**
 * @param   pid     Should always be zero, i.e. referring to the current shell
 *                  process.
 */
shpid sh_getpgid(shinstance *psh, shpid pid)
{
    shpid pgid;
    if (pid == 0 || psh->pid == pid)
    {
        pgid = psh->pgid;
#ifndef _MSC_VER
        kHlpAssert(pgid == getpgrp());
#endif
    }
    else
    {
        kHlpAssert(0);
        errno = ESRCH;
        pgid = -1;
    }

    TRACE2((psh, "sh_getpgid(%" SHPID_PRI ") -> %" SHPID_PRI " [%d]\n", pid, pgid, errno));
    return pgid;
}

/**
 *
 * @param   pid     The pid to modify.  This is always 0, except when forkparent
 *                  calls to group a newly created child.  Though, we might
 *                  almost safely ignore it in that case as the child will also
 *                  perform the operation.
 * @param   pgid    The process group to assign @a pid to.
 */
int sh_setpgid(shinstance *psh, shpid pid, shpid pgid)
{
#if defined(SH_FORKED_MODE) && !defined(_MSC_VER)
    int rc = setpgid(pid, pgid);
    TRACE2((psh, "sh_setpgid(%" SHPID_PRI ", %" SHPID_PRI ") -> %d [%d]\n", pid, pgid, rc, errno));
    (void)psh;
#else
    int rc = 0;
    if (pid == 0 || psh->pid == pid)
    {
        TRACE2((psh, "sh_setpgid(self,): %" SHPID_PRI " -> %" SHPID_PRI "\n", psh->pgid, pgid));
        psh->pgid = pgid;
    }
    else
    {
        /** @todo fixme   */
        rc = -1;
        errno = ENOSYS;
    }
#endif
    return rc;
}

shpid sh_tcgetpgrp(shinstance *psh, int fd)
{
    shpid pgrp;

#ifdef _MSC_VER
    pgrp = -1;
    errno = ENOSYS;
#elif defined(SH_FORKED_MODE)
    pgrp = tcgetpgrp(fd);
#else
# error "PORT ME"
#endif

    TRACE2((psh, "sh_tcgetpgrp(%d) -> %" SHPID_PRI " [%d]\n", fd, pgrp, errno));
    (void)psh;
    return pgrp;
}

int sh_tcsetpgrp(shinstance *psh, int fd, shpid pgrp)
{
    int rc;
    TRACE2((psh, "sh_tcsetpgrp(%d, %" SHPID_PRI ")\n", fd, pgrp));

#ifdef _MSC_VER
    rc = -1;
    errno = ENOSYS;
#elif defined(SH_FORKED_MODE)
    rc = tcsetpgrp(fd, pgrp);
#else
# error "PORT ME"
#endif

    TRACE2((psh, "sh_tcsetpgrp(%d, %" SHPID_PRI ") -> %d [%d]\n", fd, pgrp, rc, errno));
    (void)psh;
    return rc;
}

int sh_getrlimit(shinstance *psh, int resid, shrlimit *limp)
{
#ifdef _MSC_VER
    int rc = -1;
    errno = ENOSYS;
#elif defined(SH_FORKED_MODE)
    int rc = getrlimit(resid, limp);
#else
# error "PORT ME"
    /* returned the stored limit */
#endif

    TRACE2((psh, "sh_getrlimit(%d, %p) -> %d [%d] {%ld,%ld}\n",
            resid, limp, rc, errno, (long)limp->rlim_cur, (long)limp->rlim_max));
    (void)psh;
    return rc;
}

int sh_setrlimit(shinstance *psh, int resid, const shrlimit *limp)
{
#ifdef _MSC_VER
    int rc = -1;
    errno = ENOSYS;
#elif defined(SH_FORKED_MODE)
    int rc = setrlimit(resid, limp);
#else
# error "PORT ME"
    /* if max(shell) < limp; then setrlimit; fi
       if success; then store limit for later retrival and maxing. */

#endif

    TRACE2((psh, "sh_setrlimit(%d, %p:{%ld,%ld}) -> %d [%d]\n",
            resid, limp, (long)limp->rlim_cur, (long)limp->rlim_max, rc, errno));
    (void)psh;
    return rc;
}


/* Wrapper for strerror that makes sure it doesn't return NULL and causes the
   caller or fprintf routines to crash. */
const char *sh_strerror(shinstance *psh, int error)
{
    char *err = strerror(error);
    if (!err)
        return "strerror return NULL!";
    (void)psh;
    return err;
}

