-
Notifications
You must be signed in to change notification settings - Fork 2.4k
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
This includes two new hidden commands: a 9p server, `podman machine server9p`, and a 9p client, `podman machine client9p` with `server9p` currently only configured to run on Windows and serve 9p via HyperV vsock, and `client9p` only configured to run on Linux. The server is run by `podman machine start` and has the same lifespan as gvproxy (waits for the gvproxy PID to die before shutting down). The client is run inside the VM, also by `podman machine start`, and mounts uses kernel 9p mount code to complete the mount. It's unfortunately not possible to use mount directly without the wrapper; we need to set up the vsock and pass it to mount as an FD. In theory this can be generalized so that the server can run anywhere and over almost any transport, but I haven't done this here as I don't think we have a usecase other than HyperV right now. [NO NEW TESTS NEEDED] This requires changes to Podman in the VM, so we need to wait until a build with this lands in FCOS to test. Signed-off-by: Matthew Heon <[email protected]>
- Loading branch information
Showing
93 changed files
with
15,119 additions
and
29 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,135 @@ | ||
//go:build linux && (amd64 || arm64) | ||
// +build linux | ||
// +build amd64 arm64 | ||
|
||
package machine | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"os/exec" | ||
"path/filepath" | ||
"strconv" | ||
|
||
"github.com/containers/common/pkg/completion" | ||
"github.com/containers/podman/v4/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) | ||
|
||
// 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: %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} | ||
|
||
err := cmd.Run() | ||
output, outErr := cmd.CombinedOutput() | ||
switch { | ||
case outErr != nil: | ||
logrus.Errorf("Unable to obtain output of mount command: %v", err) | ||
case err == nil: | ||
logrus.Debugf("Mount output: %s", string(output)) | ||
logrus.Infof("Mounted directory %s using 9p", mountPath) | ||
default: | ||
err = fmt.Errorf("running mount: %w\nOutput: %s", err, string(output)) | ||
} | ||
|
||
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,89 @@ | ||
//go:build windows && (amd64 || arm64) | ||
// +build windows | ||
// +build amd64 arm64 | ||
|
||
package machine | ||
|
||
import ( | ||
"fmt" | ||
"strconv" | ||
"strings" | ||
|
||
"github.com/containers/common/pkg/completion" | ||
"github.com/containers/podman/v4/cmd/podman/registry" | ||
"github.com/containers/podman/v4/pkg/fileserver" | ||
"github.com/containers/podman/v4/pkg/util" | ||
"github.com/sirupsen/logrus" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
var ( | ||
server9pCommand = &cobra.Command{ | ||
Args: cobra.ExactArgs(1), | ||
Use: "server9p [options] PID", | ||
Hidden: true, | ||
Short: "Serve a directory using 9p over hvsock", | ||
Long: "Start a number of 9p servers on given hvsock UUIDs, and run until the given PID exits", | ||
RunE: remoteDirServer, | ||
ValidArgsFunction: completion.AutocompleteNone, | ||
Example: `podman system server9p --serve C:\Users\myuser:00000050-FACB-11E6-BD58-64006A7986D3 /mnt`, | ||
} | ||
) | ||
|
||
func init() { | ||
registry.Commands = append(registry.Commands, registry.CliCommand{ | ||
Command: server9pCommand, | ||
Parent: machineCmd, | ||
}) | ||
|
||
flags := server9pCommand.Flags() | ||
|
||
serveFlagName := "serve" | ||
flags.StringArrayVar(&serveDirs, serveFlagName, []string{}, "directories to serve and UUID of vsock to serve on, colon-separated") | ||
_ = server9pCommand.RegisterFlagCompletionFunc(serveFlagName, completion.AutocompleteNone) | ||
} | ||
|
||
var ( | ||
serveDirs []string | ||
) | ||
|
||
func remoteDirServer(cmd *cobra.Command, args []string) error { | ||
pid, err := strconv.Atoi(args[0]) | ||
if err != nil { | ||
return fmt.Errorf("parsing PID: %w", err) | ||
} | ||
if pid < 0 { | ||
return fmt.Errorf("PIDs cannot be negative") | ||
} | ||
|
||
if len(serveDirs) == 0 { | ||
return fmt.Errorf("must provide at least one directory to serve") | ||
} | ||
|
||
// TODO: need to support options here | ||
shares := make(map[string]string, len(serveDirs)) | ||
for _, share := range serveDirs { | ||
splitShare := strings.Split(share, ":") | ||
if len(splitShare) < 2 { | ||
return fmt.Errorf("paths passed to --share must include an hvsock GUID") | ||
} | ||
|
||
// Every element but the last one is the real filepath to share | ||
path := strings.Join(splitShare[:len(splitShare)-1], ":") | ||
|
||
shares[path] = splitShare[len(splitShare)-1] | ||
} | ||
|
||
if err := fileserver.StartShares(shares); err != nil { | ||
return err | ||
} | ||
|
||
// Wait for the given PID to exit | ||
if err := util.WaitForPIDExit(uint(pid)); err != nil { | ||
return err | ||
} | ||
|
||
logrus.Infof("Exiting cleanly as PID %d has died", pid) | ||
|
||
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
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.