exec_windows.mx raw
1 // 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
7 package syscall
8
9 import (
10 "internal/bytealg"
11 "runtime"
12 "sync"
13 "unicode/utf16"
14 "unsafe"
15 )
16
17 // ForkLock is not used on Windows.
18 var ForkLock sync.RWMutex
19
20 // EscapeArg rewrites command line argument s as prescribed
21 // in https://msdn.microsoft.com/en-us/library/ms880421.
22 // This function returns "" (2 double quotes) if s is empty.
23 // Alternatively, these transformations are done:
24 // - every back slash (\) is doubled, but only if immediately
25 // followed by double quote (");
26 // - every double quote (") is escaped by back slash (\);
27 // - finally, s is wrapped with double quotes (arg -> "arg"),
28 // but only if there is space or tab inside s.
29 func EscapeArg(s string) string {
30 if len(s) == 0 {
31 return `""`
32 }
33 for i := 0; i < len(s); i++ {
34 switch s[i] {
35 case '"', '\\', ' ', '\t':
36 // Some escaping required.
37 b := make([]byte, 0, len(s)+2)
38 b = appendEscapeArg(b, s)
39 return string(b)
40 }
41 }
42 return s
43 }
44
45 // appendEscapeArg escapes the string s, as per escapeArg,
46 // appends the result to b, and returns the updated slice.
47 func appendEscapeArg(b []byte, s string) []byte {
48 if len(s) == 0 {
49 return append(b, `""`...)
50 }
51
52 needsBackslash := false
53 hasSpace := false
54 for i := 0; i < len(s); i++ {
55 switch s[i] {
56 case '"', '\\':
57 needsBackslash = true
58 case ' ', '\t':
59 hasSpace = true
60 }
61 }
62
63 if !needsBackslash && !hasSpace {
64 // No special handling required; normal case.
65 return append(b, s...)
66 }
67 if !needsBackslash {
68 // hasSpace is true, so we need to quote the string.
69 b = append(b, '"')
70 b = append(b, s...)
71 return append(b, '"')
72 }
73
74 if hasSpace {
75 b = append(b, '"')
76 }
77 slashes := 0
78 for i := 0; i < len(s); i++ {
79 c := s[i]
80 switch c {
81 default:
82 slashes = 0
83 case '\\':
84 slashes++
85 case '"':
86 for ; slashes > 0; slashes-- {
87 b = append(b, '\\')
88 }
89 b = append(b, '\\')
90 }
91 b = append(b, c)
92 }
93 if hasSpace {
94 for ; slashes > 0; slashes-- {
95 b = append(b, '\\')
96 }
97 b = append(b, '"')
98 }
99
100 return b
101 }
102
103 // makeCmdLine builds a command line out of args by escaping "special"
104 // characters and joining the arguments with spaces.
105 func makeCmdLine(args []string) string {
106 var b []byte
107 for _, v := range args {
108 if len(b) > 0 {
109 b = append(b, ' ')
110 }
111 b = appendEscapeArg(b, v)
112 }
113 return string(b)
114 }
115
116 // createEnvBlock converts an array of environment strings into
117 // the representation required by CreateProcess: a sequence of NUL
118 // terminated strings followed by a nil.
119 // Last bytes are two UCS-2 NULs, or four NUL bytes.
120 // If any string contains a NUL, it returns (nil, EINVAL).
121 func createEnvBlock(envv []string) ([]uint16, error) {
122 if len(envv) == 0 {
123 return utf16.Encode([]rune("\x00\x00")), nil
124 }
125 var length int
126 for _, s := range envv {
127 if bytealg.IndexByteString(s, 0) != -1 {
128 return nil, EINVAL
129 }
130 length += len(s) + 1
131 }
132 length += 1
133
134 b := make([]uint16, 0, length)
135 for _, s := range envv {
136 for _, c := range s {
137 b = utf16.AppendRune(b, c)
138 }
139 b = utf16.AppendRune(b, 0)
140 }
141 b = utf16.AppendRune(b, 0)
142 return b, nil
143 }
144
145 func CloseOnExec(fd Handle) {
146 SetHandleInformation(Handle(fd), HANDLE_FLAG_INHERIT, 0)
147 }
148
149 func SetNonblock(fd Handle, nonblocking bool) (err error) {
150 return nil
151 }
152
153 // FullPath retrieves the full path of the specified file.
154 func FullPath(name string) (path string, err error) {
155 p, err := UTF16PtrFromString(name)
156 if err != nil {
157 return "", err
158 }
159 n := uint32(100)
160 for {
161 buf := make([]uint16, n)
162 n, err = GetFullPathName(p, uint32(len(buf)), &buf[0], nil)
163 if err != nil {
164 return "", err
165 }
166 if n <= uint32(len(buf)) {
167 return UTF16ToString(buf[:n]), nil
168 }
169 }
170 }
171
172 func isSlash(c uint8) bool {
173 return c == '\\' || c == '/'
174 }
175
176 func normalizeDir(dir string) (name string, err error) {
177 ndir, err := FullPath(dir)
178 if err != nil {
179 return "", err
180 }
181 if len(ndir) > 2 && isSlash(ndir[0]) && isSlash(ndir[1]) {
182 // dir cannot have \\server\share\path form
183 return "", EINVAL
184 }
185 return ndir, nil
186 }
187
188 func volToUpper(ch int) int {
189 if 'a' <= ch && ch <= 'z' {
190 ch += 'A' - 'a'
191 }
192 return ch
193 }
194
195 func joinExeDirAndFName(dir, p string) (name string, err error) {
196 if len(p) == 0 {
197 return "", EINVAL
198 }
199 if len(p) > 2 && isSlash(p[0]) && isSlash(p[1]) {
200 // \\server\share\path form
201 return p, nil
202 }
203 if len(p) > 1 && p[1] == ':' {
204 // has drive letter
205 if len(p) == 2 {
206 return "", EINVAL
207 }
208 if isSlash(p[2]) {
209 return p, nil
210 } else {
211 d, err := normalizeDir(dir)
212 if err != nil {
213 return "", err
214 }
215 if volToUpper(int(p[0])) == volToUpper(int(d[0])) {
216 return FullPath(d + "\\" + p[2:])
217 } else {
218 return FullPath(p)
219 }
220 }
221 } else {
222 // no drive letter
223 d, err := normalizeDir(dir)
224 if err != nil {
225 return "", err
226 }
227 if isSlash(p[0]) {
228 return FullPath(d[:2] + p)
229 } else {
230 return FullPath(d + "\\" + p)
231 }
232 }
233 }
234
235 type ProcAttr struct {
236 Dir string
237 Env []string
238 Files []uintptr
239 Sys *SysProcAttr
240 }
241
242 type SysProcAttr struct {
243 HideWindow bool
244 CmdLine string // used if non-empty, else the windows command line is built by escaping the arguments passed to StartProcess
245 CreationFlags uint32
246 Token Token // if set, runs new process in the security context represented by the token
247 ProcessAttributes *SecurityAttributes // if set, applies these security attributes as the descriptor for the new process
248 ThreadAttributes *SecurityAttributes // if set, applies these security attributes as the descriptor for the main thread of the new process
249 NoInheritHandles bool // if set, no handles are inherited by the new process, not even the standard handles, contained in ProcAttr.Files, nor the ones contained in AdditionalInheritedHandles
250 AdditionalInheritedHandles []Handle // a list of additional handles, already marked as inheritable, that will be inherited by the new process
251 ParentProcess Handle // if non-zero, the new process regards the process given by this handle as its parent process, and AdditionalInheritedHandles, if set, should exist in this parent process
252 }
253
254 var zeroProcAttr ProcAttr
255 var zeroSysProcAttr SysProcAttr
256
257 func StartProcess(argv0 string, argv []string, attr *ProcAttr) (pid int, handle uintptr, err error) {
258 if len(argv0) == 0 {
259 return 0, 0, EWINDOWS
260 }
261 if attr == nil {
262 attr = &zeroProcAttr
263 }
264 sys := attr.Sys
265 if sys == nil {
266 sys = &zeroSysProcAttr
267 }
268
269 if len(attr.Files) > 3 {
270 return 0, 0, EWINDOWS
271 }
272 if len(attr.Files) < 3 {
273 return 0, 0, EINVAL
274 }
275
276 if len(attr.Dir) != 0 {
277 // StartProcess assumes that argv0 is relative to attr.Dir,
278 // because it implies Chdir(attr.Dir) before executing argv0.
279 // Windows CreateProcess assumes the opposite: it looks for
280 // argv0 relative to the current directory, and, only once the new
281 // process is started, it does Chdir(attr.Dir). We are adjusting
282 // for that difference here by making argv0 absolute.
283 var err error
284 argv0, err = joinExeDirAndFName(attr.Dir, argv0)
285 if err != nil {
286 return 0, 0, err
287 }
288 }
289 argv0p, err := UTF16PtrFromString(argv0)
290 if err != nil {
291 return 0, 0, err
292 }
293
294 var cmdline string
295 // Windows CreateProcess takes the command line as a single string:
296 // use attr.CmdLine if set, else build the command line by escaping
297 // and joining each argument with spaces
298 if sys.CmdLine != "" {
299 cmdline = sys.CmdLine
300 } else {
301 cmdline = makeCmdLine(argv)
302 }
303
304 var argvp *uint16
305 if len(cmdline) != 0 {
306 argvp, err = UTF16PtrFromString(cmdline)
307 if err != nil {
308 return 0, 0, err
309 }
310 }
311
312 var dirp *uint16
313 if len(attr.Dir) != 0 {
314 dirp, err = UTF16PtrFromString(attr.Dir)
315 if err != nil {
316 return 0, 0, err
317 }
318 }
319
320 p, _ := GetCurrentProcess()
321 parentProcess := p
322 if sys.ParentProcess != 0 {
323 parentProcess = sys.ParentProcess
324 }
325 fd := make([]Handle, len(attr.Files))
326 for i := range attr.Files {
327 if attr.Files[i] > 0 {
328 err := DuplicateHandle(p, Handle(attr.Files[i]), parentProcess, &fd[i], 0, true, DUPLICATE_SAME_ACCESS)
329 if err != nil {
330 return 0, 0, err
331 }
332 defer DuplicateHandle(parentProcess, fd[i], 0, nil, 0, false, DUPLICATE_CLOSE_SOURCE)
333 }
334 }
335 procAttrList, err := newProcThreadAttributeList(2)
336 if err != nil {
337 return 0, 0, err
338 }
339 defer procAttrList.delete()
340 si := new(_STARTUPINFOEXW)
341 si.Cb = uint32(unsafe.Sizeof(*si))
342 si.Flags = STARTF_USESTDHANDLES
343 if sys.HideWindow {
344 si.Flags |= STARTF_USESHOWWINDOW
345 si.ShowWindow = SW_HIDE
346 }
347 if sys.ParentProcess != 0 {
348 err = procAttrList.update(_PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, unsafe.Pointer(&sys.ParentProcess), unsafe.Sizeof(sys.ParentProcess))
349 if err != nil {
350 return 0, 0, err
351 }
352 }
353 si.StdInput = fd[0]
354 si.StdOutput = fd[1]
355 si.StdErr = fd[2]
356
357 fd = append(fd, sys.AdditionalInheritedHandles...)
358
359 // The presence of a NULL handle in the list is enough to cause PROC_THREAD_ATTRIBUTE_HANDLE_LIST
360 // to treat the entire list as empty, so remove NULL handles.
361 j := 0
362 for i := range fd {
363 if fd[i] != 0 {
364 fd[j] = fd[i]
365 j++
366 }
367 }
368 fd = fd[:j]
369
370 willInheritHandles := len(fd) > 0 && !sys.NoInheritHandles
371
372 // Do not accidentally inherit more than these handles.
373 if willInheritHandles {
374 err = procAttrList.update(_PROC_THREAD_ATTRIBUTE_HANDLE_LIST, unsafe.Pointer(&fd[0]), uintptr(len(fd))*unsafe.Sizeof(fd[0]))
375 if err != nil {
376 return 0, 0, err
377 }
378 }
379
380 envBlock, err := createEnvBlock(attr.Env)
381 if err != nil {
382 return 0, 0, err
383 }
384
385 si.ProcThreadAttributeList = procAttrList.list()
386 pi := new(ProcessInformation)
387 flags := sys.CreationFlags | CREATE_UNICODE_ENVIRONMENT | _EXTENDED_STARTUPINFO_PRESENT
388 if sys.Token != 0 {
389 err = CreateProcessAsUser(sys.Token, argv0p, argvp, sys.ProcessAttributes, sys.ThreadAttributes, willInheritHandles, flags, &envBlock[0], dirp, &si.StartupInfo, pi)
390 } else {
391 err = CreateProcess(argv0p, argvp, sys.ProcessAttributes, sys.ThreadAttributes, willInheritHandles, flags, &envBlock[0], dirp, &si.StartupInfo, pi)
392 }
393 if err != nil {
394 return 0, 0, err
395 }
396 defer CloseHandle(Handle(pi.Thread))
397 runtime.KeepAlive(fd)
398 runtime.KeepAlive(sys)
399
400 return int(pi.ProcessId), uintptr(pi.Process), nil
401 }
402
403 func Exec(argv0 string, argv []string, envv []string) (err error) {
404 return EWINDOWS
405 }
406