module System.Process.Internals (
#ifndef __HUGS__
ProcessHandle(..), ProcessHandle__(..),
PHANDLE, closePHANDLE, mkProcessHandle,
withProcessHandle, withProcessHandle_,
#ifdef __GLASGOW_HASKELL__
CreateProcess(..),
CmdSpec(..), StdStream(..),
runGenProcess_,
#endif
#if !defined(mingw32_HOST_OS) && !defined(__MINGW32__)
pPrPr_disableITimers, c_execvpe,
ignoreSignal, defaultSignal,
#endif
#endif
withFilePathException, withCEnvironment,
translate,
#ifndef __HUGS__
fdToHandle,
#endif
) where
#ifndef __HUGS__
#if !defined(mingw32_HOST_OS) && !defined(__MINGW32__)
import System.Posix.Types ( CPid )
import System.Posix.Process.Internals ( pPrPr_disableITimers, c_execvpe )
import System.IO ( IOMode(..) )
#else
import Data.Word ( Word32 )
import Data.IORef
#endif
#endif
import System.IO ( Handle )
import System.Exit ( ExitCode )
import Control.Concurrent
import Control.Exception
import Foreign.C
import Foreign
# ifdef __GLASGOW_HASKELL__
import System.Posix.Internals
#if __GLASGOW_HASKELL__ >= 611
import GHC.IO.Exception
import GHC.IO.Encoding
import qualified GHC.IO.FD as FD
import GHC.IO.Device
import GHC.IO.Handle
import GHC.IO.Handle.FD
import GHC.IO.Handle.Internals
import GHC.IO.Handle.Types
import System.IO.Error
import Data.Typeable
#if defined(mingw32_HOST_OS)
import GHC.IO.IOMode
import System.Win32.DebugApi (PHANDLE)
#endif
#else
import GHC.IOBase ( haFD, FD, IOException(..) )
import GHC.Handle
#endif
# elif __HUGS__
import Hugs.Exception ( IOException(..) )
# endif
#ifdef base4
import System.IO.Error ( ioeSetFileName )
#endif
#if defined(mingw32_HOST_OS)
import Control.Monad ( when )
import System.Directory ( doesFileExist )
import System.IO.Error ( isDoesNotExistError, doesNotExistErrorType,
mkIOError )
import System.Environment ( getEnv )
import System.FilePath
#endif
#ifdef __HUGS__
#endif
#include "HsProcessConfig.h"
#include "processFlags.h"
#ifndef __HUGS__
data ProcessHandle__ = OpenHandle PHANDLE | ClosedHandle ExitCode
newtype ProcessHandle = ProcessHandle (MVar ProcessHandle__)
withProcessHandle
:: ProcessHandle
-> (ProcessHandle__ -> IO (ProcessHandle__, a))
-> IO a
withProcessHandle (ProcessHandle m) io = modifyMVar m io
withProcessHandle_
:: ProcessHandle
-> (ProcessHandle__ -> IO ProcessHandle__)
-> IO ()
withProcessHandle_ (ProcessHandle m) io = modifyMVar_ m io
#if !defined(mingw32_HOST_OS) && !defined(__MINGW32__)
type PHANDLE = CPid
throwErrnoIfBadPHandle :: String -> IO PHANDLE -> IO PHANDLE
throwErrnoIfBadPHandle = throwErrnoIfMinus1
mkProcessHandle :: PHANDLE -> IO ProcessHandle
mkProcessHandle p = do
m <- newMVar (OpenHandle p)
return (ProcessHandle m)
closePHANDLE :: PHANDLE -> IO ()
closePHANDLE _ = return ()
#else
throwErrnoIfBadPHandle :: String -> IO PHANDLE -> IO PHANDLE
throwErrnoIfBadPHandle = throwErrnoIfNull
mkProcessHandle :: PHANDLE -> IO ProcessHandle
mkProcessHandle h = do
m <- newMVar (OpenHandle h)
addMVarFinalizer m (processHandleFinaliser m)
return (ProcessHandle m)
processHandleFinaliser m =
modifyMVar_ m $ \p_ -> do
case p_ of
OpenHandle ph -> closePHANDLE ph
_ -> return ()
return (error "closed process handle")
closePHANDLE :: PHANDLE -> IO ()
closePHANDLE ph = c_CloseHandle ph
foreign import stdcall unsafe "CloseHandle"
c_CloseHandle
:: PHANDLE
-> IO ()
#endif
#endif /* !__HUGS__ */
data CreateProcess = CreateProcess{
cmdspec :: CmdSpec,
cwd :: Maybe FilePath,
env :: Maybe [(String,String)],
std_in :: StdStream,
std_out :: StdStream,
std_err :: StdStream,
close_fds :: Bool,
create_group :: Bool
}
data CmdSpec
= ShellCommand String
| RawCommand FilePath [String]
data StdStream
= Inherit
| UseHandle Handle
| CreatePipe
runGenProcess_
:: String
-> CreateProcess
-> Maybe CLong
-> Maybe CLong
-> IO (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)
#if !defined(mingw32_HOST_OS) && !defined(__MINGW32__)
#ifdef __GLASGOW_HASKELL__
runGenProcess_ 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 }
mb_sigint mb_sigquit
= do
let (cmd,args) = commandToProcess cmdsp
withFilePathException cmd $
alloca $ \ pfdStdInput ->
alloca $ \ pfdStdOutput ->
alloca $ \ pfdStdError ->
maybeWith withCEnvironment mb_env $ \pEnv ->
maybeWith withFilePath mb_cwd $ \pWorkDir ->
withMany withFilePath (cmd:args) $ \cstrs ->
withArray0 nullPtr cstrs $ \pargs -> do
fdin <- mbFd fun fd_stdin mb_stdin
fdout <- mbFd fun fd_stdout mb_stdout
fderr <- mbFd fun fd_stderr mb_stderr
let (set_int, inthand)
= case mb_sigint of
Nothing -> (0, 0)
Just hand -> (1, hand)
(set_quit, quithand)
= case mb_sigquit of
Nothing -> (0, 0)
Just hand -> (1, hand)
proc_handle <- withMVar runInteractiveProcess_lock $ \_ ->
throwErrnoIfMinus1 fun $
c_runInteractiveProcess pargs pWorkDir pEnv
fdin fdout fderr
pfdStdInput pfdStdOutput pfdStdError
set_int inthand set_quit quithand
((if mb_close_fds then RUN_PROCESS_IN_CLOSE_FDS else 0)
.|.(if mb_create_group then RUN_PROCESS_IN_NEW_GROUP else 0))
hndStdInput <- mbPipe mb_stdin pfdStdInput WriteMode
hndStdOutput <- mbPipe mb_stdout pfdStdOutput ReadMode
hndStdError <- mbPipe mb_stderr pfdStdError ReadMode
ph <- mkProcessHandle proc_handle
return (hndStdInput, hndStdOutput, hndStdError, ph)
runInteractiveProcess_lock :: MVar ()
runInteractiveProcess_lock = unsafePerformIO $ newMVar ()
foreign import ccall unsafe "runInteractiveProcess"
c_runInteractiveProcess
:: Ptr CString
-> CString
-> Ptr CString
-> FD
-> FD
-> FD
-> Ptr FD
-> Ptr FD
-> Ptr FD
-> CInt
-> CLong
-> CInt
-> CLong
-> CInt
-> IO PHANDLE
#endif /* __GLASGOW_HASKELL__ */
ignoreSignal, defaultSignal :: CLong
ignoreSignal = CONST_SIG_IGN
defaultSignal = CONST_SIG_DFL
#else
#ifdef __GLASGOW_HASKELL__
runGenProcess_ 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 }
_ignored_mb_sigint _ignored_mb_sigquit
= do
(cmd, cmdline) <- commandToProcess cmdsp
withFilePathException cmd $
alloca $ \ pfdStdInput ->
alloca $ \ pfdStdOutput ->
alloca $ \ pfdStdError ->
maybeWith withCEnvironment mb_env $ \pEnv ->
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 <- 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))
hndStdInput <- mbPipe mb_stdin pfdStdInput WriteMode
hndStdOutput <- mbPipe mb_stdout pfdStdOutput ReadMode
hndStdError <- mbPipe mb_stderr pfdStdError ReadMode
ph <- mkProcessHandle proc_handle
return (hndStdInput, hndStdOutput, hndStdError, ph)
runInteractiveProcess_lock :: MVar ()
runInteractiveProcess_lock = unsafePerformIO $ newMVar ()
foreign import ccall unsafe "runInteractiveProcess"
c_runInteractiveProcess
:: CWString
-> CWString
-> Ptr CWString
-> FD
-> FD
-> FD
-> Ptr FD
-> Ptr FD
-> Ptr FD
-> CInt
-> IO PHANDLE
#endif
#endif /* __GLASGOW_HASKELL__ */
fd_stdin, fd_stdout, fd_stderr :: FD
fd_stdin = 0
fd_stdout = 1
fd_stderr = 2
mbFd :: String -> FD -> StdStream -> IO FD
mbFd _ _std CreatePipe = return (1)
mbFd _fun std Inherit = return std
mbFd fun _std (UseHandle hdl) =
#if __GLASGOW_HASKELL__ < 611
withHandle_ fun hdl $ return . haFD
#else
withHandle fun hdl $ \h@Handle__{haDevice=dev,..} ->
case cast dev of
Just fd -> do
fd <- FD.setNonBlockingMode fd False
return (Handle__{haDevice=fd,..}, FD.fdFD fd)
Nothing ->
ioError (mkIOError illegalOperationErrorType
"createProcess" (Just hdl) Nothing
`ioeSetErrorString` "handle is not a file descriptor")
#endif
mbPipe :: StdStream -> Ptr FD -> IOMode -> IO (Maybe Handle)
mbPipe CreatePipe pfd mode = fmap Just (pfdToHandle pfd mode)
mbPipe _std _pfd _mode = return Nothing
pfdToHandle :: Ptr FD -> IOMode -> IO Handle
pfdToHandle pfd mode = do
fd <- peek pfd
let filepath = "fd:" ++ show fd
#if __GLASGOW_HASKELL__ >= 611
(fD,fd_type) <- FD.mkFD (fromIntegral fd) mode
(Just (Stream,0,0))
False
False
fD <- FD.setNonBlockingMode fD True
mkHandleFromFD fD fd_type filepath mode False
(Just localeEncoding)
#else
fdToHandle' fd (Just Stream)
False
filepath mode True
#endif
#ifndef __HUGS__
#if !defined(mingw32_HOST_OS) && !defined(__MINGW32__)
commandToProcess :: CmdSpec -> (FilePath, [String])
commandToProcess (ShellCommand string) = ("/bin/sh", ["-c", string])
commandToProcess (RawCommand cmd args) = (cmd, args)
#else
commandToProcess
:: CmdSpec
-> IO (FilePath, String)
commandToProcess (ShellCommand string) = do
cmd <- findCommandInterpreter
return (cmd, translate cmd ++ "/c " ++ string)
commandToProcess (RawCommand cmd args) = do
return (cmd, translate cmd ++ concatMap ((' ':) . translate) args)
findCommandInterpreter :: IO FilePath
findCommandInterpreter = do
#ifdef base3
catchJust (\e -> case e of
IOException e | isDoesNotExistError e -> Just e
_otherwise -> Nothing)
#else
catchJust (\e -> if isDoesNotExistError e then Just e else Nothing)
#endif
(getEnv "COMSPEC") $ \e -> 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
#endif
#endif /* __HUGS__ */
translate :: String -> String
#if mingw32_HOST_OS
translate str = '"' : snd (foldr escape (True,"\"") str)
where escape '"' (b, str) = (True, '\\' : '"' : str)
escape '\\' (True, str) = (True, '\\' : '\\' : str)
escape '\\' (False, str) = (False, '\\' : str)
escape c (b, str) = (False, c : str)
#else
translate str = '\'' : foldr escape "'" str
where escape '\'' = showString "'\\''"
escape c = showChar c
#endif
withFilePathException :: FilePath -> IO a -> IO a
withFilePathException fpath act = handle mapEx act
where
#ifdef base4
mapEx ex = ioError (ioeSetFileName ex fpath)
#else
mapEx (IOException (IOError h iot fun str _)) = ioError (IOError h iot fun str (Just fpath))
#endif
#if !defined(mingw32_HOST_OS) && !defined(__MINGW32__)
withCEnvironment :: [(String,String)] -> (Ptr CString -> IO a) -> IO a
withCEnvironment envir act =
let env' = map (\(name, val) -> name ++ ('=':val)) envir
in withMany withCString env' (\pEnv -> withArray0 nullPtr pEnv act)
#else
withCEnvironment :: [(String,String)] -> (Ptr CWString -> IO a) -> IO a
withCEnvironment envir act =
let env' = foldr (\(name, val) env -> name ++ ('=':val)++'\0':env) "\0" envir
in withCWString env' (act . castPtr)
#endif