| // 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 ( |
| "runtime" |
| "sync" |
| "unicode/utf16" |
| "unsafe" |
| ) |
| |
| var ForkLock sync.RWMutex |
| |
| // EscapeArg rewrites command line argument s as prescribed |
| // in https://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 `""` |
| } |
| for i := 0; i < len(s); i++ { |
| switch s[i] { |
| case '"', '\\', ' ', '\t': |
| // Some escaping required. |
| b := make([]byte, 0, len(s)+2) |
| b = appendEscapeArg(b, s) |
| return string(b) |
| } |
| } |
| return s |
| } |
| |
| // appendEscapeArg escapes the string s, as per escapeArg, |
| // appends the result to b, and returns the updated slice. |
| func appendEscapeArg(b []byte, s string) []byte { |
| if len(s) == 0 { |
| return append(b, `""`...) |
| } |
| |
| needsBackslash := false |
| hasSpace := false |
| for i := 0; i < len(s); i++ { |
| switch s[i] { |
| case '"', '\\': |
| needsBackslash = true |
| case ' ', '\t': |
| hasSpace = true |
| } |
| } |
| |
| if !needsBackslash && !hasSpace { |
| // No special handling required; normal case. |
| return append(b, s...) |
| } |
| if !needsBackslash { |
| // hasSpace is true, so we need to quote the string. |
| b = append(b, '"') |
| b = append(b, s...) |
| return append(b, '"') |
| } |
| |
| if hasSpace { |
| b = append(b, '"') |
| } |
| slashes := 0 |
| for i := 0; i < len(s); i++ { |
| c := s[i] |
| switch c { |
| default: |
| slashes = 0 |
| case '\\': |
| slashes++ |
| case '"': |
| for ; slashes > 0; slashes-- { |
| b = append(b, '\\') |
| } |
| b = append(b, '\\') |
| } |
| b = append(b, c) |
| } |
| if hasSpace { |
| for ; slashes > 0; slashes-- { |
| b = append(b, '\\') |
| } |
| b = append(b, '"') |
| } |
| |
| return b |
| } |
| |
| // makeCmdLine builds a command line out of args by escaping "special" |
| // characters and joining the arguments with spaces. |
| func makeCmdLine(args []string) string { |
| var b []byte |
| for _, v := range args { |
| if len(b) > 0 { |
| b = append(b, ' ') |
| } |
| b = appendEscapeArg(b, v) |
| } |
| return string(b) |
| } |
| |
| // 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 |
| Token Token // if set, runs new process in the security context represented by the token |
| ProcessAttributes *SecurityAttributes // if set, applies these security attributes as the descriptor for the new process |
| ThreadAttributes *SecurityAttributes // if set, applies these security attributes as the descriptor for the main thread of the new process |
| NoInheritHandles bool // if set, each inheritable handle in the calling process is not inherited by the new process |
| AdditionalInheritedHandles []Handle // a list of additional handles, already marked as inheritable, that will be inherited by the new process |
| ParentProcess Handle // if non-zero, the new process regards the process given by this handle as its parent process, and AdditionalInheritedHandles, if set, should exist in this parent process |
| } |
| |
| 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 |
| } |
| } |
| |
| var maj, min, build uint32 |
| rtlGetNtVersionNumbers(&maj, &min, &build) |
| isWin7 := maj < 6 || (maj == 6 && min <= 1) |
| // NT kernel handles are divisible by 4, with the bottom 3 bits left as |
| // a tag. The fully set tag correlates with the types of handles we're |
| // concerned about here. Except, the kernel will interpret some |
| // special handle values, like -1, -2, and so forth, so kernelbase.dll |
| // checks to see that those bottom three bits are checked, but that top |
| // bit is not checked. |
| isLegacyWin7ConsoleHandle := func(handle Handle) bool { return isWin7 && handle&0x10000003 == 3 } |
| |
| p, _ := GetCurrentProcess() |
| parentProcess := p |
| if sys.ParentProcess != 0 { |
| parentProcess = sys.ParentProcess |
| } |
| fd := make([]Handle, len(attr.Files)) |
| for i := range attr.Files { |
| if attr.Files[i] > 0 { |
| destinationProcessHandle := parentProcess |
| |
| // On Windows 7, console handles aren't real handles, and can only be duplicated |
| // into the current process, not a parent one, which amounts to the same thing. |
| if parentProcess != p && isLegacyWin7ConsoleHandle(Handle(attr.Files[i])) { |
| destinationProcessHandle = p |
| } |
| |
| err := DuplicateHandle(p, Handle(attr.Files[i]), destinationProcessHandle, &fd[i], 0, true, DUPLICATE_SAME_ACCESS) |
| if err != nil { |
| return 0, 0, err |
| } |
| defer DuplicateHandle(parentProcess, fd[i], 0, nil, 0, false, DUPLICATE_CLOSE_SOURCE) |
| } |
| } |
| si := new(_STARTUPINFOEXW) |
| si.ProcThreadAttributeList, err = newProcThreadAttributeList(2) |
| if err != nil { |
| return 0, 0, err |
| } |
| defer deleteProcThreadAttributeList(si.ProcThreadAttributeList) |
| si.Cb = uint32(unsafe.Sizeof(*si)) |
| si.Flags = STARTF_USESTDHANDLES |
| if sys.HideWindow { |
| si.Flags |= STARTF_USESHOWWINDOW |
| si.ShowWindow = SW_HIDE |
| } |
| if sys.ParentProcess != 0 { |
| err = updateProcThreadAttribute(si.ProcThreadAttributeList, 0, _PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, unsafe.Pointer(&sys.ParentProcess), unsafe.Sizeof(sys.ParentProcess), nil, nil) |
| if err != nil { |
| return 0, 0, err |
| } |
| } |
| si.StdInput = fd[0] |
| si.StdOutput = fd[1] |
| si.StdErr = fd[2] |
| |
| fd = append(fd, sys.AdditionalInheritedHandles...) |
| |
| // On Windows 7, console handles aren't real handles, so don't pass them |
| // through to PROC_THREAD_ATTRIBUTE_HANDLE_LIST. |
| for i := range fd { |
| if isLegacyWin7ConsoleHandle(fd[i]) { |
| fd[i] = 0 |
| } |
| } |
| |
| // The presence of a NULL handle in the list is enough to cause PROC_THREAD_ATTRIBUTE_HANDLE_LIST |
| // to treat the entire list as empty, so remove NULL handles. |
| j := 0 |
| for i := range fd { |
| if fd[i] != 0 { |
| fd[j] = fd[i] |
| j++ |
| } |
| } |
| fd = fd[:j] |
| |
| // Do not accidentally inherit more than these handles. |
| if len(fd) > 0 { |
| err = updateProcThreadAttribute(si.ProcThreadAttributeList, 0, _PROC_THREAD_ATTRIBUTE_HANDLE_LIST, unsafe.Pointer(&fd[0]), uintptr(len(fd))*unsafe.Sizeof(fd[0]), nil, nil) |
| if err != nil { |
| return 0, 0, err |
| } |
| } |
| |
| pi := new(ProcessInformation) |
| flags := sys.CreationFlags | CREATE_UNICODE_ENVIRONMENT | _EXTENDED_STARTUPINFO_PRESENT |
| if sys.Token != 0 { |
| err = CreateProcessAsUser(sys.Token, argv0p, argvp, sys.ProcessAttributes, sys.ThreadAttributes, len(fd) > 0 && !sys.NoInheritHandles, flags, createEnvBlock(attr.Env), dirp, &si.StartupInfo, pi) |
| } else { |
| err = CreateProcess(argv0p, argvp, sys.ProcessAttributes, sys.ThreadAttributes, len(fd) > 0 && !sys.NoInheritHandles, flags, createEnvBlock(attr.Env), dirp, &si.StartupInfo, pi) |
| } |
| if err != nil { |
| return 0, 0, err |
| } |
| defer CloseHandle(Handle(pi.Thread)) |
| runtime.KeepAlive(fd) |
| runtime.KeepAlive(sys) |
| |
| return int(pi.ProcessId), uintptr(pi.Process), nil |
| } |
| |
| func Exec(argv0 string, argv []string, envv []string) (err error) { |
| return EWINDOWS |
| } |