Commit b7907449 authored by Tamar Christina's avatar Tamar Christina

winio: Add support for WINIO to process.

parent 3fafbb80
......@@ -26,6 +26,13 @@ module System.Process.Common
#else
, CGid
#endif
-- WINIO is only available on GHC 8.12 and up.
#if defined(__IO_MANAGER_WINIO__)
, HANDLE
, mbHANDLE
, mbPipeHANDLE
#endif
) where
import Control.Concurrent
......@@ -39,6 +46,10 @@ import GHC.IO.Exception
import GHC.IO.Encoding
import qualified GHC.IO.FD as FD
import GHC.IO.Device
#if defined(__IO_MANAGER_WINIO__)
import GHC.IO.Handle.Windows
import GHC.IO.Windows.Handle (fromHANDLE, Io(), NativeHandle())
#endif
import GHC.IO.Handle.FD
import GHC.IO.Handle.Internals
import GHC.IO.Handle.Types hiding (ClosedHandle)
......@@ -51,6 +62,9 @@ import System.IO (IOMode)
#ifdef WINDOWS
import Data.Word (Word32)
import System.Win32.DebugApi (PHANDLE)
#if defined(__IO_MANAGER_WINIO__)
import System.Win32.Types (HANDLE)
#endif
#else
import System.Posix.Types
#endif
......@@ -258,3 +272,25 @@ pfdToHandle pfd mode = do
let enc = localeEncoding
#endif
mkHandleFromFD fD' fd_type filepath mode False {-is_socket-} (Just enc)
#if defined(__IO_MANAGER_WINIO__)
-- It is not completely safe to pass the values -1 and -2 as HANDLE as it's an
-- unsigned type. -1 additionally is also the value for INVALID_HANDLE. However
-- it should be safe in this case since an invalid handle would be an error here
-- anyway and the chances of us getting a handle with a value of -2 is
-- astronomical. However, sometime in the future process should really use a
-- proper structure here.
mbHANDLE :: HANDLE -> StdStream -> IO HANDLE
mbHANDLE _std CreatePipe = return $ intPtrToPtr (-1)
mbHANDLE std Inherit = return std
mbHANDLE _std NoStream = return $ intPtrToPtr (-2)
mbHANDLE _std (UseHandle hdl) = handleToHANDLE hdl
mbPipeHANDLE :: StdStream -> Ptr HANDLE -> IOMode -> IO (Maybe Handle)
mbPipeHANDLE CreatePipe pfd mode =
do raw_handle <- peek pfd
let hwnd = fromHANDLE raw_handle :: Io NativeHandle
ident = "hwnd:" ++ show raw_handle
Just <$> mkHandleFromHANDLE hwnd Stream ident mode Nothing
mbPipeHANDLE _std _pfd _mode = return Nothing
#endif
......@@ -30,6 +30,11 @@ import System.IO.Unsafe
import System.Posix.Internals
import GHC.IO.Exception
##if defined(__IO_MANAGER_WINIO__)
import GHC.IO.SubSystem
import Graphics.Win32.Misc
import qualified GHC.Event.Windows as Mgr
##endif
import GHC.IO.Handle.FD
import GHC.IO.Handle.Types hiding (ClosedHandle)
import System.IO.Error
......@@ -91,19 +96,77 @@ createProcess_Internal
-> CreateProcess
-> IO ProcRetHandles
createProcess_Internal fun CreateProcess{ cmdspec = cmdsp,
cwd = mb_cwd,
env = mb_env,
std_in = mb_stdin,
std_out = mb_stdout,
std_err = mb_stderr,
close_fds = mb_close_fds,
create_group = mb_create_group,
delegate_ctlc = _ignored,
detach_console = mb_detach_console,
create_new_console = mb_create_new_console,
new_session = mb_new_session,
use_process_jobs = use_job }
##if defined(__IO_MANAGER_WINIO__)
createProcess_Internal = createProcess_Internal_mio <!> createProcess_Internal_winio
##else
createProcess_Internal = createProcess_Internal_mio
##endif
createProcess_Internal_mio
:: String -- ^ function name (for error messages)
-> CreateProcess
-> IO ProcRetHandles
createProcess_Internal_mio fun def@CreateProcess{
std_in = mb_stdin,
std_out = mb_stdout,
std_err = mb_stderr,
close_fds = mb_close_fds,
create_group = mb_create_group,
delegate_ctlc = _ignored,
detach_console = mb_detach_console,
create_new_console = mb_create_new_console,
new_session = mb_new_session,
use_process_jobs = use_job }
= createProcess_Internal_wrapper fun def $
\pfdStdInput pfdStdOutput pfdStdError hJob pEnv pWorkDir pcmdline -> do
fdin <- mbFd fun fd_stdin mb_stdin
fdout <- mbFd fun fd_stdout mb_stdout
fderr <- mbFd fun fd_stderr mb_stderr
-- #2650: we must ensure mutual exclusion of c_runInteractiveProcess,
-- because otherwise there is a race condition whereby one thread
-- has created some pipes, and another thread spawns a process which
-- accidentally inherits some of the pipe handles that the first
-- thread has created.
--
-- An MVar in Haskell is the best way to do this, because there
-- is no way to do one-time thread-safe initialisation of a mutex
-- the C code. Also the MVar will be cheaper when not running
-- the threaded RTS.
proc_handle <- withMVar runInteractiveProcess_lock $ \_ ->
throwErrnoIfBadPHandle fun $
c_runInteractiveProcess pcmdline pWorkDir pEnv
fdin fdout fderr
pfdStdInput pfdStdOutput pfdStdError
((if mb_close_fds then RUN_PROCESS_IN_CLOSE_FDS else 0)
.|.(if mb_create_group then RUN_PROCESS_IN_NEW_GROUP else 0)
.|.(if mb_detach_console then RUN_PROCESS_DETACHED else 0)
.|.(if mb_create_new_console then RUN_PROCESS_NEW_CONSOLE else 0)
.|.(if mb_new_session then RUN_PROCESS_NEW_SESSION else 0))
use_job
hJob
hndStdInput <- mbPipe mb_stdin pfdStdInput WriteMode
hndStdOutput <- mbPipe mb_stdout pfdStdOutput ReadMode
hndStdError <- mbPipe mb_stderr pfdStdError ReadMode
return (proc_handle, hndStdInput, hndStdOutput, hndStdError)
createProcess_Internal_wrapper
:: Storable a => String -- ^ function name (for error messages)
-> CreateProcess
-> (Ptr a -> Ptr a -> Ptr a -> Ptr PHANDLE -> Ptr CWString -> CWString
-> CWString -> IO (PHANDLE, Maybe Handle, Maybe Handle, Maybe Handle))
-> IO ProcRetHandles
createProcess_Internal_wrapper _fun CreateProcess{
cmdspec = cmdsp,
cwd = mb_cwd,
env = mb_env,
delegate_ctlc = _ignored }
action
= do
let lenPtr = sizeOf (undefined :: WordPtr)
(cmd, cmdline) <- commandToProcess cmdsp
......@@ -116,9 +179,43 @@ createProcess_Internal fun CreateProcess{ cmdspec = cmdsp,
maybeWith withCWString mb_cwd $ \pWorkDir -> do
withCWString cmdline $ \pcmdline -> do
fdin <- mbFd fun fd_stdin mb_stdin
fdout <- mbFd fun fd_stdout mb_stdout
fderr <- mbFd fun fd_stderr mb_stderr
(proc_handle, hndStdInput, hndStdOutput, hndStdError)
<- action pfdStdInput pfdStdOutput pfdStdError hJob pEnv pWorkDir pcmdline
phJob <- peek hJob
ph <- mkProcessHandle proc_handle phJob
return ProcRetHandles { hStdInput = hndStdInput
, hStdOutput = hndStdOutput
, hStdError = hndStdError
, procHandle = ph
}
##if defined(__IO_MANAGER_WINIO__)
createProcess_Internal_winio
:: String -- ^ function name (for error messages)
-> CreateProcess
-> IO ProcRetHandles
createProcess_Internal_winio fun def@CreateProcess{
std_in = mb_stdin,
std_out = mb_stdout,
std_err = mb_stderr,
close_fds = mb_close_fds,
create_group = mb_create_group,
delegate_ctlc = _ignored,
detach_console = mb_detach_console,
create_new_console = mb_create_new_console,
new_session = mb_new_session,
use_process_jobs = use_job }
= createProcess_Internal_wrapper fun def $
\pfdStdInput pfdStdOutput pfdStdError hJob pEnv pWorkDir pcmdline -> do
_stdin <- getStdHandle sTD_INPUT_HANDLE
_stdout <- getStdHandle sTD_OUTPUT_HANDLE
_stderr <- getStdHandle sTD_ERROR_HANDLE
hwnd_in <- mbHANDLE _stdin mb_stdin
hwnd_out <- mbHANDLE _stdout mb_stdout
hwnd_err <- mbHANDLE _stderr mb_stderr
-- #2650: we must ensure mutual exclusion of c_runInteractiveProcess,
-- because otherwise there is a race condition whereby one thread
......@@ -132,8 +229,8 @@ createProcess_Internal fun CreateProcess{ cmdspec = cmdsp,
-- the threaded RTS.
proc_handle <- withMVar runInteractiveProcess_lock $ \_ ->
throwErrnoIfBadPHandle fun $
c_runInteractiveProcess pcmdline pWorkDir pEnv
fdin fdout fderr
c_runInteractiveProcessHANDLE pcmdline pWorkDir pEnv
hwnd_in hwnd_out hwnd_err
pfdStdInput pfdStdOutput pfdStdError
((if mb_close_fds then RUN_PROCESS_IN_CLOSE_FDS else 0)
.|.(if mb_create_group then RUN_PROCESS_IN_NEW_GROUP else 0)
......@@ -143,17 +240,20 @@ createProcess_Internal fun CreateProcess{ cmdspec = cmdsp,
use_job
hJob
hndStdInput <- mbPipe mb_stdin pfdStdInput WriteMode
hndStdOutput <- mbPipe mb_stdout pfdStdOutput ReadMode
hndStdError <- mbPipe mb_stderr pfdStdError ReadMode
-- Attach the handle to the I/O manager's CompletionPort. This allows the
-- I/O manager to service requests for this Handle.
Mgr.associateHandle' =<< peek pfdStdInput
Mgr.associateHandle' =<< peek pfdStdOutput
Mgr.associateHandle' =<< peek pfdStdError
phJob <- peek hJob
ph <- mkProcessHandle proc_handle phJob
return ProcRetHandles { hStdInput = hndStdInput
, hStdOutput = hndStdOutput
, hStdError = hndStdError
, procHandle = ph
}
-- Create the haskell mode handles as files.
hndStdInput <- mbPipeHANDLE mb_stdin pfdStdInput WriteMode
hndStdOutput <- mbPipeHANDLE mb_stdout pfdStdOutput ReadMode
hndStdError <- mbPipeHANDLE mb_stderr pfdStdError ReadMode
return (proc_handle, hndStdInput, hndStdOutput, hndStdError)
##endif
{-# NOINLINE runInteractiveProcess_lock #-}
runInteractiveProcess_lock :: MVar ()
......@@ -224,6 +324,24 @@ foreign import ccall unsafe "runInteractiveProcess"
-> Ptr PHANDLE -- Handle to Job
-> IO PHANDLE
##if defined(__IO_MANAGER_WINIO__)
foreign import ccall unsafe "runInteractiveProcessHANDLE"
c_runInteractiveProcessHANDLE
:: CWString
-> CWString
-> Ptr CWString
-> HANDLE
-> HANDLE
-> HANDLE
-> Ptr HANDLE
-> Ptr HANDLE
-> Ptr HANDLE
-> CInt -- flags
-> Bool -- useJobObject
-> Ptr PHANDLE -- Handle to Job
-> IO PHANDLE
##endif
commandToProcess
:: CmdSpec
-> IO (FilePath, String)
......@@ -299,7 +417,14 @@ isDefaultSignal :: CLong -> Bool
isDefaultSignal = const False
createPipeInternal :: IO (Handle, Handle)
createPipeInternal = do
##if defined(__IO_MANAGER_WINIO__)
createPipeInternal = createPipeInternalPosix <!> createPipeInternalHANDLE
##else
createPipeInternal = createPipeInternalPosix
##endif
createPipeInternalPosix :: IO (Handle, Handle)
createPipeInternalPosix = do
(readfd, writefd) <- createPipeInternalFd
(do readh <- fdToHandle readfd
writeh <- fdToHandle writefd
......@@ -313,6 +438,21 @@ createPipeInternalFd = do
writefd <- peekElemOff pfds 1
return (readfd, writefd)
##if defined(__IO_MANAGER_WINIO__)
createPipeInternalHANDLE :: IO (Handle, Handle)
createPipeInternalHANDLE =
alloca $ \ pfdStdInput ->
alloca $ \ pfdStdOutput -> do
throwErrnoIf_ (==False) "c_mkNamedPipe" $
c_mkNamedPipe pfdStdInput True pfdStdOutput True
Just hndStdInput <- mbPipeHANDLE CreatePipe pfdStdInput WriteMode
Just hndStdOutput <- mbPipeHANDLE CreatePipe pfdStdOutput ReadMode
return (hndStdInput, hndStdOutput)
foreign import ccall "mkNamedPipe" c_mkNamedPipe ::
Ptr HANDLE -> Bool -> Ptr HANDLE -> Bool -> IO Bool
##endif
close' :: CInt -> IO ()
close' = throwErrnoIfMinus1_ "_close" . c__close
......
/* ----------------------------------------------------------------------------
(c) The University of Glasgow 2004
(c) The University of Glasgow 2004-2020
Support for System.Process
------------------------------------------------------------------------- */
#if defined(_MSC_VER) || defined(__MINGW32__) || defined(_WIN32)
#define UNICODE
#endif
/* XXX This is a nasty hack; should put everything necessary in this package */
#include "HsBase.h"
#include "Rts.h"
#include "runProcess.h"
#if !(defined(_MSC_VER) || defined(__MINGW32__) || defined(_WIN32))
#include "execvpe.h"
/* ----------------------------------------------------------------------------
......@@ -485,439 +479,3 @@ int waitForProcess (ProcHandle handle, int *pret)
return -1;
}
#else
/* ----------------------------------------------------------------------------
Win32 versions
------------------------------------------------------------------------- */
/* -------------------- WINDOWS VERSION --------------------- */
/*
* Function: mkAnonPipe
*
* Purpose: create an anonymous pipe with read and write ends being
* optionally (non-)inheritable.
*/
static BOOL
mkAnonPipe (HANDLE* pHandleIn, BOOL isInheritableIn,
HANDLE* pHandleOut, BOOL isInheritableOut)
{
HANDLE hTemporaryIn = NULL;
HANDLE hTemporaryOut = NULL;
/* Create the anon pipe with both ends inheritable */
if (!CreatePipe(&hTemporaryIn, &hTemporaryOut, NULL, 0))
{
maperrno();
*pHandleIn = NULL;
*pHandleOut = NULL;
return FALSE;
}
if (isInheritableIn) {
// SetHandleInformation requires at least Win2k
if (!SetHandleInformation(hTemporaryIn,
HANDLE_FLAG_INHERIT,
HANDLE_FLAG_INHERIT))
{
maperrno();
*pHandleIn = NULL;
*pHandleOut = NULL;
CloseHandle(hTemporaryIn);
CloseHandle(hTemporaryOut);
return FALSE;
}
}
*pHandleIn = hTemporaryIn;
if (isInheritableOut) {
if (!SetHandleInformation(hTemporaryOut,
HANDLE_FLAG_INHERIT,
HANDLE_FLAG_INHERIT))
{
maperrno();
*pHandleIn = NULL;
*pHandleOut = NULL;
CloseHandle(hTemporaryIn);
CloseHandle(hTemporaryOut);
return FALSE;
}
}
*pHandleOut = hTemporaryOut;
return TRUE;
}
static HANDLE
createJob ()
{
HANDLE hJob = CreateJobObject (NULL, NULL);
JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli;
ZeroMemory(&jeli, sizeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
// Configure all child processes associated with the job to terminate when the
// Last process in the job terminates. This prevent half dead processes.
jeli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
if (SetInformationJobObject (hJob, JobObjectExtendedLimitInformation,
&jeli, sizeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION)))
{
return hJob;
}
maperrno();
return NULL;
}
/* Note [Windows exec interaction]
The basic issue that process jobs tried to solve is this:
Say you have two programs A and B. Now A calls B. There are two ways to do this.
1) You can use the normal CreateProcess API, which is what normal Windows code do.
Using this approach, the current waitForProcess works absolutely fine.
2) You can call the emulated POSIX function _exec, which of course is supposed to
allow the child process to replace the parent.
With approach 2) waitForProcess falls apart because the Win32's process model does
not allow this the same way as linux. _exec is emulated by first making a call to
CreateProcess to spawn B and then immediately exiting from A. So you have two
different processes.
waitForProcess is waiting on the termination of A. Because A is immediately killed,
waitForProcess will return even though B is still running. This is why for instance
the GHC testsuite on Windows had lots of file locked errors.
This approach creates a new Job and assigned A to the job, but also all future
processes spawned by A. This allows us to listen in on events, such as, when all
processes in the job are finished, but also allows us to propagate exit codes from
_exec calls.
The only reason we need this at all is because we don't interact with just actual
native code on Windows, and instead have a lot of ported POSIX code.
The Job handle is returned to the user because Jobs have additional benefits as well,
such as allowing you to specify resource limits on the to be spawned process.
*/
ProcHandle
runInteractiveProcess (wchar_t *cmd, wchar_t *workingDirectory,
wchar_t *environment,
int fdStdIn, int fdStdOut, int fdStdErr,
int *pfdStdInput, int *pfdStdOutput, int *pfdStdError,
int flags, bool useJobObject, HANDLE *hJob)
{
STARTUPINFO sInfo;
PROCESS_INFORMATION pInfo;
HANDLE hStdInputRead = INVALID_HANDLE_VALUE;
HANDLE hStdInputWrite = INVALID_HANDLE_VALUE;
HANDLE hStdOutputRead = INVALID_HANDLE_VALUE;
HANDLE hStdOutputWrite = INVALID_HANDLE_VALUE;
HANDLE hStdErrorRead = INVALID_HANDLE_VALUE;
HANDLE hStdErrorWrite = INVALID_HANDLE_VALUE;
BOOL close_fds = ((flags & RUN_PROCESS_IN_CLOSE_FDS) != 0);
// We always pass a wide environment block, so we MUST set this flag
DWORD dwFlags = CREATE_UNICODE_ENVIRONMENT;
BOOL status;
BOOL inherit;
ZeroMemory(&sInfo, sizeof(sInfo));
sInfo.cb = sizeof(sInfo);
sInfo.dwFlags = STARTF_USESTDHANDLES;
ZeroMemory(&pInfo, sizeof(pInfo));
if (fdStdIn == -1) {
if (!mkAnonPipe(&hStdInputRead, TRUE, &hStdInputWrite, FALSE))
goto cleanup_err;
sInfo.hStdInput = hStdInputRead;
} else if (fdStdIn == -2) {
sInfo.hStdInput = NULL;
} else if (fdStdIn == 0) {
// Don't duplicate stdin, as console handles cannot be
// duplicated and inherited. urg.
sInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
} else {
// The handle might not be inheritable, so duplicate it
status = DuplicateHandle(GetCurrentProcess(),
(HANDLE) _get_osfhandle(fdStdIn),
GetCurrentProcess(), &hStdInputRead,
0,
TRUE, /* inheritable */
DUPLICATE_SAME_ACCESS);
if (!status) goto cleanup_err;
sInfo.hStdInput = hStdInputRead;
}
if (fdStdOut == -1) {
if (!mkAnonPipe(&hStdOutputRead, FALSE, &hStdOutputWrite, TRUE))
goto cleanup_err;
sInfo.hStdOutput = hStdOutputWrite;
} else if (fdStdOut == -2) {
sInfo.hStdOutput = NULL;
} else if (fdStdOut == 1) {
// Don't duplicate stdout, as console handles cannot be
// duplicated and inherited. urg.
sInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
} else {
// The handle might not be inheritable, so duplicate it
status = DuplicateHandle(GetCurrentProcess(),
(HANDLE) _get_osfhandle(fdStdOut),
GetCurrentProcess(), &hStdOutputWrite,
0,
TRUE, /* inheritable */
DUPLICATE_SAME_ACCESS);
if (!status) goto cleanup_err;
sInfo.hStdOutput = hStdOutputWrite;
}
if (fdStdErr == -1) {
if (!mkAnonPipe(&hStdErrorRead, TRUE, &hStdErrorWrite, TRUE))
goto cleanup_err;
sInfo.hStdError = hStdErrorWrite;
} else if (fdStdErr == -2) {
sInfo.hStdError = NULL;
} else if (fdStdErr == 2) {
// Don't duplicate stderr, as console handles cannot be
// duplicated and inherited. urg.
sInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE);
} else {
/* The handle might not be inheritable, so duplicate it */
status = DuplicateHandle(GetCurrentProcess(),
(HANDLE) _get_osfhandle(fdStdErr),
GetCurrentProcess(), &hStdErrorWrite,
0,
TRUE, /* inheritable */
DUPLICATE_SAME_ACCESS);
if (!status) goto cleanup_err;
sInfo.hStdError = hStdErrorWrite;
}
if (sInfo.hStdInput != GetStdHandle(STD_INPUT_HANDLE) &&
sInfo.hStdOutput != GetStdHandle(STD_OUTPUT_HANDLE) &&
sInfo.hStdError != GetStdHandle(STD_ERROR_HANDLE) &&
(flags & RUN_PROCESS_IN_NEW_GROUP) == 0)
dwFlags |= CREATE_NO_WINDOW; // Run without console window only when both output and error are redirected
// See #3231
if (close_fds && fdStdIn == 0 && fdStdOut == 1 && fdStdErr == 2) {
inherit = FALSE;
} else {
inherit = TRUE;
}
if ((flags & RUN_PROCESS_IN_NEW_GROUP) != 0) {
dwFlags |= CREATE_NEW_PROCESS_GROUP;
}
if ((flags & RUN_PROCESS_DETACHED) != 0) {
dwFlags |= DETACHED_PROCESS;
}
if ((flags & RUN_PROCESS_NEW_CONSOLE) != 0) {
dwFlags |= CREATE_NEW_CONSOLE;
}
/* If we're going to use a job object, then we have to create
the thread suspended.
See Note [Windows exec interaction]. */
if (useJobObject)
{
dwFlags |= CREATE_SUSPENDED;
*hJob = createJob();
if (!*hJob)
{
goto cleanup_err;
}
} else {
*hJob = NULL;
}
if (!CreateProcess(NULL, cmd, NULL, NULL, inherit, dwFlags, environment, workingDirectory, &sInfo, &pInfo))
{
goto cleanup_err;
}
if (useJobObject && hJob && *hJob)
{
// Then associate the process and the job;
if (!AssignProcessToJobObject (*hJob, pInfo.hProcess))
{
goto cleanup_err;
}
// And now that we've associated the new process with the job
// we can actively resume it.
ResumeThread (pInfo.hThread);
}
CloseHandle(pInfo.hThread);
// Close the ends of the pipes that were inherited by the
// child process. This is important, otherwise we won't see
// EOF on these pipes when the child process exits.
if (hStdInputRead != INVALID_HANDLE_VALUE) CloseHandle(hStdInputRead);
if (hStdOutputWrite != INVALID_HANDLE_VALUE) CloseHandle(hStdOutputWrite);
if (hStdErrorWrite != INVALID_HANDLE_VALUE) CloseHandle(hStdErrorWrite);
*pfdStdInput = _open_osfhandle((intptr_t) hStdInputWrite, _O_WRONLY);
*pfdStdOutput = _open_osfhandle((intptr_t) hStdOutputRead, _O_RDONLY);
*pfdStdError = _open_osfhandle((intptr_t) hStdErrorRead, _O_RDONLY);
return pInfo.hProcess;
cleanup_err:
if (hStdInputRead != INVALID_HANDLE_VALUE) CloseHandle(hStdInputRead);
if (hStdInputWrite != INVALID_HANDLE_VALUE) CloseHandle(hStdInputWrite);
if (hStdOutputRead != INVALID_HANDLE_VALUE) CloseHandle(hStdOutputRead);
if (hStdOutputWrite != INVALID_HANDLE_VALUE) CloseHandle(hStdOutputWrite);
if (hStdErrorRead != INVALID_HANDLE_VALUE) CloseHandle(hStdErrorRead);
if (hStdErrorWrite != INVALID_HANDLE_VALUE) CloseHandle(hStdErrorWrite);
if (useJobObject && hJob && *hJob ) CloseHandle(*hJob);
maperrno();
return NULL;
}
int
terminateProcess (ProcHandle handle)
{
if (!TerminateProcess ((HANDLE) handle, 1)) {
DWORD e = GetLastError();
DWORD exitCode;
/*