...
Run Format

Source file src/syscall/exec_windows.go

Documentation: syscall

  // Copyright 2009 The Go Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style
  // license that can be found in the LICENSE file.
  
  // Fork, exec, wait, etc.
  
  package syscall
  
  import (
  	"sync"
  	"unicode/utf16"
  	"unsafe"
  )
  
  var ForkLock sync.RWMutex
  
  // EscapeArg rewrites command line argument s as prescribed
  // in http://msdn.microsoft.com/en-us/library/ms880421.
  // This function returns "" (2 double quotes) if s is empty.
  // Alternatively, these transformations are done:
  // - every back slash (\) is doubled, but only if immediately
  //   followed by double quote (");
  // - every double quote (") is escaped by back slash (\);
  // - finally, s is wrapped with double quotes (arg -> "arg"),
  //   but only if there is space or tab inside s.
  func EscapeArg(s string) string {
  	if len(s) == 0 {
  		return "\"\""
  	}
  	n := len(s)
  	hasSpace := false
  	for i := 0; i < len(s); i++ {
  		switch s[i] {
  		case '"', '\\':
  			n++
  		case ' ', '\t':
  			hasSpace = true
  		}
  	}
  	if hasSpace {
  		n += 2
  	}
  	if n == len(s) {
  		return s
  	}
  
  	qs := make([]byte, n)
  	j := 0
  	if hasSpace {
  		qs[j] = '"'
  		j++
  	}
  	slashes := 0
  	for i := 0; i < len(s); i++ {
  		switch s[i] {
  		default:
  			slashes = 0
  			qs[j] = s[i]
  		case '\\':
  			slashes++
  			qs[j] = s[i]
  		case '"':
  			for ; slashes > 0; slashes-- {
  				qs[j] = '\\'
  				j++
  			}
  			qs[j] = '\\'
  			j++
  			qs[j] = s[i]
  		}
  		j++
  	}
  	if hasSpace {
  		for ; slashes > 0; slashes-- {
  			qs[j] = '\\'
  			j++
  		}
  		qs[j] = '"'
  		j++
  	}
  	return string(qs[:j])
  }
  
  // makeCmdLine builds a command line out of args by escaping "special"
  // characters and joining the arguments with spaces.
  func makeCmdLine(args []string) string {
  	var s string
  	for _, v := range args {
  		if s != "" {
  			s += " "
  		}
  		s += EscapeArg(v)
  	}
  	return s
  }
  
  // createEnvBlock converts an array of environment strings into
  // the representation required by CreateProcess: a sequence of NUL
  // terminated strings followed by a nil.
  // Last bytes are two UCS-2 NULs, or four NUL bytes.
  func createEnvBlock(envv []string) *uint16 {
  	if len(envv) == 0 {
  		return &utf16.Encode([]rune("\x00\x00"))[0]
  	}
  	length := 0
  	for _, s := range envv {
  		length += len(s) + 1
  	}
  	length += 1
  
  	b := make([]byte, length)
  	i := 0
  	for _, s := range envv {
  		l := len(s)
  		copy(b[i:i+l], []byte(s))
  		copy(b[i+l:i+l+1], []byte{0})
  		i = i + l + 1
  	}
  	copy(b[i:i+1], []byte{0})
  
  	return &utf16.Encode([]rune(string(b)))[0]
  }
  
  func CloseOnExec(fd Handle) {
  	SetHandleInformation(Handle(fd), HANDLE_FLAG_INHERIT, 0)
  }
  
  func SetNonblock(fd Handle, nonblocking bool) (err error) {
  	return nil
  }
  
  // FullPath retrieves the full path of the specified file.
  func FullPath(name string) (path string, err error) {
  	p, err := UTF16PtrFromString(name)
  	if err != nil {
  		return "", err
  	}
  	n := uint32(100)
  	for {
  		buf := make([]uint16, n)
  		n, err = GetFullPathName(p, uint32(len(buf)), &buf[0], nil)
  		if err != nil {
  			return "", err
  		}
  		if n <= uint32(len(buf)) {
  			return UTF16ToString(buf[:n]), nil
  		}
  	}
  }
  
  func isSlash(c uint8) bool {
  	return c == '\\' || c == '/'
  }
  
  func normalizeDir(dir string) (name string, err error) {
  	ndir, err := FullPath(dir)
  	if err != nil {
  		return "", err
  	}
  	if len(ndir) > 2 && isSlash(ndir[0]) && isSlash(ndir[1]) {
  		// dir cannot have \\server\share\path form
  		return "", EINVAL
  	}
  	return ndir, nil
  }
  
  func volToUpper(ch int) int {
  	if 'a' <= ch && ch <= 'z' {
  		ch += 'A' - 'a'
  	}
  	return ch
  }
  
  func joinExeDirAndFName(dir, p string) (name string, err error) {
  	if len(p) == 0 {
  		return "", EINVAL
  	}
  	if len(p) > 2 && isSlash(p[0]) && isSlash(p[1]) {
  		// \\server\share\path form
  		return p, nil
  	}
  	if len(p) > 1 && p[1] == ':' {
  		// has drive letter
  		if len(p) == 2 {
  			return "", EINVAL
  		}
  		if isSlash(p[2]) {
  			return p, nil
  		} else {
  			d, err := normalizeDir(dir)
  			if err != nil {
  				return "", err
  			}
  			if volToUpper(int(p[0])) == volToUpper(int(d[0])) {
  				return FullPath(d + "\\" + p[2:])
  			} else {
  				return FullPath(p)
  			}
  		}
  	} else {
  		// no drive letter
  		d, err := normalizeDir(dir)
  		if err != nil {
  			return "", err
  		}
  		if isSlash(p[0]) {
  			return FullPath(d[:2] + p)
  		} else {
  			return FullPath(d + "\\" + p)
  		}
  	}
  }
  
  type ProcAttr struct {
  	Dir   string
  	Env   []string
  	Files []uintptr
  	Sys   *SysProcAttr
  }
  
  type SysProcAttr struct {
  	HideWindow    bool
  	CmdLine       string // used if non-empty, else the windows command line is built by escaping the arguments passed to StartProcess
  	CreationFlags uint32
  }
  
  var zeroProcAttr ProcAttr
  var zeroSysProcAttr SysProcAttr
  
  func StartProcess(argv0 string, argv []string, attr *ProcAttr) (pid int, handle uintptr, err error) {
  	if len(argv0) == 0 {
  		return 0, 0, EWINDOWS
  	}
  	if attr == nil {
  		attr = &zeroProcAttr
  	}
  	sys := attr.Sys
  	if sys == nil {
  		sys = &zeroSysProcAttr
  	}
  
  	if len(attr.Files) > 3 {
  		return 0, 0, EWINDOWS
  	}
  	if len(attr.Files) < 3 {
  		return 0, 0, EINVAL
  	}
  
  	if len(attr.Dir) != 0 {
  		// StartProcess assumes that argv0 is relative to attr.Dir,
  		// because it implies Chdir(attr.Dir) before executing argv0.
  		// Windows CreateProcess assumes the opposite: it looks for
  		// argv0 relative to the current directory, and, only once the new
  		// process is started, it does Chdir(attr.Dir). We are adjusting
  		// for that difference here by making argv0 absolute.
  		var err error
  		argv0, err = joinExeDirAndFName(attr.Dir, argv0)
  		if err != nil {
  			return 0, 0, err
  		}
  	}
  	argv0p, err := UTF16PtrFromString(argv0)
  	if err != nil {
  		return 0, 0, err
  	}
  
  	var cmdline string
  	// Windows CreateProcess takes the command line as a single string:
  	// use attr.CmdLine if set, else build the command line by escaping
  	// and joining each argument with spaces
  	if sys.CmdLine != "" {
  		cmdline = sys.CmdLine
  	} else {
  		cmdline = makeCmdLine(argv)
  	}
  
  	var argvp *uint16
  	if len(cmdline) != 0 {
  		argvp, err = UTF16PtrFromString(cmdline)
  		if err != nil {
  			return 0, 0, err
  		}
  	}
  
  	var dirp *uint16
  	if len(attr.Dir) != 0 {
  		dirp, err = UTF16PtrFromString(attr.Dir)
  		if err != nil {
  			return 0, 0, err
  		}
  	}
  
  	// Acquire the fork lock so that no other threads
  	// create new fds that are not yet close-on-exec
  	// before we fork.
  	ForkLock.Lock()
  	defer ForkLock.Unlock()
  
  	p, _ := GetCurrentProcess()
  	fd := make([]Handle, len(attr.Files))
  	for i := range attr.Files {
  		if attr.Files[i] > 0 {
  			err := DuplicateHandle(p, Handle(attr.Files[i]), p, &fd[i], 0, true, DUPLICATE_SAME_ACCESS)
  			if err != nil {
  				return 0, 0, err
  			}
  			defer CloseHandle(Handle(fd[i]))
  		}
  	}
  	si := new(StartupInfo)
  	si.Cb = uint32(unsafe.Sizeof(*si))
  	si.Flags = STARTF_USESTDHANDLES
  	if sys.HideWindow {
  		si.Flags |= STARTF_USESHOWWINDOW
  		si.ShowWindow = SW_HIDE
  	}
  	si.StdInput = fd[0]
  	si.StdOutput = fd[1]
  	si.StdErr = fd[2]
  
  	pi := new(ProcessInformation)
  
  	flags := sys.CreationFlags | CREATE_UNICODE_ENVIRONMENT
  	err = CreateProcess(argv0p, argvp, nil, nil, true, flags, createEnvBlock(attr.Env), dirp, si, pi)
  	if err != nil {
  		return 0, 0, err
  	}
  	defer CloseHandle(Handle(pi.Thread))
  
  	return int(pi.ProcessId), uintptr(pi.Process), nil
  }
  
  func Exec(argv0 string, argv []string, envv []string) (err error) {
  	return EWINDOWS
  }
  

View as plain text