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.
5 // Fork, exec, wait, etc.
16 var ForkLock sync
.RWMutex
18 // EscapeArg rewrites command line argument s as prescribed
19 // in https://msdn.microsoft.com/en-us/library/ms880421.
20 // This function returns "" (2 double quotes) if s is empty.
21 // Alternatively, these transformations are done:
22 // - every back slash (\) is doubled, but only if immediately
23 // followed by double quote (");
24 // - every double quote (") is escaped by back slash (\);
25 // - finally, s is wrapped with double quotes (arg -> "arg"),
26 // but only if there is space or tab inside s.
27 func EscapeArg(s
string) string {
31 for i
:= 0; i
< len(s
); i
++ {
33 case '"', '\\', ' ', '\t':
34 // Some escaping required.
35 b
:= make([]byte, 0, len(s
)+2)
36 b
= appendEscapeArg(b
, s
)
43 // appendEscapeArg escapes the string s, as per escapeArg,
44 // appends the result to b, and returns the updated slice.
45 func appendEscapeArg(b
[]byte, s
string) []byte {
47 return append(b
, `""`...)
50 needsBackslash
:= false
52 for i
:= 0; i
< len(s
); i
++ {
61 if !needsBackslash
&& !hasSpace
{
62 // No special handling required; normal case.
63 return append(b
, s
...)
66 // hasSpace is true, so we need to quote the string.
76 for i
:= 0; i
< len(s
); i
++ {
84 for ; slashes
> 0; slashes
-- {
92 for ; slashes
> 0; slashes
-- {
101 // makeCmdLine builds a command line out of args by escaping "special"
102 // characters and joining the arguments with spaces.
103 func makeCmdLine(args
[]string) string {
105 for _
, v
:= range args
{
109 b
= appendEscapeArg(b
, v
)
114 // createEnvBlock converts an array of environment strings into
115 // the representation required by CreateProcess: a sequence of NUL
116 // terminated strings followed by a nil.
117 // Last bytes are two UCS-2 NULs, or four NUL bytes.
118 func createEnvBlock(envv
[]string) *uint16 {
120 return &utf16
.Encode([]rune("\x00\x00"))[0]
123 for _
, s
:= range envv
{
128 b
:= make([]byte, length
)
130 for _
, s
:= range envv
{
132 copy(b
[i
:i
+l
], []byte(s
))
133 copy(b
[i
+l
:i
+l
+1], []byte{0})
136 copy(b
[i
:i
+1], []byte{0})
138 return &utf16
.Encode([]rune(string(b
)))[0]
141 func CloseOnExec(fd Handle
) {
142 SetHandleInformation(Handle(fd
), HANDLE_FLAG_INHERIT
, 0)
145 func SetNonblock(fd Handle
, nonblocking
bool) (err error
) {
149 // FullPath retrieves the full path of the specified file.
150 func FullPath(name
string) (path
string, err error
) {
151 p
, err
:= UTF16PtrFromString(name
)
157 buf
:= make([]uint16, n
)
158 n
, err
= GetFullPathName(p
, uint32(len(buf
)), &buf
[0], nil)
162 if n
<= uint32(len(buf
)) {
163 return UTF16ToString(buf
[:n
]), nil
168 func isSlash(c
uint8) bool {
169 return c
== '\\' || c
== '/'
172 func normalizeDir(dir
string) (name
string, err error
) {
173 ndir
, err
:= FullPath(dir
)
177 if len(ndir
) > 2 && isSlash(ndir
[0]) && isSlash(ndir
[1]) {
178 // dir cannot have \\server\share\path form
184 func volToUpper(ch
int) int {
185 if 'a' <= ch
&& ch
<= 'z' {
191 func joinExeDirAndFName(dir
, p
string) (name
string, err error
) {
195 if len(p
) > 2 && isSlash(p
[0]) && isSlash(p
[1]) {
196 // \\server\share\path form
199 if len(p
) > 1 && p
[1] == ':' {
207 d
, err
:= normalizeDir(dir
)
211 if volToUpper(int(p
[0])) == volToUpper(int(d
[0])) {
212 return FullPath(d
+ "\\" + p
[2:])
219 d
, err
:= normalizeDir(dir
)
224 return FullPath(d
[:2] + p
)
226 return FullPath(d
+ "\\" + p
)
231 type ProcAttr
struct {
238 type SysProcAttr
struct {
240 CmdLine
string // used if non-empty, else the windows command line is built by escaping the arguments passed to StartProcess
242 Token Token
// if set, runs new process in the security context represented by the token
243 ProcessAttributes
*SecurityAttributes
// if set, applies these security attributes as the descriptor for the new process
244 ThreadAttributes
*SecurityAttributes
// if set, applies these security attributes as the descriptor for the main thread of the new process
245 NoInheritHandles
bool // if set, each inheritable handle in the calling process is not inherited by the new process
246 AdditionalInheritedHandles
[]Handle
// a list of additional handles, already marked as inheritable, that will be inherited by the new process
247 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
250 var zeroProcAttr ProcAttr
251 var zeroSysProcAttr SysProcAttr
253 func StartProcess(argv0
string, argv
[]string, attr
*ProcAttr
) (pid
int, handle
uintptr, err error
) {
255 return 0, 0, EWINDOWS
262 sys
= &zeroSysProcAttr
265 if len(attr
.Files
) > 3 {
266 return 0, 0, EWINDOWS
268 if len(attr
.Files
) < 3 {
272 if len(attr
.Dir
) != 0 {
273 // StartProcess assumes that argv0 is relative to attr.Dir,
274 // because it implies Chdir(attr.Dir) before executing argv0.
275 // Windows CreateProcess assumes the opposite: it looks for
276 // argv0 relative to the current directory, and, only once the new
277 // process is started, it does Chdir(attr.Dir). We are adjusting
278 // for that difference here by making argv0 absolute.
280 argv0
, err
= joinExeDirAndFName(attr
.Dir
, argv0
)
285 argv0p
, err
:= UTF16PtrFromString(argv0
)
291 // Windows CreateProcess takes the command line as a single string:
292 // use attr.CmdLine if set, else build the command line by escaping
293 // and joining each argument with spaces
294 if sys
.CmdLine
!= "" {
295 cmdline
= sys
.CmdLine
297 cmdline
= makeCmdLine(argv
)
301 if len(cmdline
) != 0 {
302 argvp
, err
= UTF16PtrFromString(cmdline
)
309 if len(attr
.Dir
) != 0 {
310 dirp
, err
= UTF16PtrFromString(attr
.Dir
)
316 var maj
, min
, build
uint32
317 rtlGetNtVersionNumbers(&maj
, &min
, &build
)
318 isWin7
:= maj
< 6 ||
(maj
== 6 && min
<= 1)
319 // NT kernel handles are divisible by 4, with the bottom 3 bits left as
320 // a tag. The fully set tag correlates with the types of handles we're
321 // concerned about here. Except, the kernel will interpret some
322 // special handle values, like -1, -2, and so forth, so kernelbase.dll
323 // checks to see that those bottom three bits are checked, but that top
324 // bit is not checked.
325 isLegacyWin7ConsoleHandle
:= func(handle Handle
) bool { return isWin7
&& handle
&0x10000003 == 3 }
327 p
, _
:= GetCurrentProcess()
329 if sys
.ParentProcess
!= 0 {
330 parentProcess
= sys
.ParentProcess
332 fd
:= make([]Handle
, len(attr
.Files
))
333 for i
:= range attr
.Files
{
334 if attr
.Files
[i
] > 0 {
335 destinationProcessHandle
:= parentProcess
337 // On Windows 7, console handles aren't real handles, and can only be duplicated
338 // into the current process, not a parent one, which amounts to the same thing.
339 if parentProcess
!= p
&& isLegacyWin7ConsoleHandle(Handle(attr
.Files
[i
])) {
340 destinationProcessHandle
= p
343 err
:= DuplicateHandle(p
, Handle(attr
.Files
[i
]), destinationProcessHandle
, &fd
[i
], 0, true, DUPLICATE_SAME_ACCESS
)
347 defer DuplicateHandle(parentProcess
, fd
[i
], 0, nil, 0, false, DUPLICATE_CLOSE_SOURCE
)
350 si
:= new(_STARTUPINFOEXW
)
351 si
.ProcThreadAttributeList
, err
= newProcThreadAttributeList(2)
355 defer deleteProcThreadAttributeList(si
.ProcThreadAttributeList
)
356 si
.Cb
= uint32(unsafe
.Sizeof(*si
))
357 si
.Flags
= STARTF_USESTDHANDLES
359 si
.Flags |
= STARTF_USESHOWWINDOW
360 si
.ShowWindow
= SW_HIDE
362 if sys
.ParentProcess
!= 0 {
363 err
= updateProcThreadAttribute(si
.ProcThreadAttributeList
, 0, _PROC_THREAD_ATTRIBUTE_PARENT_PROCESS
, unsafe
.Pointer(&sys
.ParentProcess
), unsafe
.Sizeof(sys
.ParentProcess
), nil, nil)
372 fd
= append(fd
, sys
.AdditionalInheritedHandles
...)
374 // On Windows 7, console handles aren't real handles, so don't pass them
375 // through to PROC_THREAD_ATTRIBUTE_HANDLE_LIST.
377 if isLegacyWin7ConsoleHandle(fd
[i
]) {
382 // The presence of a NULL handle in the list is enough to cause PROC_THREAD_ATTRIBUTE_HANDLE_LIST
383 // to treat the entire list as empty, so remove NULL handles.
393 willInheritHandles
:= len(fd
) > 0 && !sys
.NoInheritHandles
395 // Do not accidentally inherit more than these handles.
396 if willInheritHandles
{
397 err
= updateProcThreadAttribute(si
.ProcThreadAttributeList
, 0, _PROC_THREAD_ATTRIBUTE_HANDLE_LIST
, unsafe
.Pointer(&fd
[0]), uintptr(len(fd
))*unsafe
.Sizeof(fd
[0]), nil, nil)
403 pi
:= new(ProcessInformation
)
404 flags
:= sys
.CreationFlags | CREATE_UNICODE_ENVIRONMENT | _EXTENDED_STARTUPINFO_PRESENT
406 err
= CreateProcessAsUser(sys
.Token
, argv0p
, argvp
, sys
.ProcessAttributes
, sys
.ThreadAttributes
, willInheritHandles
, flags
, createEnvBlock(attr
.Env
), dirp
, &si
.StartupInfo
, pi
)
408 err
= CreateProcess(argv0p
, argvp
, sys
.ProcessAttributes
, sys
.ThreadAttributes
, willInheritHandles
, flags
, createEnvBlock(attr
.Env
), dirp
, &si
.StartupInfo
, pi
)
413 defer CloseHandle(Handle(pi
.Thread
))
414 runtime
.KeepAlive(fd
)
415 runtime
.KeepAlive(sys
)
417 return int(pi
.ProcessId
), uintptr(pi
.Process
), nil
420 func Exec(argv0
string, argv
[]string, envv
[]string) (err error
) {