mirror of git://gcc.gnu.org/git/gcc.git
				
				
				
			
		
			
				
	
	
		
			342 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			342 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			Go
		
	
	
	
| // 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
 | |
| }
 | |
| 
 | |
| // getFullPath retrieves the full path of the specified file.
 | |
| // Just a wrapper for Windows GetFullPathName api.
 | |
| func getFullPath(name string) (path string, err error) {
 | |
| 	p, err := UTF16PtrFromString(name)
 | |
| 	if err != nil {
 | |
| 		return "", err
 | |
| 	}
 | |
| 	buf := make([]uint16, 100)
 | |
| 	n, err := GetFullPathName(p, uint32(len(buf)), &buf[0], nil)
 | |
| 	if err != nil {
 | |
| 		return "", err
 | |
| 	}
 | |
| 	if n > uint32(len(buf)) {
 | |
| 		// Windows is asking for bigger buffer.
 | |
| 		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 "", EINVAL
 | |
| 		}
 | |
| 	}
 | |
| 	return UTF16ToString(buf[:n]), nil
 | |
| }
 | |
| 
 | |
| func isSlash(c uint8) bool {
 | |
| 	return c == '\\' || c == '/'
 | |
| }
 | |
| 
 | |
| func normalizeDir(dir string) (name string, err error) {
 | |
| 	ndir, err := getFullPath(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 getFullPath(d + "\\" + p[2:])
 | |
| 			} else {
 | |
| 				return getFullPath(p)
 | |
| 			}
 | |
| 		}
 | |
| 	} else {
 | |
| 		// no drive letter
 | |
| 		d, err := normalizeDir(dir)
 | |
| 		if err != nil {
 | |
| 			return "", err
 | |
| 		}
 | |
| 		if isSlash(p[0]) {
 | |
| 			return getFullPath(d[:2] + p)
 | |
| 		} else {
 | |
| 			return getFullPath(d + "\\" + p)
 | |
| 		}
 | |
| 	}
 | |
| 	// we shouldn't be here
 | |
| 	return "", EINVAL
 | |
| }
 | |
| 
 | |
| 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.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
 | |
| }
 |