github.com/tcnksm/go@v0.0.0-20141208075154-439b32936367/src/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  	buf := make([]uint16, 100)
   139  	n, err := GetFullPathName(p, uint32(len(buf)), &buf[0], nil)
   140  	if err != nil {
   141  		return "", err
   142  	}
   143  	if n > uint32(len(buf)) {
   144  		// Windows is asking for bigger buffer.
   145  		buf = make([]uint16, n)
   146  		n, err = GetFullPathName(p, uint32(len(buf)), &buf[0], nil)
   147  		if err != nil {
   148  			return "", err
   149  		}
   150  		if n > uint32(len(buf)) {
   151  			return "", EINVAL
   152  		}
   153  	}
   154  	return UTF16ToString(buf[:n]), nil
   155  }
   156  
   157  func isSlash(c uint8) bool {
   158  	return c == '\\' || c == '/'
   159  }
   160  
   161  func normalizeDir(dir string) (name string, err error) {
   162  	ndir, err := FullPath(dir)
   163  	if err != nil {
   164  		return "", err
   165  	}
   166  	if len(ndir) > 2 && isSlash(ndir[0]) && isSlash(ndir[1]) {
   167  		// dir cannot have \\server\share\path form
   168  		return "", EINVAL
   169  	}
   170  	return ndir, nil
   171  }
   172  
   173  func volToUpper(ch int) int {
   174  	if 'a' <= ch && ch <= 'z' {
   175  		ch += 'A' - 'a'
   176  	}
   177  	return ch
   178  }
   179  
   180  func joinExeDirAndFName(dir, p string) (name string, err error) {
   181  	if len(p) == 0 {
   182  		return "", EINVAL
   183  	}
   184  	if len(p) > 2 && isSlash(p[0]) && isSlash(p[1]) {
   185  		// \\server\share\path form
   186  		return p, nil
   187  	}
   188  	if len(p) > 1 && p[1] == ':' {
   189  		// has drive letter
   190  		if len(p) == 2 {
   191  			return "", EINVAL
   192  		}
   193  		if isSlash(p[2]) {
   194  			return p, nil
   195  		} else {
   196  			d, err := normalizeDir(dir)
   197  			if err != nil {
   198  				return "", err
   199  			}
   200  			if volToUpper(int(p[0])) == volToUpper(int(d[0])) {
   201  				return FullPath(d + "\\" + p[2:])
   202  			} else {
   203  				return FullPath(p)
   204  			}
   205  		}
   206  	} else {
   207  		// no drive letter
   208  		d, err := normalizeDir(dir)
   209  		if err != nil {
   210  			return "", err
   211  		}
   212  		if isSlash(p[0]) {
   213  			return FullPath(d[:2] + p)
   214  		} else {
   215  			return FullPath(d + "\\" + p)
   216  		}
   217  	}
   218  	// we shouldn't be here
   219  	return "", EINVAL
   220  }
   221  
   222  type ProcAttr struct {
   223  	Dir   string
   224  	Env   []string
   225  	Files []uintptr
   226  	Sys   *SysProcAttr
   227  }
   228  
   229  type SysProcAttr struct {
   230  	HideWindow    bool
   231  	CmdLine       string // used if non-empty, else the windows command line is built by escaping the arguments passed to StartProcess
   232  	CreationFlags uint32
   233  }
   234  
   235  var zeroProcAttr ProcAttr
   236  var zeroSysProcAttr SysProcAttr
   237  
   238  func StartProcess(argv0 string, argv []string, attr *ProcAttr) (pid int, handle uintptr, err error) {
   239  	if len(argv0) == 0 {
   240  		return 0, 0, EWINDOWS
   241  	}
   242  	if attr == nil {
   243  		attr = &zeroProcAttr
   244  	}
   245  	sys := attr.Sys
   246  	if sys == nil {
   247  		sys = &zeroSysProcAttr
   248  	}
   249  
   250  	if len(attr.Files) > 3 {
   251  		return 0, 0, EWINDOWS
   252  	}
   253  
   254  	if len(attr.Dir) != 0 {
   255  		// StartProcess assumes that argv0 is relative to attr.Dir,
   256  		// because it implies Chdir(attr.Dir) before executing argv0.
   257  		// Windows CreateProcess assumes the opposite: it looks for
   258  		// argv0 relative to the current directory, and, only once the new
   259  		// process is started, it does Chdir(attr.Dir). We are adjusting
   260  		// for that difference here by making argv0 absolute.
   261  		var err error
   262  		argv0, err = joinExeDirAndFName(attr.Dir, argv0)
   263  		if err != nil {
   264  			return 0, 0, err
   265  		}
   266  	}
   267  	argv0p, err := UTF16PtrFromString(argv0)
   268  	if err != nil {
   269  		return 0, 0, err
   270  	}
   271  
   272  	var cmdline string
   273  	// Windows CreateProcess takes the command line as a single string:
   274  	// use attr.CmdLine if set, else build the command line by escaping
   275  	// and joining each argument with spaces
   276  	if sys.CmdLine != "" {
   277  		cmdline = sys.CmdLine
   278  	} else {
   279  		cmdline = makeCmdLine(argv)
   280  	}
   281  
   282  	var argvp *uint16
   283  	if len(cmdline) != 0 {
   284  		argvp, err = UTF16PtrFromString(cmdline)
   285  		if err != nil {
   286  			return 0, 0, err
   287  		}
   288  	}
   289  
   290  	var dirp *uint16
   291  	if len(attr.Dir) != 0 {
   292  		dirp, err = UTF16PtrFromString(attr.Dir)
   293  		if err != nil {
   294  			return 0, 0, err
   295  		}
   296  	}
   297  
   298  	// Acquire the fork lock so that no other threads
   299  	// create new fds that are not yet close-on-exec
   300  	// before we fork.
   301  	ForkLock.Lock()
   302  	defer ForkLock.Unlock()
   303  
   304  	p, _ := GetCurrentProcess()
   305  	fd := make([]Handle, len(attr.Files))
   306  	for i := range attr.Files {
   307  		if attr.Files[i] > 0 {
   308  			err := DuplicateHandle(p, Handle(attr.Files[i]), p, &fd[i], 0, true, DUPLICATE_SAME_ACCESS)
   309  			if err != nil {
   310  				return 0, 0, err
   311  			}
   312  			defer CloseHandle(Handle(fd[i]))
   313  		}
   314  	}
   315  	si := new(StartupInfo)
   316  	si.Cb = uint32(unsafe.Sizeof(*si))
   317  	si.Flags = STARTF_USESTDHANDLES
   318  	if sys.HideWindow {
   319  		si.Flags |= STARTF_USESHOWWINDOW
   320  		si.ShowWindow = SW_HIDE
   321  	}
   322  	si.StdInput = fd[0]
   323  	si.StdOutput = fd[1]
   324  	si.StdErr = fd[2]
   325  
   326  	pi := new(ProcessInformation)
   327  
   328  	flags := sys.CreationFlags | CREATE_UNICODE_ENVIRONMENT
   329  	err = CreateProcess(argv0p, argvp, nil, nil, true, flags, createEnvBlock(attr.Env), dirp, si, pi)
   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  }