github.com/hanks177/podman/v4@v4.1.3-0.20220613032544-16d90015bc83/pkg/machine/qemu/machine.go (about)

     1  //go:build (amd64 && !windows) || (arm64 && !windows)
     2  // +build amd64,!windows arm64,!windows
     3  
     4  package qemu
     5  
     6  import (
     7  	"bufio"
     8  	"context"
     9  	"encoding/base64"
    10  	"encoding/json"
    11  	"fmt"
    12  	"io/fs"
    13  	"io/ioutil"
    14  	"net"
    15  	"net/http"
    16  	"net/url"
    17  	"os"
    18  	"os/exec"
    19  	"path/filepath"
    20  	"strconv"
    21  	"strings"
    22  	"time"
    23  
    24  	"github.com/containers/common/pkg/config"
    25  	"github.com/hanks177/podman/v4/pkg/machine"
    26  	"github.com/hanks177/podman/v4/pkg/rootless"
    27  	"github.com/hanks177/podman/v4/utils"
    28  	"github.com/containers/storage/pkg/homedir"
    29  	"github.com/digitalocean/go-qemu/qmp"
    30  	"github.com/docker/go-units"
    31  	"github.com/pkg/errors"
    32  	"github.com/sirupsen/logrus"
    33  )
    34  
    35  var (
    36  	qemuProvider = &Provider{}
    37  	// vmtype refers to qemu (vs libvirt, krun, etc).
    38  	vmtype = "qemu"
    39  )
    40  
    41  func GetQemuProvider() machine.Provider {
    42  	return qemuProvider
    43  }
    44  
    45  const (
    46  	VolumeTypeVirtfs     = "virtfs"
    47  	MountType9p          = "9p"
    48  	dockerSock           = "/var/run/docker.sock"
    49  	dockerConnectTimeout = 5 * time.Second
    50  	apiUpTimeout         = 20 * time.Second
    51  )
    52  
    53  type apiForwardingState int
    54  
    55  const (
    56  	noForwarding apiForwardingState = iota
    57  	claimUnsupported
    58  	notInstalled
    59  	machineLocal
    60  	dockerGlobal
    61  )
    62  
    63  // NewMachine initializes an instance of a virtual machine based on the qemu
    64  // virtualization.
    65  func (p *Provider) NewMachine(opts machine.InitOptions) (machine.VM, error) {
    66  	vmConfigDir, err := machine.GetConfDir(vmtype)
    67  	if err != nil {
    68  		return nil, err
    69  	}
    70  	vm := new(MachineVM)
    71  	if len(opts.Name) > 0 {
    72  		vm.Name = opts.Name
    73  	}
    74  	ignitionFile, err := machine.NewMachineFile(filepath.Join(vmConfigDir, vm.Name+".ign"), nil)
    75  	if err != nil {
    76  		return nil, err
    77  	}
    78  	vm.IgnitionFile = *ignitionFile
    79  	imagePath, err := machine.NewMachineFile(opts.ImagePath, nil)
    80  	if err != nil {
    81  		return nil, err
    82  	}
    83  	vm.ImagePath = *imagePath
    84  	vm.RemoteUsername = opts.Username
    85  
    86  	// Add a random port for ssh
    87  	port, err := utils.GetRandomPort()
    88  	if err != nil {
    89  		return nil, err
    90  	}
    91  	vm.Port = port
    92  
    93  	vm.CPUs = opts.CPUS
    94  	vm.Memory = opts.Memory
    95  	vm.DiskSize = opts.DiskSize
    96  
    97  	vm.Created = time.Now()
    98  
    99  	// Find the qemu executable
   100  	cfg, err := config.Default()
   101  	if err != nil {
   102  		return nil, err
   103  	}
   104  	execPath, err := cfg.FindHelperBinary(QemuCommand, true)
   105  	if err != nil {
   106  		return nil, err
   107  	}
   108  	cmd := []string{execPath}
   109  	// Add memory
   110  	cmd = append(cmd, []string{"-m", strconv.Itoa(int(vm.Memory))}...)
   111  	// Add cpus
   112  	cmd = append(cmd, []string{"-smp", strconv.Itoa(int(vm.CPUs))}...)
   113  	// Add ignition file
   114  	cmd = append(cmd, []string{"-fw_cfg", "name=opt/com.coreos/config,file=" + vm.IgnitionFile.GetPath()}...)
   115  	// Add qmp socket
   116  	monitor, err := NewQMPMonitor("unix", vm.Name, defaultQMPTimeout)
   117  	if err != nil {
   118  		return nil, err
   119  	}
   120  	vm.QMPMonitor = monitor
   121  	cmd = append(cmd, []string{"-qmp", monitor.Network + ":/" + monitor.Address.GetPath() + ",server=on,wait=off"}...)
   122  
   123  	// Add network
   124  	// Right now the mac address is hardcoded so that the host networking gives it a specific IP address.  This is
   125  	// why we can only run one vm at a time right now
   126  	cmd = append(cmd, []string{"-netdev", "socket,id=vlan,fd=3", "-device", "virtio-net-pci,netdev=vlan,mac=5a:94:ef:e4:0c:ee"}...)
   127  	if err := vm.setReadySocket(); err != nil {
   128  		return nil, err
   129  	}
   130  
   131  	// Add serial port for readiness
   132  	cmd = append(cmd, []string{
   133  		"-device", "virtio-serial",
   134  		// qemu needs to establish the long name; other connections can use the symlink'd
   135  		"-chardev", "socket,path=" + vm.ReadySocket.Path + ",server=on,wait=off,id=" + vm.Name + "_ready",
   136  		"-device", "virtserialport,chardev=" + vm.Name + "_ready" + ",name=org.fedoraproject.port.0"}...)
   137  	vm.CmdLine = cmd
   138  	if err := vm.setPIDSocket(); err != nil {
   139  		return nil, err
   140  	}
   141  	return vm, nil
   142  }
   143  
   144  // migrateVM takes the old configuration structure and migrates it
   145  // to the new structure and writes it to the filesystem
   146  func migrateVM(configPath string, config []byte, vm *MachineVM) error {
   147  	fmt.Printf("Migrating machine %q\n", vm.Name)
   148  	var old MachineVMV1
   149  	err := json.Unmarshal(config, &old)
   150  	if err != nil {
   151  		return err
   152  	}
   153  	// Looks like we loaded the older structure; now we need to migrate
   154  	// from the old structure to the new structure
   155  	_, pidFile, err := vm.getSocketandPid()
   156  	if err != nil {
   157  		return err
   158  	}
   159  
   160  	pidFilePath := machine.VMFile{Path: pidFile}
   161  	qmpMonitor := Monitor{
   162  		Address: machine.VMFile{Path: old.QMPMonitor.Address},
   163  		Network: old.QMPMonitor.Network,
   164  		Timeout: old.QMPMonitor.Timeout,
   165  	}
   166  	socketPath, err := getRuntimeDir()
   167  	if err != nil {
   168  		return err
   169  	}
   170  	virtualSocketPath := filepath.Join(socketPath, "podman", vm.Name+"_ready.sock")
   171  	readySocket := machine.VMFile{Path: virtualSocketPath}
   172  
   173  	vm.HostUser = machine.HostUser{}
   174  	vm.ImageConfig = machine.ImageConfig{}
   175  	vm.ResourceConfig = machine.ResourceConfig{}
   176  	vm.SSHConfig = machine.SSHConfig{}
   177  
   178  	ignitionFilePath, err := machine.NewMachineFile(old.IgnitionFilePath, nil)
   179  	if err != nil {
   180  		return err
   181  	}
   182  	imagePath, err := machine.NewMachineFile(old.ImagePath, nil)
   183  	if err != nil {
   184  		return err
   185  	}
   186  
   187  	// setReadySocket will stick the entry into the new struct
   188  	if err := vm.setReadySocket(); err != nil {
   189  		return err
   190  	}
   191  
   192  	vm.CPUs = old.CPUs
   193  	vm.CmdLine = old.CmdLine
   194  	vm.DiskSize = old.DiskSize
   195  	vm.IdentityPath = old.IdentityPath
   196  	vm.IgnitionFile = *ignitionFilePath
   197  	vm.ImagePath = *imagePath
   198  	vm.ImageStream = old.ImageStream
   199  	vm.Memory = old.Memory
   200  	vm.Mounts = old.Mounts
   201  	vm.Name = old.Name
   202  	vm.PidFilePath = pidFilePath
   203  	vm.Port = old.Port
   204  	vm.QMPMonitor = qmpMonitor
   205  	vm.ReadySocket = readySocket
   206  	vm.RemoteUsername = old.RemoteUsername
   207  	vm.Rootful = old.Rootful
   208  	vm.UID = old.UID
   209  
   210  	// Backup the original config file
   211  	if err := os.Rename(configPath, configPath+".orig"); err != nil {
   212  		return err
   213  	}
   214  	// Write the config file
   215  	if err := vm.writeConfig(); err != nil {
   216  		// If the config file fails to be written, put the origina
   217  		// config file back before erroring
   218  		if renameError := os.Rename(configPath+".orig", configPath); renameError != nil {
   219  			logrus.Warn(renameError)
   220  		}
   221  		return err
   222  	}
   223  	// Remove the backup file
   224  	return os.Remove(configPath + ".orig")
   225  }
   226  
   227  // LoadVMByName reads a json file that describes a known qemu vm
   228  // and returns a vm instance
   229  func (p *Provider) LoadVMByName(name string) (machine.VM, error) {
   230  	vm := &MachineVM{Name: name}
   231  	vm.HostUser = machine.HostUser{UID: -1} // posix reserves -1, so use it to signify undefined
   232  	if err := vm.update(); err != nil {
   233  		return nil, err
   234  	}
   235  
   236  	// It is here for providing the ability to propagate
   237  	// proxy settings (e.g. HTTP_PROXY and others) on a start
   238  	// and avoid a need of re-creating/re-initiating a VM
   239  	if proxyOpts := machine.GetProxyVariables(); len(proxyOpts) > 0 {
   240  		proxyStr := "name=opt/com.coreos/environment,string="
   241  		var proxies string
   242  		for k, v := range proxyOpts {
   243  			proxies = fmt.Sprintf("%s%s=\"%s\"|", proxies, k, v)
   244  		}
   245  		proxyStr = fmt.Sprintf("%s%s", proxyStr, base64.StdEncoding.EncodeToString([]byte(proxies)))
   246  		vm.CmdLine = append(vm.CmdLine, "-fw_cfg", proxyStr)
   247  	}
   248  
   249  	logrus.Debug(vm.CmdLine)
   250  	return vm, nil
   251  }
   252  
   253  // Init writes the json configuration file to the filesystem for
   254  // other verbs (start, stop)
   255  func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
   256  	var (
   257  		key string
   258  	)
   259  	sshDir := filepath.Join(homedir.Get(), ".ssh")
   260  	v.IdentityPath = filepath.Join(sshDir, v.Name)
   261  	v.Rootful = opts.Rootful
   262  
   263  	switch opts.ImagePath {
   264  	case Testing, Next, Stable, "":
   265  		// Get image as usual
   266  		v.ImageStream = opts.ImagePath
   267  		dd, err := machine.NewFcosDownloader(vmtype, v.Name, opts.ImagePath)
   268  
   269  		if err != nil {
   270  			return false, err
   271  		}
   272  		uncompressedFile, err := machine.NewMachineFile(dd.Get().LocalUncompressedFile, nil)
   273  		if err != nil {
   274  			return false, err
   275  		}
   276  		v.ImagePath = *uncompressedFile
   277  		if err := machine.DownloadImage(dd); err != nil {
   278  			return false, err
   279  		}
   280  	default:
   281  		// The user has provided an alternate image which can be a file path
   282  		// or URL.
   283  		v.ImageStream = "custom"
   284  		g, err := machine.NewGenericDownloader(vmtype, v.Name, opts.ImagePath)
   285  		if err != nil {
   286  			return false, err
   287  		}
   288  		imagePath, err := machine.NewMachineFile(g.Get().LocalUncompressedFile, nil)
   289  		if err != nil {
   290  			return false, err
   291  		}
   292  		v.ImagePath = *imagePath
   293  		if err := machine.DownloadImage(g); err != nil {
   294  			return false, err
   295  		}
   296  	}
   297  	// Add arch specific options including image location
   298  	v.CmdLine = append(v.CmdLine, v.addArchOptions()...)
   299  	var volumeType string
   300  	switch opts.VolumeDriver {
   301  	case "virtfs":
   302  		volumeType = VolumeTypeVirtfs
   303  	case "": // default driver
   304  		volumeType = VolumeTypeVirtfs
   305  	default:
   306  		err := fmt.Errorf("unknown volume driver: %s", opts.VolumeDriver)
   307  		return false, err
   308  	}
   309  
   310  	mounts := []machine.Mount{}
   311  	for i, volume := range opts.Volumes {
   312  		tag := fmt.Sprintf("vol%d", i)
   313  		paths := strings.SplitN(volume, ":", 3)
   314  		source := paths[0]
   315  		target := source
   316  		readonly := false
   317  		if len(paths) > 1 {
   318  			target = paths[1]
   319  		}
   320  		if len(paths) > 2 {
   321  			options := paths[2]
   322  			volopts := strings.Split(options, ",")
   323  			for _, o := range volopts {
   324  				switch o {
   325  				case "rw":
   326  					readonly = false
   327  				case "ro":
   328  					readonly = true
   329  				default:
   330  					fmt.Printf("Unknown option: %s\n", o)
   331  				}
   332  			}
   333  		}
   334  		if volumeType == VolumeTypeVirtfs {
   335  			virtfsOptions := fmt.Sprintf("local,path=%s,mount_tag=%s,security_model=mapped-xattr", source, tag)
   336  			if readonly {
   337  				virtfsOptions += ",readonly"
   338  			}
   339  			v.CmdLine = append(v.CmdLine, []string{"-virtfs", virtfsOptions}...)
   340  			mounts = append(mounts, machine.Mount{Type: MountType9p, Tag: tag, Source: source, Target: target, ReadOnly: readonly})
   341  		}
   342  	}
   343  	v.Mounts = mounts
   344  	v.UID = os.Getuid()
   345  
   346  	// Add location of bootable image
   347  	v.CmdLine = append(v.CmdLine, "-drive", "if=virtio,file="+v.getImageFile())
   348  	// This kind of stinks but no other way around this r/n
   349  	if len(opts.IgnitionPath) < 1 {
   350  		uri := machine.SSHRemoteConnection.MakeSSHURL("localhost", fmt.Sprintf("/run/user/%d/podman/podman.sock", v.UID), strconv.Itoa(v.Port), v.RemoteUsername)
   351  		uriRoot := machine.SSHRemoteConnection.MakeSSHURL("localhost", "/run/podman/podman.sock", strconv.Itoa(v.Port), "root")
   352  		identity := filepath.Join(sshDir, v.Name)
   353  
   354  		uris := []url.URL{uri, uriRoot}
   355  		names := []string{v.Name, v.Name + "-root"}
   356  
   357  		// The first connection defined when connections is empty will become the default
   358  		// regardless of IsDefault, so order according to rootful
   359  		if opts.Rootful {
   360  			uris[0], names[0], uris[1], names[1] = uris[1], names[1], uris[0], names[0]
   361  		}
   362  
   363  		for i := 0; i < 2; i++ {
   364  			if err := machine.AddConnection(&uris[i], names[i], identity, opts.IsDefault && i == 0); err != nil {
   365  				return false, err
   366  			}
   367  		}
   368  	} else {
   369  		fmt.Println("An ignition path was provided.  No SSH connection was added to Podman")
   370  	}
   371  	// Write the JSON file
   372  	if err := v.writeConfig(); err != nil {
   373  		return false, fmt.Errorf("writing JSON file: %w", err)
   374  	}
   375  	// User has provided ignition file so keygen
   376  	// will be skipped.
   377  	if len(opts.IgnitionPath) < 1 {
   378  		var err error
   379  		key, err = machine.CreateSSHKeys(v.IdentityPath)
   380  		if err != nil {
   381  			return false, err
   382  		}
   383  	}
   384  	// Run arch specific things that need to be done
   385  	if err := v.prepare(); err != nil {
   386  		return false, err
   387  	}
   388  	originalDiskSize, err := getDiskSize(v.getImageFile())
   389  	if err != nil {
   390  		return false, err
   391  	}
   392  
   393  	if err := v.resizeDisk(opts.DiskSize, originalDiskSize>>(10*3)); err != nil {
   394  		return false, err
   395  	}
   396  	// If the user provides an ignition file, we need to
   397  	// copy it into the conf dir
   398  	if len(opts.IgnitionPath) > 0 {
   399  		inputIgnition, err := ioutil.ReadFile(opts.IgnitionPath)
   400  		if err != nil {
   401  			return false, err
   402  		}
   403  		return false, ioutil.WriteFile(v.getIgnitionFile(), inputIgnition, 0644)
   404  	}
   405  	// Write the ignition file
   406  	ign := machine.DynamicIgnition{
   407  		Name:      opts.Username,
   408  		Key:       key,
   409  		VMName:    v.Name,
   410  		TimeZone:  opts.TimeZone,
   411  		WritePath: v.getIgnitionFile(),
   412  		UID:       v.UID,
   413  	}
   414  	err = machine.NewIgnitionFile(ign)
   415  	return err == nil, err
   416  }
   417  
   418  func (v *MachineVM) Set(_ string, opts machine.SetOptions) ([]error, error) {
   419  	// If one setting fails to be applied, the others settings will not fail and still be applied.
   420  	// The setting(s) that failed to be applied will have its errors returned in setErrors
   421  	var setErrors []error
   422  
   423  	state, err := v.State(false)
   424  	if err != nil {
   425  		return setErrors, err
   426  	}
   427  
   428  	if state == machine.Running {
   429  		suffix := ""
   430  		if v.Name != machine.DefaultMachineName {
   431  			suffix = " " + v.Name
   432  		}
   433  		return setErrors, errors.Errorf("cannot change settings while the vm is running, run 'podman machine stop%s' first", suffix)
   434  	}
   435  
   436  	if opts.Rootful != nil && v.Rootful != *opts.Rootful {
   437  		if err := v.setRootful(*opts.Rootful); err != nil {
   438  			setErrors = append(setErrors, errors.Wrapf(err, "failed to set rootful option"))
   439  		} else {
   440  			v.Rootful = *opts.Rootful
   441  		}
   442  	}
   443  
   444  	if opts.CPUs != nil && v.CPUs != *opts.CPUs {
   445  		v.CPUs = *opts.CPUs
   446  		v.editCmdLine("-smp", strconv.Itoa(int(v.CPUs)))
   447  	}
   448  
   449  	if opts.Memory != nil && v.Memory != *opts.Memory {
   450  		v.Memory = *opts.Memory
   451  		v.editCmdLine("-m", strconv.Itoa(int(v.Memory)))
   452  	}
   453  
   454  	if opts.DiskSize != nil && v.DiskSize != *opts.DiskSize {
   455  		if err := v.resizeDisk(*opts.DiskSize, v.DiskSize); err != nil {
   456  			setErrors = append(setErrors, errors.Wrapf(err, "failed to resize disk"))
   457  		} else {
   458  			v.DiskSize = *opts.DiskSize
   459  		}
   460  	}
   461  
   462  	err = v.writeConfig()
   463  	if err != nil {
   464  		setErrors = append(setErrors, err)
   465  	}
   466  
   467  	if len(setErrors) > 0 {
   468  		return setErrors, setErrors[0]
   469  	}
   470  
   471  	return setErrors, nil
   472  }
   473  
   474  // Start executes the qemu command line and forks it
   475  func (v *MachineVM) Start(name string, _ machine.StartOptions) error {
   476  	var (
   477  		conn           net.Conn
   478  		err            error
   479  		qemuSocketConn net.Conn
   480  		wait           = time.Millisecond * 500
   481  	)
   482  
   483  	v.Starting = true
   484  	if err := v.writeConfig(); err != nil {
   485  		return fmt.Errorf("writing JSON file: %w", err)
   486  	}
   487  	defer func() {
   488  		v.Starting = false
   489  		if err := v.writeConfig(); err != nil {
   490  			logrus.Errorf("Writing JSON file: %v", err)
   491  		}
   492  	}()
   493  	if v.isIncompatible() {
   494  		logrus.Errorf("machine %q is incompatible with this release of podman and needs to be recreated, starting for recovery only", v.Name)
   495  	}
   496  
   497  	forwardSock, forwardState, err := v.startHostNetworking()
   498  	if err != nil {
   499  		return errors.Errorf("unable to start host networking: %q", err)
   500  	}
   501  
   502  	rtPath, err := getRuntimeDir()
   503  	if err != nil {
   504  		return err
   505  	}
   506  
   507  	// If the temporary podman dir is not created, create it
   508  	podmanTempDir := filepath.Join(rtPath, "podman")
   509  	if _, err := os.Stat(podmanTempDir); os.IsNotExist(err) {
   510  		if mkdirErr := os.MkdirAll(podmanTempDir, 0755); mkdirErr != nil {
   511  			return err
   512  		}
   513  	}
   514  
   515  	// If the qemusocketpath exists and the vm is off/down, we should rm
   516  	// it before the dial as to avoid a segv
   517  	if err := v.QMPMonitor.Address.Delete(); err != nil {
   518  		return err
   519  	}
   520  	for i := 0; i < 6; i++ {
   521  		qemuSocketConn, err = net.Dial("unix", v.QMPMonitor.Address.GetPath())
   522  		if err == nil {
   523  			break
   524  		}
   525  		time.Sleep(wait)
   526  		wait++
   527  	}
   528  	if err != nil {
   529  		return err
   530  	}
   531  	defer qemuSocketConn.Close()
   532  
   533  	fd, err := qemuSocketConn.(*net.UnixConn).File()
   534  	if err != nil {
   535  		return err
   536  	}
   537  	defer fd.Close()
   538  	dnr, err := os.OpenFile("/dev/null", os.O_RDONLY, 0755)
   539  	if err != nil {
   540  		return err
   541  	}
   542  	defer dnr.Close()
   543  	dnw, err := os.OpenFile("/dev/null", os.O_WRONLY, 0755)
   544  	if err != nil {
   545  		return err
   546  	}
   547  	defer dnw.Close()
   548  
   549  	attr := new(os.ProcAttr)
   550  	files := []*os.File{dnr, dnw, dnw, fd}
   551  	attr.Files = files
   552  	logrus.Debug(v.CmdLine)
   553  	cmd := v.CmdLine
   554  
   555  	// Disable graphic window when not in debug mode
   556  	// Done in start, so we're not suck with the debug level we used on init
   557  	if !logrus.IsLevelEnabled(logrus.DebugLevel) {
   558  		cmd = append(cmd, "-display", "none")
   559  	}
   560  
   561  	_, err = os.StartProcess(v.CmdLine[0], cmd, attr)
   562  	if err != nil {
   563  		// check if qemu was not found
   564  		if !errors.Is(err, os.ErrNotExist) {
   565  			return err
   566  		}
   567  		// lookup qemu again maybe the path was changed, https://github.com/containers/podman/issues/13394
   568  		cfg, err := config.Default()
   569  		if err != nil {
   570  			return err
   571  		}
   572  		cmd[0], err = cfg.FindHelperBinary(QemuCommand, true)
   573  		if err != nil {
   574  			return err
   575  		}
   576  		_, err = os.StartProcess(cmd[0], cmd, attr)
   577  		if err != nil {
   578  			return errors.Wrapf(err, "unable to execute %q", cmd)
   579  		}
   580  	}
   581  	fmt.Println("Waiting for VM ...")
   582  	socketPath, err := getRuntimeDir()
   583  	if err != nil {
   584  		return err
   585  	}
   586  
   587  	// The socket is not made until the qemu process is running so here
   588  	// we do a backoff waiting for it.  Once we have a conn, we break and
   589  	// then wait to read it.
   590  	for i := 0; i < 6; i++ {
   591  		conn, err = net.Dial("unix", filepath.Join(socketPath, "podman", v.Name+"_ready.sock"))
   592  		if err == nil {
   593  			break
   594  		}
   595  		time.Sleep(wait)
   596  		wait++
   597  	}
   598  	if err != nil {
   599  		return err
   600  	}
   601  	defer conn.Close()
   602  	_, err = bufio.NewReader(conn).ReadString('\n')
   603  	if err != nil {
   604  		return err
   605  	}
   606  	if len(v.Mounts) > 0 {
   607  		state, err := v.State(true)
   608  		if err != nil {
   609  			return err
   610  		}
   611  		listening := v.isListening()
   612  		for state != machine.Running || !listening {
   613  			time.Sleep(100 * time.Millisecond)
   614  			state, err = v.State(true)
   615  			if err != nil {
   616  				return err
   617  			}
   618  			listening = v.isListening()
   619  		}
   620  	}
   621  	for _, mount := range v.Mounts {
   622  		fmt.Printf("Mounting volume... %s:%s\n", mount.Source, mount.Target)
   623  		// create mountpoint directory if it doesn't exist
   624  		// because / is immutable, we have to monkey around with permissions
   625  		// if we dont mount in /home or /mnt
   626  		args := []string{"-q", "--"}
   627  		if !strings.HasPrefix(mount.Target, "/home") || !strings.HasPrefix(mount.Target, "/mnt") {
   628  			args = append(args, "sudo", "chattr", "-i", "/", ";")
   629  		}
   630  		args = append(args, "sudo", "mkdir", "-p", mount.Target)
   631  		if !strings.HasPrefix(mount.Target, "/home") || !strings.HasPrefix(mount.Target, "/mnt") {
   632  			args = append(args, ";", "sudo", "chattr", "+i", "/", ";")
   633  		}
   634  		err = v.SSH(name, machine.SSHOptions{Args: args})
   635  		if err != nil {
   636  			return err
   637  		}
   638  		switch mount.Type {
   639  		case MountType9p:
   640  			mountOptions := []string{"-t", "9p"}
   641  			mountOptions = append(mountOptions, []string{"-o", "trans=virtio", mount.Tag, mount.Target}...)
   642  			mountOptions = append(mountOptions, []string{"-o", "version=9p2000.L,msize=131072"}...)
   643  			if mount.ReadOnly {
   644  				mountOptions = append(mountOptions, []string{"-o", "ro"}...)
   645  			}
   646  			err = v.SSH(name, machine.SSHOptions{Args: append([]string{"-q", "--", "sudo", "mount"}, mountOptions...)})
   647  			if err != nil {
   648  				return err
   649  			}
   650  		default:
   651  			return fmt.Errorf("unknown mount type: %s", mount.Type)
   652  		}
   653  	}
   654  
   655  	v.waitAPIAndPrintInfo(forwardState, forwardSock)
   656  	return nil
   657  }
   658  
   659  func (v *MachineVM) checkStatus(monitor *qmp.SocketMonitor) (machine.Status, error) {
   660  	// this is the format returned from the monitor
   661  	// {"return": {"status": "running", "singlestep": false, "running": true}}
   662  
   663  	type statusDetails struct {
   664  		Status   string `json:"status"`
   665  		Step     bool   `json:"singlestep"`
   666  		Running  bool   `json:"running"`
   667  		Starting bool   `json:"starting"`
   668  	}
   669  	type statusResponse struct {
   670  		Response statusDetails `json:"return"`
   671  	}
   672  	var response statusResponse
   673  
   674  	checkCommand := struct {
   675  		Execute string `json:"execute"`
   676  	}{
   677  		Execute: "query-status",
   678  	}
   679  	input, err := json.Marshal(checkCommand)
   680  	if err != nil {
   681  		return "", err
   682  	}
   683  	b, err := monitor.Run(input)
   684  	if err != nil {
   685  		if errors.Cause(err) == os.ErrNotExist {
   686  			return machine.Stopped, nil
   687  		}
   688  		return "", err
   689  	}
   690  	if err := json.Unmarshal(b, &response); err != nil {
   691  		return "", err
   692  	}
   693  	if response.Response.Status == machine.Running {
   694  		return machine.Running, nil
   695  	}
   696  	return machine.Stopped, nil
   697  }
   698  
   699  // Stop uses the qmp monitor to call a system_powerdown
   700  func (v *MachineVM) Stop(_ string, _ machine.StopOptions) error {
   701  	var disconnected bool
   702  	// check if the qmp socket is there. if not, qemu instance is gone
   703  	if _, err := os.Stat(v.QMPMonitor.Address.GetPath()); os.IsNotExist(err) {
   704  		// Right now it is NOT an error to stop a stopped machine
   705  		logrus.Debugf("QMP monitor socket %v does not exist", v.QMPMonitor.Address)
   706  		return nil
   707  	}
   708  	qmpMonitor, err := qmp.NewSocketMonitor(v.QMPMonitor.Network, v.QMPMonitor.Address.GetPath(), v.QMPMonitor.Timeout)
   709  	if err != nil {
   710  		return err
   711  	}
   712  	// Simple JSON formation for the QAPI
   713  	stopCommand := struct {
   714  		Execute string `json:"execute"`
   715  	}{
   716  		Execute: "system_powerdown",
   717  	}
   718  	input, err := json.Marshal(stopCommand)
   719  	if err != nil {
   720  		return err
   721  	}
   722  	if err := qmpMonitor.Connect(); err != nil {
   723  		return err
   724  	}
   725  	defer func() {
   726  		if !disconnected {
   727  			if err := qmpMonitor.Disconnect(); err != nil {
   728  				logrus.Error(err)
   729  			}
   730  		}
   731  	}()
   732  
   733  	if _, err = qmpMonitor.Run(input); err != nil {
   734  		return err
   735  	}
   736  
   737  	if _, err := os.Stat(v.PidFilePath.GetPath()); os.IsNotExist(err) {
   738  		return nil
   739  	}
   740  	pidString, err := v.PidFilePath.Read()
   741  	if err != nil {
   742  		return err
   743  	}
   744  	pidNum, err := strconv.Atoi(string(pidString))
   745  	if err != nil {
   746  		return err
   747  	}
   748  
   749  	p, err := os.FindProcess(pidNum)
   750  	if p == nil && err != nil {
   751  		return err
   752  	}
   753  
   754  	v.LastUp = time.Now()
   755  	if err := v.writeConfig(); err != nil { // keep track of last up
   756  		return err
   757  	}
   758  	// Kill the process
   759  	if err := p.Kill(); err != nil {
   760  		return err
   761  	}
   762  	// Remove the pidfile
   763  	if err := v.PidFilePath.Delete(); err != nil {
   764  		return err
   765  	}
   766  	// Remove socket
   767  	if err := v.QMPMonitor.Address.Delete(); err != nil {
   768  		return err
   769  	}
   770  
   771  	if err := qmpMonitor.Disconnect(); err != nil {
   772  		// FIXME: this error should probably be returned
   773  		return nil // nolint: nilerr
   774  	}
   775  
   776  	disconnected = true
   777  	waitInternal := 250 * time.Millisecond
   778  	for i := 0; i < 5; i++ {
   779  		state, err := v.State(false)
   780  		if err != nil {
   781  			return err
   782  		}
   783  		if state != machine.Running {
   784  			break
   785  		}
   786  		time.Sleep(waitInternal)
   787  		waitInternal *= 2
   788  	}
   789  
   790  	return v.ReadySocket.Delete()
   791  }
   792  
   793  // NewQMPMonitor creates the monitor subsection of our vm
   794  func NewQMPMonitor(network, name string, timeout time.Duration) (Monitor, error) {
   795  	rtDir, err := getRuntimeDir()
   796  	if err != nil {
   797  		return Monitor{}, err
   798  	}
   799  	if !rootless.IsRootless() {
   800  		rtDir = "/run"
   801  	}
   802  	rtDir = filepath.Join(rtDir, "podman")
   803  	if _, err := os.Stat(rtDir); os.IsNotExist(err) {
   804  		if err := os.MkdirAll(rtDir, 0755); err != nil {
   805  			return Monitor{}, err
   806  		}
   807  	}
   808  	if timeout == 0 {
   809  		timeout = defaultQMPTimeout
   810  	}
   811  	address, err := machine.NewMachineFile(filepath.Join(rtDir, "qmp_"+name+".sock"), nil)
   812  	if err != nil {
   813  		return Monitor{}, err
   814  	}
   815  	monitor := Monitor{
   816  		Network: network,
   817  		Address: *address,
   818  		Timeout: timeout,
   819  	}
   820  	return monitor, nil
   821  }
   822  
   823  // Remove deletes all the files associated with a machine including ssh keys, the image itself
   824  func (v *MachineVM) Remove(_ string, opts machine.RemoveOptions) (string, func() error, error) {
   825  	var (
   826  		files []string
   827  	)
   828  
   829  	// cannot remove a running vm unless --force is used
   830  	state, err := v.State(false)
   831  	if err != nil {
   832  		return "", nil, err
   833  	}
   834  	if state == machine.Running {
   835  		if !opts.Force {
   836  			return "", nil, errors.Errorf("running vm %q cannot be destroyed", v.Name)
   837  		}
   838  		err := v.Stop(v.Name, machine.StopOptions{})
   839  		if err != nil {
   840  			return "", nil, err
   841  		}
   842  	}
   843  
   844  	// Collect all the files that need to be destroyed
   845  	if !opts.SaveKeys {
   846  		files = append(files, v.IdentityPath, v.IdentityPath+".pub")
   847  	}
   848  	if !opts.SaveIgnition {
   849  		files = append(files, v.getIgnitionFile())
   850  	}
   851  	if !opts.SaveImage {
   852  		files = append(files, v.getImageFile())
   853  	}
   854  	socketPath, err := v.forwardSocketPath()
   855  	if err != nil {
   856  		return "", nil, err
   857  	}
   858  	if socketPath.Symlink != nil {
   859  		files = append(files, *socketPath.Symlink)
   860  	}
   861  	files = append(files, socketPath.Path)
   862  	files = append(files, v.archRemovalFiles()...)
   863  
   864  	if err := machine.RemoveConnection(v.Name); err != nil {
   865  		logrus.Error(err)
   866  	}
   867  	if err := machine.RemoveConnection(v.Name + "-root"); err != nil {
   868  		logrus.Error(err)
   869  	}
   870  
   871  	vmConfigDir, err := machine.GetConfDir(vmtype)
   872  	if err != nil {
   873  		return "", nil, err
   874  	}
   875  	files = append(files, filepath.Join(vmConfigDir, v.Name+".json"))
   876  	confirmationMessage := "\nThe following files will be deleted:\n\n"
   877  	for _, msg := range files {
   878  		confirmationMessage += msg + "\n"
   879  	}
   880  
   881  	// remove socket and pid file if any: warn at low priority if things fail
   882  	// Remove the pidfile
   883  	if err := v.PidFilePath.Delete(); err != nil {
   884  		logrus.Debugf("Error while removing pidfile: %v", err)
   885  	}
   886  	// Remove socket
   887  	if err := v.QMPMonitor.Address.Delete(); err != nil {
   888  		logrus.Debugf("Error while removing podman-machine-socket: %v", err)
   889  	}
   890  
   891  	confirmationMessage += "\n"
   892  	return confirmationMessage, func() error {
   893  		for _, f := range files {
   894  			if err := os.Remove(f); err != nil && !errors.Is(err, os.ErrNotExist) {
   895  				logrus.Error(err)
   896  			}
   897  		}
   898  		return nil
   899  	}, nil
   900  }
   901  
   902  func (v *MachineVM) State(bypass bool) (machine.Status, error) {
   903  	// Check if qmp socket path exists
   904  	if _, err := os.Stat(v.QMPMonitor.Address.GetPath()); os.IsNotExist(err) {
   905  		return "", nil
   906  	}
   907  	err := v.update()
   908  	if err != nil {
   909  		return "", err
   910  	}
   911  	// Check if we can dial it
   912  	if v.Starting && !bypass {
   913  		return "", nil
   914  	}
   915  	monitor, err := qmp.NewSocketMonitor(v.QMPMonitor.Network, v.QMPMonitor.Address.GetPath(), v.QMPMonitor.Timeout)
   916  	if err != nil {
   917  		// FIXME: this error should probably be returned
   918  		return "", err
   919  	}
   920  	if err := monitor.Connect(); err != nil {
   921  		return "", err
   922  	}
   923  	defer func() {
   924  		if err := monitor.Disconnect(); err != nil {
   925  			logrus.Error(err)
   926  		}
   927  	}()
   928  	// If there is a monitor, lets see if we can query state
   929  	return v.checkStatus(monitor)
   930  }
   931  
   932  func (v *MachineVM) isListening() bool {
   933  	// Check if we can dial it
   934  	conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", "127.0.0.1", v.Port), 10*time.Millisecond)
   935  	if err != nil {
   936  		return false
   937  	}
   938  	conn.Close()
   939  	return true
   940  }
   941  
   942  // SSH opens an interactive SSH session to the vm specified.
   943  // Added ssh function to VM interface: pkg/machine/config/go : line 58
   944  func (v *MachineVM) SSH(_ string, opts machine.SSHOptions) error {
   945  	state, err := v.State(true)
   946  	if err != nil {
   947  		return err
   948  	}
   949  	if state != machine.Running {
   950  		return errors.Errorf("vm %q is not running", v.Name)
   951  	}
   952  
   953  	username := opts.Username
   954  	if username == "" {
   955  		username = v.RemoteUsername
   956  	}
   957  
   958  	sshDestination := username + "@localhost"
   959  	port := strconv.Itoa(v.Port)
   960  
   961  	args := []string{"-i", v.IdentityPath, "-p", port, sshDestination, "-o", "UserKnownHostsFile=/dev/null",
   962  		"-o", "StrictHostKeyChecking=no", "-o", "LogLevel=ERROR"}
   963  	if len(opts.Args) > 0 {
   964  		args = append(args, opts.Args...)
   965  	} else {
   966  		fmt.Printf("Connecting to vm %s. To close connection, use `~.` or `exit`\n", v.Name)
   967  	}
   968  
   969  	cmd := exec.Command("ssh", args...)
   970  	logrus.Debugf("Executing: ssh %v\n", args)
   971  
   972  	cmd.Stdout = os.Stdout
   973  	cmd.Stderr = os.Stderr
   974  	cmd.Stdin = os.Stdin
   975  
   976  	return cmd.Run()
   977  }
   978  
   979  // executes qemu-image info to get the virtual disk size
   980  // of the diskimage
   981  func getDiskSize(path string) (uint64, error) {
   982  	// Find the qemu executable
   983  	cfg, err := config.Default()
   984  	if err != nil {
   985  		return 0, err
   986  	}
   987  	qemuPathDir, err := cfg.FindHelperBinary("qemu-img", true)
   988  	if err != nil {
   989  		return 0, err
   990  	}
   991  	diskInfo := exec.Command(qemuPathDir, "info", "--output", "json", path)
   992  	stdout, err := diskInfo.StdoutPipe()
   993  	if err != nil {
   994  		return 0, err
   995  	}
   996  	if err := diskInfo.Start(); err != nil {
   997  		return 0, err
   998  	}
   999  	tmpInfo := struct {
  1000  		VirtualSize    uint64 `json:"virtual-size"`
  1001  		Filename       string `json:"filename"`
  1002  		ClusterSize    int64  `json:"cluster-size"`
  1003  		Format         string `json:"format"`
  1004  		FormatSpecific struct {
  1005  			Type string            `json:"type"`
  1006  			Data map[string]string `json:"data"`
  1007  		}
  1008  		DirtyFlag bool `json:"dirty-flag"`
  1009  	}{}
  1010  	if err := json.NewDecoder(stdout).Decode(&tmpInfo); err != nil {
  1011  		return 0, err
  1012  	}
  1013  	if err := diskInfo.Wait(); err != nil {
  1014  		return 0, err
  1015  	}
  1016  	return tmpInfo.VirtualSize, nil
  1017  }
  1018  
  1019  // List lists all vm's that use qemu virtualization
  1020  func (p *Provider) List(_ machine.ListOptions) ([]*machine.ListResponse, error) {
  1021  	return getVMInfos()
  1022  }
  1023  
  1024  func getVMInfos() ([]*machine.ListResponse, error) {
  1025  	vmConfigDir, err := machine.GetConfDir(vmtype)
  1026  	if err != nil {
  1027  		return nil, err
  1028  	}
  1029  
  1030  	var listed []*machine.ListResponse
  1031  
  1032  	if err = filepath.WalkDir(vmConfigDir, func(path string, d fs.DirEntry, err error) error {
  1033  		vm := new(MachineVM)
  1034  		if strings.HasSuffix(d.Name(), ".json") {
  1035  			fullPath := filepath.Join(vmConfigDir, d.Name())
  1036  			b, err := ioutil.ReadFile(fullPath)
  1037  			if err != nil {
  1038  				return err
  1039  			}
  1040  			err = json.Unmarshal(b, vm)
  1041  			if err != nil {
  1042  				// Checking if the file did not unmarshal because it is using
  1043  				// the deprecated config file format.
  1044  				migrateErr := migrateVM(fullPath, b, vm)
  1045  				if migrateErr != nil {
  1046  					return migrateErr
  1047  				}
  1048  			}
  1049  			listEntry := new(machine.ListResponse)
  1050  
  1051  			listEntry.Name = vm.Name
  1052  			listEntry.Stream = vm.ImageStream
  1053  			listEntry.VMType = "qemu"
  1054  			listEntry.CPUs = vm.CPUs
  1055  			listEntry.Memory = vm.Memory * units.MiB
  1056  			listEntry.DiskSize = vm.DiskSize * units.GiB
  1057  			listEntry.Port = vm.Port
  1058  			listEntry.RemoteUsername = vm.RemoteUsername
  1059  			listEntry.IdentityPath = vm.IdentityPath
  1060  			listEntry.CreatedAt = vm.Created
  1061  
  1062  			if listEntry.CreatedAt.IsZero() {
  1063  				listEntry.CreatedAt = time.Now()
  1064  				vm.Created = time.Now()
  1065  				if err := vm.writeConfig(); err != nil {
  1066  					return err
  1067  				}
  1068  			}
  1069  
  1070  			state, err := vm.State(false)
  1071  			if err != nil {
  1072  				return err
  1073  			}
  1074  
  1075  			if !vm.LastUp.IsZero() { // this means we have already written a time to the config
  1076  				listEntry.LastUp = vm.LastUp
  1077  			} else { // else we just created the machine AKA last up = created time
  1078  				listEntry.LastUp = vm.Created
  1079  				vm.LastUp = listEntry.LastUp
  1080  				if err := vm.writeConfig(); err != nil {
  1081  					return err
  1082  				}
  1083  			}
  1084  			if state == machine.Running {
  1085  				listEntry.Running = true
  1086  			}
  1087  
  1088  			listed = append(listed, listEntry)
  1089  		}
  1090  		return nil
  1091  	}); err != nil {
  1092  		return nil, err
  1093  	}
  1094  	return listed, err
  1095  }
  1096  
  1097  func (p *Provider) IsValidVMName(name string) (bool, error) {
  1098  	infos, err := getVMInfos()
  1099  	if err != nil {
  1100  		return false, err
  1101  	}
  1102  	for _, vm := range infos {
  1103  		if vm.Name == name {
  1104  			return true, nil
  1105  		}
  1106  	}
  1107  	return false, nil
  1108  }
  1109  
  1110  // CheckExclusiveActiveVM checks if there is a VM already running
  1111  // that does not allow other VMs to be running
  1112  func (p *Provider) CheckExclusiveActiveVM() (bool, string, error) {
  1113  	vms, err := getVMInfos()
  1114  	if err != nil {
  1115  		return false, "", errors.Wrap(err, "error checking VM active")
  1116  	}
  1117  	for _, vm := range vms {
  1118  		if vm.Running {
  1119  			return true, vm.Name, nil
  1120  		}
  1121  	}
  1122  	return false, "", nil
  1123  }
  1124  
  1125  // startHostNetworking runs a binary on the host system that allows users
  1126  // to setup port forwarding to the podman virtual machine
  1127  func (v *MachineVM) startHostNetworking() (string, apiForwardingState, error) {
  1128  	cfg, err := config.Default()
  1129  	if err != nil {
  1130  		return "", noForwarding, err
  1131  	}
  1132  	binary, err := cfg.FindHelperBinary(machine.ForwarderBinaryName, false)
  1133  	if err != nil {
  1134  		return "", noForwarding, err
  1135  	}
  1136  
  1137  	attr := new(os.ProcAttr)
  1138  	dnr, err := os.OpenFile("/dev/null", os.O_RDONLY, 0755)
  1139  	if err != nil {
  1140  		return "", noForwarding, err
  1141  	}
  1142  	dnw, err := os.OpenFile("/dev/null", os.O_WRONLY, 0755)
  1143  	if err != nil {
  1144  		return "", noForwarding, err
  1145  	}
  1146  
  1147  	defer dnr.Close()
  1148  	defer dnw.Close()
  1149  
  1150  	attr.Files = []*os.File{dnr, dnw, dnw}
  1151  	cmd := []string{binary}
  1152  	cmd = append(cmd, []string{"-listen-qemu", fmt.Sprintf("unix://%s", v.QMPMonitor.Address.GetPath()), "-pid-file", v.PidFilePath.GetPath()}...)
  1153  	// Add the ssh port
  1154  	cmd = append(cmd, []string{"-ssh-port", fmt.Sprintf("%d", v.Port)}...)
  1155  
  1156  	var forwardSock string
  1157  	var state apiForwardingState
  1158  	if !v.isIncompatible() {
  1159  		cmd, forwardSock, state = v.setupAPIForwarding(cmd)
  1160  	}
  1161  
  1162  	if logrus.GetLevel() == logrus.DebugLevel {
  1163  		cmd = append(cmd, "--debug")
  1164  		fmt.Println(cmd)
  1165  	}
  1166  	_, err = os.StartProcess(cmd[0], cmd, attr)
  1167  	return forwardSock, state, errors.Wrapf(err, "unable to execute: %q", cmd)
  1168  }
  1169  
  1170  func (v *MachineVM) setupAPIForwarding(cmd []string) ([]string, string, apiForwardingState) {
  1171  	socket, err := v.forwardSocketPath()
  1172  
  1173  	if err != nil {
  1174  		return cmd, "", noForwarding
  1175  	}
  1176  
  1177  	destSock := fmt.Sprintf("/run/user/%d/podman/podman.sock", v.UID)
  1178  	forwardUser := "core"
  1179  
  1180  	if v.Rootful {
  1181  		destSock = "/run/podman/podman.sock"
  1182  		forwardUser = "root"
  1183  	}
  1184  
  1185  	cmd = append(cmd, []string{"-forward-sock", socket.GetPath()}...)
  1186  	cmd = append(cmd, []string{"-forward-dest", destSock}...)
  1187  	cmd = append(cmd, []string{"-forward-user", forwardUser}...)
  1188  	cmd = append(cmd, []string{"-forward-identity", v.IdentityPath}...)
  1189  
  1190  	// The linking pattern is /var/run/docker.sock -> user global sock (link) -> machine sock (socket)
  1191  	// This allows the helper to only have to maintain one constant target to the user, which can be
  1192  	// repositioned without updating docker.sock.
  1193  
  1194  	link, err := v.userGlobalSocketLink()
  1195  	if err != nil {
  1196  		return cmd, socket.GetPath(), machineLocal
  1197  	}
  1198  
  1199  	if !dockerClaimSupported() {
  1200  		return cmd, socket.GetPath(), claimUnsupported
  1201  	}
  1202  
  1203  	if !dockerClaimHelperInstalled() {
  1204  		return cmd, socket.GetPath(), notInstalled
  1205  	}
  1206  
  1207  	if !alreadyLinked(socket.GetPath(), link) {
  1208  		if checkSockInUse(link) {
  1209  			return cmd, socket.GetPath(), machineLocal
  1210  		}
  1211  
  1212  		_ = os.Remove(link)
  1213  		if err = os.Symlink(socket.GetPath(), link); err != nil {
  1214  			logrus.Warnf("could not create user global API forwarding link: %s", err.Error())
  1215  			return cmd, socket.GetPath(), machineLocal
  1216  		}
  1217  	}
  1218  
  1219  	if !alreadyLinked(link, dockerSock) {
  1220  		if checkSockInUse(dockerSock) {
  1221  			return cmd, socket.GetPath(), machineLocal
  1222  		}
  1223  
  1224  		if !claimDockerSock() {
  1225  			logrus.Warn("podman helper is installed, but was not able to claim the global docker sock")
  1226  			return cmd, socket.GetPath(), machineLocal
  1227  		}
  1228  	}
  1229  
  1230  	return cmd, dockerSock, dockerGlobal
  1231  }
  1232  
  1233  func (v *MachineVM) isIncompatible() bool {
  1234  	return v.UID == -1
  1235  }
  1236  
  1237  func (v *MachineVM) userGlobalSocketLink() (string, error) {
  1238  	path, err := machine.GetDataDir(v.Name)
  1239  	if err != nil {
  1240  		logrus.Errorf("Resolving data dir: %s", err.Error())
  1241  		return "", err
  1242  	}
  1243  	// User global socket is located in parent directory of machine dirs (one per user)
  1244  	return filepath.Join(filepath.Dir(path), "podman.sock"), err
  1245  }
  1246  
  1247  func (v *MachineVM) forwardSocketPath() (*machine.VMFile, error) {
  1248  	sockName := "podman.sock"
  1249  	path, err := machine.GetDataDir(v.Name)
  1250  	if err != nil {
  1251  		logrus.Errorf("Resolving data dir: %s", err.Error())
  1252  		return nil, err
  1253  	}
  1254  	return machine.NewMachineFile(filepath.Join(path, sockName), &sockName)
  1255  }
  1256  
  1257  func (v *MachineVM) setConfigPath() error {
  1258  	vmConfigDir, err := machine.GetConfDir(vmtype)
  1259  	if err != nil {
  1260  		return err
  1261  	}
  1262  
  1263  	configPath, err := machine.NewMachineFile(filepath.Join(vmConfigDir, v.Name)+".json", nil)
  1264  	if err != nil {
  1265  		return err
  1266  	}
  1267  	v.ConfigPath = *configPath
  1268  	return nil
  1269  }
  1270  
  1271  func (v *MachineVM) setReadySocket() error {
  1272  	readySocketName := v.Name + "_ready.sock"
  1273  	rtPath, err := getRuntimeDir()
  1274  	if err != nil {
  1275  		return err
  1276  	}
  1277  	virtualSocketPath, err := machine.NewMachineFile(filepath.Join(rtPath, "podman", readySocketName), &readySocketName)
  1278  	if err != nil {
  1279  		return err
  1280  	}
  1281  	v.ReadySocket = *virtualSocketPath
  1282  	return nil
  1283  }
  1284  
  1285  func (v *MachineVM) setPIDSocket() error {
  1286  	rtPath, err := getRuntimeDir()
  1287  	if err != nil {
  1288  		return err
  1289  	}
  1290  	if !rootless.IsRootless() {
  1291  		rtPath = "/run"
  1292  	}
  1293  	pidFileName := fmt.Sprintf("%s.pid", v.Name)
  1294  	socketDir := filepath.Join(rtPath, "podman")
  1295  	pidFilePath, err := machine.NewMachineFile(filepath.Join(socketDir, pidFileName), &pidFileName)
  1296  	if err != nil {
  1297  		return err
  1298  	}
  1299  	v.PidFilePath = *pidFilePath
  1300  	return nil
  1301  }
  1302  
  1303  // Deprecated: getSocketandPid is being replace by setPIDSocket and
  1304  // machinefiles.
  1305  func (v *MachineVM) getSocketandPid() (string, string, error) {
  1306  	rtPath, err := getRuntimeDir()
  1307  	if err != nil {
  1308  		return "", "", err
  1309  	}
  1310  	if !rootless.IsRootless() {
  1311  		rtPath = "/run"
  1312  	}
  1313  	socketDir := filepath.Join(rtPath, "podman")
  1314  	pidFile := filepath.Join(socketDir, fmt.Sprintf("%s.pid", v.Name))
  1315  	qemuSocket := filepath.Join(socketDir, fmt.Sprintf("qemu_%s.sock", v.Name))
  1316  	return qemuSocket, pidFile, nil
  1317  }
  1318  
  1319  func checkSockInUse(sock string) bool {
  1320  	if info, err := os.Stat(sock); err == nil && info.Mode()&fs.ModeSocket == fs.ModeSocket {
  1321  		_, err = net.DialTimeout("unix", dockerSock, dockerConnectTimeout)
  1322  		return err == nil
  1323  	}
  1324  
  1325  	return false
  1326  }
  1327  
  1328  func alreadyLinked(target string, link string) bool {
  1329  	read, err := os.Readlink(link)
  1330  	return err == nil && read == target
  1331  }
  1332  
  1333  func waitAndPingAPI(sock string) {
  1334  	client := http.Client{
  1335  		Transport: &http.Transport{
  1336  			DialContext: func(context.Context, string, string) (net.Conn, error) {
  1337  				con, err := net.DialTimeout("unix", sock, apiUpTimeout)
  1338  				if err != nil {
  1339  					return nil, err
  1340  				}
  1341  				if err := con.SetDeadline(time.Now().Add(apiUpTimeout)); err != nil {
  1342  					return nil, err
  1343  				}
  1344  				return con, nil
  1345  			},
  1346  		},
  1347  	}
  1348  
  1349  	resp, err := client.Get("http://host/_ping")
  1350  	if err == nil {
  1351  		defer resp.Body.Close()
  1352  	}
  1353  	if err != nil || resp.StatusCode != 200 {
  1354  		logrus.Warn("API socket failed ping test")
  1355  	}
  1356  }
  1357  
  1358  func (v *MachineVM) waitAPIAndPrintInfo(forwardState apiForwardingState, forwardSock string) {
  1359  	suffix := ""
  1360  	if v.Name != machine.DefaultMachineName {
  1361  		suffix = " " + v.Name
  1362  	}
  1363  
  1364  	if v.isIncompatible() {
  1365  		fmt.Fprintf(os.Stderr, "\n!!! ACTION REQUIRED: INCOMPATIBLE MACHINE !!!\n")
  1366  
  1367  		fmt.Fprintf(os.Stderr, "\nThis machine was created by an older podman release that is incompatible\n")
  1368  		fmt.Fprintf(os.Stderr, "with this release of podman. It has been started in a limited operational\n")
  1369  		fmt.Fprintf(os.Stderr, "mode to allow you to copy any necessary files before recreating it. This\n")
  1370  		fmt.Fprintf(os.Stderr, "can be accomplished with the following commands:\n\n")
  1371  		fmt.Fprintf(os.Stderr, "\t# Login and copy desired files (Optional)\n")
  1372  		fmt.Fprintf(os.Stderr, "\t# podman machine ssh%s tar cvPf - /path/to/files > backup.tar\n\n", suffix)
  1373  		fmt.Fprintf(os.Stderr, "\t# Recreate machine (DESTRUCTIVE!) \n")
  1374  		fmt.Fprintf(os.Stderr, "\tpodman machine stop%s\n", suffix)
  1375  		fmt.Fprintf(os.Stderr, "\tpodman machine rm -f%s\n", suffix)
  1376  		fmt.Fprintf(os.Stderr, "\tpodman machine init --now%s\n\n", suffix)
  1377  		fmt.Fprintf(os.Stderr, "\t# Copy back files (Optional)\n")
  1378  		fmt.Fprintf(os.Stderr, "\t# cat backup.tar | podman machine ssh%s tar xvPf - \n\n", suffix)
  1379  	}
  1380  
  1381  	if forwardState == noForwarding {
  1382  		return
  1383  	}
  1384  
  1385  	waitAndPingAPI(forwardSock)
  1386  	if !v.Rootful {
  1387  		fmt.Printf("\nThis machine is currently configured in rootless mode. If your containers\n")
  1388  		fmt.Printf("require root permissions (e.g. ports < 1024), or if you run into compatibility\n")
  1389  		fmt.Printf("issues with non-podman clients, you can switch using the following command: \n")
  1390  		fmt.Printf("\n\tpodman machine set --rootful%s\n\n", suffix)
  1391  	}
  1392  
  1393  	fmt.Printf("API forwarding listening on: %s\n", forwardSock)
  1394  	if forwardState == dockerGlobal {
  1395  		fmt.Printf("Docker API clients default to this address. You do not need to set DOCKER_HOST.\n\n")
  1396  	} else {
  1397  		stillString := "still "
  1398  		switch forwardState {
  1399  		case notInstalled:
  1400  			fmt.Printf("\nThe system helper service is not installed; the default Docker API socket\n")
  1401  			fmt.Printf("address can't be used by podman. ")
  1402  			if helper := findClaimHelper(); len(helper) > 0 {
  1403  				fmt.Printf("If you would like to install it run the\nfollowing commands:\n")
  1404  				fmt.Printf("\n\tsudo %s install\n", helper)
  1405  				fmt.Printf("\tpodman machine stop%s; podman machine start%s\n\n", suffix, suffix)
  1406  			}
  1407  		case machineLocal:
  1408  			fmt.Printf("\nAnother process was listening on the default Docker API socket address.\n")
  1409  		case claimUnsupported:
  1410  			fallthrough
  1411  		default:
  1412  			stillString = ""
  1413  		}
  1414  
  1415  		fmt.Printf("You can %sconnect Docker API clients by setting DOCKER_HOST using the\n", stillString)
  1416  		fmt.Printf("following command in your terminal session:\n")
  1417  		fmt.Printf("\n\texport DOCKER_HOST='unix://%s'\n\n", forwardSock)
  1418  	}
  1419  }
  1420  
  1421  // update returns the content of the VM's
  1422  // configuration file in json
  1423  func (v *MachineVM) update() error {
  1424  	if err := v.setConfigPath(); err != nil {
  1425  		return err
  1426  	}
  1427  	b, err := v.ConfigPath.Read()
  1428  	if err != nil {
  1429  		if errors.Is(err, os.ErrNotExist) {
  1430  			return errors.Wrap(machine.ErrNoSuchVM, v.Name)
  1431  		}
  1432  		return err
  1433  	}
  1434  	if err != nil {
  1435  		return err
  1436  	}
  1437  	err = json.Unmarshal(b, v)
  1438  	if err != nil {
  1439  		err = migrateVM(v.ConfigPath.GetPath(), b, v)
  1440  		if err != nil {
  1441  			return err
  1442  		}
  1443  	}
  1444  	return err
  1445  }
  1446  
  1447  func (v *MachineVM) writeConfig() error {
  1448  	// Set the path of the configfile before writing to make
  1449  	// life easier down the line
  1450  	if err := v.setConfigPath(); err != nil {
  1451  		return err
  1452  	}
  1453  	// Write the JSON file
  1454  	b, err := json.MarshalIndent(v, "", " ")
  1455  	if err != nil {
  1456  		return err
  1457  	}
  1458  	if err := ioutil.WriteFile(v.ConfigPath.GetPath(), b, 0644); err != nil {
  1459  		return err
  1460  	}
  1461  	return nil
  1462  }
  1463  
  1464  // getImageFile wrapper returns the path to the image used
  1465  // to boot the VM
  1466  func (v *MachineVM) getImageFile() string {
  1467  	return v.ImagePath.GetPath()
  1468  }
  1469  
  1470  // getIgnitionFile wrapper returns the path to the ignition file
  1471  func (v *MachineVM) getIgnitionFile() string {
  1472  	return v.IgnitionFile.GetPath()
  1473  }
  1474  
  1475  // Inspect returns verbose detail about the machine
  1476  func (v *MachineVM) Inspect() (*machine.InspectInfo, error) {
  1477  	state, err := v.State(false)
  1478  	if err != nil {
  1479  		return nil, err
  1480  	}
  1481  	connInfo := new(machine.ConnectionConfig)
  1482  	podmanSocket, err := v.forwardSocketPath()
  1483  	if err != nil {
  1484  		return nil, err
  1485  	}
  1486  	connInfo.PodmanSocket = podmanSocket
  1487  	return &machine.InspectInfo{
  1488  		ConfigPath:     v.ConfigPath,
  1489  		ConnectionInfo: *connInfo,
  1490  		Created:        v.Created,
  1491  		Image:          v.ImageConfig,
  1492  		LastUp:         v.LastUp,
  1493  		Name:           v.Name,
  1494  		Resources:      v.ResourceConfig,
  1495  		SSHConfig:      v.SSHConfig,
  1496  		State:          state,
  1497  	}, nil
  1498  }
  1499  
  1500  // resizeDisk increases the size of the machine's disk in GB.
  1501  func (v *MachineVM) resizeDisk(diskSize uint64, oldSize uint64) error {
  1502  	// Resize the disk image to input disk size
  1503  	// only if the virtualdisk size is less than
  1504  	// the given disk size
  1505  	if diskSize < oldSize {
  1506  		return errors.Errorf("new disk size must be larger than current disk size: %vGB", oldSize)
  1507  	}
  1508  
  1509  	// Find the qemu executable
  1510  	cfg, err := config.Default()
  1511  	if err != nil {
  1512  		return err
  1513  	}
  1514  	resizePath, err := cfg.FindHelperBinary("qemu-img", true)
  1515  	if err != nil {
  1516  		return err
  1517  	}
  1518  	resize := exec.Command(resizePath, []string{"resize", v.getImageFile(), strconv.Itoa(int(diskSize)) + "G"}...)
  1519  	resize.Stdout = os.Stdout
  1520  	resize.Stderr = os.Stderr
  1521  	if err := resize.Run(); err != nil {
  1522  		return errors.Errorf("resizing image: %q", err)
  1523  	}
  1524  
  1525  	return nil
  1526  }
  1527  
  1528  func (v *MachineVM) setRootful(rootful bool) error {
  1529  	changeCon, err := machine.AnyConnectionDefault(v.Name, v.Name+"-root")
  1530  	if err != nil {
  1531  		return err
  1532  	}
  1533  
  1534  	if changeCon {
  1535  		newDefault := v.Name
  1536  		if rootful {
  1537  			newDefault += "-root"
  1538  		}
  1539  		err := machine.ChangeDefault(newDefault)
  1540  		if err != nil {
  1541  			return err
  1542  		}
  1543  	}
  1544  	return nil
  1545  }
  1546  
  1547  func (v *MachineVM) editCmdLine(flag string, value string) {
  1548  	found := false
  1549  	for i, val := range v.CmdLine {
  1550  		if val == flag {
  1551  			found = true
  1552  			v.CmdLine[i+1] = value
  1553  			break
  1554  		}
  1555  	}
  1556  	if !found {
  1557  		v.CmdLine = append(v.CmdLine, []string{flag, value}...)
  1558  	}
  1559  }
  1560  
  1561  // RemoveAndCleanMachines removes all machine and cleans up any other files associated with podman machine
  1562  func (p *Provider) RemoveAndCleanMachines() error {
  1563  	var (
  1564  		vm             machine.VM
  1565  		listResponse   []*machine.ListResponse
  1566  		opts           machine.ListOptions
  1567  		destroyOptions machine.RemoveOptions
  1568  	)
  1569  	destroyOptions.Force = true
  1570  	var prevErr error
  1571  
  1572  	listResponse, err := p.List(opts)
  1573  	if err != nil {
  1574  		return err
  1575  	}
  1576  
  1577  	for _, mach := range listResponse {
  1578  		vm, err = p.LoadVMByName(mach.Name)
  1579  		if err != nil {
  1580  			if prevErr != nil {
  1581  				logrus.Error(prevErr)
  1582  			}
  1583  			prevErr = err
  1584  		}
  1585  		_, remove, err := vm.Remove(mach.Name, destroyOptions)
  1586  		if err != nil {
  1587  			if prevErr != nil {
  1588  				logrus.Error(prevErr)
  1589  			}
  1590  			prevErr = err
  1591  		} else {
  1592  			if err := remove(); err != nil {
  1593  				if prevErr != nil {
  1594  					logrus.Error(prevErr)
  1595  				}
  1596  				prevErr = err
  1597  			}
  1598  		}
  1599  	}
  1600  
  1601  	// Clean leftover files in data dir
  1602  	dataDir, err := machine.DataDirPrefix()
  1603  	if err != nil {
  1604  		if prevErr != nil {
  1605  			logrus.Error(prevErr)
  1606  		}
  1607  		prevErr = err
  1608  	} else {
  1609  		err := os.RemoveAll(dataDir)
  1610  		if err != nil {
  1611  			if prevErr != nil {
  1612  				logrus.Error(prevErr)
  1613  			}
  1614  			prevErr = err
  1615  		}
  1616  	}
  1617  
  1618  	// Clean leftover files in conf dir
  1619  	confDir, err := machine.ConfDirPrefix()
  1620  	if err != nil {
  1621  		if prevErr != nil {
  1622  			logrus.Error(prevErr)
  1623  		}
  1624  		prevErr = err
  1625  	} else {
  1626  		err := os.RemoveAll(confDir)
  1627  		if err != nil {
  1628  			if prevErr != nil {
  1629  				logrus.Error(prevErr)
  1630  			}
  1631  			prevErr = err
  1632  		}
  1633  	}
  1634  	return prevErr
  1635  }