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