exec_windows.mx raw

   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  	procAttrList, err := newProcThreadAttributeList(2)
 336  	if err != nil {
 337  		return 0, 0, err
 338  	}
 339  	defer procAttrList.delete()
 340  	si := new(_STARTUPINFOEXW)
 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 = procAttrList.update(_PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, unsafe.Pointer(&sys.ParentProcess), unsafe.Sizeof(sys.ParentProcess))
 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 = procAttrList.update(_PROC_THREAD_ATTRIBUTE_HANDLE_LIST, unsafe.Pointer(&fd[0]), uintptr(len(fd))*unsafe.Sizeof(fd[0]))
 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  	si.ProcThreadAttributeList = procAttrList.list()
 386  	pi := new(ProcessInformation)
 387  	flags := sys.CreationFlags | CREATE_UNICODE_ENVIRONMENT | _EXTENDED_STARTUPINFO_PRESENT
 388  	if sys.Token != 0 {
 389  		err = CreateProcessAsUser(sys.Token, argv0p, argvp, sys.ProcessAttributes, sys.ThreadAttributes, willInheritHandles, flags, &envBlock[0], dirp, &si.StartupInfo, pi)
 390  	} else {
 391  		err = CreateProcess(argv0p, argvp, sys.ProcessAttributes, sys.ThreadAttributes, willInheritHandles, flags, &envBlock[0], dirp, &si.StartupInfo, pi)
 392  	}
 393  	if err != nil {
 394  		return 0, 0, err
 395  	}
 396  	defer CloseHandle(Handle(pi.Thread))
 397  	runtime.KeepAlive(fd)
 398  	runtime.KeepAlive(sys)
 399  
 400  	return int(pi.ProcessId), uintptr(pi.Process), nil
 401  }
 402  
 403  func Exec(argv0 string, argv []string, envv []string) (err error) {
 404  	return EWINDOWS
 405  }
 406