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
|
|
}
|