github.com/ltltlt/go-source-code@v0.0.0-20190830023027-95be009773aa/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  	"sync"
    11  	"unicode/utf16"
    12  	"unsafe"
    13  )
    14  
    15  var ForkLock sync.RWMutex
    16  
    17  // EscapeArg rewrites command line argument s as prescribed
    18  // in http://msdn.microsoft.com/en-us/library/ms880421.
    19  // This function returns "" (2 double quotes) if s is empty.
    20  // Alternatively, these transformations are done:
    21  // - every back slash (\) is doubled, but only if immediately
    22  //   followed by double quote (");
    23  // - every double quote (") is escaped by back slash (\);
    24  // - finally, s is wrapped with double quotes (arg -> "arg"),
    25  //   but only if there is space or tab inside s.
    26  func EscapeArg(s string) string {
    27  	if len(s) == 0 {
    28  		return "\"\""
    29  	}
    30  	n := len(s)
    31  	hasSpace := false
    32  	for i := 0; i < len(s); i++ {
    33  		switch s[i] {
    34  		case '"', '\\':
    35  			n++
    36  		case ' ', '\t':
    37  			hasSpace = true
    38  		}
    39  	}
    40  	if hasSpace {
    41  		n += 2
    42  	}
    43  	if n == len(s) {
    44  		return s
    45  	}
    46  
    47  	qs := make([]byte, n)
    48  	j := 0
    49  	if hasSpace {
    50  		qs[j] = '"'
    51  		j++
    52  	}
    53  	slashes := 0
    54  	for i := 0; i < len(s); i++ {
    55  		switch s[i] {
    56  		default:
    57  			slashes = 0
    58  			qs[j] = s[i]
    59  		case '\\':
    60  			slashes++
    61  			qs[j] = s[i]
    62  		case '"':
    63  			for ; slashes > 0; slashes-- {
    64  				qs[j] = '\\'
    65  				j++
    66  			}
    67  			qs[j] = '\\'
    68  			j++
    69  			qs[j] = s[i]
    70  		}
    71  		j++
    72  	}
    73  	if hasSpace {
    74  		for ; slashes > 0; slashes-- {
    75  			qs[j] = '\\'
    76  			j++
    77  		}
    78  		qs[j] = '"'
    79  		j++
    80  	}
    81  	return string(qs[:j])
    82  }
    83  
    84  // makeCmdLine builds a command line out of args by escaping "special"
    85  // characters and joining the arguments with spaces.
    86  func makeCmdLine(args []string) string {
    87  	var s string
    88  	for _, v := range args {
    89  		if s != "" {
    90  			s += " "
    91  		}
    92  		s += EscapeArg(v)
    93  	}
    94  	return s
    95  }
    96  
    97  // createEnvBlock converts an array of environment strings into
    98  // the representation required by CreateProcess: a sequence of NUL
    99  // terminated strings followed by a nil.
   100  // Last bytes are two UCS-2 NULs, or four NUL bytes.
   101  func createEnvBlock(envv []string) *uint16 {
   102  	if len(envv) == 0 {
   103  		return &utf16.Encode([]rune("\x00\x00"))[0]
   104  	}
   105  	length := 0
   106  	for _, s := range envv {
   107  		length += len(s) + 1
   108  	}
   109  	length += 1
   110  
   111  	b := make([]byte, length)
   112  	i := 0
   113  	for _, s := range envv {
   114  		l := len(s)
   115  		copy(b[i:i+l], []byte(s))
   116  		copy(b[i+l:i+l+1], []byte{0})
   117  		i = i + l + 1
   118  	}
   119  	copy(b[i:i+1], []byte{0})
   120  
   121  	return &utf16.Encode([]rune(string(b)))[0]
   122  }
   123  
   124  func CloseOnExec(fd Handle) {
   125  	SetHandleInformation(Handle(fd), HANDLE_FLAG_INHERIT, 0)
   126  }
   127  
   128  func SetNonblock(fd Handle, nonblocking bool) (err error) {
   129  	return nil
   130  }
   131  
   132  // FullPath retrieves the full path of the specified file.
   133  func FullPath(name string) (path string, err error) {
   134  	p, err := UTF16PtrFromString(name)
   135  	if err != nil {
   136  		return "", err
   137  	}
   138  	n := uint32(100)
   139  	for {
   140  		buf := make([]uint16, n)
   141  		n, err = GetFullPathName(p, uint32(len(buf)), &buf[0], nil)
   142  		if err != nil {
   143  			return "", err
   144  		}
   145  		if n <= uint32(len(buf)) {
   146  			return UTF16ToString(buf[:n]), nil
   147  		}
   148  	}
   149  }
   150  
   151  func isSlash(c uint8) bool {
   152  	return c == '\\' || c == '/'
   153  }
   154  
   155  func normalizeDir(dir string) (name string, err error) {
   156  	ndir, err := FullPath(dir)
   157  	if err != nil {
   158  		return "", err
   159  	}
   160  	if len(ndir) > 2 && isSlash(ndir[0]) && isSlash(ndir[1]) {
   161  		// dir cannot have \\server\share\path form
   162  		return "", EINVAL
   163  	}
   164  	return ndir, nil
   165  }
   166  
   167  func volToUpper(ch int) int {
   168  	if 'a' <= ch && ch <= 'z' {
   169  		ch += 'A' - 'a'
   170  	}
   171  	return ch
   172  }
   173  
   174  func joinExeDirAndFName(dir, p string) (name string, err error) {
   175  	if len(p) == 0 {
   176  		return "", EINVAL
   177  	}
   178  	if len(p) > 2 && isSlash(p[0]) && isSlash(p[1]) {
   179  		// \\server\share\path form
   180  		return p, nil
   181  	}
   182  	if len(p) > 1 && p[1] == ':' {
   183  		// has drive letter
   184  		if len(p) == 2 {
   185  			return "", EINVAL
   186  		}
   187  		if isSlash(p[2]) {
   188  			return p, nil
   189  		} else {
   190  			d, err := normalizeDir(dir)
   191  			if err != nil {
   192  				return "", err
   193  			}
   194  			if volToUpper(int(p[0])) == volToUpper(int(d[0])) {
   195  				return FullPath(d + "\\" + p[2:])
   196  			} else {
   197  				return FullPath(p)
   198  			}
   199  		}
   200  	} else {
   201  		// no drive letter
   202  		d, err := normalizeDir(dir)
   203  		if err != nil {
   204  			return "", err
   205  		}
   206  		if isSlash(p[0]) {
   207  			return FullPath(d[:2] + p)
   208  		} else {
   209  			return FullPath(d + "\\" + p)
   210  		}
   211  	}
   212  }
   213  
   214  type ProcAttr struct {
   215  	Dir   string
   216  	Env   []string
   217  	Files []uintptr
   218  	Sys   *SysProcAttr
   219  }
   220  
   221  type SysProcAttr struct {
   222  	HideWindow    bool
   223  	CmdLine       string // used if non-empty, else the windows command line is built by escaping the arguments passed to StartProcess
   224  	CreationFlags uint32
   225  	Token         Token // if set, runs new process in the security context represented by the token
   226  }
   227  
   228  var zeroProcAttr ProcAttr
   229  var zeroSysProcAttr SysProcAttr
   230  
   231  func StartProcess(argv0 string, argv []string, attr *ProcAttr) (pid int, handle uintptr, err error) {
   232  	if len(argv0) == 0 {
   233  		return 0, 0, EWINDOWS
   234  	}
   235  	if attr == nil {
   236  		attr = &zeroProcAttr
   237  	}
   238  	sys := attr.Sys
   239  	if sys == nil {
   240  		sys = &zeroSysProcAttr
   241  	}
   242  
   243  	if len(attr.Files) > 3 {
   244  		return 0, 0, EWINDOWS
   245  	}
   246  	if len(attr.Files) < 3 {
   247  		return 0, 0, EINVAL
   248  	}
   249  
   250  	if len(attr.Dir) != 0 {
   251  		// StartProcess assumes that argv0 is relative to attr.Dir,
   252  		// because it implies Chdir(attr.Dir) before executing argv0.
   253  		// Windows CreateProcess assumes the opposite: it looks for
   254  		// argv0 relative to the current directory, and, only once the new
   255  		// process is started, it does Chdir(attr.Dir). We are adjusting
   256  		// for that difference here by making argv0 absolute.
   257  		var err error
   258  		argv0, err = joinExeDirAndFName(attr.Dir, argv0)
   259  		if err != nil {
   260  			return 0, 0, err
   261  		}
   262  	}
   263  	argv0p, err := UTF16PtrFromString(argv0)
   264  	if err != nil {
   265  		return 0, 0, err
   266  	}
   267  
   268  	var cmdline string
   269  	// Windows CreateProcess takes the command line as a single string:
   270  	// use attr.CmdLine if set, else build the command line by escaping
   271  	// and joining each argument with spaces
   272  	if sys.CmdLine != "" {
   273  		cmdline = sys.CmdLine
   274  	} else {
   275  		cmdline = makeCmdLine(argv)
   276  	}
   277  
   278  	var argvp *uint16
   279  	if len(cmdline) != 0 {
   280  		argvp, err = UTF16PtrFromString(cmdline)
   281  		if err != nil {
   282  			return 0, 0, err
   283  		}
   284  	}
   285  
   286  	var dirp *uint16
   287  	if len(attr.Dir) != 0 {
   288  		dirp, err = UTF16PtrFromString(attr.Dir)
   289  		if err != nil {
   290  			return 0, 0, err
   291  		}
   292  	}
   293  
   294  	// Acquire the fork lock so that no other threads
   295  	// create new fds that are not yet close-on-exec
   296  	// before we fork.
   297  	ForkLock.Lock()
   298  	defer ForkLock.Unlock()
   299  
   300  	p, _ := GetCurrentProcess()
   301  	fd := make([]Handle, len(attr.Files))
   302  	for i := range attr.Files {
   303  		if attr.Files[i] > 0 {
   304  			err := DuplicateHandle(p, Handle(attr.Files[i]), p, &fd[i], 0, true, DUPLICATE_SAME_ACCESS)
   305  			if err != nil {
   306  				return 0, 0, err
   307  			}
   308  			defer CloseHandle(Handle(fd[i]))
   309  		}
   310  	}
   311  	si := new(StartupInfo)
   312  	si.Cb = uint32(unsafe.Sizeof(*si))
   313  	si.Flags = STARTF_USESTDHANDLES
   314  	if sys.HideWindow {
   315  		si.Flags |= STARTF_USESHOWWINDOW
   316  		si.ShowWindow = SW_HIDE
   317  	}
   318  	si.StdInput = fd[0]
   319  	si.StdOutput = fd[1]
   320  	si.StdErr = fd[2]
   321  
   322  	pi := new(ProcessInformation)
   323  
   324  	flags := sys.CreationFlags | CREATE_UNICODE_ENVIRONMENT
   325  	if sys.Token != 0 {
   326  		err = CreateProcessAsUser(sys.Token, argv0p, argvp, nil, nil, true, flags, createEnvBlock(attr.Env), dirp, si, pi)
   327  	} else {
   328  		err = CreateProcess(argv0p, argvp, nil, nil, true, flags, createEnvBlock(attr.Env), dirp, si, pi)
   329  	}
   330  	if err != nil {
   331  		return 0, 0, err
   332  	}
   333  	defer CloseHandle(Handle(pi.Thread))
   334  
   335  	return int(pi.ProcessId), uintptr(pi.Process), nil
   336  }
   337  
   338  func Exec(argv0 string, argv []string, envv []string) (err error) {
   339  	return EWINDOWS
   340  }