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
7package syscall
8
9import (
10	"internal/bytealg"
11	"runtime"
12	"sync"
13	"unicode/utf16"
14	"unsafe"
15)
16
17// ForkLock is not used on Windows.
18var 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.
29func 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.
47func 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.
105func 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).
121func 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
145func CloseOnExec(fd Handle) {
146	SetHandleInformation(Handle(fd), HANDLE_FLAG_INHERIT, 0)
147}
148
149func SetNonblock(fd Handle, nonblocking bool) (err error) {
150	return nil
151}
152
153// FullPath retrieves the full path of the specified file.
154func 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
172func isSlash(c uint8) bool {
173	return c == '\\' || c == '/'
174}
175
176func 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
188func volToUpper(ch int) int {
189	if 'a' <= ch && ch <= 'z' {
190		ch += 'A' - 'a'
191	}
192	return ch
193}
194
195func 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
235type ProcAttr struct {
236	Dir   string
237	Env   []string
238	Files []uintptr
239	Sys   *SysProcAttr
240}
241
242type 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
254var zeroProcAttr ProcAttr
255var zeroSysProcAttr SysProcAttr
256
257func 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	si := new(_STARTUPINFOEXW)
336	si.ProcThreadAttributeList, err = newProcThreadAttributeList(2)
337	if err != nil {
338		return 0, 0, err
339	}
340	defer deleteProcThreadAttributeList(si.ProcThreadAttributeList)
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 = updateProcThreadAttribute(si.ProcThreadAttributeList, 0, _PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, unsafe.Pointer(&sys.ParentProcess), unsafe.Sizeof(sys.ParentProcess), nil, nil)
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 = updateProcThreadAttribute(si.ProcThreadAttributeList, 0, _PROC_THREAD_ATTRIBUTE_HANDLE_LIST, unsafe.Pointer(&fd[0]), uintptr(len(fd))*unsafe.Sizeof(fd[0]), nil, nil)
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	pi := new(ProcessInformation)
386	flags := sys.CreationFlags | CREATE_UNICODE_ENVIRONMENT | _EXTENDED_STARTUPINFO_PRESENT
387	if sys.Token != 0 {
388		err = CreateProcessAsUser(sys.Token, argv0p, argvp, sys.ProcessAttributes, sys.ThreadAttributes, willInheritHandles, flags, &envBlock[0], dirp, &si.StartupInfo, pi)
389	} else {
390		err = CreateProcess(argv0p, argvp, sys.ProcessAttributes, sys.ThreadAttributes, willInheritHandles, flags, &envBlock[0], dirp, &si.StartupInfo, pi)
391	}
392	if err != nil {
393		return 0, 0, err
394	}
395	defer CloseHandle(Handle(pi.Thread))
396	runtime.KeepAlive(fd)
397	runtime.KeepAlive(sys)
398
399	return int(pi.ProcessId), uintptr(pi.Process), nil
400}
401
402func Exec(argv0 string, argv []string, envv []string) (err error) {
403	return EWINDOWS
404}
405