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

     1  //go:build darwin
     2  // +build darwin
     3  
     4  package applehv
     5  
     6  import (
     7  	"context"
     8  	"encoding/json"
     9  	"errors"
    10  	"fmt"
    11  	"io/fs"
    12  	"net"
    13  	"os"
    14  	"os/exec"
    15  	"path/filepath"
    16  	"strconv"
    17  	"strings"
    18  	"syscall"
    19  	"time"
    20  
    21  	"github.com/containers/common/pkg/config"
    22  	gvproxy "github.com/containers/gvisor-tap-vsock/pkg/types"
    23  	"github.com/containers/podman/v4/pkg/machine"
    24  	"github.com/containers/podman/v4/pkg/machine/define"
    25  	"github.com/containers/podman/v4/pkg/strongunits"
    26  	"github.com/containers/podman/v4/pkg/util"
    27  	"github.com/containers/podman/v4/utils"
    28  	"github.com/containers/storage/pkg/lockfile"
    29  	vfConfig "github.com/crc-org/vfkit/pkg/config"
    30  	vfRest "github.com/crc-org/vfkit/pkg/rest"
    31  	"github.com/docker/go-units"
    32  	"github.com/sirupsen/logrus"
    33  )
    34  
    35  var (
    36  	// vmtype refers to qemu (vs libvirt, krun, etc).
    37  	vmtype = machine.AppleHvVirt
    38  )
    39  
    40  const (
    41  	dockerSock           = "/var/run/docker.sock"
    42  	dockerConnectTimeout = 5 * time.Second
    43  	apiUpTimeout         = 20 * time.Second
    44  )
    45  
    46  // VfkitHelper describes the use of vfkit: cmdline and endpoint
    47  type VfkitHelper struct {
    48  	LogLevel        logrus.Level
    49  	Endpoint        string
    50  	VfkitBinaryPath *define.VMFile
    51  	VirtualMachine  *vfConfig.VirtualMachine
    52  }
    53  
    54  // appleHVReadyUnit is a unit file that sets up the virtual serial device
    55  // where when the VM is done configuring, it will send an ack
    56  // so a listening host knows it can begin interacting with it
    57  const appleHVReadyUnit = `[Unit]
    58  Requires=dev-virtio\\x2dports-%s.device
    59  After=remove-moby.service sshd.socket sshd.service
    60  OnFailure=emergency.target
    61  OnFailureJobMode=isolate
    62  [Service]
    63  Type=oneshot
    64  RemainAfterExit=yes
    65  ExecStart=/bin/sh -c '/usr/bin/echo Ready | socat - VSOCK-CONNECT:2:1025'
    66  [Install]
    67  RequiredBy=default.target
    68  `
    69  
    70  type MacMachine struct {
    71  	// ConfigPath is the fully qualified path to the configuration file
    72  	ConfigPath define.VMFile
    73  	// HostUser contains info about host user
    74  	machine.HostUser
    75  	// ImageConfig describes the bootable image
    76  	machine.ImageConfig
    77  	// Mounts is the list of remote filesystems to mount
    78  	Mounts []machine.Mount
    79  	// Name of VM
    80  	Name string
    81  	// ReadySocket tells host when vm is booted
    82  	ReadySocket define.VMFile
    83  	// ResourceConfig is physical attrs of the VM
    84  	machine.ResourceConfig
    85  	// SSHConfig for accessing the remote vm
    86  	machine.SSHConfig
    87  	// Starting tells us whether the machine is running or if we have just dialed it to start it
    88  	Starting bool
    89  	// Created contains the original created time instead of querying the file mod time
    90  	Created time.Time
    91  	// LastUp contains the last recorded uptime
    92  	LastUp time.Time
    93  	// The VFKit endpoint where we can interact with the VM
    94  	Vfkit       VfkitHelper
    95  	LogPath     define.VMFile
    96  	GvProxyPid  define.VMFile
    97  	GvProxySock define.VMFile
    98  
    99  	// Used at runtime for serializing write operations
   100  	lock *lockfile.LockFile
   101  }
   102  
   103  // setGVProxyInfo sets the VM's gvproxy pid and socket files
   104  func (m *MacMachine) setGVProxyInfo(runtimeDir string) error {
   105  	gvProxyPid, err := define.NewMachineFile(filepath.Join(runtimeDir, "gvproxy.pid"), nil)
   106  	if err != nil {
   107  		return err
   108  	}
   109  	m.GvProxyPid = *gvProxyPid
   110  
   111  	return machine.SetSocket(&m.GvProxySock, filepath.Join(runtimeDir, "gvproxy.sock"), nil)
   112  }
   113  
   114  // setVfkitInfo stores the default devices, sets the vfkit endpoint, and
   115  // locates/stores the path to the binary
   116  func (m *MacMachine) setVfkitInfo(cfg *config.Config, readySocket define.VMFile) error {
   117  	defaultDevices, err := getDefaultDevices(m.ImagePath.GetPath(), m.LogPath.GetPath(), readySocket.GetPath())
   118  	if err != nil {
   119  		return err
   120  	}
   121  	// Store VFKit stuffs
   122  	vfkitPath, err := cfg.FindHelperBinary("vfkit", false)
   123  	if err != nil {
   124  		return err
   125  	}
   126  	vfkitBinaryPath, err := define.NewMachineFile(vfkitPath, nil)
   127  	if err != nil {
   128  		return err
   129  	}
   130  
   131  	m.Vfkit.VirtualMachine.Devices = defaultDevices
   132  	randPort, err := utils.GetRandomPort()
   133  	if err != nil {
   134  		return err
   135  	}
   136  
   137  	m.Vfkit.Endpoint = localhostURI + ":" + strconv.Itoa(randPort)
   138  	m.Vfkit.VfkitBinaryPath = vfkitBinaryPath
   139  
   140  	return nil
   141  }
   142  
   143  // addMountsToVM converts the volumes passed through the CLI to virtio-fs mounts
   144  // and adds them to the machine
   145  func (m *MacMachine) addMountsToVM(opts machine.InitOptions, virtiofsMnts *[]machine.VirtIoFs) error {
   146  	var mounts []machine.Mount
   147  	for _, volume := range opts.Volumes {
   148  		source, target, _, readOnly, err := machine.ParseVolumeFromPath(volume)
   149  		if err != nil {
   150  			return err
   151  		}
   152  		mnt := machine.NewVirtIoFsMount(source, target, readOnly)
   153  		*virtiofsMnts = append(*virtiofsMnts, mnt)
   154  		mounts = append(mounts, mnt.ToMount())
   155  	}
   156  	m.Mounts = mounts
   157  
   158  	return nil
   159  }
   160  
   161  func (m *MacMachine) Init(opts machine.InitOptions) (bool, error) {
   162  	var (
   163  		key          string
   164  		virtiofsMnts []machine.VirtIoFs
   165  		err          error
   166  	)
   167  
   168  	// cleanup half-baked files if init fails at any point
   169  	callbackFuncs := machine.InitCleanup()
   170  	defer callbackFuncs.CleanIfErr(&err)
   171  	go callbackFuncs.CleanOnSignal()
   172  
   173  	callbackFuncs.Add(m.ConfigPath.Delete)
   174  	dataDir, err := machine.GetDataDir(machine.AppleHvVirt)
   175  	if err != nil {
   176  		return false, err
   177  	}
   178  	cfg, err := config.Default()
   179  	if err != nil {
   180  		return false, err
   181  	}
   182  
   183  	dl, err := VirtualizationProvider().NewDownload(m.Name)
   184  	if err != nil {
   185  		return false, err
   186  	}
   187  
   188  	imagePath, strm, err := dl.AcquireVMImage(opts.ImagePath)
   189  	if err != nil {
   190  		return false, err
   191  	}
   192  	callbackFuncs.Add(imagePath.Delete)
   193  
   194  	// Set the values for imagePath and strm
   195  	m.ImagePath = *imagePath
   196  	m.ImageStream = strm.String()
   197  
   198  	logPath, err := define.NewMachineFile(filepath.Join(dataDir, fmt.Sprintf("%s.log", m.Name)), nil)
   199  	if err != nil {
   200  		return false, err
   201  	}
   202  	callbackFuncs.Add(logPath.Delete)
   203  
   204  	m.LogPath = *logPath
   205  	runtimeDir, err := m.getRuntimeDir()
   206  	if err != nil {
   207  		return false, err
   208  	}
   209  
   210  	if err := machine.SetSocket(&m.ReadySocket, machine.ReadySocketPath(runtimeDir, m.Name), nil); err != nil {
   211  		return false, err
   212  	}
   213  
   214  	if err = m.setGVProxyInfo(runtimeDir); err != nil {
   215  		return false, err
   216  	}
   217  
   218  	if err = m.setVfkitInfo(cfg, m.ReadySocket); err != nil {
   219  		return false, err
   220  	}
   221  
   222  	m.IdentityPath = util.GetIdentityPath(m.Name)
   223  	m.Rootful = opts.Rootful
   224  	m.RemoteUsername = opts.Username
   225  
   226  	m.UID = os.Getuid()
   227  
   228  	sshPort, err := utils.GetRandomPort()
   229  	if err != nil {
   230  		return false, err
   231  	}
   232  	m.Port = sshPort
   233  
   234  	if err = m.addMountsToVM(opts, &virtiofsMnts); err != nil {
   235  		return false, err
   236  	}
   237  
   238  	err = machine.AddSSHConnectionsToPodmanSocket(
   239  		m.UID,
   240  		m.Port,
   241  		m.IdentityPath,
   242  		m.Name,
   243  		m.RemoteUsername,
   244  		opts,
   245  	)
   246  	if err != nil {
   247  		return false, err
   248  	}
   249  	callbackFuncs.Add(m.removeSystemConnections)
   250  
   251  	logrus.Debugf("resizing disk to %d GiB", opts.DiskSize)
   252  	if err = m.resizeDisk(strongunits.GiB(opts.DiskSize)); err != nil {
   253  		return false, err
   254  	}
   255  
   256  	if err = m.writeConfig(); err != nil {
   257  		return false, err
   258  	}
   259  
   260  	if len(opts.IgnitionPath) < 1 {
   261  		key, err = machine.CreateSSHKeys(m.IdentityPath)
   262  		if err != nil {
   263  			return false, err
   264  		}
   265  		callbackFuncs.Add(m.removeSSHKeys)
   266  	}
   267  
   268  	builder := machine.NewIgnitionBuilder(machine.DynamicIgnition{
   269  		Name:      opts.Username,
   270  		Key:       key,
   271  		VMName:    m.Name,
   272  		VMType:    machine.AppleHvVirt,
   273  		TimeZone:  opts.TimeZone,
   274  		WritePath: m.IgnitionFile.GetPath(),
   275  		UID:       m.UID,
   276  		Rootful:   m.Rootful,
   277  	})
   278  
   279  	if len(opts.IgnitionPath) > 0 {
   280  		return false, builder.BuildWithIgnitionFile(opts.IgnitionPath)
   281  	}
   282  
   283  	if err := builder.GenerateIgnitionConfig(); err != nil {
   284  		return false, err
   285  	}
   286  
   287  	builder.WithUnit(machine.Unit{
   288  		Enabled:  machine.BoolToPtr(true),
   289  		Name:     "ready.service",
   290  		Contents: machine.StrToPtr(fmt.Sprintf(appleHVReadyUnit, "vsock")),
   291  	})
   292  	builder.WithUnit(generateSystemDFilesForVirtiofsMounts(virtiofsMnts)...)
   293  
   294  	// TODO Ignition stuff goes here
   295  	err = builder.Build()
   296  	callbackFuncs.Add(m.IgnitionFile.Delete)
   297  
   298  	return err == nil, err
   299  }
   300  
   301  func (m *MacMachine) removeSSHKeys() error {
   302  	if err := os.Remove(fmt.Sprintf("%s.pub", m.IdentityPath)); err != nil {
   303  		logrus.Error(err)
   304  	}
   305  	return os.Remove(m.IdentityPath)
   306  }
   307  
   308  func (m *MacMachine) removeSystemConnections() error {
   309  	return machine.RemoveConnections(m.Name, fmt.Sprintf("%s-root", m.Name))
   310  }
   311  
   312  func (m *MacMachine) Inspect() (*machine.InspectInfo, error) {
   313  	vmState, err := m.Vfkit.state()
   314  	if err != nil {
   315  		return nil, err
   316  	}
   317  
   318  	podmanSocket, err := m.forwardSocketPath()
   319  	if err != nil {
   320  		return nil, err
   321  	}
   322  
   323  	ii := machine.InspectInfo{
   324  		ConfigPath: m.ConfigPath,
   325  		ConnectionInfo: machine.ConnectionConfig{
   326  			PodmanSocket: podmanSocket,
   327  			PodmanPipe:   nil,
   328  		},
   329  		Created: m.Created,
   330  		Image: machine.ImageConfig{
   331  			IgnitionFile: m.IgnitionFile,
   332  			ImageStream:  m.ImageStream,
   333  			ImagePath:    m.ImagePath,
   334  		},
   335  		LastUp: m.LastUp,
   336  		Name:   m.Name,
   337  		Resources: machine.ResourceConfig{
   338  			CPUs:     m.CPUs,
   339  			DiskSize: m.DiskSize,
   340  			Memory:   m.Memory,
   341  		},
   342  		SSHConfig: m.SSHConfig,
   343  		State:     vmState,
   344  		Rootful:   m.Rootful,
   345  	}
   346  	return &ii, nil
   347  }
   348  
   349  // collectFilesToDestroy retrieves the files that will be destroyed by `Remove`
   350  func (m *MacMachine) collectFilesToDestroy(opts machine.RemoveOptions) []string {
   351  	files := []string{}
   352  	if !opts.SaveKeys {
   353  		files = append(files, m.IdentityPath, m.IdentityPath+".pub")
   354  	}
   355  	if !opts.SaveIgnition {
   356  		files = append(files, m.IgnitionFile.GetPath())
   357  	}
   358  
   359  	if !opts.SaveImage {
   360  		files = append(files, m.ImagePath.GetPath())
   361  	}
   362  
   363  	files = append(files, m.ConfigPath.GetPath())
   364  	return files
   365  }
   366  
   367  func (m *MacMachine) Remove(name string, opts machine.RemoveOptions) (string, func() error, error) {
   368  	var (
   369  		files []string
   370  	)
   371  
   372  	m.lock.Lock()
   373  	defer m.lock.Unlock()
   374  
   375  	vmState, err := m.Vfkit.state()
   376  	if err != nil {
   377  		return "", nil, err
   378  	}
   379  
   380  	if vmState == machine.Running {
   381  		if !opts.Force {
   382  			return "", nil, &machine.ErrVMRunningCannotDestroyed{Name: m.Name}
   383  		}
   384  		if err := m.Vfkit.stop(true, true); err != nil {
   385  			return "", nil, err
   386  		}
   387  		defer func() {
   388  			if err := machine.CleanupGVProxy(m.GvProxyPid); err != nil {
   389  				logrus.Error(err)
   390  			}
   391  		}()
   392  	}
   393  
   394  	files = m.collectFilesToDestroy(opts)
   395  
   396  	confirmationMessage := "\nThe following files will be deleted:\n\n"
   397  	for _, msg := range files {
   398  		confirmationMessage += msg + "\n"
   399  	}
   400  
   401  	confirmationMessage += "\n"
   402  	return confirmationMessage, func() error {
   403  		machine.RemoveFilesAndConnections(files, m.Name, m.Name+"-root")
   404  		// TODO We will need something like this for applehv too i think
   405  		/*
   406  			// Remove the HVSOCK for networking
   407  			if err := m.NetworkHVSock.Remove(); err != nil {
   408  				logrus.Errorf("unable to remove registry entry for %s: %q", m.NetworkHVSock.KeyName, err)
   409  			}
   410  
   411  			// Remove the HVSOCK for events
   412  			if err := m.ReadyHVSock.Remove(); err != nil {
   413  				logrus.Errorf("unable to remove registry entry for %s: %q", m.NetworkHVSock.KeyName, err)
   414  			}
   415  		*/
   416  		return nil
   417  	}, nil
   418  }
   419  
   420  func (m *MacMachine) writeConfig() error {
   421  	b, err := json.MarshalIndent(m, "", " ")
   422  	if err != nil {
   423  		return err
   424  	}
   425  	return os.WriteFile(m.ConfigPath.Path, b, 0644)
   426  }
   427  
   428  func (m *MacMachine) setRootful(rootful bool) error {
   429  	if err := machine.SetRootful(rootful, m.Name, m.Name+"-root"); err != nil {
   430  		return err
   431  	}
   432  
   433  	m.HostUser.Modified = true
   434  	return nil
   435  }
   436  
   437  func (m *MacMachine) Set(name string, opts machine.SetOptions) ([]error, error) {
   438  	var setErrors []error
   439  
   440  	m.lock.Lock()
   441  	defer m.lock.Unlock()
   442  
   443  	vmState, err := m.State(false)
   444  	if err != nil {
   445  		return nil, err
   446  	}
   447  	if vmState != machine.Stopped {
   448  		return nil, machine.ErrWrongState
   449  	}
   450  	if cpus := opts.CPUs; cpus != nil {
   451  		m.CPUs = *cpus
   452  	}
   453  	if mem := opts.Memory; mem != nil {
   454  		m.Memory = *mem
   455  	}
   456  	if newSize := opts.DiskSize; newSize != nil {
   457  		if *newSize < m.DiskSize {
   458  			setErrors = append(setErrors, errors.New("new disk size smaller than existing disk size: cannot shrink disk size"))
   459  		} else {
   460  			m.DiskSize = *newSize
   461  			if err := m.resizeDisk(strongunits.GiB(*opts.DiskSize)); err != nil {
   462  				setErrors = append(setErrors, err)
   463  			}
   464  		}
   465  	}
   466  	if opts.USBs != nil {
   467  		setErrors = append(setErrors, errors.New("changing USBs not supported for applehv machines"))
   468  	}
   469  
   470  	if opts.Rootful != nil && m.Rootful != *opts.Rootful {
   471  		if err := m.setRootful(*opts.Rootful); err != nil {
   472  			setErrors = append(setErrors, fmt.Errorf("failed to set rootful option: %w", err))
   473  		} else {
   474  			m.Rootful = *opts.Rootful
   475  		}
   476  	}
   477  
   478  	// Write the machine config to the filesystem
   479  	err = m.writeConfig()
   480  	setErrors = append(setErrors, err)
   481  	switch len(setErrors) {
   482  	case 0:
   483  		return setErrors, nil
   484  	case 1:
   485  		return nil, setErrors[0]
   486  	default:
   487  		// Number of errors is 2 or more
   488  		lastErr := setErrors[len(setErrors)-1]
   489  		return setErrors[:len(setErrors)-1], lastErr
   490  	}
   491  }
   492  
   493  func (m *MacMachine) SSH(name string, opts machine.SSHOptions) error {
   494  	st, err := m.State(false)
   495  	if err != nil {
   496  		return err
   497  	}
   498  	if st != machine.Running {
   499  		return fmt.Errorf("vm %q is not running", m.Name)
   500  	}
   501  	username := opts.Username
   502  	if username == "" {
   503  		username = m.RemoteUsername
   504  	}
   505  	return machine.CommonSSH(username, m.IdentityPath, m.Name, m.Port, opts.Args)
   506  }
   507  
   508  // deleteIgnitionSocket retrieves the ignition socket, deletes it, and returns a
   509  // pointer to the `VMFile`
   510  func (m *MacMachine) deleteIgnitionSocket() (*define.VMFile, error) {
   511  	ignitionSocket, err := m.getIgnitionSock()
   512  	if err != nil {
   513  		return nil, err
   514  	}
   515  	if err := ignitionSocket.Delete(); err != nil {
   516  		return nil, err
   517  	}
   518  	return ignitionSocket, nil
   519  }
   520  
   521  // getIgnitionVsockDeviceAsCLI retrieves the ignition vsock device and converts
   522  // it to a cmdline format
   523  func getIgnitionVsockDeviceAsCLI(ignitionSocketPath string) ([]string, error) {
   524  	ignitionVsockDevice, err := getIgnitionVsockDevice(ignitionSocketPath)
   525  	if err != nil {
   526  		return nil, err
   527  	}
   528  	// Convert the device into cli args
   529  	ignitionVsockDeviceCLI, err := ignitionVsockDevice.ToCmdLine()
   530  	if err != nil {
   531  		return nil, err
   532  	}
   533  	return ignitionVsockDeviceCLI, nil
   534  }
   535  
   536  // getDebugDevicesCMDArgs retrieves the debug devices and converts them to a
   537  // cmdline format
   538  func getDebugDevicesCMDArgs() ([]string, error) {
   539  	args := []string{}
   540  	debugDevices, err := getDebugDevices()
   541  	if err != nil {
   542  		return nil, err
   543  	}
   544  	for _, debugDevice := range debugDevices {
   545  		debugCli, err := debugDevice.ToCmdLine()
   546  		if err != nil {
   547  			return nil, err
   548  		}
   549  		args = append(args, debugCli...)
   550  	}
   551  	return args, nil
   552  }
   553  
   554  // getVfKitEndpointCMDArgs converts the vfkit endpoint to a cmdline format
   555  func getVfKitEndpointCMDArgs(endpoint string) ([]string, error) {
   556  	restEndpoint, err := vfRest.NewEndpoint(endpoint)
   557  	if err != nil {
   558  		return nil, err
   559  	}
   560  	return restEndpoint.ToCmdLine()
   561  }
   562  
   563  // addVolumesToVfKit adds the VM's mounts to vfkit's devices
   564  func (m *MacMachine) addVolumesToVfKit() error {
   565  	for _, vol := range m.Mounts {
   566  		virtfsDevice, err := vfConfig.VirtioFsNew(vol.Source, vol.Tag)
   567  		if err != nil {
   568  			return err
   569  		}
   570  		m.Vfkit.VirtualMachine.Devices = append(m.Vfkit.VirtualMachine.Devices, virtfsDevice)
   571  	}
   572  	return nil
   573  }
   574  
   575  func (m *MacMachine) Start(name string, opts machine.StartOptions) error {
   576  	var ignitionSocket *define.VMFile
   577  
   578  	m.lock.Lock()
   579  	defer m.lock.Unlock()
   580  
   581  	st, err := m.State(false)
   582  	if err != nil {
   583  		return err
   584  	}
   585  
   586  	if st == machine.Running {
   587  		return machine.ErrVMAlreadyRunning
   588  	}
   589  
   590  	// TODO handle returns from startHostNetworking
   591  	forwardSock, forwardState, err := m.startHostNetworking()
   592  	if err != nil {
   593  		return err
   594  	}
   595  
   596  	// Add networking
   597  	netDevice, err := vfConfig.VirtioNetNew("5a:94:ef:e4:0c:ee")
   598  	if err != nil {
   599  		return err
   600  	}
   601  	// Set user networking with gvproxy
   602  	netDevice.SetUnixSocketPath(m.GvProxySock.GetPath())
   603  
   604  	m.Vfkit.VirtualMachine.Devices = append(m.Vfkit.VirtualMachine.Devices, netDevice)
   605  
   606  	if err := m.addVolumesToVfKit(); err != nil {
   607  		return err
   608  	}
   609  
   610  	// To start the VM, we need to call vfkit
   611  
   612  	logrus.Debugf("vfkit path is: %s", m.Vfkit.VfkitBinaryPath.Path)
   613  	cmd, err := m.Vfkit.VirtualMachine.Cmd(m.Vfkit.VfkitBinaryPath.Path)
   614  	if err != nil {
   615  		return err
   616  	}
   617  
   618  	vfkitEndpointArgs, err := getVfKitEndpointCMDArgs(m.Vfkit.Endpoint)
   619  	if err != nil {
   620  		return err
   621  	}
   622  	cmd.Args = append(cmd.Args, vfkitEndpointArgs...)
   623  
   624  	firstBoot, err := m.isFirstBoot()
   625  	if err != nil {
   626  		return err
   627  	}
   628  
   629  	if firstBoot {
   630  		// If this is the first boot of the vm, we need to add the vsock
   631  		// device to vfkit so we can inject the ignition file
   632  		ignitionSocket, err = m.deleteIgnitionSocket()
   633  		if err != nil {
   634  			return err
   635  		}
   636  
   637  		ignitionVsockDeviceCLI, err := getIgnitionVsockDeviceAsCLI(ignitionSocket.GetPath())
   638  		if err != nil {
   639  			return err
   640  		}
   641  		cmd.Args = append(cmd.Args, ignitionVsockDeviceCLI...)
   642  	}
   643  
   644  	if logrus.IsLevelEnabled(logrus.DebugLevel) {
   645  		debugDevArgs, err := getDebugDevicesCMDArgs()
   646  		if err != nil {
   647  			return err
   648  		}
   649  		cmd.Args = append(cmd.Args, debugDevArgs...)
   650  		cmd.Args = append(cmd.Args, "--gui") // add command line switch to pop the gui open
   651  	}
   652  
   653  	readSocketBaseDir := filepath.Dir(m.ReadySocket.GetPath())
   654  	if err := os.MkdirAll(readSocketBaseDir, 0755); err != nil {
   655  		return err
   656  	}
   657  
   658  	if firstBoot {
   659  		logrus.Debug("first boot detected")
   660  		logrus.Debugf("serving ignition file over %s", ignitionSocket.GetPath())
   661  		go func() {
   662  			if err := m.serveIgnitionOverSock(ignitionSocket); err != nil {
   663  				logrus.Error(err)
   664  			}
   665  			logrus.Debug("ignition vsock server exited")
   666  		}()
   667  	}
   668  
   669  	if err := m.ReadySocket.Delete(); err != nil {
   670  		return err
   671  	}
   672  
   673  	logrus.Debugf("listening for ready on: %s", m.ReadySocket.GetPath())
   674  	readyListen, err := net.Listen("unix", m.ReadySocket.GetPath())
   675  	if err != nil {
   676  		return err
   677  	}
   678  
   679  	logrus.Debug("waiting for ready notification")
   680  	readyChan := make(chan error)
   681  	go machine.ListenAndWaitOnSocket(readyChan, readyListen)
   682  
   683  	if err := cmd.Start(); err != nil {
   684  		return err
   685  	}
   686  
   687  	processErrChan := make(chan error)
   688  	ctx, cancel := context.WithCancel(context.Background())
   689  	defer cancel()
   690  	go func() {
   691  		defer close(processErrChan)
   692  		for {
   693  			select {
   694  			case <-ctx.Done():
   695  				return
   696  			default:
   697  			}
   698  			if err := checkProcessRunning("vfkit", cmd.Process.Pid); err != nil {
   699  				processErrChan <- err
   700  				return
   701  			}
   702  			// lets poll status every half second
   703  			time.Sleep(500 * time.Millisecond)
   704  		}
   705  	}()
   706  
   707  	// wait for either socket or to be ready or process to have exited
   708  	select {
   709  	case err := <-processErrChan:
   710  		if err != nil {
   711  			return err
   712  		}
   713  	case err := <-readyChan:
   714  		if err != nil {
   715  			return err
   716  		}
   717  	}
   718  
   719  	logrus.Debug("ready notification received")
   720  	machine.WaitAPIAndPrintInfo(
   721  		forwardState,
   722  		m.Name,
   723  		findClaimHelper(),
   724  		forwardSock,
   725  		opts.NoInfo,
   726  		m.isIncompatible(),
   727  		m.Rootful,
   728  	)
   729  
   730  	// update the podman/docker socket service if the host user has been modified at all (UID or Rootful)
   731  	if m.HostUser.Modified {
   732  		if machine.UpdatePodmanDockerSockService(m, name, m.UID, m.Rootful) == nil {
   733  			// Reset modification state if there are no errors, otherwise ignore errors
   734  			// which are already logged
   735  			m.HostUser.Modified = false
   736  			_ = m.writeConfig()
   737  		}
   738  	}
   739  
   740  	return nil
   741  }
   742  
   743  func (m *MacMachine) State(_ bool) (machine.Status, error) {
   744  	vmStatus, err := m.Vfkit.state()
   745  	if err != nil {
   746  		return "", err
   747  	}
   748  	return vmStatus, nil
   749  }
   750  
   751  func (m *MacMachine) Stop(name string, opts machine.StopOptions) error {
   752  	m.lock.Lock()
   753  	defer m.lock.Unlock()
   754  
   755  	vmState, err := m.State(false)
   756  	if err != nil {
   757  		return err
   758  	}
   759  
   760  	if vmState != machine.Running {
   761  		return nil
   762  	}
   763  
   764  	defer func() {
   765  		if err := machine.CleanupGVProxy(m.GvProxyPid); err != nil {
   766  			logrus.Error(err)
   767  		}
   768  	}()
   769  	if err := m.Vfkit.stop(false, true); err != nil {
   770  		return err
   771  	}
   772  
   773  	// keep track of last up
   774  	m.LastUp = time.Now()
   775  	return m.writeConfig()
   776  }
   777  
   778  // getVMConfigPath is a simple wrapper for getting the fully-qualified
   779  // path of the vm json config file.  It should be used to get conformity
   780  func getVMConfigPath(configDir, vmName string) string {
   781  	return filepath.Join(configDir, fmt.Sprintf("%s.json", vmName))
   782  }
   783  
   784  func (m *MacMachine) loadFromFile() (*MacMachine, error) {
   785  	if len(m.Name) < 1 {
   786  		return nil, errors.New("encountered machine with no name")
   787  	}
   788  
   789  	jsonPath, err := m.jsonConfigPath()
   790  	if err != nil {
   791  		return nil, err
   792  	}
   793  
   794  	mm, err := loadMacMachineFromJSON(jsonPath)
   795  	if err != nil {
   796  		return nil, err
   797  	}
   798  
   799  	lock, err := machine.GetLock(mm.Name, vmtype)
   800  	if err != nil {
   801  		return nil, err
   802  	}
   803  	mm.lock = lock
   804  
   805  	return mm, nil
   806  }
   807  
   808  func loadMacMachineFromJSON(fqConfigPath string) (*MacMachine, error) {
   809  	b, err := os.ReadFile(fqConfigPath)
   810  	if err != nil {
   811  		if errors.Is(err, fs.ErrNotExist) {
   812  			name := strings.TrimSuffix(filepath.Base(fqConfigPath), ".json")
   813  			return nil, fmt.Errorf("%s: %w", name, machine.ErrNoSuchVM)
   814  		}
   815  		return nil, err
   816  	}
   817  	mm := new(MacMachine)
   818  	if err := json.Unmarshal(b, mm); err != nil {
   819  		return nil, err
   820  	}
   821  	return mm, nil
   822  }
   823  
   824  func (m *MacMachine) jsonConfigPath() (string, error) {
   825  	configDir, err := machine.GetConfDir(machine.AppleHvVirt)
   826  	if err != nil {
   827  		return "", err
   828  	}
   829  	return getVMConfigPath(configDir, m.Name), nil
   830  }
   831  
   832  func getVMInfos() ([]*machine.ListResponse, error) {
   833  	vmConfigDir, err := machine.GetConfDir(vmtype)
   834  	if err != nil {
   835  		return nil, err
   836  	}
   837  
   838  	var listed []*machine.ListResponse
   839  
   840  	if err = filepath.WalkDir(vmConfigDir, func(path string, d fs.DirEntry, err error) error {
   841  		vm := new(MacMachine)
   842  		if strings.HasSuffix(d.Name(), ".json") {
   843  			fullPath := filepath.Join(vmConfigDir, d.Name())
   844  			b, err := os.ReadFile(fullPath)
   845  			if err != nil {
   846  				return err
   847  			}
   848  			err = json.Unmarshal(b, vm)
   849  			if err != nil {
   850  				return err
   851  			}
   852  			listEntry := new(machine.ListResponse)
   853  
   854  			listEntry.Name = vm.Name
   855  			listEntry.Stream = vm.ImageStream
   856  			listEntry.VMType = machine.AppleHvVirt.String()
   857  			listEntry.CPUs = vm.CPUs
   858  			listEntry.Memory = vm.Memory * units.MiB
   859  			listEntry.DiskSize = vm.DiskSize * units.GiB
   860  			listEntry.Port = vm.Port
   861  			listEntry.RemoteUsername = vm.RemoteUsername
   862  			listEntry.IdentityPath = vm.IdentityPath
   863  			listEntry.CreatedAt = vm.Created
   864  			listEntry.Starting = vm.Starting
   865  
   866  			if listEntry.CreatedAt.IsZero() {
   867  				listEntry.CreatedAt = time.Now()
   868  				vm.Created = time.Now()
   869  				if err := vm.writeConfig(); err != nil {
   870  					return err
   871  				}
   872  			}
   873  
   874  			vmState, err := vm.State(false)
   875  			if err != nil {
   876  				return err
   877  			}
   878  			listEntry.Running = vmState == machine.Running
   879  			listEntry.LastUp = vm.LastUp
   880  
   881  			listed = append(listed, listEntry)
   882  		}
   883  		return nil
   884  	}); err != nil {
   885  		return nil, err
   886  	}
   887  	return listed, err
   888  }
   889  
   890  // setupStartHostNetworkingCmd generates the cmd that will be used to start the
   891  // host networking. Includes the ssh port, gvproxy pid file, gvproxy socket, and
   892  // a debug flag depending on the logrus log level
   893  func (m *MacMachine) setupStartHostNetworkingCmd() (gvproxy.GvproxyCommand, string, machine.APIForwardingState) {
   894  	cmd := gvproxy.NewGvproxyCommand()
   895  	cmd.SSHPort = m.Port
   896  	cmd.PidFile = m.GvProxyPid.GetPath()
   897  	cmd.AddVfkitSocket(fmt.Sprintf("unixgram://%s", m.GvProxySock.GetPath()))
   898  	cmd.Debug = logrus.IsLevelEnabled(logrus.DebugLevel)
   899  
   900  	cmd, forwardSock, state := m.setupAPIForwarding(cmd)
   901  	if cmd.Debug {
   902  		logrus.Debug(cmd.ToCmdline())
   903  	}
   904  
   905  	return cmd, forwardSock, state
   906  }
   907  
   908  func (m *MacMachine) startHostNetworking() (string, machine.APIForwardingState, error) {
   909  	var (
   910  		forwardSock string
   911  		state       machine.APIForwardingState
   912  	)
   913  
   914  	// TODO This should probably be added to startHostNetworking everywhere
   915  	// GvProxy does not clean up after itself
   916  	if err := m.GvProxySock.Delete(); err != nil {
   917  		b, err := m.GvProxyPid.Read()
   918  		if err != nil {
   919  			return "", machine.NoForwarding, err
   920  		}
   921  		pid, err := strconv.Atoi(string(b))
   922  		if err != nil {
   923  			return "", 0, err
   924  		}
   925  		gvProcess, err := os.FindProcess(pid)
   926  		if err != nil {
   927  			return "", 0, err
   928  		}
   929  		// shoot it with a signal 0 and see if it is active
   930  		err = gvProcess.Signal(syscall.Signal(0))
   931  		if err == nil {
   932  			return "", 0, fmt.Errorf("gvproxy process %s already running", string(b))
   933  		}
   934  		if err := m.GvProxySock.Delete(); err != nil {
   935  			return "", 0, err
   936  		}
   937  	}
   938  	cfg, err := config.Default()
   939  	if err != nil {
   940  		return "", machine.NoForwarding, err
   941  	}
   942  
   943  	gvproxyBinary, err := cfg.FindHelperBinary("gvproxy", false)
   944  	if err != nil {
   945  		return "", 0, err
   946  	}
   947  
   948  	logrus.Debugf("gvproxy binary being used: %s", gvproxyBinary)
   949  
   950  	cmd, forwardSock, state := m.setupStartHostNetworkingCmd()
   951  	c := cmd.Cmd(gvproxyBinary)
   952  	if err := c.Start(); err != nil {
   953  		return "", 0, fmt.Errorf("unable to execute: %q: %w", cmd.ToCmdline(), err)
   954  	}
   955  
   956  	// We need to wait and make sure gvproxy is in fact running
   957  	// before continuing
   958  	for i := 0; i < 10; i++ {
   959  		_, err := os.Stat(m.GvProxySock.GetPath())
   960  		if err == nil {
   961  			break
   962  		}
   963  		if err := checkProcessRunning("gvproxy", c.Process.Pid); err != nil {
   964  			// gvproxy is no longer running
   965  			return "", 0, err
   966  		}
   967  		logrus.Debugf("gvproxy unixgram socket %q not found: %v", m.GvProxySock.GetPath(), err)
   968  		// Sleep for 1/2 second
   969  		time.Sleep(500 * time.Millisecond)
   970  	}
   971  	if err != nil {
   972  		// I guess we would also check the pidfile and look to see if it is running
   973  		// to?
   974  		return "", 0, fmt.Errorf("unable to verify gvproxy is running")
   975  	}
   976  	return forwardSock, state, nil
   977  }
   978  
   979  // checkProcessRunning checks non blocking if the pid exited
   980  // returns nil if process is running otherwise an error if not
   981  func checkProcessRunning(processName string, pid int) error {
   982  	var status syscall.WaitStatus
   983  	pid, err := syscall.Wait4(pid, &status, syscall.WNOHANG, nil)
   984  	if err != nil {
   985  		return fmt.Errorf("failed to read %s process status: %w", processName, err)
   986  	}
   987  	if pid > 0 {
   988  		// child exited
   989  		return fmt.Errorf("%s exited unexpectedly with exit code %d", processName, status.ExitStatus())
   990  	}
   991  	return nil
   992  }
   993  
   994  func (m *MacMachine) setupAPIForwarding(cmd gvproxy.GvproxyCommand) (gvproxy.GvproxyCommand, string, machine.APIForwardingState) {
   995  	socket, err := m.forwardSocketPath()
   996  	if err != nil {
   997  		return cmd, "", machine.NoForwarding
   998  	}
   999  
  1000  	destSock := fmt.Sprintf("/run/user/%d/podman/podman.sock", m.UID)
  1001  	forwardUser := m.RemoteUsername
  1002  
  1003  	if m.Rootful {
  1004  		destSock = "/run/podman/podman.sock"
  1005  		forwardUser = "root"
  1006  	}
  1007  
  1008  	cmd.AddForwardSock(socket.GetPath())
  1009  	cmd.AddForwardDest(destSock)
  1010  	cmd.AddForwardUser(forwardUser)
  1011  	cmd.AddForwardIdentity(m.IdentityPath)
  1012  
  1013  	link, err := m.userGlobalSocketLink()
  1014  	if err != nil {
  1015  		return cmd, socket.GetPath(), machine.MachineLocal
  1016  	}
  1017  
  1018  	if !dockerClaimSupported() {
  1019  		return cmd, socket.GetPath(), machine.ClaimUnsupported
  1020  	}
  1021  
  1022  	if !dockerClaimHelperInstalled() {
  1023  		return cmd, socket.GetPath(), machine.NotInstalled
  1024  	}
  1025  
  1026  	if !alreadyLinked(socket.GetPath(), link) {
  1027  		if checkSockInUse(link) {
  1028  			return cmd, socket.GetPath(), machine.MachineLocal
  1029  		}
  1030  
  1031  		_ = os.Remove(link)
  1032  		if err = os.Symlink(socket.GetPath(), link); err != nil {
  1033  			logrus.Warnf("could not create user global API forwarding link: %s", err.Error())
  1034  			return cmd, socket.GetPath(), machine.MachineLocal
  1035  		}
  1036  	}
  1037  
  1038  	if !alreadyLinked(link, dockerSock) {
  1039  		if checkSockInUse(dockerSock) {
  1040  			return cmd, socket.GetPath(), machine.MachineLocal
  1041  		}
  1042  
  1043  		if !claimDockerSock() {
  1044  			logrus.Warn("podman helper is installed, but was not able to claim the global docker sock")
  1045  			return cmd, socket.GetPath(), machine.MachineLocal
  1046  		}
  1047  	}
  1048  
  1049  	return cmd, socket.GetPath(), machine.MachineLocal
  1050  
  1051  }
  1052  
  1053  func (m *MacMachine) dockerSock() (string, error) {
  1054  	dd, err := machine.GetDataDir(machine.AppleHvVirt)
  1055  	if err != nil {
  1056  		return "", err
  1057  	}
  1058  	return filepath.Join(dd, "podman.sock"), nil
  1059  }
  1060  
  1061  func (m *MacMachine) forwardSocketPath() (*define.VMFile, error) {
  1062  	sockName := "podman.sock"
  1063  	path, err := machine.GetDataDir(machine.AppleHvVirt)
  1064  	if err != nil {
  1065  		return nil, fmt.Errorf("Resolving data dir: %s", err.Error())
  1066  	}
  1067  	return define.NewMachineFile(filepath.Join(path, sockName), &sockName)
  1068  }
  1069  
  1070  // resizeDisk uses os truncate to resize (only larger) a raw disk.  the input size
  1071  // is assumed GiB
  1072  func (m *MacMachine) resizeDisk(newSize strongunits.GiB) error {
  1073  	if uint64(newSize) < m.DiskSize {
  1074  		// TODO this error needs to be changed to the common error.  would do now but the PR for the common
  1075  		// error has not merged
  1076  		return fmt.Errorf("invalid disk size %d: new disk must be larger than %dGB", newSize, m.DiskSize)
  1077  	}
  1078  	logrus.Debugf("resizing %s to %d bytes", m.ImagePath.GetPath(), newSize.ToBytes())
  1079  	// seems like os.truncate() is not very performant with really large files
  1080  	// so exec'ing out to the command truncate
  1081  	size := fmt.Sprintf("%dG", newSize)
  1082  	c := exec.Command("truncate", "-s", size, m.ImagePath.GetPath())
  1083  	if logrus.IsLevelEnabled(logrus.DebugLevel) {
  1084  		c.Stderr = os.Stderr
  1085  		c.Stdout = os.Stdout
  1086  	}
  1087  	return c.Run()
  1088  }
  1089  
  1090  // isFirstBoot returns a bool reflecting if the machine has been booted before
  1091  func (m *MacMachine) isFirstBoot() (bool, error) {
  1092  	never, err := time.Parse(time.RFC3339, "0001-01-01T00:00:00Z")
  1093  	if err != nil {
  1094  		return false, err
  1095  	}
  1096  	return m.LastUp == never, nil
  1097  }
  1098  
  1099  func (m *MacMachine) getIgnitionSock() (*define.VMFile, error) {
  1100  	dataDir, err := machine.GetDataDir(machine.AppleHvVirt)
  1101  	if err != nil {
  1102  		return nil, err
  1103  	}
  1104  	if err := os.MkdirAll(dataDir, 0755); err != nil {
  1105  		if !errors.Is(err, os.ErrExist) {
  1106  			return nil, err
  1107  		}
  1108  	}
  1109  	return define.NewMachineFile(filepath.Join(dataDir, ignitionSocketName), nil)
  1110  }
  1111  
  1112  func (m *MacMachine) getRuntimeDir() (string, error) {
  1113  	tmpDir, ok := os.LookupEnv("TMPDIR")
  1114  	if !ok {
  1115  		tmpDir = "/tmp"
  1116  	}
  1117  	rtd := filepath.Join(tmpDir, "podman")
  1118  	logrus.Debugf("creating runtimeDir: %s", rtd)
  1119  	if err := os.MkdirAll(rtd, 0755); err != nil {
  1120  		return "", err
  1121  	}
  1122  
  1123  	return rtd, nil
  1124  }
  1125  
  1126  func (m *MacMachine) userGlobalSocketLink() (string, error) {
  1127  	path, err := machine.GetDataDir(machine.AppleHvVirt)
  1128  	if err != nil {
  1129  		logrus.Errorf("Resolving data dir: %s", err.Error())
  1130  		return "", err
  1131  	}
  1132  	// User global socket is located in parent directory of machine dirs (one per user)
  1133  	return filepath.Join(filepath.Dir(path), "podman.sock"), err
  1134  }
  1135  
  1136  func (m *MacMachine) isIncompatible() bool {
  1137  	return m.UID == -1
  1138  }
  1139  
  1140  func generateSystemDFilesForVirtiofsMounts(mounts []machine.VirtIoFs) []machine.Unit {
  1141  	// mounting in fcos with virtiofs is a bit of a dance.  we need a unit file for the mount, a unit file
  1142  	// for automatic mounting on boot, and a "preparatory" service file that disables FCOS security, performs
  1143  	// the mkdir of the mount point, and then re-enables security.  This must be done for each mount.
  1144  
  1145  	var unitFiles []machine.Unit
  1146  	for _, mnt := range mounts {
  1147  		// Here we are looping the mounts and for each mount, we are adding two unit files
  1148  		// for virtiofs.  One unit file is the mount itself and the second is to automount it
  1149  		// on boot.
  1150  		autoMountUnit := `[Automount]
  1151  Where=%s
  1152  [Install]
  1153  WantedBy=multi-user.target
  1154  
  1155  [Unit]
  1156  Description=Mount virtiofs volume %s
  1157  `
  1158  		mountUnit := `[Mount]
  1159  What=%s
  1160  Where=%s
  1161  Type=virtiofs
  1162  
  1163  [Install]
  1164  WantedBy=multi-user.target
  1165  `
  1166  		virtiofsAutomount := machine.Unit{
  1167  			Enabled:  machine.BoolToPtr(true),
  1168  			Name:     fmt.Sprintf("%s.automount", mnt.Tag),
  1169  			Contents: machine.StrToPtr(fmt.Sprintf(autoMountUnit, mnt.Target, mnt.Target)),
  1170  		}
  1171  		virtiofsMount := machine.Unit{
  1172  			Enabled:  machine.BoolToPtr(true),
  1173  			Name:     fmt.Sprintf("%s.mount", mnt.Tag),
  1174  			Contents: machine.StrToPtr(fmt.Sprintf(mountUnit, mnt.Tag, mnt.Target)),
  1175  		}
  1176  
  1177  		// This "unit" simulates something like systemctl enable virtiofs-mount-prepare@
  1178  		enablePrep := machine.Unit{
  1179  			Enabled: machine.BoolToPtr(true),
  1180  			Name:    fmt.Sprintf("virtiofs-mount-prepare@%s.service", mnt.Tag),
  1181  		}
  1182  
  1183  		unitFiles = append(unitFiles, virtiofsAutomount, virtiofsMount, enablePrep)
  1184  	}
  1185  
  1186  	// mount prep is a way to workaround the FCOS limitation of creating directories
  1187  	// at the rootfs / and then mounting to them.
  1188  	mountPrep := `
  1189  [Unit]
  1190  Description=Allow virtios to mount to /
  1191  DefaultDependencies=no
  1192  ConditionPathExists=!%f
  1193  
  1194  [Service]
  1195  Type=oneshot
  1196  ExecStartPre=chattr -i /
  1197  ExecStart=mkdir -p '%f'
  1198  ExecStopPost=chattr +i /
  1199  
  1200  [Install]
  1201  WantedBy=remote-fs.target
  1202  `
  1203  	virtioFSChattr := machine.Unit{
  1204  		Contents: machine.StrToPtr(mountPrep),
  1205  		Name:     "virtiofs-mount-prepare@.service",
  1206  	}
  1207  	unitFiles = append(unitFiles, virtioFSChattr)
  1208  
  1209  	return unitFiles
  1210  }