-- Ausnahme in child, Exception -- #hide module HsShellScript.ProcErr where import Control.Concurrent.MVar import Control.Exception import Control.Monad import Data.IORef as IORef import Data.Int import Data.List import Data.Maybe import Data.Typeable import Foreign import Foreign.C import Foreign.C.Error import GHC.Conc import GHC.IO hiding (finally, bracket) import GHC.IO.Exception -- SystemError, ioe_* import GHC.IO.Handle import GHC.IO.Handle.Internals -- withHandle', do_operation import GHC.IO.Handle.Types hiding (close) import HsShellScript.Args import HsShellScript.Shell import Prelude hiding (catch) import System.Directory import System.Environment import System.Exit import System.IO import System.IO.Error hiding (catch) import System.Posix import System.Posix.IO import System.Posix.Process (forkProcess) import System.Posix.Types -- Fd import qualified GHC.IO.FD as FD import qualified System.IO.Error -- mkIOError infixr 2 -|- -- left handed, stdout infixr 2 =|- -- left handed, stderr infixl 2 -|= -- right handed, stdout infixl 2 =|= -- right handed, stderr infixl 3 ->- -- write stdout to file infixl 3 =>- -- write stderr to file infixl 3 ->>- -- append stdout to file infixl 3 =>>- -- append stderr to file infixl 3 -<- -- read stdin from file or string infixl 3 -&>- -- write stdout and stderr to file infixl 3 -&>>- -- append stdout and stderr to file {- | Improved version of @System.Posix.Files.setFileMode@, which sets the file name in the @IOError@ which is thrown in case of an error. The implementation in GHC 6.2.2 neglects to do this. >setFileMode' path mode = > fill_in_filename path $ > setFileMode path mode -} setFileMode' :: FilePath -> FileMode -> IO () setFileMode' path mode = fill_in_filename path $ setFileMode path mode -- | -- Execute an IO action as a separate process, and wait for it to finish. -- Report errors as exceptions. -- -- The program forks a child process and performs the specified action. -- Then it waits for the child process to finish. If it exits in any way -- which indicates an error, the @ProcessStatus@ is thrown as an -- exception. -- -- When the action throws an @IOError@, it is transmitted to the parent. -- It is then raised there, as if it happened locally. The child then aborts -- quietly with an exit code of 0. -- -- When used in conjunction with an @exec@ variant, this means that the parent -- process can tell the difference between failure of the @exec@ call itself, -- and failure of the program being executed. You get the @IOError@, which -- happened in the child when calling @executeFile@ (GHC hierarchical -- libraries). Of course, the action can prevent this form happening, by -- itself catching @IOError@s. -- -- The parent process waits for the child process, if it has been stopped by a -- signal. -- -- See "HsShellScript#subr" for further details. -- -- -- Examples: -- -- Run a program with the environment replaced: -- -- >subproc (execpe "foobar" ["1","2","3"] new_env) -- -- This results in a @ProcessStatus@ exception: -- -- >subproc (exec "/bin/false" []) -- -- This results in an @IOError@ (unless you actually have @\/frooble@): -- -- >subproc (exec "/frooble" []) -- -- See 'runprog', 'spawn', 'exec', 'execp', 'exece', 'execpe'. subproc :: IO a -- ^ Action to execute in a child process -> IO () subproc io = do -- Make new error channel (readend, writeend) <- createPipe -- Set it to "close on exec" {#call c_close_on_exec#} (fromIntegral writeend) -- Fork child process flush_outerr pid <- forkProcess (do -- Child process closeFd readend -- Do it. In case some part of the child hands over an IOError to -- be transmitted to the parent, do that and abort quietly. child $ catch (io >> return ()) (\(ioe::IOError) -> do send_ioerror writeend ioe flush_outerr _exit 0 ) ) -- Parent process closeFd writeend -- Read the complete contents of the error channel as an encoding -- of a possible IOError (until closed on the other side). -- -- The write end in the child stays open, until either -- - exec in the child -- - child terminates (not merely stops) -- - child sends ioerror and closes the channel mioe <- receive_ioerror readend -- Waits for the child to finish. The process status is "Exited -- ExitSuccess" in case the child transmitted an error. (Just ps) <- getProcessStatus True False (fromIntegral pid) if ps == Exited ExitSuccess then return () else throw ps -- In case an IOError has been received, throw it locally case mioe of Just ioe -> ioError ioe Nothing -> return () -- | -- Execute an IO action as a separate process, and wait for it to finish. -- Report errors as exceptions. -- -- /This function is included only for backwards compatibility. New code should/ -- /use/ 'subproc' instead/, which has better error handling./ -- -- The program forks a child process and performs the specified action. -- Then it waits for the child process to finish. If it exits in any way -- which indicates an error, the @ProcessStatus@ is thrown. -- -- The parent process waits for the child processes, which have been stopped by -- a signal. -- -- See "HsShellScript#subr" for further details. -- -- See 'subproc', 'spawn'. call :: IO a -- ^ action to execute as a child process -> IO () call io = do pid <- spawn_loc "call" io (Just ps) <- getProcessStatus True False pid if ps == Exited ExitSuccess then return () else throw ps -- | -- Execute an IO action as a separate process, and continue without waiting -- for it to finish. -- -- The program forks a child process, which performs the specified action and terminates. -- The child's process ID is returned. -- -- See "HsShellScript#subr" for further details. -- -- See 'subproc'. spawn :: IO a -- ^ Action to execute as a child process. -> IO ProcessID -- ^ Process ID of the new process. spawn = spawn_loc "spawn" spawn_loc :: String -> IO a -> IO ProcessID spawn_loc loc io = do flush_outerr pid <- forkProcess (child io) return pid -- | -- Run an external program. This starts a program as a child -- process, and waits for it to finish. The executable is searched via the -- @PATH@. -- -- /This function is included for backwards compatibility only. New code should/ -- /use/ 'runprog'/, which has much better error handling./ -- -- When the specified program can't be executed, an error message is printed, and the main process -- gets a @ProcessStatus@ thrown, with the value @Exited -- (ExitFailure 1)@. This means that the main program can't distinguish between -- failure of calling the program and the program exiting with an exit code of -- 1. However, an error message \"Error calling ...\", including the description in the IOError produced -- by the failed @execp@ call, is printed on @stderr@. -- -- @run prog par@ is essentially @call (execp prog par)@. -- -- Example: -- -- >run "/usr/bin/foobar" ["some", "args"] -- > `catch` (\ps -> do -- oops... -- > ) -- -- See 'runprog', 'subproc', 'spawn'. run :: FilePath -- ^ Name of the executable to run -> [String] -- ^ Command line arguments -> IO () run prog par = call (child $ execp prog par) {- | An error which occured when calling an external program via 'runprog'. The fields specifiy the details of the call. See 'show_runerror', 'to_ioe', 'as_ioe', @System.Posix.ProcessStatus@. -} data RunError = RunError { re_prog :: String -- ^ Program name , re_pars :: [String] -- ^ Program arguments , re_env :: [(String,String)] -- ^ The environment in use when the call was done , re_wd :: String -- ^ The working directory when the call was done , re_ps :: ProcessStatus -- ^ The process status of the failure , re_errno :: Maybe CInt -- ^ The error (errno) code } deriving (Show, Typeable, Eq) instance Exception RunError -- | Make a readable error message. This includes all the -- fields of @RunError@ except for the environment. -- -- See 'RunError'. show_runerror :: RunError -> String show_runerror re = "The following program failed:\n\ \ " ++ shell_command (re_prog re) (re_pars re) ++ "\n" ++ explain_processstatus (re_ps re) ++ "\n\ \The working directory was " ++ quote (re_wd re) ++ "." -- | Generate a human-readable description of a @ProcessStatus@. -- -- See 'exec', 'runprog' and @System.Posix.ProcessStatus@ in the GHC hierarchical -- library documentation. explain_processstatus :: ProcessStatus -> String explain_processstatus ps = case ps of Exited (ExitFailure ec) -> "The program exited abnormally with an exit code of " ++ show ec ++ "." Exited ExitSuccess -> "The program finished normally." Terminated sig -> "The process was terminated by signal " ++ showsig sig ++ "." Stopped sig -> "The process was stopped by signal " ++ showsig sig ++ "." where showsig sig = show sig ++ case lookup sig signals of Just name -> " (" ++ name ++ ")" Nothing -> "" signals = [(sigABRT, "SIGABRT"), (sigALRM, "SIGALRM"), (sigBUS, "SIGBUS"), (sigCHLD, "SIGCHLD"), (sigCONT, "SIGCONT"), (sigFPE, "SIGFPE"), (sigHUP, "SIGHUP"), (sigILL, "SIGILL"), (sigINT, "SIGINT"), (sigKILL, "SIGKILL"), (sigPIPE, "SIGPIPE"), (sigQUIT, "SIGQUIT"), (sigSEGV, "SIGSEGV"), (sigSTOP, "SIGSTOP"), (sigTERM, "SIGTERM"), (sigTSTP, "SIGTSTP"), (sigTTIN, "SIGTTIN"), (sigTTOU, "SIGTTOU"), (sigUSR1, "SIGUSR1"), (sigUSR2, "SIGUSR2"), (sigPOLL, "SIGPOLL"), (sigPROF, "SIGPROF"), (sigSYS, "SIGSYS"), (sigTRAP, "SIGTRAP"), (sigURG, "SIGURG"), (sigVTALRM, "SIGVTALRM"), (sigXCPU, "SIGXCPU"), (sigXFSZ, "SIGXFSZ")] -- | Convert a @RunError@ to an @IOError@. -- -- The @IOError@ type isn't capable of holding all the information which is -- contained in a @RunError@. The environment is left out, and most of the other -- fields are included only informally, in the description. -- -- The fields of the generated @IOError@ are: -- -- * The handle (@ioeGetHandle@): @Nothing@ -- -- * The error type (@ioeGetErrorType@): @GHC.IO.Exception.SystemError@ -- -- * @ioe_location@: @\"runprog\"@ -- -- * @ioe_description@: The error message, as procuded by @show_runerror@. -- -- * @ioe_filename@: This is @Just (shell_command /prog/ /pars/)@, with /prog/ -- and /pars/ being the program and its arguments. -- -- See 'as_ioe', 'runprog', 'show_runerror'. to_ioe :: RunError -> IOError to_ioe re = GHC.IO.Exception.IOError { ioe_handle = Nothing, ioe_type = GHC.IO.Exception.SystemError, ioe_location = "runprog", ioe_description = show_runerror re, ioe_filename = Just (shell_command (re_prog re) (re_pars re)), ioe_errno = re_errno re } -- | Call the specified IO action (which is expected to contain calls of -- @runprog@) and convert any @RunError@ exceptions to @IOError@s. -- -- The conversion is done by @to_ioe@. -- -- See 'to_ioe', 'runprog'. as_ioe :: IO a -> IO a as_ioe io = io `catch` (\(re::RunError) -> ioError (to_ioe re)) -- | -- Run an external program, and report errors as exceptions. The executable is -- searched via the @PATH@. -- -- In case the program exits in an way which indicates an error, or is -- terminated by a signal, a @RunError@ is thrown. It -- contains the details of the call. The @runprog@ action can also be converted -- to throw @IOError@s instaed, by applying @as_ioe@ to it. Either can be used -- to generate an informative error message. -- -- In case of starting the program itself failed, an @IOError@ is thrown. -- -- @runprog prog par@ is essentially @subproc (execp prog par)@. -- -- Example 1: -- -- >do runprog "foo" ["some", "args"] -- > ... -- >`catch` (\re -> do errm (show_runerror re) -- > ... -- > ) -- -- Example 2: -- -- >do as_ioe $ runprog "foo" ["some", "args"] -- > ... -- >`catch` (\ioe -> do errm (show_ioerror ioe) -- > ... -- > ) -- -- See 'subproc', 'spawn', 'RunError', 'show_runerror', 'to_ioe', 'as_ioe'. runprog :: FilePath -- ^ Name of the executable to run -> [String] -- ^ Command line arguments -> IO () runprog prog pars = subproc (execp prog pars) `catch` -- Convert ProcessStatus error to RunError (\(ps::ProcessStatus) -> do env <- System.Environment.getEnvironment wd <- getCurrentDirectory (Errno c_errno) <- getErrno throw (RunError { re_prog = prog , re_pars = pars , re_env = env , re_wd = wd , re_ps = ps , re_errno = if c_errno /= (0::CInt) then Just c_errno else Nothing })) -- | Print an action as a shell command, then perform it. -- -- This is used with actions such as 'runprog', 'exec' or 'subproc'. For instance, -- @echo runprog prog args@ is a variant of @runprog prog args@, which prints what -- is being done before doing it. -- -- See 'runprog', 'subproc', 'exec'. echo :: ( FilePath -> [String] -> IO () ) -- ^ Action to perform -> FilePath -- ^ Name or path of the executable to run -> [String] -- ^ Command line arguments -> IO () echo action path args = do putStrLn (shell_command path args) action path args -- | Execute an external program. This replaces the running process. The path isn't searched, the environment isn't changed. In case of failure, -- an IOError is thrown. -- -- >exec path args = -- > execute_file path False args Nothing -- -- See 'execute_file', "HsShellScript#exec". exec :: String -- ^ Full path to the executable -> [String] -- ^ Command line arguments -> IO a -- ^ Never returns exec path args = execute_file path False args Nothing -- | Execute an external program. This replaces the running process. The path is searched, the environment isn't changed. In case of failure, -- an IOError is thrown. -- -- >execp prog args = -- > execute_file prog True args Nothing -- -- See 'execute_file', "HsShellScript#exec". execp :: String -- ^ Name or path of the executable -> [String] -- ^ Command line arguments -> IO a -- ^ Never returns execp prog args = execute_file prog True args Nothing -- | Execute an external program. This replaces the running process. The path isn't searched, the environment of the program is set as specified. In -- case of failure, an IOError is thrown. -- -- >exece path args env = -- > execute_file path False args (Just env) -- -- See 'execute_file', "HsShellScript#exec". exece :: String -- ^ Full path to the executable -> [String] -- ^ Command line arguments -> [(String,String)] -- ^ New environment -> IO a -- ^ Never returns exece path args env = execute_file path False args (Just env) -- | Execute an external program. This replaces the running process. The path is searched, the environment of the program is set as specified. In -- case of failure, an IOError is thrown. -- -- >execpe prog args env = -- > execute_file prog True args (Just env) -- -- See 'execute_file', "HsShellScript#exec". execpe :: String -- ^ Name or path of the executable -> [String] -- ^ Command line arguments -> [(String,String)] -- ^ New environment -> IO a -- ^ Never returns execpe prog args env = execute_file prog True args (Just env) {- | Build left handed pipe of stdout. \"@p -|- q@\" builds an IO action from the two IO actions @p@ and @q@. @q@ is executed in an external process. The standard output of @p@ is sent to the standard input of @q@ through a pipe. The result action consists of forking off @q@ (connected with a pipe), and @p@. The result action does /not/ run @p@ in a separate process. So, the pipe itself can be seen as a modified action @p@, forking a connected @q@. The pipe is called \"left handed\", because @p@ remains unforked, and not @q@. /The exit code of q is silently ignored./ The process ID of the forked copy of @q@ isn't returned to the caller, so it's lost. See "HsShellScript#subr" and "HsShellScript#exec" for further details. Examples: >call (exec "/usr/bin/foo" [] -|- exec "/usr/bin/bar" []) >call ( execp "foo" ["..."] > -|= ( -- Do something with foo's output > do cnt <- lazy_contents "-" > ... > ) > ) See 'subproc', '(=|-)', '(-|=)'. -} (-|-) :: IO a -- ^ Action which won't be forked -> IO b -- ^ Action which will be forked and connected with a pipe -> IO a -- ^ Result action p -|- q = do (Just h, _, _, _) <- pipe_fork_dup q True False False res <- redirect stdout h p hClose h return res {- | Build left handed pipe of stderr. \"@p =|- q@\" builds an IO action from the two IO actions @p@ and @q@. @q@ is executed in an external process. The standard error output of @p@ is sent to the standard input of @q@ through a pipe. The result action consists of forking off @q@ (connected with a pipe), and @p@. The result action does /not/ run @p@ in a separate process. So, the pipe itself can be seen as a modified action @p@, forking a connected @q@. The pipe is called \"left handed\", because @p@ has this property, and not @q@. /The exit code of q is silently ignored./ The process ID of the forked copy of @q@ isn't returned to the caller, so it's lost. See "HsShellScript#subr" and "HsShellScript#exec" for further details. Example: >call (exec "/usr/bin/foo" [] =|- exec "/usr/bin/bar" []) See 'subproc', '(-|-)', '(-|=)'. -} (=|-) :: IO a -- ^ Action which won't be forked -> IO b -- ^ Action which will be forked and connected with a pipe -> IO a -- ^ Result action p =|- q = do (Just h, _, _, _) <- pipe_fork_dup q True False False res <- redirect stderr h p hClose h return res {- | Build right handed pipe of stdout. \"@p -|= q@\" builds an IO action from the two IO actions @p@ and @q@. @p@ is executed in an external process. The standard output of @p@ is sent to the standard input of @q@ through a pipe. The result action consists of forking off @p@ (connected with a pipe), and @q@. The result action does /not/ run @q@ in a separate process. So, the pipe itself can be seen as a modified action @q@, forking a connected @p@. The pipe is called \"right handed\", because @q@ has this property, and not @p@. /The exit code of p is silently ignored./ The process ID of the forked copy of @q@ isn't returned to the caller, so it's lost. See "HsShellScript#subr" and "HsShellScript#exec" for further details. Example: >@call (exec \"\/usr\/bin\/foo\" [] -|= exec \"\/usr\/bin\/bar\" [])@ See 'subproc', '(=|-)', '(=|=)'. -} (-|=) :: IO a -- ^ Action which will be forked and connected with a pipe -> IO b -- ^ Action which won't be forked -> IO b -- ^ Result action p -|= q = do (_, Just h, _, _) <- pipe_fork_dup p False True False res <- redirect stdin h q hClose h return res {- | Build right handed pipe of stderr. \"@p =|= q@\" builds an IO action from the two IO actions @p@ and @q@. @p@ is executed in an external process. The standard error output of @p@ is sent to the standard input of @q@ through a pipe. The result action consists of forking off @p@ (connected with a pipe), and @q@. The result action does /not/ run @q@ in a separate process. So, the pipe itself can be seen as a modified action @q@, forking a connected @p@. The pipe is called \"right handed\", because @q@ has this property, and not @p@. /The exit code of p is silently ignored./ The process ID of the forked copy of @q@ isn't returned to the caller, so it's lost. See "HsShellScript#subr" and "HsShellScript#exec" for further details. Example: > call (exec "/usr/bin/foo" [] =|= exec "/usr/bin/bar" []) See 'subproc', '=|-', '-|='. -} (=|=) :: IO a -- ^ Action which will be forked and connected with a pipe -> IO b -- ^ Action which won't be forked -> IO b -- ^ Result action p =|= q = do (_, _, Just h, _) <- pipe_fork_dup p False False True res <- redirect stdin h q hClose h return res -- | Temporarily replace a handle. This makes a backup copy of the original handle (typically a standard handle), overwrites it with the specified one, -- runs the specified action, and restores the handle from the backup. -- -- Example: -- -- > h <- openFile "/tmp/log" WriteMode -- > redirect stdout h io -- > hClose h -- -- This is the same as -- -- > io ->- "/tmp/log" -- -- See '-|-', '=|-'. redirect :: Handle -- ^ Handle to replace -> Handle -- ^ Handle to replace it with -> IO a -- ^ Action -> IO a redirect handle replacement io = bracket (do bak <- hDuplicate handle hDuplicateTo replacement handle return bak ) (\bak -> do hDuplicateTo bak handle hClose bak ) (\_ -> io) redirect_helper stdh mode io path = do h <- openFile path mode res <- redirect stdh h io hClose h return res {- | Redirect the standard output of the specified IO action to a file. The file will be overwritten, if it already exists. What's actually modified is the @stdout@ handle, not the file descriptor 1. The @exec@ functions know about this. See "HsShellScript#fdpipes" and "HsShellScript#exec" for details. Example: >run "/some/program" [] ->- "/tmp/output" Note: You can't redirect to @\"\/dev\/null\"@ this way, because GHC 6.4's @openFile@ throws an \"invalid argument\" IOError. (This may be a bug in the GHC 6.4 libraries). Use @->>-@ instead. See 'subproc', 'runprog', '->>-', '=>-'. -} (->-) :: IO a -- ^ Action, whose output will be redirected -> FilePath -- ^ File to redirect the output to -> IO a -- ^ Result action (->-) = redirect_helper stdout WriteMode {- | Redirect the standard output of the specified IO action to a file. If the file already exists, the output will be appended. What's actually modified is the @stdout@ handle, not the file descriptor 1. The @exec@ functions know about this. See "HsShellScript#fdpipes" and "HsShellScript#exec" for details. Example: >run "/some/noisy/program" [] ->>- "/dev/null" See 'subproc', 'runprog', '(->-)', '(=>>-)'. -} (->>-) :: IO a -- ^ Action, whose output will be redirected -> FilePath -- ^ File to redirect the output to -> IO a -- ^ Result action (->>-) = redirect_helper stdout AppendMode {- | Redirect the standard error output of the specified IO action to a file. If the file already exists, it will be overwritten. What's actually modified is the @stderr@ handle, not the file descriptor 2. The @exec@ functions know about this. See "HsShellScript#fdpipes" and "HsShellScript#exec" for details. Note: You can't redirect to @\"\/dev\/null\"@ this way, because GHC 6.4's @openFile@ throws an \"invalid argument\" IOError. (This may be a bug in the GHC 6.4 libraries). Use @=>>-@ instead. Example: >run "/path/to/foo" [] =>- "/tmp/errlog" See 'subproc', 'runprog', '(->-)', '(=>>-)'. -} (=>-) :: IO a -- ^ Action, whose error output will be redirected -> FilePath -- ^ File to redirect the error output to -> IO a -- ^ Result action (=>-) = redirect_helper stderr WriteMode {- | Redirect the standard error output of the specified IO action to a file. If the file already exists, the output will be appended. What's actually modified is the @stderr@ handle, not the file descriptor 2. The @exec@ functions know about this. See "HsShellScript#fdpipes" and "HsShellScript#exec" for details. Example: >run "/some/program" [] =>>- "/dev/null" See 'subproc', 'runprog', '(->>-)', '(=>-)'. -} (=>>-) :: IO a -- ^ Action, whose error output will be redirected -> FilePath -- ^ File to redirect the error output to -> IO a -- ^ Result action (=>>-) = redirect_helper stderr AppendMode {- | Redirect both stdout and stderr to a file. This is equivalent to the shell's @&>@ operator. If the file already exists, it will be overwritten. What's actually modified are the @stdout@ and @stderr@ handles, not the file descriptors 1 and 2. The @exec@ functions know about this. See "HsShellScript#fdpipes" and "HsShellScript#exec" for details. Note: You can't redirect to @\"\/dev\/null\"@ this way, because GHC 6.4's @openFile@ throws an \"invalid argument\" IOError. (This may be a bug in the GHC 6.4 libraries). Use @-&>>-@ instead. >(-&>-) io path = err_to_out io ->- path Example: @call (exec \"\/path\/to\/foo\" [] -&\>- \"log\")@ See '(-&>>-)', 'err_to_out'. -} (-&>-) :: IO a -- ^ Action, whose output and error output will be redirected -> FilePath -- ^ File to redirect to -> IO a -- ^ Result action (-&>-) io path = err_to_out io ->- path {- | Redirect both stdout and stderr to a file. If the file already exists, the output will be appended. What's actually modified are the @stdout@ and @stderr@ handles, not the file descriptors 1 and 2. The @exec@ functions know about this. See "HsShellScript#fdpipes" and "HsShellScript#exec" for details. >(-&>>-) io path = (err_to_out >> io) ->>- path Example: >run "/some/noisy/program" [] -&>>- "/dev/null" See '(-&>-)', 'out_to_err'. -} (-&>>-) :: IO a -- ^ Action, whose output and error output will be redirected -> FilePath -- ^ File to redirect to -> IO a -- ^ Result action (-&>>-) io path = err_to_out io ->>- path {- | Redirect stdin from a file. This modifies the specified action, such that the standard input is read from a file. What's actually modified is the @stdin@ handle, not the file descriptor 0. The @exec@ functions know about this. See "HsShellScript#fdpipes" and "HsShellScript#exec" for details. Example: @call (exec \"\/path\/to\/foo\" [] -\<- \"bar\")@ See 'exec', 'runprog', '(->-)', '(=>-)'. -} (-<-) :: IO a -> FilePath -> IO a (-<-) = redirect_helper stdin ReadMode {- | Send the error output of the specified action to its standard output. What's actually modified is the @stdout@ handle, not the file descriptor 1. The @exec@ functions know about this. See "HsShellScript#fdpipes" and "HsShellScript#exec" for details. >err_to_out = redirect stderr stdout See 'redirect'. -} err_to_out :: IO a -> IO a err_to_out = redirect stderr stdout {- | Send the output of the specified action to its standard error output. What's actually modified is the @stderr@ handle, not the file descriptor 2. The @exec@ functions know about this. See "HsShellScript#fdpipes" and "HsShellScript#exec" for details. >redirect stdout stderr See 'redirect'. -} out_to_err :: IO a -> IO a out_to_err = redirect stdout stderr -- Run an IO action as a new process, and optionally connect its -- stdin, stdout and stderr via pipes. pipe_fork_dup :: IO a -- Action to run in a new process. -> Bool -- make stdin pipe? -> Bool -- make stdout pipe? -> Bool -- make stderr pipe? -> IO ( Maybe Handle -- Handle to the new process's stdin, if applicable. , Maybe Handle -- Handle to the new process's stdout, if applicable. , Maybe Handle -- Handle to the new process's stderr, if applicable. , ProcessID ) pipe_fork_dup io fd0 fd1 fd2 = do flush_outerr pipe0 <- pipe_if fd0 pipe1 <- pipe_if fd1 pipe2 <- pipe_if fd2 pid <- forkProcess (do -- child dup_close pipe0 stdin True dup_close pipe1 stdout False dup_close pipe2 stderr False child io ) -- parent h0 <- finish_pipe pipe0 True h1 <- finish_pipe pipe1 False h2 <- finish_pipe pipe2 False return (h0, h1, h2, pid) where -- Make a pipe, if applicable. pipe_if False = return Nothing --pipe_if True = fmap Just $ createPipe -- Just (read,write) pipe_if True = do (read, write) <- createPipe return (Just (read,write)) -- Child work after fork: connect a fd of the new process to the pipe. dup_close :: Maybe (Fd, Fd) -- maybe the pipe -> Handle -- which handle descriptor to connect to the pipe -> Bool -- whether the child reads from this pipe -> IO () dup_close Nothing _ _ = return () dup_close m@(Just (readend,writeend)) dest True = do h <- System.Posix.fdToHandle readend hDuplicateTo h dest hClose h closeFd writeend dup_close m@(Just (readend,writeend)) dest False = do h <- System.Posix.fdToHandle writeend hDuplicateTo h dest hClose h closeFd readend -- Parent work after fork: close surplus end of the pipe and make a handle from the other end. finish_pipe :: Maybe (Fd, Fd) -- maybe the pipe -> Bool -- whether the fd is for reading -> IO (Maybe Handle) finish_pipe Nothing _ = return Nothing finish_pipe (Just (readend,writeend)) read = do closeFd (if read then readend else writeend) let fd = if read then writeend else readend h <- System.Posix.fdToHandle fd return (Just h) -- | Run an IO action as a separate process, and pipe some text to its @stdin@. -- Then close the pipe and wait for the child process to finish. If it -- exits in a way which indicates an error, the @ProcessStatus@ is thrown. -- -- Example: @pipe_to \"blah\" $ exec \"\/usr\/bin\/foo\" [\"bar\"]@ -- -- See 'subproc', 'runprog', '-<-', 'h_pipe_to'. See "HsShellScript#fdpipes" for more details. pipe_to :: String -- ^ Text to pipe -> IO a -- ^ Action to run as a separate process, and to pipe to -> IO () pipe_to str io = do (h, pid) <- h_pipe_to io hPutStr h str hClose h (Just ps) <- getProcessStatus True False pid if ps == Exited ExitSuccess then return () else throw ps -- | Run an IO action as a separate process, and connect to its @stdin@ -- with a pipe. -- -- Example: @h \<- h_pipe_to $ exec \"\/usr\/bin\/foo\" [\"bar\"]@ -- -- See '-<-', 'pipe_to', 'pipe_from', 'pipe_from2'. See "HsShellScript#fdpipes" for more details. h_pipe_to :: IO a -- ^ Action to run as a separate process, and to pipe to -> IO (Handle, ProcessID) -- ^ Returns handle connected to the standard input of the child process, and the child's process ID h_pipe_to io = do (Just h, _, _, pid) <- pipe_fork_dup io True False False return (h, pid) -- | Run an IO action as a separate process, and read its @stdout@ -- strictly. Then wait for the child process to finish. This is like the -- backquote feature of shells. -- -- If the child process exits with a non-zero exit code, the -- @ProcessStatus@ is thrown. -- -- The whole output is returned, no trailing newline character is removed, like the shell does with backquotes. You may want to apply @chomp@ -- to the result. -- -- Example: -- -- >output <- pipe_from $ exec "/bin/foo" ["bar"] -- -- See 'exec', 'pipe_to', 'pipe_from2', 'h_pipe_from', 'lazy_pipe_from', 'chomp', 'silently'. See "HsShellScript#fdpipes" for more details. pipe_from :: IO a -- ^ Action to run as a separate process -> IO String -- ^ The called program's standard output pipe_from io = do (h, pid) <- h_pipe_from io txt <- hGetContents h seq (length txt) (hClose h) (Just ps) <- getProcessStatus True False pid if ps == Exited ExitSuccess then return txt else throw ps -- | Run an IO action as a separate process, and read its @stderr@ -- strictly. Then wait for the child process to finish, and return the text -- along with its exit code. -- -- Example: -- -- >(errmsg, ec) <- pipe_from2 $ exec "/bin/foo" ["bar"] ->- "/dev/null" -- > -- >when (ec /= Exited ExitSuccess) $ do -- > errm errmsg -- > ... -- -- See 'exec', 'pipe_to', 'pipe_from', 'h_pipe_from2', 'lazy_pipe_from2', 'silently'. See "HsShellScript#fdpipes" for more details. pipe_from2 :: IO a -- ^ Action to run as a separate process -> IO (String, ProcessStatus) -- ^ The called program's standard output pipe_from2 io = do (h, pid) <- h_pipe_from2 io txt <- hGetContents h seq (length txt) (hClose h) (Just ps) <- getProcessStatus True False pid return (txt, ps) -- | Run an IO action as a separate process, and connect to its @stdout@ -- with a pipe. -- -- A handle connected to the child process, and the process ID -- of the child are returned. The process ID can be used with -- @System.Posix.getProcessStatus@ to get the child's exit code. You must either -- ensure that all data has been read, or close the handle, before calling -- @getProcessStatus@ blockingly. Otherwise you'll get a deadlock. When you -- close the handle before all data has been read, then the child gets a -- @SIGPIPE@ signal. -- -- Example: -- -- >h <- h_pipe_from $ exec "/usr/bin/foo" ["bar"] -- -- See 'exec', 'pipe_to', 'h_pipe_from2', 'pipe_from', 'lazy_pipe_from', 'chomp', 'silently'. See "HsShellScript#fdpipes" for more details. h_pipe_from :: IO a -- ^ Action to run as a separate process, and to pipe from -> IO (Handle, ProcessID) -- ^ Returns handle connected to the standard output of the child process, and the child's process ID h_pipe_from io = do (_, Just h, _, pid) <- pipe_fork_dup io False True False return (h, pid) -- | Run an IO action as a separate process, and connect to its @stderr@ -- with a pipe. -- -- A handle connected to the child process' standard error output, and the process ID -- of the child are returned. The process ID can be used with -- @System.Posix.getProcessStatus@ to get the child's exit code. You must either -- ensure that all data has been read, or close the handle, before calling -- @getProcessStatus@ blockingly. Otherwise you'll get a deadlock. When you -- close the handle before all data has been read, then the child gets a -- @SIGPIPE@ signal. Of course, you can also use the process ID to kill the -- child process. -- -- Example: -- -- >h <- h_pipe_from2 $ exec "/usr/bin/foo" ["bar"] -- -- See 'exec', 'pipe_to', 'h_pipe_from', 'pipe_from2', 'lazy_pipe_from2', 'chomp', 'silently'. See "HsShellScript#fdpipes" for more details. h_pipe_from2 :: IO a -- ^ Action to run as a separate process, and to pipe from -> IO (Handle, ProcessID) -- ^ Returns handle connected to the standard output of the child process, and the child's process ID h_pipe_from2 io = do (_, _, Just h, pid) <- pipe_fork_dup io False False True return (h, pid) -- | Run an IO action as a separate process, and read its @stdout@, -- This is like the backquote feature of shells. The output is read -- lazily, as the returned string is evaluated. -- -- The child's output along with its process ID are returned. The process ID can -- be used with @System.Posix.getProcessStatus@ to get the child process' exit -- code. Be aware that you must evaluate the whole string, before calling -- @getProcessStatus@ blockingly, or you'll get a deadlock. -- -- The whole output is returned, no trailing newline character is removed, like -- the shell does with backquotes. You'll possibly want to apply 'chomp' to the -- result. -- -- Example: -- -- >(txt, pid) <- lazy_pipe_from $ exec "/usr/bin/foo" ["bar"] -- >... -- >-- Done, but must read the rest of the output -- >seq (length txt) (return ()) -- >(Just ps) <- getProcessStatus True False pid -- -- See 'exec', 'pipe_to', 'pipe_from', 'h_pipe_from', 'lazy_pipe_from2', 'silently'. See "HsShellScript#fdpipes" for more details. lazy_pipe_from :: IO a -- ^ Action to run as a separate process -> IO (String, ProcessID) -- ^ The action's lazy output and the process ID of the child process lazy_pipe_from io = do (_, Just h, _, pid) <- pipe_fork_dup io False True False txt <- hGetContents h return (txt, pid) -- | Run an IO action as a separate process, and read its @stderr@. The output -- is read lazily, as the returned string is evaluated. -- -- The child's error output along with its process ID are returned. The process -- ID can be used with @System.Posix.getProcessStatus@ to get the child process' -- exit code. Be aware that you must evaluate the whole string, before calling -- @getProcessStatus@ blockingly, or you'll get a deadlock. -- -- Example: -- -- >(errmsg, pid) <- lazy_pipe_from2 $ exec "/usr/bin/foo" ["bar"] ->- "/dev/null" -- >... -- >-- Read enough error messages, terminate the child. -- >signalProcess killProcess pid -- > -- >-- Make sure the file descriptor gets closed, or you may run out of file descriptors. -- >seq (length errmsg) (return ()) -- -- See 'exec', 'pipe_to', 'pipe_from2', 'h_pipe_from2', 'lazy_pipe_from', 'silently'. See "HsShellScript#fdpipes" for more details. lazy_pipe_from2 :: IO a -- ^ Action to run as a separate process -> IO (String, ProcessID) -- ^ The action's lazy output and the process ID of the child process lazy_pipe_from2 io = do (_, Just h, _, pid) <- pipe_fork_dup io False True False txt <- hGetContents h return (txt, pid) -- | Run an IO action as a separate process, and optionally connect to its -- @stdin@, its @stdout@ and its @stderr@ output with -- pipes. -- -- See 'pipe_from', 'pipe_from2', 'pipe_to'. pipes :: IO a -- ^ Action to run in a new process -> Bool -- ^ Whether to make stdin pipe -> Bool -- ^ Whether to make stdout pipe -> Bool -- ^ Whether to make stderr pipe -> IO ( Maybe Handle , Maybe Handle , Maybe Handle , ProcessID ) -- ^ Pipes to the new process's @stdin@, @stdout@ and @stderr@, if applicable; and its process id. pipes = pipe_fork_dup -- {- | Execute the supplied action. In case of an error, exit with an error -- message. -- -- > Noch nicht auf neue Exception-Bibliothek portiert. < -- -- An error is an exception, thrown using @throw@ as a type which is -- instance of @Typeable@. The type err is supposed to be a specific type used -- for specific errors. The program is terminated with @exitFailure@. -- -} -- abort :: Exception err -- => (err -> String) -- ^ Error message generation function -- -> IO a -- ^ IO action to monitor -- -> IO a -- ^ Same action, but abort with error message in case of user exception -- abort msgf io = -- io -- `catch` (\se -> hPutStrLn stderr (msgf errval) >> exitFailure) {- | Forcibly terminate the program, circumventing normal program shutdown. This is the @_exit(2)@ system call. No cleanup actions installed with @bracket@ are performed, no data buffered by file handles is written out, etc. -} _exit :: Int -- ^ Exit code -> IO a -- ^ Never returns _exit ec = do {#call _exit as _exit_prim#} (fromIntegral ec) error "Impossible error" -- never reached, only for the type checker -- | Generate an error message from an @errno@ value. This is the POSIX -- @strerror@ system library function. -- -- See the man page @strerror(3)@. strerror :: Errno -- ^ @errno@ value -> IO String -- ^ Corresponding error message strerror (Errno errno) = do peekCString ({#call pure strerror as foreign_strerror#} errno) -- | Read the global system error number. This is the POSIX @errno@ value. This -- function is redundant. Use @Foreign.C.Error.getErrno@ instead. errno :: IO Errno -- ^ @errno@ value errno = getErrno -- | Print error message corresponding to the specified @errno@ error -- number. This is similar to the POSIX system library function @perror@. -- -- See the man page @perror(3)@. perror' :: Errno -- ^ @errno@ error number -> String -- ^ Text to precede the message, separated by \"@: @\" -> IO () perror' errno txt = do str <- strerror errno hPutStrLn stderr ((if txt == "" then "" else txt ++ ": ") ++ str) -- | Print error message corresponding to the global @errno@ error -- number. This is the same as the POSIX system library function @perror@. -- -- See the man page @perror(3)@. perror :: String -- ^ Text to precede the message, separated by \"@: @\" -> IO () perror txt = do eno <- getErrno perror' eno txt -- | Print a message to @stderr@ and exit with an exit code -- indicating an error. -- -- >failIO msg = hPutStrLn stderr msg >> exitFailure failIO :: String -> IO a failIO meld = hPutStrLn stderr meld >> exitFailure -- | Modify an IO action to return the exit code of a failed program call, -- instead of throwing an exception. -- -- This is used to modify the error reporting behaviour of an IO action which -- uses 'run'/'runprog' or 'call'/'subproc'. When an external program exits with -- an exit code which indicates an error, normally an exception is thrown. After -- @exitcode@ has been applied, the exit code is retruned instead. -- -- The caught exceptions are 'RunError' and 'ProcessStatus'. Termination by a -- signal is still reported by an exception, which is passed through. -- -- Example: @ec \<- exitcode $ runprog \"foo\" [\"bar\"]@ -- -- See 'runprog', 'subproc', 'run', 'call'. exitcode :: IO () -- ^ Action to modify -> IO ExitCode -- ^ Modified action exitcode io = do io return ExitSuccess `catch` (\processstatus -> case processstatus of (Exited ec) -> return ec ps -> throw ps) `catch` (\re -> case re_ps re of (Exited ec) -> return ec ps -> throw re) -- |Create and throw an @IOError@ from the current @errno@ value, an optional handle and an optional file name. -- -- This is an extended version of the @Foreign.C.Error.throwErrno@ function -- from the GHC libraries, which additionally allows to specify a handle and a file -- name to include in the @IOError@ thrown. -- -- See @Foreign.C.Error.throwErrno@, @Foreign.C.Error.errnoToIOError@. throwErrno' :: String -- ^ Description of the location where the error occurs in the program -> Maybe Handle -- ^ Optional handle -> Maybe FilePath -- ^ Optional file name (for failing operations on files) -> IO a throwErrno' loc maybe_handle maybe_filename = do errno <- getErrno ioError (errnoToIOError loc errno maybe_handle maybe_filename) -- |Convert an @IOError@ to a string. -- -- There is an instance declaration of @IOError@ in @Show@ in the @GHC.IO@ library, but @show_ioerror@ produces a more readable, and more -- complete, message. show_ioerror :: IOError -> String show_ioerror ioe = "IO-Error\n\ \ Error type: " ++ show (ioeGetErrorType ioe) ++ "\n\ \ Location: " ++ none (indent (ioe_location ioe)) ++ "\n\ \ Description: " ++ none (indent (ioe_description ioe)) ++ "\n\ \ " ++ fn (ioeGetFileName ioe) where fn (Just n) = "File name: " ++ quote n fn Nothing = "File name: (none)" none "" = "(none)" none msg = msg indent txt = concat (intersperse ("\n ") (lines txt)) {- | Call the shell to execute a command. In case of an error, throw the @ProcessStatus@ (such as @(Exited (ExitFailure ec))@) as an exception. This is like the Haskell standard library function @system@, except that error handling is brought in accordance with HsShellScript\'s scheme. @exitcode . system_throw@ is the same as the @system@ function, except that when the called shell is terminated or stopped by a signal, this still lead to the @ProcessStatus@ being thrown. The Haskell library report says nothing about what happens in this case, when using the @system@ function. >system_throw cmd = run "/bin/sh" ["-c", "--", cmd] This function is deprecated. You should rather use 'system_runprog', which provides for much better error reporting. -} -- This function should go to HsShellScript.Shell, but this would introduce a circular dependency. system_throw :: String -> IO () system_throw cmd = run "/bin/sh" ["-c", "--", cmd] {- | Call the shell to execute a command. In case of an error, a @RunError@ ist thrown. This is like the Haskell standard library function @system@, except that error handling is brought in accordance with HsShellScript's scheme. (It is /not/ a front end to @system@.) >system_runprog cmd = runprog "/bin/sh" ["-c", "--", cmd] Example: Call \"foo\" and report Errors as @IOError@s, rather than @RunError@s. >as_ioe $ system_runprog "foo" ["bar", "baz"] See 'RunError', 'as_ioe' -} -- This function should go to HsShellScript.Shell, but this would introduce a circular dependency. system_runprog :: String -> IO () system_runprog cmd = runprog "/bin/sh" ["-c", "--", cmd] {- | Run a subroutine as a child process, but don't let it produce any messages. Read its @stdout@ and @stderr@ instead, and append it to the contents of a mutable variable. The idea is that you can run some commands silently, and report them and their messages to the user only when something goes wrong. If the child process terminates in a way which indicates an error, then the process status is thrown, in the same way as 'runprog' does. If the subroutine throws an @(Exited ec)@ exception (of type @ProcessStatus@), such as thrown by 'runprog', then the child process exits with the same exit code, such that the parent process reports it to the caller, again as a @ProcessStatus@ exception. When the subroutine finishes, the child process is terminated with @'_exit' 0@. When it throws an exception, an error message is printed and it is terminated with @'_exit' 1@. See "HsShellScript#subr" for details. The standard output (and the standard error output) of the parent process are flushed before the fork, such that no output appears twice. Example: >let handler :: IORef String -> ProcessStatus -> IO () > handler msgref ps = do hPutStrLn stderr ("Command failed with " ++ show ps ++ ". Actions so far: ") > msg <- readIORef msgref > hPutStrLn stderr msg > exitWith (ExitFailure 1) > >msgref <- newIORef "" >do silently msgref $ do putStrLn "Now doing foobar:" > echo exec "/foo/bar" ["arguments"] > silently msgref $ echo exec "/bar/baz" ["arguments"] >`catch` (handler msgref) See 'lazy_pipe_from', 'subproc', 'runprog', Data.IORef. -} silently :: IORef.IORef String -- ^ A mutable variable, which gets the output (stdout and stderr) of the action appended. -> IO () -- ^ The IO action to run. -> IO () silently ref io = do (msg, pid) <- lazy_pipe_from (err_to_out (child io)) seq (length msg) (return ()) msgs <- readIORef ref writeIORef ref (msgs ++ msg) (Just ps) <- getProcessStatus True False pid case ps of Exited ExitSuccess -> return () ps -> throw ps {- | Modify a subroutine action in order to make it suitable to run as a child process. This is used by functions like 'call', 'silently', 'pipe_to' etc. The action is executed. When it returns, the (child) process is terminated with @'_exit' 0@ (after flushing @stdout@), circumventing normal program shutdown. When it throws an exception, an error message is printed and the (child) process is terminated with @'_exit' 1@. -} child :: IO a -- Action to modify -> IO b -- Never returns child io = do (io `finally` flush_outerr) `catches` [ Handler $ (\argerror -> do errm $ "In child process:\n" ++ argerror_message argerror _exit 1 ) , Handler $ (\processstatus -> do errm $ "Process error in child process. Process status = " ++ show ( processstatus :: ProcessStatus ) _exit 1 ) , Handler $ (\(ioe::IOError) -> do errm ("In child process:\n " ++ show_ioerror ioe) _exit 1 ) , Handler $ (\(e::ExitCode) -> do -- Child process is a subroutine that has terminated normally. errm "Warning! Child process tries to shut down normally. This is a bug. It should\n\ \terminate with _exit (or catch the ExitException yourself). See section\n\"\ \Running a Subroutine in a Separate Process\" in the HsShellScript API\n\ \documentation. Terminating with _exit 0 now." _exit (case e of ExitSuccess -> 0 ExitFailure ec' -> ec' )) , Handler $ (\(e::SomeException) -> do errm ("Child process quit with unexpected exception:\n" ++ show e) _exit 1 ) ] _exit 0 {- | Print text to @stdout@. This is a shorthand for @putStrLn@, except for @stderr@ being flushed beforehand. This way normal output and error output appear in order, even when they aren't buffered as by default. An additional newline is printed at the end. >outm msg = do > hFlush stderr > putStrLn msg -} outm :: String -- ^ Message to print -> IO () outm msg = do hFlush stderr putStrLn msg {- | Print text to @stdout@. This is a shorthand for @putStr@, except for @stderr@ being flushed beforehand. This way normal output and error output appear in order, even when they aren't buffered as by default. No newline is printed at the end. >outm_ msg = do > hFlush stderr > putStr msg -} outm_ :: String -- ^ Message to print -> IO () outm_ msg = do hFlush stderr putStr msg {- | Colorful log message to @stderr@. This prints a message to @stderr@. When @stderr@ is connected to a terminal (as determined by @isatty(3)@), additional escape sequences are printed, which make the message appear in cyan. Additionally, a newline character is output at the end. @stdout@ is flushed beforehand. So normal output and error output appear in order, even when they aren't buffered as by default. See 'logm_', 'errm', 'errm_'. -} logm :: String -- ^ Message to print -> IO () logm msg = do hFlush stdout tty <- isatty stderr if tty then hPutStrLn stderr $ "\ESC[36m" ++ msg ++ "\ESC[00m" else hPutStrLn stderr msg {- | Colorful log message to @stderr@. This prints a message to @stderr@. When @stderr@ is connected to a terminal (as determined by @isatty(3)@), additional escape sequences are printed, which make the message appear in cyan. No a newline character is output at the end. @stdout@ is flushed beforehand. So normal output and error output appear in order, even when they aren't buffered as by default. See 'logm', 'errm', 'errm_'. -} logm_ :: String -> IO () logm_ msg = do do hFlush stdout tty <- isatty stderr if tty then hPutStr stderr $ "\ESC[36m" ++ msg ++ "\ESC[00m" else hPutStr stderr msg {- | Colorful error message to @stderr@. This prints a message to @stderr@. When @stderr@ is connected to a terminal (as determined by @isatty(3)@), additional escape sequences are printed, which make the message appear in red. Additionally, a newline character is output at the end. @stdout@ is flushed beforehand. So normal output and error output appear in order, even when they aren't buffered as by default. See 'logm', 'logm_', 'errm_'. -} errm :: String -> IO () errm msg = do do hFlush stdout tty <- isatty stderr if tty then hPutStrLn stderr $ "\ESC[01;31m" ++ msg ++ "\ESC[00m" else hPutStrLn stderr msg {- | Colorful error message to @stderr@. This prints a message to @stderr@. When @stderr@ is connected to a terminal (as determined by @isatty(3)@), additional escape sequences are printed, which make the message appear in red. No a newline character is output at the end. @stdout@ is flushed beforehand. So normal output and error output appear in order, even when they aren't buffered as by default. See 'logm', 'logm_', 'errm'. -} errm_ :: String -> IO () errm_ msg = do do hFlush stdout tty <- isatty stderr if tty then hPutStr stderr $ "\ESC[01;31m" ++ msg ++ "\ESC[00m" else hPutStr stderr msg {- | In case the specified action throws an IOError, fill in its filename field. This way, more useful error messages can be produced. Example: >-- Oh, the GHC libraries neglect to fill in the file name >executeFile' prog a b c = > fill_in_filename prog $ executeFile prog a b c See 'fill_in_location', 'add_location'. -} fill_in_filename :: String -- ^ File name to fill in -> IO a -- ^ IO action to modify -> IO a -- ^ Modified IO action fill_in_filename filename io = io `catch` (\ioe -> ioError (ioeSetFileName ioe filename)) {- | In case the specified action throws an IOError, fill in its location field. This way, more useful error messages can be produced. Example: >my_fun a b c = do > -- ... > fill_in_location "my_fun" $ -- Give the caller a more useful location information in case of failure > rename "foo" "bar" > -- ... See 'fill_in_filename'. -} fill_in_location :: String -- ^ Location name to fill in -> IO a -- ^ IO action to modify -> IO a -- ^ Modified IO action fill_in_location location io = io `catch` (\ioe -> ioError (ioeSetLocation ioe location)) {- | In case the specified action throws an IOError, add a line to its location field. This way, more useful error messages can be produced. The specified string is prepended to the old location, separating it with a newline from the previous location, if any. When using this thoroughly, you get a reverse call stack in IOErrors. Example: >my_fun = > add_location "my_fun" $ do > -- ... See 'fill_in_filename', 'fill_in_location'. -} add_location :: String -- ^ Location name to add -> IO a -- ^ IO action to modify -> IO a -- ^ Modified IO action add_location location io = io `catch` (\ioe -> let loc = case ioe_location ioe of "" -> location loc0 -> location ++ "\n" ++ loc0 in ioError (ioe { ioe_location = loc }) ) {- | This is a replacement for @System.Posix.Process.executeFile@. It does additional preparations, then calls @executeFile@. @executeFile@ /can't normally/ /be used directly, because it doesn't do the things which are/ /outlined here./ This are the differences to @executeFile@: 1. @stdout@ and @stderr@ are flushed. 2. The standard file descriptors 0-2 are made copies of the file descriptors which the standard handles currently use. This is necessary because they might no longer use the standard handles. See "HsShellScript#fdpipes". If the standard handles @stdin@, @stdout@, @stderr@ aren't in closed state, and they aren't already connected to the respective standard file descriptors, their file descriptors are copied to the respective standard file descriptors (with @dup2@). Backup copies are made of the file descriptors which are overwritten. If some of the standard handles are closed, the corresponding standard file descriptors are closed as well. 3. All file descriptors, except for the standard ones, are set to close-on-exec (see @fcntl(2)@), and will be closed on successful replacement of the process. Before that, the old file descriptor flags are saved. 4. The standard file descriptors are set to blocking mode, since GHC 6.2.2 sets file descriptors to non-blocking (except 0-2, which may get overwritten by a non-blocking one in step 2). The called program doesn't expect that. 5. In case replacing the process fails, the file descriptors are reset to the original state. The file descriptors flags are restored, and the file descriptors 0-2 are overwritten again, with their backup copies. Then an IOError is thrown. 6. In any IOError, the program is filled in as the file name (@executeFile@ neglects this). 7. The return type is a generic @a@, rather than @()@. Also see "HsShellScript#exec". -} execute_file :: FilePath -- ^ Program to call -> Bool -- ^ Search @PATH@? -> [String] -- ^ Arguments -> Maybe [(String, String)] -- ^ Optionally new environment -> IO a -- ^ Never returns execute_file path search args menv = fill_in_filename path $ fill_in_location "execute_file" $ do bracket (do -- Flush stdout and stderr, if open flush_outerr -- Make fds 0-2 copies of the things which the standard handles refer to. recover0 <- restore stdin 0 recover1 <- restore stdout 1 recover2 <- restore stderr 2 -- Save the flags of all file descriptors fdflags <- {# call c_save_fdflags #} -- Prepare all fds for subsequent exec. Fds 0-2 are set to blocking (because GHC sets new fds to non-blocking). All -- others are set to close-on-exec. {# call c_prepare_fd_flags_for_exec #} return (recover0, recover1, recover2, fdflags) ) (\(recover0, recover1, recover2, fdflags) -> do -- Failure of the exec. Restore the file descriptor flags {# call c_restore_fdflags #} fdflags -- Restore the standard handles recover0 recover1 recover2 ) (const $ do -- The exec. Throws an IOError in case replacing the process failed. executeFile path search args menv -- Never reached, only for the type checker error "Impossible error" ) where handleToFd_noclose :: Handle -> IO Fd handleToFd_noclose h = unsafeWithHandleFd h (\fd -> return fd) restore h@(FileHandle _ mvar) fd = do -- The fd used by the handle. This is in GHC.IO.Handle.FD -- handleToFd_noclose: Fehlerhaft, aus hssh-2.9 -- handle_fd: der file descriptor, den der Handle mitbringt. Weicht möglicherweise von 0-2 ab. handle_fd <- fmap fromIntegral (handleToFd_noclose h) -- Get the fd which the handle h uses. This locks the handle. (h__ :: Handle__) <- takeMVar mvar -- Make a copy of the fd which is about to be overwritten. Returns -1 for invalid (closed) fd. -- Mache Sicherheitskopie des Standard-file descriptor (0-2) in einem neu zu belegenden f.d. (ab 3). -- fd: Standard-file descritor, 0-2. -- Bewegt den Standardfiledescriptor aus dem Weg. fd_backup <- {# call c_fcntl_dupfd #} fd 3 -- Liefert den neuen file descriptor, oder -1 (bei Fehler), wenn der filedescriptor geschlossen ist -- Is the handle closed? let closed = case haType h__ of ClosedHandle -> True SemiClosedHandle -> True otherwise -> False -- If the handle is open, make the fd a copy of the fd which the handle uses. Otherwise, close the fd as well. if closed then {# call close #} fd >> return () else when (fd /= handle_fd) $ -- Den f.d., den der Standard-Handle benutzt, auf die Standardposition in 0-2 kopieren. {# call dup2 #} handle_fd fd >> return () -- Return recovery action which undoes everything. return (do -- Restore the fd if fd_backup /= -1 then do -- Den Inhalt des 0-2-file descriptors wiederherstellen {# call dup2 #} fd_backup fd -- Die Sicherheitskopie wieder freigeben {# call close #} fd_backup return () else do -- Wenn der 0-2-filedescriptor nicht kopiert werden konnte, dann liegt das (?) daran, daß er -- geschlossen war. Ihn dann wieder schließen. {# call close #} fd return () -- Unlock the handle putMVar mvar h__ return () ) -- Silly: The standard handle has been overwritten with a duplex. restore h fd = do -- Make a copy of the fd which is about to be closed. Returns -1 for already closed fd. fd_backup <- {# call c_fcntl_dupfd #} fd 3 -- Close the fd {# call close #} fd -- Return recovery action, which restores the fd. return (if fd_backup /= -1 then do {# call dup2 #} fd_backup fd {# call close #} fd_backup return () else do {# call close #} fd return () ) {- About Bas van Dijk's unsafeWithHandleFd: This function is broken. It blocks when called like this: -- Blocks unsafeWithHandleFd stdout $ \fd -> putStrLn ("stdout: fd = " ++ show fd) The job of unsafeWithHandleFd's job is, to keep a reference to the handle, so it won't be garbage collected, while the action is still running. Garbage collecting the handle would close it, as well as the underlying file descriptor, while the latter is still in use by the action. This can't happen as long as use of the file descriptor is encapsulated in the action. This encapsulation can be circumvented by returning the file descriptor, and that's what I do in execute_file. This should usually not be done. However, I want to use it on stdin, stdout and stderr, only. These three should never be garbage collected. Under this circumstances, it should be safe to use unsafeWithHandleFd this way. -} unsafeWithHandleFd :: Handle -> (Fd -> IO a) -> IO a unsafeWithHandleFd h@(FileHandle _ m) f = unsafeWithHandleFd' h m f -- unsafeWithHandleFd h@(DuplexHandle _ _ w) f = unsafeWithHandleFd' h w f unsafeWithHandleFd' :: Handle -> MVar Handle__ -> (Fd -> IO a) -> IO a unsafeWithHandleFd' h m f = withHandle' "unsafeWithHandleFd" h m $ \h_@Handle__{haDevice} -> case cast haDevice of Nothing -> ioError (System.IO.Error.ioeSetErrorString (System.IO.Error.mkIOError IllegalOperation "unsafeWithHandleFd" (Just h) Nothing) "handle is not a file descriptor") Just fd -> do x <- f (Fd (FD.fdFD fd)) return (h_, x) ------------------------------------------------------------------------------------------------------------------------------------------------------ {- | Check if a handle is connected to a terminal. This is a front end to the @isatty(3)@ function (see man page). It is useful, for instance, to determine if color escape sequences should be generated. -} isatty :: Handle -- ^ Handle to check -> IO Bool -- ^ Whether the handle is connected to a terminal isatty h = unsafeWithHandleFd h $ \fd -> do isterm <- {# call isatty as hssh_c_isatty #} ((fromIntegral fd) :: CInt) return (isterm /= (0::CInt)) -- Flush stdout and stderr (which should not be necessary). Discard Illegal Operation IOError which arises when they are closed. flush_outerr = do flush stdout flush stderr where flush h = hFlush h `catch` (\ioe -> if isIllegalOperation ioe then return () else ioError ioe) -- ProcessStatus doesn't derive Typeable. {- data ProcessStatus = Exited ExitCode | Terminated Signal | Stopped Signal deriving (Eq, Ord, Show) -} instance Typeable ProcessStatus where typeOf = const tyCon_ProcessStatus -- GHC 6.4 tyCon_ProcessStatus = mkTyConApp (mkTyCon3 "hsshellscript" "HsShellScript.ProcErr" "Posix.ProcessStatus") [] -- | The GHC libraries don't declare @Foreign.C.Error.Errno@ as instance of -- Show. This makes it up. instance Show Foreign.C.Error.Errno where show (Errno e) = show e ------------------------------------------------------------------------------------------------------------------------------------------------------ -- Transmission of at most one IOError through a pipe (as far as that's possible). -- This is used by execute_file to send the IOError of a failed exec...-call to the parent process. -- -- Can't be transmitted: -- - the handle field (of course...) -- - IOErrors of the type DynIOError. They carry a dynamic value, with no provisions for serialization. -- -- See base.GHC.IO.lhs -- Read a single possible IOError from a file descriptor. The stream must be -- closed on the other side after writing either nothing or a single IOError to -- it. receive_ioerror :: Fd -> IO (Maybe IOError) receive_ioerror fd = do h <- System.Posix.fdToHandle fd txt <- hGetContents h seq (length txt) (return ()) hClose h return (decode_ioerror txt) -- Write a single IOError to a file descriptor, and close it. send_ioerror fd ioe = do h <- System.Posix.fdToHandle fd Foreign.C.Error.getErrno hPutStr h (encode_ioerror ioe) hClose h encode_ioerror :: IOError -> String encode_ioerror ioe = show (ioetype_num ioe, ioe_location ioe, ioe_description ioe, ioe_filename ioe, ioe_errno ioe) decode_ioerror :: String -> Maybe IOError decode_ioerror txt = case txt of "" -> Nothing _ -> let (type_nr, location, description, filename, errno) = read txt in (Just (IOError { ioe_handle = Nothing, ioe_type = num_ioetype type_nr, ioe_location = location, ioe_description = description, ioe_filename = filename, ioe_errno = errno })) -- All IOError types in GHC 6.4, taken from the source code of GHC.IO. -- Used only for serializing IOErrors which are thrown by executeFile, so this should never go out of date. ioe_types = [(AlreadyExists, 1), (NoSuchThing, 2), (ResourceBusy, 3), (ResourceExhausted, 4), (EOF, 5), (IllegalOperation, 6), (PermissionDenied, 7), (UserError, 8), (UnsatisfiedConstraints, 9), (SystemError, 10), (ProtocolError, 11), (OtherError, 12), (InvalidArgument, 13), (InappropriateType, 14), (HardwareFault, 15), (UnsupportedOperation, 16), (TimeExpired, 17), (ResourceVanished, 18), (Interrupted, 19)] -- IOError type as a number ioetype_num ioe = case ioeGetErrorType ioe of ioetype -> case lookup ioetype ioe_types of Just num -> num Nothing -> error "Bug in HsShellScript: Unknown IOError type, can't serialize it." -- IOError type from the number num_ioetype num = case lookup num (map (\(a,b) -> (b,a)) ioe_types) of Just ioetype -> ioetype Nothing -> error ("Bug in HsShellScript: Unknown IOError type number " ++ show num) instance Exception ProcessStatus {- ALT: ------------------------------------------------------------------------------------------------------------------------------------------------------ -- Getting the file descriptor which is encapsulated inside a handle -- This is a modified version of System.Posix.IO.handleToFd. The original function has the side effect of closing the handle. From the GHC -- documentation: -- -- "converting a Handle into an Fd effectively means -- letting go of the Handle; it is put into a closed -- state as a result." -- -- The modified version does the same, but doesn't close the handle. handleToFd_noclose :: Handle -- Handle, must be a @FileHandle@. Throws an @IOError@ when the handle is a @DuplexHandle@, or when the -- handle doesn't incapsulate a file descriptor. -> IO Fd -- The file descriptor inside of the handle. handleToFd_noclose h@(FileHandle _ m) = do withHandle' "handleToFd_noclose" h m $ handleToFd'_noclose h handleToFd_noclose h@(DuplexHandle _ r w) = ioError (System.IO.Error.ioeSetErrorString (System.IO.Error.mkIOError IllegalOperation "handleToFd_noclose" (Just h) Nothing) "handle is a Duplex") handleToFd'_noclose :: Handle -> Handle__ -> IO (Handle__, Fd) handleToFd'_noclose h h_@Handle__{haType=_, ..} = do case cast haDevice of Nothing -> ioError (System.IO.Error.ioeSetErrorString (System.IO.Error.mkIOError IllegalOperation "handleToFd_noclose" (Just h) Nothing) "handle is not a file descriptor") Just fd -> do -- Removed code (2 lines) which would close the handle. return (Handle__{haType=ClosedHandle,..}, Fd (FD.fdFD fd)) -} ------------------------------------------------------------------------------------------------------------------------------------------------------ #c /* c2hs-0.14.5 chokes on the following includes. #include #include #include #include #include #include */ char *strerror(int errnum); int fork(void); void _exit(int status); int isatty(int desc); int close(int fd); int dup2(int oldfd, int newfd); /* Save all file descriptor flags in an array */ int* c_save_fdflags(void); /* Restore all file descriptor flags from the array, and free it */ void c_restore_fdflags(int* flags); /* Duplicate a file descriptor, allocating the new one at min or above */ int c_fcntl_dupfd(int fd, int min); /* Prepare all file descriptors for a subsequent exec */ void c_prepare_fd_flags_for_exec(void); /* Set a file descriptor to "close on exec" mode. Returns the old flags. */ int c_close_on_exec(int fd); /* Set the flags of a file descriptor. Returns the old flags. */ int c_set_flags(int fd, int new_flags); #endc