/
client9p.go
144 lines (125 loc) · 4.09 KB
/
client9p.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
//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
}