//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
}
