github.com/janma/nomad@v0.11.3/drivers/qemu/driver.go (about)

     1  package qemu
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"net"
     8  	"os/exec"
     9  	"path/filepath"
    10  	"regexp"
    11  	"runtime"
    12  	"strings"
    13  	"time"
    14  
    15  	"github.com/coreos/go-semver/semver"
    16  	hclog "github.com/hashicorp/go-hclog"
    17  	"github.com/hashicorp/nomad/client/taskenv"
    18  	"github.com/hashicorp/nomad/drivers/shared/eventer"
    19  	"github.com/hashicorp/nomad/drivers/shared/executor"
    20  	"github.com/hashicorp/nomad/helper/pluginutils/hclutils"
    21  	"github.com/hashicorp/nomad/helper/pluginutils/loader"
    22  	"github.com/hashicorp/nomad/plugins/base"
    23  	"github.com/hashicorp/nomad/plugins/drivers"
    24  	"github.com/hashicorp/nomad/plugins/shared/hclspec"
    25  	pstructs "github.com/hashicorp/nomad/plugins/shared/structs"
    26  )
    27  
    28  const (
    29  	// pluginName is the name of the plugin
    30  	pluginName = "qemu"
    31  
    32  	// fingerprintPeriod is the interval at which the driver will send fingerprint responses
    33  	fingerprintPeriod = 30 * time.Second
    34  
    35  	// The key populated in Node Attributes to indicate presence of the Qemu driver
    36  	driverAttr        = "driver.qemu"
    37  	driverVersionAttr = "driver.qemu.version"
    38  
    39  	// Represents an ACPI shutdown request to the VM (emulates pressing a physical power button)
    40  	// Reference: https://en.wikibooks.org/wiki/QEMU/Monitor
    41  	qemuGracefulShutdownMsg = "system_powerdown\n"
    42  	qemuMonitorSocketName   = "qemu-monitor.sock"
    43  
    44  	// Maximum socket path length prior to qemu 2.10.1
    45  	qemuLegacyMaxMonitorPathLen = 108
    46  
    47  	// taskHandleVersion is the version of task handle which this driver sets
    48  	// and understands how to decode driver state
    49  	taskHandleVersion = 1
    50  )
    51  
    52  var (
    53  	// PluginID is the qemu plugin metadata registered in the plugin
    54  	// catalog.
    55  	PluginID = loader.PluginID{
    56  		Name:       pluginName,
    57  		PluginType: base.PluginTypeDriver,
    58  	}
    59  
    60  	// PluginConfig is the qemu driver factory function registered in the
    61  	// plugin catalog.
    62  	PluginConfig = &loader.InternalPluginConfig{
    63  		Config:  map[string]interface{}{},
    64  		Factory: func(ctx context.Context, l hclog.Logger) interface{} { return NewQemuDriver(ctx, l) },
    65  	}
    66  
    67  	versionRegex = regexp.MustCompile(`version (\d[\.\d+]+)`)
    68  
    69  	// Prior to qemu 2.10.1, monitor socket paths are truncated to 108 bytes.
    70  	// We should consider this if driver.qemu.version is < 2.10.1 and the
    71  	// generated monitor path is too long.
    72  	//
    73  	// Relevant fix is here:
    74  	// https://github.com/qemu/qemu/commit/ad9579aaa16d5b385922d49edac2c96c79bcfb6
    75  	qemuVersionLongSocketPathFix = semver.New("2.10.1")
    76  
    77  	// pluginInfo is the response returned for the PluginInfo RPC
    78  	pluginInfo = &base.PluginInfoResponse{
    79  		Type:              base.PluginTypeDriver,
    80  		PluginApiVersions: []string{drivers.ApiVersion010},
    81  		PluginVersion:     "0.1.0",
    82  		Name:              pluginName,
    83  	}
    84  
    85  	// configSpec is the hcl specification returned by the ConfigSchema RPC
    86  	configSpec = hclspec.NewObject(map[string]*hclspec.Spec{})
    87  
    88  	// taskConfigSpec is the hcl specification for the driver config section of
    89  	// a taskConfig within a job. It is returned in the TaskConfigSchema RPC
    90  	taskConfigSpec = hclspec.NewObject(map[string]*hclspec.Spec{
    91  		"image_path":        hclspec.NewAttr("image_path", "string", true),
    92  		"accelerator":       hclspec.NewAttr("accelerator", "string", false),
    93  		"graceful_shutdown": hclspec.NewAttr("graceful_shutdown", "bool", false),
    94  		"args":              hclspec.NewAttr("args", "list(string)", false),
    95  		"port_map":          hclspec.NewAttr("port_map", "list(map(number))", false),
    96  	})
    97  
    98  	// capabilities is returned by the Capabilities RPC and indicates what
    99  	// optional features this driver supports
   100  	capabilities = &drivers.Capabilities{
   101  		SendSignals:  false,
   102  		Exec:         false,
   103  		FSIsolation:  drivers.FSIsolationImage,
   104  		MountConfigs: drivers.MountConfigSupportNone,
   105  	}
   106  
   107  	_ drivers.DriverPlugin = (*Driver)(nil)
   108  )
   109  
   110  // TaskConfig is the driver configuration of a taskConfig within a job
   111  type TaskConfig struct {
   112  	ImagePath        string             `codec:"image_path"`
   113  	Accelerator      string             `codec:"accelerator"`
   114  	Args             []string           `codec:"args"`     // extra arguments to qemu executable
   115  	PortMap          hclutils.MapStrInt `codec:"port_map"` // A map of host port and the port name defined in the image manifest file
   116  	GracefulShutdown bool               `codec:"graceful_shutdown"`
   117  }
   118  
   119  // TaskState is the state which is encoded in the handle returned in StartTask.
   120  // This information is needed to rebuild the taskConfig state and handler
   121  // during recovery.
   122  type TaskState struct {
   123  	ReattachConfig *pstructs.ReattachConfig
   124  	TaskConfig     *drivers.TaskConfig
   125  	Pid            int
   126  	StartedAt      time.Time
   127  }
   128  
   129  // Driver is a driver for running images via Qemu
   130  type Driver struct {
   131  	// eventer is used to handle multiplexing of TaskEvents calls such that an
   132  	// event can be broadcast to all callers
   133  	eventer *eventer.Eventer
   134  
   135  	// tasks is the in memory datastore mapping taskIDs to qemuTaskHandle
   136  	tasks *taskStore
   137  
   138  	// ctx is the context for the driver. It is passed to other subsystems to
   139  	// coordinate shutdown
   140  	ctx context.Context
   141  
   142  	// nomadConf is the client agent's configuration
   143  	nomadConfig *base.ClientDriverConfig
   144  
   145  	// logger will log to the Nomad agent
   146  	logger hclog.Logger
   147  }
   148  
   149  func NewQemuDriver(ctx context.Context, logger hclog.Logger) drivers.DriverPlugin {
   150  	logger = logger.Named(pluginName)
   151  	return &Driver{
   152  		eventer: eventer.NewEventer(ctx, logger),
   153  		tasks:   newTaskStore(),
   154  		ctx:     ctx,
   155  		logger:  logger,
   156  	}
   157  }
   158  
   159  func (d *Driver) PluginInfo() (*base.PluginInfoResponse, error) {
   160  	return pluginInfo, nil
   161  }
   162  
   163  func (d *Driver) ConfigSchema() (*hclspec.Spec, error) {
   164  	return configSpec, nil
   165  }
   166  
   167  func (d *Driver) SetConfig(cfg *base.Config) error {
   168  	if cfg.AgentConfig != nil {
   169  		d.nomadConfig = cfg.AgentConfig.Driver
   170  	}
   171  	return nil
   172  }
   173  
   174  func (d *Driver) TaskConfigSchema() (*hclspec.Spec, error) {
   175  	return taskConfigSpec, nil
   176  }
   177  
   178  func (d *Driver) Capabilities() (*drivers.Capabilities, error) {
   179  	return capabilities, nil
   180  }
   181  
   182  func (d *Driver) Fingerprint(ctx context.Context) (<-chan *drivers.Fingerprint, error) {
   183  	ch := make(chan *drivers.Fingerprint)
   184  	go d.handleFingerprint(ctx, ch)
   185  	return ch, nil
   186  }
   187  
   188  func (d *Driver) handleFingerprint(ctx context.Context, ch chan *drivers.Fingerprint) {
   189  	ticker := time.NewTimer(0)
   190  	for {
   191  		select {
   192  		case <-ctx.Done():
   193  			return
   194  		case <-d.ctx.Done():
   195  			return
   196  		case <-ticker.C:
   197  			ticker.Reset(fingerprintPeriod)
   198  			ch <- d.buildFingerprint()
   199  		}
   200  	}
   201  }
   202  
   203  func (d *Driver) buildFingerprint() *drivers.Fingerprint {
   204  	fingerprint := &drivers.Fingerprint{
   205  		Attributes:        map[string]*pstructs.Attribute{},
   206  		Health:            drivers.HealthStateHealthy,
   207  		HealthDescription: drivers.DriverHealthy,
   208  	}
   209  
   210  	bin := "qemu-system-x86_64"
   211  	if runtime.GOOS == "windows" {
   212  		// On windows, the "qemu-system-x86_64" command does not respond to the
   213  		// version flag.
   214  		bin = "qemu-img"
   215  	}
   216  	outBytes, err := exec.Command(bin, "--version").Output()
   217  	if err != nil {
   218  		// return no error, as it isn't an error to not find qemu, it just means we
   219  		// can't use it.
   220  		fingerprint.Health = drivers.HealthStateUndetected
   221  		fingerprint.HealthDescription = ""
   222  		return fingerprint
   223  	}
   224  	out := strings.TrimSpace(string(outBytes))
   225  
   226  	matches := versionRegex.FindStringSubmatch(out)
   227  	if len(matches) != 2 {
   228  		fingerprint.Health = drivers.HealthStateUndetected
   229  		fingerprint.HealthDescription = fmt.Sprintf("Failed to parse qemu version from %v", out)
   230  		return fingerprint
   231  	}
   232  	currentQemuVersion := matches[1]
   233  	fingerprint.Attributes[driverAttr] = pstructs.NewBoolAttribute(true)
   234  	fingerprint.Attributes[driverVersionAttr] = pstructs.NewStringAttribute(currentQemuVersion)
   235  	return fingerprint
   236  }
   237  
   238  func (d *Driver) RecoverTask(handle *drivers.TaskHandle) error {
   239  	if handle == nil {
   240  		return fmt.Errorf("error: handle cannot be nil")
   241  	}
   242  
   243  	// COMPAT(0.10): pre 0.9 upgrade path check
   244  	if handle.Version == 0 {
   245  		return d.recoverPre09Task(handle)
   246  	}
   247  
   248  	// If already attached to handle there's nothing to recover.
   249  	if _, ok := d.tasks.Get(handle.Config.ID); ok {
   250  		d.logger.Trace("nothing to recover; task already exists",
   251  			"task_id", handle.Config.ID,
   252  			"task_name", handle.Config.Name,
   253  		)
   254  		return nil
   255  	}
   256  
   257  	var taskState TaskState
   258  	if err := handle.GetDriverState(&taskState); err != nil {
   259  		d.logger.Error("failed to decode taskConfig state from handle", "error", err, "task_id", handle.Config.ID)
   260  		return fmt.Errorf("failed to decode taskConfig state from handle: %v", err)
   261  	}
   262  
   263  	plugRC, err := pstructs.ReattachConfigToGoPlugin(taskState.ReattachConfig)
   264  	if err != nil {
   265  		d.logger.Error("failed to build ReattachConfig from taskConfig state", "error", err, "task_id", handle.Config.ID)
   266  		return fmt.Errorf("failed to build ReattachConfig from taskConfig state: %v", err)
   267  	}
   268  
   269  	execImpl, pluginClient, err := executor.ReattachToExecutor(plugRC,
   270  		d.logger.With("task_name", handle.Config.Name, "alloc_id", handle.Config.AllocID))
   271  	if err != nil {
   272  		d.logger.Error("failed to reattach to executor", "error", err, "task_id", handle.Config.ID)
   273  		return fmt.Errorf("failed to reattach to executor: %v", err)
   274  	}
   275  
   276  	h := &taskHandle{
   277  		exec:         execImpl,
   278  		pid:          taskState.Pid,
   279  		pluginClient: pluginClient,
   280  		taskConfig:   taskState.TaskConfig,
   281  		procState:    drivers.TaskStateRunning,
   282  		startedAt:    taskState.StartedAt,
   283  		exitResult:   &drivers.ExitResult{},
   284  		logger:       d.logger,
   285  	}
   286  
   287  	d.tasks.Set(taskState.TaskConfig.ID, h)
   288  
   289  	go h.run()
   290  	return nil
   291  }
   292  
   293  func (d *Driver) StartTask(cfg *drivers.TaskConfig) (*drivers.TaskHandle, *drivers.DriverNetwork, error) {
   294  	if _, ok := d.tasks.Get(cfg.ID); ok {
   295  		return nil, nil, fmt.Errorf("taskConfig with ID '%s' already started", cfg.ID)
   296  	}
   297  
   298  	var driverConfig TaskConfig
   299  
   300  	if err := cfg.DecodeDriverConfig(&driverConfig); err != nil {
   301  		return nil, nil, fmt.Errorf("failed to decode driver config: %v", err)
   302  	}
   303  
   304  	// ensure that PortMap variables are populated early on
   305  	cfg.Env = taskenv.SetPortMapEnvs(cfg.Env, driverConfig.PortMap)
   306  
   307  	handle := drivers.NewTaskHandle(taskHandleVersion)
   308  	handle.Config = cfg
   309  
   310  	// Get the image source
   311  	vmPath := driverConfig.ImagePath
   312  	if vmPath == "" {
   313  		return nil, nil, fmt.Errorf("image_path must be set")
   314  	}
   315  	vmID := filepath.Base(vmPath)
   316  
   317  	// Parse configuration arguments
   318  	// Create the base arguments
   319  	accelerator := "tcg"
   320  	if driverConfig.Accelerator != "" {
   321  		accelerator = driverConfig.Accelerator
   322  	}
   323  
   324  	mb := cfg.Resources.NomadResources.Memory.MemoryMB
   325  	if mb < 128 || mb > 4000000 {
   326  		return nil, nil, fmt.Errorf("Qemu memory assignment out of bounds")
   327  	}
   328  	mem := fmt.Sprintf("%dM", mb)
   329  
   330  	absPath, err := GetAbsolutePath("qemu-system-x86_64")
   331  	if err != nil {
   332  		return nil, nil, err
   333  	}
   334  
   335  	args := []string{
   336  		absPath,
   337  		"-machine", "type=pc,accel=" + accelerator,
   338  		"-name", vmID,
   339  		"-m", mem,
   340  		"-drive", "file=" + vmPath,
   341  		"-nographic",
   342  	}
   343  
   344  	var monitorPath string
   345  	if driverConfig.GracefulShutdown {
   346  		if runtime.GOOS == "windows" {
   347  			return nil, nil, errors.New("QEMU graceful shutdown is unsupported on the Windows platform")
   348  		}
   349  		// This socket will be used to manage the virtual machine (for example,
   350  		// to perform graceful shutdowns)
   351  		taskDir := filepath.Join(cfg.AllocDir, cfg.Name)
   352  		fingerPrint := d.buildFingerprint()
   353  		if fingerPrint.Attributes == nil {
   354  			return nil, nil, fmt.Errorf("unable to get qemu driver version from fingerprinted attributes")
   355  		}
   356  		monitorPath, err = d.getMonitorPath(taskDir, fingerPrint)
   357  		if err != nil {
   358  			d.logger.Debug("could not get qemu monitor path", "error", err)
   359  			return nil, nil, err
   360  		}
   361  		d.logger.Debug("got monitor path", "monitorPath", monitorPath)
   362  		args = append(args, "-monitor", fmt.Sprintf("unix:%s,server,nowait", monitorPath))
   363  	}
   364  
   365  	// Add pass through arguments to qemu executable. A user can specify
   366  	// these arguments in driver task configuration. These arguments are
   367  	// passed directly to the qemu driver as command line options.
   368  	// For example, args = [ "-nodefconfig", "-nodefaults" ]
   369  	// This will allow a VM with embedded configuration to boot successfully.
   370  	args = append(args, driverConfig.Args...)
   371  
   372  	// Check the Resources required Networks to add port mappings. If no resources
   373  	// are required, we assume the VM is a purely compute job and does not require
   374  	// the outside world to be able to reach it. VMs ran without port mappings can
   375  	// still reach out to the world, but without port mappings it is effectively
   376  	// firewalled
   377  	protocols := []string{"udp", "tcp"}
   378  	if len(cfg.Resources.NomadResources.Networks) > 0 {
   379  		// Loop through the port map and construct the hostfwd string, to map
   380  		// reserved ports to the ports listenting in the VM
   381  		// Ex: hostfwd=tcp::22000-:22,hostfwd=tcp::80-:8080
   382  		var forwarding []string
   383  		taskPorts := cfg.Resources.NomadResources.Networks[0].PortLabels()
   384  		for label, guest := range driverConfig.PortMap {
   385  			host, ok := taskPorts[label]
   386  			if !ok {
   387  				return nil, nil, fmt.Errorf("Unknown port label %q", label)
   388  			}
   389  
   390  			for _, p := range protocols {
   391  				forwarding = append(forwarding, fmt.Sprintf("hostfwd=%s::%d-:%d", p, host, guest))
   392  			}
   393  		}
   394  
   395  		if len(forwarding) != 0 {
   396  			args = append(args,
   397  				"-netdev",
   398  				fmt.Sprintf("user,id=user.0,%s", strings.Join(forwarding, ",")),
   399  				"-device", "virtio-net,netdev=user.0",
   400  			)
   401  		}
   402  	}
   403  
   404  	// If using KVM, add optimization args
   405  	if accelerator == "kvm" {
   406  		if runtime.GOOS == "windows" {
   407  			return nil, nil, errors.New("KVM accelerator is unsupported on the Windows platform")
   408  		}
   409  		args = append(args,
   410  			"-enable-kvm",
   411  			"-cpu", "host",
   412  			// Do we have cores information available to the Driver?
   413  			// "-smp", fmt.Sprintf("%d", cores),
   414  		)
   415  	}
   416  	d.logger.Debug("starting QemuVM command ", "args", strings.Join(args, " "))
   417  
   418  	pluginLogFile := filepath.Join(cfg.TaskDir().Dir, fmt.Sprintf("%s-executor.out", cfg.Name))
   419  	executorConfig := &executor.ExecutorConfig{
   420  		LogFile:  pluginLogFile,
   421  		LogLevel: "debug",
   422  	}
   423  
   424  	execImpl, pluginClient, err := executor.CreateExecutor(
   425  		d.logger.With("task_name", handle.Config.Name, "alloc_id", handle.Config.AllocID),
   426  		d.nomadConfig, executorConfig)
   427  	if err != nil {
   428  		return nil, nil, err
   429  	}
   430  
   431  	execCmd := &executor.ExecCommand{
   432  		Cmd:        args[0],
   433  		Args:       args[1:],
   434  		Env:        cfg.EnvList(),
   435  		User:       cfg.User,
   436  		TaskDir:    cfg.TaskDir().Dir,
   437  		StdoutPath: cfg.StdoutPath,
   438  		StderrPath: cfg.StderrPath,
   439  	}
   440  	ps, err := execImpl.Launch(execCmd)
   441  	if err != nil {
   442  		pluginClient.Kill()
   443  		return nil, nil, err
   444  	}
   445  	d.logger.Debug("started new QemuVM", "ID", vmID)
   446  
   447  	h := &taskHandle{
   448  		exec:         execImpl,
   449  		pid:          ps.Pid,
   450  		monitorPath:  monitorPath,
   451  		pluginClient: pluginClient,
   452  		taskConfig:   cfg,
   453  		procState:    drivers.TaskStateRunning,
   454  		startedAt:    time.Now().Round(time.Millisecond),
   455  		logger:       d.logger,
   456  	}
   457  
   458  	qemuDriverState := TaskState{
   459  		ReattachConfig: pstructs.ReattachConfigFromGoPlugin(pluginClient.ReattachConfig()),
   460  		Pid:            ps.Pid,
   461  		TaskConfig:     cfg,
   462  		StartedAt:      h.startedAt,
   463  	}
   464  
   465  	if err := handle.SetDriverState(&qemuDriverState); err != nil {
   466  		d.logger.Error("failed to start task, error setting driver state", "error", err)
   467  		execImpl.Shutdown("", 0)
   468  		pluginClient.Kill()
   469  		return nil, nil, fmt.Errorf("failed to set driver state: %v", err)
   470  	}
   471  
   472  	d.tasks.Set(cfg.ID, h)
   473  	go h.run()
   474  
   475  	var driverNetwork *drivers.DriverNetwork
   476  	if len(driverConfig.PortMap) == 1 {
   477  		driverNetwork = &drivers.DriverNetwork{
   478  			PortMap: driverConfig.PortMap,
   479  		}
   480  	}
   481  	return handle, driverNetwork, nil
   482  }
   483  
   484  func (d *Driver) WaitTask(ctx context.Context, taskID string) (<-chan *drivers.ExitResult, error) {
   485  	handle, ok := d.tasks.Get(taskID)
   486  	if !ok {
   487  		return nil, drivers.ErrTaskNotFound
   488  	}
   489  
   490  	ch := make(chan *drivers.ExitResult)
   491  	go d.handleWait(ctx, handle, ch)
   492  
   493  	return ch, nil
   494  }
   495  
   496  func (d *Driver) StopTask(taskID string, timeout time.Duration, signal string) error {
   497  	handle, ok := d.tasks.Get(taskID)
   498  	if !ok {
   499  		return drivers.ErrTaskNotFound
   500  	}
   501  
   502  	// Attempt a graceful shutdown only if it was configured in the job
   503  	if handle.monitorPath != "" {
   504  		if err := sendQemuShutdown(d.logger, handle.monitorPath, handle.pid); err != nil {
   505  			d.logger.Debug("error sending graceful shutdown ", "pid", handle.pid, "error", err)
   506  		}
   507  	}
   508  
   509  	// TODO(preetha) we are calling shutdown on the executor here
   510  	// after attempting a graceful qemu shutdown, qemu process may
   511  	// not be around when we call exec.shutdown
   512  	if err := handle.exec.Shutdown(signal, timeout); err != nil {
   513  		if handle.pluginClient.Exited() {
   514  			return nil
   515  		}
   516  		return fmt.Errorf("executor Shutdown failed: %v", err)
   517  	}
   518  
   519  	return nil
   520  }
   521  
   522  func (d *Driver) DestroyTask(taskID string, force bool) error {
   523  	handle, ok := d.tasks.Get(taskID)
   524  	if !ok {
   525  		return drivers.ErrTaskNotFound
   526  	}
   527  
   528  	if handle.IsRunning() && !force {
   529  		return fmt.Errorf("cannot destroy running task")
   530  	}
   531  
   532  	if !handle.pluginClient.Exited() {
   533  		if err := handle.exec.Shutdown("", 0); err != nil {
   534  			handle.logger.Error("destroying executor failed", "err", err)
   535  		}
   536  
   537  		handle.pluginClient.Kill()
   538  	}
   539  
   540  	d.tasks.Delete(taskID)
   541  	return nil
   542  }
   543  
   544  func (d *Driver) InspectTask(taskID string) (*drivers.TaskStatus, error) {
   545  	handle, ok := d.tasks.Get(taskID)
   546  	if !ok {
   547  		return nil, drivers.ErrTaskNotFound
   548  	}
   549  
   550  	return handle.TaskStatus(), nil
   551  }
   552  
   553  func (d *Driver) TaskStats(ctx context.Context, taskID string, interval time.Duration) (<-chan *drivers.TaskResourceUsage, error) {
   554  	handle, ok := d.tasks.Get(taskID)
   555  	if !ok {
   556  		return nil, drivers.ErrTaskNotFound
   557  	}
   558  
   559  	return handle.exec.Stats(ctx, interval)
   560  }
   561  
   562  func (d *Driver) TaskEvents(ctx context.Context) (<-chan *drivers.TaskEvent, error) {
   563  	return d.eventer.TaskEvents(ctx)
   564  }
   565  
   566  func (d *Driver) SignalTask(taskID string, signal string) error {
   567  	return fmt.Errorf("Qemu driver can't signal commands")
   568  }
   569  
   570  func (d *Driver) ExecTask(taskID string, cmdArgs []string, timeout time.Duration) (*drivers.ExecTaskResult, error) {
   571  	return nil, fmt.Errorf("Qemu driver can't execute commands")
   572  
   573  }
   574  
   575  // GetAbsolutePath returns the absolute path of the passed binary by resolving
   576  // it in the path and following symlinks.
   577  func GetAbsolutePath(bin string) (string, error) {
   578  	lp, err := exec.LookPath(bin)
   579  	if err != nil {
   580  		return "", fmt.Errorf("failed to resolve path to %q executable: %v", bin, err)
   581  	}
   582  
   583  	return filepath.EvalSymlinks(lp)
   584  }
   585  
   586  func (d *Driver) handleWait(ctx context.Context, handle *taskHandle, ch chan *drivers.ExitResult) {
   587  	defer close(ch)
   588  	var result *drivers.ExitResult
   589  	ps, err := handle.exec.Wait(ctx)
   590  	if err != nil {
   591  		result = &drivers.ExitResult{
   592  			Err: fmt.Errorf("executor: error waiting on process: %v", err),
   593  		}
   594  	} else {
   595  		result = &drivers.ExitResult{
   596  			ExitCode: ps.ExitCode,
   597  			Signal:   ps.Signal,
   598  		}
   599  	}
   600  
   601  	select {
   602  	case <-ctx.Done():
   603  	case <-d.ctx.Done():
   604  	case ch <- result:
   605  	}
   606  }
   607  
   608  // getMonitorPath is used to determine whether a qemu monitor socket can be
   609  // safely created and accessed in the task directory by the version of qemu
   610  // present on the host. If it is safe to use, the socket's full path is
   611  // returned along with a nil error. Otherwise, an empty string is returned
   612  // along with a descriptive error.
   613  func (d *Driver) getMonitorPath(dir string, fingerPrint *drivers.Fingerprint) (string, error) {
   614  	var longPathSupport bool
   615  	currentQemuVer := fingerPrint.Attributes[driverVersionAttr]
   616  	currentQemuSemver := semver.New(currentQemuVer.GoString())
   617  	if currentQemuSemver.LessThan(*qemuVersionLongSocketPathFix) {
   618  		longPathSupport = false
   619  		d.logger.Debug("long socket paths are not available in this version of QEMU", "version", currentQemuVer)
   620  	} else {
   621  		longPathSupport = true
   622  		d.logger.Debug("long socket paths available in this version of QEMU", "version", currentQemuVer)
   623  	}
   624  	fullSocketPath := fmt.Sprintf("%s/%s", dir, qemuMonitorSocketName)
   625  	if len(fullSocketPath) > qemuLegacyMaxMonitorPathLen && longPathSupport == false {
   626  		return "", fmt.Errorf("monitor path is too long for this version of qemu")
   627  	}
   628  	return fullSocketPath, nil
   629  }
   630  
   631  // sendQemuShutdown attempts to issue an ACPI power-off command via the qemu
   632  // monitor
   633  func sendQemuShutdown(logger hclog.Logger, monitorPath string, userPid int) error {
   634  	if monitorPath == "" {
   635  		return errors.New("monitorPath not set")
   636  	}
   637  	monitorSocket, err := net.Dial("unix", monitorPath)
   638  	if err != nil {
   639  		logger.Warn("could not connect to qemu monitor", "pid", userPid, "monitorPath", monitorPath, "error", err)
   640  		return err
   641  	}
   642  	defer monitorSocket.Close()
   643  	logger.Debug("sending graceful shutdown command to qemu monitor socket", "monitor_path", monitorPath, "pid", userPid)
   644  	_, err = monitorSocket.Write([]byte(qemuGracefulShutdownMsg))
   645  	if err != nil {
   646  		logger.Warn("failed to send shutdown message", "shutdown message", qemuGracefulShutdownMsg, "monitorPath", monitorPath, "userPid", userPid, "error", err)
   647  	}
   648  	return err
   649  }