//go:build linux || freebsd || windows 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 }