{-# LINE 1 "libraries\\process\\System\\Process\\Windows.hsc" #-}
{-# LANGUAGE CPP, ForeignFunctionInterface #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE InterruptibleFFI #-}
module System.Process.Windows
( mkProcessHandle
, translateInternal
, createProcess_Internal
, withCEnvironment
, closePHANDLE
, startDelegateControlC
, endDelegateControlC
, stopDelegateControlC
, isDefaultSignal
, createPipeInternal
, createPipeInternalFd
, interruptProcessGroupOfInternal
, terminateJob
, terminateJobUnsafe
, waitForJobCompletion
, timeout_Infinite
) where
import System.Process.Common
import Control.Concurrent
import Control.Exception
import Control.Monad
import Data.Bits
import Data.Char (toLower)
import Foreign.C
import Foreign.Marshal
import Foreign.Ptr
import Foreign.Storable
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
import System.IO (IOMode(..))
import System.Directory ( doesFileExist )
import System.Environment ( getEnv )
import System.FilePath
import System.Win32.Console (generateConsoleCtrlEvent, cTRL_BREAK_EVENT)
import System.Win32.Process (getProcessId)
#include "processFlags.h"
#if defined(i386_HOST_ARCH)
# define WINDOWS_CCONV stdcall
#elif defined(x86_64_HOST_ARCH)
# define WINDOWS_CCONV ccall
#else
# error Unknown mingw32 arch
#endif
throwErrnoIfBadPHandle :: String -> IO PHANDLE -> IO PHANDLE
throwErrnoIfBadPHandle = throwErrnoIfNull
mkProcessHandle :: PHANDLE -> Bool -> PHANDLE -> IO ProcessHandle
mkProcessHandle h ignore_signals job = do
m <- if job == nullPtr
then newMVar (OpenHandle h)
else newMVar (OpenExtHandle h job)
_ <- mkWeakMVar m (processHandleFinaliser m)
l <- newMVar ()
return (ProcessHandle m ignore_signals l)
processHandleFinaliser :: MVar ProcessHandle__ -> IO ()
processHandleFinaliser m =
modifyMVar_ m $ \p_ -> do
case p_ of
OpenHandle ph -> closePHANDLE ph
OpenExtHandle ph job -> closePHANDLE ph
>> closePHANDLE job
_ -> return ()
return (error "closed process handle")
closePHANDLE :: PHANDLE -> IO ()
closePHANDLE ph = c_CloseHandle ph
foreign import WINDOWS_CCONV unsafe "CloseHandle"
c_CloseHandle
:: PHANDLE
-> IO ()
createProcess_Internal
:: String
-> CreateProcess
-> IO ProcRetHandles
#if defined(__IO_MANAGER_WINIO__)
createProcess_Internal = createProcess_Internal_mio <!> createProcess_Internal_winio
#else
createProcess_Internal = createProcess_Internal_mio
#endif
createProcess_Internal_mio
:: String
-> 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,
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
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
-> 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 = ignore_signals }
action
= do
let lenPtr = sizeOf (undefined :: WordPtr)
(cmd, cmdline) <- commandToProcess cmdsp
withFilePathException cmd $
alloca $ \ pfdStdInput ->
alloca $ \ pfdStdOutput ->
alloca $ \ pfdStdError ->
allocaBytes lenPtr $ \ hJob ->
maybeWith withCEnvironment mb_env $ \pEnv ->
maybeWith withCWString mb_cwd $ \pWorkDir -> do
withCWString cmdline $ \pcmdline -> do
(proc_handle, hndStdInput, hndStdOutput, hndStdError)
<- action pfdStdInput pfdStdOutput pfdStdError hJob pEnv pWorkDir pcmdline
when ignore_signals $
startDelegateControlC
phJob <- peek hJob
ph <- mkProcessHandle proc_handle ignore_signals phJob
return ProcRetHandles { hStdInput = hndStdInput
, hStdOutput = hndStdOutput
, hStdError = hndStdError
, procHandle = ph
}
#if defined(__IO_MANAGER_WINIO__)
createProcess_Internal_winio
:: String
-> 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,
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
proc_handle <- withMVar runInteractiveProcess_lock $ \_ ->
throwErrnoIfBadPHandle fun $
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)
.|.(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
Mgr.associateHandle' =<< peek pfdStdInput
Mgr.associateHandle' =<< peek pfdStdOutput
Mgr.associateHandle' =<< peek pfdStdError
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 ()
runInteractiveProcess_lock = unsafePerformIO $ newMVar ()
{-# NOINLINE runInteractiveProcess_delegate_ctlc #-}
runInteractiveProcess_delegate_ctlc :: MVar (Maybe (Int))
runInteractiveProcess_delegate_ctlc = unsafePerformIO $ newMVar Nothing
startDelegateControlC :: IO ()
startDelegateControlC =
modifyMVar_ runInteractiveProcess_delegate_ctlc $ \delegating -> do
case delegating of
Nothing -> do
_ <- c_setConsoleCtrlHandler nullPtr True
return (Just 1)
Just count -> do
let !count' = count + 1
return (Just count')
stopDelegateControlC :: IO ()
stopDelegateControlC =
modifyMVar_ runInteractiveProcess_delegate_ctlc $ \delegating -> do
case delegating of
Just 1 -> do
_ <- c_setConsoleCtrlHandler nullPtr False
return Nothing
Just count -> do
let !count' = count - 1
return (Just count')
Nothing -> return Nothing
endDelegateControlC :: ExitCode -> IO ()
endDelegateControlC _ = stopDelegateControlC
terminateJobUnsafe :: ProcessHandle__ -> CUInt -> IO Bool
terminateJobUnsafe p_ ecode = do
case p_ of
ClosedHandle _ -> return False
OpenHandle _ -> return False
OpenExtHandle _ job -> c_terminateJobObject job ecode
terminateJob :: ProcessHandle -> CUInt -> IO Bool
terminateJob jh ecode =
withProcessHandle jh $ \p_ -> terminateJobUnsafe p_ ecode
timeout_Infinite :: CUInt
timeout_Infinite = 0xFFFFFFFF
waitForJobCompletion :: PHANDLE
-> IO ()
waitForJobCompletion job =
throwErrnoIf_ not "waitForJobCompletion" $ c_waitForJobCompletion job
foreign import WINDOWS_CCONV unsafe "TerminateJobObject"
c_terminateJobObject
:: PHANDLE
-> CUInt
-> IO Bool
foreign import WINDOWS_CCONV unsafe "SetConsoleCtrlHandler"
c_setConsoleCtrlHandler
:: Ptr ()
-> Bool
-> IO Bool
foreign import ccall interruptible "waitForJobCompletion"
c_waitForJobCompletion
:: PHANDLE
-> IO Bool
foreign import ccall unsafe "runInteractiveProcess"
c_runInteractiveProcess
:: CWString
-> CWString
-> Ptr CWString
-> FD
-> FD
-> FD
-> Ptr FD
-> Ptr FD
-> Ptr FD
-> CInt
-> Bool
-> Ptr PHANDLE
-> 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
-> Bool
-> Ptr PHANDLE
-> IO PHANDLE
#endif
commandToProcess
:: CmdSpec
-> IO (FilePath, String)
commandToProcess (ShellCommand string) = do
cmd <- findCommandInterpreter
return (cmd, translateInternal cmd ++ " /c " ++ string)
commandToProcess (RawCommand cmd args)
| map toLower (takeExtension cmd) `elem` [".bat", ".cmd"]
= return (cmd, translateInternal cmd ++ concatMap ((' ':) . translateCmdExeArg) args)
| otherwise
= return (cmd, translateInternal cmd ++ concatMap ((' ':) . translateInternal) args)
findCommandInterpreter :: IO FilePath
findCommandInterpreter = do
catchJust (\e -> if isDoesNotExistError e then Just e else Nothing)
(getEnv "COMSPEC") $ \_ -> do
path <- getEnv "PATH"
let
search :: [FilePath] -> IO (Maybe FilePath)
search [] = return Nothing
search (d:ds) = do
let path1 = d </> "cmd.exe"
path2 = d </> "command.com"
b1 <- doesFileExist path1
b2 <- doesFileExist path2
if b1 then return (Just path1)
else if b2 then return (Just path2)
else search ds
mb_path <- search (splitSearchPath path)
case mb_path of
Nothing -> ioError (mkIOError doesNotExistErrorType
"findCommandInterpreter" Nothing Nothing)
Just cmd -> return cmd
translateCmdExeArg :: String -> String
translateCmdExeArg xs = "^\"" ++ snd (foldr escape (True,"^\"") xs)
where escape '"' (_, str) = (True, '\\' : '"' : str)
escape '\\' (True, str) = (True, '\\' : '\\' : str)
escape '\\' (False, str) = (False, '\\' : str)
escape c (_, str)
| c `elem` "^<>|&()" = (False, '^' : c : str)
| otherwise = (False, c : str)
translateInternal :: String -> String
translateInternal xs = '"' : snd (foldr escape (True,"\"") xs)
where escape '"' (_, str) = (True, '\\' : '"' : str)
escape '\\' (True, str) = (True, '\\' : '\\' : str)
escape '\\' (False, str) = (False, '\\' : str)
escape c (_, str) = (False, c : str)
withCEnvironment :: [(String,String)] -> (Ptr CWString -> IO a) -> IO a
withCEnvironment envir act =
let env' = foldr (\(name, val) env0 -> name ++ ('=':val)++'\0':env0) "\0" envir
in withCWString env' (act . castPtr)
isDefaultSignal :: CLong -> Bool
isDefaultSignal = const False
createPipeInternal :: IO (Handle, Handle)
#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
return (readh, writeh)) `onException` (close' readfd >> close' writefd)
createPipeInternalFd :: IO (FD, FD)
createPipeInternalFd = do
allocaArray 2 $ \ pfds -> do
throwErrnoIfMinus1_ "_pipe" $ c__pipe pfds 8192 (32768)
{-# LINE 536 "libraries\\process\\System\\Process\\Windows.hsc" #-}
readfd <- peek pfds
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 ReadMode
Just hndStdOutput <- mbPipeHANDLE CreatePipe pfdStdOutput WriteMode
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
foreign import ccall "io.h _pipe" c__pipe ::
Ptr CInt -> CUInt -> CInt -> IO CInt
foreign import ccall "io.h _close" c__close ::
CInt -> IO CInt
interruptProcessGroupOfInternal
:: ProcessHandle
-> IO ()
interruptProcessGroupOfInternal ph = do
withProcessHandle ph $ \p_ -> do
case p_ of
ClosedHandle _ -> return ()
_ -> do let h = phdlProcessHandle p_
{-# LINE 574 "libraries\\process\\System\\Process\\Windows.hsc" #-}
pid <- getProcessId h
generateConsoleCtrlEvent cTRL_BREAK_EVENT pid
{-# LINE 583 "libraries\\process\\System\\Process\\Windows.hsc" #-}
return ()