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

View as plain text