Source file src/syscall/exec_windows.go

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

View as plain text