145 lines
4.1 KiB
Go
145 lines
4.1 KiB
Go
//go:build linux && (amd64 || arm64)
|
|
|
|
package machine
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/containers/common/pkg/completion"
|
|
"github.com/containers/podman/v5/cmd/podman/registry"
|
|
"github.com/mdlayher/vsock"
|
|
"github.com/sirupsen/logrus"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
var (
|
|
client9pCommand = &cobra.Command{
|
|
Args: cobra.ExactArgs(2),
|
|
Use: "client9p PORT DIR",
|
|
Hidden: true,
|
|
Short: "Mount a remote directory using 9p over hvsock",
|
|
Long: "Connect to the given hvsock port using 9p and mount the served filesystem at the given directory",
|
|
RunE: remoteDirClient,
|
|
ValidArgsFunction: completion.AutocompleteNone,
|
|
Example: `podman system client9p 55000 /mnt`,
|
|
}
|
|
)
|
|
|
|
func init() {
|
|
registry.Commands = append(registry.Commands, registry.CliCommand{
|
|
Command: client9pCommand,
|
|
Parent: machineCmd,
|
|
})
|
|
}
|
|
|
|
func remoteDirClient(cmd *cobra.Command, args []string) error {
|
|
port, err := strconv.Atoi(args[0])
|
|
if err != nil {
|
|
return fmt.Errorf("error parsing port number: %w", err)
|
|
}
|
|
|
|
if err := client9p(uint32(port), args[1]); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// This is Linux-only as we only intend for this function to be used inside the
|
|
// `podman machine` VM, which is guaranteed to be Linux.
|
|
func client9p(portNum uint32, mountPath string) error {
|
|
cleanPath, err := filepath.Abs(mountPath)
|
|
if err != nil {
|
|
return fmt.Errorf("absolute path for %s: %w", mountPath, err)
|
|
}
|
|
mountPath = cleanPath
|
|
|
|
// Mountpath needs to exist and be a directory
|
|
stat, err := os.Stat(mountPath)
|
|
if err != nil {
|
|
return fmt.Errorf("stat %s: %w", mountPath, err)
|
|
}
|
|
if !stat.IsDir() {
|
|
return fmt.Errorf("path %s is not a directory", mountPath)
|
|
}
|
|
|
|
logrus.Infof("Going to mount 9p on vsock port %d to directory %s", portNum, mountPath)
|
|
|
|
// The server is starting at the same time.
|
|
// Perform up to 5 retries with a backoff.
|
|
var (
|
|
conn *vsock.Conn
|
|
retries = 20
|
|
)
|
|
for i := 0; i < retries; i++ {
|
|
// Host connects to non-hypervisor processes on the host running the VM.
|
|
conn, err = vsock.Dial(vsock.Host, portNum, nil)
|
|
// If errors.Is worked on this error, we could detect non-timeout errors.
|
|
// But it doesn't. So retry 5 times regardless.
|
|
if err == nil {
|
|
break
|
|
}
|
|
time.Sleep(250 * time.Millisecond)
|
|
}
|
|
if err != nil {
|
|
return fmt.Errorf("dialing vsock port %d: %w", portNum, err)
|
|
}
|
|
defer func() {
|
|
if err := conn.Close(); err != nil {
|
|
logrus.Errorf("Error closing vsock: %v", err)
|
|
}
|
|
}()
|
|
|
|
// vsock doesn't give us direct access to the underlying FD. That's kind
|
|
// of inconvenient, because we have to pass it off to mount.
|
|
// However, it does give us the ability to get a syscall.RawConn, which
|
|
// has a method that allows us to run a function that takes the FD
|
|
// number as an argument.
|
|
// Which ought to be good enough? Probably?
|
|
// Overall, this is gross and I hate it, but I don't see a better way.
|
|
rawConn, err := conn.SyscallConn()
|
|
if err != nil {
|
|
return fmt.Errorf("getting vsock raw conn: %w", err)
|
|
}
|
|
errChan := make(chan error, 1)
|
|
runMount := func(fd uintptr) {
|
|
vsock := os.NewFile(fd, "vsock")
|
|
if vsock == nil {
|
|
errChan <- fmt.Errorf("could not convert vsock fd to os.File")
|
|
return
|
|
}
|
|
|
|
// This is ugly, but it lets us use real kernel mount code,
|
|
// instead of maintaining our own FUSE 9p implementation.
|
|
cmd := exec.Command("mount", "-t", "9p", "-o", "trans=fd,rfdno=3,wfdno=3,version=9p2000.L", "9p", mountPath)
|
|
cmd.ExtraFiles = []*os.File{vsock}
|
|
|
|
output, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
err = fmt.Errorf("running mount: %w\nOutput: %s", err, string(output))
|
|
} else {
|
|
logrus.Debugf("Mount output: %s", string(output))
|
|
logrus.Infof("Mounted directory %s using 9p", mountPath)
|
|
}
|
|
|
|
errChan <- err
|
|
close(errChan)
|
|
}
|
|
if err := rawConn.Control(runMount); err != nil {
|
|
return fmt.Errorf("running mount function for dir %s: %w", mountPath, err)
|
|
}
|
|
|
|
if err := <-errChan; err != nil {
|
|
return fmt.Errorf("mounting filesystem %s: %w", mountPath, err)
|
|
}
|
|
|
|
logrus.Infof("Mount of filesystem %s successful", mountPath)
|
|
|
|
return nil
|
|
}
|