github.com/dorkamotorka/go/src@v0.0.0-20230614113921-187095f0e316/syscall/exec_windows.go (about)

     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  	"internal/bytealg"
    11  	"runtime"
    12  	"sync"
    13  	"unicode/utf16"
    14  	"unsafe"
    15  )
    16  
    17  var ForkLock sync.RWMutex
    18  
    19  // EscapeArg rewrites command line argument s as prescribed
    20  // in https://msdn.microsoft.com/en-us/library/ms880421.
    21  // This function returns "" (2 double quotes) if s is empty.
    22  // Alternatively, these transformations are done:
    23  //   - every back slash (\) is doubled, but only if immediately
    24  //     followed by double quote (");
    25  //   - every double quote (") is escaped by back slash (\);
    26  //   - finally, s is wrapped with double quotes (arg -> "arg"),
    27  //     but only if there is space or tab inside s.
    28  func EscapeArg(s string) string {
    29  	if len(s) == 0 {
    30  		return `""`
    31  	}
    32  	for i := 0; i < len(s); i++ {
    33  		switch s[i] {
    34  		case '"', '\\', ' ', '\t':
    35  			// Some escaping required.
    36  			b := make([]byte, 0, len(s)+2)
    37  			b = appendEscapeArg(b, s)
    38  			return string(b)
    39  		}
    40  	}
    41  	return s
    42  }
    43  
    44  // appendEscapeArg escapes the string s, as per escapeArg,
    45  // appends the result to b, and returns the updated slice.
    46  func appendEscapeArg(b []byte, s string) []byte {
    47  	if len(s) == 0 {
    48  		return append(b, `""`...)
    49  	}
    50  
    51  	needsBackslash := false
    52  	hasSpace := false
    53  	for i := 0; i < len(s); i++ {
    54  		switch s[i] {
    55  		case '"', '\\':
    56  			needsBackslash = true
    57  		case ' ', '\t':
    58  			hasSpace = true
    59  		}
    60  	}
    61  
    62  	if !needsBackslash && !hasSpace {
    63  		// No special handling required; normal case.
    64  		return append(b, s...)
    65  	}
    66  	if !needsBackslash {
    67  		// hasSpace is true, so we need to quote the string.
    68  		b = append(b, '"')
    69  		b = append(b, s...)
    70  		return append(b, '"')
    71  	}
    72  
    73  	if hasSpace {
    74  		b = append(b, '"')
    75  	}
    76  	slashes := 0
    77  	for i := 0; i < len(s); i++ {
    78  		c := s[i]
    79  		switch c {
    80  		default:
    81  			slashes = 0
    82  		case '\\':
    83  			slashes++
    84  		case '"':
    85  			for ; slashes > 0; slashes-- {
    86  				b = append(b, '\\')
    87  			}
    88  			b = append(b, '\\')
    89  		}
    90  		b = append(b, c)
    91  	}
    92  	if hasSpace {
    93  		for ; slashes > 0; slashes-- {
    94  			b = append(b, '\\')
    95  		}
    96  		b = append(b, '"')
    97  	}
    98  
    99  	return b
   100  }
   101  
   102  // makeCmdLine builds a command line out of args by escaping "special"
   103  // characters and joining the arguments with spaces.
   104  func makeCmdLine(args []string) string {
   105  	var b []byte
   106  	for _, v := range args {
   107  		if len(b) > 0 {
   108  			b = append(b, ' ')
   109  		}
   110  		b = appendEscapeArg(b, v)
   111  	}
   112  	return string(b)
   113  }
   114  
   115  // createEnvBlock converts an array of environment strings into
   116  // the representation required by CreateProcess: a sequence of NUL
   117  // terminated strings followed by a nil.
   118  // Last bytes are two UCS-2 NULs, or four NUL bytes.
   119  // If any string contains a NUL, it returns (nil, EINVAL).
   120  func createEnvBlock(envv []string) (*uint16, error) {
   121  	if len(envv) == 0 {
   122  		return &utf16.Encode([]rune("\x00\x00"))[0], nil
   123  	}
   124  	length := 0
   125  	for _, s := range envv {
   126  		if bytealg.IndexByteString(s, 0) != -1 {
   127  			return nil, EINVAL
   128  		}
   129  		length += len(s) + 1
   130  	}
   131  	length += 1
   132  
   133  	b := make([]byte, length)
   134  	i := 0
   135  	for _, s := range envv {
   136  		l := len(s)
   137  		copy(b[i:i+l], []byte(s))
   138  		copy(b[i+l:i+l+1], []byte{0})
   139  		i = i + l + 1
   140  	}
   141  	copy(b[i:i+1], []byte{0})
   142  
   143  	return &utf16.Encode([]rune(string(b)))[0], nil
   144  }
   145  
   146  func CloseOnExec(fd Handle) {
   147  	SetHandleInformation(Handle(fd), HANDLE_FLAG_INHERIT, 0)
   148  }
   149  
   150  func SetNonblock(fd Handle, nonblocking bool) (err error) {
   151  	return nil
   152  }
   153  
   154  // FullPath retrieves the full path of the specified file.
   155  func FullPath(name string) (path string, err error) {
   156  	p, err := UTF16PtrFromString(name)
   157  	if err != nil {
   158  		return "", err
   159  	}
   160  	n := uint32(100)
   161  	for {
   162  		buf := make([]uint16, n)
   163  		n, err = GetFullPathName(p, uint32(len(buf)), &buf[0], nil)
   164  		if err != nil {
   165  			return "", err
   166  		}
   167  		if n <= uint32(len(buf)) {
   168  			return UTF16ToString(buf[:n]), nil
   169  		}
   170  	}
   171  }
   172  
   173  func isSlash(c uint8) bool {
   174  	return c == '\\' || c == '/'
   175  }
   176  
   177  func normalizeDir(dir string) (name string, err error) {
   178  	ndir, err := FullPath(dir)
   179  	if err != nil {
   180  		return "", err
   181  	}
   182  	if len(ndir) > 2 && isSlash(ndir[0]) && isSlash(ndir[1]) {
   183  		// dir cannot have \\server\share\path form
   184  		return "", EINVAL
   185  	}
   186  	return ndir, nil
   187  }
   188  
   189  func volToUpper(ch int) int {
   190  	if 'a' <= ch && ch <= 'z' {
   191  		ch += 'A' - 'a'
   192  	}
   193  	return ch
   194  }
   195  
   196  func joinExeDirAndFName(dir, p string) (name string, err error) {
   197  	if len(p) == 0 {
   198  		return "", EINVAL
   199  	}
   200  	if len(p) > 2 && isSlash(p[0]) && isSlash(p[1]) {
   201  		// \\server\share\path form
   202  		return p, nil
   203  	}
   204  	if len(p) > 1 && p[1] == ':' {
   205  		// has drive letter
   206  		if len(p) == 2 {
   207  			return "", EINVAL
   208  		}
   209  		if isSlash(p[2]) {
   210  			return p, nil
   211  		} else {
   212  			d, err := normalizeDir(dir)
   213  			if err != nil {
   214  				return "", err
   215  			}
   216  			if volToUpper(int(p[0])) == volToUpper(int(d[0])) {
   217  				return FullPath(d + "\\" + p[2:])
   218  			} else {
   219  				return FullPath(p)
   220  			}
   221  		}
   222  	} else {
   223  		// no drive letter
   224  		d, err := normalizeDir(dir)
   225  		if err != nil {
   226  			return "", err
   227  		}
   228  		if isSlash(p[0]) {
   229  			return FullPath(d[:2] + p)
   230  		} else {
   231  			return FullPath(d + "\\" + p)
   232  		}
   233  	}
   234  }
   235  
   236  type ProcAttr struct {
   237  	Dir   string
   238  	Env   []string
   239  	Files []uintptr
   240  	Sys   *SysProcAttr
   241  }
   242  
   243  type SysProcAttr struct {
   244  	HideWindow                 bool
   245  	CmdLine                    string // used if non-empty, else the windows command line is built by escaping the arguments passed to StartProcess
   246  	CreationFlags              uint32
   247  	Token                      Token               // if set, runs new process in the security context represented by the token
   248  	ProcessAttributes          *SecurityAttributes // if set, applies these security attributes as the descriptor for the new process
   249  	ThreadAttributes           *SecurityAttributes // if set, applies these security attributes as the descriptor for the main thread of the new process
   250  	NoInheritHandles           bool                // if set, each inheritable handle in the calling process is not inherited by the new process
   251  	AdditionalInheritedHandles []Handle            // a list of additional handles, already marked as inheritable, that will be inherited by the new process
   252  	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
   253  }
   254  
   255  var zeroProcAttr ProcAttr
   256  var zeroSysProcAttr SysProcAttr
   257  
   258  func StartProcess(argv0 string, argv []string, attr *ProcAttr) (pid int, handle uintptr, err error) {
   259  	if len(argv0) == 0 {
   260  		return 0, 0, EWINDOWS
   261  	}
   262  	if attr == nil {
   263  		attr = &zeroProcAttr
   264  	}
   265  	sys := attr.Sys
   266  	if sys == nil {
   267  		sys = &zeroSysProcAttr
   268  	}
   269  
   270  	if len(attr.Files) > 3 {
   271  		return 0, 0, EWINDOWS
   272  	}
   273  	if len(attr.Files) < 3 {
   274  		return 0, 0, EINVAL
   275  	}
   276  
   277  	if len(attr.Dir) != 0 {
   278  		// StartProcess assumes that argv0 is relative to attr.Dir,
   279  		// because it implies Chdir(attr.Dir) before executing argv0.
   280  		// Windows CreateProcess assumes the opposite: it looks for
   281  		// argv0 relative to the current directory, and, only once the new
   282  		// process is started, it does Chdir(attr.Dir). We are adjusting
   283  		// for that difference here by making argv0 absolute.
   284  		var err error
   285  		argv0, err = joinExeDirAndFName(attr.Dir, argv0)
   286  		if err != nil {
   287  			return 0, 0, err
   288  		}
   289  	}
   290  	argv0p, err := UTF16PtrFromString(argv0)
   291  	if err != nil {
   292  		return 0, 0, err
   293  	}
   294  
   295  	var cmdline string
   296  	// Windows CreateProcess takes the command line as a single string:
   297  	// use attr.CmdLine if set, else build the command line by escaping
   298  	// and joining each argument with spaces
   299  	if sys.CmdLine != "" {
   300  		cmdline = sys.CmdLine
   301  	} else {
   302  		cmdline = makeCmdLine(argv)
   303  	}
   304  
   305  	var argvp *uint16
   306  	if len(cmdline) != 0 {
   307  		argvp, err = UTF16PtrFromString(cmdline)
   308  		if err != nil {
   309  			return 0, 0, err
   310  		}
   311  	}
   312  
   313  	var dirp *uint16
   314  	if len(attr.Dir) != 0 {
   315  		dirp, err = UTF16PtrFromString(attr.Dir)
   316  		if err != nil {
   317  			return 0, 0, err
   318  		}
   319  	}
   320  
   321  	var maj, min, build uint32
   322  	rtlGetNtVersionNumbers(&maj, &min, &build)
   323  	isWin7 := maj < 6 || (maj == 6 && min <= 1)
   324  	// NT kernel handles are divisible by 4, with the bottom 3 bits left as
   325  	// a tag. The fully set tag correlates with the types of handles we're
   326  	// concerned about here.  Except, the kernel will interpret some
   327  	// special handle values, like -1, -2, and so forth, so kernelbase.dll
   328  	// checks to see that those bottom three bits are checked, but that top
   329  	// bit is not checked.
   330  	isLegacyWin7ConsoleHandle := func(handle Handle) bool { return isWin7 && handle&0x10000003 == 3 }
   331  
   332  	p, _ := GetCurrentProcess()
   333  	parentProcess := p
   334  	if sys.ParentProcess != 0 {
   335  		parentProcess = sys.ParentProcess
   336  	}
   337  	fd := make([]Handle, len(attr.Files))
   338  	for i := range attr.Files {
   339  		if attr.Files[i] > 0 {
   340  			destinationProcessHandle := parentProcess
   341  
   342  			// On Windows 7, console handles aren't real handles, and can only be duplicated
   343  			// into the current process, not a parent one, which amounts to the same thing.
   344  			if parentProcess != p && isLegacyWin7ConsoleHandle(Handle(attr.Files[i])) {
   345  				destinationProcessHandle = p
   346  			}
   347  
   348  			err := DuplicateHandle(p, Handle(attr.Files[i]), destinationProcessHandle, &fd[i], 0, true, DUPLICATE_SAME_ACCESS)
   349  			if err != nil {
   350  				return 0, 0, err
   351  			}
   352  			defer DuplicateHandle(parentProcess, fd[i], 0, nil, 0, false, DUPLICATE_CLOSE_SOURCE)
   353  		}
   354  	}
   355  	si := new(_STARTUPINFOEXW)
   356  	si.ProcThreadAttributeList, err = newProcThreadAttributeList(2)
   357  	if err != nil {
   358  		return 0, 0, err
   359  	}
   360  	defer deleteProcThreadAttributeList(si.ProcThreadAttributeList)
   361  	si.Cb = uint32(unsafe.Sizeof(*si))
   362  	si.Flags = STARTF_USESTDHANDLES
   363  	if sys.HideWindow {
   364  		si.Flags |= STARTF_USESHOWWINDOW
   365  		si.ShowWindow = SW_HIDE
   366  	}
   367  	if sys.ParentProcess != 0 {
   368  		err = updateProcThreadAttribute(si.ProcThreadAttributeList, 0, _PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, unsafe.Pointer(&sys.ParentProcess), unsafe.Sizeof(sys.ParentProcess), nil, nil)
   369  		if err != nil {
   370  			return 0, 0, err
   371  		}
   372  	}
   373  	si.StdInput = fd[0]
   374  	si.StdOutput = fd[1]
   375  	si.StdErr = fd[2]
   376  
   377  	fd = append(fd, sys.AdditionalInheritedHandles...)
   378  
   379  	// On Windows 7, console handles aren't real handles, so don't pass them
   380  	// through to PROC_THREAD_ATTRIBUTE_HANDLE_LIST.
   381  	for i := range fd {
   382  		if isLegacyWin7ConsoleHandle(fd[i]) {
   383  			fd[i] = 0
   384  		}
   385  	}
   386  
   387  	// The presence of a NULL handle in the list is enough to cause PROC_THREAD_ATTRIBUTE_HANDLE_LIST
   388  	// to treat the entire list as empty, so remove NULL handles.
   389  	j := 0
   390  	for i := range fd {
   391  		if fd[i] != 0 {
   392  			fd[j] = fd[i]
   393  			j++
   394  		}
   395  	}
   396  	fd = fd[:j]
   397  
   398  	willInheritHandles := len(fd) > 0 && !sys.NoInheritHandles
   399  
   400  	// Do not accidentally inherit more than these handles.
   401  	if willInheritHandles {
   402  		err = updateProcThreadAttribute(si.ProcThreadAttributeList, 0, _PROC_THREAD_ATTRIBUTE_HANDLE_LIST, unsafe.Pointer(&fd[0]), uintptr(len(fd))*unsafe.Sizeof(fd[0]), nil, nil)
   403  		if err != nil {
   404  			return 0, 0, err
   405  		}
   406  	}
   407  
   408  	envBlock, err := createEnvBlock(attr.Env)
   409  	if err != nil {
   410  		return 0, 0, err
   411  	}
   412  
   413  	pi := new(ProcessInformation)
   414  	flags := sys.CreationFlags | CREATE_UNICODE_ENVIRONMENT | _EXTENDED_STARTUPINFO_PRESENT
   415  	if sys.Token != 0 {
   416  		err = CreateProcessAsUser(sys.Token, argv0p, argvp, sys.ProcessAttributes, sys.ThreadAttributes, willInheritHandles, flags, envBlock, dirp, &si.StartupInfo, pi)
   417  	} else {
   418  		err = CreateProcess(argv0p, argvp, sys.ProcessAttributes, sys.ThreadAttributes, willInheritHandles, flags, envBlock, dirp, &si.StartupInfo, pi)
   419  	}
   420  	if err != nil {
   421  		return 0, 0, err
   422  	}
   423  	defer CloseHandle(Handle(pi.Thread))
   424  	runtime.KeepAlive(fd)
   425  	runtime.KeepAlive(sys)
   426  
   427  	return int(pi.ProcessId), uintptr(pi.Process), nil
   428  }
   429  
   430  func Exec(argv0 string, argv []string, envv []string) (err error) {
   431  	return EWINDOWS
   432  }