[project @ 2004-02-12 14:55:55 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 The exact rules used by the C runtime to unscramble quoted argumets
146 are quite complex.  For example, how do you get the string \" into an
147 argument?  You can find the rules in MSDN, here:
148     http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vccelng/htm/progs_12.asp
149
150 Here are some pages that give informations on Windows-related 
151 limitations and deviations from Unix conventions:
152
153     http://support.microsoft.com/default.aspx?scid=kb;en-us;830473
154     Command lines and environment variables effectively limited to 8191 
155     characters on Win XP, 2047 on NT/2000 (probably even less on Win 9x):
156
157     http://www.microsoft.com/windowsxp/home/using/productdoc/en/default.asp?url=/WINDOWSXP/home/using/productdoc/en/percent.asp
158     Command-line substitution under Windows XP. IIRC these facilities (or at 
159     least a large subset of them) are available on Win NT and 2000. Some 
160     might be available on Win 9x.
161
162     http://www.microsoft.com/windowsxp/home/using/productdoc/en/default.asp?url=/WINDOWSXP/home/using/productdoc/en/Cmd.asp
163     How CMD.EXE processes command lines.
164
165
166
167 Note: CreateProcess does have a separate argument (lpApplicationName)
168 with which you can specify the command, but we have to slap the
169 command into lpCommandLine anyway, so that argv[0] is what a C program
170 expects (namely the application name).  So it seems simpler to just
171 use lpCommandLine alone, which CreateProcess supports.
172
173 ----------------------------------------------------------------------------- -}
174
175 #ifndef mingw32_TARGET_OS
176
177 rawSystem cmd args =
178   withCString cmd $ \pcmd ->
179     withMany withCString (cmd:args) $ \cstrs ->
180       withArray0 nullPtr cstrs $ \arr -> do
181         status <- throwErrnoIfMinus1 "rawSystem" (c_rawSystem pcmd arr)
182         case status of
183             0  -> return ExitSuccess
184             n  -> return (ExitFailure n)
185
186 foreign import ccall unsafe "rawSystem"
187   c_rawSystem :: CString -> Ptr CString -> IO Int
188
189 #else
190
191 -- On Windows, the command line is passed to the operating system as
192 -- a single string.  Command-line parsing is done by the executable
193 -- itself.
194 rawSystem cmd args = do
195         -- NOTE: 'cmd' is assumed to contain the application to run _only_,
196         -- as it'll be quoted surrounded in quotes here.
197   let cmdline = translate cmd ++ concat (map ((' ':) . translate) args)
198   withCString cmdline $ \pcmdline -> do
199     status <- throwErrnoIfMinus1 "rawSystem" (c_rawSystem pcmdline)
200     case status of
201        0  -> return ExitSuccess
202        n  -> return (ExitFailure n)
203
204 translate :: String -> String
205 translate str@('"':_) = str -- already escaped.
206 translate str = '"' : foldr escape "\"" str
207   where escape '"'  str = '\\' : '"'  : str
208         escape c    str = c : str
209
210 foreign import ccall unsafe "rawSystem"
211   c_rawSystem :: CString -> IO Int
212
213 #endif
214
215 #endif  /* __GLASGOW_HASKELL__ */