forked from containers/podman
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial addition of 9p code to Podman
Server logic lives in gvisor-tap-vsock (specifically gvproxy), where it will live through the entire life of the VM, unlike any of our `podman machine` commands. Client logic lives in a new, hidden Podman command (`podman system client9`) that makes a connection to the server and then uses the kernel 9p mount code to complete the mount. Unfortunately we can't just directly call mount, as it doesn't support the connection type we're using; instead we need to make the connection ourselves and mount using FDs. The initial PR only works over Hyper-V vsocks, but the actual 9p protocol should work over just about anything so this can easily be generalized to work elsewhere if necessary. The client code (which is staying in Podman) is unfortunately a lot more gross than I want it to be, but I don't see an easy way around that. At the very least, it does avoid the need for us to maintain our own FUSE filesystem by using the kernel's 9p mount code (which looks like it's the same code QEMU uses, which is promising in terms of maintenance). Signed-off-by: Matthew Heon <matthew.heon@pm.me>
- Loading branch information
Showing
39 changed files
with
3,903 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
package system | ||
|
||
import ( | ||
"fmt" | ||
"strconv" | ||
|
||
"github.com/containers/common/pkg/completion" | ||
"github.com/containers/podman/v4/cmd/podman/registry" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
var ( | ||
client9Command = &cobra.Command{ | ||
Args: cobra.ExactArgs(2), | ||
Use: "client9", | ||
Short: "Connect to a 9p + vsock connection", | ||
Long: "Connect to a 9p + vsock connection.", | ||
RunE: remoteDirClient, | ||
ValidArgsFunction: completion.AutocompleteNone, | ||
Example: `podman system client9`, | ||
} | ||
) | ||
|
||
func init() { | ||
registry.Commands = append(registry.Commands, registry.CliCommand{ | ||
Command: client9Command, | ||
Parent: systemCmd, | ||
}) | ||
} | ||
|
||
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 file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
package system | ||
|
||
import ( | ||
"fmt" | ||
"bytes" | ||
"os" | ||
"os/exec" | ||
"path/filepath" | ||
|
||
"github.com/mdlayher/vsock" | ||
"github.com/sirupsen/logrus" | ||
) | ||
|
||
// 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) | ||
|
||
// Host connects to non-hypervisor processes on the host running the VM. | ||
conn, err := vsock.Dial(vsock.Host, portNum, nil) | ||
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: %w", 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) | ||
runMount := func(fd uintptr) { | ||
var ( | ||
stdout bytes.Buffer | ||
stderr bytes.Buffer | ||
vsock *os.File | ||
) | ||
|
||
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.Stdout = &stdout | ||
cmd.Stderr = &stderr | ||
cmd.ExtraFiles = []*os.File{vsock} | ||
|
||
err := cmd.Run() | ||
logrus.Debugf("Mount stdout: %s", stdout.String()) | ||
if err != nil { | ||
logrus.Debugf("Mount stderr: %s", stderr.String()) | ||
logrus.Infof("Mounted directory %s using 9p", mountPath) | ||
} else { | ||
logrus.Errorf("Error mounting directory %s. Mount stderr: %s", mountPath, stderr.String()) | ||
} | ||
|
||
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
//go+build !linux | ||
// +build !linux | ||
|
||
package system | ||
|
||
import ( | ||
"fmt" | ||
) | ||
|
||
func client9p(socketNum uint32, mountPath string) error { | ||
return fmt.Errorf("unsupported on this OS") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.