github.com/containers/podman/v4@v4.9.4/pkg/machine/hyperv/machine.go (about)

     1  //go:build windows
     2  // +build windows
     3  
     4  package hyperv
     5  
     6  import (
     7  	"bytes"
     8  	"encoding/json"
     9  	"errors"
    10  	"fmt"
    11  	"io/fs"
    12  	"os"
    13  	"os/exec"
    14  	"path/filepath"
    15  	"strings"
    16  	"time"
    17  
    18  	"github.com/Microsoft/go-winio"
    19  	"github.com/containers/common/pkg/config"
    20  	gvproxy "github.com/containers/gvisor-tap-vsock/pkg/types"
    21  	"github.com/containers/libhvee/pkg/hypervctl"
    22  	"github.com/containers/podman/v4/pkg/machine"
    23  	"github.com/containers/podman/v4/pkg/machine/define"
    24  	"github.com/containers/podman/v4/pkg/strongunits"
    25  	"github.com/containers/podman/v4/pkg/util"
    26  	"github.com/containers/podman/v4/utils"
    27  	"github.com/containers/storage/pkg/lockfile"
    28  	psutil "github.com/shirou/gopsutil/v3/process"
    29  	"github.com/sirupsen/logrus"
    30  )
    31  
    32  var (
    33  	// vmtype refers to qemu (vs libvirt, krun, etc).
    34  	vmtype = machine.HyperVVirt
    35  )
    36  
    37  const (
    38  	// Some of this will need to change when we are closer to having
    39  	// working code.
    40  	VolumeTypeVirtfs     = "virtfs"
    41  	MountType9p          = "9p"
    42  	dockerSockPath       = "/var/run/docker.sock"
    43  	dockerConnectTimeout = 5 * time.Second
    44  	apiUpTimeout         = 20 * time.Second
    45  )
    46  
    47  // hyperVReadyUnit is a unit file that sets up the virtual serial device
    48  // where when the VM is done configuring, it will send an ack
    49  // so a listening host knows it can begin interacting with it
    50  //
    51  // VSOCK-CONNECT:2 <- shortcut to connect to the hostvm
    52  const hyperVReadyUnit = `[Unit]
    53  After=remove-moby.service sshd.socket sshd.service
    54  After=systemd-user-sessions.service
    55  OnFailure=emergency.target
    56  OnFailureJobMode=isolate
    57  [Service]
    58  Type=oneshot
    59  RemainAfterExit=yes
    60  ExecStart=/bin/sh -c '/usr/bin/echo Ready | socat - VSOCK-CONNECT:2:%d'
    61  [Install]
    62  RequiredBy=default.target
    63  `
    64  
    65  // hyperVVsockNetUnit is a systemd unit file that calls the vm helper utility
    66  // needed to take traffic from a network vsock0 device to the actual vsock
    67  // and onto the host
    68  const hyperVVsockNetUnit = `
    69  [Unit]
    70  Description=vsock_network
    71  After=NetworkManager.service
    72  
    73  [Service]
    74  ExecStart=/usr/libexec/podman/gvforwarder -preexisting -iface vsock0 -url vsock://2:%d/connect
    75  ExecStartPost=/usr/bin/nmcli c up vsock0
    76  
    77  [Install]
    78  WantedBy=multi-user.target
    79  `
    80  
    81  const hyperVVsockNMConnection = `
    82  [connection]
    83  id=vsock0
    84  type=tun
    85  interface-name=vsock0
    86  
    87  [tun]
    88  mode=2
    89  
    90  [802-3-ethernet]
    91  cloned-mac-address=5A:94:EF:E4:0C:EE
    92  
    93  [ipv4]
    94  method=auto
    95  
    96  [proxy]
    97  `
    98  
    99  type HyperVMachine struct {
   100  	// ConfigPath is the fully qualified path to the configuration file
   101  	ConfigPath define.VMFile
   102  	// HostUser contains info about host user
   103  	machine.HostUser
   104  	// ImageConfig describes the bootable image
   105  	machine.ImageConfig
   106  	// Mounts is the list of remote filesystems to mount
   107  	Mounts []machine.Mount
   108  	// Name of VM
   109  	Name string
   110  	// NetworkVSock is for the user networking
   111  	NetworkHVSock HVSockRegistryEntry
   112  	// ReadySocket tells host when vm is booted
   113  	ReadyHVSock HVSockRegistryEntry
   114  	// ResourceConfig is physical attrs of the VM
   115  	machine.ResourceConfig
   116  	// SSHConfig for accessing the remote vm
   117  	machine.SSHConfig
   118  	// Starting tells us whether the machine is running or if we have just dialed it to start it
   119  	Starting bool
   120  	// Created contains the original created time instead of querying the file mod time
   121  	Created time.Time
   122  	// LastUp contains the last recorded uptime
   123  	LastUp time.Time
   124  	// GVProxy will write its PID here
   125  	GvProxyPid define.VMFile
   126  	// MountVsocks contains the currently-active vsocks, mapped to the
   127  	// directory they should be mounted on.
   128  	MountVsocks map[string]uint64
   129  	// Used at runtime for serializing write operations
   130  	lock *lockfile.LockFile
   131  }
   132  
   133  // addNetworkAndReadySocketsToRegistry adds the Network and Ready sockets to the
   134  // Windows registry
   135  func (m *HyperVMachine) addNetworkAndReadySocketsToRegistry() error {
   136  	networkHVSock, err := NewHVSockRegistryEntry(m.Name, Network)
   137  	if err != nil {
   138  		return err
   139  	}
   140  	eventHVSocket, err := NewHVSockRegistryEntry(m.Name, Events)
   141  	if err != nil {
   142  		return err
   143  	}
   144  	m.NetworkHVSock = *networkHVSock
   145  	m.ReadyHVSock = *eventHVSocket
   146  	return nil
   147  }
   148  
   149  // readAndSplitIgnition reads the ignition file and splits it into key:value pairs
   150  func (m *HyperVMachine) readAndSplitIgnition() error {
   151  	ignFile, err := m.IgnitionFile.Read()
   152  	if err != nil {
   153  		return err
   154  	}
   155  	reader := bytes.NewReader(ignFile)
   156  
   157  	vm, err := hypervctl.NewVirtualMachineManager().GetMachine(m.Name)
   158  	if err != nil {
   159  		return err
   160  	}
   161  	return vm.SplitAndAddIgnition("ignition.config.", reader)
   162  }
   163  
   164  func (m *HyperVMachine) Init(opts machine.InitOptions) (bool, error) {
   165  	var (
   166  		key string
   167  		err error
   168  	)
   169  
   170  	// cleanup half-baked files if init fails at any point
   171  	callbackFuncs := machine.InitCleanup()
   172  	defer callbackFuncs.CleanIfErr(&err)
   173  	go callbackFuncs.CleanOnSignal()
   174  
   175  	callbackFuncs.Add(m.ImagePath.Delete)
   176  	callbackFuncs.Add(m.ConfigPath.Delete)
   177  	callbackFuncs.Add(m.unregisterMachine)
   178  
   179  	// Parsing here is confusing.
   180  	// Basically, we have two paths: a source path, on the Windows machine,
   181  	// with all that entails (drive letter, backslash separator, etc) and a
   182  	// dest path, in the Linux machine, normal Unix semantics. They are
   183  	// separated by a : character, with source path first, dest path second.
   184  	// So we split on :, first two parts are guaranteed to be Windows (the
   185  	// drive letter and file path), next one is Linux. Options, when we get
   186  	// around to those, would be another : after that.
   187  	// TODO: Need to support options here
   188  	for _, mount := range opts.Volumes {
   189  		newMount := machine.Mount{}
   190  
   191  		splitMount := strings.Split(mount, ":")
   192  		if len(splitMount) < 3 {
   193  			return false, fmt.Errorf("volumes must be specified as source:destination and must be absolute")
   194  		}
   195  		newMount.Target = splitMount[2]
   196  		newMount.Source = strings.Join(splitMount[:2], ":")
   197  		if len(splitMount) > 3 {
   198  			return false, fmt.Errorf("volume options are not presently supported on Hyper-V")
   199  		}
   200  
   201  		m.Mounts = append(m.Mounts, newMount)
   202  	}
   203  
   204  	if err = m.addNetworkAndReadySocketsToRegistry(); err != nil {
   205  		return false, err
   206  	}
   207  	callbackFuncs.Add(func() error {
   208  		m.removeNetworkAndReadySocketsFromRegistry()
   209  		return nil
   210  	})
   211  
   212  	m.IdentityPath = util.GetIdentityPath(m.Name)
   213  
   214  	if m.UID == 0 {
   215  		m.UID = 1000
   216  	}
   217  
   218  	sshPort, err := utils.GetRandomPort()
   219  	if err != nil {
   220  		return false, err
   221  	}
   222  	m.Port = sshPort
   223  
   224  	m.RemoteUsername = opts.Username
   225  	err = machine.AddSSHConnectionsToPodmanSocket(
   226  		m.UID,
   227  		m.Port,
   228  		m.IdentityPath,
   229  		m.Name,
   230  		m.RemoteUsername,
   231  		opts,
   232  	)
   233  	if err != nil {
   234  		return false, err
   235  	}
   236  	callbackFuncs.Add(m.removeSystemConnections)
   237  
   238  	if len(opts.IgnitionPath) < 1 {
   239  		key, err = machine.CreateSSHKeys(m.IdentityPath)
   240  		if err != nil {
   241  			return false, err
   242  		}
   243  		callbackFuncs.Add(m.removeSSHKeys)
   244  	}
   245  
   246  	m.ResourceConfig = machine.ResourceConfig{
   247  		CPUs:     opts.CPUS,
   248  		DiskSize: opts.DiskSize,
   249  		Memory:   opts.Memory,
   250  	}
   251  	m.Rootful = opts.Rootful
   252  
   253  	builder := machine.NewIgnitionBuilder(machine.DynamicIgnition{
   254  		Name:      m.RemoteUsername,
   255  		Key:       key,
   256  		VMName:    m.Name,
   257  		VMType:    machine.HyperVVirt,
   258  		TimeZone:  opts.TimeZone,
   259  		WritePath: m.IgnitionFile.GetPath(),
   260  		UID:       m.UID,
   261  		Rootful:   m.Rootful,
   262  	})
   263  
   264  	// If the user provides an ignition file, we need to
   265  	// copy it into the conf dir
   266  	if len(opts.IgnitionPath) > 0 {
   267  		return false, builder.BuildWithIgnitionFile(opts.IgnitionPath)
   268  	}
   269  	callbackFuncs.Add(m.IgnitionFile.Delete)
   270  
   271  	if err := m.writeConfig(); err != nil {
   272  		return false, err
   273  	}
   274  
   275  	if err := builder.GenerateIgnitionConfig(); err != nil {
   276  		return false, err
   277  	}
   278  
   279  	builder.WithUnit(machine.Unit{
   280  		Enabled:  machine.BoolToPtr(true),
   281  		Name:     "ready.service",
   282  		Contents: machine.StrToPtr(fmt.Sprintf(hyperVReadyUnit, m.ReadyHVSock.Port)),
   283  	})
   284  
   285  	builder.WithUnit(machine.Unit{
   286  		Contents: machine.StrToPtr(fmt.Sprintf(hyperVVsockNetUnit, m.NetworkHVSock.Port)),
   287  		Enabled:  machine.BoolToPtr(true),
   288  		Name:     "vsock-network.service",
   289  	})
   290  
   291  	builder.WithFile(machine.File{
   292  		Node: machine.Node{
   293  			Path: "/etc/NetworkManager/system-connections/vsock0.nmconnection",
   294  		},
   295  		FileEmbedded1: machine.FileEmbedded1{
   296  			Append: nil,
   297  			Contents: machine.Resource{
   298  				Source: machine.EncodeDataURLPtr(hyperVVsockNMConnection),
   299  			},
   300  			Mode: machine.IntToPtr(0600),
   301  		},
   302  	})
   303  
   304  	if err := builder.Build(); err != nil {
   305  		return false, err
   306  	}
   307  
   308  	if err = m.resizeDisk(strongunits.GiB(opts.DiskSize)); err != nil {
   309  		return false, err
   310  	}
   311  	// The ignition file has been written. We now need to
   312  	// read it so that we can put it into key-value pairs
   313  	err = m.readAndSplitIgnition()
   314  	return err == nil, err
   315  }
   316  
   317  func (m *HyperVMachine) unregisterMachine() error {
   318  	vmm := hypervctl.NewVirtualMachineManager()
   319  	vm, err := vmm.GetMachine(m.Name)
   320  	if err != nil {
   321  		logrus.Error(err)
   322  	}
   323  	return vm.Remove("")
   324  }
   325  
   326  func (m *HyperVMachine) removeSSHKeys() error {
   327  	if err := os.Remove(fmt.Sprintf("%s.pub", m.IdentityPath)); err != nil {
   328  		logrus.Error(err)
   329  	}
   330  	return os.Remove(m.IdentityPath)
   331  }
   332  
   333  func (m *HyperVMachine) removeSystemConnections() error {
   334  	return machine.RemoveConnections(m.Name, fmt.Sprintf("%s-root", m.Name))
   335  }
   336  
   337  func (m *HyperVMachine) Inspect() (*machine.InspectInfo, error) {
   338  	vm, err := hypervctl.NewVirtualMachineManager().GetMachine(m.Name)
   339  	if err != nil {
   340  		return nil, err
   341  	}
   342  
   343  	cfg, err := vm.GetConfig(m.ImagePath.GetPath())
   344  	if err != nil {
   345  		return nil, err
   346  	}
   347  
   348  	vmState, err := stateConversion(vm.State())
   349  	if err != nil {
   350  		return nil, err
   351  	}
   352  
   353  	podmanSocket, err := m.forwardSocketPath()
   354  	if err != nil {
   355  		return nil, err
   356  	}
   357  	machinePipe := machine.ToDist(m.Name)
   358  	podmanPipe := &define.VMFile{Path: `\\.\pipe\` + machinePipe}
   359  
   360  	return &machine.InspectInfo{
   361  		ConfigPath: m.ConfigPath,
   362  		ConnectionInfo: machine.ConnectionConfig{
   363  			PodmanSocket: podmanSocket,
   364  			PodmanPipe:   podmanPipe,
   365  		},
   366  		Created: m.Created,
   367  		Image: machine.ImageConfig{
   368  			IgnitionFile: m.IgnitionFile,
   369  			ImageStream:  "",
   370  			ImagePath:    m.ImagePath,
   371  		},
   372  		LastUp: m.LastUp,
   373  		Name:   m.Name,
   374  		Resources: machine.ResourceConfig{
   375  			CPUs:     uint64(cfg.Hardware.CPUs),
   376  			DiskSize: 0,
   377  			Memory:   cfg.Hardware.Memory,
   378  		},
   379  		SSHConfig: m.SSHConfig,
   380  		State:     string(vmState),
   381  		Rootful:   m.Rootful,
   382  	}, nil
   383  }
   384  
   385  // collectFilesToDestroy retrieves the files that will be destroyed by `Remove`
   386  func (m *HyperVMachine) collectFilesToDestroy(opts machine.RemoveOptions, diskPath *string) []string {
   387  	files := []string{}
   388  
   389  	if !opts.SaveKeys {
   390  		files = append(files, m.IdentityPath, m.IdentityPath+".pub")
   391  	}
   392  	if !opts.SaveIgnition {
   393  		files = append(files, m.IgnitionFile.GetPath())
   394  	}
   395  
   396  	if !opts.SaveImage {
   397  		*diskPath = m.ImagePath.GetPath()
   398  		files = append(files, *diskPath)
   399  	}
   400  
   401  	files = append(files, m.ConfigPath.GetPath())
   402  	return files
   403  }
   404  
   405  // removeNetworkAndReadySocketsFromRegistry removes the Network and Ready sockets
   406  // from the Windows Registry
   407  func (m *HyperVMachine) removeNetworkAndReadySocketsFromRegistry() {
   408  	// Remove the HVSOCK for networking
   409  	if err := m.NetworkHVSock.Remove(); err != nil {
   410  		logrus.Errorf("unable to remove registry entry for %s: %q", m.NetworkHVSock.KeyName, err)
   411  	}
   412  
   413  	// Remove the HVSOCK for events
   414  	if err := m.ReadyHVSock.Remove(); err != nil {
   415  		logrus.Errorf("unable to remove registry entry for %s: %q", m.ReadyHVSock.KeyName, err)
   416  	}
   417  }
   418  
   419  func (m *HyperVMachine) Remove(_ string, opts machine.RemoveOptions) (string, func() error, error) {
   420  	var (
   421  		files    []string
   422  		diskPath string
   423  	)
   424  	m.lock.Lock()
   425  	defer m.lock.Unlock()
   426  
   427  	vmm := hypervctl.NewVirtualMachineManager()
   428  	vm, err := vmm.GetMachine(m.Name)
   429  	if err != nil {
   430  		return "", nil, fmt.Errorf("getting virtual machine: %w", err)
   431  	}
   432  	// In hyperv, they call running 'enabled'
   433  	if vm.State() == hypervctl.Enabled {
   434  		if !opts.Force {
   435  			return "", nil, &machine.ErrVMRunningCannotDestroyed{Name: m.Name}
   436  		}
   437  		// force stop bc we are destroying
   438  		if err := vm.StopWithForce(); err != nil {
   439  			return "", nil, fmt.Errorf("stopping virtual machine: %w", err)
   440  		}
   441  
   442  		// Update state on the VM by pulling its info again
   443  		vm, err = vmm.GetMachine(m.Name)
   444  		if err != nil {
   445  			return "", nil, fmt.Errorf("getting VM: %w", err)
   446  		}
   447  	}
   448  
   449  	// Tear down vsocks
   450  	if err := m.removeShares(); err != nil {
   451  		logrus.Errorf("Error removing vsock: %w", err)
   452  	}
   453  
   454  	// Collect all the files that need to be destroyed
   455  	files = m.collectFilesToDestroy(opts, &diskPath)
   456  
   457  	confirmationMessage := "\nThe following files will be deleted:\n\n"
   458  	for _, msg := range files {
   459  		confirmationMessage += msg + "\n"
   460  	}
   461  
   462  	confirmationMessage += "\n"
   463  	return confirmationMessage, func() error {
   464  		machine.RemoveFilesAndConnections(files, m.Name, m.Name+"-root")
   465  		m.removeNetworkAndReadySocketsFromRegistry()
   466  		if err := vm.Remove(""); err != nil {
   467  			return fmt.Errorf("removing virtual machine: %w", err)
   468  		}
   469  		return nil
   470  	}, nil
   471  }
   472  
   473  func (m *HyperVMachine) Set(name string, opts machine.SetOptions) ([]error, error) {
   474  	var (
   475  		cpuChanged, memoryChanged bool
   476  		setErrors                 []error
   477  	)
   478  
   479  	m.lock.Lock()
   480  	defer m.lock.Unlock()
   481  
   482  	vmm := hypervctl.NewVirtualMachineManager()
   483  	// Considering this a hard return if we cannot lookup the machine
   484  	vm, err := vmm.GetMachine(m.Name)
   485  	if err != nil {
   486  		return setErrors, fmt.Errorf("getting machine: %w", err)
   487  	}
   488  	if vm.State() != hypervctl.Disabled {
   489  		return nil, errors.New("unable to change settings unless vm is stopped")
   490  	}
   491  
   492  	if opts.Rootful != nil && m.Rootful != *opts.Rootful {
   493  		if err := m.setRootful(*opts.Rootful); err != nil {
   494  			setErrors = append(setErrors, fmt.Errorf("failed to set rootful option: %w", err))
   495  		} else {
   496  			m.Rootful = *opts.Rootful
   497  		}
   498  	}
   499  	if opts.DiskSize != nil && m.DiskSize != *opts.DiskSize {
   500  		newDiskSize := strongunits.GiB(*opts.DiskSize)
   501  		if err := m.resizeDisk(newDiskSize); err != nil {
   502  			setErrors = append(setErrors, err)
   503  		}
   504  	}
   505  	if opts.CPUs != nil && m.CPUs != *opts.CPUs {
   506  		m.CPUs = *opts.CPUs
   507  		cpuChanged = true
   508  	}
   509  	if opts.Memory != nil && m.Memory != *opts.Memory {
   510  		m.Memory = *opts.Memory
   511  		memoryChanged = true
   512  	}
   513  
   514  	if opts.USBs != nil {
   515  		setErrors = append(setErrors, errors.New("changing USBs not supported for hyperv machines"))
   516  	}
   517  
   518  	if cpuChanged || memoryChanged {
   519  		err := vm.UpdateProcessorMemSettings(func(ps *hypervctl.ProcessorSettings) {
   520  			if cpuChanged {
   521  				ps.VirtualQuantity = m.CPUs
   522  			}
   523  		}, func(ms *hypervctl.MemorySettings) {
   524  			if memoryChanged {
   525  				ms.DynamicMemoryEnabled = false
   526  				ms.VirtualQuantity = m.Memory
   527  				ms.Limit = m.Memory
   528  				ms.Reservation = m.Memory
   529  			}
   530  		})
   531  		if err != nil {
   532  			setErrors = append(setErrors, fmt.Errorf("setting CPU and Memory for VM: %w", err))
   533  		}
   534  	}
   535  
   536  	if len(setErrors) > 0 {
   537  		return setErrors, setErrors[0]
   538  	}
   539  
   540  	// Write the new JSON out
   541  	// considering this a hard return if we cannot write the JSON file.
   542  	return setErrors, m.writeConfig()
   543  }
   544  
   545  func (m *HyperVMachine) SSH(name string, opts machine.SSHOptions) error {
   546  	state, err := m.State(false)
   547  	if err != nil {
   548  		return err
   549  	}
   550  	if state != machine.Running {
   551  		return fmt.Errorf("vm %q is not running", m.Name)
   552  	}
   553  
   554  	username := opts.Username
   555  	if username == "" {
   556  		username = m.RemoteUsername
   557  	}
   558  	return machine.CommonSSH(username, m.IdentityPath, m.Name, m.Port, opts.Args)
   559  }
   560  
   561  func (m *HyperVMachine) Start(name string, opts machine.StartOptions) error {
   562  	m.lock.Lock()
   563  	defer m.lock.Unlock()
   564  
   565  	// Start 9p shares
   566  	shares, err := m.createShares()
   567  	if err != nil {
   568  		return err
   569  	}
   570  	m.MountVsocks = shares
   571  	if err := m.writeConfig(); err != nil {
   572  		return err
   573  	}
   574  
   575  	vmm := hypervctl.NewVirtualMachineManager()
   576  	vm, err := vmm.GetMachine(m.Name)
   577  	if err != nil {
   578  		return err
   579  	}
   580  	if vm.State() != hypervctl.Disabled {
   581  		return hypervctl.ErrMachineStateInvalid
   582  	}
   583  	gvproxyPid, _, _, err := m.startHostNetworking()
   584  	if err != nil {
   585  		return fmt.Errorf("unable to start host networking: %q", err)
   586  	}
   587  
   588  	// The "starting" status from hyper v is a very small windows and not really
   589  	// the same as what we want.  so modeling starting behaviour after qemu
   590  	m.Starting = true
   591  	if err := m.writeConfig(); err != nil {
   592  		return fmt.Errorf("writing JSON file: %w", err)
   593  	}
   594  
   595  	if err := vm.Start(); err != nil {
   596  		return err
   597  	}
   598  	// Wait on notification from the guest
   599  	if err := m.ReadyHVSock.Listen(); err != nil {
   600  		return err
   601  	}
   602  
   603  	// set starting back false now that we are running
   604  	m.Starting = false
   605  
   606  	if m.HostUser.Modified {
   607  		if machine.UpdatePodmanDockerSockService(m, name, m.UID, m.Rootful) == nil {
   608  			// Reset modification state if there are no errors, otherwise ignore errors
   609  			// which are already logged
   610  			m.HostUser.Modified = false
   611  		}
   612  	}
   613  	winProxyOpts := machine.WinProxyOpts{
   614  		Name:           m.Name,
   615  		IdentityPath:   m.IdentityPath,
   616  		Port:           m.Port,
   617  		RemoteUsername: m.RemoteUsername,
   618  		Rootful:        m.Rootful,
   619  		VMType:         vmtype,
   620  	}
   621  	machine.LaunchWinProxy(winProxyOpts, opts.NoInfo)
   622  
   623  	// Write the config with updated starting status and hostuser modification
   624  	if err := m.writeConfig(); err != nil {
   625  		return err
   626  	}
   627  
   628  	// Check if gvproxy is still running.
   629  	// Do this *after* we write config, so we have still recorded that the
   630  	// VM is actually running - to ensure that stopping the machine works as
   631  	// expected.
   632  	_, err = psutil.NewProcess(gvproxyPid)
   633  	if err != nil {
   634  		return fmt.Errorf("gvproxy appears to have stopped (PID %d): %w", gvproxyPid, err)
   635  	}
   636  
   637  	// Finalize starting shares after we are confident gvproxy is still alive.
   638  	if err := m.startShares(); err != nil {
   639  		return err
   640  	}
   641  
   642  	return nil
   643  }
   644  
   645  func (m *HyperVMachine) State(_ bool) (machine.Status, error) {
   646  	vmm := hypervctl.NewVirtualMachineManager()
   647  	vm, err := vmm.GetMachine(m.Name)
   648  	if err != nil {
   649  		return "", err
   650  	}
   651  	if vm.IsStarting() {
   652  		return machine.Starting, nil
   653  	}
   654  	if vm.State() == hypervctl.Enabled {
   655  		return machine.Running, nil
   656  	}
   657  	// Following QEMU pattern here where only three
   658  	// states seem valid
   659  	return machine.Stopped, nil
   660  }
   661  
   662  func (m *HyperVMachine) Stop(name string, opts machine.StopOptions) error {
   663  	m.lock.Lock()
   664  	defer m.lock.Unlock()
   665  
   666  	vmm := hypervctl.NewVirtualMachineManager()
   667  	vm, err := vmm.GetMachine(m.Name)
   668  	if err != nil {
   669  		return fmt.Errorf("getting virtual machine: %w", err)
   670  	}
   671  	vmState := vm.State()
   672  	if vm.State() == hypervctl.Disabled {
   673  		return nil
   674  	}
   675  	if vmState != hypervctl.Enabled { // more states could be provided as well
   676  		return hypervctl.ErrMachineStateInvalid
   677  	}
   678  
   679  	if err := machine.CleanupGVProxy(m.GvProxyPid); err != nil {
   680  		logrus.Error(err)
   681  	}
   682  
   683  	if err := machine.StopWinProxy(m.Name, vmtype); err != nil {
   684  		fmt.Fprintf(os.Stderr, "Could not stop API forwarding service (win-sshproxy.exe): %s\n", err.Error())
   685  	}
   686  
   687  	if err := vm.Stop(); err != nil {
   688  		return fmt.Errorf("stopping virtual machine: %w", err)
   689  	}
   690  
   691  	// keep track of last up
   692  	m.LastUp = time.Now()
   693  	return m.writeConfig()
   694  }
   695  
   696  func (m *HyperVMachine) jsonConfigPath() (string, error) {
   697  	configDir, err := machine.GetConfDir(machine.HyperVVirt)
   698  	if err != nil {
   699  		return "", err
   700  	}
   701  	return getVMConfigPath(configDir, m.Name), nil
   702  }
   703  
   704  func (m *HyperVMachine) loadFromFile() (*HyperVMachine, error) {
   705  	if len(m.Name) < 1 {
   706  		return nil, errors.New("encountered machine with no name")
   707  	}
   708  
   709  	jsonPath, err := m.jsonConfigPath()
   710  	if err != nil {
   711  		return nil, err
   712  	}
   713  	mm := HyperVMachine{}
   714  
   715  	if err := mm.loadHyperVMachineFromJSON(jsonPath); err != nil {
   716  		if errors.Is(err, machine.ErrNoSuchVM) {
   717  			return nil, &machine.ErrVMDoesNotExist{Name: m.Name}
   718  		}
   719  		return nil, err
   720  	}
   721  
   722  	lock, err := machine.GetLock(mm.Name, vmtype)
   723  	if err != nil {
   724  		return nil, err
   725  	}
   726  	mm.lock = lock
   727  
   728  	vmm := hypervctl.NewVirtualMachineManager()
   729  	vm, err := vmm.GetMachine(m.Name)
   730  	if err != nil {
   731  		return nil, err
   732  	}
   733  
   734  	cfg, err := vm.GetConfig(mm.ImagePath.GetPath())
   735  	if err != nil {
   736  		return nil, err
   737  	}
   738  
   739  	// If the machine is on, we can get what it is actually using
   740  	if cfg.Hardware.CPUs > 0 {
   741  		mm.CPUs = uint64(cfg.Hardware.CPUs)
   742  	}
   743  	// Same for memory
   744  	if cfg.Hardware.Memory > 0 {
   745  		mm.Memory = uint64(cfg.Hardware.Memory)
   746  	}
   747  
   748  	return &mm, nil
   749  }
   750  
   751  // getVMConfigPath is a simple wrapper for getting the fully-qualified
   752  // path of the vm json config file.  It should be used to get conformity
   753  func getVMConfigPath(configDir, vmName string) string {
   754  	return filepath.Join(configDir, fmt.Sprintf("%s.json", vmName))
   755  }
   756  
   757  func (m *HyperVMachine) loadHyperVMachineFromJSON(fqConfigPath string) error {
   758  	b, err := os.ReadFile(fqConfigPath)
   759  	if err != nil {
   760  		if errors.Is(err, fs.ErrNotExist) {
   761  			return machine.ErrNoSuchVM
   762  		}
   763  		return err
   764  	}
   765  	return json.Unmarshal(b, m)
   766  }
   767  
   768  func (m *HyperVMachine) startHostNetworking() (int32, string, machine.APIForwardingState, error) {
   769  	var (
   770  		forwardSock string
   771  		state       machine.APIForwardingState
   772  	)
   773  	cfg, err := config.Default()
   774  	if err != nil {
   775  		return -1, "", machine.NoForwarding, err
   776  	}
   777  
   778  	executable, err := os.Executable()
   779  	if err != nil {
   780  		return -1, "", 0, fmt.Errorf("unable to locate executable: %w", err)
   781  	}
   782  
   783  	gvproxyBinary, err := cfg.FindHelperBinary("gvproxy.exe", false)
   784  	if err != nil {
   785  		return -1, "", 0, err
   786  	}
   787  
   788  	cmd := gvproxy.NewGvproxyCommand()
   789  	cmd.SSHPort = m.Port
   790  	cmd.AddEndpoint(fmt.Sprintf("vsock://%s", m.NetworkHVSock.KeyName))
   791  	cmd.PidFile = m.GvProxyPid.GetPath()
   792  
   793  	cmd, forwardSock, state = m.setupAPIForwarding(cmd)
   794  	if logrus.IsLevelEnabled(logrus.DebugLevel) {
   795  		cmd.Debug = true
   796  	}
   797  
   798  	c := cmd.Cmd(gvproxyBinary)
   799  
   800  	if logrus.IsLevelEnabled(logrus.DebugLevel) {
   801  		if err := logCommandToFile(c, "gvproxy.log"); err != nil {
   802  			return -1, "", 0, err
   803  		}
   804  	}
   805  
   806  	logrus.Debugf("Starting gvproxy with command: %s %v", gvproxyBinary, c.Args)
   807  
   808  	if err := c.Start(); err != nil {
   809  		return -1, "", 0, fmt.Errorf("unable to execute: %s: %w", cmd.ToCmdline(), err)
   810  	}
   811  
   812  	logrus.Debugf("Got gvproxy PID as %d", c.Process.Pid)
   813  
   814  	if len(m.MountVsocks) == 0 {
   815  		return int32(c.Process.Pid), forwardSock, state, nil
   816  	}
   817  
   818  	// Start the 9p server in the background
   819  	args := []string{}
   820  	if logrus.IsLevelEnabled(logrus.DebugLevel) {
   821  		args = append(args, "--log-level=debug")
   822  	}
   823  	args = append(args, "machine", "server9p")
   824  	for dir, vsock := range m.MountVsocks {
   825  		for _, mount := range m.Mounts {
   826  			if mount.Target == dir {
   827  				args = append(args, "--serve", fmt.Sprintf("%s:%s", mount.Source, winio.VsockServiceID(uint32(vsock)).String()))
   828  				break
   829  			}
   830  		}
   831  	}
   832  	args = append(args, fmt.Sprintf("%d", c.Process.Pid))
   833  
   834  	logrus.Debugf("Going to start 9p server using command: %s %v", executable, args)
   835  
   836  	fsCmd := exec.Command(executable, args...)
   837  
   838  	if logrus.IsLevelEnabled(logrus.DebugLevel) {
   839  		if err := logCommandToFile(fsCmd, "podman-machine-server9.log"); err != nil {
   840  			return -1, "", 0, err
   841  		}
   842  	}
   843  
   844  	if err := fsCmd.Start(); err != nil {
   845  		return -1, "", 0, fmt.Errorf("unable to execute: %s %v: %w", executable, args, err)
   846  	}
   847  
   848  	logrus.Infof("Started podman 9p server as PID %d", fsCmd.Process.Pid)
   849  
   850  	return int32(c.Process.Pid), forwardSock, state, nil
   851  }
   852  
   853  func logCommandToFile(c *exec.Cmd, filename string) error {
   854  	dir, err := machine.GetDataDir(machine.HyperVVirt)
   855  	if err != nil {
   856  		return fmt.Errorf("obtain machine dir: %w", err)
   857  	}
   858  	path := filepath.Join(dir, filename)
   859  	logrus.Infof("Going to log to %s", path)
   860  	log, err := os.Create(path)
   861  	if err != nil {
   862  		return fmt.Errorf("create log file: %w", err)
   863  	}
   864  	defer log.Close()
   865  
   866  	c.Stdout = log
   867  	c.Stderr = log
   868  
   869  	return nil
   870  }
   871  
   872  func (m *HyperVMachine) setupAPIForwarding(cmd gvproxy.GvproxyCommand) (gvproxy.GvproxyCommand, string, machine.APIForwardingState) {
   873  	socket, err := m.forwardSocketPath()
   874  	if err != nil {
   875  		return cmd, "", machine.NoForwarding
   876  	}
   877  
   878  	destSock := fmt.Sprintf("/run/user/%d/podman/podman.sock", m.UID)
   879  	forwardUser := m.RemoteUsername
   880  
   881  	if m.Rootful {
   882  		destSock = "/run/podman/podman.sock"
   883  		forwardUser = "root"
   884  	}
   885  
   886  	cmd.AddForwardSock(socket.GetPath())
   887  	cmd.AddForwardDest(destSock)
   888  	cmd.AddForwardUser(forwardUser)
   889  	cmd.AddForwardIdentity(m.IdentityPath)
   890  
   891  	return cmd, "", machine.MachineLocal
   892  }
   893  
   894  func (m *HyperVMachine) dockerSock() (string, error) {
   895  	dd, err := machine.GetDataDir(machine.HyperVVirt)
   896  	if err != nil {
   897  		return "", err
   898  	}
   899  	return filepath.Join(dd, "podman.sock"), nil
   900  }
   901  
   902  func (m *HyperVMachine) forwardSocketPath() (*define.VMFile, error) {
   903  	sockName := "podman.sock"
   904  	path, err := machine.GetDataDir(machine.HyperVVirt)
   905  	if err != nil {
   906  		return nil, fmt.Errorf("Resolving data dir: %s", err.Error())
   907  	}
   908  	return define.NewMachineFile(filepath.Join(path, sockName), &sockName)
   909  }
   910  
   911  func (m *HyperVMachine) writeConfig() error {
   912  	// Write the JSON file
   913  	return machine.WriteConfig(m.ConfigPath.Path, m)
   914  }
   915  
   916  func (m *HyperVMachine) setRootful(rootful bool) error {
   917  	if err := machine.SetRootful(rootful, m.Name, m.Name+"-root"); err != nil {
   918  		return err
   919  	}
   920  
   921  	m.HostUser.Modified = true
   922  	return nil
   923  }
   924  
   925  func (m *HyperVMachine) resizeDisk(newSize strongunits.GiB) error {
   926  	if m.DiskSize > uint64(newSize) {
   927  		return &machine.ErrNewDiskSizeTooSmall{OldSize: strongunits.ToGiB(strongunits.B(m.DiskSize)), NewSize: newSize}
   928  	}
   929  	resize := exec.Command("powershell", []string{"-command", fmt.Sprintf("Resize-VHD %s %d", m.ImagePath.GetPath(), newSize.ToBytes())}...)
   930  	resize.Stdout = os.Stdout
   931  	resize.Stderr = os.Stderr
   932  	if err := resize.Run(); err != nil {
   933  		return fmt.Errorf("resizing image: %q", err)
   934  	}
   935  	return nil
   936  }
   937  
   938  func (m *HyperVMachine) isStarting() bool {
   939  	return m.Starting
   940  }
   941  
   942  func (m *HyperVMachine) createShares() (_ map[string]uint64, defErr error) {
   943  	toReturn := make(map[string]uint64)
   944  
   945  	for _, mount := range m.Mounts {
   946  		var vsock *HVSockRegistryEntry
   947  
   948  		vsockNum, ok := m.MountVsocks[mount.Target]
   949  		if ok {
   950  			// Ignore errors here, we'll just try and recreate the
   951  			// vsock below.
   952  			testVsock, err := LoadHVSockRegistryEntry(vsockNum)
   953  			if err == nil {
   954  				vsock = testVsock
   955  			}
   956  		}
   957  		if vsock == nil {
   958  			testVsock, err := NewHVSockRegistryEntry(m.Name, Fileserver)
   959  			if err != nil {
   960  				return nil, err
   961  			}
   962  			defer func() {
   963  				if defErr != nil {
   964  					if err := testVsock.Remove(); err != nil {
   965  						logrus.Errorf("Removing vsock: %v", err)
   966  					}
   967  				}
   968  			}()
   969  			vsock = testVsock
   970  		}
   971  
   972  		logrus.Debugf("Going to share directory %s via 9p on vsock %d", mount.Source, vsock.Port)
   973  
   974  		toReturn[mount.Target] = vsock.Port
   975  	}
   976  
   977  	return toReturn, nil
   978  }
   979  
   980  func (m *HyperVMachine) removeShares() error {
   981  	var removalErr error
   982  
   983  	for _, mount := range m.Mounts {
   984  		vsockNum, ok := m.MountVsocks[mount.Target]
   985  		if !ok {
   986  			// Mount doesn't have a valid vsock, no need to tear down
   987  			continue
   988  		}
   989  
   990  		vsock, err := LoadHVSockRegistryEntry(vsockNum)
   991  		if err != nil {
   992  			logrus.Debugf("Vsock %d for mountpoint %s does not have a valid registry entry, skipping removal", vsockNum, mount.Target)
   993  			continue
   994  		}
   995  
   996  		if err := vsock.Remove(); err != nil {
   997  			if removalErr != nil {
   998  				logrus.Errorf("Error removing vsock: %w", removalErr)
   999  			}
  1000  			removalErr = fmt.Errorf("removing vsock %d for mountpoint %s: %w", vsockNum, mount.Target, err)
  1001  		}
  1002  	}
  1003  
  1004  	return removalErr
  1005  }
  1006  
  1007  func (m *HyperVMachine) startShares() error {
  1008  	for mountpoint, sockNum := range m.MountVsocks {
  1009  		args := []string{"-q", "--", "sudo", "podman"}
  1010  		if logrus.IsLevelEnabled(logrus.DebugLevel) {
  1011  			args = append(args, "--log-level=debug")
  1012  		}
  1013  		args = append(args, "machine", "client9p", fmt.Sprintf("%d", sockNum), mountpoint)
  1014  
  1015  		sshOpts := machine.SSHOptions{
  1016  			Args: args,
  1017  		}
  1018  
  1019  		if err := m.SSH(m.Name, sshOpts); err != nil {
  1020  			return err
  1021  		}
  1022  	}
  1023  
  1024  	return nil
  1025  }