summaryrefslogtreecommitdiffstats
path: root/plugin/rpcplugin/process_windows.go
diff options
context:
space:
mode:
Diffstat (limited to 'plugin/rpcplugin/process_windows.go')
-rw-r--r--plugin/rpcplugin/process_windows.go648
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
-}