1 -----------------------------------------------------------------------------
4 -- Copyright : (c) The University of Glasgow 2001
5 -- License : BSD-style (see the file libraries/base/LICENSE)
7 -- Maintainer : libraries@haskell.org
8 -- Stability : provisional
9 -- Portability : portable
11 -- Executing an external command.
13 -----------------------------------------------------------------------------
16 ( system, -- :: String -> IO ExitCode
17 #ifdef __GLASGOW_HASKELL__
18 rawSystem, -- :: FilePath -> [String] -> IO ExitCode
24 #ifdef __GLASGOW_HASKELL__
37 import System (system)
40 -- ---------------------------------------------------------------------------
44 Computation @system cmd@ returns the exit code
45 produced when the operating system processes the command @cmd@.
47 This computation may fail with
49 * @PermissionDenied@: The process has insufficient privileges to
50 perform the operation.
52 * @ResourceExhausted@: Insufficient resources are available to
53 perform the operation.
55 * @UnsupportedOperation@: The implementation does not support
58 On Windows, 'system' is implemented using Windows's native system
59 call, which ignores the @SHELL@ environment variable, and always
60 passes the command to the Windows command interpreter (@CMD.EXE@ or
61 @COMMAND.COM@), hence Unixy shell tricks will not work.
63 #ifdef __GLASGOW_HASKELL__
64 system :: String -> IO ExitCode
65 system "" = ioException (IOError Nothing InvalidArgument "system" "null command" Nothing)
67 withCString cmd $ \s -> do
68 status <- throwErrnoIfMinus1 "system" (primSystem s)
70 0 -> return ExitSuccess
71 n -> return (ExitFailure n)
73 foreign import ccall unsafe "systemCmd" primSystem :: CString -> IO Int
76 ------------------------------------------------------------------------
80 ------------------------------------------------------------------------
83 The computation @rawSystem cmd args@ runs the operating system command
84 whose file name is @cmd@, passing it the arguments @args@. It
85 bypasses the shell, so that @cmd@ should see precisely the argument
86 strings @args@, with no funny escaping or shell meta-syntax expansion.
87 (Unix users will recognise this behaviour
88 as @execvp@, and indeed that's how it's implemented.)
89 It will therefore behave more portably between operating systems than @system@.
91 The return codes are the same as for @system@.
94 rawSystem :: FilePath -> [String] -> IO ExitCode
96 {- -------------------------------------------------------------------------
97 IMPORTANT IMPLEMENTATION NOTES
98 (see also libraries/base/cbits/rawSystem.c)
100 On Unix, rawSystem is easy to implement: use execvp.
102 On Windows it's more tricky. We use CreateProcess, passing a single
103 command-line string (lpCommandLine) as its argument. (CreateProcess
104 is well documented on http://msdn.microsoft/com.)
106 - It parses the beginning of the string to find the command. If the
107 file name has embedded spaces, it must be quoted, using double
109 "foo\this that\cmd" arg1 arg2
111 - The invoked command can in turn access the entire lpCommandLine string,
112 and the C runtime does indeed do so, parsing it to generate the
113 traditional argument vector argv[0], argv[1], etc. Again, to
114 break it into argument items, any spaces must be quoted using
116 cmd "this is arg 1" "this is arg 2"
118 What if an argument itself contains double-quotes? (File names can't
119 can't, on Windows.) Then the quote must be escaped with a backslash.
120 If we call Create Process with this lpArgument:
121 cmd "Foo=\"baz\"" arg2
122 then cmd will see argv[1] as
124 However, experiments show that backslashes themselves must *not* be escaped.
125 That is, to get a backslash in an argument, just put backslash, even inside
126 quotes. For eaxmple, this works fine to show the contents of the file
129 If you escape the backslash, thus
131 then @cat@ will see argument foo\\baz, and on WinME/98/95 you'll get
132 "can't find file foo\\baz". (As it happens, WinNT/XP commands don't
133 mind double backslashes, but it's still a bug, given rawSystem's claim
134 to pass exactly args to the command.)
137 1 We wrap the command, and each argument, in quotes
138 2 Inside the quotes, we escape any double-quote characters
140 3 Then concatenate all these quoted things together, separated with
143 Steps 1,2 are done by the function 'translate' below.
145 Question: how do you get the string \" into an argument? Turns out that
146 the argument "\\"" does not do the job. (This turns into a single \.)
147 Puzzling but probably not important in practice.
149 Note: CreateProcess does have a separate argument (lpApplicationName)
150 with which you can specify the command, but we have to slap the
151 command into lpCommandLine anyway, so that argv[0] is what a C program
152 expects (namely the application name). So it seems simpler to just
153 use lpCommandLine alone, which CreateProcess supports.
155 ----------------------------------------------------------------------------- -}
157 #ifndef mingw32_TARGET_OS
160 withCString cmd $ \pcmd ->
161 withMany withCString (cmd:args) $ \cstrs ->
162 withArray0 nullPtr cstrs $ \arr -> do
163 status <- throwErrnoIfMinus1 "rawSystem" (c_rawSystem pcmd arr)
165 0 -> return ExitSuccess
166 n -> return (ExitFailure n)
168 foreign import ccall unsafe "rawSystem"
169 c_rawSystem :: CString -> Ptr CString -> IO Int
173 -- On Windows, the command line is passed to the operating system as
174 -- a single string. Command-line parsing is done by the executable
176 rawSystem cmd args = do
177 -- NOTE: 'cmd' is assumed to contain the application to run _only_,
178 -- as it'll be quoted surrounded in quotes here.
179 let cmdline = translate cmd ++ concat (map ((' ':) . translate) args)
180 withCString cmdline $ \pcmdline -> do
181 status <- throwErrnoIfMinus1 "rawSystem" (c_rawSystem pcmdline)
183 0 -> return ExitSuccess
184 n -> return (ExitFailure n)
186 translate :: String -> String
187 translate str@('"':_) = str -- already escaped.
188 translate str = '"' : foldr escape "\"" str
189 where escape '"' str = '\\' : '"' : str
190 escape c str = c : str
192 foreign import ccall unsafe "rawSystem"
193 c_rawSystem :: CString -> IO Int
197 #endif /* __GLASGOW_HASKELL__ */