summaryrefslogtreecommitdiffstats
path: root/src/kash/shinstance.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:21:29 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:21:29 +0000
commit29cd838eab01ed7110f3ccb2e8c6a35c8a31dbcc (patch)
tree63ef546b10a81d461e5cf5ed9e98a68cd7dee1aa /src/kash/shinstance.c
parentInitial commit. (diff)
downloadkbuild-29cd838eab01ed7110f3ccb2e8c6a35c8a31dbcc.tar.xz
kbuild-29cd838eab01ed7110f3ccb2e8c6a35c8a31dbcc.zip
Adding upstream version 1:0.1.9998svn3589+dfsg.upstream/1%0.1.9998svn3589+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/kash/shinstance.c')
-rw-r--r--src/kash/shinstance.c2389
1 files changed, 2389 insertions, 0 deletions
diff --git a/src/kash/shinstance.c b/src/kash/shinstance.c
new file mode 100644
index 0000000..868a3c3
--- /dev/null
+++ b/src/kash/shinstance.c
@@ -0,0 +1,2389 @@
+/* $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;
+}
+