blob: aeee191dda7d332cf1feb6cfdd016714cb3c5926 [file] [log] [blame]
Daniel Theophanes7f9e2472010-07-26 09:43:35 +10001// Copyright 2009 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// Fork, exec, wait, etc.
6
7package syscall
8
9import (
10 "sync"
11 "utf16"
12)
13
Daniel Theophanes7f9e2472010-07-26 09:43:35 +100014var ForkLock sync.RWMutex
15
Alex Brainman913c8d72011-03-24 11:20:28 +110016// escape rewrites command line argument s as prescribed
17// in http://msdn.microsoft.com/en-us/library/ms880421.
18// This function returns "" (2 double quotes) if s is empty.
19// Alternatively, these transformations are done:
20// - every back slash (\) is doubled, but only if immediately
21// followed by double quote (");
22// - every double quote (") is escaped by back slash (\);
23// - finally, s is wrapped with double quotes (arg -> "arg"),
24// but only if there is space or tab inside s.
25func escape(s string) string {
26 if len(s) == 0 {
27 return "\"\""
Daniel Theophanes7f9e2472010-07-26 09:43:35 +100028 }
Alex Brainman913c8d72011-03-24 11:20:28 +110029 n := len(s)
30 hasSpace := false
31 for i := 0; i < len(s); i++ {
32 switch s[i] {
33 case '"', '\\':
34 n++
35 case ' ', '\t':
36 hasSpace = true
37 }
Daniel Theophanes7f9e2472010-07-26 09:43:35 +100038 }
Alex Brainman913c8d72011-03-24 11:20:28 +110039 if hasSpace {
40 n += 2
41 }
42 if n == len(s) {
43 return s
Daniel Theophanes7f9e2472010-07-26 09:43:35 +100044 }
45
Alex Brainman913c8d72011-03-24 11:20:28 +110046 qs := make([]byte, n)
47 j := 0
48 if hasSpace {
49 qs[j] = '"'
50 j++
Daniel Theophanes7f9e2472010-07-26 09:43:35 +100051 }
Alex Brainman913c8d72011-03-24 11:20:28 +110052 slashes := 0
53 for i := 0; i < len(s); i++ {
54 switch s[i] {
55 default:
56 slashes = 0
57 qs[j] = s[i]
58 case '\\':
59 slashes++
60 qs[j] = s[i]
61 case '"':
62 for ; slashes > 0; slashes-- {
63 qs[j] = '\\'
64 j++
65 }
66 qs[j] = '\\'
67 j++
68 qs[j] = s[i]
69 }
70 j++
71 }
72 if hasSpace {
73 for ; slashes > 0; slashes-- {
74 qs[j] = '\\'
75 j++
76 }
77 qs[j] = '"'
78 j++
79 }
80 return string(qs[:j])
Daniel Theophanes7f9e2472010-07-26 09:43:35 +100081}
82
Alex Brainman913c8d72011-03-24 11:20:28 +110083// makeCmdLine builds a command line out of args by escaping "special"
84// characters and joining the arguments with spaces.
85func makeCmdLine(args []string) string {
86 var s string
87 for _, v := range args {
88 if s != "" {
89 s += " "
90 }
91 s += escape(v)
92 }
93 return s
94}
95
96// createEnvBlock converts an array of environment strings into
97// the representation required by CreateProcess: a sequence of NUL
98// terminated strings followed by a nil.
99// Last bytes are two UCS-2 NULs, or four NUL bytes.
Daniel Theophanes7f9e2472010-07-26 09:43:35 +1000100func createEnvBlock(envv []string) *uint16 {
101 if len(envv) == 0 {
102 return &utf16.Encode([]int("\x00\x00"))[0]
103 }
104 length := 0
105 for _, s := range envv {
106 length += len(s) + 1
107 }
108 length += 1
109
110 b := make([]byte, length)
111 i := 0
112 for _, s := range envv {
113 l := len(s)
114 copy(b[i:i+l], []byte(s))
115 copy(b[i+l:i+l+1], []byte{0})
116 i = i + l + 1
117 }
118 copy(b[i:i+1], []byte{0})
119
120 return &utf16.Encode([]int(string(b)))[0]
121}
122
Daniel Theophanes7f9e2472010-07-26 09:43:35 +1000123func CloseOnExec(fd int) {
Hector Chufb9fc882011-02-03 12:50:41 +1100124 SetHandleInformation(int32(fd), HANDLE_FLAG_INHERIT, 0)
Daniel Theophanes7f9e2472010-07-26 09:43:35 +1000125}
126
127func SetNonblock(fd int, nonblocking bool) (errno int) {
128 return 0
129}
130
Alex Brainman913c8d72011-03-24 11:20:28 +1100131// getFullPath retrieves the full path of the specified file.
132// Just a wrapper for Windows GetFullPathName api.
133func getFullPath(name string) (path string, err int) {
134 p := StringToUTF16Ptr(name)
135 buf := make([]uint16, 100)
136 n, err := GetFullPathName(p, uint32(len(buf)), &buf[0], nil)
137 if err != 0 {
138 return "", err
139 }
140 if n > uint32(len(buf)) {
141 // Windows is asking for bigger buffer.
142 buf = make([]uint16, n)
143 n, err = GetFullPathName(p, uint32(len(buf)), &buf[0], nil)
144 if err != 0 {
145 return "", err
146 }
147 if n > uint32(len(buf)) {
148 return "", EINVAL
149 }
150 }
151 return UTF16ToString(buf[:n]), 0
152}
153
154func isSlash(c uint8) bool {
155 return c == '\\' || c == '/'
156}
157
158func normalizeDir(dir string) (name string, err int) {
159 ndir, err := getFullPath(dir)
160 if err != 0 {
161 return "", err
162 }
163 if len(ndir) > 2 && isSlash(ndir[0]) && isSlash(ndir[1]) {
164 // dir cannot have \\server\share\path form
165 return "", EINVAL
166 }
167 return ndir, 0
168}
169
170func volToUpper(ch int) int {
171 if 'a' <= ch && ch <= 'z' {
172 ch += 'A' - 'a'
173 }
174 return ch
175}
176
177func joinExeDirAndFName(dir, p string) (name string, err int) {
178 if len(p) == 0 {
179 return "", EINVAL
180 }
181 if len(p) > 2 && isSlash(p[0]) && isSlash(p[1]) {
182 // \\server\share\path form
183 return p, 0
184 }
185 if len(p) > 1 && p[1] == ':' {
186 // has drive letter
187 if len(p) == 2 {
188 return "", EINVAL
189 }
190 if isSlash(p[2]) {
191 return p, 0
192 } else {
193 d, err := normalizeDir(dir)
194 if err != 0 {
195 return "", err
196 }
197 if volToUpper(int(p[0])) == volToUpper(int(d[0])) {
198 return getFullPath(d + "\\" + p[2:])
199 } else {
200 return getFullPath(p)
201 }
202 }
203 } else {
204 // no drive letter
205 d, err := normalizeDir(dir)
206 if err != 0 {
207 return "", err
208 }
209 if isSlash(p[0]) {
210 return getFullPath(d[:2] + p)
211 } else {
212 return getFullPath(d + "\\" + p)
213 }
214 }
215 // we shouldn't be here
216 return "", EINVAL
217}
218
Roger Peppeaa55c052011-03-15 14:41:19 -0400219type ProcAttr struct {
220 Dir string
221 Env []string
222 Files []int
223}
224
225var zeroAttributes ProcAttr
Daniel Theophanes7f9e2472010-07-26 09:43:35 +1000226
Roger Peppeaa55c052011-03-15 14:41:19 -0400227func StartProcess(argv0 string, argv []string, attr *ProcAttr) (pid, handle int, err int) {
Alex Brainman913c8d72011-03-24 11:20:28 +1100228 if len(argv0) == 0 {
229 return 0, 0, EWINDOWS
230 }
Roger Peppeaa55c052011-03-15 14:41:19 -0400231 if attr == nil {
232 attr = &zeroAttributes
233 }
234 if len(attr.Files) > 3 {
Alex Brainman4ecebfe2011-02-04 14:41:26 +1100235 return 0, 0, EWINDOWS
Daniel Theophanes7f9e2472010-07-26 09:43:35 +1000236 }
237
Alex Brainman913c8d72011-03-24 11:20:28 +1100238 if len(attr.Dir) != 0 {
239 // StartProcess assumes that argv0 is relative to attr.Dir,
240 // because it implies Chdir(attr.Dir) before executing argv0.
241 // Windows CreateProcess assumes the opposite: it looks for
242 // argv0 relative to the current directory, and, only once the new
243 // process is started, it does Chdir(attr.Dir). We are adjusting
244 // for that difference here by making argv0 absolute.
245 var err int
246 argv0, err = joinExeDirAndFName(attr.Dir, argv0)
247 if err != 0 {
248 return 0, 0, err
Daniel Theophanes7f9e2472010-07-26 09:43:35 +1000249 }
250 }
Alex Brainman913c8d72011-03-24 11:20:28 +1100251 argv0p := StringToUTF16Ptr(argv0)
Daniel Theophanes7f9e2472010-07-26 09:43:35 +1000252
Alex Brainman913c8d72011-03-24 11:20:28 +1100253 var argvp *uint16
254 s := makeCmdLine(argv)
255 if len(s) != 0 {
256 argvp = StringToUTF16Ptr(s)
257 }
Daniel Theophanes7f9e2472010-07-26 09:43:35 +1000258
Alex Brainman913c8d72011-03-24 11:20:28 +1100259 var dirp *uint16
260 if len(attr.Dir) != 0 {
261 dirp = StringToUTF16Ptr(attr.Dir)
262 }
Daniel Theophanes7f9e2472010-07-26 09:43:35 +1000263
Hector Chufb9fc882011-02-03 12:50:41 +1100264 // Acquire the fork lock so that no other threads
265 // create new fds that are not yet close-on-exec
266 // before we fork.
267 ForkLock.Lock()
268 defer ForkLock.Unlock()
269
Alex Brainman913c8d72011-03-24 11:20:28 +1100270 p, _ := GetCurrentProcess()
271 fd := make([]int32, len(attr.Files))
Robert Griesemera2e28682011-04-13 15:13:59 -0700272 for i := range attr.Files {
Alex Brainman913c8d72011-03-24 11:20:28 +1100273 if attr.Files[i] > 0 {
274 err := DuplicateHandle(p, int32(attr.Files[i]), p, &fd[i], 0, true, DUPLICATE_SAME_ACCESS)
275 if err != 0 {
276 return 0, 0, err
277 }
278 defer CloseHandle(int32(fd[i]))
Daniel Theophanes7f9e2472010-07-26 09:43:35 +1000279 }
280 }
Alex Brainman913c8d72011-03-24 11:20:28 +1100281 si := new(StartupInfo)
282 GetStartupInfo(si)
283 si.Flags = STARTF_USESTDHANDLES
284 si.StdInput = fd[0]
285 si.StdOutput = fd[1]
286 si.StdErr = fd[2]
Daniel Theophanes7f9e2472010-07-26 09:43:35 +1000287
Alex Brainman913c8d72011-03-24 11:20:28 +1100288 pi := new(ProcessInformation)
289
290 err = CreateProcess(argv0p, argvp, nil, nil, true, CREATE_UNICODE_ENVIRONMENT, createEnvBlock(attr.Env), dirp, si, pi)
291 if err != 0 {
292 return 0, 0, err
Daniel Theophanes7f9e2472010-07-26 09:43:35 +1000293 }
Alex Brainman913c8d72011-03-24 11:20:28 +1100294 defer CloseHandle(pi.Thread)
295
296 return int(pi.ProcessId), int(pi.Process), 0
Daniel Theophanes7f9e2472010-07-26 09:43:35 +1000297}
298
Daniel Theophanes7f9e2472010-07-26 09:43:35 +1000299func Exec(argv0 string, argv []string, envv []string) (err int) {
300 return EWINDOWS
301}