[project @ 2004-01-28 10:04:25 by simonpj]
[ghc-base.git] / System / Cmd.hs
1 -----------------------------------------------------------------------------
2 -- |
3 -- Module      :  System.Cmd
4 -- Copyright   :  (c) The University of Glasgow 2001
5 -- License     :  BSD-style (see the file libraries/base/LICENSE)
6 -- 
7 -- Maintainer  :  libraries@haskell.org
8 -- Stability   :  provisional
9 -- Portability :  portable
10 --
11 -- Executing an external command.
12 --
13 -----------------------------------------------------------------------------
14
15 module System.Cmd
16     ( system,        -- :: String -> IO ExitCode
17 #ifdef __GLASGOW_HASKELL__
18       rawSystem,     -- :: FilePath -> [String] -> IO ExitCode
19 #endif
20     ) where
21
22 import Prelude
23
24 #ifdef __GLASGOW_HASKELL__
25 import Foreign
26 import Foreign.C
27 import System.Exit
28 import GHC.IOBase
29 #include "config.h"
30 #endif
31
32 #ifdef __HUGS__
33 import Hugs.System
34 #endif
35
36 #ifdef __NHC__
37 import System (system)
38 #endif
39
40 -- ---------------------------------------------------------------------------
41 -- system
42
43 {-| 
44 Computation @system cmd@ returns the exit code
45 produced when the operating system processes the command @cmd@.
46
47 This computation may fail with
48
49    * @PermissionDenied@: The process has insufficient privileges to
50      perform the operation.
51
52    * @ResourceExhausted@: Insufficient resources are available to
53      perform the operation.
54
55    * @UnsupportedOperation@: The implementation does not support
56      system calls.
57
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.
62 -}
63 #ifdef __GLASGOW_HASKELL__
64 system :: String -> IO ExitCode
65 system "" = ioException (IOError Nothing InvalidArgument "system" "null command" Nothing)
66 system cmd =
67   withCString cmd $ \s -> do
68     status <- throwErrnoIfMinus1 "system" (primSystem s)
69     case status of
70         0  -> return ExitSuccess
71         n  -> return (ExitFailure n)
72
73 foreign import ccall unsafe "systemCmd" primSystem :: CString -> IO Int
74
75
76 ------------------------------------------------------------------------
77 --
78 --                      rawSystem
79 --
80 ------------------------------------------------------------------------
81
82 {- | 
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@.
90
91 The return codes are the same as for @system@.
92 -}
93
94 rawSystem :: FilePath -> [String] -> IO ExitCode
95
96 {- -------------------------------------------------------------------------
97         IMPORTANT IMPLEMENTATION NOTES
98    (see also libraries/base/cbits/rawSystem.c)
99
100 On Unix, rawSystem is easy to implement: use execvp.
101
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.)
105
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
108         quotes thus 
109                 "foo\this that\cmd" arg1 arg2
110
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
115         double quote thus
116                 cmd "this is arg 1" "this is arg 2"
117
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
123         Foo="baz"
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
127 foo\baz
128         cat "foo\baz"
129 If you escape the backslash, thus
130         cat "foo\\baz"
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.)
135
136 BOTTOM LINE: 
137         1 We wrap the command, and each argument, in quotes
138         2 Inside the quotes, we escape any double-quote characters
139                 (but nothing else)
140         3 Then concatenate all these quoted things together, separated with 
141                 spaces
142
143 Steps 1,2 are done by the function 'translate' below.
144
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.
148
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.
154
155 ----------------------------------------------------------------------------- -}
156
157 #ifndef mingw32_TARGET_OS
158
159 rawSystem cmd args =
160   withCString cmd $ \pcmd ->
161     withMany withCString (cmd:args) $ \cstrs ->
162       withArray0 nullPtr cstrs $ \arr -> do
163         status <- throwErrnoIfMinus1 "rawSystem" (c_rawSystem pcmd arr)
164         case status of
165             0  -> return ExitSuccess
166             n  -> return (ExitFailure n)
167
168 foreign import ccall unsafe "rawSystem"
169   c_rawSystem :: CString -> Ptr CString -> IO Int
170
171 #else
172
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
175 -- itself.
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)
182     case status of
183        0  -> return ExitSuccess
184        n  -> return (ExitFailure n)
185
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
191
192 foreign import ccall unsafe "rawSystem"
193   c_rawSystem :: CString -> IO Int
194
195 #endif
196
197 #endif  /* __GLASGOW_HASKELL__ */