github.com/hashicorp/go-plugin@v1.6.0/internal/cmdrunner/cmd_runner.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package cmdrunner 5 6 import ( 7 "context" 8 "errors" 9 "fmt" 10 "io" 11 "os" 12 "os/exec" 13 14 "github.com/hashicorp/go-hclog" 15 "github.com/hashicorp/go-plugin/runner" 16 ) 17 18 var ( 19 _ runner.Runner = (*CmdRunner)(nil) 20 21 // ErrProcessNotFound is returned when a client is instantiated to 22 // reattach to an existing process and it isn't found. 23 ErrProcessNotFound = errors.New("Reattachment process not found") 24 ) 25 26 const unrecognizedRemotePluginMessage = `This usually means 27 the plugin was not compiled for this architecture, 28 the plugin is missing dynamic-link libraries necessary to run, 29 the plugin is not executable by this process due to file permissions, or 30 the plugin failed to negotiate the initial go-plugin protocol handshake 31 %s` 32 33 // CmdRunner implements the runner.Runner interface. It mostly just passes through 34 // to exec.Cmd methods. 35 type CmdRunner struct { 36 logger hclog.Logger 37 cmd *exec.Cmd 38 39 stdout io.ReadCloser 40 stderr io.ReadCloser 41 42 // Cmd info is persisted early, since the process information will be removed 43 // after Kill is called. 44 path string 45 pid int 46 47 addrTranslator 48 } 49 50 // NewCmdRunner returns an implementation of runner.Runner for running a plugin 51 // as a subprocess. It must be passed a cmd that hasn't yet been started. 52 func NewCmdRunner(logger hclog.Logger, cmd *exec.Cmd) (*CmdRunner, error) { 53 stdout, err := cmd.StdoutPipe() 54 if err != nil { 55 return nil, err 56 } 57 58 stderr, err := cmd.StderrPipe() 59 if err != nil { 60 return nil, err 61 } 62 63 return &CmdRunner{ 64 logger: logger, 65 cmd: cmd, 66 stdout: stdout, 67 stderr: stderr, 68 path: cmd.Path, 69 }, nil 70 } 71 72 func (c *CmdRunner) Start(_ context.Context) error { 73 c.logger.Debug("starting plugin", "path", c.cmd.Path, "args", c.cmd.Args) 74 err := c.cmd.Start() 75 if err != nil { 76 return err 77 } 78 79 c.pid = c.cmd.Process.Pid 80 c.logger.Debug("plugin started", "path", c.path, "pid", c.pid) 81 return nil 82 } 83 84 func (c *CmdRunner) Wait(_ context.Context) error { 85 return c.cmd.Wait() 86 } 87 88 func (c *CmdRunner) Kill(_ context.Context) error { 89 if c.cmd.Process != nil { 90 err := c.cmd.Process.Kill() 91 // Swallow ErrProcessDone, we support calling Kill multiple times. 92 if !errors.Is(err, os.ErrProcessDone) { 93 return err 94 } 95 return nil 96 } 97 98 return nil 99 } 100 101 func (c *CmdRunner) Stdout() io.ReadCloser { 102 return c.stdout 103 } 104 105 func (c *CmdRunner) Stderr() io.ReadCloser { 106 return c.stderr 107 } 108 109 func (c *CmdRunner) Name() string { 110 return c.path 111 } 112 113 func (c *CmdRunner) ID() string { 114 return fmt.Sprintf("%d", c.pid) 115 } 116 117 // peTypes is a list of Portable Executable (PE) machine types from https://learn.microsoft.com/en-us/windows/win32/debug/pe-format 118 // mapped to GOARCH types. It is not comprehensive, and only includes machine types that Go supports. 119 var peTypes = map[uint16]string{ 120 0x14c: "386", 121 0x1c0: "arm", 122 0x6264: "loong64", 123 0x8664: "amd64", 124 0xaa64: "arm64", 125 } 126 127 func (c *CmdRunner) Diagnose(_ context.Context) string { 128 return fmt.Sprintf(unrecognizedRemotePluginMessage, additionalNotesAboutCommand(c.cmd.Path)) 129 }