module RawFilePath.Process.Posix
( createProcessInternal
, withCEnvironment
, closePHANDLE
, startDelegateControlC
, endDelegateControlC
, stopDelegateControlC
, c_execvpe
, pPrPr_disableITimers
, createPipe
, createPipeInternalFd
) where
import RawFilePath.Import
import Data.ByteString.Internal (ByteString(..), memcpy)
import System.Posix.ByteString.FilePath (withFilePath)
import System.Posix.Internals hiding (withFilePath)
import System.Posix.Process.Internals ( pPrPr_disableITimers, c_execvpe )
import System.Posix.Signals
import qualified System.Posix.Signals as Sig
import qualified System.Posix.IO as Posix
import RawFilePath.Process.Common
#include "processFlags.c"
closePHANDLE :: PHANDLE -> IO ()
closePHANDLE _ = return ()
withManyByteString :: [ByteString] -> (Ptr CString -> IO a) -> IO a
withManyByteString bs action =
allocaBytes wholeLength $ \ buf ->
allocaBytes ptrLength $ \ cs -> do
copyByteStrings bs buf cs
action (castPtr cs)
where
ptrLength = (length bs + 1) * sizeOf (undefined :: Ptr CString)
wholeLength = sum (map (\ (PS _ _ l) -> l + 1) bs)
copyByteStrings :: [ByteString] -> Ptr Word8 -> Ptr (Ptr Word8) -> IO ()
copyByteStrings [] _ cs = poke cs nullPtr
copyByteStrings (PS fp o l : xs) buf cs = withForeignPtr fp $ \ p -> do
memcpy buf (p `plusPtr` o) (fromIntegral l)
pokeByteOff buf l (0 :: Word8)
poke cs (buf :: Ptr Word8)
copyByteStrings xs (buf `plusPtr` (l + 1))
(cs `plusPtr` sizeOf (undefined :: Ptr CString))
withCEnvironment :: [(ByteString, ByteString)] -> (Ptr CString -> IO a) -> IO a
withCEnvironment envir act =
let env' = map (\(name, val) -> name <> "=" <> val) envir
in withManyByteString env' act
createProcessInternal
:: (StreamType stdin, StreamType stdout, StreamType stderr)
=> ProcessConf stdin stdout stderr
-> IO (Process stdin stdout stderr)
createProcessInternal ProcessConf{..}
= alloca $ \ pfdStdInput ->
alloca $ \ pfdStdOutput ->
alloca $ \ pfdStdError ->
alloca $ \ pFailedDoing ->
maybeWith withCEnvironment env $ \pEnv ->
maybeWith withFilePath cwd $ \pWorkDir ->
maybeWith with childGroup $ \pChildGroup ->
maybeWith with childUser $ \pChildUser ->
withManyByteString cmdargs $ \pargs -> do
fdin <- mbFd fdStdin cfgStdin
fdout <- mbFd fdStdout cfgStdout
fderr <- mbFd fdStderr cfgStderr
when delegateCtlc startDelegateControlC
procHandle <- withMVar runInteractiveProcessLock $ \_ ->
c_runInteractiveProcess pargs pWorkDir pEnv
fdin fdout fderr
pfdStdInput pfdStdOutput pfdStdError
pChildGroup pChildUser
(if delegateCtlc then 1 else 0)
((if closeFds then RUN_PROCESS_IN_CLOSE_FDS else 0)
.|.(if createGroup then RUN_PROCESS_IN_NEW_GROUP else 0)
.|.(if createNewConsole then RUN_PROCESS_NEW_CONSOLE else 0)
.|.(if newSession then RUN_PROCESS_NEW_SESSION else 0))
pFailedDoing
when (procHandle == 1) $ do
cFailedDoing <- peek pFailedDoing
failedDoing <- peekCString cFailedDoing
when delegateCtlc stopDelegateControlC
throwErrno (show (head cmdargs) ++ ": " ++ failedDoing)
hIn <- mbPipe cfgStdin pfdStdInput WriteMode
hOut <- mbPipe cfgStdout pfdStdOutput ReadMode
hErr <- mbPipe cfgStderr pfdStdError ReadMode
mvarProcHandle <- newMVar (OpenHandle procHandle)
lock <- newMVar ()
return (Process hIn hOut hErr mvarProcHandle delegateCtlc lock)
runInteractiveProcessLock :: MVar ()
runInteractiveProcessLock = unsafePerformIO $ newMVar ()
runInteractiveProcessDelegateCtlc :: MVar (Maybe (Int, Sig.Handler, Sig.Handler))
runInteractiveProcessDelegateCtlc = unsafePerformIO $ newMVar Nothing
startDelegateControlC :: IO ()
startDelegateControlC =
modifyMVar_ runInteractiveProcessDelegateCtlc $ \ case
Nothing -> do
old_int <- installHandler sigINT Ignore Nothing
old_quit <- installHandler sigQUIT Ignore Nothing
return (Just (1, old_int, old_quit))
Just (count, old_int, old_quit) -> do
let !count' = count + 1
return (Just (count', old_int, old_quit))
stopDelegateControlC :: IO ()
stopDelegateControlC =
modifyMVar_ runInteractiveProcessDelegateCtlc $ \ case
Just (1, old_int, old_quit) -> do
_ <- installHandler sigINT old_int Nothing
_ <- installHandler sigQUIT old_quit Nothing
return Nothing
Just (count, old_int, old_quit) -> do
let !count' = count 1
return (Just (count', old_int, old_quit))
Nothing -> return Nothing
endDelegateControlC :: ExitCode -> IO ()
endDelegateControlC exitCode = do
stopDelegateControlC
case exitCode of
ExitFailure n | isSigIntQuit n -> throwIO UserInterrupt
_ -> return ()
where
isSigIntQuit n = sig == sigINT || sig == sigQUIT
where
sig = fromIntegral (n)
foreign import ccall unsafe "runInteractiveProcess"
c_runInteractiveProcess
:: Ptr CString
-> CString
-> Ptr CString
-> FD
-> FD
-> FD
-> Ptr FD
-> Ptr FD
-> Ptr FD
-> Ptr CGid
-> Ptr CUid
-> CInt
-> CInt
-> Ptr CString
-> IO PHANDLE
createPipe :: IO (Handle, Handle)
createPipe = do
(readfd, writefd) <- Posix.createPipe
readh <- Posix.fdToHandle readfd
writeh <- Posix.fdToHandle writefd
return (readh, writeh)
createPipeInternalFd :: IO (FD, FD)
createPipeInternalFd = do
(Fd readfd, Fd writefd) <- Posix.createPipe
return (readfd, writefd)