mirror of
				https://github.com/containers/podman.git
				synced 2025-11-04 08:56:05 +08:00 
			
		
		
		
	machine/linux: Support virtiofs mounts (retain 9p default)
I'm hitting a bug with 9p when trying to transfer large files. In RHEL at least 9p isn't supported because it's known to have a lot of design flaws; virtiofsd is the supported and recommended way to share files between a host and guest. Add a new hidden `PODMAN_MACHINE_VIRTFS` environment variable that can be set to `virtiofs` to switch to virtiofsd. Signed-off-by: Colin Walters <walters@verbum.org>
This commit is contained in:
		@ -53,6 +53,8 @@ func TestQemuCmd(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	expected := []string{
 | 
			
		||||
		"/usr/bin/qemu-system-x86_64",
 | 
			
		||||
		"-object",
 | 
			
		||||
		"memory-backend-memfd,id=mem,size=2048M,share=on",
 | 
			
		||||
		"-m", "2048",
 | 
			
		||||
		"-smp", "4",
 | 
			
		||||
		"-fw_cfg", fmt.Sprintf("name=opt/com.coreos/config,file=%s", ignPath),
 | 
			
		||||
 | 
			
		||||
@ -25,9 +25,28 @@ import (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	MountType9p = "9p"
 | 
			
		||||
	MountType9p       = "9p"
 | 
			
		||||
	MountTypeVirtiofs = "virtiofs"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func NewStubber() (*QEMUStubber, error) {
 | 
			
		||||
	var mountType string
 | 
			
		||||
	if v, ok := os.LookupEnv("PODMAN_MACHINE_VIRTFS"); ok {
 | 
			
		||||
		logrus.Debugf("using virtiofs %q", v)
 | 
			
		||||
		switch v {
 | 
			
		||||
		case MountType9p, MountTypeVirtiofs:
 | 
			
		||||
			mountType = v
 | 
			
		||||
		default:
 | 
			
		||||
			return nil, fmt.Errorf("failed to parse PODMAN_MACHINE_VIRTFS=%s", v)
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		mountType = MountType9p
 | 
			
		||||
	}
 | 
			
		||||
	return &QEMUStubber{
 | 
			
		||||
		mountType: mountType,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// qemuPid returns -1 or the PID of the running QEMU instance.
 | 
			
		||||
func qemuPid(pidFile *define.VMFile) (int, error) {
 | 
			
		||||
	pidData, err := os.ReadFile(pidFile.GetPath())
 | 
			
		||||
 | 
			
		||||
@ -31,6 +31,11 @@ type QEMUStubber struct {
 | 
			
		||||
	vmconfigs.QEMUConfig
 | 
			
		||||
	// Command describes the final QEMU command line
 | 
			
		||||
	Command command.QemuCmd
 | 
			
		||||
 | 
			
		||||
	// mountType is one of virtiofs or 9p
 | 
			
		||||
	mountType string
 | 
			
		||||
	// virtiofsHelpers are virtiofsd child processes
 | 
			
		||||
	virtiofsHelpers []virtiofsdHelperCmd
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
@ -83,13 +88,6 @@ func (q *QEMUStubber) setQEMUCommandLine(mc *vmconfigs.MachineConfig) error {
 | 
			
		||||
	}
 | 
			
		||||
	q.Command.SetSerialPort(*readySocket, *mc.QEMUHypervisor.QEMUPidPath, mc.Name)
 | 
			
		||||
 | 
			
		||||
	// Add volumes to qemu command line
 | 
			
		||||
	for _, mount := range mc.Mounts {
 | 
			
		||||
		// the index provided in this case is thrown away
 | 
			
		||||
		_, _, _, _, securityModel := vmconfigs.SplitVolume(0, mount.OriginalInput)
 | 
			
		||||
		q.Command.SetVirtfsMount(mount.Source, mount.Tag, securityModel, mount.ReadOnly)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	q.Command.SetUSBHostPassthrough(mc.Resources.USBs)
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
@ -168,6 +166,33 @@ func (q *QEMUStubber) StartVM(mc *vmconfigs.MachineConfig) (func() error, func()
 | 
			
		||||
	defer dnr.Close()
 | 
			
		||||
	defer dnw.Close()
 | 
			
		||||
 | 
			
		||||
	if q.mountType == MountTypeVirtiofs {
 | 
			
		||||
		runtime, err := mc.RuntimeDir()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, nil, err
 | 
			
		||||
		}
 | 
			
		||||
		spawner, err := newVirtiofsdSpawner(runtime)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, nil, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, hostmnt := range mc.Mounts {
 | 
			
		||||
			qemuArgs, virtiofsdHelper, err := spawner.spawnForMount(hostmnt)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, nil, fmt.Errorf("failed to init virtiofsd for mount %s: %w", hostmnt.Source, err)
 | 
			
		||||
			}
 | 
			
		||||
			q.Command = append(q.Command, qemuArgs...)
 | 
			
		||||
			q.virtiofsHelpers = append(q.virtiofsHelpers, *virtiofsdHelper)
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		// Add volumes to qemu command line
 | 
			
		||||
		for _, mount := range mc.Mounts {
 | 
			
		||||
			// the index provided in this case is thrown away
 | 
			
		||||
			_, _, _, _, securityModel := vmconfigs.SplitVolume(0, mount.OriginalInput)
 | 
			
		||||
			q.Command.SetVirtfsMount(mount.Source, mount.Tag, securityModel, mount.ReadOnly)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmdLine := q.Command
 | 
			
		||||
 | 
			
		||||
	// Disable graphic window when not in debug mode
 | 
			
		||||
@ -198,8 +223,20 @@ func (q *QEMUStubber) StartVM(mc *vmconfigs.MachineConfig) (func() error, func()
 | 
			
		||||
		return waitForReady(readySocket, cmd.Process.Pid, stderrBuf)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	releaseFunc := func() error {
 | 
			
		||||
		if err := cmd.Process.Release(); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		for _, virtiofsdCmd := range q.virtiofsHelpers {
 | 
			
		||||
			if err := virtiofsdCmd.command.Process.Release(); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// if this is not the last line in the func, make it a defer
 | 
			
		||||
	return cmd.Process.Release, readyFunc, nil
 | 
			
		||||
	return releaseFunc, readyFunc, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func waitForReady(readySocket *define.VMFile, pid int, stdErrBuffer *bytes.Buffer) error {
 | 
			
		||||
@ -325,7 +362,9 @@ func (q *QEMUStubber) MountVolumesToVM(mc *vmconfigs.MachineConfig, quiet bool)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		switch mount.Type {
 | 
			
		||||
		// NOTE: We ignore q.Type here (and it should eventually be dropped from being serialized); we
 | 
			
		||||
		// want the mount type to be dynamic, not static.
 | 
			
		||||
		switch q.mountType {
 | 
			
		||||
		case MountType9p:
 | 
			
		||||
			mountOptions := []string{"-t", "9p"}
 | 
			
		||||
			mountOptions = append(mountOptions, []string{"-o", "trans=virtio", mount.Tag, mount.Target}...)
 | 
			
		||||
@ -337,6 +376,18 @@ func (q *QEMUStubber) MountVolumesToVM(mc *vmconfigs.MachineConfig, quiet bool)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		case MountTypeVirtiofs:
 | 
			
		||||
			mountOptions := []string{"-t", "virtiofs"}
 | 
			
		||||
			mountOptions = append(mountOptions, []string{mount.Tag, mount.Target}...)
 | 
			
		||||
			mountFlags := fmt.Sprintf("context=\"%s\"", machine.NFSSELinuxContext)
 | 
			
		||||
			if mount.ReadOnly {
 | 
			
		||||
				mountFlags += ",ro"
 | 
			
		||||
			}
 | 
			
		||||
			mountOptions = append(mountOptions, "-o", mountFlags)
 | 
			
		||||
			err = machine.CommonSSH(mc.SSH.RemoteUsername, mc.SSH.IdentityPath, mc.Name, mc.SSH.Port, append([]string{"sudo", "mount"}, mountOptions...))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		default:
 | 
			
		||||
			return fmt.Errorf("unknown mount type: %s", mount.Type)
 | 
			
		||||
		}
 | 
			
		||||
@ -345,7 +396,14 @@ func (q *QEMUStubber) MountVolumesToVM(mc *vmconfigs.MachineConfig, quiet bool)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (q *QEMUStubber) MountType() vmconfigs.VolumeMountType {
 | 
			
		||||
	return vmconfigs.NineP
 | 
			
		||||
	switch q.mountType {
 | 
			
		||||
	case MountType9p:
 | 
			
		||||
		return vmconfigs.NineP
 | 
			
		||||
	case MountTypeVirtiofs:
 | 
			
		||||
		return vmconfigs.VirtIOFS
 | 
			
		||||
	default:
 | 
			
		||||
		panic(fmt.Errorf("unexpected mount type %q", q.mountType))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (q *QEMUStubber) PostStartNetworking(mc *vmconfigs.MachineConfig, noInfo bool) error {
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										99
									
								
								pkg/machine/qemu/virtiofsd.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								pkg/machine/qemu/virtiofsd.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,99 @@
 | 
			
		||||
//go:build linux || freebsd
 | 
			
		||||
 | 
			
		||||
package qemu
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"os/exec"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/containers/common/pkg/config"
 | 
			
		||||
	"github.com/containers/podman/v5/pkg/machine/define"
 | 
			
		||||
	"github.com/containers/podman/v5/pkg/machine/vmconfigs"
 | 
			
		||||
	"github.com/containers/storage/pkg/fileutils"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// VirtiofsdSpawner spawns an instance of virtiofsd
 | 
			
		||||
type virtiofsdSpawner struct {
 | 
			
		||||
	runtimeDir *define.VMFile
 | 
			
		||||
	binaryPath string
 | 
			
		||||
	mountCount uint
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newVirtiofsdSpawner(runtimeDir *define.VMFile) (*virtiofsdSpawner, error) {
 | 
			
		||||
	var binaryPath string
 | 
			
		||||
	cfg, err := config.Default()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	binaryPath, err = cfg.FindHelperBinary("virtiofsd", true)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("failed to find virtiofsd: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
	return &virtiofsdSpawner{binaryPath: binaryPath, runtimeDir: runtimeDir}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// createVirtiofsCmd returns a new command instance configured to launch virtiofsd.
 | 
			
		||||
func (v *virtiofsdSpawner) createVirtiofsCmd(directory, socketPath string) *exec.Cmd {
 | 
			
		||||
	args := []string{"--sandbox", "none", "--socket-path", socketPath, "--shared-dir", "."}
 | 
			
		||||
	// We don't need seccomp filtering; we trust our workloads. This incidentally
 | 
			
		||||
	// works around issues like https://gitlab.com/virtio-fs/virtiofsd/-/merge_requests/200.
 | 
			
		||||
	args = append(args, "--seccomp=none")
 | 
			
		||||
	cmd := exec.Command(v.binaryPath, args...)
 | 
			
		||||
	// This sets things up so that the `.` we passed in the arguments is the target directory
 | 
			
		||||
	cmd.Dir = directory
 | 
			
		||||
	// Quiet the daemon by default
 | 
			
		||||
	cmd.Env = append(cmd.Environ(), "RUST_LOG=ERROR")
 | 
			
		||||
	cmd.Stdin = nil
 | 
			
		||||
	cmd.Stdout = nil
 | 
			
		||||
	if logrus.IsLevelEnabled(logrus.DebugLevel) {
 | 
			
		||||
		cmd.Stderr = os.Stderr
 | 
			
		||||
	}
 | 
			
		||||
	return cmd
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type virtiofsdHelperCmd struct {
 | 
			
		||||
	command exec.Cmd
 | 
			
		||||
	socket  *define.VMFile
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// spawnForMount returns on success a combination of qemu commandline and child process for virtiofsd
 | 
			
		||||
func (v *virtiofsdSpawner) spawnForMount(hostmnt *vmconfigs.Mount) ([]string, *virtiofsdHelperCmd, error) {
 | 
			
		||||
	logrus.Debugf("Initializing virtiofsd mount for %s", hostmnt.Source)
 | 
			
		||||
	// By far the most common failure to spawn virtiofsd will be a typo'd source directory,
 | 
			
		||||
	// so let's synchronously check that ourselves here.
 | 
			
		||||
	if err := fileutils.Exists(hostmnt.Source); err != nil {
 | 
			
		||||
		return nil, nil, fmt.Errorf("failed to access virtiofs source directory %s", hostmnt.Source)
 | 
			
		||||
	}
 | 
			
		||||
	virtiofsChar := fmt.Sprintf("virtiofschar%d", v.mountCount)
 | 
			
		||||
	virtiofsCharPath, err := v.runtimeDir.AppendToNewVMFile(virtiofsChar, nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	qemuCommand := []string{}
 | 
			
		||||
 | 
			
		||||
	qemuCommand = append(qemuCommand, "-chardev", fmt.Sprintf("socket,id=%s,path=%s", virtiofsChar, virtiofsCharPath.Path))
 | 
			
		||||
	qemuCommand = append(qemuCommand, "-device", fmt.Sprintf("vhost-user-fs-pci,queue-size=1024,chardev=%s,tag=%s", virtiofsChar, hostmnt.Tag))
 | 
			
		||||
	// TODO: Honor hostmnt.readonly somehow here (add an option to virtiofsd)
 | 
			
		||||
	virtiofsdCmd := v.createVirtiofsCmd(hostmnt.Source, virtiofsCharPath.Path)
 | 
			
		||||
	if err := virtiofsdCmd.Start(); err != nil {
 | 
			
		||||
		return nil, nil, fmt.Errorf("failed to start virtiofsd")
 | 
			
		||||
	}
 | 
			
		||||
	// Wait for the socket
 | 
			
		||||
	for {
 | 
			
		||||
		if err := fileutils.Exists(virtiofsCharPath.Path); err == nil {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		logrus.Debugf("waiting for virtiofsd socket %q", virtiofsCharPath.Path)
 | 
			
		||||
		time.Sleep(time.Millisecond * 100)
 | 
			
		||||
	}
 | 
			
		||||
	// Increment our count of mounts which are used to create unique names for the devices
 | 
			
		||||
	v.mountCount += 1
 | 
			
		||||
	return qemuCommand, &virtiofsdHelperCmd{
 | 
			
		||||
		command: *virtiofsdCmd,
 | 
			
		||||
		socket:  virtiofsCharPath,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user