Black Lives Matter. Support the Equal Justice Initiative.

Source file src/syscall/exec_windows.go

Documentation: syscall

     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  	"runtime"
    11  	"sync"
    12  	"unicode/utf16"
    13  	"unsafe"
    14  )
    15  
    16  var ForkLock sync.RWMutex
    17  
    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 {
    28  	if len(s) == 0 {
    29  		return `""`
    30  	}
    31  	for i := 0; i < len(s); i++ {
    32  		switch s[i] {
    33  		case '"', '\\', ' ', '\t':
    34  			// Some escaping required.
    35  			b := make([]byte, 0, len(s)+2)
    36  			b = appendEscapeArg(b, s)
    37  			return string(b)
    38  		}
    39  	}
    40  	return s
    41  }
    42  
    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 {
    46  	if len(s) == 0 {
    47  		return append(b, `""`...)
    48  	}
    49  
    50  	needsBackslash := false
    51  	hasSpace := false
    52  	for i := 0; i < len(s); i++ {
    53  		switch s[i] {
    54  		case '"', '\\':
    55  			needsBackslash = true
    56  		case ' ', '\t':
    57  			hasSpace = true
    58  		}
    59  	}
    60  
    61  	if !needsBackslash && !hasSpace {
    62  		// No special handling required; normal case.
    63  		return append(b, s...)
    64  	}
    65  	if !needsBackslash {
    66  		// hasSpace is true, so we need to quote the string.
    67  		b = append(b, '"')
    68  		b = append(b, s...)
    69  		return append(b, '"')
    70  	}
    71  
    72  	if hasSpace {
    73  		b = append(b, '"')
    74  	}
    75  	slashes := 0
    76  	for i := 0; i < len(s); i++ {
    77  		c := s[i]
    78  		switch c {
    79  		default:
    80  			slashes = 0
    81  		case '\\':
    82  			slashes++
    83  		case '"':
    84  			for ; slashes > 0; slashes-- {
    85  				b = append(b, '\\')
    86  			}
    87  			b = append(b, '\\')
    88  		}
    89  		b = append(b, c)
    90  	}
    91  	if hasSpace {
    92  		for ; slashes > 0; slashes-- {
    93  			b = append(b, '\\')
    94  		}
    95  		b = append(b, '"')
    96  	}
    97  
    98  	return b
    99  }
   100  
   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 {
   104  	var b []byte
   105  	for _, v := range args {
   106  		if len(b) > 0 {
   107  			b = append(b, ' ')
   108  		}
   109  		b = appendEscapeArg(b, v)
   110  	}
   111  	return string(b)
   112  }
   113  
   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 {
   119  	if len(envv) == 0 {
   120  		return &utf16.Encode([]rune("\x00\x00"))[0]
   121  	}
   122  	length := 0
   123  	for _, s := range envv {
   124  		length += len(s) + 1
   125  	}
   126  	length += 1
   127  
   128  	b := make([]byte, length)
   129  	i := 0
   130  	for _, s := range envv {
   131  		l := len(s)
   132  		copy(b[i:i+l], []byte(s))
   133  		copy(b[i+l:i+l+1], []byte{0})
   134  		i = i + l + 1
   135  	}
   136  	copy(b[i:i+1], []byte{0})
   137  
   138  	return &utf16.Encode([]rune(string(b)))[0]
   139  }
   140  
   141  func CloseOnExec(fd Handle) {
   142  	SetHandleInformation(Handle(fd), HANDLE_FLAG_INHERIT, 0)
   143  }
   144  
   145  func SetNonblock(fd Handle, nonblocking bool) (err error) {
   146  	return nil
   147  }
   148  
   149  // FullPath retrieves the full path of the specified file.
   150  func FullPath(name string) (path string, err error) {
   151  	p, err := UTF16PtrFromString(name)
   152  	if err != nil {
   153  		return "", err
   154  	}
   155  	n := uint32(100)
   156  	for {
   157  		buf := make([]uint16, n)
   158  		n, err = GetFullPathName(p, uint32(len(buf)), &buf[0], nil)
   159  		if err != nil {
   160  			return "", err
   161  		}
   162  		if n <= uint32(len(buf)) {
   163  			return UTF16ToString(buf[:n]), nil
   164  		}
   165  	}
   166  }
   167  
   168  func isSlash(c uint8) bool {
   169  	return c == '\\' || c == '/'
   170  }
   171  
   172  func normalizeDir(dir string) (name string, err error) {
   173  	ndir, err := FullPath(dir)
   174  	if err != nil {
   175  		return "", err
   176  	}
   177  	if len(ndir) > 2 && isSlash(ndir[0]) && isSlash(ndir[1]) {
   178  		// dir cannot have \\server\share\path form
   179  		return "", EINVAL
   180  	}
   181  	return ndir, nil
   182  }
   183  
   184  func volToUpper(ch int) int {
   185  	if 'a' <= ch && ch <= 'z' {
   186  		ch += 'A' - 'a'
   187  	}
   188  	return ch
   189  }
   190  
   191  func joinExeDirAndFName(dir, p string) (name string, err error) {
   192  	if len(p) == 0 {
   193  		return "", EINVAL
   194  	}
   195  	if len(p) > 2 && isSlash(p[0]) && isSlash(p[1]) {
   196  		// \\server\share\path form
   197  		return p, nil
   198  	}
   199  	if len(p) > 1 && p[1] == ':' {
   200  		// has drive letter
   201  		if len(p) == 2 {
   202  			return "", EINVAL
   203  		}
   204  		if isSlash(p[2]) {
   205  			return p, nil
   206  		} else {
   207  			d, err := normalizeDir(dir)
   208  			if err != nil {
   209  				return "", err
   210  			}
   211  			if volToUpper(int(p[0])) == volToUpper(int(d[0])) {
   212  				return FullPath(d + "\\" + p[2:])
   213  			} else {
   214  				return FullPath(p)
   215  			}
   216  		}
   217  	} else {
   218  		// no drive letter
   219  		d, err := normalizeDir(dir)
   220  		if err != nil {
   221  			return "", err
   222  		}
   223  		if isSlash(p[0]) {
   224  			return FullPath(d[:2] + p)
   225  		} else {
   226  			return FullPath(d + "\\" + p)
   227  		}
   228  	}
   229  }
   230  
   231  type ProcAttr struct {
   232  	Dir   string
   233  	Env   []string
   234  	Files []uintptr
   235  	Sys   *SysProcAttr
   236  }
   237  
   238  type SysProcAttr struct {
   239  	HideWindow                 bool
   240  	CmdLine                    string // used if non-empty, else the windows command line is built by escaping the arguments passed to StartProcess
   241  	CreationFlags              uint32
   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
   248  }
   249  
   250  var zeroProcAttr ProcAttr
   251  var zeroSysProcAttr SysProcAttr
   252  
   253  func StartProcess(argv0 string, argv []string, attr *ProcAttr) (pid int, handle uintptr, err error) {
   254  	if len(argv0) == 0 {
   255  		return 0, 0, EWINDOWS
   256  	}
   257  	if attr == nil {
   258  		attr = &zeroProcAttr
   259  	}
   260  	sys := attr.Sys
   261  	if sys == nil {
   262  		sys = &zeroSysProcAttr
   263  	}
   264  
   265  	if len(attr.Files) > 3 {
   266  		return 0, 0, EWINDOWS
   267  	}
   268  	if len(attr.Files) < 3 {
   269  		return 0, 0, EINVAL
   270  	}
   271  
   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.
   279  		var err error
   280  		argv0, err = joinExeDirAndFName(attr.Dir, argv0)
   281  		if err != nil {
   282  			return 0, 0, err
   283  		}
   284  	}
   285  	argv0p, err := UTF16PtrFromString(argv0)
   286  	if err != nil {
   287  		return 0, 0, err
   288  	}
   289  
   290  	var cmdline string
   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
   296  	} else {
   297  		cmdline = makeCmdLine(argv)
   298  	}
   299  
   300  	var argvp *uint16
   301  	if len(cmdline) != 0 {
   302  		argvp, err = UTF16PtrFromString(cmdline)
   303  		if err != nil {
   304  			return 0, 0, err
   305  		}
   306  	}
   307  
   308  	var dirp *uint16
   309  	if len(attr.Dir) != 0 {
   310  		dirp, err = UTF16PtrFromString(attr.Dir)
   311  		if err != nil {
   312  			return 0, 0, err
   313  		}
   314  	}
   315  
   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 }
   326  
   327  	p, _ := GetCurrentProcess()
   328  	parentProcess := p
   329  	if sys.ParentProcess != 0 {
   330  		parentProcess = sys.ParentProcess
   331  	}
   332  	fd := make([]Handle, len(attr.Files))
   333  	for i := range attr.Files {
   334  		if attr.Files[i] > 0 {
   335  			destinationProcessHandle := parentProcess
   336  
   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
   341  			}
   342  
   343  			err := DuplicateHandle(p, Handle(attr.Files[i]), destinationProcessHandle, &fd[i], 0, true, DUPLICATE_SAME_ACCESS)
   344  			if err != nil {
   345  				return 0, 0, err
   346  			}
   347  			defer DuplicateHandle(parentProcess, fd[i], 0, nil, 0, false, DUPLICATE_CLOSE_SOURCE)
   348  		}
   349  	}
   350  	si := new(_STARTUPINFOEXW)
   351  	si.ProcThreadAttributeList, err = newProcThreadAttributeList(2)
   352  	if err != nil {
   353  		return 0, 0, err
   354  	}
   355  	defer deleteProcThreadAttributeList(si.ProcThreadAttributeList)
   356  	si.Cb = uint32(unsafe.Sizeof(*si))
   357  	si.Flags = STARTF_USESTDHANDLES
   358  	if sys.HideWindow {
   359  		si.Flags |= STARTF_USESHOWWINDOW
   360  		si.ShowWindow = SW_HIDE
   361  	}
   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)
   364  		if err != nil {
   365  			return 0, 0, err
   366  		}
   367  	}
   368  	si.StdInput = fd[0]
   369  	si.StdOutput = fd[1]
   370  	si.StdErr = fd[2]
   371  
   372  	fd = append(fd, sys.AdditionalInheritedHandles...)
   373  
   374  	// On Windows 7, console handles aren't real handles, so don't pass them
   375  	// through to PROC_THREAD_ATTRIBUTE_HANDLE_LIST.
   376  	for i := range fd {
   377  		if isLegacyWin7ConsoleHandle(fd[i]) {
   378  			fd[i] = 0
   379  		}
   380  	}
   381  
   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.
   384  	j := 0
   385  	for i := range fd {
   386  		if fd[i] != 0 {
   387  			fd[j] = fd[i]
   388  			j++
   389  		}
   390  	}
   391  	fd = fd[:j]
   392  
   393  	// Do not accidentally inherit more than these handles.
   394  	if len(fd) > 0 {
   395  		err = updateProcThreadAttribute(si.ProcThreadAttributeList, 0, _PROC_THREAD_ATTRIBUTE_HANDLE_LIST, unsafe.Pointer(&fd[0]), uintptr(len(fd))*unsafe.Sizeof(fd[0]), nil, nil)
   396  		if err != nil {
   397  			return 0, 0, err
   398  		}
   399  	}
   400  
   401  	pi := new(ProcessInformation)
   402  	flags := sys.CreationFlags | CREATE_UNICODE_ENVIRONMENT | _EXTENDED_STARTUPINFO_PRESENT
   403  	if sys.Token != 0 {
   404  		err = CreateProcessAsUser(sys.Token, argv0p, argvp, sys.ProcessAttributes, sys.ThreadAttributes, len(fd) > 0 && !sys.NoInheritHandles, flags, createEnvBlock(attr.Env), dirp, &si.StartupInfo, pi)
   405  	} else {
   406  		err = CreateProcess(argv0p, argvp, sys.ProcessAttributes, sys.ThreadAttributes, len(fd) > 0 && !sys.NoInheritHandles, flags, createEnvBlock(attr.Env), dirp, &si.StartupInfo, pi)
   407  	}
   408  	if err != nil {
   409  		return 0, 0, err
   410  	}
   411  	defer CloseHandle(Handle(pi.Thread))
   412  	runtime.KeepAlive(fd)
   413  	runtime.KeepAlive(sys)
   414  
   415  	return int(pi.ProcessId), uintptr(pi.Process), nil
   416  }
   417  
   418  func Exec(argv0 string, argv []string, envv []string) (err error) {
   419  	return EWINDOWS
   420  }
   421  

View as plain text