diff options
Diffstat (limited to 'plugin/rpcplugin/process_windows.go')
-rw-r--r-- | plugin/rpcplugin/process_windows.go | 648 |
1 files changed, 0 insertions, 648 deletions
diff --git a/plugin/rpcplugin/process_windows.go b/plugin/rpcplugin/process_windows.go deleted file mode 100644 index 069f147c1..000000000 --- a/plugin/rpcplugin/process_windows.go +++ /dev/null @@ -1,648 +0,0 @@ -package rpcplugin - -import ( - "context" - "errors" - "fmt" - "io" - "os" - "os/exec" - "path/filepath" - "runtime" - "strconv" - "strings" - "syscall" - "unicode/utf16" - "unsafe" - - pkgerrors "github.com/pkg/errors" -) - -type process struct { - command *cmd -} - -func newProcess(ctx context.Context, path string) (Process, io.ReadWriteCloser, error) { - ipc, childFiles, err := NewIPC() - if err != nil { - return nil, nil, err - } - defer childFiles[0].Close() - defer childFiles[1].Close() - - cmd := commandContext(ctx, path) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - cmd.ExtraFiles = childFiles - cmd.Env = append(os.Environ(), - fmt.Sprintf("MM_IPC_FD0=%v", childFiles[0].Fd()), - fmt.Sprintf("MM_IPC_FD1=%v", childFiles[1].Fd()), - ) - err = cmd.Start() - if err != nil { - ipc.Close() - return nil, nil, err - } - - return &process{ - command: cmd, - }, ipc, nil -} - -func (p *process) Wait() error { - return p.command.Wait() -} - -func inheritedProcessIPC() (io.ReadWriteCloser, error) { - fd0, err := strconv.ParseUint(os.Getenv("MM_IPC_FD0"), 0, 64) - if err != nil { - return nil, pkgerrors.Wrapf(err, "unable to get ipc file descriptor 0") - } - fd1, err := strconv.ParseUint(os.Getenv("MM_IPC_FD1"), 0, 64) - if err != nil { - return nil, pkgerrors.Wrapf(err, "unable to get ipc file descriptor 1") - } - return InheritedIPC(uintptr(fd0), uintptr(fd1)) -} - -// XXX: EVERYTHING BELOW THIS IS COPIED / PASTED STANDARD LIBRARY CODE! -// IT CAN BE DELETED IF / WHEN THIS ISSUE IS RESOLVED: https://github.com/golang/go/issues/21085 - -// Just about all of os/exec/exec.go is copied / pasted below, altered to use our modified startProcess functions even -// further below. - -type cmd struct { - // Path is the path of the command to run. - // - // This is the only field that must be set to a non-zero - // value. If Path is relative, it is evaluated relative - // to Dir. - Path string - - // Args holds command line arguments, including the command as Args[0]. - // If the Args field is empty or nil, Run uses {Path}. - // - // In typical use, both Path and Args are set by calling Command. - Args []string - - // Env specifies the environment of the process. - // If Env is nil, Run uses the current process's environment. - Env []string - - // Dir specifies the working directory of the command. - // If Dir is the empty string, Run runs the command in the - // calling process's current directory. - Dir string - - // Stdin specifies the process's standard input. - // If Stdin is nil, the process reads from the null device (os.DevNull). - // If Stdin is an *os.File, the process's standard input is connected - // directly to that file. - // Otherwise, during the execution of the command a separate - // goroutine reads from Stdin and delivers that data to the command - // over a pipe. In this case, Wait does not complete until the goroutine - // stops copying, either because it has reached the end of Stdin - // (EOF or a read error) or because writing to the pipe returned an error. - Stdin io.Reader - - // Stdout and Stderr specify the process's standard output and error. - // - // If either is nil, Run connects the corresponding file descriptor - // to the null device (os.DevNull). - // - // If Stdout and Stderr are the same writer, at most one - // goroutine at a time will call Write. - Stdout io.Writer - Stderr io.Writer - - // ExtraFiles specifies additional open files to be inherited by the - // new process. It does not include standard input, standard output, or - // standard error. If non-nil, entry i becomes file descriptor 3+i. - // - // BUG(rsc): On OS X 10.6, child processes may sometimes inherit unwanted fds. - // https://golang.org/issue/2603 - ExtraFiles []*os.File - - // SysProcAttr holds optional, operating system-specific attributes. - // Run passes it to os.StartProcess as the os.ProcAttr's Sys field. - SysProcAttr *syscall.SysProcAttr - - // Process is the underlying process, once started. - Process *os.Process - - // ProcessState contains information about an exited process, - // available after a call to Wait or Run. - ProcessState *os.ProcessState - - ctx context.Context // nil means none - lookPathErr error // LookPath error, if any. - finished bool // when Wait was called - childFiles []*os.File - closeAfterStart []io.Closer - closeAfterWait []io.Closer - goroutine []func() error - errch chan error // one send per goroutine - waitDone chan struct{} -} - -func command(name string, arg ...string) *cmd { - cmd := &cmd{ - Path: name, - Args: append([]string{name}, arg...), - } - if filepath.Base(name) == name { - if lp, err := exec.LookPath(name); err != nil { - cmd.lookPathErr = err - } else { - cmd.Path = lp - } - } - return cmd -} - -func commandContext(ctx context.Context, name string, arg ...string) *cmd { - if ctx == nil { - panic("nil Context") - } - cmd := command(name, arg...) - cmd.ctx = ctx - return cmd -} - -func interfaceEqual(a, b interface{}) bool { - defer func() { - recover() - }() - return a == b -} - -func (c *cmd) envv() []string { - if c.Env != nil { - return c.Env - } - return os.Environ() -} - -func (c *cmd) argv() []string { - if len(c.Args) > 0 { - return c.Args - } - return []string{c.Path} -} - -var skipStdinCopyError func(error) bool - -func (c *cmd) stdin() (f *os.File, err error) { - if c.Stdin == nil { - f, err = os.Open(os.DevNull) - if err != nil { - return - } - c.closeAfterStart = append(c.closeAfterStart, f) - return - } - - if f, ok := c.Stdin.(*os.File); ok { - return f, nil - } - - pr, pw, err := os.Pipe() - if err != nil { - return - } - - c.closeAfterStart = append(c.closeAfterStart, pr) - c.closeAfterWait = append(c.closeAfterWait, pw) - c.goroutine = append(c.goroutine, func() error { - _, err := io.Copy(pw, c.Stdin) - if skip := skipStdinCopyError; skip != nil && skip(err) { - err = nil - } - if err1 := pw.Close(); err == nil { - err = err1 - } - return err - }) - return pr, nil -} - -func (c *cmd) stdout() (f *os.File, err error) { - return c.writerDescriptor(c.Stdout) -} - -func (c *cmd) stderr() (f *os.File, err error) { - if c.Stderr != nil && interfaceEqual(c.Stderr, c.Stdout) { - return c.childFiles[1], nil - } - return c.writerDescriptor(c.Stderr) -} - -func (c *cmd) writerDescriptor(w io.Writer) (f *os.File, err error) { - if w == nil { - f, err = os.OpenFile(os.DevNull, os.O_WRONLY, 0) - if err != nil { - return - } - c.closeAfterStart = append(c.closeAfterStart, f) - return - } - - if f, ok := w.(*os.File); ok { - return f, nil - } - - pr, pw, err := os.Pipe() - if err != nil { - return - } - - c.closeAfterStart = append(c.closeAfterStart, pw) - c.closeAfterWait = append(c.closeAfterWait, pr) - c.goroutine = append(c.goroutine, func() error { - _, err := io.Copy(w, pr) - pr.Close() // in case io.Copy stopped due to write error - return err - }) - return pw, nil -} - -func (c *cmd) closeDescriptors(closers []io.Closer) { - for _, fd := range closers { - fd.Close() - } -} - -func lookExtensions(path, dir string) (string, error) { - if filepath.Base(path) == path { - path = filepath.Join(".", path) - } - if dir == "" { - return exec.LookPath(path) - } - if filepath.VolumeName(path) != "" { - return exec.LookPath(path) - } - if len(path) > 1 && os.IsPathSeparator(path[0]) { - return exec.LookPath(path) - } - dirandpath := filepath.Join(dir, path) - // We assume that LookPath will only add file extension. - lp, err := exec.LookPath(dirandpath) - if err != nil { - return "", err - } - ext := strings.TrimPrefix(lp, dirandpath) - return path + ext, nil -} - -// Copied from os/exec/exec.go, altered to use osStartProcess (defined below). -func (c *cmd) Start() error { - if c.lookPathErr != nil { - c.closeDescriptors(c.closeAfterStart) - c.closeDescriptors(c.closeAfterWait) - return c.lookPathErr - } - if runtime.GOOS == "windows" { - lp, err := lookExtensions(c.Path, c.Dir) - if err != nil { - c.closeDescriptors(c.closeAfterStart) - c.closeDescriptors(c.closeAfterWait) - return err - } - c.Path = lp - } - if c.Process != nil { - return errors.New("exec: already started") - } - if c.ctx != nil { - select { - case <-c.ctx.Done(): - c.closeDescriptors(c.closeAfterStart) - c.closeDescriptors(c.closeAfterWait) - return c.ctx.Err() - default: - } - } - - type F func(*cmd) (*os.File, error) - for _, setupFd := range []F{(*cmd).stdin, (*cmd).stdout, (*cmd).stderr} { - fd, err := setupFd(c) - if err != nil { - c.closeDescriptors(c.closeAfterStart) - c.closeDescriptors(c.closeAfterWait) - return err - } - c.childFiles = append(c.childFiles, fd) - } - c.childFiles = append(c.childFiles, c.ExtraFiles...) - - var err error - c.Process, err = osStartProcess(c.Path, c.argv(), &os.ProcAttr{ - Dir: c.Dir, - Files: c.childFiles, - Env: c.envv(), - Sys: c.SysProcAttr, - }) - if err != nil { - c.closeDescriptors(c.closeAfterStart) - c.closeDescriptors(c.closeAfterWait) - return err - } - - c.closeDescriptors(c.closeAfterStart) - - c.errch = make(chan error, len(c.goroutine)) - for _, fn := range c.goroutine { - go func(fn func() error) { - c.errch <- fn() - }(fn) - } - - if c.ctx != nil { - c.waitDone = make(chan struct{}) - go func() { - select { - case <-c.ctx.Done(): - c.Process.Kill() - case <-c.waitDone: - } - }() - } - - return nil -} - -func (c *cmd) Wait() error { - if c.Process == nil { - return errors.New("exec: not started") - } - if c.finished { - return errors.New("exec: Wait was already called") - } - c.finished = true - - state, err := c.Process.Wait() - if c.waitDone != nil { - close(c.waitDone) - } - c.ProcessState = state - - var copyError error - for range c.goroutine { - if err := <-c.errch; err != nil && copyError == nil { - copyError = err - } - } - - c.closeDescriptors(c.closeAfterWait) - - if err != nil { - return err - } else if !state.Success() { - return &exec.ExitError{ProcessState: state} - } - - return copyError -} - -// Copied from os/exec_posix.go, altered to use syscallStartProcess (defined below). -func osStartProcess(name string, argv []string, attr *os.ProcAttr) (p *os.Process, err error) { - // If there is no SysProcAttr (ie. no Chroot or changed - // UID/GID), double-check existence of the directory we want - // to chdir into. We can make the error clearer this way. - if attr != nil && attr.Sys == nil && attr.Dir != "" { - if _, err := os.Stat(attr.Dir); err != nil { - pe := err.(*os.PathError) - pe.Op = "chdir" - return nil, pe - } - } - - sysattr := &syscall.ProcAttr{ - Dir: attr.Dir, - Env: attr.Env, - Sys: attr.Sys, - } - if sysattr.Env == nil { - sysattr.Env = os.Environ() - } - for _, f := range attr.Files { - sysattr.Files = append(sysattr.Files, f.Fd()) - } - - pid, _, e := syscallStartProcess(name, argv, sysattr) - if e != nil { - return nil, &os.PathError{Op: "fork/exec", Path: name, Err: e} - } - return os.FindProcess(pid) -} - -// Everything from this point on is copied from syscall/exec_windows.go - -func makeCmdLine(args []string) string { - var s string - for _, v := range args { - if s != "" { - s += " " - } - s += syscall.EscapeArg(v) - } - return s -} - -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 isSlash(c uint8) bool { - return c == '\\' || c == '/' -} - -func normalizeDir(dir string) (name string, err error) { - ndir, err := syscall.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 "", syscall.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 "", syscall.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 "", syscall.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 syscall.FullPath(d + "\\" + p[2:]) - } else { - return syscall.FullPath(p) - } - } - } else { - // no drive letter - d, err := normalizeDir(dir) - if err != nil { - return "", err - } - if isSlash(p[0]) { - return syscall.FullPath(d[:2] + p) - } else { - return syscall.FullPath(d + "\\" + p) - } - } -} - -var zeroProcAttr syscall.ProcAttr -var zeroSysProcAttr syscall.SysProcAttr - -// Has minor changes to support file inheritance. -func syscallStartProcess(argv0 string, argv []string, attr *syscall.ProcAttr) (pid int, handle uintptr, err error) { - if len(argv0) == 0 { - return 0, 0, syscall.EWINDOWS - } - if attr == nil { - attr = &zeroProcAttr - } - sys := attr.Sys - if sys == nil { - sys = &zeroSysProcAttr - } - - if len(attr.Files) < 3 { - return 0, 0, syscall.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 := syscall.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 = syscall.UTF16PtrFromString(cmdline) - if err != nil { - return 0, 0, err - } - } - - var dirp *uint16 - if len(attr.Dir) != 0 { - dirp, err = syscall.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. - syscall.ForkLock.Lock() - defer syscall.ForkLock.Unlock() - - p, _ := syscall.GetCurrentProcess() - fd := make([]syscall.Handle, len(attr.Files)) - for i := range attr.Files { - if attr.Files[i] <= 0 { - continue - } - if i < 3 { - err := syscall.DuplicateHandle(p, syscall.Handle(attr.Files[i]), p, &fd[i], 0, true, syscall.DUPLICATE_SAME_ACCESS) - if err != nil { - return 0, 0, err - } - defer syscall.CloseHandle(syscall.Handle(fd[i])) - } else { - // This is the modification that allows files to be inherited. - syscall.SetHandleInformation(syscall.Handle(attr.Files[i]), syscall.HANDLE_FLAG_INHERIT, 1) - defer syscall.SetHandleInformation(syscall.Handle(attr.Files[i]), syscall.HANDLE_FLAG_INHERIT, 0) - } - } - si := new(syscall.StartupInfo) - si.Cb = uint32(unsafe.Sizeof(*si)) - si.Flags = syscall.STARTF_USESTDHANDLES - if sys.HideWindow { - si.Flags |= syscall.STARTF_USESHOWWINDOW - si.ShowWindow = syscall.SW_HIDE - } - si.StdInput = fd[0] - si.StdOutput = fd[1] - si.StdErr = fd[2] - - pi := new(syscall.ProcessInformation) - - flags := sys.CreationFlags | syscall.CREATE_UNICODE_ENVIRONMENT - err = syscall.CreateProcess(argv0p, argvp, nil, nil, true, flags, createEnvBlock(attr.Env), dirp, si, pi) - if err != nil { - return 0, 0, err - } - defer syscall.CloseHandle(syscall.Handle(pi.Thread)) - - return int(pi.ProcessId), uintptr(pi.Process), nil -} |