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