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	"sync"
11	"unicode/utf16"
12	"unsafe"
13)
14
15var ForkLock sync.RWMutex
16
17// EscapeArg rewrites command line argument s as prescribed
18// in https://msdn.microsoft.com/en-us/library/ms880421.
19// This function returns "" (2 double quotes) if s is empty.
20// Alternatively, these transformations are done:
21// - every back slash (\) is doubled, but only if immediately
22//   followed by double quote (");
23// - every double quote (") is escaped by back slash (\);
24// - finally, s is wrapped with double quotes (arg -> "arg"),
25//   but only if there is space or tab inside s.
26func EscapeArg(s string) string {
27	if len(s) == 0 {
28		return "\"\""
29	}
30	n := len(s)
31	hasSpace := false
32	for i := 0; i < len(s); i++ {
33		switch s[i] {
34		case '"', '\\':
35			n++
36		case ' ', '\t':
37			hasSpace = true
38		}
39	}
40	if hasSpace {
41		n += 2
42	}
43	if n == len(s) {
44		return s
45	}
46
47	qs := make([]byte, n)
48	j := 0
49	if hasSpace {
50		qs[j] = '"'
51		j++
52	}
53	slashes := 0
54	for i := 0; i < len(s); i++ {
55		switch s[i] {
56		default:
57			slashes = 0
58			qs[j] = s[i]
59		case '\\':
60			slashes++
61			qs[j] = s[i]
62		case '"':
63			for ; slashes > 0; slashes-- {
64				qs[j] = '\\'
65				j++
66			}
67			qs[j] = '\\'
68			j++
69			qs[j] = s[i]
70		}
71		j++
72	}
73	if hasSpace {
74		for ; slashes > 0; slashes-- {
75			qs[j] = '\\'
76			j++
77		}
78		qs[j] = '"'
79		j++
80	}
81	return string(qs[:j])
82}
83
84// makeCmdLine builds a command line out of args by escaping "special"
85// characters and joining the arguments with spaces.
86func makeCmdLine(args []string) string {
87	var s string
88	for _, v := range args {
89		if s != "" {
90			s += " "
91		}
92		s += EscapeArg(v)
93	}
94	return s
95}
96
97// createEnvBlock converts an array of environment strings into
98// the representation required by CreateProcess: a sequence of NUL
99// terminated strings followed by a nil.
100// Last bytes are two UCS-2 NULs, or four NUL bytes.
101func createEnvBlock(envv []string) *uint16 {
102	if len(envv) == 0 {
103		return &utf16.Encode([]rune("\x00\x00"))[0]
104	}
105	length := 0
106	for _, s := range envv {
107		length += len(s) + 1
108	}
109	length += 1
110
111	b := make([]byte, length)
112	i := 0
113	for _, s := range envv {
114		l := len(s)
115		copy(b[i:i+l], []byte(s))
116		copy(b[i+l:i+l+1], []byte{0})
117		i = i + l + 1
118	}
119	copy(b[i:i+1], []byte{0})
120
121	return &utf16.Encode([]rune(string(b)))[0]
122}
123
124func CloseOnExec(fd Handle) {
125	SetHandleInformation(Handle(fd), HANDLE_FLAG_INHERIT, 0)
126}
127
128func SetNonblock(fd Handle, nonblocking bool) (err error) {
129	return nil
130}
131
132// FullPath retrieves the full path of the specified file.
133func FullPath(name string) (path string, err error) {
134	p, err := UTF16PtrFromString(name)
135	if err != nil {
136		return "", err
137	}
138	n := uint32(100)
139	for {
140		buf := make([]uint16, n)
141		n, err = GetFullPathName(p, uint32(len(buf)), &buf[0], nil)
142		if err != nil {
143			return "", err
144		}
145		if n <= uint32(len(buf)) {
146			return UTF16ToString(buf[:n]), nil
147		}
148	}
149}
150
151func isSlash(c uint8) bool {
152	return c == '\\' || c == '/'
153}
154
155func normalizeDir(dir string) (name string, err error) {
156	ndir, err := FullPath(dir)
157	if err != nil {
158		return "", err
159	}
160	if len(ndir) > 2 && isSlash(ndir[0]) && isSlash(ndir[1]) {
161		// dir cannot have \\server\share\path form
162		return "", EINVAL
163	}
164	return ndir, nil
165}
166
167func volToUpper(ch int) int {
168	if 'a' <= ch && ch <= 'z' {
169		ch += 'A' - 'a'
170	}
171	return ch
172}
173
174func joinExeDirAndFName(dir, p string) (name string, err error) {
175	if len(p) == 0 {
176		return "", EINVAL
177	}
178	if len(p) > 2 && isSlash(p[0]) && isSlash(p[1]) {
179		// \\server\share\path form
180		return p, nil
181	}
182	if len(p) > 1 && p[1] == ':' {
183		// has drive letter
184		if len(p) == 2 {
185			return "", EINVAL
186		}
187		if isSlash(p[2]) {
188			return p, nil
189		} else {
190			d, err := normalizeDir(dir)
191			if err != nil {
192				return "", err
193			}
194			if volToUpper(int(p[0])) == volToUpper(int(d[0])) {
195				return FullPath(d + "\\" + p[2:])
196			} else {
197				return FullPath(p)
198			}
199		}
200	} else {
201		// no drive letter
202		d, err := normalizeDir(dir)
203		if err != nil {
204			return "", err
205		}
206		if isSlash(p[0]) {
207			return FullPath(d[:2] + p)
208		} else {
209			return FullPath(d + "\\" + p)
210		}
211	}
212}
213
214type ProcAttr struct {
215	Dir   string
216	Env   []string
217	Files []uintptr
218	Sys   *SysProcAttr
219}
220
221type SysProcAttr struct {
222	HideWindow        bool
223	CmdLine           string // used if non-empty, else the windows command line is built by escaping the arguments passed to StartProcess
224	CreationFlags     uint32
225	Token             Token               // if set, runs new process in the security context represented by the token
226	ProcessAttributes *SecurityAttributes // if set, applies these security attributes as the descriptor for the new process
227	ThreadAttributes  *SecurityAttributes // if set, applies these security attributes as the descriptor for the main thread of the new process
228}
229
230var zeroProcAttr ProcAttr
231var zeroSysProcAttr SysProcAttr
232
233func StartProcess(argv0 string, argv []string, attr *ProcAttr) (pid int, handle uintptr, err error) {
234	if len(argv0) == 0 {
235		return 0, 0, EWINDOWS
236	}
237	if attr == nil {
238		attr = &zeroProcAttr
239	}
240	sys := attr.Sys
241	if sys == nil {
242		sys = &zeroSysProcAttr
243	}
244
245	if len(attr.Files) > 3 {
246		return 0, 0, EWINDOWS
247	}
248	if len(attr.Files) < 3 {
249		return 0, 0, EINVAL
250	}
251
252	if len(attr.Dir) != 0 {
253		// StartProcess assumes that argv0 is relative to attr.Dir,
254		// because it implies Chdir(attr.Dir) before executing argv0.
255		// Windows CreateProcess assumes the opposite: it looks for
256		// argv0 relative to the current directory, and, only once the new
257		// process is started, it does Chdir(attr.Dir). We are adjusting
258		// for that difference here by making argv0 absolute.
259		var err error
260		argv0, err = joinExeDirAndFName(attr.Dir, argv0)
261		if err != nil {
262			return 0, 0, err
263		}
264	}
265	argv0p, err := UTF16PtrFromString(argv0)
266	if err != nil {
267		return 0, 0, err
268	}
269
270	var cmdline string
271	// Windows CreateProcess takes the command line as a single string:
272	// use attr.CmdLine if set, else build the command line by escaping
273	// and joining each argument with spaces
274	if sys.CmdLine != "" {
275		cmdline = sys.CmdLine
276	} else {
277		cmdline = makeCmdLine(argv)
278	}
279
280	var argvp *uint16
281	if len(cmdline) != 0 {
282		argvp, err = UTF16PtrFromString(cmdline)
283		if err != nil {
284			return 0, 0, err
285		}
286	}
287
288	var dirp *uint16
289	if len(attr.Dir) != 0 {
290		dirp, err = UTF16PtrFromString(attr.Dir)
291		if err != nil {
292			return 0, 0, err
293		}
294	}
295
296	// Acquire the fork lock so that no other threads
297	// create new fds that are not yet close-on-exec
298	// before we fork.
299	ForkLock.Lock()
300	defer ForkLock.Unlock()
301
302	p, _ := GetCurrentProcess()
303	fd := make([]Handle, len(attr.Files))
304	for i := range attr.Files {
305		if attr.Files[i] > 0 {
306			err := DuplicateHandle(p, Handle(attr.Files[i]), p, &fd[i], 0, true, DUPLICATE_SAME_ACCESS)
307			if err != nil {
308				return 0, 0, err
309			}
310			defer CloseHandle(Handle(fd[i]))
311		}
312	}
313	si := new(StartupInfo)
314	si.Cb = uint32(unsafe.Sizeof(*si))
315	si.Flags = STARTF_USESTDHANDLES
316	if sys.HideWindow {
317		si.Flags |= STARTF_USESHOWWINDOW
318		si.ShowWindow = SW_HIDE
319	}
320	si.StdInput = fd[0]
321	si.StdOutput = fd[1]
322	si.StdErr = fd[2]
323
324	pi := new(ProcessInformation)
325
326	flags := sys.CreationFlags | CREATE_UNICODE_ENVIRONMENT
327	if sys.Token != 0 {
328		err = CreateProcessAsUser(sys.Token, argv0p, argvp, sys.ProcessAttributes, sys.ThreadAttributes, true, flags, createEnvBlock(attr.Env), dirp, si, pi)
329	} else {
330		err = CreateProcess(argv0p, argvp, sys.ProcessAttributes, sys.ThreadAttributes, true, flags, createEnvBlock(attr.Env), dirp, si, pi)
331	}
332	if err != nil {
333		return 0, 0, err
334	}
335	defer CloseHandle(Handle(pi.Thread))
336
337	return int(pi.ProcessId), uintptr(pi.Process), nil
338}
339
340func Exec(argv0 string, argv []string, envv []string) (err error) {
341	return EWINDOWS
342}
343