{-# LINE 1 "Z/IO/Process.hsc" #-}
module Z.IO.Process (
initProcess
, readProcess
, readProcessText
, ProcessOptions(..)
, defaultProcessOptions
, ProcessStdStream(..)
, ProcessState(..)
, ExitCode(..)
, waitProcessExit
, getProcessPID
, killPID
, getPriority, setPriority
, spawn
, ProcessFlag
, pattern PROCESS_SETUID
, pattern PROCESS_SETGID
, pattern PROCESS_WINDOWS_VERBATIM_ARGUMENTS
, pattern PROCESS_DETACHED
, pattern PROCESS_WINDOWS_HIDE_CONSOLE
, pattern PROCESS_WINDOWS_HIDE_GUI
, Signal
, pattern SIGTERM
, pattern SIGINT
, pattern SIGKILL
, pattern SIGHUP
, Priority
, pattern PRIORITY_LOW
, pattern PRIORITY_BELOW_NORMAL
, pattern PRIORITY_NORMAL
, pattern PRIORITY_ABOVE_NORMAL
, pattern PRIORITY_HIGH
, pattern PRIORITY_HIGHEST
) where
import Control.Concurrent
import Control.Concurrent.STM
import Control.Monad
import Data.Primitive.ByteArray
import GHC.Generics
import GHC.Conc.Signal (Signal)
import System.Exit
import Z.Data.CBytes
import Z.Data.CBytes as CBytes
import Z.Data.JSON (EncodeJSON, ToValue, FromValue)
import Z.Data.Vector as V
import Z.Data.Text as T
import Z.Data.Text.ShowT (ShowT)
import qualified Data.List as List
import Z.Data.Array.Unaligned
import Z.Foreign
import Z.IO.Buffered
import Z.IO.Exception
import Z.IO.Network.IPC
import Z.IO.Resource
import Z.IO.UV.FFI
import Z.IO.UV.Manager
import Z.IO.UV.UVStream
defaultProcessOptions :: ProcessOptions
defaultProcessOptions = ProcessOptions
{ processFile = "./main"
, processArgs = []
, processEnv = Nothing
, processCWD = "."
, processFlags = 0
, processUID = UID 0
, processGID = GID 0
, processStdStreams = (ProcessIgnore, ProcessIgnore, ProcessIgnore)
}
data ProcessState = ProcessRunning PID | ProcessExited ExitCode
deriving (Show, Eq, Ord, Generic)
deriving anyclass (ShowT, EncodeJSON, ToValue, FromValue)
waitProcessExit :: TVar ProcessState -> IO ExitCode
waitProcessExit svar = atomically $ do
s <- readTVar svar
case s of ProcessExited e -> return e
_ -> retry
getProcessPID :: TVar ProcessState -> IO (Maybe PID)
getProcessPID svar = atomically $ do
s <- readTVar svar
case s of ProcessRunning pid -> return (Just pid)
_ -> return Nothing
killPID :: HasCallStack => PID -> Signal -> IO ()
killPID (PID pid) sig = throwUVIfMinus_ (uv_kill pid sig)
pattern SIGTERM :: Signal
pattern SIGINT :: Signal
pattern SIGKILL :: Signal
pattern SIGHUP :: Signal
pattern SIGTERM = 15
{-# LINE 129 "Z/IO/Process.hsc" #-}
pattern SIGINT = 2
{-# LINE 130 "Z/IO/Process.hsc" #-}
pattern SIGKILL = 9
pattern SIGHUP = 1
{-# LINE 132 "Z/IO/Process.hsc" #-}
initProcess :: ProcessOptions -> Resource (Maybe UVStream, Maybe UVStream, Maybe UVStream, TVar ProcessState)
initProcess opt = initResource (spawn opt) $ \ (s0,s1,s2, pstate) -> void . forkIO $ do
_ <- waitProcessExit pstate
forM_ s0 closeUVStream
forM_ s1 closeUVStream
forM_ s2 closeUVStream
readProcess :: HasCallStack
=> ProcessOptions
-> V.Bytes
-> IO (V.Bytes, V.Bytes, ExitCode)
readProcess opts inp = do
withResource (initProcess opts{processStdStreams=(ProcessCreate, ProcessCreate, ProcessCreate)})
$ \ (Just s0, Just s1, Just s2, pstate) -> do
r1 <- newEmptyMVar
r2 <- newEmptyMVar
_ <- forkIO $ do
withPrimVectorSafe inp (writeOutput s0)
closeUVStream s0
_ <- forkIO $ do
b1 <- newBufferedInput s1
readAll' b1 >>= putMVar r1
_ <- forkIO $ do
b2 <- newBufferedInput s2
readAll' b2 >>= putMVar r2
(,,) <$> takeMVar r1 <*> takeMVar r2 <*> waitProcessExit pstate
readProcessText :: HasCallStack
=> ProcessOptions
-> T.Text
-> IO (T.Text, T.Text, ExitCode)
readProcessText opts inp = do
(out, err, e) <- readProcess opts (T.getUTF8Bytes inp)
return (T.validate out, T.validate err, e)
spawn :: HasCallStack => ProcessOptions -> IO (Maybe UVStream, Maybe UVStream, Maybe UVStream, TVar ProcessState)
spawn ProcessOptions{..} = do
(MutableByteArray popts#) <- newByteArray ((64))
{-# LINE 198 "Z/IO/Process.hsc" #-}
(MutableByteArray pstdio#) <- newByteArray (((16))*3)
{-# LINE 199 "Z/IO/Process.hsc" #-}
pokeMBA popts# ((40)) processFlags
{-# LINE 201 "Z/IO/Process.hsc" #-}
pokeMBA popts# ((56)) processUID
{-# LINE 202 "Z/IO/Process.hsc" #-}
pokeMBA popts# ((60)) processGID
{-# LINE 203 "Z/IO/Process.hsc" #-}
uvm <- getUVManager
let (s0, s1, s2) = processStdStreams
pokeMBA pstdio# ((0)) (processStdStreamFlag s0)
{-# LINE 209 "Z/IO/Process.hsc" #-}
uvs0' <- case s0 of
ProcessInherit fd -> do
pokeMBA pstdio# ((8)) fd
{-# LINE 212 "Z/IO/Process.hsc" #-}
return Nothing
ProcessCreate -> do
(uvs0, _) <- acquire (initIPCStream uvm)
pokeMBA pstdio# ((8)) (uvsHandle uvs0)
{-# LINE 216 "Z/IO/Process.hsc" #-}
return (Just uvs0)
_ -> return Nothing
pokeMBA pstdio# (((0))+((16)))
{-# LINE 220 "Z/IO/Process.hsc" #-}
(processStdStreamFlag s1)
uvs1' <- case s1 of
ProcessInherit fd -> do
pokeMBA pstdio# (((8))+((16))) fd
{-# LINE 224 "Z/IO/Process.hsc" #-}
return Nothing
ProcessCreate -> do
(uvs1, _) <- acquire (initIPCStream uvm)
pokeMBA pstdio# (((8))+((16)))
{-# LINE 228 "Z/IO/Process.hsc" #-}
(uvsHandle uvs1)
return (Just uvs1)
_ -> return Nothing
pokeMBA pstdio# (((0))+((16))*2)
{-# LINE 233 "Z/IO/Process.hsc" #-}
(processStdStreamFlag s2)
uvs2' <- case s2 of
ProcessInherit fd -> do
pokeMBA pstdio# (((8))+((16))*2) fd
{-# LINE 237 "Z/IO/Process.hsc" #-}
return Nothing
ProcessCreate -> do
(uvs2, _) <- acquire (initIPCStream uvm)
pokeMBA pstdio# (((8))+((16))*2)
{-# LINE 241 "Z/IO/Process.hsc" #-}
(uvsHandle uvs2)
return (Just uvs2)
_ -> return Nothing
let mkEnv (k, v) = CBytes.concat [k, "=", v]
allEnv = case processEnv of
Just e -> List.map mkEnv e
_ -> []
envLen = case processEnv of
Just e -> List.length e
_ -> -1
(slot, pid) <- withCBytesUnsafe processFile $ \ pfile ->
withCBytesUnsafe processCWD $ \ pcwd ->
withCBytesListUnsafe processArgs $ \ pargs argsLen ->
withCBytesListUnsafe allEnv $ \ penv _ ->
withUVManager uvm $ \ loop -> do
slot <- throwUVIfMinus (hs_uv_spawn loop popts# pfile
pargs argsLen penv envLen pcwd pstdio#)
pid <- peekBufferSizeTable uvm slot
return (slot, pid)
exitLock <- getBlockMVar uvm slot
ps <- newTVarIO (ProcessRunning (PID (fromIntegral pid)))
_ <- forkFinally (takeMVar exitLock) $ \ r -> do
case r of
Left _ -> atomically (writeTVar ps (ProcessExited (ExitFailure (-1))))
Right e ->
let e' = if e == 0 then ExitSuccess else ExitFailure e
in atomically (writeTVar ps (ProcessExited e'))
return (uvs0', uvs1', uvs2', ps)
getPriority :: HasCallStack => PID -> IO Priority
getPriority pid = do
(p, _) <- allocPrimUnsafe $ \ p_p -> throwUVIfMinus_ (uv_os_getpriority pid p_p)
return p
setPriority :: HasCallStack => PID -> Priority -> IO ()
setPriority pid p = throwUVIfMinus_ (uv_os_setpriority pid p)