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  }