| // 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) | |
| } | |
| } | |
| // 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.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 | |
| } |