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