Skip to content
Snippets Groups Projects
exec_windows.go 7.29 KiB
Newer Older
  • Learn to ignore specific revisions
  • // 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"
    
    // 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.
    
    	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]
    
    	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 += " "
    		}
    
    // 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)
    
    Russ Cox's avatar
    Russ Cox committed
    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)
    
    Russ Cox's avatar
    Russ Cox committed
    		if err != nil {
    
    		if n <= uint32(len(buf)) {
    			return UTF16ToString(buf[:n]), nil
    
    func isSlash(c uint8) bool {
    	return c == '\\' || c == '/'
    }
    
    Russ Cox's avatar
    Russ Cox committed
    func normalizeDir(dir string) (name string, err error) {
    
    	ndir, err := FullPath(dir)
    
    Russ Cox's avatar
    Russ Cox committed
    	if err != nil {
    
    		return "", err
    	}
    	if len(ndir) > 2 && isSlash(ndir[0]) && isSlash(ndir[1]) {
    		// dir cannot have \\server\share\path form
    		return "", EINVAL
    	}
    
    Russ Cox's avatar
    Russ Cox committed
    	return ndir, nil
    
    func volToUpper(ch int) int {
    	if 'a' <= ch && ch <= 'z' {
    		ch += 'A' - 'a'
    	}
    	return ch
    }
    
    
    Russ Cox's avatar
    Russ Cox committed
    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
    
    Russ Cox's avatar
    Russ Cox committed
    		return p, nil
    
    	}
    	if len(p) > 1 && p[1] == ':' {
    		// has drive letter
    		if len(p) == 2 {
    			return "", EINVAL
    		}
    		if isSlash(p[2]) {
    
    Russ Cox's avatar
    Russ Cox committed
    			return p, nil
    
    		} else {
    			d, err := normalizeDir(dir)
    
    Russ Cox's avatar
    Russ Cox committed
    			if err != nil {
    
    				return "", err
    			}
    			if volToUpper(int(p[0])) == volToUpper(int(d[0])) {
    
    				return FullPath(d + "\\" + p[2:])
    
    			}
    		}
    	} else {
    		// no drive letter
    		d, err := normalizeDir(dir)
    
    Russ Cox's avatar
    Russ Cox committed
    		if err != nil {
    
    			return "", err
    		}
    		if isSlash(p[0]) {
    
    			return FullPath(d[:2] + p)
    
    			return FullPath(d + "\\" + p)
    
    	Files []uintptr
    
    	HideWindow    bool
    	CmdLine       string // used if non-empty, else the windows command line is built by escaping the arguments passed to StartProcess
    	CreationFlags uint32
    
    	Token         Handle // if set, runs new process in the security context represented by the token
    
    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
    	}
    
    	sys := attr.Sys
    	if sys == nil {
    		sys = &zeroSysProcAttr
    	}
    
    
    		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.
    
    Russ Cox's avatar
    Russ Cox committed
    		var err error
    
    		argv0, err = joinExeDirAndFName(attr.Dir, argv0)
    
    Russ Cox's avatar
    Russ Cox committed
    		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
    
    	var argvp *uint16
    
    		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)
    
    Russ Cox's avatar
    Russ Cox committed
    			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
    
    	si.StdInput = fd[0]
    	si.StdOutput = fd[1]
    	si.StdErr = fd[2]
    
    	pi := new(ProcessInformation)
    
    
    	flags := sys.CreationFlags | CREATE_UNICODE_ENVIRONMENT
    
    	if sys.Token != 0 {
    		err = CreateProcessAsUser(sys.Token, argv0p, argvp, nil, nil, true, flags, createEnvBlock(attr.Env), dirp, si, pi)
    	} else {
    		err = CreateProcess(argv0p, argvp, nil, nil, true, flags, createEnvBlock(attr.Env), dirp, si, pi)
    	}
    
    Russ Cox's avatar
    Russ Cox committed
    	if err != nil {
    
    		return 0, 0, err
    
    	defer CloseHandle(Handle(pi.Thread))
    
    	return int(pi.ProcessId), uintptr(pi.Process), nil
    
    Russ Cox's avatar
    Russ Cox committed
    func Exec(argv0 string, argv []string, envv []string) (err error) {