From 642fa98976d75c78a5e5af39c185d59f8268cb1f Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Mon, 11 Sep 2023 13:41:15 -0400 Subject: [PATCH] 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 --- cmd/podman/machine/client9p.go | 135 + cmd/podman/machine/server9p.go | 89 + go.mod | 5 + go.sum | 25 + pkg/fileserver/plan9/serve.go | 94 + pkg/fileserver/server_unsupported.go | 16 + pkg/fileserver/server_windows.go | 62 + pkg/machine/gvproxy.go | 10 +- pkg/machine/hyperv/machine.go | 220 +- pkg/machine/hyperv/vsock.go | 23 +- pkg/machine/machine_windows.go | 7 +- pkg/util/utils_supported.go | 7 + pkg/util/utils_windows.go | 33 + vendor/github.com/hugelgupf/p9/LICENSE | 202 ++ .../hugelgupf/p9/fsimpl/localfs/localfs.go | 281 ++ .../hugelgupf/p9/fsimpl/localfs/readdir.go | 50 + .../p9/fsimpl/localfs/system_unix.go | 40 + .../p9/fsimpl/localfs/system_windows.go | 50 + .../p9/fsimpl/localfs/system_xattr.go | 25 + .../p9/fsimpl/templatefs/readonly.go | 194 ++ .../p9/fsimpl/templatefs/unimplfs.go | 193 ++ .../hugelgupf/p9/fsimpl/xattr/xattr.go | 39 + .../p9/fsimpl/xattr/xattr_windows.go | 11 + .../github.com/hugelgupf/p9/internal/doc.go | 2 + .../hugelgupf/p9/internal/stat_bsd.go | 31 + .../hugelgupf/p9/internal/stat_openbsd.go | 28 + .../hugelgupf/p9/internal/stat_standard.go | 31 + .../hugelgupf/p9/internal/stat_unix.go | 11 + .../hugelgupf/p9/internal/stat_windows.go | 74 + vendor/github.com/hugelgupf/p9/linux/errno.go | 291 ++ .../github.com/hugelgupf/p9/linux/errors.go | 38 + .../hugelgupf/p9/linux/errors_linux.go | 17 + .../hugelgupf/p9/linux/errors_unix.go | 21 + .../hugelgupf/p9/linux/errors_windows.go | 28 + vendor/github.com/hugelgupf/p9/p9/buffer.go | 253 ++ vendor/github.com/hugelgupf/p9/p9/client.go | 344 +++ .../github.com/hugelgupf/p9/p9/client_file.go | 568 ++++ vendor/github.com/hugelgupf/p9/p9/file.go | 274 ++ vendor/github.com/hugelgupf/p9/p9/fuzz.go | 27 + vendor/github.com/hugelgupf/p9/p9/handlers.go | 1390 ++++++++++ vendor/github.com/hugelgupf/p9/p9/messages.go | 2348 +++++++++++++++++ vendor/github.com/hugelgupf/p9/p9/p9.go | 1167 ++++++++ .../github.com/hugelgupf/p9/p9/path_tree.go | 238 ++ vendor/github.com/hugelgupf/p9/p9/pool.go | 65 + vendor/github.com/hugelgupf/p9/p9/server.go | 681 +++++ .../github.com/hugelgupf/p9/p9/transport.go | 245 ++ vendor/github.com/hugelgupf/p9/p9/version.go | 134 + .../hugelgupf/p9/vecnet/iov32_linux.go | 22 + .../hugelgupf/p9/vecnet/iov_linux.go | 22 + .../github.com/hugelgupf/p9/vecnet/vecnet.go | 54 + .../hugelgupf/p9/vecnet/vecnet_linux.go | 112 + .../hugelgupf/p9/vecnet/vecnet_other.go | 24 + vendor/github.com/linuxkit/virtsock/AUTHORS | 11 + vendor/github.com/linuxkit/virtsock/LICENSE | 16 + .../linuxkit/virtsock/pkg/hvsock/hvsock.go | 115 + .../virtsock/pkg/hvsock/hvsock_fallback.go | 22 + .../virtsock/pkg/hvsock/hvsock_linux.go | 277 ++ .../virtsock/pkg/hvsock/hvsock_windows.go | 496 ++++ .../virtsock/pkg/hvsock/zsyscall_windows.go | 171 ++ .../github.com/mdlayher/socket/CHANGELOG.md | 80 + vendor/github.com/mdlayher/socket/LICENSE.md | 9 + vendor/github.com/mdlayher/socket/README.md | 23 + vendor/github.com/mdlayher/socket/accept.go | 23 + vendor/github.com/mdlayher/socket/accept4.go | 15 + vendor/github.com/mdlayher/socket/conn.go | 880 ++++++ .../github.com/mdlayher/socket/conn_linux.go | 118 + vendor/github.com/mdlayher/socket/doc.go | 13 + .../github.com/mdlayher/socket/netns_linux.go | 150 ++ .../mdlayher/socket/netns_others.go | 14 + .../mdlayher/socket/setbuffer_linux.go | 24 + .../mdlayher/socket/setbuffer_others.go | 16 + .../mdlayher/socket/typ_cloexec_nonblock.go | 12 + vendor/github.com/mdlayher/socket/typ_none.go | 11 + vendor/github.com/mdlayher/vsock/.gitignore | 4 + vendor/github.com/mdlayher/vsock/CHANGELOG.md | 53 + vendor/github.com/mdlayher/vsock/LICENSE.md | 9 + vendor/github.com/mdlayher/vsock/README.md | 21 + .../github.com/mdlayher/vsock/conn_linux.go | 62 + vendor/github.com/mdlayher/vsock/doc.go | 10 + vendor/github.com/mdlayher/vsock/fd_linux.go | 36 + .../mdlayher/vsock/listener_linux.go | 133 + vendor/github.com/mdlayher/vsock/vsock.go | 435 +++ .../github.com/mdlayher/vsock/vsock_others.go | 45 + vendor/github.com/u-root/uio/LICENSE | 29 + vendor/github.com/u-root/uio/ulog/log.go | 31 + vendor/golang.org/x/net/bpf/asm.go | 41 + vendor/golang.org/x/net/bpf/constants.go | 222 ++ vendor/golang.org/x/net/bpf/doc.go | 80 + vendor/golang.org/x/net/bpf/instructions.go | 726 +++++ vendor/golang.org/x/net/bpf/setter.go | 10 + vendor/golang.org/x/net/bpf/vm.go | 150 ++ .../golang.org/x/net/bpf/vm_instructions.go | 182 ++ vendor/modules.txt | 22 + 93 files changed, 15119 insertions(+), 29 deletions(-) create mode 100644 cmd/podman/machine/client9p.go create mode 100644 cmd/podman/machine/server9p.go create mode 100644 pkg/fileserver/plan9/serve.go create mode 100644 pkg/fileserver/server_unsupported.go create mode 100644 pkg/fileserver/server_windows.go create mode 100644 vendor/github.com/hugelgupf/p9/LICENSE create mode 100644 vendor/github.com/hugelgupf/p9/fsimpl/localfs/localfs.go create mode 100644 vendor/github.com/hugelgupf/p9/fsimpl/localfs/readdir.go create mode 100644 vendor/github.com/hugelgupf/p9/fsimpl/localfs/system_unix.go create mode 100644 vendor/github.com/hugelgupf/p9/fsimpl/localfs/system_windows.go create mode 100644 vendor/github.com/hugelgupf/p9/fsimpl/localfs/system_xattr.go create mode 100644 vendor/github.com/hugelgupf/p9/fsimpl/templatefs/readonly.go create mode 100644 vendor/github.com/hugelgupf/p9/fsimpl/templatefs/unimplfs.go create mode 100644 vendor/github.com/hugelgupf/p9/fsimpl/xattr/xattr.go create mode 100644 vendor/github.com/hugelgupf/p9/fsimpl/xattr/xattr_windows.go create mode 100644 vendor/github.com/hugelgupf/p9/internal/doc.go create mode 100644 vendor/github.com/hugelgupf/p9/internal/stat_bsd.go create mode 100644 vendor/github.com/hugelgupf/p9/internal/stat_openbsd.go create mode 100644 vendor/github.com/hugelgupf/p9/internal/stat_standard.go create mode 100644 vendor/github.com/hugelgupf/p9/internal/stat_unix.go create mode 100644 vendor/github.com/hugelgupf/p9/internal/stat_windows.go create mode 100644 vendor/github.com/hugelgupf/p9/linux/errno.go create mode 100644 vendor/github.com/hugelgupf/p9/linux/errors.go create mode 100644 vendor/github.com/hugelgupf/p9/linux/errors_linux.go create mode 100644 vendor/github.com/hugelgupf/p9/linux/errors_unix.go create mode 100644 vendor/github.com/hugelgupf/p9/linux/errors_windows.go create mode 100644 vendor/github.com/hugelgupf/p9/p9/buffer.go create mode 100644 vendor/github.com/hugelgupf/p9/p9/client.go create mode 100644 vendor/github.com/hugelgupf/p9/p9/client_file.go create mode 100644 vendor/github.com/hugelgupf/p9/p9/file.go create mode 100644 vendor/github.com/hugelgupf/p9/p9/fuzz.go create mode 100644 vendor/github.com/hugelgupf/p9/p9/handlers.go create mode 100644 vendor/github.com/hugelgupf/p9/p9/messages.go create mode 100644 vendor/github.com/hugelgupf/p9/p9/p9.go create mode 100644 vendor/github.com/hugelgupf/p9/p9/path_tree.go create mode 100644 vendor/github.com/hugelgupf/p9/p9/pool.go create mode 100644 vendor/github.com/hugelgupf/p9/p9/server.go create mode 100644 vendor/github.com/hugelgupf/p9/p9/transport.go create mode 100644 vendor/github.com/hugelgupf/p9/p9/version.go create mode 100644 vendor/github.com/hugelgupf/p9/vecnet/iov32_linux.go create mode 100644 vendor/github.com/hugelgupf/p9/vecnet/iov_linux.go create mode 100644 vendor/github.com/hugelgupf/p9/vecnet/vecnet.go create mode 100644 vendor/github.com/hugelgupf/p9/vecnet/vecnet_linux.go create mode 100644 vendor/github.com/hugelgupf/p9/vecnet/vecnet_other.go create mode 100644 vendor/github.com/linuxkit/virtsock/AUTHORS create mode 100644 vendor/github.com/linuxkit/virtsock/LICENSE create mode 100644 vendor/github.com/linuxkit/virtsock/pkg/hvsock/hvsock.go create mode 100644 vendor/github.com/linuxkit/virtsock/pkg/hvsock/hvsock_fallback.go create mode 100644 vendor/github.com/linuxkit/virtsock/pkg/hvsock/hvsock_linux.go create mode 100644 vendor/github.com/linuxkit/virtsock/pkg/hvsock/hvsock_windows.go create mode 100644 vendor/github.com/linuxkit/virtsock/pkg/hvsock/zsyscall_windows.go create mode 100644 vendor/github.com/mdlayher/socket/CHANGELOG.md create mode 100644 vendor/github.com/mdlayher/socket/LICENSE.md create mode 100644 vendor/github.com/mdlayher/socket/README.md create mode 100644 vendor/github.com/mdlayher/socket/accept.go create mode 100644 vendor/github.com/mdlayher/socket/accept4.go create mode 100644 vendor/github.com/mdlayher/socket/conn.go create mode 100644 vendor/github.com/mdlayher/socket/conn_linux.go create mode 100644 vendor/github.com/mdlayher/socket/doc.go create mode 100644 vendor/github.com/mdlayher/socket/netns_linux.go create mode 100644 vendor/github.com/mdlayher/socket/netns_others.go create mode 100644 vendor/github.com/mdlayher/socket/setbuffer_linux.go create mode 100644 vendor/github.com/mdlayher/socket/setbuffer_others.go create mode 100644 vendor/github.com/mdlayher/socket/typ_cloexec_nonblock.go create mode 100644 vendor/github.com/mdlayher/socket/typ_none.go create mode 100644 vendor/github.com/mdlayher/vsock/.gitignore create mode 100644 vendor/github.com/mdlayher/vsock/CHANGELOG.md create mode 100644 vendor/github.com/mdlayher/vsock/LICENSE.md create mode 100644 vendor/github.com/mdlayher/vsock/README.md create mode 100644 vendor/github.com/mdlayher/vsock/conn_linux.go create mode 100644 vendor/github.com/mdlayher/vsock/doc.go create mode 100644 vendor/github.com/mdlayher/vsock/fd_linux.go create mode 100644 vendor/github.com/mdlayher/vsock/listener_linux.go create mode 100644 vendor/github.com/mdlayher/vsock/vsock.go create mode 100644 vendor/github.com/mdlayher/vsock/vsock_others.go create mode 100644 vendor/github.com/u-root/uio/LICENSE create mode 100644 vendor/github.com/u-root/uio/ulog/log.go create mode 100644 vendor/golang.org/x/net/bpf/asm.go create mode 100644 vendor/golang.org/x/net/bpf/constants.go create mode 100644 vendor/golang.org/x/net/bpf/doc.go create mode 100644 vendor/golang.org/x/net/bpf/instructions.go create mode 100644 vendor/golang.org/x/net/bpf/setter.go create mode 100644 vendor/golang.org/x/net/bpf/vm.go create mode 100644 vendor/golang.org/x/net/bpf/vm_instructions.go diff --git a/cmd/podman/machine/client9p.go b/cmd/podman/machine/client9p.go new file mode 100644 index 0000000000..4bf1318230 --- /dev/null +++ b/cmd/podman/machine/client9p.go @@ -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 +} diff --git a/cmd/podman/machine/server9p.go b/cmd/podman/machine/server9p.go new file mode 100644 index 0000000000..e10ce1de4a --- /dev/null +++ b/cmd/podman/machine/server9p.go @@ -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 +} diff --git a/go.mod b/go.mod index 3bc751cdd4..86f2bbdf18 100644 --- a/go.mod +++ b/go.mod @@ -39,9 +39,12 @@ require ( github.com/gorilla/mux v1.8.0 github.com/gorilla/schema v1.2.0 github.com/hashicorp/go-multierror v1.1.1 + github.com/hugelgupf/p9 v0.3.1-0.20230822151754-54f5c5530921 github.com/json-iterator/go v1.1.12 + github.com/linuxkit/virtsock v0.0.0-20220523201153-1a23e78aa7a2 github.com/mattn/go-shellwords v1.0.12 github.com/mattn/go-sqlite3 v1.14.17 + github.com/mdlayher/vsock v1.2.1 github.com/moby/term v0.5.0 github.com/nxadm/tail v1.4.11 github.com/onsi/ginkgo/v2 v2.13.0 @@ -149,6 +152,7 @@ require ( github.com/manifoldco/promptui v0.9.0 // indirect github.com/mattn/go-isatty v0.0.19 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/mdlayher/socket v0.4.1 // indirect github.com/miekg/pkcs11 v1.1.1 // indirect github.com/mistifyio/go-zfs/v3 v3.0.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect @@ -180,6 +184,7 @@ require ( github.com/theupdateframework/go-tuf v0.5.2 // indirect github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63 // indirect github.com/ugorji/go/codec v1.2.11 // indirect github.com/vbatts/tar-split v0.11.5 // indirect github.com/vishvananda/netns v0.0.4 // indirect diff --git a/go.sum b/go.sum index 1f9b680f0d..a029ca1c59 100644 --- a/go.sum +++ b/go.sum @@ -72,6 +72,7 @@ github.com/Microsoft/hcsshim v0.12.0-rc.0/go.mod h1:rvOnw3YlfoNnEp45wReUngvsXbwR github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU= github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= @@ -360,6 +361,7 @@ github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZ github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= @@ -579,6 +581,7 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/goterm v0.0.0-20200907032337-555d40f16ae2 h1:CVuJwN34x4xM2aT4sIKhmeib40NeBPhRihNjQmpJsA4= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -641,6 +644,10 @@ github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T github.com/honeycombio/beeline-go v1.10.0 h1:cUDe555oqvw8oD76BQJ8alk7FP0JZ/M/zXpNvOEDLDc= github.com/honeycombio/libhoney-go v1.16.0 h1:kPpqoz6vbOzgp7jC6SR7SkNj7rua7rgxvznI6M3KdHc= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/hugelgupf/p9 v0.3.1-0.20230822151754-54f5c5530921 h1:cfYGdNpXGZobTSSDFB+wx2FRfWptM7sCkScJgVx0Tkk= +github.com/hugelgupf/p9 v0.3.1-0.20230822151754-54f5c5530921/go.mod h1:nMr69J6AmirlSvzeVLK7gj4DUY1oYtSwcSiSJ7BBb0A= +github.com/hugelgupf/socketpair v0.0.0-20230822150718-707395b1939a h1:Nq7wDsqsVBUBfGn8yB1M028ShWTKTtZBcafaTJ35N0s= +github.com/hugelgupf/vmtest v0.0.0-20230810222836-f8c8e381617c h1:4A+BVHylCBQPxlW1NrUITDpRAHCeX6QSZHmzzFQqliU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= @@ -651,6 +658,7 @@ github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/insomniacslk/dhcp v0.0.0-20230731140434-0f9eb93a696c h1:P/3mFnHCv1A/ej4m8pF5EB6FUt9qEL2Q9lfrcUNwCYs= github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA= github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= @@ -662,6 +670,8 @@ github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqx github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= +github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -711,6 +721,8 @@ github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNa github.com/letsencrypt/boulder v0.0.0-20230213213521-fdfea0d469b6 h1:unJdfS94Y3k85TKy+mvKzjW5R9rIC+Lv4KGbE7uNu0I= github.com/letsencrypt/boulder v0.0.0-20230213213521-fdfea0d469b6/go.mod h1:PUgW5vI9ANEaV6qv9a6EKu8gAySgwf0xrzG9xIB/CK0= github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo= +github.com/linuxkit/virtsock v0.0.0-20220523201153-1a23e78aa7a2 h1:DZMFueDbfz6PNc1GwDRA8+6lBx1TB9UnxDQliCqR73Y= +github.com/linuxkit/virtsock v0.0.0-20220523201153-1a23e78aa7a2/go.mod h1:SWzULI85WerrFt3u+nIm5F9l7EvxZTKQvd0InF3nmgM= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -741,6 +753,11 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5 github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY= +github.com/mdlayher/packet v1.1.2 h1:3Up1NG6LZrsgDVn6X4L9Ge/iyRyxFEFD9o6Pr3Q1nQY= +github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= +github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= +github.com/mdlayher/vsock v1.2.1 h1:pC1mTJTvjo1r9n9fbm7S1j04rCgCzhCOS5DY0zqHlnQ= +github.com/mdlayher/vsock v1.2.1/go.mod h1:NRfCibel++DgeMD8z/hP+PPTjlNJsdPOmxcnENvE+SE= github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU= github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= @@ -858,6 +875,8 @@ github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrap github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pierrec/lz4/v4 v4.1.18 h1:xaKrnTkyoqfh1YItXl56+6KJNVYWlEEPuAQW9xsplYQ= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -1013,6 +1032,10 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1 github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/u-root/gobusybox/src v0.0.0-20230806212452-e9366a5b9fdc h1:udgfN9Qy573qgHWMEORFgy6YXNDiN/Fd5LlKdlp+/Mo= +github.com/u-root/u-root v0.11.1-0.20230807200058-f87ad7ccb594 h1:1AIJqOtdEufYfGb3eRpdaqWONzBOpAwrg1fehbWg+Mg= +github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63 h1:YcojQL98T/OO+rybuzn2+5KrD5dBwXIvYBvQ2cD3Avg= +github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= @@ -1319,6 +1342,7 @@ golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1608,3 +1632,4 @@ sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= +src.elv.sh v0.16.0-rc1.0.20220116211855-fda62502ad7f h1:pjVeIo9Ba6K1Wy+rlwX91zT7A+xGEmxiNRBdN04gDTQ= diff --git a/pkg/fileserver/plan9/serve.go b/pkg/fileserver/plan9/serve.go new file mode 100644 index 0000000000..34f3f77eba --- /dev/null +++ b/pkg/fileserver/plan9/serve.go @@ -0,0 +1,94 @@ +package plan9 + +import ( + "fmt" + "net" + "os" + "path/filepath" + + "github.com/hugelgupf/p9/fsimpl/localfs" + "github.com/hugelgupf/p9/p9" + "github.com/sirupsen/logrus" +) + +type Server struct { + server *p9.Server + // TODO: Once server has a proper Close() we don't need this. + // This is basically just a short-circuit to actually close the server + // without that ability. + listener net.Listener + // Errors from the server being started will come out here. + errChan chan error +} + +// Expose a single directory (and all children) via the given net.Listener. +// Directory given must be an absolute path and must exist. +func New9pServer(listener net.Listener, exposeDir string) (*Server, error) { + // Verify that exposeDir makes sense. + if !filepath.IsAbs(exposeDir) { + return nil, fmt.Errorf("path to expose to machine must be absolute: %s", exposeDir) + } + stat, err := os.Stat(exposeDir) + if err != nil { + return nil, fmt.Errorf("cannot stat path to expose to machine: %w", err) + } + if !stat.IsDir() { + return nil, fmt.Errorf("path to expose to machine must be a directory: %s", exposeDir) + } + + server := p9.NewServer(localfs.Attacher(exposeDir), []p9.ServerOpt{}...) + if server == nil { + return nil, fmt.Errorf("p9.NewServer returned nil") + } + + errChan := make(chan error) + + // TODO: Use a channel to pass back this if it occurs within a + // reasonable timeframe. + go func() { + errChan <- server.Serve(listener) + close(errChan) + }() + + toReturn := new(Server) + toReturn.listener = listener + toReturn.server = server + toReturn.errChan = errChan + + // Just before returning, check to see if we got an error off server + // startup. + select { + case err := <-errChan: + return nil, fmt.Errorf("starting 9p server: %w", err) + default: + logrus.Infof("Successfully started 9p server for directory %s", exposeDir) + } + + return toReturn, nil +} + +// Stop a running server. +// Please note that this does *BAD THINGS* to clients if they are still running +// when the server stops. Processes get stuck in I/O deep sleep and zombify, and +// nothing I do save restarting the VM can remove the zombies. +func (s *Server) Stop() error { + if s.server != nil { + if err := s.listener.Close(); err != nil { + return err + } + s.server = nil + } + + return nil +} + +// Wait for an error from a running server. +func (s *Server) WaitForError() error { + if s.server != nil { + err := <-s.errChan + return err + } + + // Server already down, return nil + return nil +} diff --git a/pkg/fileserver/server_unsupported.go b/pkg/fileserver/server_unsupported.go new file mode 100644 index 0000000000..0f8edf8c51 --- /dev/null +++ b/pkg/fileserver/server_unsupported.go @@ -0,0 +1,16 @@ +//go:build !windows +// +build !windows + +package fileserver + +import ( + "fmt" +) + +func StartShares(mounts map[string]string) error { + if len(mounts) == 0 { + return nil + } + + return fmt.Errorf("this platform does not support sharing directories") +} diff --git a/pkg/fileserver/server_windows.go b/pkg/fileserver/server_windows.go new file mode 100644 index 0000000000..949398bda7 --- /dev/null +++ b/pkg/fileserver/server_windows.go @@ -0,0 +1,62 @@ +package fileserver + +import ( + "fmt" + + "github.com/containers/podman/v4/pkg/fileserver/plan9" + "github.com/linuxkit/virtsock/pkg/hvsock" + "github.com/sirupsen/logrus" +) + +// Start serving the given shares on Windows HVSocks for use by a Hyper-V VM. +// Mounts is formatted as a map of directory to be shared to vsock GUID. +// The vsocks used must already be defined before StartShares is called; it's +// expected that the vsocks will be created and torn down by the program calling +// gvproxy. +// TODO: The map here probably doesn't make sense. +// In the future, possibly accept a struct instead, so we can accept things +// other than a vsock and support non-Windows OSes. +func StartShares(mounts map[string]string) (defErr error) { + for path, guid := range mounts { + service, err := hvsock.GUIDFromString(guid) + if err != nil { + return fmt.Errorf("parsing vsock guid %s: %w", guid, err) + } + + listener, err := hvsock.Listen(hvsock.Addr{ + VMID: hvsock.GUIDWildcard, + ServiceID: service, + }) + if err != nil { + return fmt.Errorf("retrieving listener for vsock %s: %w", guid, err) + } + + logrus.Debugf("Going to serve directory %s on vsock %s", path, guid) + + server, err := plan9.New9pServer(listener, path) + if err != nil { + return fmt.Errorf("serving directory %s on vsock %s: %w", path, guid, err) + } + defer func() { + if defErr != nil { + if err := server.Stop(); err != nil { + logrus.Errorf("Error stopping 9p server: %v", err) + } + } + }() + + serverDir := path + + go func() { + if err := server.WaitForError(); err != nil { + logrus.Errorf("Error from 9p server for %s: %v", serverDir, err) + } else { + // We do not expect server exits - this should + // run until the program exits. + logrus.Warnf("9p server for %s exited without error", serverDir) + } + }() + } + + return nil +} diff --git a/pkg/machine/gvproxy.go b/pkg/machine/gvproxy.go index 1dddb1c1f1..dd322f0f51 100644 --- a/pkg/machine/gvproxy.go +++ b/pkg/machine/gvproxy.go @@ -6,10 +6,12 @@ import ( "strconv" "syscall" "time" + + "github.com/sirupsen/logrus" ) const ( - loops = 5 + loops = 10 sleepTime = time.Millisecond * 1 ) @@ -19,8 +21,10 @@ const ( func backoffForProcess(pid int) error { sleepInterval := sleepTime for i := 0; i < loops; i++ { + logrus.Debugf("Sleeping for %d milliseconds", sleepInterval.Milliseconds()) proxyProc, err := findProcess(pid) if proxyProc == nil && err != nil { + logrus.Debugf("Error opening process %d: %v", pid, err) // process is killed, gone return nil //nolint: nilerr } @@ -35,6 +39,8 @@ func backoffForProcess(pid int) error { // process to not exist. if the sigterm does not end the process after an interval, // then sigkill is sent. it also waits for the process to exit after the sigkill too. func waitOnProcess(processID int) error { + logrus.Infof("Going to stop gvproxy (PID %d)", processID) + proxyProc, err := findProcess(processID) if err != nil { return err @@ -58,10 +64,12 @@ func waitOnProcess(processID int) error { proxyProc, err = findProcess(processID) if proxyProc == nil && err != nil { // process is killed, gone + logrus.Debugf("Error opening gvproxy process: %v", err) return nil //nolint: nilerr } if err := proxyProc.Signal(syscall.SIGKILL); err != nil { if err == syscall.ESRCH { + logrus.Debugf("Gvproxy already dead, exiting cleanly") return nil } return err diff --git a/pkg/machine/hyperv/machine.go b/pkg/machine/hyperv/machine.go index 4914378022..891e0a9f75 100644 --- a/pkg/machine/hyperv/machine.go +++ b/pkg/machine/hyperv/machine.go @@ -12,8 +12,10 @@ import ( "os" "os/exec" "path/filepath" + "strings" "time" + "github.com/Microsoft/go-winio" "github.com/containers/common/pkg/config" gvproxy "github.com/containers/gvisor-tap-vsock/pkg/types" "github.com/containers/libhvee/pkg/hypervctl" @@ -67,6 +69,9 @@ type HyperVMachine struct { LastUp time.Time // GVProxy will write its PID here GvProxyPid machine.VMFile + // MountVsocks contains the currently-active vsocks, mapped to the + // directory they should be mounted on. + MountVsocks map[string]uint64 // Used at runtime for serializing write operations lock *lockfile.LockFile } @@ -215,6 +220,31 @@ func (m *HyperVMachine) Init(opts machine.InitOptions) (bool, error) { callbackFuncs.Add(m.ConfigPath.Delete) callbackFuncs.Add(m.unregisterMachine) + // Parsing here is confusing. + // Basically, we have two paths: a source path, on the Windows machine, + // with all that entails (drive letter, backslash separator, etc) and a + // dest path, in the Linux machine, normal Unix semantics. They are + // separated by a : character, with source path first, dest path second. + // So we split on :, first two parts are guaranteed to be Windows (the + // drive letter and file path), next one is Linux. Options, when we get + // around to those, would be another : after that. + // TODO: Need to support options here + for _, mount := range opts.Volumes { + newMount := machine.Mount{} + + splitMount := strings.Split(mount, ":") + if len(splitMount) < 3 { + return false, fmt.Errorf("volumes must be specified as source:destination and must be absolute") + } + newMount.Target = splitMount[2] + newMount.Source = strings.Join(splitMount[:2], ":") + if len(splitMount) > 3 { + return false, fmt.Errorf("volume options are not presently supported on Hyper-V") + } + + m.Mounts = append(m.Mounts, newMount) + } + if err = m.addNetworkAndReadySocketsToRegistry(); err != nil { return false, err } @@ -403,7 +433,7 @@ func (m *HyperVMachine) Remove(_ string, opts machine.RemoveOptions) (string, fu vmm := hypervctl.NewVirtualMachineManager() vm, err := vmm.GetMachine(m.Name) if err != nil { - return "", nil, err + return "", nil, fmt.Errorf("getting virtual machine: %w", err) } // In hyperv, they call running 'enabled' if vm.State() == hypervctl.Enabled { @@ -412,13 +442,19 @@ func (m *HyperVMachine) Remove(_ string, opts machine.RemoveOptions) (string, fu } // force stop bc we are destroying if err := vm.StopWithForce(); err != nil { - return "", nil, err + return "", nil, fmt.Errorf("stopping virtual machine: %w", err) + } + + // Update state on the VM by pulling its info again + vm, err = vmm.GetMachine(m.Name) + if err != nil { + return "", nil, fmt.Errorf("getting VM: %w", err) } } - vm, err = vmm.GetMachine(m.Name) - if err != nil { - return "", nil, err + // Tear down vsocks + if err := m.removeShares(); err != nil { + logrus.Errorf("Error removing vsock: %w", err) } // Collect all the files that need to be destroyed @@ -433,7 +469,10 @@ func (m *HyperVMachine) Remove(_ string, opts machine.RemoveOptions) (string, fu return confirmationMessage, func() error { machine.RemoveFilesAndConnections(files, m.Name, m.Name+"-root") m.removeNetworkAndReadySocketsFromRegistry() - return vm.Remove("") + if err := vm.Remove(""); err != nil { + return fmt.Errorf("removing virtual machine: %w", err) + } + return nil }, nil } @@ -450,7 +489,7 @@ func (m *HyperVMachine) Set(name string, opts machine.SetOptions) ([]error, erro // Considering this a hard return if we cannot lookup the machine vm, err := vmm.GetMachine(m.Name) if err != nil { - return setErrors, err + return setErrors, fmt.Errorf("getting machine: %w", err) } if vm.State() != hypervctl.Disabled { return nil, errors.New("unable to change settings unless vm is stopped") @@ -492,7 +531,7 @@ func (m *HyperVMachine) Set(name string, opts machine.SetOptions) ([]error, erro } }) if err != nil { - setErrors = append(setErrors, err) + setErrors = append(setErrors, fmt.Errorf("setting CPU and Memory for VM: %w", err)) } } @@ -525,6 +564,16 @@ func (m *HyperVMachine) Start(name string, opts machine.StartOptions) error { m.lock.Lock() defer m.lock.Unlock() + // Start 9p shares + shares, err := m.createShares() + if err != nil { + return err + } + m.MountVsocks = shares + if err := m.writeConfig(); err != nil { + return err + } + vmm := hypervctl.NewVirtualMachineManager() vm, err := vmm.GetMachine(m.Name) if err != nil { @@ -556,6 +605,10 @@ func (m *HyperVMachine) Start(name string, opts machine.StartOptions) error { // set starting back false now that we are running m.Starting = false + if err := m.startShares(); err != nil { + return err + } + if m.HostUser.Modified { if machine.UpdatePodmanDockerSockService(m, name, m.UID, m.Rootful) == nil { // Reset modification state if there are no errors, otherwise ignore errors @@ -591,7 +644,7 @@ func (m *HyperVMachine) Stop(name string, opts machine.StopOptions) error { vmm := hypervctl.NewVirtualMachineManager() vm, err := vmm.GetMachine(m.Name) if err != nil { - return err + return fmt.Errorf("getting virtual machine: %w", err) } vmState := vm.State() if vm.State() == hypervctl.Disabled { @@ -606,7 +659,7 @@ func (m *HyperVMachine) Stop(name string, opts machine.StopOptions) error { } if err := vm.Stop(); err != nil { - return err + return fmt.Errorf("stopping virtual machine: %w", err) } // keep track of last up @@ -696,22 +749,11 @@ func (m *HyperVMachine) startHostNetworking() (string, machine.APIForwardingStat return "", machine.NoForwarding, err } - dnr, dnw, err := machine.GetDevNullFiles() + executable, err := os.Executable() if err != nil { - return "", machine.NoForwarding, err + return "", 0, fmt.Errorf("unable to locate executable: %w", err) } - defer func() { - if err := dnr.Close(); err != nil { - logrus.Error(err) - } - }() - defer func() { - if err := dnw.Close(); err != nil { - logrus.Error(err) - } - }() - gvproxyBinary, err := cfg.FindHelperBinary("gvproxy.exe", false) if err != nil { return "", 0, err @@ -725,13 +767,58 @@ func (m *HyperVMachine) startHostNetworking() (string, machine.APIForwardingStat cmd, forwardSock, state = m.setupAPIForwarding(cmd) if logrus.IsLevelEnabled(logrus.DebugLevel) { cmd.Debug = true - logrus.Debug(cmd) } c := cmd.Cmd(gvproxyBinary) + + if logrus.IsLevelEnabled(logrus.DebugLevel) { + c.Stdout = os.Stdout + c.Stderr = os.Stderr + } + + logrus.Debugf("Starting gvproxy with command: %s %v", gvproxyBinary, c.Args) + if err := c.Start(); err != nil { return "", 0, fmt.Errorf("unable to execute: %s: %w", cmd.ToCmdline(), err) } + + logrus.Debugf("Got gvproxy PID as %d", c.Process.Pid) + + if len(m.MountVsocks) == 0 { + return forwardSock, state, nil + } + + // Start the 9p server in the background + args := []string{} + if logrus.IsLevelEnabled(logrus.DebugLevel) { + args = append(args, "--log-level=debug") + } + args = append(args, "machine", "server9p") + for dir, vsock := range m.MountVsocks { + for _, mount := range m.Mounts { + if mount.Target == dir { + args = append(args, "--serve", fmt.Sprintf("%s:%s", mount.Source, winio.VsockServiceID(uint32(vsock)).String())) + break + } + } + } + args = append(args, fmt.Sprintf("%d", c.Process.Pid)) + + logrus.Debugf("Going to start 9p server using command: %s %v", executable, args) + + fsCmd := exec.Command(executable, args...) + + if logrus.IsLevelEnabled(logrus.DebugLevel) { + fsCmd.Stdout = os.Stdout + fsCmd.Stderr = os.Stderr + } + + if err := fsCmd.Start(); err != nil { + return "", 0, fmt.Errorf("unable to execute: %s %v: %w", executable, args, err) + } + + logrus.Infof("Started podman 9p server as PID %d", fsCmd.Process.Pid) + return forwardSock, state, nil } @@ -804,3 +891,88 @@ func (m *HyperVMachine) resizeDisk(newSize strongunits.GiB) error { func (m *HyperVMachine) isStarting() bool { return m.Starting } + +func (m *HyperVMachine) createShares() (_ map[string]uint64, defErr error) { + toReturn := make(map[string]uint64) + + for _, mount := range m.Mounts { + var vsock *HVSockRegistryEntry + + vsockNum, ok := m.MountVsocks[mount.Target] + if ok { + // Ignore errors here, we'll just try and recreate the + // vsock below. + testVsock, err := LoadHVSockRegistryEntry(vsockNum) + if err == nil { + vsock = testVsock + } + } + if vsock == nil { + testVsock, err := NewHVSockRegistryEntry(m.Name, Fileserver) + if err != nil { + return nil, err + } + defer func() { + if defErr != nil { + if err := testVsock.Remove(); err != nil { + logrus.Errorf("Removing vsock: %v", err) + } + } + }() + vsock = testVsock + } + + logrus.Debugf("Going to share directory %s via 9p on vsock %d", mount.Source, vsock.Port) + + toReturn[mount.Target] = vsock.Port + } + + return toReturn, nil +} + +func (m *HyperVMachine) removeShares() error { + var removalErr error + + for _, mount := range m.Mounts { + vsockNum, ok := m.MountVsocks[mount.Target] + if !ok { + // Mount doesn't have a valid vsock, no need to tear down + continue + } + + vsock, err := LoadHVSockRegistryEntry(vsockNum) + if err != nil { + logrus.Debugf("Vsock %d for mountpoint %s does not have a valid registry entry, skipping removal", vsockNum, mount.Target) + continue + } + + if err := vsock.Remove(); err != nil { + if removalErr != nil { + logrus.Errorf("Error removing vsock: %w", removalErr) + } + removalErr = fmt.Errorf("removing vsock %d for mountpoint %s: %w", vsockNum, mount.Target, err) + } + } + + return removalErr +} + +func (m *HyperVMachine) startShares() error { + for mountpoint, sockNum := range m.MountVsocks { + args := []string{"-q", "--", "sudo", "podman"} + if logrus.IsLevelEnabled(logrus.DebugLevel) { + args = append(args, "--log-level=debug") + } + args = append(args, "machine", "client9p", fmt.Sprintf("%d", sockNum), mountpoint) + + sshOpts := machine.SSHOptions{ + Args: args, + } + + if err := m.SSH(m.Name, sshOpts); err != nil { + return err + } + } + + return nil +} diff --git a/pkg/machine/hyperv/vsock.go b/pkg/machine/hyperv/vsock.go index 13136d4b1a..869905f01d 100644 --- a/pkg/machine/hyperv/vsock.go +++ b/pkg/machine/hyperv/vsock.go @@ -7,6 +7,7 @@ import ( "bufio" "errors" "fmt" + "net" "strings" "github.com/Microsoft/go-winio" @@ -36,6 +37,8 @@ const ( Network HVSockPurpose = iota // Events implies the sock is used for notification (like "Ready") Events + // Fileserver implies that the sock is used for serving files from host to VM + Fileserver ) func (hv HVSockPurpose) string() string { @@ -44,6 +47,8 @@ func (hv HVSockPurpose) string() string { return "Network" case Events: return "Events" + case Fileserver: + return "Fileserver" } return "" } @@ -58,6 +63,8 @@ func toHVSockPurpose(p string) (HVSockPurpose, error) { return Network, nil case "Events": return Events, nil + case "Fileserver": + return Fileserver, nil } return 0, fmt.Errorf("unknown hvsockpurpose: %s", p) } @@ -239,14 +246,24 @@ func LoadHVSockRegistryEntry(port uint64) (*HVSockRegistryEntry, error) { }, nil } -// Listen is used on the windows side to listen for anything to come -// over the hvsock as a signal the vm is booted -func (hv *HVSockRegistryEntry) Listen() error { +// Listener returns a net.Listener for the given HvSock. +func (hv *HVSockRegistryEntry) Listener() (net.Listener, error) { n := winio.HvsockAddr{ VMID: winio.HvsockGUIDWildcard(), // When listening on the host side, use equiv of 0.0.0.0 ServiceID: winio.VsockServiceID(uint32(hv.Port)), } listener, err := winio.ListenHvsock(&n) + if err != nil { + return nil, err + } + + return listener, nil +} + +// Listen is used on the windows side to listen for anything to come +// over the hvsock as a signal the vm is booted +func (hv *HVSockRegistryEntry) Listen() error { + listener, err := hv.Listener() if err != nil { return err } diff --git a/pkg/machine/machine_windows.go b/pkg/machine/machine_windows.go index 2372644483..fe1d2fa5c3 100644 --- a/pkg/machine/machine_windows.go +++ b/pkg/machine/machine_windows.go @@ -7,17 +7,22 @@ import ( "os" "syscall" "time" + + "github.com/sirupsen/logrus" ) func GetProcessState(pid int) (active bool, exitCode int) { const da = syscall.STANDARD_RIGHTS_READ | syscall.PROCESS_QUERY_INFORMATION | syscall.SYNCHRONIZE handle, err := syscall.OpenProcess(da, false, uint32(pid)) if err != nil { + logrus.Debugf("Error retrieving process %d: %v", pid, err) return false, int(syscall.ERROR_PROC_NOT_FOUND) } var code uint32 - syscall.GetExitCodeProcess(handle, &code) + if err := syscall.GetExitCodeProcess(handle, &code); err != nil { + logrus.Errorf("Error retrieving process %d exit code: %v", pid, err) + } return code == 259, int(code) } diff --git a/pkg/util/utils_supported.go b/pkg/util/utils_supported.go index 406d56ce6f..6e87b0456b 100644 --- a/pkg/util/utils_supported.go +++ b/pkg/util/utils_supported.go @@ -14,6 +14,7 @@ import ( "strconv" "syscall" + "github.com/containers/podman/v4/libpod/define" "github.com/containers/podman/v4/pkg/rootless" "github.com/sirupsen/logrus" ) @@ -118,3 +119,9 @@ func GetRootlessPauseProcessPidPath() (string, error) { // the tmpdir which can be changed via --tmpdir. return filepath.Join(runtimeDir, "libpod", "tmp", "pause.pid"), nil } + +// WaitForPIDExit waits for a PID to exit. +// Not implemented for Linux at this time, only for Windows. +func WaitForPIDExit(pid uint) error { + return define.ErrNotImplemented +} diff --git a/pkg/util/utils_windows.go b/pkg/util/utils_windows.go index 1e48eb5721..2be774f316 100644 --- a/pkg/util/utils_windows.go +++ b/pkg/util/utils_windows.go @@ -9,6 +9,7 @@ import ( "path/filepath" "github.com/containers/storage/pkg/homedir" + "golang.org/x/sys/windows" ) var errNotImplemented = errors.New("not yet implemented") @@ -44,3 +45,35 @@ func GetRuntimeDir() (string, error) { func GetRootlessConfigHomeDir() (string, error) { return "", errors.New("this function is not implemented for windows") } + +// Wait until the given PID exits. Returns nil if wait was successful, errors on +// unexpected condition (IE, pid was not valid) +func WaitForPIDExit(pid uint) error { + const PROCESS_ALL_ACCESS = 0x1F0FFF + + // We need to turn the PID into a Windows handle. + // To do this we need Windows' OpenProcess func. + // To get that, we need to open the kernel32 DLL. + kernel32, err := windows.LoadDLL("kernel32.dll") + if err != nil { + return fmt.Errorf("loading kernel32 dll: %w", err) + } + + openProc, err := kernel32.FindProc("OpenProcess") + if err != nil { + return fmt.Errorf("loading OpenProcess API: %w", err) + } + + handle, _, err := openProc.Call(uintptr(PROCESS_ALL_ACCESS), uintptr(1), uintptr(pid)) + if err != nil { + return fmt.Errorf("converting PID to handle: %w", err) + } + + // We can now wait for the handle. + _, err = windows.WaitForSingleObject(windows.Handle(handle), 0) + if err != nil { + return fmt.Errorf("waiting for handle: %w", err) + } + + return nil +} diff --git a/vendor/github.com/hugelgupf/p9/LICENSE b/vendor/github.com/hugelgupf/p9/LICENSE new file mode 100644 index 0000000000..d645695673 --- /dev/null +++ b/vendor/github.com/hugelgupf/p9/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/hugelgupf/p9/fsimpl/localfs/localfs.go b/vendor/github.com/hugelgupf/p9/fsimpl/localfs/localfs.go new file mode 100644 index 0000000000..c8ef1b7792 --- /dev/null +++ b/vendor/github.com/hugelgupf/p9/fsimpl/localfs/localfs.go @@ -0,0 +1,281 @@ +// Copyright 2018 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package localfs exposes the host's local file system as a p9.File. +package localfs + +import ( + "os" + "path" + + "github.com/hugelgupf/p9/fsimpl/templatefs" + "github.com/hugelgupf/p9/internal" + "github.com/hugelgupf/p9/linux" + "github.com/hugelgupf/p9/p9" +) + +type attacher struct { + root string +} + +var ( + _ p9.Attacher = &attacher{} +) + +// RootAttacher attaches at the host file system's root. +func RootAttacher() p9.Attacher { + return &attacher{root: "/"} +} + +// Attacher returns an attacher that exposes files under root. +func Attacher(root string) p9.Attacher { + if len(root) == 0 { + root = "/" + } + return &attacher{root: root} +} + +// Attach implements p9.Attacher.Attach. +func (a *attacher) Attach() (p9.File, error) { + umask(0) + return &Local{path: a.root}, nil +} + +// Local is a p9.File. +type Local struct { + p9.DefaultWalkGetAttr + templatefs.NoopFile + + path string + file *os.File +} + +var ( + _ p9.File = &Local{} +) + +// info constructs a QID for this file. +func (l *Local) info() (p9.QID, os.FileInfo, error) { + var ( + qid p9.QID + fi os.FileInfo + err error + ) + + // Stat the file. + if l.file != nil { + fi, err = l.file.Stat() + } else { + fi, err = os.Lstat(l.path) + } + if err != nil { + return qid, nil, err + } + + // Construct the QID type. + qid.Type = p9.ModeFromOS(fi.Mode()).QIDType() + + // Save the path from the Ino. + ninePath, err := localToQid(l.path, fi) + if err != nil { + return qid, nil, err + } + + qid.Path = ninePath + + return qid, fi, nil +} + +// Walk implements p9.File.Walk. +func (l *Local) Walk(names []string) ([]p9.QID, p9.File, error) { + var qids []p9.QID + last := &Local{path: l.path} + + // A walk with no names is a copy of self. + if len(names) == 0 { + return nil, last, nil + } + + for _, name := range names { + c := &Local{path: path.Join(last.path, name)} + qid, _, err := c.info() + if err != nil { + return nil, nil, err + } + qids = append(qids, qid) + last = c + } + return qids, last, nil +} + +// FSync implements p9.File.FSync. +func (l *Local) FSync() error { + return l.file.Sync() +} + +// GetAttr implements p9.File.GetAttr. +// +// Not fully implemented. +func (l *Local) GetAttr(req p9.AttrMask) (p9.QID, p9.AttrMask, p9.Attr, error) { + qid, fi, err := l.info() + if err != nil { + return qid, p9.AttrMask{}, p9.Attr{}, err + } + + stat := internal.InfoToStat(fi) + attr := &p9.Attr{ + Mode: p9.FileMode(stat.Mode), + UID: p9.UID(stat.Uid), + GID: p9.GID(stat.Gid), + NLink: p9.NLink(stat.Nlink), + RDev: p9.Dev(stat.Rdev), + Size: uint64(stat.Size), + BlockSize: uint64(stat.Blksize), + Blocks: uint64(stat.Blocks), + ATimeSeconds: uint64(stat.Atim.Sec), + ATimeNanoSeconds: uint64(stat.Atim.Nsec), + MTimeSeconds: uint64(stat.Mtim.Sec), + MTimeNanoSeconds: uint64(stat.Mtim.Nsec), + CTimeSeconds: uint64(stat.Ctim.Sec), + CTimeNanoSeconds: uint64(stat.Ctim.Nsec), + } + return qid, req, *attr, nil +} + +// Close implements p9.File.Close. +func (l *Local) Close() error { + if l.file != nil { + // We don't set l.file = nil, as Close is called by servers + // only in Clunk. Clunk should release the last (direct) + // reference to this file. + return l.file.Close() + } + return nil +} + +// Open implements p9.File.Open. +func (l *Local) Open(mode p9.OpenFlags) (p9.QID, uint32, error) { + qid, _, err := l.info() + if err != nil { + return qid, 0, err + } + + // Do the actual open. + f, err := os.OpenFile(l.path, int(mode), 0) + if err != nil { + return qid, 0, err + } + l.file = f + + return qid, 0, nil +} + +// ReadAt implements p9.File.ReadAt. +func (l *Local) ReadAt(p []byte, offset int64) (int, error) { + return l.file.ReadAt(p, offset) +} + +// Lock implements p9.File.Lock. +func (l *Local) Lock(pid int, locktype p9.LockType, flags p9.LockFlags, start, length uint64, client string) (p9.LockStatus, error) { + return l.lock(pid, locktype, flags, start, length, client) +} + +// WriteAt implements p9.File.WriteAt. +func (l *Local) WriteAt(p []byte, offset int64) (int, error) { + return l.file.WriteAt(p, offset) +} + +// Create implements p9.File.Create. +func (l *Local) Create(name string, mode p9.OpenFlags, permissions p9.FileMode, _ p9.UID, _ p9.GID) (p9.File, p9.QID, uint32, error) { + newName := path.Join(l.path, name) + f, err := os.OpenFile(newName, int(mode)|os.O_CREATE|os.O_EXCL, os.FileMode(permissions)) + if err != nil { + return nil, p9.QID{}, 0, err + } + + l2 := &Local{path: newName, file: f} + qid, _, err := l2.info() + if err != nil { + l2.Close() + return nil, p9.QID{}, 0, err + } + return l2, qid, 0, nil +} + +// Mkdir implements p9.File.Mkdir. +// +// Not properly implemented. +func (l *Local) Mkdir(name string, permissions p9.FileMode, _ p9.UID, _ p9.GID) (p9.QID, error) { + if err := os.Mkdir(path.Join(l.path, name), os.FileMode(permissions)); err != nil { + return p9.QID{}, err + } + + // Blank QID. + return p9.QID{}, nil +} + +// Symlink implements p9.File.Symlink. +// +// Not properly implemented. +func (l *Local) Symlink(oldname string, newname string, _ p9.UID, _ p9.GID) (p9.QID, error) { + if err := os.Symlink(oldname, path.Join(l.path, newname)); err != nil { + return p9.QID{}, err + } + + // Blank QID. + return p9.QID{}, nil +} + +// Link implements p9.File.Link. +// +// Not properly implemented. +func (l *Local) Link(target p9.File, newname string) error { + return os.Link(target.(*Local).path, path.Join(l.path, newname)) +} + +// RenameAt implements p9.File.RenameAt. +func (l *Local) RenameAt(oldName string, newDir p9.File, newName string) error { + oldPath := path.Join(l.path, oldName) + newPath := path.Join(newDir.(*Local).path, newName) + + return os.Rename(oldPath, newPath) +} + +// Readlink implements p9.File.Readlink. +// +// Not properly implemented. +func (l *Local) Readlink() (string, error) { + return os.Readlink(l.path) +} + +// Renamed implements p9.File.Renamed. +func (l *Local) Renamed(parent p9.File, newName string) { + l.path = path.Join(parent.(*Local).path, newName) +} + +// SetAttr implements p9.File.SetAttr. +func (l *Local) SetAttr(valid p9.SetAttrMask, attr p9.SetAttr) error { + // When truncate(2) is called on Linux, Linux will try to set time & size. Fake it. Sorry. + supported := p9.SetAttrMask{Size: true, MTime: true, CTime: true} + if !valid.IsSubsetOf(supported) { + return linux.ENOSYS + } + + if valid.Size { + // If more than one thing is ever implemented, we can't just + // return an error here. + return os.Truncate(l.path, int64(attr.Size)) + } + return nil +} diff --git a/vendor/github.com/hugelgupf/p9/fsimpl/localfs/readdir.go b/vendor/github.com/hugelgupf/p9/fsimpl/localfs/readdir.go new file mode 100644 index 0000000000..6bb0edf7e1 --- /dev/null +++ b/vendor/github.com/hugelgupf/p9/fsimpl/localfs/readdir.go @@ -0,0 +1,50 @@ +package localfs + +import ( + "io" + "path" + + "github.com/hugelgupf/p9/p9" +) + +// Readdir implements p9.File.Readdir. +func (l *Local) Readdir(offset uint64, count uint32) (p9.Dirents, error) { + var ( + p9Ents = make([]p9.Dirent, 0) + cursor = uint64(0) + ) + + for len(p9Ents) < int(count) { + singleEnt, err := l.file.Readdirnames(1) + + if err == io.EOF { + return p9Ents, nil + } else if err != nil { + return nil, err + } + + // we consumed an entry + cursor++ + + // cursor \in (offset, offset+count) + if cursor < offset || cursor > offset+uint64(count) { + continue + } + + name := singleEnt[0] + + localEnt := Local{path: path.Join(l.path, name)} + qid, _, err := localEnt.info() + if err != nil { + return p9Ents, err + } + p9Ents = append(p9Ents, p9.Dirent{ + QID: qid, + Type: qid.Type, + Name: name, + Offset: cursor, + }) + } + + return p9Ents, nil +} diff --git a/vendor/github.com/hugelgupf/p9/fsimpl/localfs/system_unix.go b/vendor/github.com/hugelgupf/p9/fsimpl/localfs/system_unix.go new file mode 100644 index 0000000000..ad82a12d85 --- /dev/null +++ b/vendor/github.com/hugelgupf/p9/fsimpl/localfs/system_unix.go @@ -0,0 +1,40 @@ +//go:build !windows +// +build !windows + +package localfs + +import ( + "os" + "syscall" + + "github.com/hugelgupf/p9/p9" + "golang.org/x/sys/unix" +) + +func umask(mask int) int { + return syscall.Umask(mask) +} + +func localToQid(_ string, fi os.FileInfo) (uint64, error) { + return uint64(fi.Sys().(*syscall.Stat_t).Ino), nil +} + +// lock implements p9.File.Lock. +func (l *Local) lock(pid int, locktype p9.LockType, flags p9.LockFlags, start, length uint64, client string) (p9.LockStatus, error) { + switch locktype { + case p9.ReadLock, p9.WriteLock: + if err := unix.Flock(int(l.file.Fd()), unix.LOCK_EX); err != nil { + return p9.LockStatusError, nil + } + + case p9.Unlock: + if err := unix.Flock(int(l.file.Fd()), unix.LOCK_EX); err != nil { + return p9.LockStatusError, nil + } + + default: + return p9.LockStatusOK, unix.ENOSYS + } + + return p9.LockStatusOK, nil +} diff --git a/vendor/github.com/hugelgupf/p9/fsimpl/localfs/system_windows.go b/vendor/github.com/hugelgupf/p9/fsimpl/localfs/system_windows.go new file mode 100644 index 0000000000..e845b2e0a5 --- /dev/null +++ b/vendor/github.com/hugelgupf/p9/fsimpl/localfs/system_windows.go @@ -0,0 +1,50 @@ +package localfs + +import ( + "os" + + "github.com/hugelgupf/p9/linux" + "github.com/hugelgupf/p9/p9" + "golang.org/x/sys/windows" +) + +func umask(_ int) int { + return 0 +} + +func localToQid(path string, info os.FileInfo) (uint64, error) { + pathPtr, err := windows.UTF16PtrFromString(path) + if err != nil { + return 0, err + } + + var ( + access uint32 // none; we only need metadata + sharemode uint32 + createmode uint32 = windows.OPEN_EXISTING + attribute uint32 = windows.FILE_ATTRIBUTE_NORMAL + ) + if info.IsDir() { + attribute = windows.FILE_FLAG_BACKUP_SEMANTICS + } + fd, err := windows.CreateFile(pathPtr, access, sharemode, nil, createmode, attribute, 0) + if err != nil { + return 0, err + } + + fi := &windows.ByHandleFileInformation{} + if err = windows.GetFileInformationByHandle(fd, fi); err != nil { + return 0, err + } + + x := uint64(fi.FileIndexHigh)<<32 | uint64(fi.FileIndexLow) + return x, nil +} + +// lock implements p9.File.Lock. +// As in FreeBSD NFS locking, we just say "sure, we did it" without actually +// doing anything; this lock design makes even less sense on Windows than +// it does on Linux (pid? really? what were they thinking?) +func (l *Local) lock(pid int, locktype p9.LockType, flags p9.LockFlags, start, length uint64, client string) (p9.LockStatus, error) { + return p9.LockStatusOK, linux.ENOSYS +} diff --git a/vendor/github.com/hugelgupf/p9/fsimpl/localfs/system_xattr.go b/vendor/github.com/hugelgupf/p9/fsimpl/localfs/system_xattr.go new file mode 100644 index 0000000000..00057bd9c0 --- /dev/null +++ b/vendor/github.com/hugelgupf/p9/fsimpl/localfs/system_xattr.go @@ -0,0 +1,25 @@ +//go:build unix && !solaris && !openbsd + +package localfs + +import ( + "github.com/hugelgupf/p9/fsimpl/xattr" + "github.com/hugelgupf/p9/p9" + "golang.org/x/sys/unix" +) + +func (l *Local) SetXattr(attr string, data []byte, flags p9.XattrFlags) error { + return unix.Setxattr(l.path, attr, data, int(flags)) +} + +func (l *Local) ListXattrs() ([]string, error) { + return xattr.List(l.path) +} + +func (l *Local) GetXattr(attr string) ([]byte, error) { + return xattr.Get(l.path, attr) +} + +func (l *Local) RemoveXattr(attr string) error { + return unix.Removexattr(l.path, attr) +} diff --git a/vendor/github.com/hugelgupf/p9/fsimpl/templatefs/readonly.go b/vendor/github.com/hugelgupf/p9/fsimpl/templatefs/readonly.go new file mode 100644 index 0000000000..b9223e39da --- /dev/null +++ b/vendor/github.com/hugelgupf/p9/fsimpl/templatefs/readonly.go @@ -0,0 +1,194 @@ +package templatefs + +import ( + "github.com/hugelgupf/p9/linux" + "github.com/hugelgupf/p9/p9" +) + +// NotSymlinkFile denies Readlink with EINVAL. +// +// EINVAL is returned by readlink(2) when the file is not a symlink. +type NotSymlinkFile struct{} + +// Readlink implements p9.File.Readlink. +func (NotSymlinkFile) Readlink() (string, error) { + return "", linux.EINVAL +} + +// NotDirectoryFile denies any directory operations with ENOTDIR. +// +// Those operations are Create, Mkdir, Symlink, Link, Mknod, RenameAt, +// UnlinkAt, and Readdir. +type NotDirectoryFile struct{} + +// Create implements p9.File.Create. +func (NotDirectoryFile) Create(name string, mode p9.OpenFlags, permissions p9.FileMode, _ p9.UID, _ p9.GID) (p9.File, p9.QID, uint32, error) { + return nil, p9.QID{}, 0, linux.ENOTDIR +} + +// Mkdir implements p9.File.Mkdir. +func (NotDirectoryFile) Mkdir(name string, permissions p9.FileMode, _ p9.UID, _ p9.GID) (p9.QID, error) { + return p9.QID{}, linux.ENOTDIR +} + +// Symlink implements p9.File.Symlink. +func (NotDirectoryFile) Symlink(oldname string, newname string, _ p9.UID, _ p9.GID) (p9.QID, error) { + return p9.QID{}, linux.ENOTDIR +} + +// Link implements p9.File.Link. +func (NotDirectoryFile) Link(target p9.File, newname string) error { + return linux.ENOTDIR +} + +// Mknod implements p9.File.Mknod. +func (NotDirectoryFile) Mknod(name string, mode p9.FileMode, major uint32, minor uint32, _ p9.UID, _ p9.GID) (p9.QID, error) { + return p9.QID{}, linux.ENOTDIR +} + +// RenameAt implements p9.File.RenameAt. +func (NotDirectoryFile) RenameAt(oldname string, newdir p9.File, newname string) error { + return linux.ENOTDIR +} + +// UnlinkAt implements p9.File.UnlinkAt. +func (NotDirectoryFile) UnlinkAt(name string, flags uint32) error { + return linux.ENOTDIR +} + +// Readdir implements p9.File.Readdir. +func (NotDirectoryFile) Readdir(offset uint64, count uint32) (p9.Dirents, error) { + return nil, linux.ENOTDIR +} + +// ReadOnlyFile returns default denials for all methods except Open, ReadAt, +// Walk, Close, and GetAttr. +// +// Returns EROFS for most modifying operations, ENOTDIR for file creation ops +// or readdir, EINVAL for readlink, xattr and lock operations return ENOSYS. +// +// Does nothing for Renamed. +type ReadOnlyFile struct { + NotSymlinkFile + NotDirectoryFile + XattrUnimplemented + NoopRenamed + NotLockable +} + +// FSync implements p9.File.FSync. +func (ReadOnlyFile) FSync() error { + return linux.EROFS +} + +// SetAttr implements p9.File.SetAttr. +func (ReadOnlyFile) SetAttr(valid p9.SetAttrMask, attr p9.SetAttr) error { + return linux.EROFS +} + +// Remove implements p9.File.Remove. +func (ReadOnlyFile) Remove() error { + return linux.EROFS +} + +// Rename implements p9.File.Rename. +func (ReadOnlyFile) Rename(directory p9.File, name string) error { + return linux.EROFS +} + +// WriteAt implements p9.File.WriteAt. +func (ReadOnlyFile) WriteAt(p []byte, offset int64) (int, error) { + return 0, linux.EROFS +} + +// Flush implements p9.File.Flush. +func (ReadOnlyFile) Flush() error { + return nil +} + +// ReadOnlyDir implements default denials for all methods except Walk, Open, +// GetAttr, Readdir, Close. +// +// Creation operations return EROFS. Read/write operations return EISDIR. +// EINVAL for readlink. Renaming does nothing by default, xattr and locking are +// unimplemented. +type ReadOnlyDir struct { + NotSymlinkFile + IsDir + XattrUnimplemented + NoopRenamed + NotLockable +} + +// Create implements p9.File.Create. +func (ReadOnlyDir) Create(name string, mode p9.OpenFlags, permissions p9.FileMode, _ p9.UID, _ p9.GID) (p9.File, p9.QID, uint32, error) { + return nil, p9.QID{}, 0, linux.EROFS +} + +// Mkdir implements p9.File.Mkdir. +func (ReadOnlyDir) Mkdir(name string, permissions p9.FileMode, _ p9.UID, _ p9.GID) (p9.QID, error) { + return p9.QID{}, linux.EROFS +} + +// Symlink implements p9.File.Symlink. +func (ReadOnlyDir) Symlink(oldname string, newname string, _ p9.UID, _ p9.GID) (p9.QID, error) { + return p9.QID{}, linux.EROFS +} + +// Link implements p9.File.Link. +func (ReadOnlyDir) Link(target p9.File, newname string) error { + return linux.EROFS +} + +// Mknod implements p9.File.Mknod. +func (ReadOnlyDir) Mknod(name string, mode p9.FileMode, major uint32, minor uint32, _ p9.UID, _ p9.GID) (p9.QID, error) { + return p9.QID{}, linux.EROFS +} + +// RenameAt implements p9.File.RenameAt. +func (ReadOnlyDir) RenameAt(oldname string, newdir p9.File, newname string) error { + return linux.EROFS +} + +// UnlinkAt implements p9.File.UnlinkAt. +func (ReadOnlyDir) UnlinkAt(name string, flags uint32) error { + return linux.EROFS +} + +// Readdir implements p9.File.Readdir. +func (ReadOnlyDir) Readdir(offset uint64, count uint32) (p9.Dirents, error) { + return nil, linux.EROFS +} + +// FSync implements p9.File.FSync. +func (ReadOnlyDir) FSync() error { + return linux.EROFS +} + +// SetAttr implements p9.File.SetAttr. +func (ReadOnlyDir) SetAttr(valid p9.SetAttrMask, attr p9.SetAttr) error { + return linux.EROFS +} + +// Remove implements p9.File.Remove. +func (ReadOnlyDir) Remove() error { + return linux.EROFS +} + +// Rename implements p9.File.Rename. +func (ReadOnlyDir) Rename(directory p9.File, name string) error { + return linux.EROFS +} + +// IsDir returns EISDIR for ReadAt and WriteAt. +type IsDir struct{} + +// WriteAt implements p9.File.WriteAt. +func (IsDir) WriteAt(p []byte, offset int64) (int, error) { + return 0, linux.EISDIR +} + +// ReadAt implements p9.File.ReadAt. +func (IsDir) ReadAt(p []byte, offset int64) (int, error) { + return 0, linux.EISDIR +} diff --git a/vendor/github.com/hugelgupf/p9/fsimpl/templatefs/unimplfs.go b/vendor/github.com/hugelgupf/p9/fsimpl/templatefs/unimplfs.go new file mode 100644 index 0000000000..a12edf58c2 --- /dev/null +++ b/vendor/github.com/hugelgupf/p9/fsimpl/templatefs/unimplfs.go @@ -0,0 +1,193 @@ +// Copyright 2018 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package templatefs provides template p9.Files. +// +// NoopFile can be used to leave some methods unimplemented in incomplete +// p9.File implementations. +// +// NilCloser, ReadOnlyFile, NotDirectoryFile, and NotSymlinkFile can be used as +// templates for commonly implemented file types. They are careful not to +// conflict with each others' methods, so they can be embedded together. +package templatefs + +import ( + "github.com/hugelgupf/p9/linux" + "github.com/hugelgupf/p9/p9" +) + +// NilCloser returns nil for Close. +type NilCloser struct{} + +// Close implements p9.File.Close. +func (NilCloser) Close() error { + return nil +} + +// NilSyncer returns nil for FSync. +type NilSyncer struct{} + +// FSync implements p9.File.FSync. +func (NilSyncer) FSync() error { + return nil +} + +// NoopRenamed does nothing when the file is renamed. +type NoopRenamed struct{} + +// Renamed implements p9.File.Renamed. +func (NoopRenamed) Renamed(parent p9.File, newName string) {} + +// NotImplementedFile is a p9.File that returns ENOSYS for every listed method. +// +// Compatible with NoopRenamed, NilCloser, and NilSyncer. +type NotImplementedFile struct { + p9.DefaultWalkGetAttr + NotLockable + XattrUnimplemented +} + +// NoopFile is a p9.File with every method unimplemented. +type NoopFile struct { + NotImplementedFile + NilCloser + NilSyncer + NoopRenamed +} + +var ( + _ p9.File = &NoopFile{} +) + +// Walk implements p9.File.Walk. +func (NotImplementedFile) Walk(names []string) ([]p9.QID, p9.File, error) { + return nil, nil, linux.ENOSYS +} + +// StatFS implements p9.File.StatFS. +// +// Not implemented. +func (NotImplementedFile) StatFS() (p9.FSStat, error) { + return p9.FSStat{}, linux.ENOSYS +} + +// Open implements p9.File.Open. +func (NotImplementedFile) Open(mode p9.OpenFlags) (p9.QID, uint32, error) { + return p9.QID{}, 0, linux.ENOSYS +} + +// ReadAt implements p9.File.ReadAt. +func (NotImplementedFile) ReadAt(p []byte, offset int64) (int, error) { + return 0, linux.ENOSYS +} + +// GetAttr implements p9.File.GetAttr. +func (NotImplementedFile) GetAttr(req p9.AttrMask) (p9.QID, p9.AttrMask, p9.Attr, error) { + return p9.QID{}, p9.AttrMask{}, p9.Attr{}, linux.ENOSYS +} + +// SetAttr implements p9.File.SetAttr. +func (NotImplementedFile) SetAttr(valid p9.SetAttrMask, attr p9.SetAttr) error { + return linux.ENOSYS +} + +// Remove implements p9.File.Remove. +func (NotImplementedFile) Remove() error { + return linux.ENOSYS +} + +// Rename implements p9.File.Rename. +func (NotImplementedFile) Rename(directory p9.File, name string) error { + return linux.ENOSYS +} + +// WriteAt implements p9.File.WriteAt. +func (NotImplementedFile) WriteAt(p []byte, offset int64) (int, error) { + return 0, linux.ENOSYS +} + +// Create implements p9.File.Create. +func (NotImplementedFile) Create(name string, mode p9.OpenFlags, permissions p9.FileMode, _ p9.UID, _ p9.GID) (p9.File, p9.QID, uint32, error) { + return nil, p9.QID{}, 0, linux.ENOSYS +} + +// Mkdir implements p9.File.Mkdir. +func (NotImplementedFile) Mkdir(name string, permissions p9.FileMode, _ p9.UID, _ p9.GID) (p9.QID, error) { + return p9.QID{}, linux.ENOSYS +} + +// Symlink implements p9.File.Symlink. +func (NotImplementedFile) Symlink(oldname string, newname string, _ p9.UID, _ p9.GID) (p9.QID, error) { + return p9.QID{}, linux.ENOSYS +} + +// Link implements p9.File.Link. +func (NotImplementedFile) Link(target p9.File, newname string) error { + return linux.ENOSYS +} + +// Mknod implements p9.File.Mknod. +func (NotImplementedFile) Mknod(name string, mode p9.FileMode, major uint32, minor uint32, _ p9.UID, _ p9.GID) (p9.QID, error) { + return p9.QID{}, linux.ENOSYS +} + +// RenameAt implements p9.File.RenameAt. +func (NotImplementedFile) RenameAt(oldname string, newdir p9.File, newname string) error { + return linux.ENOSYS +} + +// UnlinkAt implements p9.File.UnlinkAt. +func (NotImplementedFile) UnlinkAt(name string, flags uint32) error { + return linux.ENOSYS +} + +// Readdir implements p9.File.Readdir. +func (NotImplementedFile) Readdir(offset uint64, count uint32) (p9.Dirents, error) { + return nil, linux.ENOSYS +} + +// Readlink implements p9.File.Readlink. +func (NotImplementedFile) Readlink() (string, error) { + return "", linux.ENOSYS +} + +// XattrUnimplemented implements Xattr methods returning ENOSYS. +type XattrUnimplemented struct{} + +// SetXattr implements p9.File.SetXattr. +func (XattrUnimplemented) SetXattr(attr string, data []byte, flags p9.XattrFlags) error { + return linux.ENOSYS +} + +// GetXattr implements p9.File.GetXattr. +func (XattrUnimplemented) GetXattr(attr string) ([]byte, error) { + return nil, linux.ENOSYS +} + +// ListXattrs implements p9.File.ListXattrs. +func (XattrUnimplemented) ListXattrs() ([]string, error) { + return nil, linux.ENOSYS +} + +// RemoveXattr implements p9.File.RemoveXattr. +func (XattrUnimplemented) RemoveXattr(attr string) error { + return linux.ENOSYS +} + +type NotLockable struct{} + +// Lock implements p9.File.Lock. +func (NotLockable) Lock(pid int, locktype p9.LockType, flags p9.LockFlags, start, length uint64, client string) (p9.LockStatus, error) { + return p9.LockStatusOK, linux.ENOSYS +} diff --git a/vendor/github.com/hugelgupf/p9/fsimpl/xattr/xattr.go b/vendor/github.com/hugelgupf/p9/fsimpl/xattr/xattr.go new file mode 100644 index 0000000000..fadd2dd040 --- /dev/null +++ b/vendor/github.com/hugelgupf/p9/fsimpl/xattr/xattr.go @@ -0,0 +1,39 @@ +//go:build unix && !openbsd && !solaris + +package xattr + +import ( + "io/fs" + "strings" + + "golang.org/x/sys/unix" +) + +func List(p string) ([]string, error) { + sz, err := unix.Listxattr(p, nil) + if err != nil { + return nil, &fs.PathError{Op: "listxattr-get-size", Path: p, Err: err} + } + + b := make([]byte, sz) + sz, err = unix.Listxattr(p, b) + if err != nil { + return nil, &fs.PathError{Op: "listxattr", Path: p, Err: err} + } + + return strings.Split(strings.Trim(string(b[:sz]), "\000"), "\000"), nil +} + +func Get(p string, attr string) ([]byte, error) { + sz, err := unix.Getxattr(p, attr, nil) + if err != nil { + return nil, &fs.PathError{Op: "getxattr-get-size", Path: p, Err: err} + } + + b := make([]byte, sz) + sz, err = unix.Getxattr(p, attr, b) + if err != nil { + return nil, &fs.PathError{Op: "getxattr", Path: p, Err: err} + } + return b[:sz], nil +} diff --git a/vendor/github.com/hugelgupf/p9/fsimpl/xattr/xattr_windows.go b/vendor/github.com/hugelgupf/p9/fsimpl/xattr/xattr_windows.go new file mode 100644 index 0000000000..835e8b7046 --- /dev/null +++ b/vendor/github.com/hugelgupf/p9/fsimpl/xattr/xattr_windows.go @@ -0,0 +1,11 @@ +package xattr + +import "github.com/hugelgupf/p9/linux" + +func List(p string) ([]string, error) { + return nil, linux.ENOSYS +} + +func Get(p string, attr string) ([]byte, error) { + return nil, linux.ENOSYS +} diff --git a/vendor/github.com/hugelgupf/p9/internal/doc.go b/vendor/github.com/hugelgupf/p9/internal/doc.go new file mode 100644 index 0000000000..fef18691bf --- /dev/null +++ b/vendor/github.com/hugelgupf/p9/internal/doc.go @@ -0,0 +1,2 @@ +// Package sys abstracts operating system features for p9. +package internal diff --git a/vendor/github.com/hugelgupf/p9/internal/stat_bsd.go b/vendor/github.com/hugelgupf/p9/internal/stat_bsd.go new file mode 100644 index 0000000000..e0ce5c7608 --- /dev/null +++ b/vendor/github.com/hugelgupf/p9/internal/stat_bsd.go @@ -0,0 +1,31 @@ +//go:build freebsd || darwin || netbsd +// +build freebsd darwin netbsd + +package internal + +import ( + "os" + "syscall" + + "golang.org/x/sys/unix" +) + +// InfoToStat takes a platform native FileInfo and converts it into a 9P2000.L compatible Stat_t +func InfoToStat(fi os.FileInfo) *Stat_t { + nativeStat := fi.Sys().(*syscall.Stat_t) + return &Stat_t{ + Dev: nativeStat.Dev, + Ino: nativeStat.Ino, + Nlink: nativeStat.Nlink, + Mode: nativeStat.Mode, + Uid: nativeStat.Uid, + Gid: nativeStat.Gid, + Rdev: nativeStat.Rdev, + Size: nativeStat.Size, + Blksize: nativeStat.Blksize, + Blocks: nativeStat.Blocks, + Atim: unix.NsecToTimespec(syscall.TimespecToNsec(nativeStat.Atimespec)), + Mtim: unix.NsecToTimespec(syscall.TimespecToNsec(nativeStat.Mtimespec)), + Ctim: unix.NsecToTimespec(syscall.TimespecToNsec(nativeStat.Ctimespec)), + } +} diff --git a/vendor/github.com/hugelgupf/p9/internal/stat_openbsd.go b/vendor/github.com/hugelgupf/p9/internal/stat_openbsd.go new file mode 100644 index 0000000000..5549e63f04 --- /dev/null +++ b/vendor/github.com/hugelgupf/p9/internal/stat_openbsd.go @@ -0,0 +1,28 @@ +package internal + +import ( + "os" + "syscall" + + "golang.org/x/sys/unix" +) + +// InfoToStat takes a platform native FileInfo and converts it into a 9P2000.L compatible Stat_t +func InfoToStat(fi os.FileInfo) *Stat_t { + nativeStat := fi.Sys().(*syscall.Stat_t) + return &Stat_t{ + Dev: nativeStat.Dev, + Ino: nativeStat.Ino, + Nlink: nativeStat.Nlink, + Mode: nativeStat.Mode, + Uid: nativeStat.Uid, + Gid: nativeStat.Gid, + Rdev: nativeStat.Rdev, + Size: nativeStat.Size, + Blksize: int32(nativeStat.Blksize), + Blocks: nativeStat.Blocks, + Atim: unix.NsecToTimespec(syscall.TimespecToNsec(nativeStat.Atim)), + Mtim: unix.NsecToTimespec(syscall.TimespecToNsec(nativeStat.Mtim)), + Ctim: unix.NsecToTimespec(syscall.TimespecToNsec(nativeStat.Ctim)), + } +} diff --git a/vendor/github.com/hugelgupf/p9/internal/stat_standard.go b/vendor/github.com/hugelgupf/p9/internal/stat_standard.go new file mode 100644 index 0000000000..5ad4083f57 --- /dev/null +++ b/vendor/github.com/hugelgupf/p9/internal/stat_standard.go @@ -0,0 +1,31 @@ +//go:build linux || dragonfly || solaris +// +build linux dragonfly solaris + +package internal + +import ( + "os" + "syscall" + + "golang.org/x/sys/unix" +) + +// InfoToStat takes a platform native FileInfo and converts it into a 9P2000.L compatible Stat_t +func InfoToStat(fi os.FileInfo) *Stat_t { + nativeStat := fi.Sys().(*syscall.Stat_t) + return &Stat_t{ + Dev: nativeStat.Dev, + Ino: nativeStat.Ino, + Nlink: nativeStat.Nlink, + Mode: nativeStat.Mode, + Uid: nativeStat.Uid, + Gid: nativeStat.Gid, + Rdev: nativeStat.Rdev, + Size: nativeStat.Size, + Blksize: nativeStat.Blksize, + Blocks: nativeStat.Blocks, + Atim: unix.NsecToTimespec(syscall.TimespecToNsec(nativeStat.Atim)), + Mtim: unix.NsecToTimespec(syscall.TimespecToNsec(nativeStat.Mtim)), + Ctim: unix.NsecToTimespec(syscall.TimespecToNsec(nativeStat.Ctim)), + } +} diff --git a/vendor/github.com/hugelgupf/p9/internal/stat_unix.go b/vendor/github.com/hugelgupf/p9/internal/stat_unix.go new file mode 100644 index 0000000000..0c02369ef1 --- /dev/null +++ b/vendor/github.com/hugelgupf/p9/internal/stat_unix.go @@ -0,0 +1,11 @@ +//go:build !windows +// +build !windows + +package internal + +import ( + "golang.org/x/sys/unix" +) + +// Stat_t is the Linux Stat_t. +type Stat_t = unix.Stat_t diff --git a/vendor/github.com/hugelgupf/p9/internal/stat_windows.go b/vendor/github.com/hugelgupf/p9/internal/stat_windows.go new file mode 100644 index 0000000000..2f7f0a0882 --- /dev/null +++ b/vendor/github.com/hugelgupf/p9/internal/stat_windows.go @@ -0,0 +1,74 @@ +package internal + +import ( + "os" +) + +// NOTE: taken from amd64 Linux +type Timespec struct { + Sec int64 + Nsec int64 +} + +type Stat_t struct { + Dev uint64 + Ino uint64 + Nlink uint64 + Mode uint32 + Uid uint32 + Gid uint32 + Rdev uint64 + Size int64 + Blksize int64 + Blocks int64 + Atim Timespec + Mtim Timespec + Ctim Timespec +} + +// InfoToStat takes a platform native FileInfo and converts it into a 9P2000.L compatible Stat_t +func InfoToStat(fi os.FileInfo) *Stat_t { + return &Stat_t{ + Size: fi.Size(), + Mode: uint32(modeFromOS(fi.Mode())), + Mtim: Timespec{ + Sec: fi.ModTime().Unix(), + Nsec: fi.ModTime().UnixNano(), + }, + } + +} + +// TODO: copied from pkg p9 +// we should probably migrate the OS methods from p9 into sys +const ( + FileModeMask uint32 = 0170000 + ModeSocket = 0140000 + ModeSymlink = 0120000 + ModeRegular = 0100000 + ModeBlockDevice = 060000 + ModeDirectory = 040000 + ModeCharacterDevice = 020000 + ModeNamedPipe = 010000 +) + +func modeFromOS(mode os.FileMode) uint32 { + m := uint32(mode.Perm()) + switch { + case mode.IsDir(): + m |= ModeDirectory + case mode&os.ModeSymlink != 0: + m |= ModeSymlink + case mode&os.ModeSocket != 0: + m |= ModeSocket + case mode&os.ModeNamedPipe != 0: + m |= ModeNamedPipe + case mode&os.ModeCharDevice != 0: + m |= ModeCharacterDevice + case mode&os.ModeDevice != 0: + m |= ModeBlockDevice + default: + m |= ModeRegular + } + return m +} diff --git a/vendor/github.com/hugelgupf/p9/linux/errno.go b/vendor/github.com/hugelgupf/p9/linux/errno.go new file mode 100644 index 0000000000..137cb59fd4 --- /dev/null +++ b/vendor/github.com/hugelgupf/p9/linux/errno.go @@ -0,0 +1,291 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package linux + +import "fmt" + +// Errno is a Linux error number on every GOOS. +type Errno uintptr + +func (e Errno) Error() string { + if 0 <= int(e) && int(e) < len(errorTable) { + s := errorTable[e] + if s != "" { + return s + } + } + return fmt.Sprintf("errno %d", int(e)) +} + +// numbers defined on Linux/amd64. +const ( + E2BIG = Errno(0x7) + EACCES = Errno(0xd) + EADDRINUSE = Errno(0x62) + EADDRNOTAVAIL = Errno(0x63) + EADV = Errno(0x44) + EAFNOSUPPORT = Errno(0x61) + EAGAIN = Errno(0xb) + EALREADY = Errno(0x72) + EBADE = Errno(0x34) + EBADF = Errno(0x9) + EBADFD = Errno(0x4d) + EBADMSG = Errno(0x4a) + EBADR = Errno(0x35) + EBADRQC = Errno(0x38) + EBADSLT = Errno(0x39) + EBFONT = Errno(0x3b) + EBUSY = Errno(0x10) + ECANCELED = Errno(0x7d) + ECHILD = Errno(0xa) + ECHRNG = Errno(0x2c) + ECOMM = Errno(0x46) + ECONNABORTED = Errno(0x67) + ECONNREFUSED = Errno(0x6f) + ECONNRESET = Errno(0x68) + EDEADLK = Errno(0x23) + EDEADLOCK = Errno(0x23) + EDESTADDRREQ = Errno(0x59) + EDOM = Errno(0x21) + EDOTDOT = Errno(0x49) + EDQUOT = Errno(0x7a) + EEXIST = Errno(0x11) + EFAULT = Errno(0xe) + EFBIG = Errno(0x1b) + EHOSTDOWN = Errno(0x70) + EHOSTUNREACH = Errno(0x71) + EHWPOISON = Errno(0x85) + EIDRM = Errno(0x2b) + EILSEQ = Errno(0x54) + EINPROGRESS = Errno(0x73) + EINTR = Errno(0x4) + EINVAL = Errno(0x16) + EIO = Errno(0x5) + EISCONN = Errno(0x6a) + EISDIR = Errno(0x15) + EISNAM = Errno(0x78) + EKEYEXPIRED = Errno(0x7f) + EKEYREJECTED = Errno(0x81) + EKEYREVOKED = Errno(0x80) + EL2HLT = Errno(0x33) + EL2NSYNC = Errno(0x2d) + EL3HLT = Errno(0x2e) + EL3RST = Errno(0x2f) + ELIBACC = Errno(0x4f) + ELIBBAD = Errno(0x50) + ELIBEXEC = Errno(0x53) + ELIBMAX = Errno(0x52) + ELIBSCN = Errno(0x51) + ELNRNG = Errno(0x30) + ELOOP = Errno(0x28) + EMEDIUMTYPE = Errno(0x7c) + EMFILE = Errno(0x18) + EMLINK = Errno(0x1f) + EMSGSIZE = Errno(0x5a) + EMULTIHOP = Errno(0x48) + ENAMETOOLONG = Errno(0x24) + ENAVAIL = Errno(0x77) + ENETDOWN = Errno(0x64) + ENETRESET = Errno(0x66) + ENETUNREACH = Errno(0x65) + ENFILE = Errno(0x17) + ENOANO = Errno(0x37) + ENOBUFS = Errno(0x69) + ENOCSI = Errno(0x32) + ENODATA = Errno(0x3d) + ENODEV = Errno(0x13) + ENOENT = Errno(0x2) + ENOEXEC = Errno(0x8) + ENOKEY = Errno(0x7e) + ENOLCK = Errno(0x25) + ENOLINK = Errno(0x43) + ENOMEDIUM = Errno(0x7b) + ENOMEM = Errno(0xc) + ENOMSG = Errno(0x2a) + ENONET = Errno(0x40) + ENOPKG = Errno(0x41) + ENOPROTOOPT = Errno(0x5c) + ENOSPC = Errno(0x1c) + ENOSR = Errno(0x3f) + ENOSTR = Errno(0x3c) + ENOSYS = Errno(0x26) + ENOTBLK = Errno(0xf) + ENOTCONN = Errno(0x6b) + ENOTDIR = Errno(0x14) + ENOTEMPTY = Errno(0x27) + ENOTNAM = Errno(0x76) + ENOTRECOVERABLE = Errno(0x83) + ENOTSOCK = Errno(0x58) + ENOTSUP = Errno(0x5f) + ENOTTY = Errno(0x19) + ENOTUNIQ = Errno(0x4c) + ENXIO = Errno(0x6) + EOPNOTSUPP = Errno(0x5f) + EOVERFLOW = Errno(0x4b) + EOWNERDEAD = Errno(0x82) + EPERM = Errno(0x1) + EPFNOSUPPORT = Errno(0x60) + EPIPE = Errno(0x20) + EPROTO = Errno(0x47) + EPROTONOSUPPORT = Errno(0x5d) + EPROTOTYPE = Errno(0x5b) + ERANGE = Errno(0x22) + EREMCHG = Errno(0x4e) + EREMOTE = Errno(0x42) + EREMOTEIO = Errno(0x79) + ERESTART = Errno(0x55) + ERFKILL = Errno(0x84) + EROFS = Errno(0x1e) + ESHUTDOWN = Errno(0x6c) + ESOCKTNOSUPPORT = Errno(0x5e) + ESPIPE = Errno(0x1d) + ESRCH = Errno(0x3) + ESRMNT = Errno(0x45) + ESTALE = Errno(0x74) + ESTRPIPE = Errno(0x56) + ETIME = Errno(0x3e) + ETIMEDOUT = Errno(0x6e) + ETOOMANYREFS = Errno(0x6d) + ETXTBSY = Errno(0x1a) + EUCLEAN = Errno(0x75) + EUNATCH = Errno(0x31) + EUSERS = Errno(0x57) + EWOULDBLOCK = Errno(0xb) + EXDEV = Errno(0x12) + EXFULL = Errno(0x36) +) + +var errorTable = [...]string{ + 1: "operation not permitted", + 2: "no such file or directory", + 3: "no such process", + 4: "interrupted system call", + 5: "input/output error", + 6: "no such device or address", + 7: "argument list too long", + 8: "exec format error", + 9: "bad file descriptor", + 10: "no child processes", + 11: "resource temporarily unavailable", + 12: "cannot allocate memory", + 13: "permission denied", + 14: "bad address", + 15: "block device required", + 16: "device or resource busy", + 17: "file exists", + 18: "invalid cross-device link", + 19: "no such device", + 20: "not a directory", + 21: "is a directory", + 22: "invalid argument", + 23: "too many open files in system", + 24: "too many open files", + 25: "inappropriate ioctl for device", + 26: "text file busy", + 27: "file too large", + 28: "no space left on device", + 29: "illegal seek", + 30: "read-only file system", + 31: "too many links", + 32: "broken pipe", + 33: "numerical argument out of domain", + 34: "numerical result out of range", + 35: "resource deadlock avoided", + 36: "file name too long", + 37: "no locks available", + 38: "function not implemented", + 39: "directory not empty", + 40: "too many levels of symbolic links", + 42: "no message of desired type", + 43: "identifier removed", + 44: "channel number out of range", + 45: "level 2 not synchronized", + 46: "level 3 halted", + 47: "level 3 reset", + 48: "link number out of range", + 49: "protocol driver not attached", + 50: "no CSI structure available", + 51: "level 2 halted", + 52: "invalid exchange", + 53: "invalid request descriptor", + 54: "exchange full", + 55: "no anode", + 56: "invalid request code", + 57: "invalid slot", + 59: "bad font file format", + 60: "device not a stream", + 61: "no data available", + 62: "timer expired", + 63: "out of streams resources", + 64: "machine is not on the network", + 65: "package not installed", + 66: "object is remote", + 67: "link has been severed", + 68: "advertise error", + 69: "srmount error", + 70: "communication error on send", + 71: "protocol error", + 72: "multihop attempted", + 73: "RFS specific error", + 74: "bad message", + 75: "value too large for defined data type", + 76: "name not unique on network", + 77: "file descriptor in bad state", + 78: "remote address changed", + 79: "can not access a needed shared library", + 80: "accessing a corrupted shared library", + 81: ".lib section in a.out corrupted", + 82: "attempting to link in too many shared libraries", + 83: "cannot exec a shared library directly", + 84: "invalid or incomplete multibyte or wide character", + 85: "interrupted system call should be restarted", + 86: "streams pipe error", + 87: "too many users", + 88: "socket operation on non-socket", + 89: "destination address required", + 90: "message too long", + 91: "protocol wrong type for socket", + 92: "protocol not available", + 93: "protocol not supported", + 94: "socket type not supported", + 95: "operation not supported", + 96: "protocol family not supported", + 97: "address family not supported by protocol", + 98: "address already in use", + 99: "cannot assign requested address", + 100: "network is down", + 101: "network is unreachable", + 102: "network dropped connection on reset", + 103: "software caused connection abort", + 104: "connection reset by peer", + 105: "no buffer space available", + 106: "transport endpoint is already connected", + 107: "transport endpoint is not connected", + 108: "cannot send after transport endpoint shutdown", + 109: "too many references: cannot splice", + 110: "connection timed out", + 111: "connection refused", + 112: "host is down", + 113: "no route to host", + 114: "operation already in progress", + 115: "operation now in progress", + 116: "stale NFS file handle", + 117: "structure needs cleaning", + 118: "not a XENIX named type file", + 119: "no XENIX semaphores available", + 120: "is a named type file", + 121: "remote I/O error", + 122: "disk quota exceeded", + 123: "no medium found", + 124: "wrong medium type", + 125: "operation canceled", + 126: "required key not available", + 127: "key has expired", + 128: "key has been revoked", + 129: "key was rejected by service", + 130: "owner died", + 131: "state not recoverable", + 132: "operation not possible due to RF-kill", +} diff --git a/vendor/github.com/hugelgupf/p9/linux/errors.go b/vendor/github.com/hugelgupf/p9/linux/errors.go new file mode 100644 index 0000000000..cf4be5ae33 --- /dev/null +++ b/vendor/github.com/hugelgupf/p9/linux/errors.go @@ -0,0 +1,38 @@ +package linux + +import ( + "errors" + "os" +) + +// ExtractErrno extracts an [Errno] from an error, best effort. +// +// If the system-specific or Go-specific error cannot be mapped to anything, it +// will be logged and EIO will be returned. +func ExtractErrno(err error) Errno { + for _, pair := range []struct { + error + Errno + }{ + {os.ErrNotExist, ENOENT}, + {os.ErrExist, EEXIST}, + {os.ErrPermission, EACCES}, + {os.ErrInvalid, EINVAL}, + } { + if errors.Is(err, pair.error) { + return pair.Errno + } + } + + var errno Errno + if errors.As(err, &errno) { + return errno + } + + if e := sysErrno(err); e != 0 { + return e + } + + // Default case. + return EIO +} diff --git a/vendor/github.com/hugelgupf/p9/linux/errors_linux.go b/vendor/github.com/hugelgupf/p9/linux/errors_linux.go new file mode 100644 index 0000000000..b99a437475 --- /dev/null +++ b/vendor/github.com/hugelgupf/p9/linux/errors_linux.go @@ -0,0 +1,17 @@ +//go:build linux +// +build linux + +package linux + +import ( + "errors" + "syscall" +) + +func sysErrno(err error) Errno { + var systemErr syscall.Errno + if errors.As(err, &systemErr) { + return Errno(systemErr) + } + return 0 +} diff --git a/vendor/github.com/hugelgupf/p9/linux/errors_unix.go b/vendor/github.com/hugelgupf/p9/linux/errors_unix.go new file mode 100644 index 0000000000..11a65c451f --- /dev/null +++ b/vendor/github.com/hugelgupf/p9/linux/errors_unix.go @@ -0,0 +1,21 @@ +//go:build !windows && !linux +// +build !windows,!linux + +package linux + +import "syscall" + +func sysErrno(err error) Errno { + se, ok := err.(syscall.Errno) + if ok { + // POSIX-defined errors seem to match up to error number 34 + // according to http://www.ioplex.com/~miallen/errcmpp.html. + // + // 9P2000.L expects Linux error codes, so after 34 we normalize. + if se <= 34 { + return Errno(se) + } + return 0 + } + return 0 +} diff --git a/vendor/github.com/hugelgupf/p9/linux/errors_windows.go b/vendor/github.com/hugelgupf/p9/linux/errors_windows.go new file mode 100644 index 0000000000..fa3e135592 --- /dev/null +++ b/vendor/github.com/hugelgupf/p9/linux/errors_windows.go @@ -0,0 +1,28 @@ +//go:build windows +// +build windows + +package linux + +import ( + "errors" + "syscall" +) + +func sysErrno(err error) Errno { + for _, pair := range []struct { + error + Errno + }{ + {syscall.ERROR_FILE_NOT_FOUND, ENOENT}, + {syscall.ERROR_PATH_NOT_FOUND, ENOENT}, + {syscall.ERROR_ACCESS_DENIED, EACCES}, + {syscall.ERROR_FILE_EXISTS, EEXIST}, + {syscall.ERROR_INSUFFICIENT_BUFFER, ENOMEM}, + } { + if errors.Is(err, pair.error) { + return pair.Errno + } + } + // No clue what to do with others. + return 0 +} diff --git a/vendor/github.com/hugelgupf/p9/p9/buffer.go b/vendor/github.com/hugelgupf/p9/p9/buffer.go new file mode 100644 index 0000000000..ab71cda757 --- /dev/null +++ b/vendor/github.com/hugelgupf/p9/p9/buffer.go @@ -0,0 +1,253 @@ +// Copyright 2018 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package p9 + +import ( + "encoding/binary" +) + +// encoder is used for messages and 9P primitives. +type encoder interface { + // decode decodes from the given buffer. decode may be called more than once + // to reuse the instance. It must clear any previous state. + // + // This may not fail, exhaustion will be recorded in the buffer. + decode(b *buffer) + + // encode encodes to the given buffer. + // + // This may not fail. + encode(b *buffer) +} + +// order is the byte order used for encoding. +var order = binary.LittleEndian + +// buffer is a slice that is consumed. +// +// This is passed to the encoder methods. +type buffer struct { + // data is the underlying data. This may grow during encode. + data []byte + + // overflow indicates whether an overflow has occurred. + overflow bool +} + +// append appends n bytes to the buffer and returns a slice pointing to the +// newly appended bytes. +func (b *buffer) append(n int) []byte { + b.data = append(b.data, make([]byte, n)...) + return b.data[len(b.data)-n:] +} + +// consume consumes n bytes from the buffer. +func (b *buffer) consume(n int) ([]byte, bool) { + if !b.has(n) { + b.markOverrun() + return nil, false + } + rval := b.data[:n] + b.data = b.data[n:] + return rval, true +} + +// has returns true if n bytes are available. +func (b *buffer) has(n int) bool { + return len(b.data) >= n +} + +// markOverrun immediately marks this buffer as overrun. +// +// This is used by ReadString, since some invalid data implies the rest of the +// buffer is no longer valid either. +func (b *buffer) markOverrun() { + b.overflow = true +} + +// isOverrun returns true if this buffer has run past the end. +func (b *buffer) isOverrun() bool { + return b.overflow +} + +// Read8 reads a byte from the buffer. +func (b *buffer) Read8() uint8 { + v, ok := b.consume(1) + if !ok { + return 0 + } + return uint8(v[0]) +} + +// Read16 reads a 16-bit value from the buffer. +func (b *buffer) Read16() uint16 { + v, ok := b.consume(2) + if !ok { + return 0 + } + return order.Uint16(v) +} + +// Read32 reads a 32-bit value from the buffer. +func (b *buffer) Read32() uint32 { + v, ok := b.consume(4) + if !ok { + return 0 + } + return order.Uint32(v) +} + +// Read64 reads a 64-bit value from the buffer. +func (b *buffer) Read64() uint64 { + v, ok := b.consume(8) + if !ok { + return 0 + } + return order.Uint64(v) +} + +// ReadQIDType reads a QIDType value. +func (b *buffer) ReadQIDType() QIDType { + return QIDType(b.Read8()) +} + +// ReadTag reads a Tag value. +func (b *buffer) ReadTag() tag { + return tag(b.Read16()) +} + +// ReadFID reads a FID value. +func (b *buffer) ReadFID() fid { + return fid(b.Read32()) +} + +// ReadUID reads a UID value. +func (b *buffer) ReadUID() UID { + return UID(b.Read32()) +} + +// ReadGID reads a GID value. +func (b *buffer) ReadGID() GID { + return GID(b.Read32()) +} + +// ReadPermissions reads a file mode value and applies the mask for permissions. +func (b *buffer) ReadPermissions() FileMode { + return b.ReadFileMode() & permissionsMask +} + +// ReadFileMode reads a file mode value. +func (b *buffer) ReadFileMode() FileMode { + return FileMode(b.Read32()) +} + +// ReadOpenFlags reads an OpenFlags. +func (b *buffer) ReadOpenFlags() OpenFlags { + return OpenFlags(b.Read32()) +} + +// ReadMsgType writes a msgType. +func (b *buffer) ReadMsgType() msgType { + return msgType(b.Read8()) +} + +// ReadString deserializes a string. +func (b *buffer) ReadString() string { + l := b.Read16() + if !b.has(int(l)) { + // Mark the buffer as corrupted. + b.markOverrun() + return "" + } + + bs := make([]byte, l) + for i := 0; i < int(l); i++ { + bs[i] = byte(b.Read8()) + } + return string(bs) +} + +// Write8 writes a byte to the buffer. +func (b *buffer) Write8(v uint8) { + b.append(1)[0] = byte(v) +} + +// Write16 writes a 16-bit value to the buffer. +func (b *buffer) Write16(v uint16) { + order.PutUint16(b.append(2), v) +} + +// Write32 writes a 32-bit value to the buffer. +func (b *buffer) Write32(v uint32) { + order.PutUint32(b.append(4), v) +} + +// Write64 writes a 64-bit value to the buffer. +func (b *buffer) Write64(v uint64) { + order.PutUint64(b.append(8), v) +} + +// WriteQIDType writes a QIDType value. +func (b *buffer) WriteQIDType(qidType QIDType) { + b.Write8(uint8(qidType)) +} + +// WriteTag writes a Tag value. +func (b *buffer) WriteTag(tag tag) { + b.Write16(uint16(tag)) +} + +// WriteFID writes a FID value. +func (b *buffer) WriteFID(fid fid) { + b.Write32(uint32(fid)) +} + +// WriteUID writes a UID value. +func (b *buffer) WriteUID(uid UID) { + b.Write32(uint32(uid)) +} + +// WriteGID writes a GID value. +func (b *buffer) WriteGID(gid GID) { + b.Write32(uint32(gid)) +} + +// WritePermissions applies a permissions mask and writes the FileMode. +func (b *buffer) WritePermissions(perm FileMode) { + b.WriteFileMode(perm & permissionsMask) +} + +// WriteFileMode writes a FileMode. +func (b *buffer) WriteFileMode(mode FileMode) { + b.Write32(uint32(mode)) +} + +// WriteOpenFlags writes an OpenFlags. +func (b *buffer) WriteOpenFlags(flags OpenFlags) { + b.Write32(uint32(flags)) +} + +// WriteMsgType writes a MsgType. +func (b *buffer) WriteMsgType(t msgType) { + b.Write8(uint8(t)) +} + +// WriteString serializes the given string. +func (b *buffer) WriteString(s string) { + b.Write16(uint16(len(s))) + for i := 0; i < len(s); i++ { + b.Write8(byte(s[i])) + } +} diff --git a/vendor/github.com/hugelgupf/p9/p9/client.go b/vendor/github.com/hugelgupf/p9/p9/client.go new file mode 100644 index 0000000000..76ce6de041 --- /dev/null +++ b/vendor/github.com/hugelgupf/p9/p9/client.go @@ -0,0 +1,344 @@ +// Copyright 2018 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package p9 + +import ( + "errors" + "fmt" + "io" + "sync" + + "github.com/hugelgupf/p9/linux" + "github.com/u-root/uio/ulog" +) + +// ErrOutOfTags indicates no tags are available. +var ErrOutOfTags = errors.New("out of tags -- messages lost?") + +// ErrOutOfFIDs indicates no more FIDs are available. +var ErrOutOfFIDs = errors.New("out of FIDs -- messages lost?") + +// ErrUnexpectedTag indicates a response with an unexpected tag was received. +var ErrUnexpectedTag = errors.New("unexpected tag in response") + +// ErrVersionsExhausted indicates that all versions to negotiate have been exhausted. +var ErrVersionsExhausted = errors.New("exhausted all versions to negotiate") + +// ErrBadVersionString indicates that the version string is malformed or unsupported. +var ErrBadVersionString = errors.New("bad version string") + +// ErrBadResponse indicates the response didn't match the request. +type ErrBadResponse struct { + Got msgType + Want msgType +} + +// Error returns a highly descriptive error. +func (e *ErrBadResponse) Error() string { + return fmt.Sprintf("unexpected message type: got %v, want %v", e.Got, e.Want) +} + +// response is the asynchronous return from recv. +// +// This is used in the pending map below. +type response struct { + r message + done chan error +} + +var responsePool = sync.Pool{ + New: func() interface{} { + return &response{ + done: make(chan error, 1), + } + }, +} + +// Client is at least a 9P2000.L client. +type Client struct { + // conn is the connected conn. + conn io.ReadWriteCloser + + // tagPool is the collection of available tags. + tagPool pool + + // fidPool is the collection of available fids. + fidPool pool + + // pending is the set of pending messages. + pending map[tag]*response + pendingMu sync.Mutex + + // sendMu is the lock for sending a request. + sendMu sync.Mutex + + // recvr is essentially a mutex for calling recv. + // + // Whoever writes to this channel is permitted to call recv. When + // finished calling recv, this channel should be emptied. + recvr chan bool + + // messageSize is the maximum total size of a message. + messageSize uint32 + + // payloadSize is the maximum payload size of a read or write + // request. For large reads and writes this means that the + // read or write is broken up into buffer-size/payloadSize + // requests. + payloadSize uint32 + + // version is the agreed upon version X of 9P2000.L.Google.X. + // version 0 implies 9P2000.L. + version uint32 + + // log is the logger to write to, if specified. + log ulog.Logger +} + +// ClientOpt enables optional client configuration. +type ClientOpt func(*Client) error + +// WithMessageSize overrides the default message size. +func WithMessageSize(m uint32) ClientOpt { + return func(c *Client) error { + // Need at least one byte of payload. + if m <= msgDotLRegistry.largestFixedSize { + return &ErrMessageTooLarge{ + size: m, + msize: msgDotLRegistry.largestFixedSize, + } + } + c.messageSize = m + return nil + } +} + +// WithClientLogger overrides the default logger for the client. +func WithClientLogger(l ulog.Logger) ClientOpt { + return func(c *Client) error { + c.log = l + return nil + } +} + +func roundDown(p uint32, align uint32) uint32 { + if p > align && p%align != 0 { + return p - p%align + } + return p +} + +// NewClient creates a new client. It performs a Tversion exchange with +// the server to assert that messageSize is ok to use. +// +// You should not use the same conn for multiple clients. +func NewClient(conn io.ReadWriteCloser, o ...ClientOpt) (*Client, error) { + c := &Client{ + conn: conn, + tagPool: pool{start: 1, limit: uint64(noTag)}, + fidPool: pool{start: 1, limit: uint64(noFID)}, + pending: make(map[tag]*response), + recvr: make(chan bool, 1), + messageSize: DefaultMessageSize, + log: ulog.Null, + + // Request a high version by default. + version: highestSupportedVersion, + } + + for _, opt := range o { + if err := opt(c); err != nil { + return nil, err + } + } + + // Compute a payload size and round to 512 (normal block size) + // if it's larger than a single block. + c.payloadSize = roundDown(c.messageSize-msgDotLRegistry.largestFixedSize, 512) + + // Agree upon a version. + requested := c.version + for { + rversion := rversion{} + err := c.sendRecv(&tversion{Version: versionString(version9P2000L, requested), MSize: c.messageSize}, &rversion) + + // The server told us to try again with a lower version. + if errors.Is(err, linux.EAGAIN) { + if requested == lowestSupportedVersion { + return nil, ErrVersionsExhausted + } + requested-- + continue + } + + // We requested an impossible version or our other parameters were bogus. + if err != nil { + return nil, err + } + + // Parse the version. + baseVersion, version, ok := parseVersion(rversion.Version) + if !ok { + // The server gave us a bad version. We return a generically worrisome error. + c.log.Printf("server returned bad version string %q", rversion.Version) + return nil, ErrBadVersionString + } + if baseVersion != version9P2000L { + c.log.Printf("server returned unsupported base version %q (version %q)", baseVersion, rversion.Version) + return nil, ErrBadVersionString + } + c.version = version + break + } + return c, nil +} + +// handleOne handles a single incoming message. +// +// This should only be called with the token from recvr. Note that the received +// tag will automatically be cleared from pending. +func (c *Client) handleOne() { + t, r, err := recv(c.log, c.conn, c.messageSize, func(t tag, mt msgType) (message, error) { + c.pendingMu.Lock() + resp := c.pending[t] + c.pendingMu.Unlock() + + // Not expecting this message? + if resp == nil { + c.log.Printf("client received unexpected tag %v, ignoring", t) + return nil, ErrUnexpectedTag + } + + // Is it an error? We specifically allow this to + // go through, and then we deserialize below. + if mt == msgRlerror { + return &rlerror{}, nil + } + + // Does it match expectations? + if mt != resp.r.typ() { + return nil, &ErrBadResponse{Got: mt, Want: resp.r.typ()} + } + + // Return the response. + return resp.r, nil + }) + + if err != nil { + // No tag was extracted (probably a conn error). + // + // Likely catastrophic. Notify all waiters and clear pending. + c.pendingMu.Lock() + for _, resp := range c.pending { + resp.done <- err + } + c.pending = make(map[tag]*response) + c.pendingMu.Unlock() + } else { + // Process the tag. + // + // We know that is is contained in the map because our lookup function + // above must have succeeded (found the tag) to return nil err. + c.pendingMu.Lock() + resp := c.pending[t] + delete(c.pending, t) + c.pendingMu.Unlock() + resp.r = r + resp.done <- err + } +} + +// waitAndRecv co-ordinates with other receivers to handle responses. +func (c *Client) waitAndRecv(done chan error) error { + for { + select { + case err := <-done: + return err + case c.recvr <- true: + select { + case err := <-done: + // It's possible that we got the token, despite + // done also being available. Check for that. + <-c.recvr + return err + default: + // Handle receiving one tag. + c.handleOne() + + // Return the token. + <-c.recvr + } + } + } +} + +// sendRecv performs a roundtrip message exchange. +// +// This is called by internal functions. +func (c *Client) sendRecv(tm message, rm message) error { + t, ok := c.tagPool.Get() + if !ok { + return ErrOutOfTags + } + defer c.tagPool.Put(t) + + // Indicate we're expecting a response. + // + // Note that the tag will be cleared from pending + // automatically (see handleOne for details). + resp := responsePool.Get().(*response) + defer responsePool.Put(resp) + resp.r = rm + c.pendingMu.Lock() + c.pending[tag(t)] = resp + c.pendingMu.Unlock() + + // Send the request over the wire. + c.sendMu.Lock() + err := send(c.log, c.conn, tag(t), tm) + c.sendMu.Unlock() + if err != nil { + return fmt.Errorf("send: %w", err) + } + + // Co-ordinate with other receivers. + if err := c.waitAndRecv(resp.done); err != nil { + return fmt.Errorf("wait: %w", err) + } + + // Is it an error message? + // + // For convenience, we transform these directly + // into errors. Handlers need not handle this case. + if rlerr, ok := resp.r.(*rlerror); ok { + return linux.Errno(rlerr.Error) + } + + // At this point, we know it matches. + // + // Per recv call above, we will only allow a type + // match (and give our r) or an instance of Rlerror. + return nil +} + +// Version returns the negotiated 9P2000.L.Google version number. +func (c *Client) Version() uint32 { + return c.version +} + +// Close closes the underlying connection. +func (c *Client) Close() error { + return c.conn.Close() +} diff --git a/vendor/github.com/hugelgupf/p9/p9/client_file.go b/vendor/github.com/hugelgupf/p9/p9/client_file.go new file mode 100644 index 0000000000..f69324e2e2 --- /dev/null +++ b/vendor/github.com/hugelgupf/p9/p9/client_file.go @@ -0,0 +1,568 @@ +// Copyright 2018 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package p9 + +import ( + "fmt" + "io" + "runtime" + "sync/atomic" + + "github.com/hugelgupf/p9/linux" +) + +// Attach attaches to a server. +// +// Note that authentication is not currently supported. +func (c *Client) Attach(name string) (File, error) { + id, ok := c.fidPool.Get() + if !ok { + return nil, ErrOutOfFIDs + } + + rattach := rattach{} + if err := c.sendRecv(&tattach{fid: fid(id), Auth: tauth{AttachName: name, Authenticationfid: noFID, UID: NoUID}}, &rattach); err != nil { + c.fidPool.Put(id) + return nil, err + } + + return c.newFile(fid(id)), nil +} + +// newFile returns a new client file. +func (c *Client) newFile(fid fid) *clientFile { + cf := &clientFile{ + client: c, + fid: fid, + } + + // Make sure the file is closed. + runtime.SetFinalizer(cf, (*clientFile).Close) + + return cf +} + +// clientFile is provided to clients. +// +// This proxies all of the interfaces found in file.go. +type clientFile struct { + // client is the originating client. + client *Client + + // fid is the fid for this file. + fid fid + + // closed indicates whether this file has been closed. + closed uint32 +} + +// SetXattr implements p9.File.SetXattr. +func (c *clientFile) SetXattr(attr string, data []byte, flags XattrFlags) error { + return linux.ENOSYS +} + +// RemoveXattr implements p9.File.RemoveXattr. +func (c *clientFile) RemoveXattr(attr string) error { + return linux.ENOSYS +} + +// GetXattr implements p9.File.GetXattr. +func (c *clientFile) GetXattr(attr string) ([]byte, error) { + return nil, linux.ENOSYS +} + +// ListXattrs implements p9.File.ListXattrs. +func (c *clientFile) ListXattrs() ([]string, error) { + return nil, linux.ENOSYS +} + +// Walk implements File.Walk. +func (c *clientFile) Walk(names []string) ([]QID, File, error) { + if atomic.LoadUint32(&c.closed) != 0 { + return nil, nil, linux.EBADF + } + + id, ok := c.client.fidPool.Get() + if !ok { + return nil, nil, ErrOutOfFIDs + } + + rwalk := rwalk{} + if err := c.client.sendRecv(&twalk{fid: c.fid, newFID: fid(id), Names: names}, &rwalk); err != nil { + c.client.fidPool.Put(id) + return nil, nil, err + } + + // Return a new client file. + return rwalk.QIDs, c.client.newFile(fid(id)), nil +} + +// WalkGetAttr implements File.WalkGetAttr. +func (c *clientFile) WalkGetAttr(components []string) ([]QID, File, AttrMask, Attr, error) { + if atomic.LoadUint32(&c.closed) != 0 { + return nil, nil, AttrMask{}, Attr{}, linux.EBADF + } + + if !versionSupportsTwalkgetattr(c.client.version) { + qids, file, err := c.Walk(components) + if err != nil { + return nil, nil, AttrMask{}, Attr{}, err + } + _, valid, attr, err := file.GetAttr(AttrMaskAll) + if err != nil { + file.Close() + return nil, nil, AttrMask{}, Attr{}, err + } + return qids, file, valid, attr, nil + } + + id, ok := c.client.fidPool.Get() + if !ok { + return nil, nil, AttrMask{}, Attr{}, ErrOutOfFIDs + } + + rwalkgetattr := rwalkgetattr{} + if err := c.client.sendRecv(&twalkgetattr{fid: c.fid, newFID: fid(id), Names: components}, &rwalkgetattr); err != nil { + c.client.fidPool.Put(id) + return nil, nil, AttrMask{}, Attr{}, err + } + + // Return a new client file. + return rwalkgetattr.QIDs, c.client.newFile(fid(id)), rwalkgetattr.Valid, rwalkgetattr.Attr, nil +} + +// StatFS implements File.StatFS. +func (c *clientFile) StatFS() (FSStat, error) { + if atomic.LoadUint32(&c.closed) != 0 { + return FSStat{}, linux.EBADF + } + + rstatfs := rstatfs{} + if err := c.client.sendRecv(&tstatfs{fid: c.fid}, &rstatfs); err != nil { + return FSStat{}, err + } + + return rstatfs.FSStat, nil +} + +// FSync implements File.FSync. +func (c *clientFile) FSync() error { + if atomic.LoadUint32(&c.closed) != 0 { + return linux.EBADF + } + + return c.client.sendRecv(&tfsync{fid: c.fid}, &rfsync{}) +} + +// GetAttr implements File.GetAttr. +func (c *clientFile) GetAttr(req AttrMask) (QID, AttrMask, Attr, error) { + if atomic.LoadUint32(&c.closed) != 0 { + return QID{}, AttrMask{}, Attr{}, linux.EBADF + } + + rgetattr := rgetattr{} + if err := c.client.sendRecv(&tgetattr{fid: c.fid, AttrMask: req}, &rgetattr); err != nil { + return QID{}, AttrMask{}, Attr{}, err + } + + return rgetattr.QID, rgetattr.Valid, rgetattr.Attr, nil +} + +// SetAttr implements File.SetAttr. +func (c *clientFile) SetAttr(valid SetAttrMask, attr SetAttr) error { + if atomic.LoadUint32(&c.closed) != 0 { + return linux.EBADF + } + + return c.client.sendRecv(&tsetattr{fid: c.fid, Valid: valid, SetAttr: attr}, &rsetattr{}) +} + +// Lock implements File.Lock +func (c *clientFile) Lock(pid int, locktype LockType, flags LockFlags, start, length uint64, client string) (LockStatus, error) { + if atomic.LoadUint32(&c.closed) != 0 { + return LockStatusError, linux.EBADF + } + + r := rlock{} + err := c.client.sendRecv(&tlock{ + Type: locktype, + Flags: flags, + Start: start, + Length: length, + PID: int32(pid), + Client: client, + }, &r) + return r.Status, err +} + +// Remove implements File.Remove. +// +// N.B. This method is no longer part of the file interface and should be +// considered deprecated. +func (c *clientFile) Remove() error { + // Avoid double close. + if !atomic.CompareAndSwapUint32(&c.closed, 0, 1) { + return linux.EBADF + } + runtime.SetFinalizer(c, nil) + + // Send the remove message. + if err := c.client.sendRecv(&tremove{fid: c.fid}, &rremove{}); err != nil { + return err + } + + // "It is correct to consider remove to be a clunk with the side effect + // of removing the file if permissions allow." + // https://swtch.com/plan9port/man/man9/remove.html + + // Return the fid to the pool. + c.client.fidPool.Put(uint64(c.fid)) + return nil +} + +// Close implements File.Close. +func (c *clientFile) Close() error { + // Avoid double close. + if !atomic.CompareAndSwapUint32(&c.closed, 0, 1) { + return linux.EBADF + } + runtime.SetFinalizer(c, nil) + + // Send the close message. + if err := c.client.sendRecv(&tclunk{fid: c.fid}, &rclunk{}); err != nil { + // If an error occurred, we toss away the fid. This isn't ideal, + // but I'm not sure what else makes sense in this context. + return err + } + + // Return the fid to the pool. + c.client.fidPool.Put(uint64(c.fid)) + return nil +} + +// Open implements File.Open. +func (c *clientFile) Open(flags OpenFlags) (QID, uint32, error) { + if atomic.LoadUint32(&c.closed) != 0 { + return QID{}, 0, linux.EBADF + } + + rlopen := rlopen{} + if err := c.client.sendRecv(&tlopen{fid: c.fid, Flags: flags}, &rlopen); err != nil { + return QID{}, 0, err + } + + return rlopen.QID, rlopen.IoUnit, nil +} + +// chunk applies fn to p in chunkSize-sized chunks until fn returns a partial result, p is +// exhausted, or an error is encountered (which may be io.EOF). +func chunk(chunkSize uint32, fn func([]byte, int64) (int, error), p []byte, offset int64) (int, error) { + // Some p9.Clients depend on executing fn on zero-byte buffers. Handle this + // as a special case (normally it is fine to short-circuit and return (0, nil)). + if len(p) == 0 { + return fn(p, offset) + } + + // total is the cumulative bytes processed. + var total int + for { + var n int + var err error + + // We're done, don't bother trying to do anything more. + if total == len(p) { + return total, nil + } + + // Apply fn to a chunkSize-sized (or less) chunk of p. + if len(p) < total+int(chunkSize) { + n, err = fn(p[total:], offset) + } else { + n, err = fn(p[total:total+int(chunkSize)], offset) + } + total += n + offset += int64(n) + + // Return whatever we have processed if we encounter an error. This error + // could be io.EOF. + if err != nil { + return total, err + } + + // Did we get a partial result? If so, return it immediately. + if n < int(chunkSize) { + return total, nil + } + + // If we received more bytes than we ever requested, this is a problem. + if total > len(p) { + panic(fmt.Sprintf("bytes completed (%d)) > requested (%d)", total, len(p))) + } + } +} + +// ReadAt proxies File.ReadAt. +func (c *clientFile) ReadAt(p []byte, offset int64) (int, error) { + return chunk(c.client.payloadSize, c.readAt, p, offset) +} + +func (c *clientFile) readAt(p []byte, offset int64) (int, error) { + if atomic.LoadUint32(&c.closed) != 0 { + return 0, linux.EBADF + } + + rread := rread{Data: p} + if err := c.client.sendRecv(&tread{fid: c.fid, Offset: uint64(offset), Count: uint32(len(p))}, &rread); err != nil { + return 0, err + } + + // The message may have been truncated, or for some reason a new buffer + // allocated. This isn't the common path, but we make sure that if the + // payload has changed we copy it. See transport.go for more information. + if len(p) > 0 && len(rread.Data) > 0 && &rread.Data[0] != &p[0] { + copy(p, rread.Data) + } + + // io.EOF is not an error that a p9 server can return. Use POSIX semantics to + // return io.EOF manually: zero bytes were returned and a non-zero buffer was used. + if len(rread.Data) == 0 && len(p) > 0 { + return 0, io.EOF + } + + return len(rread.Data), nil +} + +// WriteAt proxies File.WriteAt. +func (c *clientFile) WriteAt(p []byte, offset int64) (int, error) { + return chunk(c.client.payloadSize, c.writeAt, p, offset) +} + +func (c *clientFile) writeAt(p []byte, offset int64) (int, error) { + if atomic.LoadUint32(&c.closed) != 0 { + return 0, linux.EBADF + } + + rwrite := rwrite{} + if err := c.client.sendRecv(&twrite{fid: c.fid, Offset: uint64(offset), Data: p}, &rwrite); err != nil { + return 0, err + } + + return int(rwrite.Count), nil +} + +// Rename implements File.Rename. +func (c *clientFile) Rename(dir File, name string) error { + if atomic.LoadUint32(&c.closed) != 0 { + return linux.EBADF + } + + clientDir, ok := dir.(*clientFile) + if !ok { + return linux.EBADF + } + + return c.client.sendRecv(&trename{fid: c.fid, Directory: clientDir.fid, Name: name}, &rrename{}) +} + +// Create implements File.Create. +func (c *clientFile) Create(name string, openFlags OpenFlags, permissions FileMode, uid UID, gid GID) (File, QID, uint32, error) { + if atomic.LoadUint32(&c.closed) != 0 { + return nil, QID{}, 0, linux.EBADF + } + + msg := tlcreate{ + fid: c.fid, + Name: name, + OpenFlags: openFlags, + Permissions: permissions, + GID: NoGID, + } + + if versionSupportsTucreation(c.client.version) { + msg.GID = gid + rucreate := rucreate{} + if err := c.client.sendRecv(&tucreate{tlcreate: msg, UID: uid}, &rucreate); err != nil { + return nil, QID{}, 0, err + } + return c, rucreate.QID, rucreate.IoUnit, nil + } + + rlcreate := rlcreate{} + if err := c.client.sendRecv(&msg, &rlcreate); err != nil { + return nil, QID{}, 0, err + } + + return c, rlcreate.QID, rlcreate.IoUnit, nil +} + +// Mkdir implements File.Mkdir. +func (c *clientFile) Mkdir(name string, permissions FileMode, uid UID, gid GID) (QID, error) { + if atomic.LoadUint32(&c.closed) != 0 { + return QID{}, linux.EBADF + } + + msg := tmkdir{ + Directory: c.fid, + Name: name, + Permissions: permissions, + GID: NoGID, + } + + if versionSupportsTucreation(c.client.version) { + msg.GID = gid + rumkdir := rumkdir{} + if err := c.client.sendRecv(&tumkdir{tmkdir: msg, UID: uid}, &rumkdir); err != nil { + return QID{}, err + } + return rumkdir.QID, nil + } + + rmkdir := rmkdir{} + if err := c.client.sendRecv(&msg, &rmkdir); err != nil { + return QID{}, err + } + + return rmkdir.QID, nil +} + +// Symlink implements File.Symlink. +func (c *clientFile) Symlink(oldname string, newname string, uid UID, gid GID) (QID, error) { + if atomic.LoadUint32(&c.closed) != 0 { + return QID{}, linux.EBADF + } + + msg := tsymlink{ + Directory: c.fid, + Name: newname, + Target: oldname, + GID: NoGID, + } + + if versionSupportsTucreation(c.client.version) { + msg.GID = gid + rusymlink := rusymlink{} + if err := c.client.sendRecv(&tusymlink{tsymlink: msg, UID: uid}, &rusymlink); err != nil { + return QID{}, err + } + return rusymlink.QID, nil + } + + rsymlink := rsymlink{} + if err := c.client.sendRecv(&msg, &rsymlink); err != nil { + return QID{}, err + } + + return rsymlink.QID, nil +} + +// Link implements File.Link. +func (c *clientFile) Link(target File, newname string) error { + if atomic.LoadUint32(&c.closed) != 0 { + return linux.EBADF + } + + targetFile, ok := target.(*clientFile) + if !ok { + return linux.EBADF + } + + return c.client.sendRecv(&tlink{Directory: c.fid, Name: newname, Target: targetFile.fid}, &rlink{}) +} + +// Mknod implements File.Mknod. +func (c *clientFile) Mknod(name string, mode FileMode, major uint32, minor uint32, uid UID, gid GID) (QID, error) { + if atomic.LoadUint32(&c.closed) != 0 { + return QID{}, linux.EBADF + } + + msg := tmknod{ + Directory: c.fid, + Name: name, + Mode: mode, + Major: major, + Minor: minor, + GID: NoGID, + } + + if versionSupportsTucreation(c.client.version) { + msg.GID = gid + rumknod := rumknod{} + if err := c.client.sendRecv(&tumknod{tmknod: msg, UID: uid}, &rumknod); err != nil { + return QID{}, err + } + return rumknod.QID, nil + } + + rmknod := rmknod{} + if err := c.client.sendRecv(&msg, &rmknod); err != nil { + return QID{}, err + } + + return rmknod.QID, nil +} + +// RenameAt implements File.RenameAt. +func (c *clientFile) RenameAt(oldname string, newdir File, newname string) error { + if atomic.LoadUint32(&c.closed) != 0 { + return linux.EBADF + } + + clientNewDir, ok := newdir.(*clientFile) + if !ok { + return linux.EBADF + } + + return c.client.sendRecv(&trenameat{OldDirectory: c.fid, OldName: oldname, NewDirectory: clientNewDir.fid, NewName: newname}, &rrenameat{}) +} + +// UnlinkAt implements File.UnlinkAt. +func (c *clientFile) UnlinkAt(name string, flags uint32) error { + if atomic.LoadUint32(&c.closed) != 0 { + return linux.EBADF + } + + return c.client.sendRecv(&tunlinkat{Directory: c.fid, Name: name, Flags: flags}, &runlinkat{}) +} + +// Readdir implements File.Readdir. +func (c *clientFile) Readdir(offset uint64, count uint32) (Dirents, error) { + if atomic.LoadUint32(&c.closed) != 0 { + return nil, linux.EBADF + } + + rreaddir := rreaddir{} + if err := c.client.sendRecv(&treaddir{Directory: c.fid, Offset: offset, Count: count}, &rreaddir); err != nil { + return nil, err + } + + return rreaddir.Entries, nil +} + +// Readlink implements File.Readlink. +func (c *clientFile) Readlink() (string, error) { + if atomic.LoadUint32(&c.closed) != 0 { + return "", linux.EBADF + } + + rreadlink := rreadlink{} + if err := c.client.sendRecv(&treadlink{fid: c.fid}, &rreadlink); err != nil { + return "", err + } + + return rreadlink.Target, nil +} + +// Renamed implements File.Renamed. +func (c *clientFile) Renamed(newDir File, newName string) {} diff --git a/vendor/github.com/hugelgupf/p9/p9/file.go b/vendor/github.com/hugelgupf/p9/p9/file.go new file mode 100644 index 0000000000..b4ba28349d --- /dev/null +++ b/vendor/github.com/hugelgupf/p9/p9/file.go @@ -0,0 +1,274 @@ +// Copyright 2018 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package p9 + +import ( + "github.com/hugelgupf/p9/linux" +) + +// Attacher is provided by the server. +type Attacher interface { + // Attach returns a new File. + // + // The client-side attach will be translate to a series of walks from + // the file returned by this Attach call. + Attach() (File, error) +} + +// File is a set of operations corresponding to a single node. +// +// Note that on the server side, the server logic places constraints on +// concurrent operations to make things easier. This may reduce the need for +// complex, error-prone locking and logic in the backend. These are documented +// for each method. +// +// There are three different types of guarantees provided: +// +// none: There is no concurrency guarantee. The method may be invoked +// concurrently with any other method on any other file. +// +// read: The method is guaranteed to be exclusive of any write or global +// operation that is mutating the state of the directory tree starting at this +// node. For example, this means creating new files, symlinks, directories or +// renaming a directory entry (or renaming in to this target), but the method +// may be called concurrently with other read methods. +// +// write: The method is guaranteed to be exclusive of any read, write or global +// operation that is mutating the state of the directory tree starting at this +// node, as described in read above. There may however, be other write +// operations executing concurrently on other components in the directory tree. +// +// global: The method is guaranteed to be exclusive of any read, write or +// global operation. +type File interface { + // Walk walks to the path components given in names. + // + // Walk returns QIDs in the same order that the names were passed in. + // + // An empty list of arguments should return a copy of the current file. + // + // On the server, Walk has a read concurrency guarantee. + Walk(names []string) ([]QID, File, error) + + // WalkGetAttr walks to the next file and returns its maximal set of + // attributes. + // + // Server-side p9.Files may return linux.ENOSYS to indicate that Walk + // and GetAttr should be used separately to satisfy this request. + // + // On the server, WalkGetAttr has a read concurrency guarantee. + WalkGetAttr([]string) ([]QID, File, AttrMask, Attr, error) + + // StatFS returns information about the file system associated with + // this file. + // + // On the server, StatFS has no concurrency guarantee. + StatFS() (FSStat, error) + + // GetAttr returns attributes of this node. + // + // On the server, GetAttr has a read concurrency guarantee. + GetAttr(req AttrMask) (QID, AttrMask, Attr, error) + + // SetAttr sets attributes on this node. + // + // On the server, SetAttr has a write concurrency guarantee. + SetAttr(valid SetAttrMask, attr SetAttr) error + + // Close is called when all references are dropped on the server side, + // and Close should be called by the client to drop all references. + // + // For server-side implementations of Close, the error is ignored. + // + // Close must be called even when Open has not been called. + // + // On the server, Close has no concurrency guarantee. + Close() error + + // Open must be called prior to using ReadAt, WriteAt, or Readdir. Once + // Open is called, some operations, such as Walk, will no longer work. + // + // On the client, Open should be called only once. The fd return is + // optional, and may be nil. + // + // On the server, Open has a read concurrency guarantee. Open is + // guaranteed to be called only once. + // + // N.B. The server must resolve any lazy paths when open is called. + // After this point, read and write may be called on files with no + // deletion check, so resolving in the data path is not viable. + Open(mode OpenFlags) (QID, uint32, error) + + // ReadAt reads from this file. Open must be called first. + // + // This may return io.EOF in addition to linux.Errno values. + // + // On the server, ReadAt has a read concurrency guarantee. See Open for + // additional requirements regarding lazy path resolution. + ReadAt(p []byte, offset int64) (int, error) + + // WriteAt writes to this file. Open must be called first. + // + // This may return io.EOF in addition to linux.Errno values. + // + // On the server, WriteAt has a read concurrency guarantee. See Open + // for additional requirements regarding lazy path resolution. + WriteAt(p []byte, offset int64) (int, error) + + // SetXattr sets the extended attributes attr=data of the file. + // + // Flags are implementation-specific, but are + // generally Linux setxattr(2) flags. + SetXattr(attr string, data []byte, flags XattrFlags) error + + // GetXattr fetches the extended attribute attr of the file. + GetXattr(attr string) ([]byte, error) + + // ListXattrs lists the extended attribute names of the file. + ListXattrs() ([]string, error) + + // RemoveXattr removes the extended attribute attr from the file. + RemoveXattr(attr string) error + + // FSync syncs this node. Open must be called first. + // + // On the server, FSync has a read concurrency guarantee. + FSync() error + + // Lock locks the file. The operation as defined in 9P2000.L is fairly + // ambitious, being a near-direct mapping to lockf(2)/fcntl(2)-style + // locking, but most implementations use flock(2). + // + // Arguments are defined by the 9P2000.L standard. + // + // Pid is the PID on the client. Locktype is one of read, write, or + // unlock (resp. 0, 1, or 2). Flags are to block (0), meaning wait; or + // reclaim (1), which is currently "reserved for future use." Start and + // length are the start of the region to use and the size. In many + // implementations, they are ignored and flock(2) is used. Client is an + // arbitrary string, also frequently unused. The Linux v9fs client + // happens to set the client name to the node name. + // + // The Linux v9fs client implements fcntl(F_SETLK) by calling lock + // without any flags set. + // + // The Linux v9fs client implements the fcntl(F_SETLKW) (blocking) + // lock request by calling lock with P9_LOCK_FLAGS_BLOCK set. If the + // response is P9_LOCK_BLOCKED, it retries the lock request in an + // interruptible loop until status is no longer P9_LOCK_BLOCKED. + // + // The Linux v9fs client translates BSD advisory locks (flock) to + // whole-file POSIX record locks. v9fs does not implement mandatory + // locks and will return ENOLCK if use is attempted. + // + // In the return values, a LockStatus corresponds to an Rlock, while + // returning an error corresponds to an Rlerror message. If any non-nil + // error is returned, an Rlerror message will be sent. + // + // The most commonly used return values are success and error (resp. 0 + // and 2); blocked (1) and grace (3) are also possible. + Lock(pid int, locktype LockType, flags LockFlags, start, length uint64, client string) (LockStatus, error) + + // Create creates a new regular file and opens it according to the + // flags given. This file is already Open. + // + // N.B. On the client, the returned file is a reference to the current + // file, which now represents the created file. This is not the case on + // the server. These semantics are very subtle and can easily lead to + // bugs, but are a consequence of the 9P create operation. + // + // On the server, Create has a write concurrency guarantee. + Create(name string, flags OpenFlags, permissions FileMode, uid UID, gid GID) (File, QID, uint32, error) + + // Mkdir creates a subdirectory. + // + // On the server, Mkdir has a write concurrency guarantee. + Mkdir(name string, permissions FileMode, uid UID, gid GID) (QID, error) + + // Symlink makes a new symbolic link. + // + // On the server, Symlink has a write concurrency guarantee. + Symlink(oldName string, newName string, uid UID, gid GID) (QID, error) + + // Link makes a new hard link. + // + // On the server, Link has a write concurrency guarantee. + Link(target File, newName string) error + + // Mknod makes a new device node. + // + // On the server, Mknod has a write concurrency guarantee. + Mknod(name string, mode FileMode, major uint32, minor uint32, uid UID, gid GID) (QID, error) + + // Rename renames the file. + // + // Rename will never be called on the server, and RenameAt will always + // be used instead. + Rename(newDir File, newName string) error + + // RenameAt renames a given file to a new name in a potentially new + // directory. + // + // oldName must be a name relative to this file, which must be a + // directory. newName is a name relative to newDir. + // + // On the server, RenameAt has a global concurrency guarantee. + RenameAt(oldName string, newDir File, newName string) error + + // UnlinkAt the given named file. + // + // name must be a file relative to this directory. + // + // Flags are implementation-specific (e.g. O_DIRECTORY), but are + // generally Linux unlinkat(2) flags. + // + // On the server, UnlinkAt has a write concurrency guarantee. + UnlinkAt(name string, flags uint32) error + + // Readdir reads directory entries. + // + // offset is the entry offset, and count the number of entries to + // return. + // + // This may return io.EOF in addition to linux.Errno values. + // + // On the server, Readdir has a read concurrency guarantee. + Readdir(offset uint64, count uint32) (Dirents, error) + + // Readlink reads the link target. + // + // On the server, Readlink has a read concurrency guarantee. + Readlink() (string, error) + + // Renamed is called when this node is renamed. + // + // This may not fail. The file will hold a reference to its parent + // within the p9 package, and is therefore safe to use for the lifetime + // of this File (until Close is called). + // + // This method should not be called by clients, who should use the + // relevant Rename methods. (Although the method will be a no-op.) + // + // On the server, Renamed has a global concurrency guarantee. + Renamed(newDir File, newName string) +} + +// DefaultWalkGetAttr implements File.WalkGetAttr to return ENOSYS for server-side Files. +type DefaultWalkGetAttr struct{} + +// WalkGetAttr implements File.WalkGetAttr. +func (DefaultWalkGetAttr) WalkGetAttr([]string) ([]QID, File, AttrMask, Attr, error) { + return nil, nil, AttrMask{}, Attr{}, linux.ENOSYS +} diff --git a/vendor/github.com/hugelgupf/p9/p9/fuzz.go b/vendor/github.com/hugelgupf/p9/p9/fuzz.go new file mode 100644 index 0000000000..3e7d21790d --- /dev/null +++ b/vendor/github.com/hugelgupf/p9/p9/fuzz.go @@ -0,0 +1,27 @@ +//go:build gofuzz +// +build gofuzz + +package p9 + +import ( + "bytes" + + "github.com/u-root/uio/ulog" +) + +func Fuzz(data []byte) int { + buf := bytes.NewBuffer(data) + tag, msg, err := recv(ulog.Null, buf, DefaultMessageSize, msgDotLRegistry.get) + if err != nil { + if msg != nil { + panic("msg !=nil on error") + } + return 0 + } + buf.Reset() + send(ulog.Null, buf, tag, msg) + if err != nil { + panic(err) + } + return 1 +} diff --git a/vendor/github.com/hugelgupf/p9/p9/handlers.go b/vendor/github.com/hugelgupf/p9/p9/handlers.go new file mode 100644 index 0000000000..4ee6c616ff --- /dev/null +++ b/vendor/github.com/hugelgupf/p9/p9/handlers.go @@ -0,0 +1,1390 @@ +// Copyright 2018 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package p9 + +import ( + "errors" + "fmt" + "io" + "path" + "strings" + "sync" + "sync/atomic" + + "github.com/hugelgupf/p9/linux" +) + +// newErr returns a new error message from an error. +func newErr(err error) *rlerror { + return &rlerror{Error: uint32(linux.ExtractErrno(err))} +} + +// handler is implemented for server-handled messages. +// +// See server.go for call information. +type handler interface { + // Handle handles the given message. + // + // This may modify the server state. The handle function must return a + // message which will be sent back to the client. It may be useful to + // use newErr to automatically extract an error message. + handle(cs *connState) message +} + +// handle implements handler.handle. +func (t *tversion) handle(cs *connState) message { + // "If the server does not understand the client's version string, it + // should respond with an Rversion message (not Rerror) with the + // version string the 7 characters "unknown"". + // + // - 9P2000 spec. + // + // Makes sense, since there are two different kinds of errors depending on the version. + unknown := &rversion{ + MSize: 0, + Version: "unknown", + } + if t.MSize == 0 { + return unknown + } + msize := t.MSize + if t.MSize > maximumLength { + msize = maximumLength + } + + reqBaseVersion, reqVersion, ok := parseVersion(t.Version) + if !ok { + return unknown + } + var baseVersion baseVersion + var version uint32 + + switch reqBaseVersion { + case version9P2000, version9P2000U: + return unknown + + case version9P2000L: + baseVersion = reqBaseVersion + // The server cannot support newer versions that it doesn't know about. In this + // case we return EAGAIN to tell the client to try again with a lower version. + if reqVersion > highestSupportedVersion { + version = highestSupportedVersion + } else { + version = reqVersion + } + } + + // From Tversion(9P): "The server may respond with the client’s version + // string, or a version string identifying an earlier defined protocol version". + atomic.StoreUint32(&cs.messageSize, msize) + atomic.StoreUint32(&cs.version, version) + // This is not thread-safe. We're changing this into sessions anyway, + // so who cares. + cs.baseVersion = baseVersion + + // Initial a pool with msize-shaped buffers. + cs.readBufPool = sync.Pool{ + New: func() interface{} { + // These buffers are used for decoding without a payload. + // We need to return a pointer to avoid unnecessary allocations + // (see https://staticcheck.io/docs/checks#SA6002). + b := make([]byte, msize) + return &b + }, + } + // Buffer of zeros. + cs.pristineZeros = make([]byte, msize) + + return &rversion{ + MSize: msize, + Version: versionString(baseVersion, version), + } +} + +// handle implements handler.handle. +func (t *tflush) handle(cs *connState) message { + cs.WaitTag(t.OldTag) + return &rflush{} +} + +// checkSafeName validates the name and returns nil or returns an error. +func checkSafeName(name string) error { + if name != "" && !strings.Contains(name, "/") && name != "." && name != ".." { + return nil + } + return linux.EINVAL +} + +func clunkHandleXattr(cs *connState, t *tclunk) message { + // Lookup the fid. + ref, ok := cs.LookupFID(t.fid) + if !ok { + return newErr(linux.EBADF) + } + defer ref.DecRef() + + if err := ref.safelyRead(func() error { + if ref.pendingXattr.op == xattrCreate { + if len(ref.pendingXattr.buf) != int(ref.pendingXattr.size) { + return linux.EINVAL + } + if ref.pendingXattr.flags == XattrReplace && ref.pendingXattr.size == 0 { + return ref.file.RemoveXattr(ref.pendingXattr.name) + } + return ref.file.SetXattr(ref.pendingXattr.name, ref.pendingXattr.buf, ref.pendingXattr.flags) + } + return nil + }); err != nil { + return newErr(err) + } + return nil +} + +// handle implements handler.handle. +func (t *tclunk) handle(cs *connState) message { + cerr := clunkHandleXattr(cs, t) + + if err := cs.DeleteFID(t.fid); err != nil { + return newErr(err) + } + if cerr != nil { + return cerr + } + return &rclunk{} +} + +// handle implements handler.handle. +func (t *tremove) handle(cs *connState) message { + ref, ok := cs.LookupFID(t.fid) + if !ok { + return newErr(linux.EBADF) + } + defer ref.DecRef() + + // Frustratingly, because we can't be guaranteed that a rename is not + // occurring simultaneously with this removal, we need to acquire the + // global rename lock for this kind of remove operation to ensure that + // ref.parent does not change out from underneath us. + // + // This is why Tremove is a bad idea, and clients should generally use + // Tunlinkat. All p9 clients will use Tunlinkat. + err := ref.safelyGlobal(func() error { + // Is this a root? Can't remove that. + if ref.isRoot() { + return linux.EINVAL + } + + // N.B. this remove operation is permitted, even if the file is open. + // See also rename below for reasoning. + + // Is this file already deleted? + if ref.isDeleted() { + return linux.EINVAL + } + + // Retrieve the file's proper name. + name := ref.parent.pathNode.nameFor(ref) + + // Attempt the removal. + if err := ref.parent.file.UnlinkAt(name, 0); err != nil { + return err + } + + // Mark all relevant fids as deleted. We don't need to lock any + // individual nodes because we already hold the global lock. + ref.parent.markChildDeleted(name) + return nil + }) + + // "The remove request asks the file server both to remove the file + // represented by fid and to clunk the fid, even if the remove fails." + // + // "It is correct to consider remove to be a clunk with the side effect + // of removing the file if permissions allow." + // https://swtch.com/plan9port/man/man9/remove.html + if fidErr := cs.DeleteFID(t.fid); fidErr != nil { + return newErr(fidErr) + } + if err != nil { + return newErr(err) + } + + return &rremove{} +} + +// handle implements handler.handle. +// +// We don't support authentication, so this just returns ENOSYS. +func (t *tauth) handle(cs *connState) message { + return newErr(linux.ENOSYS) +} + +// handle implements handler.handle. +func (t *tattach) handle(cs *connState) message { + // Ensure no authentication fid is provided. + if t.Auth.Authenticationfid != noFID { + return newErr(linux.EINVAL) + } + + // Must provide an absolute path. + if path.IsAbs(t.Auth.AttachName) { + // Trim off the leading / if the path is absolute. We always + // treat attach paths as absolute and call attach with the root + // argument on the server file for clarity. + t.Auth.AttachName = t.Auth.AttachName[1:] + } + + // Do the attach on the root. + sf, err := cs.server.attacher.Attach() + if err != nil { + return newErr(err) + } + qid, valid, attr, err := sf.GetAttr(AttrMaskAll) + if err != nil { + sf.Close() // Drop file. + return newErr(err) + } + if !valid.Mode { + sf.Close() // Drop file. + return newErr(linux.EINVAL) + } + + // Build a transient reference. + root := &fidRef{ + server: cs.server, + parent: nil, + file: sf, + refs: 1, + mode: attr.Mode.FileType(), + pathNode: cs.server.pathTree, + } + defer root.DecRef() + + // Attach the root? + if len(t.Auth.AttachName) == 0 { + cs.InsertFID(t.fid, root) + return &rattach{QID: qid} + } + + // We want the same traversal checks to apply on attach, so always + // attach at the root and use the regular walk paths. + names := strings.Split(t.Auth.AttachName, "/") + _, newRef, _, _, err := doWalk(cs, root, names, false) + if err != nil { + return newErr(err) + } + defer newRef.DecRef() + + // Insert the fid. + cs.InsertFID(t.fid, newRef) + return &rattach{QID: qid} +} + +// CanOpen returns whether this file open can be opened, read and written to. +// +// This includes everything except symlinks and sockets. +func CanOpen(mode FileMode) bool { + return mode.IsRegular() || mode.IsDir() || mode.IsNamedPipe() || mode.IsBlockDevice() || mode.IsCharacterDevice() +} + +// handle implements handler.handle. +func (t *tlopen) handle(cs *connState) message { + // Lookup the fid. + ref, ok := cs.LookupFID(t.fid) + if !ok { + return newErr(linux.EBADF) + } + defer ref.DecRef() + + var ( + qid QID + ioUnit uint32 + ) + if err := ref.safelyRead(func() (err error) { + // Has it been deleted already? + if ref.isDeleted() { + return linux.EINVAL + } + + // Has it been opened already? + if ref.opened || !CanOpen(ref.mode) { + return linux.EINVAL + } + + // Is this an attempt to open a directory as writable? Don't accept. + if ref.mode.IsDir() && t.Flags.Mode() != ReadOnly { + return linux.EISDIR + } + + // Do the open. + qid, ioUnit, err = ref.file.Open(t.Flags) + return err + }); err != nil { + return newErr(err) + } + + // Mark file as opened and set open mode. + ref.opened = true + ref.openFlags = t.Flags + + return &rlopen{QID: qid, IoUnit: ioUnit} +} + +func (t *tlcreate) do(cs *connState, uid UID) (*rlcreate, error) { + // Don't allow complex names. + if err := checkSafeName(t.Name); err != nil { + return nil, err + } + + // Lookup the fid. + ref, ok := cs.LookupFID(t.fid) + if !ok { + return nil, linux.EBADF + } + defer ref.DecRef() + + var ( + nsf File + qid QID + ioUnit uint32 + newRef *fidRef + ) + if err := ref.safelyWrite(func() (err error) { + // Don't allow creation from non-directories or deleted directories. + if ref.isDeleted() || !ref.mode.IsDir() { + return linux.EINVAL + } + + // Not allowed on open directories. + if ref.opened { + return linux.EINVAL + } + + // Do the create. + nsf, qid, ioUnit, err = ref.file.Create(t.Name, t.OpenFlags, t.Permissions, uid, t.GID) + if err != nil { + return err + } + + newRef = &fidRef{ + server: cs.server, + parent: ref, + file: nsf, + opened: true, + openFlags: t.OpenFlags, + mode: ModeRegular, + pathNode: ref.pathNode.pathNodeFor(t.Name), + } + ref.pathNode.addChild(newRef, t.Name) + ref.IncRef() // Acquire parent reference. + return nil + }); err != nil { + return nil, err + } + + // Replace the fid reference. + cs.InsertFID(t.fid, newRef) + + return &rlcreate{rlopen: rlopen{QID: qid, IoUnit: ioUnit}}, nil +} + +// handle implements handler.handle. +func (t *tlcreate) handle(cs *connState) message { + rlcreate, err := t.do(cs, NoUID) + if err != nil { + return newErr(err) + } + return rlcreate +} + +// handle implements handler.handle. +func (t *tsymlink) handle(cs *connState) message { + rsymlink, err := t.do(cs, NoUID) + if err != nil { + return newErr(err) + } + return rsymlink +} + +func (t *tsymlink) do(cs *connState, uid UID) (*rsymlink, error) { + // Don't allow complex names. + if err := checkSafeName(t.Name); err != nil { + return nil, err + } + + // Lookup the fid. + ref, ok := cs.LookupFID(t.Directory) + if !ok { + return nil, linux.EBADF + } + defer ref.DecRef() + + var qid QID + if err := ref.safelyWrite(func() (err error) { + // Don't allow symlinks from non-directories or deleted directories. + if ref.isDeleted() || !ref.mode.IsDir() { + return linux.EINVAL + } + + // Not allowed on open directories. + if ref.opened { + return linux.EINVAL + } + + // Do the symlink. + qid, err = ref.file.Symlink(t.Target, t.Name, uid, t.GID) + return err + }); err != nil { + return nil, err + } + + return &rsymlink{QID: qid}, nil +} + +// handle implements handler.handle. +func (t *tlink) handle(cs *connState) message { + // Don't allow complex names. + if err := checkSafeName(t.Name); err != nil { + return newErr(err) + } + + // Lookup the fid. + ref, ok := cs.LookupFID(t.Directory) + if !ok { + return newErr(linux.EBADF) + } + defer ref.DecRef() + + // Lookup the other fid. + refTarget, ok := cs.LookupFID(t.Target) + if !ok { + return newErr(linux.EBADF) + } + defer refTarget.DecRef() + + if err := ref.safelyWrite(func() (err error) { + // Don't allow create links from non-directories or deleted directories. + if ref.isDeleted() || !ref.mode.IsDir() { + return linux.EINVAL + } + + // Not allowed on open directories. + if ref.opened { + return linux.EINVAL + } + + // Do the link. + return ref.file.Link(refTarget.file, t.Name) + }); err != nil { + return newErr(err) + } + + return &rlink{} +} + +// handle implements handler.handle. +func (t *trenameat) handle(cs *connState) message { + // Don't allow complex names. + if err := checkSafeName(t.OldName); err != nil { + return newErr(err) + } + if err := checkSafeName(t.NewName); err != nil { + return newErr(err) + } + + // Lookup the fid. + ref, ok := cs.LookupFID(t.OldDirectory) + if !ok { + return newErr(linux.EBADF) + } + defer ref.DecRef() + + // Lookup the other fid. + refTarget, ok := cs.LookupFID(t.NewDirectory) + if !ok { + return newErr(linux.EBADF) + } + defer refTarget.DecRef() + + // Perform the rename holding the global lock. + if err := ref.safelyGlobal(func() (err error) { + // Don't allow renaming across deleted directories. + if ref.isDeleted() || !ref.mode.IsDir() || refTarget.isDeleted() || !refTarget.mode.IsDir() { + return linux.EINVAL + } + + // Not allowed on open directories. + if ref.opened { + return linux.EINVAL + } + + // Is this the same file? If yes, short-circuit and return success. + if ref.pathNode == refTarget.pathNode && t.OldName == t.NewName { + return nil + } + + // Attempt the actual rename. + if err := ref.file.RenameAt(t.OldName, refTarget.file, t.NewName); err != nil { + return err + } + + // Update the path tree. + ref.renameChildTo(t.OldName, refTarget, t.NewName) + return nil + }); err != nil { + return newErr(err) + } + + return &rrenameat{} +} + +// handle implements handler.handle. +func (t *tunlinkat) handle(cs *connState) message { + // Don't allow complex names. + if err := checkSafeName(t.Name); err != nil { + return newErr(err) + } + + // Lookup the fid. + ref, ok := cs.LookupFID(t.Directory) + if !ok { + return newErr(linux.EBADF) + } + defer ref.DecRef() + + if err := ref.safelyWrite(func() (err error) { + // Don't allow deletion from non-directories or deleted directories. + if ref.isDeleted() || !ref.mode.IsDir() { + return linux.EINVAL + } + + // Not allowed on open directories. + if ref.opened { + return linux.EINVAL + } + + // Before we do the unlink itself, we need to ensure that there + // are no operations in flight on associated path node. The + // child's path node lock must be held to ensure that the + // unlinkat marking the child deleted below is atomic with + // respect to any other read or write operations. + // + // This is one case where we have a lock ordering issue, but + // since we always acquire deeper in the hierarchy, we know + // that we are free of lock cycles. + childPathNode := ref.pathNode.pathNodeFor(t.Name) + childPathNode.opMu.Lock() + defer childPathNode.opMu.Unlock() + + // Do the unlink. + err = ref.file.UnlinkAt(t.Name, t.Flags) + if err != nil { + return err + } + + // Mark the path as deleted. + ref.markChildDeleted(t.Name) + return nil + }); err != nil { + return newErr(err) + } + + return &runlinkat{} +} + +// handle implements handler.handle. +func (t *trename) handle(cs *connState) message { + // Don't allow complex names. + if err := checkSafeName(t.Name); err != nil { + return newErr(err) + } + + // Lookup the fid. + ref, ok := cs.LookupFID(t.fid) + if !ok { + return newErr(linux.EBADF) + } + defer ref.DecRef() + + // Lookup the target. + refTarget, ok := cs.LookupFID(t.Directory) + if !ok { + return newErr(linux.EBADF) + } + defer refTarget.DecRef() + + if err := ref.safelyGlobal(func() (err error) { + // Don't allow a root rename. + if ref.isRoot() { + return linux.EINVAL + } + + // Don't allow renaming deleting entries, or target non-directories. + if ref.isDeleted() || refTarget.isDeleted() || !refTarget.mode.IsDir() { + return linux.EINVAL + } + + // If the parent is deleted, but we not, something is seriously wrong. + // It's fail to die at this point with an assertion failure. + if ref.parent.isDeleted() { + panic(fmt.Sprintf("parent %+v deleted, child %+v is not", ref.parent, ref)) + } + + // N.B. The rename operation is allowed to proceed on open files. It + // does impact the state of its parent, but this is merely a sanity + // check in any case, and the operation is safe. There may be other + // files corresponding to the same path that are renamed anyways. + + // Check for the exact same file and short-circuit. + oldName := ref.parent.pathNode.nameFor(ref) + if ref.parent.pathNode == refTarget.pathNode && oldName == t.Name { + return nil + } + + // Call the rename method on the parent. + if err := ref.parent.file.RenameAt(oldName, refTarget.file, t.Name); err != nil { + return err + } + + // Update the path tree. + ref.parent.renameChildTo(oldName, refTarget, t.Name) + return nil + }); err != nil { + return newErr(err) + } + + return &rrename{} +} + +// handle implements handler.handle. +func (t *treadlink) handle(cs *connState) message { + // Lookup the fid. + ref, ok := cs.LookupFID(t.fid) + if !ok { + return newErr(linux.EBADF) + } + defer ref.DecRef() + + var target string + if err := ref.safelyRead(func() (err error) { + // Don't allow readlink on deleted files. There is no need to + // check if this file is opened because symlinks cannot be + // opened. + if ref.isDeleted() || !ref.mode.IsSymlink() { + return linux.EINVAL + } + + // Do the read. + target, err = ref.file.Readlink() + return err + }); err != nil { + return newErr(err) + } + + return &rreadlink{target} +} + +// handle implements handler.handle. +func (t *tread) handle(cs *connState) message { + // Lookup the fid. + ref, ok := cs.LookupFID(t.fid) + if !ok { + return newErr(linux.EBADF) + } + defer ref.DecRef() + + // Constrain the size of the read buffer. + if int(t.Count) > int(maximumLength) { + return newErr(linux.ENOBUFS) + } + + var n int + data := cs.readBufPool.Get().(*[]byte) + // Retain a reference to the full length of the buffer. + dataBuf := (*data) + if err := ref.safelyRead(func() (err error) { + switch ref.pendingXattr.op { + case xattrNone: + // Has it been opened already? + if !ref.opened { + return linux.EINVAL + } + + // Can it be read? Check permissions. + if ref.openFlags&OpenFlagsModeMask == WriteOnly { + return linux.EPERM + } + + n, err = ref.file.ReadAt(dataBuf[:t.Count], int64(t.Offset)) + return err + + case xattrWalk: + // Make sure we do not pass an empty buffer to GetXattr or ListXattrs. + // Both of them will return the required buffer length if + // the input buffer has length 0. + // tread means the caller already knows the required buffer length + // and wants to get the attribute value. + if t.Count == 0 { + if ref.pendingXattr.size == 0 { + // the provided buffer has length 0 and + // the attribute value is also empty. + return nil + } + // buffer too small. + return linux.EINVAL + } + + if t.Offset+uint64(t.Count) > uint64(len(ref.pendingXattr.buf)) { + return linux.EINVAL + } + + n = copy(dataBuf[:t.Count], ref.pendingXattr.buf[t.Offset:]) + return nil + default: + return linux.EINVAL + } + }); err != nil && !errors.Is(err, io.EOF) { + return newErr(err) + } + + return &rreadServerPayloader{ + rread: rread{ + Data: dataBuf[:n], + }, + cs: cs, + fullBuffer: dataBuf, + } +} + +// handle implements handler.handle. +func (t *twrite) handle(cs *connState) message { + // Lookup the fid. + ref, ok := cs.LookupFID(t.fid) + if !ok { + return newErr(linux.EBADF) + } + defer ref.DecRef() + + var n int + if err := ref.safelyRead(func() (err error) { + switch ref.pendingXattr.op { + case xattrNone: + // Has it been opened already? + if !ref.opened { + return linux.EINVAL + } + + // Can it be written? Check permissions. + if ref.openFlags&OpenFlagsModeMask == ReadOnly { + return linux.EPERM + } + + n, err = ref.file.WriteAt(t.Data, int64(t.Offset)) + + case xattrCreate: + if uint64(len(ref.pendingXattr.buf)) != t.Offset { + return linux.EINVAL + } + if t.Offset+uint64(len(t.Data)) > ref.pendingXattr.size { + return linux.EINVAL + } + ref.pendingXattr.buf = append(ref.pendingXattr.buf, t.Data...) + n = len(t.Data) + + default: + return linux.EINVAL + } + return err + }); err != nil { + return newErr(err) + } + + return &rwrite{Count: uint32(n)} +} + +// handle implements handler.handle. +func (t *tmknod) handle(cs *connState) message { + rmknod, err := t.do(cs, NoUID) + if err != nil { + return newErr(err) + } + return rmknod +} + +func (t *tmknod) do(cs *connState, uid UID) (*rmknod, error) { + // Don't allow complex names. + if err := checkSafeName(t.Name); err != nil { + return nil, err + } + + // Lookup the fid. + ref, ok := cs.LookupFID(t.Directory) + if !ok { + return nil, linux.EBADF + } + defer ref.DecRef() + + var qid QID + if err := ref.safelyWrite(func() (err error) { + // Don't allow mknod on deleted files. + if ref.isDeleted() || !ref.mode.IsDir() { + return linux.EINVAL + } + + // Not allowed on open directories. + if ref.opened { + return linux.EINVAL + } + + // Do the mknod. + qid, err = ref.file.Mknod(t.Name, t.Mode, t.Major, t.Minor, uid, t.GID) + return err + }); err != nil { + return nil, err + } + + return &rmknod{QID: qid}, nil +} + +// handle implements handler.handle. +func (t *tmkdir) handle(cs *connState) message { + rmkdir, err := t.do(cs, NoUID) + if err != nil { + return newErr(err) + } + return rmkdir +} + +func (t *tmkdir) do(cs *connState, uid UID) (*rmkdir, error) { + // Don't allow complex names. + if err := checkSafeName(t.Name); err != nil { + return nil, err + } + + // Lookup the fid. + ref, ok := cs.LookupFID(t.Directory) + if !ok { + return nil, linux.EBADF + } + defer ref.DecRef() + + var qid QID + if err := ref.safelyWrite(func() (err error) { + // Don't allow mkdir on deleted files. + if ref.isDeleted() || !ref.mode.IsDir() { + return linux.EINVAL + } + + // Not allowed on open directories. + if ref.opened { + return linux.EINVAL + } + + // Do the mkdir. + qid, err = ref.file.Mkdir(t.Name, t.Permissions, uid, t.GID) + return err + }); err != nil { + return nil, err + } + + return &rmkdir{QID: qid}, nil +} + +// handle implements handler.handle. +func (t *tgetattr) handle(cs *connState) message { + // Lookup the fid. + ref, ok := cs.LookupFID(t.fid) + if !ok { + return newErr(linux.EBADF) + } + defer ref.DecRef() + + // We allow getattr on deleted files. Depending on the backing + // implementation, it's possible that races exist that might allow + // fetching attributes of other files. But we need to generally allow + // refreshing attributes and this is a minor leak, if at all. + + var ( + qid QID + valid AttrMask + attr Attr + ) + if err := ref.safelyRead(func() (err error) { + qid, valid, attr, err = ref.file.GetAttr(t.AttrMask) + return err + }); err != nil { + return newErr(err) + } + + return &rgetattr{QID: qid, Valid: valid, Attr: attr} +} + +// handle implements handler.handle. +func (t *tsetattr) handle(cs *connState) message { + // Lookup the fid. + ref, ok := cs.LookupFID(t.fid) + if !ok { + return newErr(linux.EBADF) + } + defer ref.DecRef() + + if err := ref.safelyWrite(func() error { + // We don't allow setattr on files that have been deleted. + // This might be technically incorrect, as it's possible that + // there were multiple links and you can still change the + // corresponding inode information. + if ref.isDeleted() { + return linux.EINVAL + } + + // Set the attributes. + return ref.file.SetAttr(t.Valid, t.SetAttr) + }); err != nil { + return newErr(err) + } + + return &rsetattr{} +} + +// handle implements handler.handle. +func (t *txattrwalk) handle(cs *connState) message { + // Lookup the fid. + ref, ok := cs.LookupFID(t.fid) + if !ok { + return newErr(linux.EBADF) + } + defer ref.DecRef() + + size := 0 + if err := ref.safelyRead(func() error { + if ref.isDeleted() { + return linux.EINVAL + } + var buf []byte + var err error + if len(t.Name) > 0 { + buf, err = ref.file.GetXattr(t.Name) + } else { + var xattrs []string + xattrs, err = ref.file.ListXattrs() + if err == nil { + buf = []byte(strings.Join(xattrs, "\000") + "\000") + } + } + if err != nil || uint32(len(buf)) > maximumLength { + return linux.EINVAL + } + size = len(buf) + newRef := &fidRef{ + server: cs.server, + file: ref.file, + pendingXattr: pendingXattr{ + op: xattrWalk, + name: t.Name, + size: uint64(size), + buf: buf, + }, + pathNode: ref.pathNode, + parent: ref.parent, + } + cs.InsertFID(t.newFID, newRef) + return nil + }); err != nil { + return newErr(err) + } + return &rxattrwalk{Size: uint64(size)} +} + +// handle implements handler.handle. +func (t *txattrcreate) handle(cs *connState) message { + // Lookup the fid. + ref, ok := cs.LookupFID(t.fid) + if !ok { + return newErr(linux.EBADF) + } + defer ref.DecRef() + if err := ref.safelyWrite(func() error { + if ref.isDeleted() { + return linux.EINVAL + } + ref.pendingXattr = pendingXattr{ + op: xattrCreate, + name: t.Name, + size: t.AttrSize, + flags: XattrFlags(t.Flags), + } + return nil + }); err != nil { + return newErr(err) + } + return &rxattrcreate{} +} + +// handle implements handler.handle. +func (t *treaddir) handle(cs *connState) message { + // Lookup the fid. + ref, ok := cs.LookupFID(t.Directory) + if !ok { + return newErr(linux.EBADF) + } + defer ref.DecRef() + + var entries []Dirent + if err := ref.safelyRead(func() (err error) { + // Don't allow reading deleted directories. + if ref.isDeleted() || !ref.mode.IsDir() { + return linux.EINVAL + } + + // Has it been opened already? + if !ref.opened { + return linux.EINVAL + } + + // Read the entries. + entries, err = ref.file.Readdir(t.Offset, t.Count) + if err != nil && !errors.Is(err, io.EOF) { + return err + } + return nil + }); err != nil { + return newErr(err) + } + + return &rreaddir{Count: t.Count, Entries: entries} +} + +// handle implements handler.handle. +func (t *tfsync) handle(cs *connState) message { + // Lookup the fid. + ref, ok := cs.LookupFID(t.fid) + if !ok { + return newErr(linux.EBADF) + } + defer ref.DecRef() + + if err := ref.safelyRead(func() (err error) { + // Has it been opened already? + if !ref.opened { + return linux.EINVAL + } + + // Perform the sync. + return ref.file.FSync() + }); err != nil { + return newErr(err) + } + + return &rfsync{} +} + +// handle implements handler.handle. +func (t *tstatfs) handle(cs *connState) message { + // Lookup the fid. + ref, ok := cs.LookupFID(t.fid) + if !ok { + return newErr(linux.EBADF) + } + defer ref.DecRef() + + st, err := ref.file.StatFS() + if err != nil { + return newErr(err) + } + + return &rstatfs{st} +} + +// handle implements handler.handle. +func (t *tlock) handle(cs *connState) message { + // Lookup the fid. + ref, ok := cs.LookupFID(t.fid) + if !ok { + return newErr(linux.EBADF) + } + defer ref.DecRef() + + status, err := ref.file.Lock(int(t.PID), t.Type, t.Flags, t.Start, t.Length, t.Client) + if err != nil { + return newErr(err) + } + return &rlock{Status: status} +} + +// walkOne walks zero or one path elements. +// +// The slice passed as qids is append and returned. +func walkOne(qids []QID, from File, names []string, getattr bool) ([]QID, File, AttrMask, Attr, error) { + nwname := len(names) + if nwname > 1 { + // We require exactly zero or one elements. + return nil, nil, AttrMask{}, Attr{}, linux.EINVAL + } + var ( + localQIDs []QID + sf File + valid AttrMask + attr Attr + err error + ) + switch { + case getattr: + localQIDs, sf, valid, attr, err = from.WalkGetAttr(names) + // Can't put fallthrough in the if because Go. + if !errors.Is(err, linux.ENOSYS) { + break + } + fallthrough + default: + localQIDs, sf, err = from.Walk(names) + if err != nil { + // No way to walk this element. + break + } + if getattr { + _, valid, attr, err = sf.GetAttr(AttrMaskAll) + if err != nil { + // Don't leak the file. + sf.Close() + } + } + } + if err != nil { + // Error walking, don't return anything. + return nil, nil, AttrMask{}, Attr{}, err + } + if nwname == 1 && len(localQIDs) != 1 { + // Expected a single QID. + sf.Close() + return nil, nil, AttrMask{}, Attr{}, linux.EINVAL + } + return append(qids, localQIDs...), sf, valid, attr, nil +} + +// doWalk walks from a given fidRef. +// +// This enforces that all intermediate nodes are walkable (directories). The +// fidRef returned (newRef) has a reference associated with it that is now +// owned by the caller and must be handled appropriately. +func doWalk(cs *connState, ref *fidRef, names []string, getattr bool) (qids []QID, newRef *fidRef, valid AttrMask, attr Attr, err error) { + // Check the names. + for _, name := range names { + err = checkSafeName(name) + if err != nil { + return + } + } + + // validate anything since this is always permitted. + if len(names) == 0 { + var sf File // Temporary. + if err := ref.maybeParent().safelyRead(func() (err error) { + // Clone the single element. + qids, sf, valid, attr, err = walkOne(nil, ref.file, nil, getattr) + if err != nil { + return err + } + + newRef = &fidRef{ + server: cs.server, + parent: ref.parent, + file: sf, + mode: ref.mode, + pathNode: ref.pathNode, + } + if !ref.isRoot() { + if !newRef.isDeleted() { + // Add only if a non-root node; the same node. + ref.parent.pathNode.addChild(newRef, ref.parent.pathNode.nameFor(ref)) + } + ref.parent.IncRef() // Acquire parent reference. + } + // doWalk returns a reference. + newRef.IncRef() + return nil + }); err != nil { + return nil, nil, AttrMask{}, Attr{}, err + } + + // Do not return the new QID. + // walk(5) "nwqid will always be less than or equal to nwname" + return nil, newRef, valid, attr, nil + } + + // Do the walk, one element at a time. + walkRef := ref + walkRef.IncRef() + for i := 0; i < len(names); i++ { + // We won't allow beyond past symlinks; stop here if this isn't + // a proper directory and we have additional paths to walk. + if !walkRef.mode.IsDir() { + walkRef.DecRef() // Drop walk reference; no lock required. + return nil, nil, AttrMask{}, Attr{}, linux.EINVAL + } + + var sf File // Temporary. + if err := walkRef.safelyRead(func() (err error) { + // It is not safe to walk on a deleted directory. It + // could have been replaced with a malicious symlink. + if walkRef.isDeleted() { + // Fail this operation as the result will not + // be meaningful if walkRef is deleted. + return linux.ENOENT + } + + // Pass getattr = true to walkOne since we need the file type for + // newRef. + qids, sf, valid, attr, err = walkOne(qids, walkRef.file, names[i:i+1], true) + if err != nil { + return err + } + + // Note that we don't need to acquire a lock on any of + // these individual instances. That's because they are + // not actually addressable via a fid. They are + // anonymous. They exist in the tree for tracking + // purposes. + newRef := &fidRef{ + server: cs.server, + parent: walkRef, + file: sf, + mode: attr.Mode.FileType(), + pathNode: walkRef.pathNode.pathNodeFor(names[i]), + } + walkRef.pathNode.addChild(newRef, names[i]) + // We allow our walk reference to become the new parent + // reference here and so we don't IncRef. Instead, just + // set walkRef to the newRef above and acquire a new + // walk reference. + walkRef = newRef + walkRef.IncRef() + return nil + }); err != nil { + walkRef.DecRef() // Drop the old walkRef. + return nil, nil, AttrMask{}, Attr{}, err + } + } + + // Success. + return qids, walkRef, valid, attr, nil +} + +// handle implements handler.handle. +func (t *twalk) handle(cs *connState) message { + // Lookup the fid. + ref, ok := cs.LookupFID(t.fid) + if !ok { + return newErr(linux.EBADF) + } + defer ref.DecRef() + + if err := ref.safelyRead(func() error { + // Has it been opened already? + // + // That as OK as long as newFID is different. Note this + // violates the spec, but the Linux client does too, so we have + // little choice. + if ref.opened && t.fid == t.newFID { + return linux.EBUSY + } + return nil + }); err != nil { + return newErr(err) + } + + // Is this an empty list? Handle specially. We don't actually need to + // Do the walk. + qids, newRef, _, _, err := doWalk(cs, ref, t.Names, false) + if err != nil { + return newErr(err) + } + defer newRef.DecRef() + + // Install the new fid. + cs.InsertFID(t.newFID, newRef) + return &rwalk{QIDs: qids} +} + +// handle implements handler.handle. +func (t *twalkgetattr) handle(cs *connState) message { + // Lookup the fid. + ref, ok := cs.LookupFID(t.fid) + if !ok { + return newErr(linux.EBADF) + } + defer ref.DecRef() + + if err := ref.safelyRead(func() error { + // Has it been opened already? + // + // That as OK as long as newFID is different. Note this + // violates the spec, but the Linux client does too, so we have + // little choice. + if ref.opened && t.fid == t.newFID { + return linux.EBUSY + } + return nil + }); err != nil { + return newErr(err) + } + + // Is this an empty list? Handle specially. We don't actually need to + // Do the walk. + qids, newRef, valid, attr, err := doWalk(cs, ref, t.Names, true) + if err != nil { + return newErr(err) + } + defer newRef.DecRef() + + // Install the new fid. + cs.InsertFID(t.newFID, newRef) + return &rwalkgetattr{QIDs: qids, Valid: valid, Attr: attr} +} + +// handle implements handler.handle. +func (t *tucreate) handle(cs *connState) message { + rlcreate, err := t.tlcreate.do(cs, t.UID) + if err != nil { + return newErr(err) + } + return &rucreate{*rlcreate} +} + +// handle implements handler.handle. +func (t *tumkdir) handle(cs *connState) message { + rmkdir, err := t.tmkdir.do(cs, t.UID) + if err != nil { + return newErr(err) + } + return &rumkdir{*rmkdir} +} + +// handle implements handler.handle. +func (t *tusymlink) handle(cs *connState) message { + rsymlink, err := t.tsymlink.do(cs, t.UID) + if err != nil { + return newErr(err) + } + return &rusymlink{*rsymlink} +} + +// handle implements handler.handle. +func (t *tumknod) handle(cs *connState) message { + rmknod, err := t.tmknod.do(cs, t.UID) + if err != nil { + return newErr(err) + } + return &rumknod{*rmknod} +} diff --git a/vendor/github.com/hugelgupf/p9/p9/messages.go b/vendor/github.com/hugelgupf/p9/p9/messages.go new file mode 100644 index 0000000000..54feafd469 --- /dev/null +++ b/vendor/github.com/hugelgupf/p9/p9/messages.go @@ -0,0 +1,2348 @@ +// Copyright 2018 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package p9 + +import ( + "fmt" + "math" +) + +// ErrInvalidMsgType is returned when an unsupported message type is found. +type ErrInvalidMsgType struct { + msgType +} + +// Error returns a useful string. +func (e *ErrInvalidMsgType) Error() string { + return fmt.Sprintf("invalid message type: %d", e.msgType) +} + +// message is a generic 9P message. +type message interface { + encoder + fmt.Stringer + + // Type returns the message type number. + typ() msgType +} + +// payloader is a special message which may include an inline payload. +type payloader interface { + // FixedSize returns the size of the fixed portion of this message. + FixedSize() uint32 + + // Payload returns the payload for sending. + Payload() []byte + + // SetPayload returns the decoded message. + // + // This is going to be total message size - FixedSize. But this should + // be validated during decode, which will be called after SetPayload. + SetPayload([]byte) + + // PayloadCleanup is called after a payloader message is sent and + // buffers can be reapt. + PayloadCleanup() +} + +// tversion is a version request. +type tversion struct { + // MSize is the message size to use. + MSize uint32 + + // Version is the version string. + // + // For this implementation, this must be 9P2000.L. + Version string +} + +// decode implements encoder.decode. +func (t *tversion) decode(b *buffer) { + t.MSize = b.Read32() + t.Version = b.ReadString() +} + +// encode implements encoder.encode. +func (t *tversion) encode(b *buffer) { + b.Write32(t.MSize) + b.WriteString(t.Version) +} + +// typ implements message.typ. +func (*tversion) typ() msgType { + return msgTversion +} + +// String implements fmt.Stringer. +func (t *tversion) String() string { + return fmt.Sprintf("Tversion{MSize: %d, Version: %s}", t.MSize, t.Version) +} + +// rversion is a version response. +type rversion struct { + // MSize is the negotiated size. + MSize uint32 + + // Version is the negotiated version. + Version string +} + +// decode implements encoder.decode. +func (r *rversion) decode(b *buffer) { + r.MSize = b.Read32() + r.Version = b.ReadString() +} + +// encode implements encoder.encode. +func (r *rversion) encode(b *buffer) { + b.Write32(r.MSize) + b.WriteString(r.Version) +} + +// typ implements message.typ. +func (*rversion) typ() msgType { + return msgRversion +} + +// String implements fmt.Stringer. +func (r *rversion) String() string { + return fmt.Sprintf("Rversion{MSize: %d, Version: %s}", r.MSize, r.Version) +} + +// tflush is a flush request. +type tflush struct { + // OldTag is the tag to wait on. + OldTag tag +} + +// decode implements encoder.decode. +func (t *tflush) decode(b *buffer) { + t.OldTag = b.ReadTag() +} + +// encode implements encoder.encode. +func (t *tflush) encode(b *buffer) { + b.WriteTag(t.OldTag) +} + +// typ implements message.typ. +func (*tflush) typ() msgType { + return msgTflush +} + +// String implements fmt.Stringer. +func (t *tflush) String() string { + return fmt.Sprintf("Tflush{OldTag: %d}", t.OldTag) +} + +// rflush is a flush response. +type rflush struct { +} + +// decode implements encoder.decode. +func (*rflush) decode(b *buffer) { +} + +// encode implements encoder.encode. +func (*rflush) encode(b *buffer) { +} + +// typ implements message.typ. +func (*rflush) typ() msgType { + return msgRflush +} + +// String implements fmt.Stringer. +func (r *rflush) String() string { + return fmt.Sprintf("Rflush{}") +} + +// twalk is a walk request. +type twalk struct { + // fid is the fid to be walked. + fid fid + + // newFID is the resulting fid. + newFID fid + + // Names are the set of names to be walked. + Names []string +} + +// decode implements encoder.decode. +func (t *twalk) decode(b *buffer) { + t.fid = b.ReadFID() + t.newFID = b.ReadFID() + n := b.Read16() + t.Names = t.Names[:0] + for i := 0; i < int(n); i++ { + t.Names = append(t.Names, b.ReadString()) + } +} + +// encode implements encoder.encode. +func (t *twalk) encode(b *buffer) { + b.WriteFID(t.fid) + b.WriteFID(t.newFID) + b.Write16(uint16(len(t.Names))) + for _, name := range t.Names { + b.WriteString(name) + } +} + +// typ implements message.typ. +func (*twalk) typ() msgType { + return msgTwalk +} + +// String implements fmt.Stringer. +func (t *twalk) String() string { + return fmt.Sprintf("Twalk{FID: %d, newFID: %d, Names: %v}", t.fid, t.newFID, t.Names) +} + +// rwalk is a walk response. +type rwalk struct { + // QIDs are the set of QIDs returned. + QIDs []QID +} + +// decode implements encoder.decode. +func (r *rwalk) decode(b *buffer) { + n := b.Read16() + r.QIDs = r.QIDs[:0] + for i := 0; i < int(n); i++ { + var q QID + q.decode(b) + r.QIDs = append(r.QIDs, q) + } +} + +// encode implements encoder.encode. +func (r *rwalk) encode(b *buffer) { + b.Write16(uint16(len(r.QIDs))) + for _, q := range r.QIDs { + q.encode(b) + } +} + +// typ implements message.typ. +func (*rwalk) typ() msgType { + return msgRwalk +} + +// String implements fmt.Stringer. +func (r *rwalk) String() string { + return fmt.Sprintf("Rwalk{QIDs: %v}", r.QIDs) +} + +// tclunk is a close request. +type tclunk struct { + // fid is the fid to be closed. + fid fid +} + +// decode implements encoder.decode. +func (t *tclunk) decode(b *buffer) { + t.fid = b.ReadFID() +} + +// encode implements encoder.encode. +func (t *tclunk) encode(b *buffer) { + b.WriteFID(t.fid) +} + +// typ implements message.typ. +func (*tclunk) typ() msgType { + return msgTclunk +} + +// String implements fmt.Stringer. +func (t *tclunk) String() string { + return fmt.Sprintf("Tclunk{FID: %d}", t.fid) +} + +// rclunk is a close response. +type rclunk struct{} + +// decode implements encoder.decode. +func (*rclunk) decode(b *buffer) { +} + +// encode implements encoder.encode. +func (*rclunk) encode(b *buffer) { +} + +// typ implements message.typ. +func (*rclunk) typ() msgType { + return msgRclunk +} + +// String implements fmt.Stringer. +func (r *rclunk) String() string { + return fmt.Sprintf("Rclunk{}") +} + +// tremove is a remove request. +type tremove struct { + // fid is the fid to be removed. + fid fid +} + +// decode implements encoder.decode. +func (t *tremove) decode(b *buffer) { + t.fid = b.ReadFID() +} + +// encode implements encoder.encode. +func (t *tremove) encode(b *buffer) { + b.WriteFID(t.fid) +} + +// typ implements message.typ. +func (*tremove) typ() msgType { + return msgTremove +} + +// String implements fmt.Stringer. +func (t *tremove) String() string { + return fmt.Sprintf("Tremove{FID: %d}", t.fid) +} + +// rremove is a remove response. +type rremove struct { +} + +// decode implements encoder.decode. +func (*rremove) decode(b *buffer) { +} + +// encode implements encoder.encode. +func (*rremove) encode(b *buffer) { +} + +// typ implements message.typ. +func (*rremove) typ() msgType { + return msgRremove +} + +// String implements fmt.Stringer. +func (r *rremove) String() string { + return fmt.Sprintf("Rremove{}") +} + +// rlerror is an error response. +// +// Note that this replaces the error code used in 9p. +type rlerror struct { + Error uint32 +} + +// decode implements encoder.decode. +func (r *rlerror) decode(b *buffer) { + r.Error = b.Read32() +} + +// encode implements encoder.encode. +func (r *rlerror) encode(b *buffer) { + b.Write32(r.Error) +} + +// typ implements message.typ. +func (*rlerror) typ() msgType { + return msgRlerror +} + +// String implements fmt.Stringer. +func (r *rlerror) String() string { + return fmt.Sprintf("Rlerror{Error: %d}", r.Error) +} + +// tauth is an authentication request. +type tauth struct { + // Authenticationfid is the fid to attach the authentication result. + Authenticationfid fid + + // UserName is the user to attach. + UserName string + + // AttachName is the attach name. + AttachName string + + // UserID is the numeric identifier for UserName. + UID UID +} + +// decode implements encoder.decode. +func (t *tauth) decode(b *buffer) { + t.Authenticationfid = b.ReadFID() + t.UserName = b.ReadString() + t.AttachName = b.ReadString() + t.UID = b.ReadUID() +} + +// encode implements encoder.encode. +func (t *tauth) encode(b *buffer) { + b.WriteFID(t.Authenticationfid) + b.WriteString(t.UserName) + b.WriteString(t.AttachName) + b.WriteUID(t.UID) +} + +// typ implements message.typ. +func (*tauth) typ() msgType { + return msgTauth +} + +// String implements fmt.Stringer. +func (t *tauth) String() string { + return fmt.Sprintf("Tauth{AuthFID: %d, UserName: %s, AttachName: %s, UID: %d", t.Authenticationfid, t.UserName, t.AttachName, t.UID) +} + +// rauth is an authentication response. +// +// encode, decode and Length are inherited directly from QID. +type rauth struct { + QID +} + +// typ implements message.typ. +func (*rauth) typ() msgType { + return msgRauth +} + +// String implements fmt.Stringer. +func (r *rauth) String() string { + return fmt.Sprintf("Rauth{QID: %s}", r.QID) +} + +// tattach is an attach request. +type tattach struct { + // fid is the fid to be attached. + fid fid + + // Auth is the embedded authentication request. + // + // See client.Attach for information regarding authentication. + Auth tauth +} + +// decode implements encoder.decode. +func (t *tattach) decode(b *buffer) { + t.fid = b.ReadFID() + t.Auth.decode(b) +} + +// encode implements encoder.encode. +func (t *tattach) encode(b *buffer) { + b.WriteFID(t.fid) + t.Auth.encode(b) +} + +// typ implements message.typ. +func (*tattach) typ() msgType { + return msgTattach +} + +// String implements fmt.Stringer. +func (t *tattach) String() string { + return fmt.Sprintf("Tattach{FID: %d, AuthFID: %d, UserName: %s, AttachName: %s, UID: %d}", t.fid, t.Auth.Authenticationfid, t.Auth.UserName, t.Auth.AttachName, t.Auth.UID) +} + +// rattach is an attach response. +type rattach struct { + QID +} + +// typ implements message.typ. +func (*rattach) typ() msgType { + return msgRattach +} + +// String implements fmt.Stringer. +func (r *rattach) String() string { + return fmt.Sprintf("Rattach{QID: %s}", r.QID) +} + +// tlopen is an open request. +type tlopen struct { + // fid is the fid to be opened. + fid fid + + // Flags are the open flags. + Flags OpenFlags +} + +// decode implements encoder.decode. +func (t *tlopen) decode(b *buffer) { + t.fid = b.ReadFID() + t.Flags = b.ReadOpenFlags() +} + +// encode implements encoder.encode. +func (t *tlopen) encode(b *buffer) { + b.WriteFID(t.fid) + b.WriteOpenFlags(t.Flags) +} + +// typ implements message.typ. +func (*tlopen) typ() msgType { + return msgTlopen +} + +// String implements fmt.Stringer. +func (t *tlopen) String() string { + return fmt.Sprintf("Tlopen{FID: %d, Flags: %v}", t.fid, t.Flags) +} + +// rlopen is a open response. +type rlopen struct { + // QID is the file's QID. + QID QID + + // IoUnit is the recommended I/O unit. + IoUnit uint32 +} + +// decode implements encoder.decode. +func (r *rlopen) decode(b *buffer) { + r.QID.decode(b) + r.IoUnit = b.Read32() +} + +// encode implements encoder.encode. +func (r *rlopen) encode(b *buffer) { + r.QID.encode(b) + b.Write32(r.IoUnit) +} + +// typ implements message.typ. +func (*rlopen) typ() msgType { + return msgRlopen +} + +// String implements fmt.Stringer. +func (r *rlopen) String() string { + return fmt.Sprintf("Rlopen{QID: %s, IoUnit: %d}", r.QID, r.IoUnit) +} + +// tlcreate is a create request. +type tlcreate struct { + // fid is the parent fid. + // + // This becomes the new file. + fid fid + + // Name is the file name to create. + Name string + + // Mode is the open mode (O_RDWR, etc.). + // + // Note that flags like O_TRUNC are ignored, as is O_EXCL. All + // create operations are exclusive. + OpenFlags OpenFlags + + // Permissions is the set of permission bits. + Permissions FileMode + + // GID is the group ID to use for creating the file. + GID GID +} + +// decode implements encoder.decode. +func (t *tlcreate) decode(b *buffer) { + t.fid = b.ReadFID() + t.Name = b.ReadString() + t.OpenFlags = b.ReadOpenFlags() + t.Permissions = b.ReadPermissions() + t.GID = b.ReadGID() +} + +// encode implements encoder.encode. +func (t *tlcreate) encode(b *buffer) { + b.WriteFID(t.fid) + b.WriteString(t.Name) + b.WriteOpenFlags(t.OpenFlags) + b.WritePermissions(t.Permissions) + b.WriteGID(t.GID) +} + +// typ implements message.typ. +func (*tlcreate) typ() msgType { + return msgTlcreate +} + +// String implements fmt.Stringer. +func (t *tlcreate) String() string { + return fmt.Sprintf("Tlcreate{FID: %d, Name: %s, OpenFlags: %s, Permissions: 0o%o, GID: %d}", t.fid, t.Name, t.OpenFlags, t.Permissions, t.GID) +} + +// rlcreate is a create response. +// +// The encode, decode, etc. methods are inherited from Rlopen. +type rlcreate struct { + rlopen +} + +// typ implements message.typ. +func (*rlcreate) typ() msgType { + return msgRlcreate +} + +// String implements fmt.Stringer. +func (r *rlcreate) String() string { + return fmt.Sprintf("Rlcreate{QID: %s, IoUnit: %d}", r.QID, r.IoUnit) +} + +// tsymlink is a symlink request. +type tsymlink struct { + // Directory is the directory fid. + Directory fid + + // Name is the new in the directory. + Name string + + // Target is the symlink target. + Target string + + // GID is the owning group. + GID GID +} + +// decode implements encoder.decode. +func (t *tsymlink) decode(b *buffer) { + t.Directory = b.ReadFID() + t.Name = b.ReadString() + t.Target = b.ReadString() + t.GID = b.ReadGID() +} + +// encode implements encoder.encode. +func (t *tsymlink) encode(b *buffer) { + b.WriteFID(t.Directory) + b.WriteString(t.Name) + b.WriteString(t.Target) + b.WriteGID(t.GID) +} + +// typ implements message.typ. +func (*tsymlink) typ() msgType { + return msgTsymlink +} + +// String implements fmt.Stringer. +func (t *tsymlink) String() string { + return fmt.Sprintf("Tsymlink{DirectoryFID: %d, Name: %s, Target: %s, GID: %d}", t.Directory, t.Name, t.Target, t.GID) +} + +// rsymlink is a symlink response. +type rsymlink struct { + // QID is the new symlink's QID. + QID QID +} + +// decode implements encoder.decode. +func (r *rsymlink) decode(b *buffer) { + r.QID.decode(b) +} + +// encode implements encoder.encode. +func (r *rsymlink) encode(b *buffer) { + r.QID.encode(b) +} + +// typ implements message.typ. +func (*rsymlink) typ() msgType { + return msgRsymlink +} + +// String implements fmt.Stringer. +func (r *rsymlink) String() string { + return fmt.Sprintf("Rsymlink{QID: %s}", r.QID) +} + +// tlink is a link request. +type tlink struct { + // Directory is the directory to contain the link. + Directory fid + + // fid is the target. + Target fid + + // Name is the new source name. + Name string +} + +// decode implements encoder.decode. +func (t *tlink) decode(b *buffer) { + t.Directory = b.ReadFID() + t.Target = b.ReadFID() + t.Name = b.ReadString() +} + +// encode implements encoder.encode. +func (t *tlink) encode(b *buffer) { + b.WriteFID(t.Directory) + b.WriteFID(t.Target) + b.WriteString(t.Name) +} + +// typ implements message.typ. +func (*tlink) typ() msgType { + return msgTlink +} + +// String implements fmt.Stringer. +func (t *tlink) String() string { + return fmt.Sprintf("Tlink{DirectoryFID: %d, TargetFID: %d, Name: %s}", t.Directory, t.Target, t.Name) +} + +// rlink is a link response. +type rlink struct { +} + +// typ implements message.typ. +func (*rlink) typ() msgType { + return msgRlink +} + +// decode implements encoder.decode. +func (*rlink) decode(b *buffer) { +} + +// encode implements encoder.encode. +func (*rlink) encode(b *buffer) { +} + +// String implements fmt.Stringer. +func (r *rlink) String() string { + return fmt.Sprintf("Rlink{}") +} + +// trenameat is a rename request. +type trenameat struct { + // OldDirectory is the source directory. + OldDirectory fid + + // OldName is the source file name. + OldName string + + // NewDirectory is the target directory. + NewDirectory fid + + // NewName is the new file name. + NewName string +} + +// decode implements encoder.decode. +func (t *trenameat) decode(b *buffer) { + t.OldDirectory = b.ReadFID() + t.OldName = b.ReadString() + t.NewDirectory = b.ReadFID() + t.NewName = b.ReadString() +} + +// encode implements encoder.encode. +func (t *trenameat) encode(b *buffer) { + b.WriteFID(t.OldDirectory) + b.WriteString(t.OldName) + b.WriteFID(t.NewDirectory) + b.WriteString(t.NewName) +} + +// typ implements message.typ. +func (*trenameat) typ() msgType { + return msgTrenameat +} + +// String implements fmt.Stringer. +func (t *trenameat) String() string { + return fmt.Sprintf("TrenameAt{OldDirectoryFID: %d, OldName: %s, NewDirectoryFID: %d, NewName: %s}", t.OldDirectory, t.OldName, t.NewDirectory, t.NewName) +} + +// rrenameat is a rename response. +type rrenameat struct { +} + +// decode implements encoder.decode. +func (*rrenameat) decode(b *buffer) { +} + +// encode implements encoder.encode. +func (*rrenameat) encode(b *buffer) { +} + +// typ implements message.typ. +func (*rrenameat) typ() msgType { + return msgRrenameat +} + +// String implements fmt.Stringer. +func (r *rrenameat) String() string { + return fmt.Sprintf("Rrenameat{}") +} + +// tunlinkat is an unlink request. +type tunlinkat struct { + // Directory is the originating directory. + Directory fid + + // Name is the name of the entry to unlink. + Name string + + // Flags are extra flags (e.g. O_DIRECTORY). These are not interpreted by p9. + Flags uint32 +} + +// decode implements encoder.decode. +func (t *tunlinkat) decode(b *buffer) { + t.Directory = b.ReadFID() + t.Name = b.ReadString() + t.Flags = b.Read32() +} + +// encode implements encoder.encode. +func (t *tunlinkat) encode(b *buffer) { + b.WriteFID(t.Directory) + b.WriteString(t.Name) + b.Write32(t.Flags) +} + +// typ implements message.typ. +func (*tunlinkat) typ() msgType { + return msgTunlinkat +} + +// String implements fmt.Stringer. +func (t *tunlinkat) String() string { + return fmt.Sprintf("Tunlinkat{DirectoryFID: %d, Name: %s, Flags: 0x%X}", t.Directory, t.Name, t.Flags) +} + +// runlinkat is an unlink response. +type runlinkat struct { +} + +// decode implements encoder.decode. +func (*runlinkat) decode(b *buffer) { +} + +// encode implements encoder.encode. +func (*runlinkat) encode(b *buffer) { +} + +// typ implements message.typ. +func (*runlinkat) typ() msgType { + return msgRunlinkat +} + +// String implements fmt.Stringer. +func (r *runlinkat) String() string { + return fmt.Sprintf("Runlinkat{}") +} + +// trename is a rename request. +type trename struct { + // fid is the fid to rename. + fid fid + + // Directory is the target directory. + Directory fid + + // Name is the new file name. + Name string +} + +// decode implements encoder.decode. +func (t *trename) decode(b *buffer) { + t.fid = b.ReadFID() + t.Directory = b.ReadFID() + t.Name = b.ReadString() +} + +// encode implements encoder.encode. +func (t *trename) encode(b *buffer) { + b.WriteFID(t.fid) + b.WriteFID(t.Directory) + b.WriteString(t.Name) +} + +// typ implements message.typ. +func (*trename) typ() msgType { + return msgTrename +} + +// String implements fmt.Stringer. +func (t *trename) String() string { + return fmt.Sprintf("Trename{FID: %d, DirectoryFID: %d, Name: %s}", t.fid, t.Directory, t.Name) +} + +// rrename is a rename response. +type rrename struct { +} + +// decode implements encoder.decode. +func (*rrename) decode(b *buffer) { +} + +// encode implements encoder.encode. +func (*rrename) encode(b *buffer) { +} + +// typ implements message.typ. +func (*rrename) typ() msgType { + return msgRrename +} + +// String implements fmt.Stringer. +func (r *rrename) String() string { + return fmt.Sprintf("Rrename{}") +} + +// treadlink is a readlink request. +type treadlink struct { + // fid is the symlink. + fid fid +} + +// decode implements encoder.decode. +func (t *treadlink) decode(b *buffer) { + t.fid = b.ReadFID() +} + +// encode implements encoder.encode. +func (t *treadlink) encode(b *buffer) { + b.WriteFID(t.fid) +} + +// typ implements message.typ. +func (*treadlink) typ() msgType { + return msgTreadlink +} + +// String implements fmt.Stringer. +func (t *treadlink) String() string { + return fmt.Sprintf("Treadlink{FID: %d}", t.fid) +} + +// rreadlink is a readlink response. +type rreadlink struct { + // Target is the symlink target. + Target string +} + +// decode implements encoder.decode. +func (r *rreadlink) decode(b *buffer) { + r.Target = b.ReadString() +} + +// encode implements encoder.encode. +func (r *rreadlink) encode(b *buffer) { + b.WriteString(r.Target) +} + +// typ implements message.typ. +func (*rreadlink) typ() msgType { + return msgRreadlink +} + +// String implements fmt.Stringer. +func (r *rreadlink) String() string { + return fmt.Sprintf("Rreadlink{Target: %s}", r.Target) +} + +// tread is a read request. +type tread struct { + // fid is the fid to read. + fid fid + + // Offset indicates the file offset. + Offset uint64 + + // Count indicates the number of bytes to read. + Count uint32 +} + +// decode implements encoder.decode. +func (t *tread) decode(b *buffer) { + t.fid = b.ReadFID() + t.Offset = b.Read64() + t.Count = b.Read32() +} + +// encode implements encoder.encode. +func (t *tread) encode(b *buffer) { + b.WriteFID(t.fid) + b.Write64(t.Offset) + b.Write32(t.Count) +} + +// typ implements message.typ. +func (*tread) typ() msgType { + return msgTread +} + +// String implements fmt.Stringer. +func (t *tread) String() string { + return fmt.Sprintf("Tread{FID: %d, Offset: %d, Count: %d}", t.fid, t.Offset, t.Count) +} + +// rreadServerPayloader is the response for a Tread by p9 servers. +// +// rreadServerPayloader exists so the fuzzer can fuzz rread -- however, +// PayloadCleanup causes it to panic, and putting connState in the fuzzer seems +// excessive. +type rreadServerPayloader struct { + rread + + fullBuffer []byte + cs *connState +} + +// rread is the response for a Tread. +type rread struct { + // Data is the resulting data. + Data []byte +} + +// decode implements encoder.decode. +// +// Data is automatically decoded via Payload. +func (r *rread) decode(b *buffer) { + count := b.Read32() + if count != uint32(len(r.Data)) { + b.markOverrun() + } +} + +// encode implements encoder.encode. +// +// Data is automatically encoded via Payload. +func (r *rread) encode(b *buffer) { + b.Write32(uint32(len(r.Data))) +} + +// typ implements message.typ. +func (*rread) typ() msgType { + return msgRread +} + +// FixedSize implements payloader.FixedSize. +func (*rread) FixedSize() uint32 { + return 4 +} + +// Payload implements payloader.Payload. +func (r *rread) Payload() []byte { + return r.Data +} + +// SetPayload implements payloader.SetPayload. +func (r *rread) SetPayload(p []byte) { + r.Data = p +} + +func (*rread) PayloadCleanup() {} + +// FixedSize implements payloader.FixedSize. +func (*rreadServerPayloader) FixedSize() uint32 { + return 4 +} + +// Payload implements payloader.Payload. +func (r *rreadServerPayloader) Payload() []byte { + return r.Data +} + +// SetPayload implements payloader.SetPayload. +func (r *rreadServerPayloader) SetPayload(p []byte) { + r.Data = p +} + +// PayloadCleanup implements payloader.PayloadCleanup. +func (r *rreadServerPayloader) PayloadCleanup() { + // Fill it with zeros to not risk leaking previous files' data. + copy(r.Data, r.cs.pristineZeros) + r.cs.readBufPool.Put(&r.fullBuffer) +} + +// String implements fmt.Stringer. +func (r *rread) String() string { + return fmt.Sprintf("Rread{len(Data): %d}", len(r.Data)) +} + +// twrite is a write request. +type twrite struct { + // fid is the fid to read. + fid fid + + // Offset indicates the file offset. + Offset uint64 + + // Data is the data to be written. + Data []byte +} + +// decode implements encoder.decode. +func (t *twrite) decode(b *buffer) { + t.fid = b.ReadFID() + t.Offset = b.Read64() + count := b.Read32() + if count != uint32(len(t.Data)) { + b.markOverrun() + } +} + +// encode implements encoder.encode. +// +// This uses the buffer payload to avoid a copy. +func (t *twrite) encode(b *buffer) { + b.WriteFID(t.fid) + b.Write64(t.Offset) + b.Write32(uint32(len(t.Data))) +} + +// typ implements message.typ. +func (*twrite) typ() msgType { + return msgTwrite +} + +// FixedSize implements payloader.FixedSize. +func (*twrite) FixedSize() uint32 { + return 16 +} + +// Payload implements payloader.Payload. +func (t *twrite) Payload() []byte { + return t.Data +} + +func (t *twrite) PayloadCleanup() {} + +// SetPayload implements payloader.SetPayload. +func (t *twrite) SetPayload(p []byte) { + t.Data = p +} + +// String implements fmt.Stringer. +func (t *twrite) String() string { + return fmt.Sprintf("Twrite{FID: %v, Offset %d, len(Data): %d}", t.fid, t.Offset, len(t.Data)) +} + +// rwrite is the response for a Twrite. +type rwrite struct { + // Count indicates the number of bytes successfully written. + Count uint32 +} + +// decode implements encoder.decode. +func (r *rwrite) decode(b *buffer) { + r.Count = b.Read32() +} + +// encode implements encoder.encode. +func (r *rwrite) encode(b *buffer) { + b.Write32(r.Count) +} + +// typ implements message.typ. +func (*rwrite) typ() msgType { + return msgRwrite +} + +// String implements fmt.Stringer. +func (r *rwrite) String() string { + return fmt.Sprintf("Rwrite{Count: %d}", r.Count) +} + +// tmknod is a mknod request. +type tmknod struct { + // Directory is the parent directory. + Directory fid + + // Name is the device name. + Name string + + // Mode is the device mode and permissions. + Mode FileMode + + // Major is the device major number. + Major uint32 + + // Minor is the device minor number. + Minor uint32 + + // GID is the device GID. + GID GID +} + +// decode implements encoder.decode. +func (t *tmknod) decode(b *buffer) { + t.Directory = b.ReadFID() + t.Name = b.ReadString() + t.Mode = b.ReadFileMode() + t.Major = b.Read32() + t.Minor = b.Read32() + t.GID = b.ReadGID() +} + +// encode implements encoder.encode. +func (t *tmknod) encode(b *buffer) { + b.WriteFID(t.Directory) + b.WriteString(t.Name) + b.WriteFileMode(t.Mode) + b.Write32(t.Major) + b.Write32(t.Minor) + b.WriteGID(t.GID) +} + +// typ implements message.typ. +func (*tmknod) typ() msgType { + return msgTmknod +} + +// String implements fmt.Stringer. +func (t *tmknod) String() string { + return fmt.Sprintf("Tmknod{DirectoryFID: %d, Name: %s, Mode: 0o%o, Major: %d, Minor: %d, GID: %d}", t.Directory, t.Name, t.Mode, t.Major, t.Minor, t.GID) +} + +// rmknod is a mknod response. +type rmknod struct { + // QID is the resulting QID. + QID QID +} + +// decode implements encoder.decode. +func (r *rmknod) decode(b *buffer) { + r.QID.decode(b) +} + +// encode implements encoder.encode. +func (r *rmknod) encode(b *buffer) { + r.QID.encode(b) +} + +// typ implements message.typ. +func (*rmknod) typ() msgType { + return msgRmknod +} + +// String implements fmt.Stringer. +func (r *rmknod) String() string { + return fmt.Sprintf("Rmknod{QID: %s}", r.QID) +} + +// tmkdir is a mkdir request. +type tmkdir struct { + // Directory is the parent directory. + Directory fid + + // Name is the new directory name. + Name string + + // Permissions is the set of permission bits. + Permissions FileMode + + // GID is the owning group. + GID GID +} + +// decode implements encoder.decode. +func (t *tmkdir) decode(b *buffer) { + t.Directory = b.ReadFID() + t.Name = b.ReadString() + t.Permissions = b.ReadPermissions() + t.GID = b.ReadGID() +} + +// encode implements encoder.encode. +func (t *tmkdir) encode(b *buffer) { + b.WriteFID(t.Directory) + b.WriteString(t.Name) + b.WritePermissions(t.Permissions) + b.WriteGID(t.GID) +} + +// typ implements message.typ. +func (*tmkdir) typ() msgType { + return msgTmkdir +} + +// String implements fmt.Stringer. +func (t *tmkdir) String() string { + return fmt.Sprintf("Tmkdir{DirectoryFID: %d, Name: %s, Permissions: 0o%o, GID: %d}", t.Directory, t.Name, t.Permissions, t.GID) +} + +// rmkdir is a mkdir response. +type rmkdir struct { + // QID is the resulting QID. + QID QID +} + +// decode implements encoder.decode. +func (r *rmkdir) decode(b *buffer) { + r.QID.decode(b) +} + +// encode implements encoder.encode. +func (r *rmkdir) encode(b *buffer) { + r.QID.encode(b) +} + +// typ implements message.typ. +func (*rmkdir) typ() msgType { + return msgRmkdir +} + +// String implements fmt.Stringer. +func (r *rmkdir) String() string { + return fmt.Sprintf("Rmkdir{QID: %s}", r.QID) +} + +// tgetattr is a getattr request. +type tgetattr struct { + // fid is the fid to get attributes for. + fid fid + + // AttrMask is the set of attributes to get. + AttrMask AttrMask +} + +// decode implements encoder.decode. +func (t *tgetattr) decode(b *buffer) { + t.fid = b.ReadFID() + t.AttrMask.decode(b) +} + +// encode implements encoder.encode. +func (t *tgetattr) encode(b *buffer) { + b.WriteFID(t.fid) + t.AttrMask.encode(b) +} + +// typ implements message.typ. +func (*tgetattr) typ() msgType { + return msgTgetattr +} + +// String implements fmt.Stringer. +func (t *tgetattr) String() string { + return fmt.Sprintf("Tgetattr{FID: %d, AttrMask: %s}", t.fid, t.AttrMask) +} + +// rgetattr is a getattr response. +type rgetattr struct { + // Valid indicates which fields are valid. + Valid AttrMask + + // QID is the QID for this file. + QID + + // Attr is the set of attributes. + Attr Attr +} + +// decode implements encoder.decode. +func (r *rgetattr) decode(b *buffer) { + r.Valid.decode(b) + r.QID.decode(b) + r.Attr.decode(b) +} + +// encode implements encoder.encode. +func (r *rgetattr) encode(b *buffer) { + r.Valid.encode(b) + r.QID.encode(b) + r.Attr.encode(b) +} + +// typ implements message.typ. +func (*rgetattr) typ() msgType { + return msgRgetattr +} + +// String implements fmt.Stringer. +func (r *rgetattr) String() string { + return fmt.Sprintf("Rgetattr{Valid: %v, QID: %s, Attr: %s}", r.Valid, r.QID, r.Attr) +} + +// tsetattr is a setattr request. +type tsetattr struct { + // fid is the fid to change. + fid fid + + // Valid is the set of bits which will be used. + Valid SetAttrMask + + // SetAttr is the set request. + SetAttr SetAttr +} + +// decode implements encoder.decode. +func (t *tsetattr) decode(b *buffer) { + t.fid = b.ReadFID() + t.Valid.decode(b) + t.SetAttr.decode(b) +} + +// encode implements encoder.encode. +func (t *tsetattr) encode(b *buffer) { + b.WriteFID(t.fid) + t.Valid.encode(b) + t.SetAttr.encode(b) +} + +// typ implements message.typ. +func (*tsetattr) typ() msgType { + return msgTsetattr +} + +// String implements fmt.Stringer. +func (t *tsetattr) String() string { + return fmt.Sprintf("Tsetattr{FID: %d, Valid: %v, SetAttr: %s}", t.fid, t.Valid, t.SetAttr) +} + +// rsetattr is a setattr response. +type rsetattr struct { +} + +// decode implements encoder.decode. +func (*rsetattr) decode(b *buffer) { +} + +// encode implements encoder.encode. +func (*rsetattr) encode(b *buffer) { +} + +// typ implements message.typ. +func (*rsetattr) typ() msgType { + return msgRsetattr +} + +// String implements fmt.Stringer. +func (r *rsetattr) String() string { + return fmt.Sprintf("Rsetattr{}") +} + +// txattrwalk walks extended attributes. +type txattrwalk struct { + // fid is the fid to check for attributes. + fid fid + + // newFID is the new fid associated with the attributes. + newFID fid + + // Name is the attribute name. + Name string +} + +// decode implements encoder.decode. +func (t *txattrwalk) decode(b *buffer) { + t.fid = b.ReadFID() + t.newFID = b.ReadFID() + t.Name = b.ReadString() +} + +// encode implements encoder.encode. +func (t *txattrwalk) encode(b *buffer) { + b.WriteFID(t.fid) + b.WriteFID(t.newFID) + b.WriteString(t.Name) +} + +// typ implements message.typ. +func (*txattrwalk) typ() msgType { + return msgTxattrwalk +} + +// String implements fmt.Stringer. +func (t *txattrwalk) String() string { + return fmt.Sprintf("Txattrwalk{FID: %d, newFID: %d, Name: %s}", t.fid, t.newFID, t.Name) +} + +// rxattrwalk is a xattrwalk response. +type rxattrwalk struct { + // Size is the size of the extended attribute. + Size uint64 +} + +// decode implements encoder.decode. +func (r *rxattrwalk) decode(b *buffer) { + r.Size = b.Read64() +} + +// encode implements encoder.encode. +func (r *rxattrwalk) encode(b *buffer) { + b.Write64(r.Size) +} + +// typ implements message.typ. +func (*rxattrwalk) typ() msgType { + return msgRxattrwalk +} + +// String implements fmt.Stringer. +func (r *rxattrwalk) String() string { + return fmt.Sprintf("Rxattrwalk{Size: %d}", r.Size) +} + +// txattrcreate prepare to set extended attributes. +type txattrcreate struct { + // fid is input/output parameter, it identifies the file on which + // extended attributes will be set but after successful Rxattrcreate + // it is used to write the extended attribute value. + fid fid + + // Name is the attribute name. + Name string + + // Size of the attribute value. When the fid is clunked it has to match + // the number of bytes written to the fid. + AttrSize uint64 + + // Linux setxattr(2) flags. + Flags uint32 +} + +// decode implements encoder.decode. +func (t *txattrcreate) decode(b *buffer) { + t.fid = b.ReadFID() + t.Name = b.ReadString() + t.AttrSize = b.Read64() + t.Flags = b.Read32() +} + +// encode implements encoder.encode. +func (t *txattrcreate) encode(b *buffer) { + b.WriteFID(t.fid) + b.WriteString(t.Name) + b.Write64(t.AttrSize) + b.Write32(t.Flags) +} + +// typ implements message.typ. +func (*txattrcreate) typ() msgType { + return msgTxattrcreate +} + +// String implements fmt.Stringer. +func (t *txattrcreate) String() string { + return fmt.Sprintf("Txattrcreate{FID: %d, Name: %s, AttrSize: %d, Flags: %d}", t.fid, t.Name, t.AttrSize, t.Flags) +} + +// rxattrcreate is a xattrcreate response. +type rxattrcreate struct { +} + +// decode implements encoder.decode. +func (r *rxattrcreate) decode(b *buffer) { +} + +// encode implements encoder.encode. +func (r *rxattrcreate) encode(b *buffer) { +} + +// typ implements message.typ. +func (*rxattrcreate) typ() msgType { + return msgRxattrcreate +} + +// String implements fmt.Stringer. +func (r *rxattrcreate) String() string { + return fmt.Sprintf("Rxattrcreate{}") +} + +// treaddir is a readdir request. +type treaddir struct { + // Directory is the directory fid to read. + Directory fid + + // Offset is the offset to read at. + Offset uint64 + + // Count is the number of bytes to read. + Count uint32 +} + +// decode implements encoder.decode. +func (t *treaddir) decode(b *buffer) { + t.Directory = b.ReadFID() + t.Offset = b.Read64() + t.Count = b.Read32() +} + +// encode implements encoder.encode. +func (t *treaddir) encode(b *buffer) { + b.WriteFID(t.Directory) + b.Write64(t.Offset) + b.Write32(t.Count) +} + +// typ implements message.typ. +func (*treaddir) typ() msgType { + return msgTreaddir +} + +// String implements fmt.Stringer. +func (t *treaddir) String() string { + return fmt.Sprintf("Treaddir{DirectoryFID: %d, Offset: %d, Count: %d}", t.Directory, t.Offset, t.Count) +} + +// rreaddir is a readdir response. +type rreaddir struct { + // Count is the byte limit. + // + // This should always be set from the Treaddir request. + Count uint32 + + // Entries are the resulting entries. + // + // This may be constructed in decode. + Entries []Dirent + + // payload is the encoded payload. + // + // This is constructed by encode. + payload []byte +} + +// decode implements encoder.decode. +func (r *rreaddir) decode(b *buffer) { + r.Count = b.Read32() + entriesBuf := buffer{data: r.payload} + r.Entries = r.Entries[:0] + for { + var d Dirent + d.decode(&entriesBuf) + if entriesBuf.isOverrun() { + // Couldn't decode a complete entry. + break + } + r.Entries = append(r.Entries, d) + } +} + +// encode implements encoder.encode. +func (r *rreaddir) encode(b *buffer) { + entriesBuf := buffer{} + payloadSize := 0 + for _, d := range r.Entries { + d.encode(&entriesBuf) + if len(entriesBuf.data) > int(r.Count) { + break + } + payloadSize = len(entriesBuf.data) + } + r.Count = uint32(payloadSize) + r.payload = entriesBuf.data[:payloadSize] + b.Write32(r.Count) +} + +// typ implements message.typ. +func (*rreaddir) typ() msgType { + return msgRreaddir +} + +// FixedSize implements payloader.FixedSize. +func (*rreaddir) FixedSize() uint32 { + return 4 +} + +// Payload implements payloader.Payload. +func (r *rreaddir) Payload() []byte { + return r.payload +} + +func (r *rreaddir) PayloadCleanup() {} + +// SetPayload implements payloader.SetPayload. +func (r *rreaddir) SetPayload(p []byte) { + r.payload = p +} + +// String implements fmt.Stringer. +func (r *rreaddir) String() string { + return fmt.Sprintf("Rreaddir{Count: %d, Entries: %s}", r.Count, r.Entries) +} + +// Tfsync is an fsync request. +type tfsync struct { + // fid is the fid to sync. + fid fid +} + +// decode implements encoder.decode. +func (t *tfsync) decode(b *buffer) { + t.fid = b.ReadFID() +} + +// encode implements encoder.encode. +func (t *tfsync) encode(b *buffer) { + b.WriteFID(t.fid) +} + +// typ implements message.typ. +func (*tfsync) typ() msgType { + return msgTfsync +} + +// String implements fmt.Stringer. +func (t *tfsync) String() string { + return fmt.Sprintf("Tfsync{FID: %d}", t.fid) +} + +// rfsync is an fsync response. +type rfsync struct { +} + +// decode implements encoder.decode. +func (*rfsync) decode(b *buffer) { +} + +// encode implements encoder.encode. +func (*rfsync) encode(b *buffer) { +} + +// typ implements message.typ. +func (*rfsync) typ() msgType { + return msgRfsync +} + +// String implements fmt.Stringer. +func (r *rfsync) String() string { + return fmt.Sprintf("Rfsync{}") +} + +// tstatfs is a stat request. +type tstatfs struct { + // fid is the root. + fid fid +} + +// decode implements encoder.decode. +func (t *tstatfs) decode(b *buffer) { + t.fid = b.ReadFID() +} + +// encode implements encoder.encode. +func (t *tstatfs) encode(b *buffer) { + b.WriteFID(t.fid) +} + +// typ implements message.typ. +func (*tstatfs) typ() msgType { + return msgTstatfs +} + +// String implements fmt.Stringer. +func (t *tstatfs) String() string { + return fmt.Sprintf("Tstatfs{FID: %d}", t.fid) +} + +// rstatfs is the response for a Tstatfs. +type rstatfs struct { + // FSStat is the stat result. + FSStat FSStat +} + +// decode implements encoder.decode. +func (r *rstatfs) decode(b *buffer) { + r.FSStat.decode(b) +} + +// encode implements encoder.encode. +func (r *rstatfs) encode(b *buffer) { + r.FSStat.encode(b) +} + +// typ implements message.typ. +func (*rstatfs) typ() msgType { + return msgRstatfs +} + +// String implements fmt.Stringer. +func (r *rstatfs) String() string { + return fmt.Sprintf("Rstatfs{FSStat: %v}", r.FSStat) +} + +// twalkgetattr is a walk request. +type twalkgetattr struct { + // fid is the fid to be walked. + fid fid + + // newFID is the resulting fid. + newFID fid + + // Names are the set of names to be walked. + Names []string +} + +// decode implements encoder.decode. +func (t *twalkgetattr) decode(b *buffer) { + t.fid = b.ReadFID() + t.newFID = b.ReadFID() + n := b.Read16() + t.Names = t.Names[:0] + for i := 0; i < int(n); i++ { + t.Names = append(t.Names, b.ReadString()) + } +} + +// encode implements encoder.encode. +func (t *twalkgetattr) encode(b *buffer) { + b.WriteFID(t.fid) + b.WriteFID(t.newFID) + b.Write16(uint16(len(t.Names))) + for _, name := range t.Names { + b.WriteString(name) + } +} + +// typ implements message.typ. +func (*twalkgetattr) typ() msgType { + return msgTwalkgetattr +} + +// String implements fmt.Stringer. +func (t *twalkgetattr) String() string { + return fmt.Sprintf("Twalkgetattr{FID: %d, newFID: %d, Names: %v}", t.fid, t.newFID, t.Names) +} + +// rwalkgetattr is a walk response. +type rwalkgetattr struct { + // Valid indicates which fields are valid in the Attr below. + Valid AttrMask + + // Attr is the set of attributes for the last QID (the file walked to). + Attr Attr + + // QIDs are the set of QIDs returned. + QIDs []QID +} + +// decode implements encoder.decode. +func (r *rwalkgetattr) decode(b *buffer) { + r.Valid.decode(b) + r.Attr.decode(b) + n := b.Read16() + r.QIDs = r.QIDs[:0] + for i := 0; i < int(n); i++ { + var q QID + q.decode(b) + r.QIDs = append(r.QIDs, q) + } +} + +// encode implements encoder.encode. +func (r *rwalkgetattr) encode(b *buffer) { + r.Valid.encode(b) + r.Attr.encode(b) + b.Write16(uint16(len(r.QIDs))) + for _, q := range r.QIDs { + q.encode(b) + } +} + +// typ implements message.typ. +func (*rwalkgetattr) typ() msgType { + return msgRwalkgetattr +} + +// String implements fmt.Stringer. +func (r *rwalkgetattr) String() string { + return fmt.Sprintf("Rwalkgetattr{Valid: %s, Attr: %s, QIDs: %v}", r.Valid, r.Attr, r.QIDs) +} + +// tucreate is a tlcreate message that includes a UID. +type tucreate struct { + tlcreate + + // UID is the UID to use as the effective UID in creation messages. + UID UID +} + +// decode implements encoder.decode. +func (t *tucreate) decode(b *buffer) { + t.tlcreate.decode(b) + t.UID = b.ReadUID() +} + +// encode implements encoder.encode. +func (t *tucreate) encode(b *buffer) { + t.tlcreate.encode(b) + b.WriteUID(t.UID) +} + +// typ implements message.typ. +func (t *tucreate) typ() msgType { + return msgTucreate +} + +// String implements fmt.Stringer. +func (t *tucreate) String() string { + return fmt.Sprintf("Tucreate{Tlcreate: %v, UID: %d}", &t.tlcreate, t.UID) +} + +// rucreate is a file creation response. +type rucreate struct { + rlcreate +} + +// typ implements message.typ. +func (*rucreate) typ() msgType { + return msgRucreate +} + +// String implements fmt.Stringer. +func (r *rucreate) String() string { + return fmt.Sprintf("Rucreate{%v}", &r.rlcreate) +} + +// tumkdir is a Tmkdir message that includes a UID. +type tumkdir struct { + tmkdir + + // UID is the UID to use as the effective UID in creation messages. + UID UID +} + +// decode implements encoder.decode. +func (t *tumkdir) decode(b *buffer) { + t.tmkdir.decode(b) + t.UID = b.ReadUID() +} + +// encode implements encoder.encode. +func (t *tumkdir) encode(b *buffer) { + t.tmkdir.encode(b) + b.WriteUID(t.UID) +} + +// typ implements message.typ. +func (t *tumkdir) typ() msgType { + return msgTumkdir +} + +// String implements fmt.Stringer. +func (t *tumkdir) String() string { + return fmt.Sprintf("Tumkdir{Tmkdir: %v, UID: %d}", &t.tmkdir, t.UID) +} + +// rumkdir is a umkdir response. +type rumkdir struct { + rmkdir +} + +// typ implements message.typ. +func (*rumkdir) typ() msgType { + return msgRumkdir +} + +// String implements fmt.Stringer. +func (r *rumkdir) String() string { + return fmt.Sprintf("Rumkdir{%v}", &r.rmkdir) +} + +// tumknod is a Tmknod message that includes a UID. +type tumknod struct { + tmknod + + // UID is the UID to use as the effective UID in creation messages. + UID UID +} + +// decode implements encoder.decode. +func (t *tumknod) decode(b *buffer) { + t.tmknod.decode(b) + t.UID = b.ReadUID() +} + +// encode implements encoder.encode. +func (t *tumknod) encode(b *buffer) { + t.tmknod.encode(b) + b.WriteUID(t.UID) +} + +// typ implements message.typ. +func (t *tumknod) typ() msgType { + return msgTumknod +} + +// String implements fmt.Stringer. +func (t *tumknod) String() string { + return fmt.Sprintf("Tumknod{Tmknod: %v, UID: %d}", &t.tmknod, t.UID) +} + +// rumknod is a umknod response. +type rumknod struct { + rmknod +} + +// typ implements message.typ. +func (*rumknod) typ() msgType { + return msgRumknod +} + +// String implements fmt.Stringer. +func (r *rumknod) String() string { + return fmt.Sprintf("Rumknod{%v}", &r.rmknod) +} + +// tusymlink is a Tsymlink message that includes a UID. +type tusymlink struct { + tsymlink + + // UID is the UID to use as the effective UID in creation messages. + UID UID +} + +// decode implements encoder.decode. +func (t *tusymlink) decode(b *buffer) { + t.tsymlink.decode(b) + t.UID = b.ReadUID() +} + +// encode implements encoder.encode. +func (t *tusymlink) encode(b *buffer) { + t.tsymlink.encode(b) + b.WriteUID(t.UID) +} + +// typ implements message.typ. +func (t *tusymlink) typ() msgType { + return msgTusymlink +} + +// String implements fmt.Stringer. +func (t *tusymlink) String() string { + return fmt.Sprintf("Tusymlink{Tsymlink: %v, UID: %d}", &t.tsymlink, t.UID) +} + +// rusymlink is a usymlink response. +type rusymlink struct { + rsymlink +} + +// typ implements message.typ. +func (*rusymlink) typ() msgType { + return msgRusymlink +} + +// String implements fmt.Stringer. +func (r *rusymlink) String() string { + return fmt.Sprintf("Rusymlink{%v}", &r.rsymlink) +} + +// LockType is lock type for Tlock +type LockType uint8 + +// These constants define Lock operations: Read, Write, and Un(lock) +// They map to Linux values of F_RDLCK, F_WRLCK, F_UNLCK. +// If that seems a little Linux-centric, recall that the "L" +// in 9P2000.L means "Linux" :-) +const ( + ReadLock LockType = iota + WriteLock + Unlock +) + +func (l LockType) String() string { + switch l { + case ReadLock: + return "ReadLock" + case WriteLock: + return "WriteLock" + case Unlock: + return "Unlock" + } + return "unknown lock type" +} + +// LockFlags are flags for the lock. Currently, and possibly forever, only one +// is really used: LockFlagsBlock +type LockFlags uint32 + +const ( + // LockFlagsBlock indicates a blocking request. + LockFlagsBlock LockFlags = 1 + + // LockFlagsReclaim is "Reserved for future use." + // It's been some time since 9P2000.L came about, + // I suspect "future" in this case is "never"? + LockFlagsReclaim LockFlags = 2 +) + +// LockStatus contains lock status result. +type LockStatus uint8 + +// These are the four current return values for Rlock. +const ( + LockStatusOK LockStatus = iota + LockStatusBlocked + LockStatusError + LockStatusGrace +) + +func (s LockStatus) String() string { + switch s { + case LockStatusOK: + return "LockStatusOK" + case LockStatusBlocked: + return "LockStatusBlocked" + case LockStatusError: + return "LockStatusError" + case LockStatusGrace: + return "LockStatusGrace" + } + return "unknown lock status" +} + +// tlock is a Tlock message +type tlock struct { + // fid is the fid to lock. + fid fid + + Type LockType // Type of lock: F_RDLCK, F_WRLCK, F_UNLCK */ + Flags LockFlags // flags, not whence, docs are wrong. + Start uint64 // Starting offset for lock + Length uint64 // Number of bytes to lock + PID int32 // PID of process blocking our lock (F_GETLK only) + + // "client_id is an additional mechanism for uniquely + // identifying the lock requester and is set to the nodename + // by the Linux v9fs client." + // https://github.com/chaos/diod/blob/master/protocol.md#lock---acquire-or-release-a-posix-record-lock + Client string // Client id -- but technically can be anything. +} + +// decode implements encoder.decode. +func (t *tlock) decode(b *buffer) { + t.fid = b.ReadFID() + t.Type = LockType(b.Read8()) + t.Flags = LockFlags(b.Read32()) + t.Start = b.Read64() + t.Length = b.Read64() + t.PID = int32(b.Read32()) + t.Client = b.ReadString() +} + +// encode implements encoder.encode. +func (t *tlock) encode(b *buffer) { + b.WriteFID(t.fid) + b.Write8(uint8(t.Type)) + b.Write32(uint32(t.Flags)) + b.Write64(t.Start) + b.Write64(t.Length) + b.Write32(uint32(t.PID)) + b.WriteString(t.Client) +} + +// typ implements message.typ. +func (*tlock) typ() msgType { + return msgTlock +} + +// String implements fmt.Stringer. +func (t *tlock) String() string { + return fmt.Sprintf("Tlock{Type: %s, Flags: %#x, Start: %d, Length: %d, PID: %d, Client: %s}", t.Type.String(), t.Flags, t.Start, t.Length, t.PID, t.Client) +} + +// rlock is a lock response. +type rlock struct { + Status LockStatus +} + +// decode implements encoder.decode. +func (r *rlock) decode(b *buffer) { + r.Status = LockStatus(b.Read8()) +} + +// encode implements encoder.encode. +func (r *rlock) encode(b *buffer) { + b.Write8(uint8(r.Status)) +} + +// typ implements message.typ. +func (*rlock) typ() msgType { + return msgRlock +} + +// String implements fmt.Stringer. +func (r *rlock) String() string { + return fmt.Sprintf("Rlock{Status: %s}", r.Status) +} + +// Let's wait until we need this? POSIX locks over a network make 0 sense. +// getlock - test for the existence of a POSIX record lock +// size[4] Tgetlock tag[2] fid[4] type[1] start[8] length[8] proc_id[4] client_id[s] +// size[4] Rgetlock tag[2] type[1] start[8] length[8] proc_id[4] client_id[s] +// getlock tests for the existence of a POSIX record lock and has semantics similar to Linux fcntl(F_GETLK). + +// As with lock, type has one of the values defined above, and start, +// length, and proc_id correspond to the analogous fields in struct +// flock passed to Linux fcntl(F_GETLK), and client_Id is an +// additional mechanism for uniquely identifying the lock requester +// and is set to the nodename by the Linux v9fs client. tusymlink is +// a Tsymlink message that includes a UID. + +/// END LOCK + +const maxCacheSize = 3 + +// msgFactory is used to reduce allocations by caching messages for reuse. +type msgFactory struct { + create func() message + cache chan message +} + +// msgDotLRegistry indexes all 9P2000.L(.Google.N) message factories by type. +var msgDotLRegistry registry + +type registry struct { + factories [math.MaxUint8 + 1]msgFactory + + // largestFixedSize is computed so that given some message size M, you can + // compute the maximum payload size (e.g. for Twrite, Rread) with + // M-largestFixedSize. You could do this individual on a per-message basis, + // but it's easier to compute a single maximum safe payload. + largestFixedSize uint32 +} + +// get returns a new message by type. +// +// An error is returned in the case of an unknown message. +// +// This takes, and ignores, a message tag so that it may be used directly as a +// lookuptagAndType function for recv (by design). +func (r *registry) get(_ tag, t msgType) (message, error) { + entry := &r.factories[t] + if entry.create == nil { + return nil, &ErrInvalidMsgType{t} + } + + select { + case msg := <-entry.cache: + return msg, nil + default: + return entry.create(), nil + } +} + +func (r *registry) put(msg message) { + if p, ok := msg.(payloader); ok { + p.SetPayload(nil) + } + + entry := &r.factories[msg.typ()] + select { + case entry.cache <- msg: + default: + } +} + +// register registers the given message type. +// +// This may cause panic on failure and should only be used from init. +func (r *registry) register(t msgType, fn func() message) { + if int(t) >= len(r.factories) { + panic(fmt.Sprintf("message type %d is too large. It must be smaller than %d", t, len(r.factories))) + } + if r.factories[t].create != nil { + panic(fmt.Sprintf("duplicate message type %d: first is %T, second is %T", t, r.factories[t].create(), fn())) + } + r.factories[t] = msgFactory{ + create: fn, + cache: make(chan message, maxCacheSize), + } + + if size := calculateSize(fn()); size > r.largestFixedSize { + r.largestFixedSize = size + } +} + +func calculateSize(m message) uint32 { + if p, ok := m.(payloader); ok { + return p.FixedSize() + } + var dataBuf buffer + m.encode(&dataBuf) + return uint32(len(dataBuf.data)) +} + +func init() { + msgDotLRegistry.register(msgRlerror, func() message { return &rlerror{} }) + msgDotLRegistry.register(msgTstatfs, func() message { return &tstatfs{} }) + msgDotLRegistry.register(msgRstatfs, func() message { return &rstatfs{} }) + msgDotLRegistry.register(msgTlopen, func() message { return &tlopen{} }) + msgDotLRegistry.register(msgRlopen, func() message { return &rlopen{} }) + msgDotLRegistry.register(msgTlcreate, func() message { return &tlcreate{} }) + msgDotLRegistry.register(msgRlcreate, func() message { return &rlcreate{} }) + msgDotLRegistry.register(msgTsymlink, func() message { return &tsymlink{} }) + msgDotLRegistry.register(msgRsymlink, func() message { return &rsymlink{} }) + msgDotLRegistry.register(msgTmknod, func() message { return &tmknod{} }) + msgDotLRegistry.register(msgRmknod, func() message { return &rmknod{} }) + msgDotLRegistry.register(msgTrename, func() message { return &trename{} }) + msgDotLRegistry.register(msgRrename, func() message { return &rrename{} }) + msgDotLRegistry.register(msgTreadlink, func() message { return &treadlink{} }) + msgDotLRegistry.register(msgRreadlink, func() message { return &rreadlink{} }) + msgDotLRegistry.register(msgTgetattr, func() message { return &tgetattr{} }) + msgDotLRegistry.register(msgRgetattr, func() message { return &rgetattr{} }) + msgDotLRegistry.register(msgTsetattr, func() message { return &tsetattr{} }) + msgDotLRegistry.register(msgRsetattr, func() message { return &rsetattr{} }) + msgDotLRegistry.register(msgTxattrwalk, func() message { return &txattrwalk{} }) + msgDotLRegistry.register(msgRxattrwalk, func() message { return &rxattrwalk{} }) + msgDotLRegistry.register(msgTxattrcreate, func() message { return &txattrcreate{} }) + msgDotLRegistry.register(msgRxattrcreate, func() message { return &rxattrcreate{} }) + msgDotLRegistry.register(msgTreaddir, func() message { return &treaddir{} }) + msgDotLRegistry.register(msgRreaddir, func() message { return &rreaddir{} }) + msgDotLRegistry.register(msgTfsync, func() message { return &tfsync{} }) + msgDotLRegistry.register(msgRfsync, func() message { return &rfsync{} }) + msgDotLRegistry.register(msgTlink, func() message { return &tlink{} }) + msgDotLRegistry.register(msgRlink, func() message { return &rlink{} }) + msgDotLRegistry.register(msgTlock, func() message { return &tlock{} }) + msgDotLRegistry.register(msgRlock, func() message { return &rlock{} }) + msgDotLRegistry.register(msgTmkdir, func() message { return &tmkdir{} }) + msgDotLRegistry.register(msgRmkdir, func() message { return &rmkdir{} }) + msgDotLRegistry.register(msgTrenameat, func() message { return &trenameat{} }) + msgDotLRegistry.register(msgRrenameat, func() message { return &rrenameat{} }) + msgDotLRegistry.register(msgTunlinkat, func() message { return &tunlinkat{} }) + msgDotLRegistry.register(msgRunlinkat, func() message { return &runlinkat{} }) + msgDotLRegistry.register(msgTversion, func() message { return &tversion{} }) + msgDotLRegistry.register(msgRversion, func() message { return &rversion{} }) + msgDotLRegistry.register(msgTauth, func() message { return &tauth{} }) + msgDotLRegistry.register(msgRauth, func() message { return &rauth{} }) + msgDotLRegistry.register(msgTattach, func() message { return &tattach{} }) + msgDotLRegistry.register(msgRattach, func() message { return &rattach{} }) + msgDotLRegistry.register(msgTflush, func() message { return &tflush{} }) + msgDotLRegistry.register(msgRflush, func() message { return &rflush{} }) + msgDotLRegistry.register(msgTwalk, func() message { return &twalk{} }) + msgDotLRegistry.register(msgRwalk, func() message { return &rwalk{} }) + msgDotLRegistry.register(msgTread, func() message { return &tread{} }) + msgDotLRegistry.register(msgRread, func() message { return &rread{} }) + msgDotLRegistry.register(msgTwrite, func() message { return &twrite{} }) + msgDotLRegistry.register(msgRwrite, func() message { return &rwrite{} }) + msgDotLRegistry.register(msgTclunk, func() message { return &tclunk{} }) + msgDotLRegistry.register(msgRclunk, func() message { return &rclunk{} }) + msgDotLRegistry.register(msgTremove, func() message { return &tremove{} }) + msgDotLRegistry.register(msgRremove, func() message { return &rremove{} }) + msgDotLRegistry.register(msgTwalkgetattr, func() message { return &twalkgetattr{} }) + msgDotLRegistry.register(msgRwalkgetattr, func() message { return &rwalkgetattr{} }) + msgDotLRegistry.register(msgTucreate, func() message { return &tucreate{} }) + msgDotLRegistry.register(msgRucreate, func() message { return &rucreate{} }) + msgDotLRegistry.register(msgTumkdir, func() message { return &tumkdir{} }) + msgDotLRegistry.register(msgRumkdir, func() message { return &rumkdir{} }) + msgDotLRegistry.register(msgTumknod, func() message { return &tumknod{} }) + msgDotLRegistry.register(msgRumknod, func() message { return &rumknod{} }) + msgDotLRegistry.register(msgTusymlink, func() message { return &tusymlink{} }) + msgDotLRegistry.register(msgRusymlink, func() message { return &rusymlink{} }) +} diff --git a/vendor/github.com/hugelgupf/p9/p9/p9.go b/vendor/github.com/hugelgupf/p9/p9/p9.go new file mode 100644 index 0000000000..e3c3f32716 --- /dev/null +++ b/vendor/github.com/hugelgupf/p9/p9/p9.go @@ -0,0 +1,1167 @@ +// Copyright 2018 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package p9 is a 9P2000.L implementation. +// +// Servers implement Attacher and File interfaces. +// +// Clients can use Client. +package p9 + +import ( + "fmt" + "math" + "os" + "strings" + "sync/atomic" + + "github.com/hugelgupf/p9/internal" +) + +// Debug can be assigned to log.Printf to print messages received and sent. +var Debug = func(fmt string, v ...interface{}) {} + +const ( + // DefaultMessageSize is a sensible default. + DefaultMessageSize uint32 = 64 << 10 +) + +// OpenFlags is the mode passed to Open and Create operations. +// +// These correspond to bits sent over the wire. +type OpenFlags uint32 + +const ( + // ReadOnly is a Topen and Tcreate flag indicating read-only mode. + ReadOnly OpenFlags = 0 + + // WriteOnly is a Topen and Tcreate flag indicating write-only mode. + WriteOnly OpenFlags = 1 + + // ReadWrite is a Topen flag indicates read-write mode. + ReadWrite OpenFlags = 2 + + // OpenFlagsModeMask is a mask of valid OpenFlags mode bits. + OpenFlagsModeMask OpenFlags = 3 +) + +// Mode returns only the open mode (read-only, read-write, or write-only). +func (o OpenFlags) Mode() OpenFlags { + return o & OpenFlagsModeMask +} + +// OSFlags converts a p9.OpenFlags to an int compatible with open(2). +func (o OpenFlags) OSFlags() int { + return int(o & OpenFlagsModeMask) +} + +// String implements fmt.Stringer. +func (o OpenFlags) String() string { + switch o { + case ReadOnly: + return "ReadOnly" + case WriteOnly: + return "WriteOnly" + case ReadWrite: + return "ReadWrite" + case OpenFlagsModeMask: + return "OpenFlagsModeMask" + default: + return fmt.Sprintf("unknown (%#x)", uint32(o)) + } +} + +// XattrFlags are flags set on a setxattr operation. +type XattrFlags int + +const ( + // XattrCreate set on setxattr requires a pure create, which fails if + // the named attribute already exists. + XattrCreate XattrFlags = 1 + + // XattrReplace set on setxattr requires a pure replace, which fails if + // the named attribute does not already exist. + XattrReplace XattrFlags = 2 +) + +// tag is a message tag. +type tag uint16 + +// fid is a file identifier. +type fid uint64 + +// FileMode are flags corresponding to file modes. +// +// These correspond to bits sent over the wire. +// These also correspond to mode_t bits. +type FileMode uint32 + +const ( + // FileModeMask is a mask of all the file mode bits of FileMode. + FileModeMask FileMode = 0170000 + + // ModeSocket is an (unused) mode bit for a socket. + ModeSocket FileMode = 0140000 + + // ModeSymlink is a mode bit for a symlink. + ModeSymlink FileMode = 0120000 + + // ModeRegular is a mode bit for regular files. + ModeRegular FileMode = 0100000 + + // ModeBlockDevice is a mode bit for block devices. + ModeBlockDevice FileMode = 060000 + + // ModeDirectory is a mode bit for directories. + ModeDirectory FileMode = 040000 + + // ModeCharacterDevice is a mode bit for a character device. + ModeCharacterDevice FileMode = 020000 + + // ModeNamedPipe is a mode bit for a named pipe. + ModeNamedPipe FileMode = 010000 + + // Read is a mode bit indicating read permission. + Read FileMode = 04 + + // Write is a mode bit indicating write permission. + Write FileMode = 02 + + // Exec is a mode bit indicating exec permission. + Exec FileMode = 01 + + // AllPermissions is a mask with rwx bits set for user, group and others. + AllPermissions FileMode = 0777 + + // Sticky is a mode bit indicating sticky directories. + Sticky FileMode = 01000 + + // permissionsMask is the mask to apply to FileModes for permissions. It + // includes rwx bits for user, group and others, and sticky bit. + permissionsMask FileMode = 01777 +) + +// QIDType is the most significant byte of the FileMode word, to be used as the +// Type field of p9.QID. +func (m FileMode) QIDType() QIDType { + switch { + case m.IsDir(): + return TypeDir + case m.IsSocket(), m.IsNamedPipe(), m.IsCharacterDevice(): + // Best approximation. + return TypeAppendOnly + case m.IsSymlink(): + return TypeSymlink + default: + return TypeRegular + } +} + +// FileType returns the file mode without the permission bits. +func (m FileMode) FileType() FileMode { + return m & FileModeMask +} + +// Permissions returns just the permission bits of the mode. +func (m FileMode) Permissions() FileMode { + return m & permissionsMask +} + +// Writable returns the mode with write bits added. +func (m FileMode) Writable() FileMode { + return m | 0222 +} + +// IsReadable returns true if m represents a file that can be read. +func (m FileMode) IsReadable() bool { + return m&0444 != 0 +} + +// IsWritable returns true if m represents a file that can be written to. +func (m FileMode) IsWritable() bool { + return m&0222 != 0 +} + +// IsExecutable returns true if m represents a file that can be executed. +func (m FileMode) IsExecutable() bool { + return m&0111 != 0 +} + +// IsRegular returns true if m is a regular file. +func (m FileMode) IsRegular() bool { + return m&FileModeMask == ModeRegular +} + +// IsDir returns true if m represents a directory. +func (m FileMode) IsDir() bool { + return m&FileModeMask == ModeDirectory +} + +// IsNamedPipe returns true if m represents a named pipe. +func (m FileMode) IsNamedPipe() bool { + return m&FileModeMask == ModeNamedPipe +} + +// IsCharacterDevice returns true if m represents a character device. +func (m FileMode) IsCharacterDevice() bool { + return m&FileModeMask == ModeCharacterDevice +} + +// IsBlockDevice returns true if m represents a character device. +func (m FileMode) IsBlockDevice() bool { + return m&FileModeMask == ModeBlockDevice +} + +// IsSocket returns true if m represents a socket. +func (m FileMode) IsSocket() bool { + return m&FileModeMask == ModeSocket +} + +// IsSymlink returns true if m represents a symlink. +func (m FileMode) IsSymlink() bool { + return m&FileModeMask == ModeSymlink +} + +// ModeFromOS returns a FileMode from an os.FileMode. +func ModeFromOS(mode os.FileMode) FileMode { + m := FileMode(mode.Perm()) + switch { + case mode.IsDir(): + m |= ModeDirectory + case mode&os.ModeSymlink != 0: + m |= ModeSymlink + case mode&os.ModeSocket != 0: + m |= ModeSocket + case mode&os.ModeNamedPipe != 0: + m |= ModeNamedPipe + case mode&os.ModeCharDevice != 0: + m |= ModeCharacterDevice + case mode&os.ModeDevice != 0: + m |= ModeBlockDevice + default: + m |= ModeRegular + } + return m +} + +// OSMode converts a p9.FileMode to an os.FileMode. +func (m FileMode) OSMode() os.FileMode { + var osMode os.FileMode + osMode |= os.FileMode(m.Permissions()) + switch { + case m.IsDir(): + osMode |= os.ModeDir + case m.IsSymlink(): + osMode |= os.ModeSymlink + case m.IsSocket(): + osMode |= os.ModeSocket + case m.IsNamedPipe(): + osMode |= os.ModeNamedPipe + case m.IsCharacterDevice(): + osMode |= os.ModeCharDevice | os.ModeDevice + case m.IsBlockDevice(): + osMode |= os.ModeDevice + } + return osMode +} + +// UID represents a user ID. +type UID uint32 + +// Ok returns true if uid is not NoUID. +func (uid UID) Ok() bool { + return uid != NoUID +} + +// GID represents a group ID. +type GID uint32 + +// Ok returns true if gid is not NoGID. +func (gid GID) Ok() bool { + return gid != NoGID +} + +const ( + // notag is a sentinel used to indicate no valid tag. + noTag tag = math.MaxUint16 + + // Nofid is a sentinel used to indicate no valid fid. + noFID fid = math.MaxUint32 + + // NoUID is a sentinel used to indicate no valid UID. + NoUID UID = math.MaxUint32 + + // NoGID is a sentinel used to indicate no valid GID. + NoGID GID = math.MaxUint32 +) + +// msgType is a type identifier. +type msgType uint8 + +// msgType declarations. +const ( + msgRlerror msgType = 7 + msgTstatfs msgType = 8 + msgRstatfs msgType = 9 + msgTlopen msgType = 12 + msgRlopen msgType = 13 + msgTlcreate msgType = 14 + msgRlcreate msgType = 15 + msgTsymlink msgType = 16 + msgRsymlink msgType = 17 + msgTmknod msgType = 18 + msgRmknod msgType = 19 + msgTrename msgType = 20 + msgRrename msgType = 21 + msgTreadlink msgType = 22 + msgRreadlink msgType = 23 + msgTgetattr msgType = 24 + msgRgetattr msgType = 25 + msgTsetattr msgType = 26 + msgRsetattr msgType = 27 + msgTxattrwalk msgType = 30 + msgRxattrwalk msgType = 31 + msgTxattrcreate msgType = 32 + msgRxattrcreate msgType = 33 + msgTreaddir msgType = 40 + msgRreaddir msgType = 41 + msgTfsync msgType = 50 + msgRfsync msgType = 51 + msgTlock msgType = 52 + msgRlock msgType = 53 + msgTgetlock msgType = 54 + msgRgetlock msgType = 55 + msgTlink msgType = 70 + msgRlink msgType = 71 + msgTmkdir msgType = 72 + msgRmkdir msgType = 73 + msgTrenameat msgType = 74 + msgRrenameat msgType = 75 + msgTunlinkat msgType = 76 + msgRunlinkat msgType = 77 + msgTversion msgType = 100 + msgRversion msgType = 101 + msgTauth msgType = 102 + msgRauth msgType = 103 + msgTattach msgType = 104 + msgRattach msgType = 105 + msgTflush msgType = 108 + msgRflush msgType = 109 + msgTwalk msgType = 110 + msgRwalk msgType = 111 + msgTread msgType = 116 + msgRread msgType = 117 + msgTwrite msgType = 118 + msgRwrite msgType = 119 + msgTclunk msgType = 120 + msgRclunk msgType = 121 + msgTremove msgType = 122 + msgRremove msgType = 123 + msgTflushf msgType = 124 + msgRflushf msgType = 125 + msgTwalkgetattr msgType = 126 + msgRwalkgetattr msgType = 127 + msgTucreate msgType = 128 + msgRucreate msgType = 129 + msgTumkdir msgType = 130 + msgRumkdir msgType = 131 + msgTumknod msgType = 132 + msgRumknod msgType = 133 + msgTusymlink msgType = 134 + msgRusymlink msgType = 135 +) + +// QIDType represents the file type for QIDs. +// +// QIDType corresponds to the high 8 bits of a Plan 9 file mode. +type QIDType uint8 + +const ( + // TypeDir represents a directory type. + TypeDir QIDType = 0x80 + + // TypeAppendOnly represents an append only file. + TypeAppendOnly QIDType = 0x40 + + // TypeExclusive represents an exclusive-use file. + TypeExclusive QIDType = 0x20 + + // TypeMount represents a mounted channel. + TypeMount QIDType = 0x10 + + // TypeAuth represents an authentication file. + TypeAuth QIDType = 0x08 + + // TypeTemporary represents a temporary file. + TypeTemporary QIDType = 0x04 + + // TypeSymlink represents a symlink. + TypeSymlink QIDType = 0x02 + + // TypeLink represents a hard link. + TypeLink QIDType = 0x01 + + // TypeRegular represents a regular file. + TypeRegular QIDType = 0x00 +) + +var qidTypeString = map[QIDType]string{ + TypeDir: "QID Type Directory", + TypeAppendOnly: "QID Type Append Only", + TypeExclusive: "QID Type Exclusive", + TypeMount: "QID Type Mount", + TypeAuth: "QID Type Auth", + TypeTemporary: "QID Type Temporary", + TypeSymlink: "QID Type Symlink", + TypeLink: "QID Type Link", + TypeRegular: "QID Type Regular", +} + +func (q QIDType) String() string { + s, ok := qidTypeString[q] + if ok { + return s + } + return fmt.Sprintf("unknown QID type (%#x)", uint8(q)) +} + +// QID is a unique file identifier. +// +// This may be embedded in other requests and responses. +type QID struct { + // Type is the highest order byte of the file mode. + Type QIDType + + // Version is an arbitrary server version number. + Version uint32 + + // Path is a unique server identifier for this path (e.g. inode). + Path uint64 +} + +// String implements fmt.Stringer. +func (q QID) String() string { + return fmt.Sprintf("QID{Type: %d, Version: %d, Path: %d}", q.Type, q.Version, q.Path) +} + +// decode implements encoder.decode. +func (q *QID) decode(b *buffer) { + q.Type = b.ReadQIDType() + q.Version = b.Read32() + q.Path = b.Read64() +} + +// encode implements encoder.encode. +func (q *QID) encode(b *buffer) { + b.WriteQIDType(q.Type) + b.Write32(q.Version) + b.Write64(q.Path) +} + +// QIDGenerator is a simple generator for QIDs that atomically increments Path +// values. +type QIDGenerator struct { + // uids is an ever increasing value that can be atomically incremented + // to provide unique Path values for QIDs. + uids uint64 +} + +// Get returns a new 9P unique ID with a unique Path given a QID type. +// +// While the 9P spec allows Version to be incremented every time the file is +// modified, we currently do not use the Version member for anything. Hence, +// it is set to 0. +func (q *QIDGenerator) Get(t QIDType) QID { + return QID{ + Type: t, + Version: 0, + Path: atomic.AddUint64(&q.uids, 1), + } +} + +// FSStat is used by statfs. +type FSStat struct { + // Type is the filesystem type. + Type uint32 + + // BlockSize is the blocksize. + BlockSize uint32 + + // Blocks is the number of blocks. + Blocks uint64 + + // BlocksFree is the number of free blocks. + BlocksFree uint64 + + // BlocksAvailable is the number of blocks *available*. + BlocksAvailable uint64 + + // Files is the number of files available. + Files uint64 + + // FilesFree is the number of free file nodes. + FilesFree uint64 + + // FSID is the filesystem ID. + FSID uint64 + + // NameLength is the maximum name length. + NameLength uint32 +} + +// decode implements encoder.decode. +func (f *FSStat) decode(b *buffer) { + f.Type = b.Read32() + f.BlockSize = b.Read32() + f.Blocks = b.Read64() + f.BlocksFree = b.Read64() + f.BlocksAvailable = b.Read64() + f.Files = b.Read64() + f.FilesFree = b.Read64() + f.FSID = b.Read64() + f.NameLength = b.Read32() +} + +// encode implements encoder.encode. +func (f *FSStat) encode(b *buffer) { + b.Write32(f.Type) + b.Write32(f.BlockSize) + b.Write64(f.Blocks) + b.Write64(f.BlocksFree) + b.Write64(f.BlocksAvailable) + b.Write64(f.Files) + b.Write64(f.FilesFree) + b.Write64(f.FSID) + b.Write32(f.NameLength) +} + +// AttrMask is a mask of attributes for getattr. +type AttrMask struct { + Mode bool + NLink bool + UID bool + GID bool + RDev bool + ATime bool + MTime bool + CTime bool + INo bool + Size bool + Blocks bool + BTime bool + Gen bool + DataVersion bool +} + +// Contains returns true if a contains all of the attributes masked as b. +func (a AttrMask) Contains(b AttrMask) bool { + if b.Mode && !a.Mode { + return false + } + if b.NLink && !a.NLink { + return false + } + if b.UID && !a.UID { + return false + } + if b.GID && !a.GID { + return false + } + if b.RDev && !a.RDev { + return false + } + if b.ATime && !a.ATime { + return false + } + if b.MTime && !a.MTime { + return false + } + if b.CTime && !a.CTime { + return false + } + if b.INo && !a.INo { + return false + } + if b.Size && !a.Size { + return false + } + if b.Blocks && !a.Blocks { + return false + } + if b.BTime && !a.BTime { + return false + } + if b.Gen && !a.Gen { + return false + } + if b.DataVersion && !a.DataVersion { + return false + } + return true +} + +// Empty returns true if no fields are masked. +func (a AttrMask) Empty() bool { + return !a.Mode && !a.NLink && !a.UID && !a.GID && !a.RDev && !a.ATime && !a.MTime && !a.CTime && !a.INo && !a.Size && !a.Blocks && !a.BTime && !a.Gen && !a.DataVersion +} + +// AttrMaskAll is an AttrMask with all fields masked. +var AttrMaskAll = AttrMask{ + Mode: true, + NLink: true, + UID: true, + GID: true, + RDev: true, + ATime: true, + MTime: true, + CTime: true, + INo: true, + Size: true, + Blocks: true, + BTime: true, + Gen: true, + DataVersion: true, +} + +// String implements fmt.Stringer. +func (a AttrMask) String() string { + var masks []string + if a.Mode { + masks = append(masks, "Mode") + } + if a.NLink { + masks = append(masks, "NLink") + } + if a.UID { + masks = append(masks, "UID") + } + if a.GID { + masks = append(masks, "GID") + } + if a.RDev { + masks = append(masks, "RDev") + } + if a.ATime { + masks = append(masks, "ATime") + } + if a.MTime { + masks = append(masks, "MTime") + } + if a.CTime { + masks = append(masks, "CTime") + } + if a.INo { + masks = append(masks, "INo") + } + if a.Size { + masks = append(masks, "Size") + } + if a.Blocks { + masks = append(masks, "Blocks") + } + if a.BTime { + masks = append(masks, "BTime") + } + if a.Gen { + masks = append(masks, "Gen") + } + if a.DataVersion { + masks = append(masks, "DataVersion") + } + return fmt.Sprintf("AttrMask{with: %s}", strings.Join(masks, " ")) +} + +// decode implements encoder.decode. +func (a *AttrMask) decode(b *buffer) { + mask := b.Read64() + a.Mode = mask&0x00000001 != 0 + a.NLink = mask&0x00000002 != 0 + a.UID = mask&0x00000004 != 0 + a.GID = mask&0x00000008 != 0 + a.RDev = mask&0x00000010 != 0 + a.ATime = mask&0x00000020 != 0 + a.MTime = mask&0x00000040 != 0 + a.CTime = mask&0x00000080 != 0 + a.INo = mask&0x00000100 != 0 + a.Size = mask&0x00000200 != 0 + a.Blocks = mask&0x00000400 != 0 + a.BTime = mask&0x00000800 != 0 + a.Gen = mask&0x00001000 != 0 + a.DataVersion = mask&0x00002000 != 0 +} + +// encode implements encoder.encode. +func (a *AttrMask) encode(b *buffer) { + var mask uint64 + if a.Mode { + mask |= 0x00000001 + } + if a.NLink { + mask |= 0x00000002 + } + if a.UID { + mask |= 0x00000004 + } + if a.GID { + mask |= 0x00000008 + } + if a.RDev { + mask |= 0x00000010 + } + if a.ATime { + mask |= 0x00000020 + } + if a.MTime { + mask |= 0x00000040 + } + if a.CTime { + mask |= 0x00000080 + } + if a.INo { + mask |= 0x00000100 + } + if a.Size { + mask |= 0x00000200 + } + if a.Blocks { + mask |= 0x00000400 + } + if a.BTime { + mask |= 0x00000800 + } + if a.Gen { + mask |= 0x00001000 + } + if a.DataVersion { + mask |= 0x00002000 + } + b.Write64(mask) +} + +// NLink is the number of links to this fs object. +// +// While this type has no utilities, it is useful in order to force linux+amd64 +// only developers to cast to NLink for the NLink field, which will make their +// code compatible with other GOARCH and GOOS values. +type NLink uint64 + +// Dev is the device number of an fs object. +// +// While this type has no utilities, it is useful in order to force linux+amd64 +// only developers to cast to Dev for the Dev field, which will make their +// code compatible with other GOARCH and GOOS values. +type Dev uint64 + +// Attr is a set of attributes for getattr. +type Attr struct { + Mode FileMode + UID UID + GID GID + NLink NLink + RDev Dev + Size uint64 + BlockSize uint64 + Blocks uint64 + ATimeSeconds uint64 + ATimeNanoSeconds uint64 + MTimeSeconds uint64 + MTimeNanoSeconds uint64 + CTimeSeconds uint64 + CTimeNanoSeconds uint64 + BTimeSeconds uint64 + BTimeNanoSeconds uint64 + Gen uint64 + DataVersion uint64 +} + +// String implements fmt.Stringer. +func (a Attr) String() string { + return fmt.Sprintf("Attr{Mode: 0o%o, UID: %d, GID: %d, NLink: %d, RDev: %d, Size: %d, BlockSize: %d, Blocks: %d, ATime: {Sec: %d, NanoSec: %d}, MTime: {Sec: %d, NanoSec: %d}, CTime: {Sec: %d, NanoSec: %d}, BTime: {Sec: %d, NanoSec: %d}, Gen: %d, DataVersion: %d}", + a.Mode, a.UID, a.GID, a.NLink, a.RDev, a.Size, a.BlockSize, a.Blocks, a.ATimeSeconds, a.ATimeNanoSeconds, a.MTimeSeconds, a.MTimeNanoSeconds, a.CTimeSeconds, a.CTimeNanoSeconds, a.BTimeSeconds, a.BTimeNanoSeconds, a.Gen, a.DataVersion) +} + +// Apply applies this to the given Attr. +func (a Attr) WithMask(mask AttrMask) Attr { + var b Attr + if mask.Mode { + b.Mode = a.Mode + } + if mask.NLink { + b.NLink = a.NLink + } + if mask.UID { + b.UID = a.UID + } + if mask.GID { + b.GID = a.GID + } + if mask.RDev { + b.RDev = a.RDev + } + if mask.ATime { + b.ATimeSeconds = a.ATimeSeconds + b.ATimeNanoSeconds = a.ATimeNanoSeconds + } + if mask.MTime { + b.MTimeSeconds = a.MTimeSeconds + b.MTimeNanoSeconds = a.MTimeNanoSeconds + } + if mask.CTime { + b.CTimeSeconds = a.CTimeSeconds + b.CTimeNanoSeconds = a.CTimeNanoSeconds + } + + // Unclear on mask.INo. It corresponds to the inode number, but the + // inode number really is subsumed in the QID's path field normally and + // not accessible via GetAttr anyway. + + if mask.Size { + b.Size = a.Size + } + if mask.Blocks { + b.Blocks = a.Blocks + // I don't know if Size or Blocks fills in BlockSize. + b.BlockSize = a.BlockSize + } + if mask.BTime { + b.BTimeSeconds = a.BTimeSeconds + b.BTimeNanoSeconds = a.BTimeNanoSeconds + } + if mask.Gen { + b.Gen = a.Gen + } + if mask.DataVersion { + b.DataVersion = a.DataVersion + } + return b +} + +// encode implements encoder.encode. +func (a *Attr) encode(b *buffer) { + b.WriteFileMode(a.Mode) + b.WriteUID(a.UID) + b.WriteGID(a.GID) + b.Write64(uint64(a.NLink)) + b.Write64(uint64(a.RDev)) + b.Write64(a.Size) + b.Write64(a.BlockSize) + b.Write64(a.Blocks) + b.Write64(a.ATimeSeconds) + b.Write64(a.ATimeNanoSeconds) + b.Write64(a.MTimeSeconds) + b.Write64(a.MTimeNanoSeconds) + b.Write64(a.CTimeSeconds) + b.Write64(a.CTimeNanoSeconds) + b.Write64(a.BTimeSeconds) + b.Write64(a.BTimeNanoSeconds) + b.Write64(a.Gen) + b.Write64(a.DataVersion) +} + +// decode implements encoder.decode. +func (a *Attr) decode(b *buffer) { + a.Mode = b.ReadFileMode() + a.UID = b.ReadUID() + a.GID = b.ReadGID() + a.NLink = NLink(b.Read64()) + a.RDev = Dev(b.Read64()) + a.Size = b.Read64() + a.BlockSize = b.Read64() + a.Blocks = b.Read64() + a.ATimeSeconds = b.Read64() + a.ATimeNanoSeconds = b.Read64() + a.MTimeSeconds = b.Read64() + a.MTimeNanoSeconds = b.Read64() + a.CTimeSeconds = b.Read64() + a.CTimeNanoSeconds = b.Read64() + a.BTimeSeconds = b.Read64() + a.BTimeNanoSeconds = b.Read64() + a.Gen = b.Read64() + a.DataVersion = b.Read64() +} + +// StatToAttr converts a Linux syscall stat structure to an Attr. +func StatToAttr(s *internal.Stat_t, req AttrMask) (Attr, AttrMask) { + attr := Attr{ + UID: NoUID, + GID: NoGID, + } + if req.Mode { + // p9.FileMode corresponds to Linux mode_t. + attr.Mode = FileMode(s.Mode) + } + if req.NLink { + attr.NLink = NLink(s.Nlink) + } + if req.UID { + attr.UID = UID(s.Uid) + } + if req.GID { + attr.GID = GID(s.Gid) + } + if req.RDev { + attr.RDev = Dev(s.Dev) + } + if req.ATime { + attr.ATimeSeconds = uint64(s.Atim.Sec) + attr.ATimeNanoSeconds = uint64(s.Atim.Nsec) + } + if req.MTime { + attr.MTimeSeconds = uint64(s.Mtim.Sec) + attr.MTimeNanoSeconds = uint64(s.Mtim.Nsec) + } + if req.CTime { + attr.CTimeSeconds = uint64(s.Ctim.Sec) + attr.CTimeNanoSeconds = uint64(s.Ctim.Nsec) + } + if req.Size { + attr.Size = uint64(s.Size) + } + if req.Blocks { + attr.BlockSize = uint64(s.Blksize) + attr.Blocks = uint64(s.Blocks) + } + + // Use the req field because we already have it. + req.BTime = false + req.Gen = false + req.DataVersion = false + + return attr, req +} + +// SetAttrMask specifies a valid mask for setattr. +type SetAttrMask struct { + Permissions bool + UID bool + GID bool + Size bool + ATime bool + MTime bool + CTime bool + ATimeNotSystemTime bool + MTimeNotSystemTime bool +} + +// IsSubsetOf returns whether s is a subset of m. +func (s SetAttrMask) IsSubsetOf(m SetAttrMask) bool { + sb := s.bitmask() + sm := m.bitmask() + return sm|sb == sm +} + +// String implements fmt.Stringer. +func (s SetAttrMask) String() string { + var masks []string + if s.Permissions { + masks = append(masks, "Permissions") + } + if s.UID { + masks = append(masks, "UID") + } + if s.GID { + masks = append(masks, "GID") + } + if s.Size { + masks = append(masks, "Size") + } + if s.ATime { + masks = append(masks, "ATime") + } + if s.MTime { + masks = append(masks, "MTime") + } + if s.CTime { + masks = append(masks, "CTime") + } + if s.ATimeNotSystemTime { + masks = append(masks, "ATimeNotSystemTime") + } + if s.MTimeNotSystemTime { + masks = append(masks, "MTimeNotSystemTime") + } + return fmt.Sprintf("SetAttrMask{with: %s}", strings.Join(masks, " ")) +} + +// Empty returns true if no fields are masked. +func (s SetAttrMask) Empty() bool { + return !s.Permissions && !s.UID && !s.GID && !s.Size && !s.ATime && !s.MTime && !s.CTime && !s.ATimeNotSystemTime && !s.MTimeNotSystemTime +} + +// decode implements encoder.decode. +func (s *SetAttrMask) decode(b *buffer) { + mask := b.Read32() + s.Permissions = mask&0x00000001 != 0 + s.UID = mask&0x00000002 != 0 + s.GID = mask&0x00000004 != 0 + s.Size = mask&0x00000008 != 0 + s.ATime = mask&0x00000010 != 0 + s.MTime = mask&0x00000020 != 0 + s.CTime = mask&0x00000040 != 0 + s.ATimeNotSystemTime = mask&0x00000080 != 0 + s.MTimeNotSystemTime = mask&0x00000100 != 0 +} + +func (s SetAttrMask) bitmask() uint32 { + var mask uint32 + if s.Permissions { + mask |= 0x00000001 + } + if s.UID { + mask |= 0x00000002 + } + if s.GID { + mask |= 0x00000004 + } + if s.Size { + mask |= 0x00000008 + } + if s.ATime { + mask |= 0x00000010 + } + if s.MTime { + mask |= 0x00000020 + } + if s.CTime { + mask |= 0x00000040 + } + if s.ATimeNotSystemTime { + mask |= 0x00000080 + } + if s.MTimeNotSystemTime { + mask |= 0x00000100 + } + return mask +} + +// encode implements encoder.encode. +func (s *SetAttrMask) encode(b *buffer) { + b.Write32(s.bitmask()) +} + +// SetAttr specifies a set of attributes for a setattr. +type SetAttr struct { + Permissions FileMode + UID UID + GID GID + Size uint64 + ATimeSeconds uint64 + ATimeNanoSeconds uint64 + MTimeSeconds uint64 + MTimeNanoSeconds uint64 +} + +// String implements fmt.Stringer. +func (s SetAttr) String() string { + return fmt.Sprintf("SetAttr{Permissions: 0o%o, UID: %d, GID: %d, Size: %d, ATime: {Sec: %d, NanoSec: %d}, MTime: {Sec: %d, NanoSec: %d}}", s.Permissions, s.UID, s.GID, s.Size, s.ATimeSeconds, s.ATimeNanoSeconds, s.MTimeSeconds, s.MTimeNanoSeconds) +} + +// decode implements encoder.decode. +func (s *SetAttr) decode(b *buffer) { + s.Permissions = b.ReadPermissions() + s.UID = b.ReadUID() + s.GID = b.ReadGID() + s.Size = b.Read64() + s.ATimeSeconds = b.Read64() + s.ATimeNanoSeconds = b.Read64() + s.MTimeSeconds = b.Read64() + s.MTimeNanoSeconds = b.Read64() +} + +// encode implements encoder.encode. +func (s *SetAttr) encode(b *buffer) { + b.WritePermissions(s.Permissions) + b.WriteUID(s.UID) + b.WriteGID(s.GID) + b.Write64(s.Size) + b.Write64(s.ATimeSeconds) + b.Write64(s.ATimeNanoSeconds) + b.Write64(s.MTimeSeconds) + b.Write64(s.MTimeNanoSeconds) +} + +// Apply applies this to the given Attr. +func (a *Attr) Apply(mask SetAttrMask, attr SetAttr) { + if mask.Permissions { + a.Mode = a.Mode&^permissionsMask | (attr.Permissions & permissionsMask) + } + if mask.UID { + a.UID = attr.UID + } + if mask.GID { + a.GID = attr.GID + } + if mask.Size { + a.Size = attr.Size + } + if mask.ATime { + a.ATimeSeconds = attr.ATimeSeconds + a.ATimeNanoSeconds = attr.ATimeNanoSeconds + } + if mask.MTime { + a.MTimeSeconds = attr.MTimeSeconds + a.MTimeNanoSeconds = attr.MTimeNanoSeconds + } +} + +// Dirents is a collection of directory entries. +type Dirents []Dirent + +// Find returns a Dirent with the given name if it exists, or nil. +func (d Dirents) Find(name string) *Dirent { + for _, dir := range d { + if dir.Name == name { + return &dir + } + } + return nil +} + +// Dirent represents a directory entry in File.Readdir. +type Dirent struct { + // QID is the entry QID. + QID QID + + // Offset is the offset in the directory. + // + // This will be communicated back the original caller. + Offset uint64 + + // Type is the 9P type. + Type QIDType + + // Name is the name of the entry (i.e. basename). + Name string +} + +// String implements fmt.Stringer. +func (d Dirent) String() string { + return fmt.Sprintf("Dirent{QID: %d, Offset: %d, Type: 0x%X, Name: %s}", d.QID, d.Offset, d.Type, d.Name) +} + +// decode implements encoder.decode. +func (d *Dirent) decode(b *buffer) { + d.QID.decode(b) + d.Offset = b.Read64() + d.Type = b.ReadQIDType() + d.Name = b.ReadString() +} + +// encode implements encoder.encode. +func (d *Dirent) encode(b *buffer) { + d.QID.encode(b) + b.Write64(d.Offset) + b.WriteQIDType(d.Type) + b.WriteString(d.Name) +} diff --git a/vendor/github.com/hugelgupf/p9/p9/path_tree.go b/vendor/github.com/hugelgupf/p9/p9/path_tree.go new file mode 100644 index 0000000000..745de5138a --- /dev/null +++ b/vendor/github.com/hugelgupf/p9/p9/path_tree.go @@ -0,0 +1,238 @@ +// Copyright 2018 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package p9 + +import ( + "fmt" + "sync" +) + +// pathNode is a single node in a path traversal. +// +// These are shared by all fidRefs that point to the same path. +// +// Lock ordering: +// +// opMu +// childMu +// +// Two different pathNodes may only be locked if Server.renameMu is held for +// write, in which case they can be acquired in any order. +type pathNode struct { + // opMu synchronizes high-level, sematic operations, such as the + // simultaneous creation and deletion of a file. + opMu sync.RWMutex + + // deleted indicates that the backing file has been deleted. We stop many + // operations at the API level if they are incompatible with a file that has + // already been unlinked. deleted is protected by opMu. However, it may be + // changed without opMu if this node is deleted as part of an entire subtree + // on unlink. So deleted must only be accessed/mutated using atomics. + deleted uint32 + + // childMu protects the fields below. + childMu sync.RWMutex + + // childNodes maps child path component names to their pathNode. + childNodes map[string]*pathNode + + // childRefs maps child path component names to all of the their + // references. + childRefs map[string]map[*fidRef]struct{} + + // childRefNames maps child references back to their path component + // name. + childRefNames map[*fidRef]string +} + +func newPathNode() *pathNode { + return &pathNode{ + childNodes: make(map[string]*pathNode), + childRefs: make(map[string]map[*fidRef]struct{}), + childRefNames: make(map[*fidRef]string), + } +} + +// forEachChildRef calls fn for each child reference. +func (p *pathNode) forEachChildRef(fn func(ref *fidRef, name string)) { + p.childMu.RLock() + defer p.childMu.RUnlock() + + for name, m := range p.childRefs { + for ref := range m { + fn(ref, name) + } + } +} + +// forEachChildNode calls fn for each child pathNode. +func (p *pathNode) forEachChildNode(fn func(pn *pathNode)) { + p.childMu.RLock() + defer p.childMu.RUnlock() + + for _, pn := range p.childNodes { + fn(pn) + } +} + +// pathNodeFor returns the path node for the given name, or a new one. +func (p *pathNode) pathNodeFor(name string) *pathNode { + p.childMu.RLock() + // Fast path, node already exists. + if pn, ok := p.childNodes[name]; ok { + p.childMu.RUnlock() + return pn + } + p.childMu.RUnlock() + + // Slow path, create a new pathNode for shared use. + p.childMu.Lock() + + // Re-check after re-lock. + if pn, ok := p.childNodes[name]; ok { + p.childMu.Unlock() + return pn + } + + pn := newPathNode() + p.childNodes[name] = pn + p.childMu.Unlock() + return pn +} + +// nameFor returns the name for the given fidRef. +// +// Precondition: addChild is called for ref before nameFor. +func (p *pathNode) nameFor(ref *fidRef) string { + p.childMu.RLock() + n, ok := p.childRefNames[ref] + p.childMu.RUnlock() + + if !ok { + // This should not happen, don't proceed. + panic(fmt.Sprintf("expected name for %+v, none found", ref)) + } + + return n +} + +// addChildLocked adds a child reference to p. +// +// Precondition: As addChild, plus childMu is locked for write. +func (p *pathNode) addChildLocked(ref *fidRef, name string) { + if n, ok := p.childRefNames[ref]; ok { + // This should not happen, don't proceed. + panic(fmt.Sprintf("unexpected fidRef %+v with path %q, wanted %q", ref, n, name)) + } + + p.childRefNames[ref] = name + + m, ok := p.childRefs[name] + if !ok { + m = make(map[*fidRef]struct{}) + p.childRefs[name] = m + } + + m[ref] = struct{}{} +} + +// addChild adds a child reference to p. +// +// Precondition: ref may only be added once at a time. +func (p *pathNode) addChild(ref *fidRef, name string) { + p.childMu.Lock() + p.addChildLocked(ref, name) + p.childMu.Unlock() +} + +// removeChild removes the given child. +// +// This applies only to an individual fidRef, which is not required to exist. +func (p *pathNode) removeChild(ref *fidRef) { + p.childMu.Lock() + + // This ref may not exist anymore. This can occur, e.g., in unlink, + // where a removeWithName removes the ref, and then a DecRef on the ref + // attempts to remove again. + if name, ok := p.childRefNames[ref]; ok { + m, ok := p.childRefs[name] + if !ok { + // This should not happen, don't proceed. + p.childMu.Unlock() + panic(fmt.Sprintf("name %s missing from childfidRefs", name)) + } + + delete(m, ref) + if len(m) == 0 { + delete(p.childRefs, name) + } + } + + delete(p.childRefNames, ref) + + p.childMu.Unlock() +} + +// addPathNodeFor adds an existing pathNode as the node for name. +// +// Preconditions: newName does not exist. +func (p *pathNode) addPathNodeFor(name string, pn *pathNode) { + p.childMu.Lock() + + if opn, ok := p.childNodes[name]; ok { + p.childMu.Unlock() + panic(fmt.Sprintf("unexpected pathNode %+v with path %q", opn, name)) + } + + p.childNodes[name] = pn + p.childMu.Unlock() +} + +// removeWithName removes all references with the given name. +// +// The provided function is executed after reference removal. The only method +// it may (transitively) call on this pathNode is addChildLocked. +// +// If a child pathNode for name exists, it is removed from this pathNode and +// returned by this function. Any operations on the removed tree must use this +// value. +func (p *pathNode) removeWithName(name string, fn func(ref *fidRef)) *pathNode { + p.childMu.Lock() + defer p.childMu.Unlock() + + if m, ok := p.childRefs[name]; ok { + for ref := range m { + delete(m, ref) + delete(p.childRefNames, ref) + if fn == nil { + continue + } + + // Attempt to hold a reference while calling fn() to + // prevent concurrent destruction of the child, which + // can lead to data races. If the child has already + // been destroyed, then we can skip the callback. + if ref.TryIncRef() { + fn(ref) + ref.DecRef() + } + } + } + + // Return the original path node, if it exists. + origPathNode := p.childNodes[name] + delete(p.childNodes, name) + return origPathNode +} diff --git a/vendor/github.com/hugelgupf/p9/p9/pool.go b/vendor/github.com/hugelgupf/p9/p9/pool.go new file mode 100644 index 0000000000..0aa274f113 --- /dev/null +++ b/vendor/github.com/hugelgupf/p9/p9/pool.go @@ -0,0 +1,65 @@ +// Copyright 2018 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package p9 + +import ( + "sync" +) + +// pool is a simple allocator. +// +// It is used for both tags and FIDs. +type pool struct { + mu sync.Mutex + + // cache is the set of returned values. + cache []uint64 + + // start is the starting value (if needed). + start uint64 + + // limit is the upper limit. + limit uint64 +} + +// Get gets a value from the pool. +func (p *pool) Get() (uint64, bool) { + p.mu.Lock() + defer p.mu.Unlock() + + // Anything cached? + if len(p.cache) > 0 { + v := p.cache[len(p.cache)-1] + p.cache = p.cache[:len(p.cache)-1] + return v, true + } + + // Over the limit? + if p.start == p.limit { + return 0, false + } + + // Generate a new value. + v := p.start + p.start++ + return v, true +} + +// Put returns a value to the pool. +func (p *pool) Put(v uint64) { + p.mu.Lock() + p.cache = append(p.cache, v) + p.mu.Unlock() +} diff --git a/vendor/github.com/hugelgupf/p9/p9/server.go b/vendor/github.com/hugelgupf/p9/p9/server.go new file mode 100644 index 0000000000..c6e9ff365c --- /dev/null +++ b/vendor/github.com/hugelgupf/p9/p9/server.go @@ -0,0 +1,681 @@ +// Copyright 2018 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package p9 + +import ( + "context" + "errors" + "fmt" + "io" + "net" + "runtime/debug" + "strings" + "sync" + "sync/atomic" + + "github.com/hugelgupf/p9/linux" + "github.com/u-root/uio/ulog" +) + +// Server is a 9p2000.L server. +type Server struct { + // attacher provides the attach function. + attacher Attacher + + // pathTree is the full set of paths opened on this server. + // + // These may be across different connections, but rename operations + // must be serialized globally for safely. There is a single pathTree + // for the entire server, and not per connection. + pathTree *pathNode + + // renameMu is a global lock protecting rename operations. With this + // lock, we can be certain that any given rename operation can safely + // acquire two path nodes in any order, as all other concurrent + // operations acquire at most a single node. + renameMu sync.RWMutex + + // log is a logger to log to, if specified. + log ulog.Logger +} + +// ServerOpt is an optional config for a new server. +type ServerOpt func(s *Server) + +// WithServerLogger overrides the default logger for the server. +func WithServerLogger(l ulog.Logger) ServerOpt { + return func(s *Server) { + s.log = l + } +} + +// NewServer returns a new server. +func NewServer(attacher Attacher, o ...ServerOpt) *Server { + s := &Server{ + attacher: attacher, + pathTree: newPathNode(), + log: ulog.Null, + } + for _, opt := range o { + opt(s) + } + return s +} + +// connState is the state for a single connection. +type connState struct { + // server is the backing server. + server *Server + + // fids is the set of active fids. + // + // This is used to find fids for files. + fidMu sync.Mutex + fids map[fid]*fidRef + + // tags is the set of active tags. + // + // The given channel is closed when the + // tag is finished with processing. + tagMu sync.Mutex + tags map[tag]chan struct{} + + // messageSize is the maximum message size. The server does not + // do automatic splitting of messages. + messageSize uint32 + readBufPool sync.Pool + pristineZeros []byte + + // baseVersion is the version of 9P protocol. + baseVersion baseVersion + + // version is the agreed upon version X of 9P2000.L.Google.X. + // version 0 implies 9P2000.L. + version uint32 + + // pendingWg counts requests that are still being handled. + pendingWg sync.WaitGroup + + // recvMu serializes receiving from t. + recvMu sync.Mutex + + // recvIdle is the number of goroutines in handleRequests() attempting to + // lock recvMu so that they can receive from t. recvIdle is accessed + // using atomic memory operations. + recvIdle int32 + + // If recvShutdown is true, at least one goroutine has observed a + // connection error while receiving from t, and all goroutines in + // handleRequests() should exit immediately. recvShutdown is protected + // by recvMu. + recvShutdown bool + + // sendMu serializes sending to r. + sendMu sync.Mutex + + // t reads T messages and r write R messages + t io.ReadCloser + r io.WriteCloser +} + +// xattrOp is the xattr related operations, walk or create. +type xattrOp int + +const ( + xattrNone = 0 + xattrCreate = 1 + xattrWalk = 2 +) + +type pendingXattr struct { + // the pending xattr-related operation + op xattrOp + + // name is the attribute. + name string + + // size of the attribute value, represents the + // length of the attribute value that is going to write to or read from a file. + size uint64 + + // flags associated with a txattrcreate message. + // generally Linux setxattr(2) flags. + flags XattrFlags + + // saved up xattr operation value (for reads, listed / gotten buffer -- + // ready for chunking; for writes, this is used to accumulate chunked + // values until a Tclunk actuates the operation) + buf []byte +} + +// fidRef wraps a node and tracks references. +type fidRef struct { + // server is the associated server. + server *Server + + // file is the associated File. + file File + + // pendingXattr is the xattr-related operations that are going to be done + // in a tread or twrite request. + pendingXattr pendingXattr + + // refs is an active refence count. + // + // The node above will be closed only when refs reaches zero. + refs int64 + + // opened indicates whether this has been opened already. + // + // This is updated in handlers.go. + // + // opened is protected by pathNode.opMu or renameMu (for write). + opened bool + + // mode is the fidRef's mode from the walk. Only the type bits are + // valid, the permissions may change. This is used to sanity check + // operations on this element, and prevent walks across + // non-directories. + mode FileMode + + // openFlags is the mode used in the open. + // + // This is updated in handlers.go. + // + // opened is protected by pathNode.opMu or renameMu (for write). + openFlags OpenFlags + + // pathNode is the current pathNode for this fid. + pathNode *pathNode + + // parent is the parent fidRef. We hold on to a parent reference to + // ensure that hooks, such as Renamed, can be executed safely by the + // server code. + // + // Note that parent cannot be changed without holding both the global + // rename lock and a writable lock on the associated pathNode for this + // fidRef. Holding either of these locks is sufficient to examine + // parent safely. + // + // The parent will be nil for root fidRefs, and non-nil otherwise. The + // method maybeParent can be used to return a cyclical reference, and + // isRoot should be used to check for root over looking at parent + // directly. + parent *fidRef +} + +// IncRef increases the references on a fid. +func (f *fidRef) IncRef() { + atomic.AddInt64(&f.refs, 1) +} + +// DecRef should be called when you're finished with a fid. +func (f *fidRef) DecRef() error { + if atomic.AddInt64(&f.refs, -1) == 0 { + var ( + errs []error + err = f.file.Close() + ) + if err != nil { + err = fmt.Errorf("file: %w", err) + errs = append(errs, err) + } + + // Drop the parent reference. + // + // Since this fidRef is guaranteed to be non-discoverable when + // the references reach zero, we don't need to worry about + // clearing the parent. + if f.parent != nil { + // If we've been previously deleted, removing this + // ref is a no-op. That's expected. + f.parent.pathNode.removeChild(f) + if pErr := f.parent.DecRef(); pErr != nil { + pErr = fmt.Errorf("parent: %w", pErr) + errs = append(errs, pErr) + } + } + return errors.Join(errs...) + } + return nil +} + +// TryIncRef returns true if a new reference is taken on the fid, and false if +// the fid has been destroyed. +func (f *fidRef) TryIncRef() bool { + for { + r := atomic.LoadInt64(&f.refs) + if r <= 0 { + return false + } + if atomic.CompareAndSwapInt64(&f.refs, r, r+1) { + return true + } + } +} + +// isDeleted returns true if this fidRef has been deleted. +// +// Precondition: this must be called via safelyRead, safelyWrite or +// safelyGlobal. +func (f *fidRef) isDeleted() bool { + return atomic.LoadUint32(&f.pathNode.deleted) != 0 +} + +// isRoot indicates whether this is a root fid. +func (f *fidRef) isRoot() bool { + return f.parent == nil +} + +// maybeParent returns a cyclic reference for roots, and the parent otherwise. +func (f *fidRef) maybeParent() *fidRef { + if f.parent != nil { + return f.parent + } + return f // Root has itself. +} + +// notifyDelete marks all fidRefs as deleted. +// +// Precondition: this must be called via safelyWrite or safelyGlobal. +func notifyDelete(pn *pathNode) { + atomic.StoreUint32(&pn.deleted, 1) + + // Call on all subtrees. + pn.forEachChildNode(func(pn *pathNode) { + notifyDelete(pn) + }) +} + +// markChildDeleted marks all children below the given name as deleted. +// +// Precondition: this must be called via safelyWrite or safelyGlobal. +func (f *fidRef) markChildDeleted(name string) { + if origPathNode := f.pathNode.removeWithName(name, nil); origPathNode != nil { + // Mark all children as deleted. + notifyDelete(origPathNode) + } +} + +// notifyNameChange calls the relevant Renamed method on all nodes in the path, +// recursively. Note that this applies only for subtrees, as these +// notifications do not apply to the actual file whose name has changed. +// +// Precondition: this must be called via safelyGlobal. +func notifyNameChange(pn *pathNode) { + // Call on all local references. + pn.forEachChildRef(func(ref *fidRef, name string) { + ref.file.Renamed(ref.parent.file, name) + }) + + // Call on all subtrees. + pn.forEachChildNode(func(pn *pathNode) { + notifyNameChange(pn) + }) +} + +// renameChildTo renames the given child to the target. +// +// Precondition: this must be called via safelyGlobal. +func (f *fidRef) renameChildTo(oldName string, target *fidRef, newName string) { + target.markChildDeleted(newName) + origPathNode := f.pathNode.removeWithName(oldName, func(ref *fidRef) { + // N.B. DecRef can take f.pathNode's parent's childMu. This is + // allowed because renameMu is held for write via safelyGlobal. + ref.parent.DecRef() // Drop original reference. + ref.parent = target // Change parent. + ref.parent.IncRef() // Acquire new one. + if f.pathNode == target.pathNode { + target.pathNode.addChildLocked(ref, newName) + } else { + target.pathNode.addChild(ref, newName) + } + ref.file.Renamed(target.file, newName) + }) + + if origPathNode != nil { + // Replace the previous (now deleted) path node. + target.pathNode.addPathNodeFor(newName, origPathNode) + // Call Renamed on all children. + notifyNameChange(origPathNode) + } +} + +// safelyRead executes the given operation with the local path node locked. +// This implies that paths will not change during the operation. +func (f *fidRef) safelyRead(fn func() error) (err error) { + f.server.renameMu.RLock() + defer f.server.renameMu.RUnlock() + f.pathNode.opMu.RLock() + defer f.pathNode.opMu.RUnlock() + return fn() +} + +// safelyWrite executes the given operation with the local path node locked in +// a writable fashion. This implies some paths may change. +func (f *fidRef) safelyWrite(fn func() error) (err error) { + f.server.renameMu.RLock() + defer f.server.renameMu.RUnlock() + f.pathNode.opMu.Lock() + defer f.pathNode.opMu.Unlock() + return fn() +} + +// safelyGlobal executes the given operation with the global path lock held. +func (f *fidRef) safelyGlobal(fn func() error) (err error) { + f.server.renameMu.Lock() + defer f.server.renameMu.Unlock() + return fn() +} + +// Lookupfid finds the given fid. +// +// You should call fid.DecRef when you are finished using the fid. +func (cs *connState) LookupFID(fid fid) (*fidRef, bool) { + cs.fidMu.Lock() + defer cs.fidMu.Unlock() + fidRef, ok := cs.fids[fid] + if ok { + fidRef.IncRef() + return fidRef, true + } + return nil, false +} + +// Insertfid installs the given fid. +// +// This fid starts with a reference count of one. If a fid exists in +// the slot already it is closed, per the specification. +func (cs *connState) InsertFID(fid fid, newRef *fidRef) { + cs.fidMu.Lock() + defer cs.fidMu.Unlock() + origRef, ok := cs.fids[fid] + if ok { + defer origRef.DecRef() + } + newRef.IncRef() + cs.fids[fid] = newRef +} + +// Deletefid removes the given fid. +// +// This simply removes it from the map and drops a reference. +func (cs *connState) DeleteFID(fid fid) error { + cs.fidMu.Lock() + defer cs.fidMu.Unlock() + fidRef, ok := cs.fids[fid] + if !ok { + return linux.EBADF + } + delete(cs.fids, fid) + return fidRef.DecRef() +} + +// StartTag starts handling the tag. +// +// False is returned if this tag is already active. +func (cs *connState) StartTag(t tag) bool { + cs.tagMu.Lock() + defer cs.tagMu.Unlock() + _, ok := cs.tags[t] + if ok { + return false + } + cs.tags[t] = make(chan struct{}) + return true +} + +// ClearTag finishes handling a tag. +func (cs *connState) ClearTag(t tag) { + cs.tagMu.Lock() + defer cs.tagMu.Unlock() + ch, ok := cs.tags[t] + if !ok { + // Should never happen. + panic("unused tag cleared") + } + delete(cs.tags, t) + + // Notify. + close(ch) +} + +// Waittag waits for a tag to finish. +func (cs *connState) WaitTag(t tag) { + cs.tagMu.Lock() + ch, ok := cs.tags[t] + cs.tagMu.Unlock() + if !ok { + return + } + + // Wait for close. + <-ch +} + +// handleRequest handles a single request. +// +// The recvDone channel is signaled when recv is done (with a error if +// necessary). The sendDone channel is signaled with the result of the send. +func (cs *connState) handleRequest() bool { + cs.pendingWg.Add(1) + defer cs.pendingWg.Done() + + // Obtain the right to receive a message from cs.t. + atomic.AddInt32(&cs.recvIdle, 1) + cs.recvMu.Lock() + atomic.AddInt32(&cs.recvIdle, -1) + + if cs.recvShutdown { + // Another goroutine already detected a connection problem; exit + // immediately. + cs.recvMu.Unlock() + return false + } + + messageSize := atomic.LoadUint32(&cs.messageSize) + if messageSize == 0 { + // Default or not yet negotiated. + messageSize = maximumLength + } + + // Receive a message. + tag, m, err := recv(cs.server.log, cs.t, messageSize, msgDotLRegistry.get) + if errSocket, ok := err.(ConnError); ok { + if errSocket.error != io.EOF { + // Connection problem; stop serving. + cs.server.log.Printf("p9.recv: %v", errSocket.error) + } + cs.recvShutdown = true + cs.recvMu.Unlock() + return false + } + + // Ensure that another goroutine is available to receive from cs.t. + if atomic.LoadInt32(&cs.recvIdle) == 0 { + go cs.handleRequests() // S/R-SAFE: Irrelevant. + } + cs.recvMu.Unlock() + + // Deal with other errors. + if err != nil && err != io.EOF { + // If it's not a connection error, but some other protocol error, + // we can send a response immediately. + cs.sendMu.Lock() + err := send(cs.server.log, cs.r, tag, newErr(err)) + cs.sendMu.Unlock() + if err != nil { + cs.server.log.Printf("p9.send: %v", err) + } + return true + } + + // Try to start the tag. + if !cs.StartTag(tag) { + cs.server.log.Printf("no valid tag [%05d]", tag) + // Nothing we can do at this point; client is bogus. + return true + } + + // Handle the message. + r := cs.handle(m) + + // Clear the tag before sending. That's because as soon as this + // hits the wire, the client can legally send another message + // with the same tag. + cs.ClearTag(tag) + + // Send back the result. + cs.sendMu.Lock() + err = send(cs.server.log, cs.r, tag, r) + cs.sendMu.Unlock() + if err != nil { + cs.server.log.Printf("p9.send: %v", err) + } + + msgDotLRegistry.put(m) + m = nil // 'm' should not be touched after this point. + return true +} + +func (cs *connState) handle(m message) (r message) { + defer func() { + if r == nil { + // Don't allow a panic to propagate. + err := recover() + + // Include a useful log message. + cs.server.log.Printf("panic in handler - %v: %s", err, debug.Stack()) + + // Wrap in an EFAULT error; we don't really have a + // better way to describe this kind of error. It will + // usually manifest as a result of the test framework. + r = newErr(linux.EFAULT) + } + }() + + if handler, ok := m.(handler); ok { + // Call the message handler. + r = handler.handle(cs) + } else { + // Produce an ENOSYS error. + r = newErr(linux.ENOSYS) + } + return +} + +func (cs *connState) handleRequests() { + for { + if !cs.handleRequest() { + return + } + } +} + +func (cs *connState) stop() { + // Wait for completion of all inflight request goroutines.. If a + // request is stuck, something has the opportunity to kill us with + // SIGABRT to get a stack dump of the offending handler. + cs.pendingWg.Wait() + + // Ensure the connection is closed. + cs.r.Close() + cs.t.Close() + + for _, fidRef := range cs.fids { + // Drop final reference in the fid table. Note this should + // always close the file, since we've ensured that there are no + // handlers running via the wait for Pending => 0 below. + fidRef.DecRef() + } +} + +// Handle handles a single connection. +func (s *Server) Handle(t io.ReadCloser, r io.WriteCloser) error { + cs := &connState{ + server: s, + t: t, + r: r, + fids: make(map[fid]*fidRef), + tags: make(map[tag]chan struct{}), + } + defer cs.stop() + + // Serve requests from t in the current goroutine; handleRequests() + // will create more goroutines as needed. + cs.handleRequests() + return nil +} + +func isErrClosing(err error) bool { + return strings.Contains(err.Error(), "use of closed network connection") +} + +// Serve handles requests from the bound socket. +// +// The passed serverSocket _must_ be created in packet mode. +func (s *Server) Serve(serverSocket net.Listener) error { + return s.ServeContext(nil, serverSocket) +} + +var errAlreadyClosed = errors.New("already closed") + +// ServeContext handles requests from the bound socket. +// +// The passed serverSocket _must_ be created in packet mode. +// +// When the context is done, the listener is closed and serve returns once +// every request has been handled. +func (s *Server) ServeContext(ctx context.Context, serverSocket net.Listener) error { + var wg sync.WaitGroup + defer wg.Wait() + + var cancelCause context.CancelCauseFunc + if ctx != nil { + ctx, cancelCause = context.WithCancelCause(ctx) + + wg.Add(1) + go func() { + defer wg.Done() + <-ctx.Done() + + // Only close the server socket if it wasn't already closed. + if err := ctx.Err(); errors.Is(err, errAlreadyClosed) { + return + } + serverSocket.Close() + }() + } + + for { + conn, err := serverSocket.Accept() + if err != nil { + if cancelCause != nil { + cancelCause(errAlreadyClosed) + } + if isErrClosing(err) { + return nil + } + // Something went wrong. + return err + } + + wg.Add(1) + go func(conn net.Conn) { // S/R-SAFE: Irrelevant. + s.Handle(conn, conn) + wg.Done() + }(conn) + } +} diff --git a/vendor/github.com/hugelgupf/p9/p9/transport.go b/vendor/github.com/hugelgupf/p9/p9/transport.go new file mode 100644 index 0000000000..e0b6947d28 --- /dev/null +++ b/vendor/github.com/hugelgupf/p9/p9/transport.go @@ -0,0 +1,245 @@ +// Copyright 2018 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package p9 + +import ( + "errors" + "fmt" + "io" + "io/ioutil" + "net" + "sync" + + "github.com/hugelgupf/p9/vecnet" + "github.com/u-root/uio/ulog" +) + +// ConnError is returned in cases of a connection issue. +// +// This may be treated differently than other errors. +type ConnError struct { + // error is the socket error. + error +} + +func (e ConnError) Error() string { + return fmt.Sprintf("socket error: %v", e.error) +} + +// Is reports whether any error in err's chain matches target. +func (e ConnError) Is(target error) bool { return target == e.error } + +// ErrMessageTooLarge indicates the size was larger than reasonable. +type ErrMessageTooLarge struct { + size uint32 + msize uint32 +} + +// Error returns a sensible error. +func (e *ErrMessageTooLarge) Error() string { + return fmt.Sprintf("message too large for fixed buffer: size is %d, limit is %d", e.size, e.msize) +} + +// ErrNoValidMessage indicates no valid message could be decoded. +var ErrNoValidMessage = errors.New("buffer contained no valid message") + +const ( + // headerLength is the number of bytes required for a header. + headerLength uint32 = 7 + + // maximumLength is the largest possible message. + maximumLength uint32 = 4 * 1024 * 1024 + + // initialBufferLength is the initial data buffer we allocate. + initialBufferLength uint32 = 64 +) + +var dataPool = sync.Pool{ + New: func() interface{} { + // These buffers are used for decoding without a payload. + // We need to return a pointer to avoid unnecessary allocations + // (see https://staticcheck.io/docs/checks#SA6002). + b := make([]byte, initialBufferLength) + return &b + }, +} + +// send sends the given message over the socket. +func send(l ulog.Logger, w io.Writer, tag tag, m message) error { + data := dataPool.Get().(*[]byte) + dataBuf := buffer{data: (*data)[:0]} + + // Encode the message. The buffer will grow automatically. + m.encode(&dataBuf) + + l.Printf("send [w %p] [Tag %06d] %s", w, tag, m) + + // Get our vectors to send. + var hdr [headerLength]byte + vecs := make(net.Buffers, 0, 3) + vecs = append(vecs, hdr[:]) + if len(dataBuf.data) > 0 { + vecs = append(vecs, dataBuf.data) + } + totalLength := headerLength + uint32(len(dataBuf.data)) + + // Is there a payload? + if payloader, ok := m.(payloader); ok { + p := payloader.Payload() + if len(p) > 0 { + vecs = append(vecs, p) + totalLength += uint32(len(p)) + } + defer payloader.PayloadCleanup() + } + + // Construct the header. + headerBuf := buffer{data: hdr[:0]} + headerBuf.Write32(totalLength) + headerBuf.WriteMsgType(m.typ()) + headerBuf.WriteTag(tag) + + if _, err := vecs.WriteTo(w); err != nil { + return ConnError{err} + } + + // All set. + dataPool.Put(&dataBuf.data) + return nil +} + +// lookupTagAndType looks up an existing message or creates a new one. +// +// This is called by recv after decoding the header. Any error returned will be +// propagating back to the caller. You may use messageByType directly as a +// lookupTagAndType function (by design). +type lookupTagAndType func(tag tag, t msgType) (message, error) + +// recv decodes a message from the socket. +// +// This is done in two parts, and is thus not safe for multiple callers. +// +// On a socket error, the special error type ErrSocket is returned. +// +// The tag value NoTag will always be returned if err is non-nil. +func recv(l ulog.Logger, r io.Reader, msize uint32, lookup lookupTagAndType) (tag, message, error) { + // Read a header. + var hdr [headerLength]byte + + if _, err := io.ReadAtLeast(r, hdr[:], int(headerLength)); err != nil { + return noTag, nil, ConnError{err} + } + + // Decode the header. + headerBuf := buffer{data: hdr[:]} + size := headerBuf.Read32() + t := headerBuf.ReadMsgType() + tag := headerBuf.ReadTag() + if size < headerLength { + // The message is too small. + // + // See above: it's probably screwed. + return noTag, nil, ConnError{ErrNoValidMessage} + } + if size > maximumLength || size > msize { + // The message is too big. + return noTag, nil, ConnError{&ErrMessageTooLarge{size, msize}} + } + remaining := size - headerLength + + // Find our message to decode. + m, err := lookup(tag, t) + if err != nil { + // Throw away the contents of this message. + if remaining > 0 { + _, _ = io.Copy(ioutil.Discard, io.LimitReader(r, int64(remaining))) + } + return tag, nil, err + } + + // Not yet initialized. + var dataBuf buffer + var vecs vecnet.Buffers + + appendBuffer := func(size int) *[]byte { + // Pull a data buffer from the pool. + datap := dataPool.Get().(*[]byte) + data := *datap + if size > len(data) { + // Create a larger data buffer. + data = make([]byte, size) + datap = &data + } else { + // Limit the data buffer. + data = data[:size] + } + dataBuf = buffer{data: data} + vecs = append(vecs, data) + return datap + } + + // Read the rest of the payload. + // + // This requires some special care to ensure that the vectors all line + // up the way they should. We do this to minimize copying data around. + if payloader, ok := m.(payloader); ok { + fixedSize := payloader.FixedSize() + + // Do we need more than there is? + if fixedSize > remaining { + // This is not a valid message. + if remaining > 0 { + _, _ = io.Copy(ioutil.Discard, io.LimitReader(r, int64(remaining))) + } + return noTag, nil, ErrNoValidMessage + } + + if fixedSize != 0 { + datap := appendBuffer(int(fixedSize)) + defer dataPool.Put(datap) + } + + // Include the payload. + p := payloader.Payload() + if p == nil || len(p) != int(remaining-fixedSize) { + p = make([]byte, remaining-fixedSize) + payloader.SetPayload(p) + } + if len(p) > 0 { + vecs = append(vecs, p) + } + } else if remaining != 0 { + datap := appendBuffer(int(remaining)) + defer dataPool.Put(datap) + } + + if len(vecs) > 0 { + if _, err := vecs.ReadFrom(r); err != nil { + return noTag, nil, ConnError{err} + } + } + + // Decode the message data. + m.decode(&dataBuf) + if dataBuf.isOverrun() { + // No need to drain the socket. + return noTag, nil, ErrNoValidMessage + } + + l.Printf("recv [r %p] [Tag %06d] %s", r, tag, m) + + // All set. + return tag, m, nil +} diff --git a/vendor/github.com/hugelgupf/p9/p9/version.go b/vendor/github.com/hugelgupf/p9/p9/version.go new file mode 100644 index 0000000000..236e1a3196 --- /dev/null +++ b/vendor/github.com/hugelgupf/p9/p9/version.go @@ -0,0 +1,134 @@ +// Copyright 2018 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package p9 + +import ( + "fmt" + "strconv" + "strings" +) + +const ( + // highestSupportedVersion is the highest supported version X in a + // version string of the format 9P2000.L.Google.X. + // + // Clients are expected to start requesting this version number and + // to continuously decrement it until a Tversion request succeeds. + highestSupportedVersion uint32 = 7 + + // lowestSupportedVersion is the lowest supported version X in a + // version string of the format 9P2000.L.Google.X. + // + // Clients are free to send a Tversion request at a version below this + // value but are expected to encounter an Rlerror in response. + lowestSupportedVersion uint32 = 0 +) + +type baseVersion string + +const ( + undetermined baseVersion = "" + version9P2000 baseVersion = "9P2000" + version9P2000U baseVersion = "9P2000.u" + version9P2000L baseVersion = "9P2000.L" +) + +// HighestVersionString returns the highest possible version string that a client +// may request or a server may support. +func HighestVersionString() string { + return versionString(version9P2000L, highestSupportedVersion) +} + +// parseVersion parses a Tversion version string into a numeric version number +// if the version string is supported by p9. Otherwise returns (0, false). +// +// From Tversion(9P): "Version strings are defined such that, if the client string +// contains one or more period characters, the initial substring up to but not +// including any single period in the version string defines a version of the protocol." +// +// p9 intentionally diverges from this and always requires that the version string +// start with 9P2000.L to express that it is always compatible with 9P2000.L. The +// only supported versions extensions are of the format 9p2000.L.Google.X where X +// is an ever increasing version counter. +// +// Version 9P2000.L.Google.0 implies 9P2000.L. +// +// New versions must always be a strict superset of 9P2000.L. A version increase must +// define a predicate representing the feature extension introduced by that version. The +// predicate must be commented and should take the format: +// +// // VersionSupportsX returns true if version v supports X and must be checked when ... +// +// func VersionSupportsX(v int32) bool { +// ... +// +// ) +func parseVersion(str string) (baseVersion, uint32, bool) { + switch str { + case "9P2000.L": + return version9P2000L, 0, true + case "9P2000.u": + return version9P2000U, 0, true + case "9P2000": + return version9P2000, 0, true + default: + substr := strings.Split(str, ".") + if len(substr) != 4 { + return "", 0, false + } + if substr[0] != "9P2000" || substr[1] != "L" || substr[2] != "Google" || len(substr[3]) == 0 { + return "", 0, false + } + version, err := strconv.ParseUint(substr[3], 10, 32) + if err != nil { + return "", 0, false + } + return version9P2000L, uint32(version), true + } +} + +// versionString formats a p9 version number into a Tversion version string. +func versionString(baseVersion baseVersion, version uint32) string { + // Special case the base version so that clients expecting this string + // instead of the 9P2000.L.Google.0 equivalent get it. This is important + // for backwards compatibility with legacy servers that check for exactly + // the baseVersion and allow nothing else. + if version == 0 { + return string(baseVersion) + } + return fmt.Sprintf("9P2000.L.Google.%d", version) +} + +// versionSupportsTwalkgetattr returns true if version v supports the +// Twalkgetattr message. This predicate must be checked by clients before +// attempting to make a Twalkgetattr request. +func versionSupportsTwalkgetattr(v uint32) bool { + return v >= 2 +} + +// versionSupportsTucreation returns true if version v supports the Tucreation +// messages (Tucreate, Tusymlink, Tumkdir, Tumknod). This predicate must be +// checked by clients before attempting to make a Tucreation request. +// If Tucreation messages are not supported, their non-UID supporting +// counterparts (Tlcreate, Tsymlink, Tmkdir, Tmknod) should be used. +func versionSupportsTucreation(v uint32) bool { + return v >= 3 +} + +// VersionSupportsMultiUser returns true if version v supports multi-user fake +// directory permissions and ID values. +func VersionSupportsMultiUser(v uint32) bool { + return v >= 6 +} diff --git a/vendor/github.com/hugelgupf/p9/vecnet/iov32_linux.go b/vendor/github.com/hugelgupf/p9/vecnet/iov32_linux.go new file mode 100644 index 0000000000..55a988f8c6 --- /dev/null +++ b/vendor/github.com/hugelgupf/p9/vecnet/iov32_linux.go @@ -0,0 +1,22 @@ +// Copyright 2018 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build 386 || mips || arm || mipsle +// +build 386 mips arm mipsle + +package vecnet + +func iovlen(i int) uint32 { + return uint32(i) +} diff --git a/vendor/github.com/hugelgupf/p9/vecnet/iov_linux.go b/vendor/github.com/hugelgupf/p9/vecnet/iov_linux.go new file mode 100644 index 0000000000..5e57961122 --- /dev/null +++ b/vendor/github.com/hugelgupf/p9/vecnet/iov_linux.go @@ -0,0 +1,22 @@ +// Copyright 2018 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build !386 && !arm && !mips && !mipsle +// +build !386,!arm,!mips,!mipsle + +package vecnet + +func iovlen(i int) uint64 { + return uint64(i) +} diff --git a/vendor/github.com/hugelgupf/p9/vecnet/vecnet.go b/vendor/github.com/hugelgupf/p9/vecnet/vecnet.go new file mode 100644 index 0000000000..042078d54e --- /dev/null +++ b/vendor/github.com/hugelgupf/p9/vecnet/vecnet.go @@ -0,0 +1,54 @@ +// Copyright 2018 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package vecnet provides access to recvmsg syscalls on net.Conns. +package vecnet + +import ( + "io" + "net" + "syscall" +) + +// Buffers points to zero or more buffers to read into. +// +// On connections that support it, ReadFrom is optimized into the batch read +// operation recvmsg. +type Buffers net.Buffers + +// ReadFrom reads into the pre-allocated bufs. Returns bytes read. +// +// ReadFrom keeps reading until all bufs are filled or EOF is received. +// +// The pre-allocatted space used by ReadFrom is based upon slice lengths. +func (bufs Buffers) ReadFrom(r io.Reader) (int64, error) { + if conn, ok := r.(syscall.Conn); ok && readFromBuffers != nil { + return readFromBuffers(bufs, conn) + } + + var total int64 + for _, buf := range bufs { + for filled := 0; filled < len(buf); { + n, err := r.Read(buf[filled:]) + total += int64(n) + filled += n + if (n == 0 && err == nil) || err == io.EOF { + return total, io.EOF + } else if err != nil { + return total, err + } + } + } + return total, nil +} diff --git a/vendor/github.com/hugelgupf/p9/vecnet/vecnet_linux.go b/vendor/github.com/hugelgupf/p9/vecnet/vecnet_linux.go new file mode 100644 index 0000000000..0c351f3fc0 --- /dev/null +++ b/vendor/github.com/hugelgupf/p9/vecnet/vecnet_linux.go @@ -0,0 +1,112 @@ +// Copyright 2018 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build !386 +// +build !386 + +package vecnet + +import ( + "io" + "runtime" + "syscall" + "unsafe" +) + +var readFromBuffers = readFromBuffersLinux + +func readFromBuffersLinux(bufs Buffers, conn syscall.Conn) (int64, error) { + rc, err := conn.SyscallConn() + if err != nil { + return 0, err + } + + length := int64(0) + for _, buf := range bufs { + length += int64(len(buf)) + } + + for n := int64(0); n < length; { + cur, err := recvmsg(bufs, rc) + if err != nil && (cur == 0 || err != io.EOF) { + return n, err + } + n += int64(cur) + + // Consume iovecs to retry. + for consumed := 0; consumed < cur; { + if len(bufs[0]) <= cur-consumed { + consumed += len(bufs[0]) + bufs = bufs[1:] + } else { + bufs[0] = bufs[0][cur-consumed:] + break + } + } + } + return length, nil +} + +// buildIovec builds an iovec slice from the given []byte slice. +// +// iovecs is used as an initial slice, to avoid excessive allocations. +func buildIovec(bufs Buffers, iovecs []syscall.Iovec) ([]syscall.Iovec, int) { + var length int + for _, buf := range bufs { + if l := len(buf); l > 0 { + iovecs = append(iovecs, syscall.Iovec{ + Base: &buf[0], + Len: iovlen(l), + }) + length += l + } + } + return iovecs, length +} + +func recvmsg(bufs Buffers, rc syscall.RawConn) (int, error) { + iovecs, length := buildIovec(bufs, make([]syscall.Iovec, 0, 2)) + + var msg syscall.Msghdr + if len(iovecs) != 0 { + msg.Iov = &iovecs[0] + msg.Iovlen = iovlen(len(iovecs)) + } + + // n is the bytes received. + var n uintptr + var e syscall.Errno + err := rc.Read(func(fd uintptr) bool { + n, _, e = syscall.Syscall(syscall.SYS_RECVMSG, fd, uintptr(unsafe.Pointer(&msg)), syscall.MSG_DONTWAIT) + // Return false if EINTR, EAGAIN, or EWOULDBLOCK to retry. + return !(e == syscall.EINTR || e == syscall.EAGAIN || e == syscall.EWOULDBLOCK) + }) + runtime.KeepAlive(iovecs) + if err != nil { + return 0, err + } + if e != 0 { + return 0, e + } + + // The other end is closed by returning a 0 length read with no error. + if n == 0 { + return 0, io.EOF + } + + if int(n) > length { + return length, io.ErrShortBuffer + } + return int(n), nil +} diff --git a/vendor/github.com/hugelgupf/p9/vecnet/vecnet_other.go b/vendor/github.com/hugelgupf/p9/vecnet/vecnet_other.go new file mode 100644 index 0000000000..c596450c6f --- /dev/null +++ b/vendor/github.com/hugelgupf/p9/vecnet/vecnet_other.go @@ -0,0 +1,24 @@ +// Copyright 2018 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build !linux || (linux && 386) +// +build !linux linux,386 + +package vecnet + +import ( + "syscall" +) + +var readFromBuffers func(bufs Buffers, conn syscall.Conn) (int64, error) diff --git a/vendor/github.com/linuxkit/virtsock/AUTHORS b/vendor/github.com/linuxkit/virtsock/AUTHORS new file mode 100644 index 0000000000..8cf1ee7e95 --- /dev/null +++ b/vendor/github.com/linuxkit/virtsock/AUTHORS @@ -0,0 +1,11 @@ +# This file lists all individuals having contributed content to the repository. +# For how it is generated, see `scripts/generate-authors.sh`. + +Ben Weedon +Ian Campbell +John Starks +Justin Cormack +Magnus Skjegstad +Riyaz Faizullabhoy +Rolf Neugebauer +Simon Ferquel diff --git a/vendor/github.com/linuxkit/virtsock/LICENSE b/vendor/github.com/linuxkit/virtsock/LICENSE new file mode 100644 index 0000000000..57e2b431d5 --- /dev/null +++ b/vendor/github.com/linuxkit/virtsock/LICENSE @@ -0,0 +1,16 @@ +Unless explicitly stated at the top of the file, this code is covered +by the following license: + +Copyright 2016-2017 The authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/vendor/github.com/linuxkit/virtsock/pkg/hvsock/hvsock.go b/vendor/github.com/linuxkit/virtsock/pkg/hvsock/hvsock.go new file mode 100644 index 0000000000..f54ddda5f5 --- /dev/null +++ b/vendor/github.com/linuxkit/virtsock/pkg/hvsock/hvsock.go @@ -0,0 +1,115 @@ +// Package hvsock provides a Go interface to Hyper-V sockets both on +// Windows and on Linux. The Linux bindings require patches for the +// 4.9.x kernel. If you are using a Linux kernel 4.14.x or newer you +// should use the vsock package instead as the Hyper-V socket support +// in these kernels have been merged with the virtio sockets +// implementation. +package hvsock + +import ( + "encoding/binary" + "fmt" + "net" + "reflect" +) + +var ( + // GUIDZero used by listeners to accept connections from all partitions + GUIDZero, _ = GUIDFromString("00000000-0000-0000-0000-000000000000") + // GUIDWildcard used by listeners to accept connections from all partitions + GUIDWildcard, _ = GUIDFromString("00000000-0000-0000-0000-000000000000") + // GUIDBroadcast undocumented + GUIDBroadcast, _ = GUIDFromString("FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF") + // GUIDChildren used by listeners to accept connections from children + GUIDChildren, _ = GUIDFromString("90db8b89-0d35-4f79-8ce9-49ea0ac8b7cd") + // GUIDLoopback use to connect in loopback mode + GUIDLoopback, _ = GUIDFromString("e0e16197-dd56-4a10-9195-5ee7a155a838") + // GUIDParent use to connect to the parent partition + GUIDParent, _ = GUIDFromString("a42e7cda-d03f-480c-9cc2-a4de20abb878") + + // GUIDs for LinuxVMs with the new Hyper-V socket implementation need to match this template + guidTemplate, _ = GUIDFromString("00000000-facb-11e6-bd58-64006a7986d3") +) + +const ( + // The Hyper-V socket implementation used in the 4.9.x kernels + // seems to fail silently if messages are above 8k. The newer + // implementation in the 4.14.x (and newer) kernels seems to + // work fine with larger messages. This is constant is used as + // a temporary workaround to limit the amount of data sent and + // should be removed once support for 4.9.x kernels is + // deprecated. + maxMsgSize = 8 * 1024 +) + +// GUID is used by Hypper-V sockets for "addresses" and "ports" +type GUID [16]byte + +// Convert a GUID into a string +func (g *GUID) String() string { + /* XXX This assume little endian */ + return fmt.Sprintf("%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", + g[3], g[2], g[1], g[0], + g[5], g[4], + g[7], g[6], + g[8], g[9], + g[10], g[11], g[12], g[13], g[14], g[15]) +} + +// Port converts a Service GUID to a "port" usable by the vsock package. +// It can be used to convert hvsock code to vsock code. On 4.14.x +// kernels Service GUIDs for talking to Linux should have the form of +// xxxxxxxx-facb-11e6-bd58-64006a7986d3, where xxxxxxxx is the vsock port. +func (g *GUID) Port() (uint32, error) { + // Check that the GUID is as expected + if !reflect.DeepEqual(g[4:], guidTemplate[4:]) { + return 0, fmt.Errorf("%s does not conform with the template", g) + } + return binary.LittleEndian.Uint32(g[0:4]), nil +} + +// GUIDFromString parses a string and returns a GUID +func GUIDFromString(s string) (GUID, error) { + var g GUID + var err error + _, err = fmt.Sscanf(s, "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", + &g[3], &g[2], &g[1], &g[0], + &g[5], &g[4], + &g[7], &g[6], + &g[8], &g[9], + &g[10], &g[11], &g[12], &g[13], &g[14], &g[15]) + return g, err +} + +// Addr represents a Hyper-V socket address +type Addr struct { + VMID GUID + ServiceID GUID +} + +// Network returns the type of network for Hyper-V sockets +func (a Addr) Network() string { + return "hvsock" +} + +func (a Addr) String() string { + vmid := a.VMID.String() + svc := a.ServiceID.String() + + return vmid + ":" + svc +} + +// Conn is a hvsock connection which supports half-close. +type Conn interface { + net.Conn + CloseRead() error + CloseWrite() error +} + +// Since there doesn't seem to be a standard min function +func min(x, y int) int { + if x < y { + return x + } + return y +} diff --git a/vendor/github.com/linuxkit/virtsock/pkg/hvsock/hvsock_fallback.go b/vendor/github.com/linuxkit/virtsock/pkg/hvsock/hvsock_fallback.go new file mode 100644 index 0000000000..050419bd36 --- /dev/null +++ b/vendor/github.com/linuxkit/virtsock/pkg/hvsock/hvsock_fallback.go @@ -0,0 +1,22 @@ +// +build !linux,!windows + +package hvsock + +import ( + "fmt" + "net" + "runtime" +) + +// Supported returns if hvsocks are supported on your platform +func Supported() bool { + return false +} + +func Dial(raddr Addr) (Conn, error) { + return nil, fmt.Errorf("Dial() not implemented on %s", runtime.GOOS) +} + +func Listen(addr Addr) (net.Listener, error) { + return nil, fmt.Errorf("Listen() not implemented on %s", runtime.GOOS) +} diff --git a/vendor/github.com/linuxkit/virtsock/pkg/hvsock/hvsock_linux.go b/vendor/github.com/linuxkit/virtsock/pkg/hvsock/hvsock_linux.go new file mode 100644 index 0000000000..569e66d601 --- /dev/null +++ b/vendor/github.com/linuxkit/virtsock/pkg/hvsock/hvsock_linux.go @@ -0,0 +1,277 @@ +package hvsock + +// On Linux we have to deal with two different implementations. The +// "legacy" implementation never made it into the kernel, but several +// kernels, including the LinuxKit one carried patches for it for +// quite a while. The legacy version defined a new address family +// while the new version sits on top of the existing VMware/virtio +// socket implementation. +// +// We try to determine at init if we are on a kernel with the legacy +// implementation or the new version and set "legacyMode" accordingly. +// +// We can't just reuse the vsock implementation as we still need to +// emulated CloseRead()/CloseWrite() as not all Windows builds support +// it. + +/* +#include + +struct sockaddr_hv { + unsigned short shv_family; + unsigned short reserved; + unsigned char shv_vm_id[16]; + unsigned char shv_service_id[16]; +}; +int bind_sockaddr_hv(int fd, const struct sockaddr_hv *sa_hv) { + return bind(fd, (const struct sockaddr*)sa_hv, sizeof(*sa_hv)); +} +int connect_sockaddr_hv(int fd, const struct sockaddr_hv *sa_hv) { + return connect(fd, (const struct sockaddr*)sa_hv, sizeof(*sa_hv)); +} +int accept_hv(int fd, struct sockaddr_hv *sa_hv, socklen_t *sa_hv_len) { + return accept(fd, (struct sockaddr *)sa_hv, sa_hv_len); +} +int getsockname_hv(int fd, struct sockaddr_hv *sa_hv, socklen_t *sa_hv_len) { + return getsockname(fd, (struct sockaddr *)sa_hv, sa_hv_len); +} +*/ +import "C" + +import ( + "fmt" + "net" + "os" + "syscall" + "time" + + "github.com/pkg/errors" + "golang.org/x/sys/unix" +) + +const ( + hvsockAF = 43 //SHV_PROTO_RAW + hvsockRaw = 1 // SHV_PROTO_RAW +) + +// Supported returns if hvsocks are supported on your platform +func Supported() bool { + var sa C.struct_sockaddr_hv + var sa_len C.socklen_t + + // Try opening a hvsockAF socket. If it works we are on older, i.e. 4.9.x kernels. + // 4.11 defines AF_SMC as 43 but it doesn't support protocol 1 so the + // socket() call should fail. + fd, err := syscall.Socket(hvsockAF, syscall.SOCK_STREAM, hvsockRaw) + if err != nil { + return false + } + + // 4.16 defines SMCPROTO_SMC6 as 1 but its socket name size doesn't match + // size of sockaddr_hv so corresponding check should fail. + sa_len = C.sizeof_struct_sockaddr_hv + ret, _ := C.getsockname_hv(C.int(fd), &sa, &sa_len) + syscall.Close(fd) + if ret < 0 || sa_len != C.sizeof_struct_sockaddr_hv { + return false + } + + return true +} + +// Dial a Hyper-V socket address +func Dial(raddr Addr) (Conn, error) { + fd, err := syscall.Socket(hvsockAF, syscall.SOCK_STREAM, hvsockRaw) + if err != nil { + return nil, err + } + + sa := C.struct_sockaddr_hv{} + sa.shv_family = hvsockAF + sa.reserved = 0 + + for i := 0; i < 16; i++ { + sa.shv_vm_id[i] = C.uchar(raddr.VMID[i]) + } + for i := 0; i < 16; i++ { + sa.shv_service_id[i] = C.uchar(raddr.ServiceID[i]) + } + + // Retry connect in a loop if EINTR is encountered. + for { + if ret, errno := C.connect_sockaddr_hv(C.int(fd), &sa); ret != 0 { + if errno == syscall.EINTR { + continue + } + return nil, fmt.Errorf("connect(%s) failed with %d, errno=%d", raddr, ret, errno) + } + break + } + + return newHVsockConn(uintptr(fd), &Addr{VMID: GUIDZero, ServiceID: GUIDZero}, &raddr), nil +} + +// Listen returns a net.Listener which can accept connections on the given port +func Listen(addr Addr) (net.Listener, error) { + fd, err := syscall.Socket(hvsockAF, syscall.SOCK_STREAM, hvsockRaw) + if err != nil { + return nil, err + } + + sa := C.struct_sockaddr_hv{} + sa.shv_family = hvsockAF + sa.reserved = 0 + + for i := 0; i < 16; i++ { + sa.shv_vm_id[i] = C.uchar(addr.VMID[i]) + } + for i := 0; i < 16; i++ { + sa.shv_service_id[i] = C.uchar(addr.ServiceID[i]) + } + + if ret, errno := C.bind_sockaddr_hv(C.int(fd), &sa); ret != 0 { + return nil, fmt.Errorf("listen(%s) failed with %d, errno=%d", addr, ret, errno) + } + + err = syscall.Listen(fd, syscall.SOMAXCONN) + if err != nil { + return nil, errors.Wrapf(err, "listen(%s) failed", addr) + } + return &hvsockListener{fd, addr}, nil +} + +// +// Hyper-v sockets Listener implementation +// + +type hvsockListener struct { + fd int + local Addr +} + +// Accept accepts an incoming call and returns the new connection. +func (v *hvsockListener) Accept() (net.Conn, error) { + var acceptSA C.struct_sockaddr_hv + var acceptSALen C.socklen_t + + acceptSALen = C.sizeof_struct_sockaddr_hv + fd, err := C.accept_hv(C.int(v.fd), &acceptSA, &acceptSALen) + if err != nil { + return nil, errors.Wrapf(err, "accept(%s) failed", v.local) + } + + remote := &Addr{VMID: guidFromC(acceptSA.shv_vm_id), ServiceID: guidFromC(acceptSA.shv_service_id)} + return newHVsockConn(uintptr(fd), &v.local, remote), nil +} + +// Close closes the listening connection +func (v *hvsockListener) Close() error { + // Note this won't cause the Accept to unblock. + return unix.Close(v.fd) +} + +// Addr returns the address the Listener is listening on +func (v *hvsockListener) Addr() net.Addr { + return v.local +} + +// +// Hyper-V socket connection implementation +// + +// hvsockConn represents a connection over a Hyper-V socket +type hvsockConn struct { + hvsock *os.File + fd uintptr + local *Addr + remote *Addr +} + +func newHVsockConn(fd uintptr, local, remote *Addr) *hvsockConn { + hvsock := os.NewFile(fd, fmt.Sprintf("hvsock:%d", fd)) + return &hvsockConn{hvsock: hvsock, fd: fd, local: local, remote: remote} +} + +// LocalAddr returns the local address of a connection +func (v *hvsockConn) LocalAddr() net.Addr { + return v.local +} + +// RemoteAddr returns the remote address of a connection +func (v *hvsockConn) RemoteAddr() net.Addr { + return v.remote +} + +// Close closes the connection +func (v *hvsockConn) Close() error { + return v.hvsock.Close() +} + +// CloseRead shuts down the reading side of a hvsock connection +func (v *hvsockConn) CloseRead() error { + return syscall.Shutdown(int(v.fd), syscall.SHUT_RD) +} + +// CloseWrite shuts down the writing side of a hvsock connection +func (v *hvsockConn) CloseWrite() error { + return syscall.Shutdown(int(v.fd), syscall.SHUT_WR) +} + +// Read reads data from the connection +func (v *hvsockConn) Read(buf []byte) (int, error) { + return v.hvsock.Read(buf) +} + +// Write writes data over the connection +// TODO(rn): replace with a straight call to v.hvsock.Write() once 4.9.x support is deprecated +func (v *hvsockConn) Write(buf []byte) (int, error) { + written := 0 + toWrite := len(buf) + for toWrite > 0 { + thisBatch := min(toWrite, maxMsgSize) + n, err := v.hvsock.Write(buf[written : written+thisBatch]) + if err != nil { + return written, err + } + if n != thisBatch { + return written, fmt.Errorf("short write %d != %d", n, thisBatch) + } + toWrite -= n + written += n + } + + return written, nil +} + +// SetDeadline sets the read and write deadlines associated with the connection +func (v *hvsockConn) SetDeadline(t time.Time) error { + return nil // FIXME +} + +// SetReadDeadline sets the deadline for future Read calls. +func (v *hvsockConn) SetReadDeadline(t time.Time) error { + return nil // FIXME +} + +// SetWriteDeadline sets the deadline for future Write calls +func (v *hvsockConn) SetWriteDeadline(t time.Time) error { + return nil // FIXME +} + +// File duplicates the underlying socket descriptor and returns it. +func (v *hvsockConn) File() (*os.File, error) { + // This is equivalent to dup(2) but creates the new fd with CLOEXEC already set. + r0, _, e1 := syscall.Syscall(syscall.SYS_FCNTL, uintptr(v.hvsock.Fd()), syscall.F_DUPFD_CLOEXEC, 0) + if e1 != 0 { + return nil, os.NewSyscallError("fcntl", e1) + } + return os.NewFile(r0, v.hvsock.Name()), nil +} + +func guidFromC(cg [16]C.uchar) GUID { + var g GUID + for i := 0; i < 16; i++ { + g[i] = byte(cg[i]) + } + return g +} diff --git a/vendor/github.com/linuxkit/virtsock/pkg/hvsock/hvsock_windows.go b/vendor/github.com/linuxkit/virtsock/pkg/hvsock/hvsock_windows.go new file mode 100644 index 0000000000..f21e9a3151 --- /dev/null +++ b/vendor/github.com/linuxkit/virtsock/pkg/hvsock/hvsock_windows.go @@ -0,0 +1,496 @@ +package hvsock + +import ( + "fmt" + "io" + "log" + "net" + "runtime" + "sync" + "sync/atomic" + "syscall" + "time" + "unsafe" + + "github.com/pkg/errors" +) + +// Make sure Winsock2 is initialised +func init() { + e := syscall.WSAStartup(uint32(0x202), &wsaData) + if e != nil { + log.Fatal("WSAStartup", e) + } +} + +const ( + hvsockAF = 34 // AF_HYPERV + hvsockRaw = 1 // SHV_PROTO_RAW +) + +var ( + // ErrTimeout is an error returned on timeout + ErrTimeout = &timeoutError{} + + wsaData syscall.WSAData +) + +// Supported returns if hvsocks are supported on your platform +func Supported() bool { + return true +} + +// Dial a Hyper-V socket address +func Dial(raddr Addr) (Conn, error) { + fd, err := syscall.Socket(hvsockAF, syscall.SOCK_STREAM, hvsockRaw) + if err != nil { + return nil, err + } + + var sa rawSockaddrHyperv + ptr, n, err := raddr.sockaddr(&sa) + if err != nil { + return nil, err + } + + if err := sys_connect(fd, ptr, n); err != nil { + return nil, errors.Wrapf(err, "connect(%s) failed", raddr) + } + + return newHVsockConn(fd, Addr{VMID: GUIDZero, ServiceID: GUIDZero}, raddr) +} + +// Listen returns a net.Listener which can accept connections on the given port +func Listen(addr Addr) (net.Listener, error) { + fd, err := syscall.Socket(hvsockAF, syscall.SOCK_STREAM, hvsockRaw) + if err != nil { + return nil, err + } + + var sa rawSockaddrHyperv + ptr, n, err := addr.sockaddr(&sa) + if err != nil { + return nil, err + } + if err := sys_bind(fd, ptr, n); err != nil { + return nil, fmt.Errorf("bind(%s) failed with %v", addr, err) + } + + err = syscall.Listen(fd, syscall.SOMAXCONN) + if err != nil { + return nil, errors.Wrapf(err, "listen(%s) failed", addr) + } + + return &hvsockListener{fd, addr}, nil +} + +// +// Hyper-v sockets Listener implementation +// + +type hvsockListener struct { + fd syscall.Handle + local Addr +} + +// Accept accepts an incoming call and returns the new connection +func (v *hvsockListener) Accept() (net.Conn, error) { + var sa rawSockaddrHyperv + var n = int32(unsafe.Sizeof(sa)) + fd, err := sys_accept(v.fd, &sa, &n) + if err != nil { + return nil, err + } + + // Extract an Addr from sa + raddr := Addr{} + for i := 0; i < len(raddr.VMID); i++ { + raddr.VMID[i] = sa.VMID[i] + } + for i := 0; i < len(raddr.ServiceID); i++ { + raddr.ServiceID[i] = sa.ServiceID[i] + } + return newHVsockConn(fd, v.local, raddr) +} + +// Close closes the listening connection +func (v *hvsockListener) Close() error { + return syscall.Close(v.fd) +} + +// Addr returns the address the Listener is listening on +func (v *hvsockListener) Addr() net.Addr { + return v.local +} + +// +// Hyper-V socket connection implementation +// + +// hvsockConn represent a Hyper-V connection. Complex mostly due to asynch send()/recv() syscalls. +type hvsockConn struct { + fd syscall.Handle + local Addr + remote Addr + + wg sync.WaitGroup + wgLock sync.RWMutex + closing atomicBool + + readDeadline deadlineHandler + writeDeadline deadlineHandler +} + +func newHVsockConn(h syscall.Handle, local Addr, remote Addr) (*hvsockConn, error) { + ioInitOnce.Do(initIo) + v := &hvsockConn{fd: h, local: local, remote: remote} + + _, err := createIoCompletionPort(h, ioCompletionPort, 0, 0xffffffff) + if err != nil { + return nil, err + } + err = setFileCompletionNotificationModes(h, + cFILE_SKIP_COMPLETION_PORT_ON_SUCCESS|cFILE_SKIP_SET_EVENT_ON_HANDLE) + if err != nil { + return nil, err + } + v.readDeadline.channel = make(timeoutChan) + v.writeDeadline.channel = make(timeoutChan) + + return v, nil +} + +// LocalAddr returns the local address of a connection +func (v *hvsockConn) LocalAddr() net.Addr { + return v.local +} + +// RemoteAddr returns the remote address of a connection +func (v *hvsockConn) RemoteAddr() net.Addr { + return v.remote +} + +// Close closes the connection +func (v *hvsockConn) Close() error { + v.close() + return nil +} + +// CloseRead shuts down the reading side of a hvsock connection +func (v *hvsockConn) CloseRead() error { + return syscall.Shutdown(v.fd, syscall.SHUT_RD) +} + +// CloseWrite shuts down the writing side of a hvsock connection +func (v *hvsockConn) CloseWrite() error { + return syscall.Shutdown(v.fd, syscall.SHUT_WR) +} + +// Read reads data from the connection +func (v *hvsockConn) Read(buf []byte) (int, error) { + var b syscall.WSABuf + var f uint32 + + b.Len = uint32(len(buf)) + b.Buf = &buf[0] + + c, err := v.prepareIo() + if err != nil { + return 0, err + } + defer v.wg.Done() + + if v.readDeadline.timedout.isSet() { + return 0, ErrTimeout + } + + var bytes uint32 + err = syscall.WSARecv(v.fd, &b, 1, &bytes, &f, &c.o, nil) + n, err := v.asyncIo(c, &v.readDeadline, bytes, err) + runtime.KeepAlive(buf) + + // Handle EOF conditions. + if err == nil && n == 0 && len(buf) != 0 { + return 0, io.EOF + } else if err == syscall.ERROR_BROKEN_PIPE { + return 0, io.EOF + } else { + return n, err + } +} + +// Write writes data over the connection +// TODO(rn): Remove once 4.9.x support is deprecated +func (v *hvsockConn) Write(buf []byte) (int, error) { + written := 0 + toWrite := len(buf) + for toWrite > 0 { + thisBatch := min(toWrite, maxMsgSize) + n, err := v.write(buf[written : written+thisBatch]) + if err != nil { + return written, err + } + if n != thisBatch { + return written, fmt.Errorf("short write %d != %d", n, thisBatch) + } + toWrite -= n + written += n + } + + return written, nil +} + +func (v *hvsockConn) write(buf []byte) (int, error) { + var b syscall.WSABuf + var f uint32 + + if len(buf) == 0 { + return 0, nil + } + + f = 0 + b.Len = uint32(len(buf)) + b.Buf = &buf[0] + + c, err := v.prepareIo() + if err != nil { + return 0, err + } + defer v.wg.Done() + + if v.writeDeadline.timedout.isSet() { + return 0, ErrTimeout + } + + var bytes uint32 + err = syscall.WSASend(v.fd, &b, 1, &bytes, f, &c.o, nil) + n, err := v.asyncIo(c, &v.writeDeadline, bytes, err) + runtime.KeepAlive(buf) + return n, err +} + +// SetReadDeadline implementation for Hyper-V sockets +func (v *hvsockConn) SetReadDeadline(deadline time.Time) error { + return v.readDeadline.set(deadline) +} + +// SetWriteDeadline implementation for Hyper-V sockets +func (v *hvsockConn) SetWriteDeadline(deadline time.Time) error { + return v.writeDeadline.set(deadline) +} + +// SetDeadline implementation for Hyper-V sockets +func (v *hvsockConn) SetDeadline(deadline time.Time) error { + if err := v.SetReadDeadline(deadline); err != nil { + return err + } + return v.SetWriteDeadline(deadline) +} + +// Helper functions for conversion to sockaddr + +// struck sockaddr equivalent +type rawSockaddrHyperv struct { + Family uint16 + Reserved uint16 + VMID GUID + ServiceID GUID +} + +// Utility function to build a struct sockaddr for syscalls. +func (a Addr) sockaddr(sa *rawSockaddrHyperv) (unsafe.Pointer, int32, error) { + sa.Family = hvsockAF + sa.Reserved = 0 + for i := 0; i < len(sa.VMID); i++ { + sa.VMID[i] = a.VMID[i] + } + for i := 0; i < len(sa.ServiceID); i++ { + sa.ServiceID[i] = a.ServiceID[i] + } + + return unsafe.Pointer(sa), int32(unsafe.Sizeof(*sa)), nil +} + +// Help for read/write timeouts +type deadlineHandler struct { + setLock sync.Mutex + channel timeoutChan + channelLock sync.RWMutex + timer *time.Timer + timedout atomicBool +} + +// The code below here is adjusted from: +// https://github.com/Microsoft/go-winio/blob/master/file.go +type atomicBool int32 + +func (b *atomicBool) isSet() bool { return atomic.LoadInt32((*int32)(b)) != 0 } +func (b *atomicBool) setFalse() { atomic.StoreInt32((*int32)(b), 0) } +func (b *atomicBool) setTrue() { atomic.StoreInt32((*int32)(b), 1) } +func (b *atomicBool) swap(new bool) bool { + var newInt int32 + if new { + newInt = 1 + } + return atomic.SwapInt32((*int32)(b), newInt) == 1 +} + +type timeoutError struct{} + +func (e *timeoutError) Error() string { return "i/o timeout" } +func (e *timeoutError) Timeout() bool { return true } +func (e *timeoutError) Temporary() bool { return true } + +type timeoutChan chan struct{} + +var ioInitOnce sync.Once +var ioCompletionPort syscall.Handle + +// ioResult contains the result of an asynchronous IO operation +type ioResult struct { + bytes uint32 + err error +} + +type ioOperation struct { + o syscall.Overlapped + ch chan ioResult +} + +func initIo() { + h, err := createIoCompletionPort(syscall.InvalidHandle, 0, 0, 0xffffffff) + if err != nil { + panic(err) + } + ioCompletionPort = h + go ioCompletionProcessor(h) +} + +func (v *hvsockConn) close() { + v.wgLock.Lock() + if !v.closing.swap(true) { + v.wgLock.Unlock() + // cancel all IO and wait for it to complete + cancelIoEx(v.fd, nil) + v.wg.Wait() + // at this point, no new IO can start + syscall.Close(v.fd) + v.fd = 0 + } else { + v.wgLock.Unlock() + } +} + +// prepareIo prepares for a new IO operation +func (v *hvsockConn) prepareIo() (*ioOperation, error) { + v.wgLock.RLock() + if v.closing.isSet() { + v.wgLock.RUnlock() + return nil, fmt.Errorf("HvSocket has already been closed") + } + v.wg.Add(1) + v.wgLock.RUnlock() + c := &ioOperation{} + c.ch = make(chan ioResult) + return c, nil +} + +// ioCompletionProcessor processes completed async IOs forever +func ioCompletionProcessor(h syscall.Handle) { + // Set the timer resolution to 1. This fixes a performance regression in golang 1.6. + timeBeginPeriod(1) + for { + var bytes uint32 + var key uintptr + var op *ioOperation + err := getQueuedCompletionStatus(h, &bytes, &key, &op, syscall.INFINITE) + if op == nil { + panic(err) + } + op.ch <- ioResult{bytes, err} + } +} + +// asyncIo processes the return value from Recv or Send, blocking until +// the operation has actually completed. +func (v *hvsockConn) asyncIo(c *ioOperation, d *deadlineHandler, bytes uint32, err error) (int, error) { + if err != syscall.ERROR_IO_PENDING { + return int(bytes), err + } + + if v.closing.isSet() { + cancelIoEx(v.fd, &c.o) + } + + var timeout timeoutChan + if d != nil { + d.channelLock.Lock() + timeout = d.channel + d.channelLock.Unlock() + } + + var r ioResult + select { + case r = <-c.ch: + err = r.err + if err == syscall.ERROR_OPERATION_ABORTED { + if v.closing.isSet() { + err = fmt.Errorf("HvSocket has already been closed") + } + } + case <-timeout: + cancelIoEx(v.fd, &c.o) + r = <-c.ch + err = r.err + if err == syscall.ERROR_OPERATION_ABORTED { + err = ErrTimeout + } + } + + // runtime.KeepAlive is needed, as c is passed via native + // code to ioCompletionProcessor, c must remain alive + // until the channel read is complete. + runtime.KeepAlive(c) + return int(r.bytes), err +} + +func (d *deadlineHandler) set(deadline time.Time) error { + d.setLock.Lock() + defer d.setLock.Unlock() + + if d.timer != nil { + if !d.timer.Stop() { + <-d.channel + } + d.timer = nil + } + d.timedout.setFalse() + + select { + case <-d.channel: + d.channelLock.Lock() + d.channel = make(chan struct{}) + d.channelLock.Unlock() + default: + } + + if deadline.IsZero() { + return nil + } + + timeoutIO := func() { + d.timedout.setTrue() + close(d.channel) + } + + now := time.Now() + duration := deadline.Sub(now) + if deadline.After(now) { + // Deadline is in the future, set a timer to wait + d.timer = time.AfterFunc(duration, timeoutIO) + } else { + // Deadline is in the past. Cancel all pending IO now. + timeoutIO() + } + return nil +} diff --git a/vendor/github.com/linuxkit/virtsock/pkg/hvsock/zsyscall_windows.go b/vendor/github.com/linuxkit/virtsock/pkg/hvsock/zsyscall_windows.go new file mode 100644 index 0000000000..bd4ca8a4f5 --- /dev/null +++ b/vendor/github.com/linuxkit/virtsock/pkg/hvsock/zsyscall_windows.go @@ -0,0 +1,171 @@ +package hvsock + +/* +Most of this code was derived from: https://github.com/Microsoft/go-winio +which has the following license: + +The MIT License (MIT) + +Copyright (c) 2015 Microsoft + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +import ( + "syscall" + "unsafe" +) + +var ( + modws2_32 = syscall.NewLazyDLL("ws2_32.dll") + modwinmm = syscall.NewLazyDLL("winmm.dll") + modkernel32 = syscall.NewLazyDLL("kernel32.dll") + + procConnect = modws2_32.NewProc("connect") + procBind = modws2_32.NewProc("bind") + procAccept = modws2_32.NewProc("accept") + + procCancelIoEx = modkernel32.NewProc("CancelIoEx") + procCreateIoCompletionPort = modkernel32.NewProc("CreateIoCompletionPort") + procGetQueuedCompletionStatus = modkernel32.NewProc("GetQueuedCompletionStatus") + procSetFileCompletionNotificationModes = modkernel32.NewProc("SetFileCompletionNotificationModes") + proctimeBeginPeriod = modwinmm.NewProc("timeBeginPeriod") +) + +// Do the interface allocations only once for common +// Errno values. +const ( + errnoERROR_IO_PENDING = 997 + socketError = uintptr(^uint32(0)) + + cFILE_SKIP_COMPLETION_PORT_ON_SUCCESS = 1 + cFILE_SKIP_SET_EVENT_ON_HANDLE = 2 +) + +var ( + errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING) +) + +// errnoErr returns common boxed Errno values, to prevent +// allocations at runtime. +func errnoErr(e syscall.Errno) error { + switch e { + case 0: + return nil + case errnoERROR_IO_PENDING: + return errERROR_IO_PENDING + } + // TODO: add more here, after collecting data on the common + // error values see on Windows. (perhaps when running + // all.bat?) + return e +} + +func sys_connect(s syscall.Handle, name unsafe.Pointer, namelen int32) (err error) { + r1, _, e1 := syscall.Syscall(procConnect.Addr(), 3, uintptr(s), uintptr(name), uintptr(namelen)) + if r1 == socketError { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + + return +} + +func sys_bind(s syscall.Handle, name unsafe.Pointer, namelen int32) (err error) { + r1, _, e1 := syscall.Syscall(procBind.Addr(), 3, uintptr(s), uintptr(name), uintptr(namelen)) + if r1 == socketError { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func sys_accept(s syscall.Handle, rsa *rawSockaddrHyperv, addrlen *int32) (handle syscall.Handle, err error) { + r1, _, e1 := syscall.Syscall(procAccept.Addr(), 3, uintptr(s), uintptr(unsafe.Pointer(rsa)), uintptr(unsafe.Pointer(addrlen))) + handle = syscall.Handle(r1) + if r1 == socketError { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func cancelIoEx(file syscall.Handle, o *syscall.Overlapped) (err error) { + r1, _, e1 := syscall.Syscall(procCancelIoEx.Addr(), 2, uintptr(file), uintptr(unsafe.Pointer(o)), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func createIoCompletionPort(file syscall.Handle, port syscall.Handle, key uintptr, threadCount uint32) (newport syscall.Handle, err error) { + r0, _, e1 := syscall.Syscall6(procCreateIoCompletionPort.Addr(), 4, uintptr(file), uintptr(port), uintptr(key), uintptr(threadCount), 0, 0) + newport = syscall.Handle(r0) + if newport == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func getQueuedCompletionStatus(port syscall.Handle, bytes *uint32, key *uintptr, o **ioOperation, timeout uint32) (err error) { + r1, _, e1 := syscall.Syscall6(procGetQueuedCompletionStatus.Addr(), 5, uintptr(port), uintptr(unsafe.Pointer(bytes)), uintptr(unsafe.Pointer(key)), uintptr(unsafe.Pointer(o)), uintptr(timeout), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func setFileCompletionNotificationModes(h syscall.Handle, flags uint8) (err error) { + r1, _, e1 := syscall.Syscall(procSetFileCompletionNotificationModes.Addr(), 2, uintptr(h), uintptr(flags), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func timeBeginPeriod(period uint32) (n int32) { + r0, _, _ := syscall.Syscall(proctimeBeginPeriod.Addr(), 1, uintptr(period), 0, 0) + n = int32(r0) + return +} diff --git a/vendor/github.com/mdlayher/socket/CHANGELOG.md b/vendor/github.com/mdlayher/socket/CHANGELOG.md new file mode 100644 index 0000000000..f0d01641a2 --- /dev/null +++ b/vendor/github.com/mdlayher/socket/CHANGELOG.md @@ -0,0 +1,80 @@ +# CHANGELOG + +## v0.4.1 + +- [Bug Fix] [commit](https://github.com/mdlayher/socket/commit/2a14ceef4da279de1f957c5761fffcc6c87bbd3b): + ensure `socket.Conn` can be used with non-socket file descriptors by handling + `ENOTSOCK` in the constructor. + +## v0.4.0 + +**This is the first release of package socket that only supports Go 1.18+. +Users on older versions of Go must use v0.3.0.** + +- [Improvement]: drop support for older versions of Go so we can begin using + modern versions of `x/sys` and other dependencies. + +## v0.3.0 + +**This is the last release of package socket that supports Go 1.17 and below.** + +- [New API/API change] [PR](https://github.com/mdlayher/socket/pull/8): + numerous `socket.Conn` methods now support context cancelation. Future + releases will continue adding support as needed. + - New `ReadContext` and `WriteContext` methods. + - `Connect`, `Recvfrom`, `Recvmsg`, `Sendmsg`, and `Sendto` methods now accept + a context. + - `Sendto` parameter order was also fixed to match the underlying syscall. + +## v0.2.3 + +- [New API] [commit](https://github.com/mdlayher/socket/commit/a425d96e0f772c053164f8ce4c9c825380a98086): + `socket.Conn` has new `Pidfd*` methods for wrapping the `pidfd_*(2)` family of + system calls. + +## v0.2.2 + +- [New API] [commit](https://github.com/mdlayher/socket/commit/a2429f1dfe8ec2586df5a09f50ead865276cd027): + `socket.Conn` has new `IoctlKCM*` methods for wrapping `ioctl(2)` for `AF_KCM` + operations. + +## v0.2.1 + +- [New API] [commit](https://github.com/mdlayher/socket/commit/b18ddbe9caa0e34552b4409a3aa311cb460d2f99): + `socket.Conn` has a new `SetsockoptPacketMreq` method for wrapping + `setsockopt(2)` for `AF_PACKET` socket options. + +## v0.2.0 + +- [New API] [commit](https://github.com/mdlayher/socket/commit/6e912a68523c45e5fd899239f4b46c402dd856da): + `socket.FileConn` can be used to create a `socket.Conn` from an existing + `os.File`, which may be provided by systemd socket activation or another + external mechanism. +- [API change] [commit](https://github.com/mdlayher/socket/commit/66d61f565188c23fe02b24099ddc856d538bf1a7): + `socket.Conn.Connect` now returns the `unix.Sockaddr` value provided by + `getpeername(2)`, since we have to invoke that system call anyway to verify + that a connection to a remote peer was successfully established. +- [Bug Fix] [commit](https://github.com/mdlayher/socket/commit/b60b2dbe0ac3caff2338446a150083bde8c5c19c): + check the correct error from `unix.GetsockoptInt` in the `socket.Conn.Connect` + method. Thanks @vcabbage! + +## v0.1.2 + +- [Bug Fix]: `socket.Conn.Connect` now properly checks the `SO_ERROR` socket + option value after calling `connect(2)` to verify whether or not a connection + could successfully be established. This means that `Connect` should now report + an error for an `AF_INET` TCP connection refused or `AF_VSOCK` connection + reset by peer. +- [New API]: add `socket.Conn.Getpeername` for use in `Connect`, but also for + use by external callers. + +## v0.1.1 + +- [New API]: `socket.Conn` now has `CloseRead`, `CloseWrite`, and `Shutdown` + methods. +- [Improvement]: internal rework to more robustly handle various errors. + +## v0.1.0 + +- Initial unstable release. Most functionality has been developed and ported +from package [`netlink`](https://github.com/mdlayher/netlink). diff --git a/vendor/github.com/mdlayher/socket/LICENSE.md b/vendor/github.com/mdlayher/socket/LICENSE.md new file mode 100644 index 0000000000..3ccdb75b26 --- /dev/null +++ b/vendor/github.com/mdlayher/socket/LICENSE.md @@ -0,0 +1,9 @@ +# MIT License + +Copyright (C) 2021 Matt Layher + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/mdlayher/socket/README.md b/vendor/github.com/mdlayher/socket/README.md new file mode 100644 index 0000000000..2aa065cbb7 --- /dev/null +++ b/vendor/github.com/mdlayher/socket/README.md @@ -0,0 +1,23 @@ +# socket [![Test Status](https://github.com/mdlayher/socket/workflows/Test/badge.svg)](https://github.com/mdlayher/socket/actions) [![Go Reference](https://pkg.go.dev/badge/github.com/mdlayher/socket.svg)](https://pkg.go.dev/github.com/mdlayher/socket) [![Go Report Card](https://goreportcard.com/badge/github.com/mdlayher/socket)](https://goreportcard.com/report/github.com/mdlayher/socket) + +Package `socket` provides a low-level network connection type which integrates +with Go's runtime network poller to provide asynchronous I/O and deadline +support. MIT Licensed. + +This package focuses on UNIX-like operating systems which make use of BSD +sockets system call APIs. It is meant to be used as a foundation for the +creation of operating system-specific socket packages, for socket families such +as Linux's `AF_NETLINK`, `AF_PACKET`, or `AF_VSOCK`. This package should not be +used directly in end user applications. + +Any use of package socket should be guarded by build tags, as one would also +use when importing the `syscall` or `golang.org/x/sys` packages. + +## Stability + +See the [CHANGELOG](./CHANGELOG.md) file for a description of changes between +releases. + +This package only supports the two most recent major versions of Go, mirroring +Go's own release policy. Older versions of Go may lack critical features and bug +fixes which are necessary for this package to function correctly. diff --git a/vendor/github.com/mdlayher/socket/accept.go b/vendor/github.com/mdlayher/socket/accept.go new file mode 100644 index 0000000000..47e9d897ef --- /dev/null +++ b/vendor/github.com/mdlayher/socket/accept.go @@ -0,0 +1,23 @@ +//go:build !dragonfly && !freebsd && !illumos && !linux +// +build !dragonfly,!freebsd,!illumos,!linux + +package socket + +import ( + "fmt" + "runtime" + + "golang.org/x/sys/unix" +) + +const sysAccept = "accept" + +// accept wraps accept(2). +func accept(fd, flags int) (int, unix.Sockaddr, error) { + if flags != 0 { + // These operating systems have no support for flags to accept(2). + return 0, nil, fmt.Errorf("socket: Conn.Accept flags are ineffective on %s", runtime.GOOS) + } + + return unix.Accept(fd) +} diff --git a/vendor/github.com/mdlayher/socket/accept4.go b/vendor/github.com/mdlayher/socket/accept4.go new file mode 100644 index 0000000000..e1016b2063 --- /dev/null +++ b/vendor/github.com/mdlayher/socket/accept4.go @@ -0,0 +1,15 @@ +//go:build dragonfly || freebsd || illumos || linux +// +build dragonfly freebsd illumos linux + +package socket + +import ( + "golang.org/x/sys/unix" +) + +const sysAccept = "accept4" + +// accept wraps accept4(2). +func accept(fd, flags int) (int, unix.Sockaddr, error) { + return unix.Accept4(fd, flags) +} diff --git a/vendor/github.com/mdlayher/socket/conn.go b/vendor/github.com/mdlayher/socket/conn.go new file mode 100644 index 0000000000..7b3cc7a6e7 --- /dev/null +++ b/vendor/github.com/mdlayher/socket/conn.go @@ -0,0 +1,880 @@ +package socket + +import ( + "context" + "errors" + "io" + "os" + "sync" + "sync/atomic" + "syscall" + "time" + + "golang.org/x/sys/unix" +) + +// Lock in an expected public interface for convenience. +var _ interface { + io.ReadWriteCloser + syscall.Conn + SetDeadline(t time.Time) error + SetReadDeadline(t time.Time) error + SetWriteDeadline(t time.Time) error +} = &Conn{} + +// A Conn is a low-level network connection which integrates with Go's runtime +// network poller to provide asynchronous I/O and deadline support. +// +// Many of a Conn's blocking methods support net.Conn deadlines as well as +// cancelation via context. Note that passing a context with a deadline set will +// override any of the previous deadlines set by calls to the SetDeadline family +// of methods. +type Conn struct { + // Indicates whether or not Conn.Close has been called. Must be accessed + // atomically. Atomics definitions must come first in the Conn struct. + closed uint32 + + // A unique name for the Conn which is also associated with derived file + // descriptors such as those created by accept(2). + name string + + // facts contains information we have determined about Conn to trigger + // alternate behavior in certain functions. + facts facts + + // Provides access to the underlying file registered with the runtime + // network poller, and arbitrary raw I/O calls. + fd *os.File + rc syscall.RawConn +} + +// facts contains facts about a Conn. +type facts struct { + // isStream reports whether this is a streaming descriptor, as opposed to a + // packet-based descriptor like a UDP socket. + isStream bool + + // zeroReadIsEOF reports Whether a zero byte read indicates EOF. This is + // false for a message based socket connection. + zeroReadIsEOF bool +} + +// A Config contains options for a Conn. +type Config struct { + // NetNS specifies the Linux network namespace the Conn will operate in. + // This option is unsupported on other operating systems. + // + // If set (non-zero), Conn will enter the specified network namespace and an + // error will occur in Socket if the operation fails. + // + // If not set (zero), a best-effort attempt will be made to enter the + // network namespace of the calling thread: this means that any changes made + // to the calling thread's network namespace will also be reflected in Conn. + // If this operation fails (due to lack of permissions or because network + // namespaces are disabled by kernel configuration), Socket will not return + // an error, and the Conn will operate in the default network namespace of + // the process. This enables non-privileged use of Conn in applications + // which do not require elevated privileges. + // + // Entering a network namespace is a privileged operation (root or + // CAP_SYS_ADMIN are required), and most applications should leave this set + // to 0. + NetNS int +} + +// High-level methods which provide convenience over raw system calls. + +// Close closes the underlying file descriptor for the Conn, which also causes +// all in-flight I/O operations to immediately unblock and return errors. Any +// subsequent uses of Conn will result in EBADF. +func (c *Conn) Close() error { + // The caller has expressed an intent to close the socket, so immediately + // increment s.closed to force further calls to result in EBADF before also + // closing the file descriptor to unblock any outstanding operations. + // + // Because other operations simply check for s.closed != 0, we will permit + // double Close, which would increment s.closed beyond 1. + if atomic.AddUint32(&c.closed, 1) != 1 { + // Multiple Close calls. + return nil + } + + return os.NewSyscallError("close", c.fd.Close()) +} + +// CloseRead shuts down the reading side of the Conn. Most callers should just +// use Close. +func (c *Conn) CloseRead() error { return c.Shutdown(unix.SHUT_RD) } + +// CloseWrite shuts down the writing side of the Conn. Most callers should just +// use Close. +func (c *Conn) CloseWrite() error { return c.Shutdown(unix.SHUT_WR) } + +// Read reads directly from the underlying file descriptor. +func (c *Conn) Read(b []byte) (int, error) { return c.fd.Read(b) } + +// ReadContext reads from the underlying file descriptor with added support for +// context cancelation. +func (c *Conn) ReadContext(ctx context.Context, b []byte) (int, error) { + if c.facts.isStream && len(b) > maxRW { + b = b[:maxRW] + } + + n, err := readT(c, ctx, "read", func(fd int) (int, error) { + return unix.Read(fd, b) + }) + if n == 0 && err == nil && c.facts.zeroReadIsEOF { + return 0, io.EOF + } + + return n, os.NewSyscallError("read", err) +} + +// Write writes directly to the underlying file descriptor. +func (c *Conn) Write(b []byte) (int, error) { return c.fd.Write(b) } + +// WriteContext writes to the underlying file descriptor with added support for +// context cancelation. +func (c *Conn) WriteContext(ctx context.Context, b []byte) (int, error) { + var ( + n, nn int + err error + ) + + doErr := c.write(ctx, "write", func(fd int) error { + max := len(b) + if c.facts.isStream && max-nn > maxRW { + max = nn + maxRW + } + + n, err = unix.Write(fd, b[nn:max]) + if n > 0 { + nn += n + } + if nn == len(b) { + return err + } + if n == 0 && err == nil { + err = io.ErrUnexpectedEOF + return nil + } + + return err + }) + if doErr != nil { + return 0, doErr + } + + return nn, os.NewSyscallError("write", err) +} + +// SetDeadline sets both the read and write deadlines associated with the Conn. +func (c *Conn) SetDeadline(t time.Time) error { return c.fd.SetDeadline(t) } + +// SetReadDeadline sets the read deadline associated with the Conn. +func (c *Conn) SetReadDeadline(t time.Time) error { return c.fd.SetReadDeadline(t) } + +// SetWriteDeadline sets the write deadline associated with the Conn. +func (c *Conn) SetWriteDeadline(t time.Time) error { return c.fd.SetWriteDeadline(t) } + +// ReadBuffer gets the size of the operating system's receive buffer associated +// with the Conn. +func (c *Conn) ReadBuffer() (int, error) { + return c.GetsockoptInt(unix.SOL_SOCKET, unix.SO_RCVBUF) +} + +// WriteBuffer gets the size of the operating system's transmit buffer +// associated with the Conn. +func (c *Conn) WriteBuffer() (int, error) { + return c.GetsockoptInt(unix.SOL_SOCKET, unix.SO_SNDBUF) +} + +// SetReadBuffer sets the size of the operating system's receive buffer +// associated with the Conn. +// +// When called with elevated privileges on Linux, the SO_RCVBUFFORCE option will +// be used to override operating system limits. Otherwise SO_RCVBUF is used +// (which obeys operating system limits). +func (c *Conn) SetReadBuffer(bytes int) error { return c.setReadBuffer(bytes) } + +// SetWriteBuffer sets the size of the operating system's transmit buffer +// associated with the Conn. +// +// When called with elevated privileges on Linux, the SO_SNDBUFFORCE option will +// be used to override operating system limits. Otherwise SO_SNDBUF is used +// (which obeys operating system limits). +func (c *Conn) SetWriteBuffer(bytes int) error { return c.setWriteBuffer(bytes) } + +// SyscallConn returns a raw network connection. This implements the +// syscall.Conn interface. +// +// SyscallConn is intended for advanced use cases, such as getting and setting +// arbitrary socket options using the socket's file descriptor. If possible, +// those operations should be performed using methods on Conn instead. +// +// Once invoked, it is the caller's responsibility to ensure that operations +// performed using Conn and the syscall.RawConn do not conflict with each other. +func (c *Conn) SyscallConn() (syscall.RawConn, error) { + if atomic.LoadUint32(&c.closed) != 0 { + return nil, os.NewSyscallError("syscallconn", unix.EBADF) + } + + // TODO(mdlayher): mutex or similar to enforce syscall.RawConn contract of + // FD remaining valid for duration of calls? + return c.rc, nil +} + +// Socket wraps the socket(2) system call to produce a Conn. domain, typ, and +// proto are passed directly to socket(2), and name should be a unique name for +// the socket type such as "netlink" or "vsock". +// +// The cfg parameter specifies optional configuration for the Conn. If nil, no +// additional configuration will be applied. +// +// If the operating system supports SOCK_CLOEXEC and SOCK_NONBLOCK, they are +// automatically applied to typ to mirror the standard library's socket flag +// behaviors. +func Socket(domain, typ, proto int, name string, cfg *Config) (*Conn, error) { + if cfg == nil { + cfg = &Config{} + } + + if cfg.NetNS == 0 { + // Non-Linux or no network namespace. + return socket(domain, typ, proto, name) + } + + // Linux only: create Conn in the specified network namespace. + return withNetNS(cfg.NetNS, func() (*Conn, error) { + return socket(domain, typ, proto, name) + }) +} + +// socket is the internal, cross-platform entry point for socket(2). +func socket(domain, typ, proto int, name string) (*Conn, error) { + var ( + fd int + err error + ) + + for { + fd, err = unix.Socket(domain, typ|socketFlags, proto) + switch { + case err == nil: + // Some OSes already set CLOEXEC with typ. + if !flagCLOEXEC { + unix.CloseOnExec(fd) + } + + // No error, prepare the Conn. + return New(fd, name) + case !ready(err): + // System call interrupted or not ready, try again. + continue + case err == unix.EINVAL, err == unix.EPROTONOSUPPORT: + // On Linux, SOCK_NONBLOCK and SOCK_CLOEXEC were introduced in + // 2.6.27. On FreeBSD, both flags were introduced in FreeBSD 10. + // EINVAL and EPROTONOSUPPORT check for earlier versions of these + // OSes respectively. + // + // Mirror what the standard library does when creating file + // descriptors: avoid racing a fork/exec with the creation of new + // file descriptors, so that child processes do not inherit socket + // file descriptors unexpectedly. + // + // For a more thorough explanation, see similar work in the Go tree: + // func sysSocket in net/sock_cloexec.go, as well as the detailed + // comment in syscall/exec_unix.go. + syscall.ForkLock.RLock() + fd, err = unix.Socket(domain, typ, proto) + if err != nil { + syscall.ForkLock.RUnlock() + return nil, os.NewSyscallError("socket", err) + } + unix.CloseOnExec(fd) + syscall.ForkLock.RUnlock() + + return New(fd, name) + default: + // Unhandled error. + return nil, os.NewSyscallError("socket", err) + } + } +} + +// FileConn returns a copy of the network connection corresponding to the open +// file. It is the caller's responsibility to close the file when finished. +// Closing the Conn does not affect the File, and closing the File does not +// affect the Conn. +func FileConn(f *os.File, name string) (*Conn, error) { + // First we'll try to do fctnl(2) with F_DUPFD_CLOEXEC because we can dup + // the file descriptor and set the flag in one syscall. + fd, err := unix.FcntlInt(f.Fd(), unix.F_DUPFD_CLOEXEC, 0) + switch err { + case nil: + // OK, ready to set up non-blocking I/O. + return New(fd, name) + case unix.EINVAL: + // The kernel rejected our fcntl(2), fall back to separate dup(2) and + // setting close on exec. + // + // Mirror what the standard library does when creating file descriptors: + // avoid racing a fork/exec with the creation of new file descriptors, + // so that child processes do not inherit socket file descriptors + // unexpectedly. + syscall.ForkLock.RLock() + fd, err := unix.Dup(fd) + if err != nil { + syscall.ForkLock.RUnlock() + return nil, os.NewSyscallError("dup", err) + } + unix.CloseOnExec(fd) + syscall.ForkLock.RUnlock() + + return New(fd, name) + default: + // Any other errors. + return nil, os.NewSyscallError("fcntl", err) + } +} + +// New wraps an existing file descriptor to create a Conn. name should be a +// unique name for the socket type such as "netlink" or "vsock". +// +// Most callers should use Socket or FileConn to construct a Conn. New is +// intended for integrating with specific system calls which provide a file +// descriptor that supports asynchronous I/O. The file descriptor is immediately +// set to nonblocking mode and registered with Go's runtime network poller for +// future I/O operations. +// +// Unlike FileConn, New does not duplicate the existing file descriptor in any +// way. The returned Conn takes ownership of the underlying file descriptor. +func New(fd int, name string) (*Conn, error) { + // All Conn I/O is nonblocking for integration with Go's runtime network + // poller. Depending on the OS this might already be set but it can't hurt + // to set it again. + if err := unix.SetNonblock(fd, true); err != nil { + return nil, os.NewSyscallError("setnonblock", err) + } + + // os.NewFile registers the non-blocking file descriptor with the runtime + // poller, which is then used for most subsequent operations except those + // that require raw I/O via SyscallConn. + // + // See also: https://golang.org/pkg/os/#NewFile + f := os.NewFile(uintptr(fd), name) + rc, err := f.SyscallConn() + if err != nil { + return nil, err + } + + c := &Conn{ + name: name, + fd: f, + rc: rc, + } + + // Probe the file descriptor for socket settings. + sotype, err := c.GetsockoptInt(unix.SOL_SOCKET, unix.SO_TYPE) + switch { + case err == nil: + // File is a socket, check its properties. + c.facts = facts{ + isStream: sotype == unix.SOCK_STREAM, + zeroReadIsEOF: sotype != unix.SOCK_DGRAM && sotype != unix.SOCK_RAW, + } + case errors.Is(err, unix.ENOTSOCK): + // File is not a socket, treat it as a regular file. + c.facts = facts{ + isStream: true, + zeroReadIsEOF: true, + } + default: + return nil, err + } + + return c, nil +} + +// Low-level methods which provide raw system call access. + +// Accept wraps accept(2) or accept4(2) depending on the operating system, but +// returns a Conn for the accepted connection rather than a raw file descriptor. +// +// If the operating system supports accept4(2) (which allows flags), +// SOCK_CLOEXEC and SOCK_NONBLOCK are automatically applied to flags to mirror +// the standard library's socket flag behaviors. +// +// If the operating system only supports accept(2) (which does not allow flags) +// and flags is not zero, an error will be returned. +// +// Accept obeys context cancelation and uses the deadline set on the context to +// cancel accepting the next connection. If a deadline is set on ctx, this +// deadline will override any previous deadlines set using SetDeadline or +// SetReadDeadline. Upon return, the read deadline is cleared. +func (c *Conn) Accept(ctx context.Context, flags int) (*Conn, unix.Sockaddr, error) { + type ret struct { + nfd int + sa unix.Sockaddr + } + + r, err := readT(c, ctx, sysAccept, func(fd int) (ret, error) { + // Either accept(2) or accept4(2) depending on the OS. + nfd, sa, err := accept(fd, flags|socketFlags) + return ret{nfd, sa}, err + }) + if err != nil { + // internal/poll, context error, or user function error. + return nil, nil, err + } + + // Successfully accepted a connection, wrap it in a Conn for use by the + // caller. + ac, err := New(r.nfd, c.name) + if err != nil { + return nil, nil, err + } + + return ac, r.sa, nil +} + +// Bind wraps bind(2). +func (c *Conn) Bind(sa unix.Sockaddr) error { + return c.control(context.Background(), "bind", func(fd int) error { + return unix.Bind(fd, sa) + }) +} + +// Connect wraps connect(2). In order to verify that the underlying socket is +// connected to a remote peer, Connect calls getpeername(2) and returns the +// unix.Sockaddr from that call. +// +// Connect obeys context cancelation and uses the deadline set on the context to +// cancel connecting to a remote peer. If a deadline is set on ctx, this +// deadline will override any previous deadlines set using SetDeadline or +// SetWriteDeadline. Upon return, the write deadline is cleared. +func (c *Conn) Connect(ctx context.Context, sa unix.Sockaddr) (unix.Sockaddr, error) { + const op = "connect" + + // TODO(mdlayher): it would seem that trying to connect to unbound vsock + // listeners by calling Connect multiple times results in ECONNRESET for the + // first and nil error for subsequent calls. Do we need to memoize the + // error? Check what the stdlib behavior is. + + var ( + // Track progress between invocations of the write closure. We don't + // have an explicit WaitWrite call like internal/poll does, so we have + // to wait until the runtime calls the closure again to indicate we can + // write. + progress uint32 + + // Capture closure sockaddr and error. + rsa unix.Sockaddr + err error + ) + + doErr := c.write(ctx, op, func(fd int) error { + if atomic.AddUint32(&progress, 1) == 1 { + // First call: initiate connect. + return unix.Connect(fd, sa) + } + + // Subsequent calls: the runtime network poller indicates fd is + // writable. Check for errno. + errno, gerr := c.GetsockoptInt(unix.SOL_SOCKET, unix.SO_ERROR) + if gerr != nil { + return gerr + } + if errno != 0 { + // Connection is still not ready or failed. If errno indicates + // the socket is not ready, we will wait for the next write + // event. Otherwise we propagate this errno back to the as a + // permanent error. + uerr := unix.Errno(errno) + err = uerr + return uerr + } + + // According to internal/poll, it's possible for the runtime network + // poller to spuriously wake us and return errno 0 for SO_ERROR. + // Make sure we are actually connected to a peer. + peer, err := c.Getpeername() + if err != nil { + // internal/poll unconditionally goes back to WaitWrite. + // Synthesize an error that will do the same for us. + return unix.EAGAIN + } + + // Connection complete. + rsa = peer + return nil + }) + if doErr != nil { + // internal/poll or context error. + return nil, doErr + } + + if err == unix.EISCONN { + // TODO(mdlayher): is this block obsolete with the addition of the + // getsockopt SO_ERROR check above? + // + // EISCONN is reported if the socket is already established and should + // not be treated as an error. + // - Darwin reports this for at least TCP sockets + // - Linux reports this for at least AF_VSOCK sockets + return rsa, nil + } + + return rsa, os.NewSyscallError(op, err) +} + +// Getsockname wraps getsockname(2). +func (c *Conn) Getsockname() (unix.Sockaddr, error) { + return controlT(c, context.Background(), "getsockname", unix.Getsockname) +} + +// Getpeername wraps getpeername(2). +func (c *Conn) Getpeername() (unix.Sockaddr, error) { + return controlT(c, context.Background(), "getpeername", unix.Getpeername) +} + +// GetsockoptInt wraps getsockopt(2) for integer values. +func (c *Conn) GetsockoptInt(level, opt int) (int, error) { + return controlT(c, context.Background(), "getsockopt", func(fd int) (int, error) { + return unix.GetsockoptInt(fd, level, opt) + }) +} + +// Listen wraps listen(2). +func (c *Conn) Listen(n int) error { + return c.control(context.Background(), "listen", func(fd int) error { + return unix.Listen(fd, n) + }) +} + +// Recvmsg wraps recvmsg(2). +func (c *Conn) Recvmsg(ctx context.Context, p, oob []byte, flags int) (int, int, int, unix.Sockaddr, error) { + type ret struct { + n, oobn, recvflags int + from unix.Sockaddr + } + + r, err := readT(c, ctx, "recvmsg", func(fd int) (ret, error) { + n, oobn, recvflags, from, err := unix.Recvmsg(fd, p, oob, flags) + return ret{n, oobn, recvflags, from}, err + }) + if r.n == 0 && err == nil && c.facts.zeroReadIsEOF { + return 0, 0, 0, nil, io.EOF + } + + return r.n, r.oobn, r.recvflags, r.from, err +} + +// Recvfrom wraps recvfrom(2). +func (c *Conn) Recvfrom(ctx context.Context, p []byte, flags int) (int, unix.Sockaddr, error) { + type ret struct { + n int + addr unix.Sockaddr + } + + out, err := readT(c, ctx, "recvfrom", func(fd int) (ret, error) { + n, addr, err := unix.Recvfrom(fd, p, flags) + return ret{n, addr}, err + }) + if out.n == 0 && err == nil && c.facts.zeroReadIsEOF { + return 0, nil, io.EOF + } + + return out.n, out.addr, err +} + +// Sendmsg wraps sendmsg(2). +func (c *Conn) Sendmsg(ctx context.Context, p, oob []byte, to unix.Sockaddr, flags int) (int, error) { + return writeT(c, ctx, "sendmsg", func(fd int) (int, error) { + return unix.SendmsgN(fd, p, oob, to, flags) + }) +} + +// Sendto wraps sendto(2). +func (c *Conn) Sendto(ctx context.Context, p []byte, flags int, to unix.Sockaddr) error { + return c.write(ctx, "sendto", func(fd int) error { + return unix.Sendto(fd, p, flags, to) + }) +} + +// SetsockoptInt wraps setsockopt(2) for integer values. +func (c *Conn) SetsockoptInt(level, opt, value int) error { + return c.control(context.Background(), "setsockopt", func(fd int) error { + return unix.SetsockoptInt(fd, level, opt, value) + }) +} + +// Shutdown wraps shutdown(2). +func (c *Conn) Shutdown(how int) error { + return c.control(context.Background(), "shutdown", func(fd int) error { + return unix.Shutdown(fd, how) + }) +} + +// Conn low-level read/write/control functions. These functions mirror the +// syscall.RawConn APIs but the input closures return errors rather than +// booleans. + +// read wraps readT to execute a function and capture its error result. This is +// a convenience wrapper for functions which don't return any extra values. +func (c *Conn) read(ctx context.Context, op string, f func(fd int) error) error { + _, err := readT(c, ctx, op, func(fd int) (struct{}, error) { + return struct{}{}, f(fd) + }) + return err +} + +// write executes f, a write function, against the associated file descriptor. +// op is used to create an *os.SyscallError if the file descriptor is closed. +func (c *Conn) write(ctx context.Context, op string, f func(fd int) error) error { + _, err := writeT(c, ctx, op, func(fd int) (struct{}, error) { + return struct{}{}, f(fd) + }) + return err +} + +// readT executes c.rc.Read for op using the input function, returning a newly +// allocated result T. +func readT[T any](c *Conn, ctx context.Context, op string, f func(fd int) (T, error)) (T, error) { + return rwT(c, rwContext[T]{ + Context: ctx, + Type: read, + Op: op, + Do: f, + }) +} + +// writeT executes c.rc.Write for op using the input function, returning a newly +// allocated result T. +func writeT[T any](c *Conn, ctx context.Context, op string, f func(fd int) (T, error)) (T, error) { + return rwT(c, rwContext[T]{ + Context: ctx, + Type: write, + Op: op, + Do: f, + }) +} + +// readWrite indicates if an operation intends to read or write. +type readWrite bool + +// Possible readWrite values. +const ( + read readWrite = false + write readWrite = true +) + +// An rwContext provides arguments to rwT. +type rwContext[T any] struct { + // The caller's context passed for cancelation. + Context context.Context + + // The type of an operation: read or write. + Type readWrite + + // The name of the operation used in errors. + Op string + + // The actual function to perform. + Do func(fd int) (T, error) +} + +// rwT executes c.rc.Read or c.rc.Write (depending on the value of rw.Type) for +// rw.Op using the input function, returning a newly allocated result T. +// +// It obeys context cancelation and the rw.Context must not be nil. +func rwT[T any](c *Conn, rw rwContext[T]) (T, error) { + if atomic.LoadUint32(&c.closed) != 0 { + // If the file descriptor is already closed, do nothing. + return *new(T), os.NewSyscallError(rw.Op, unix.EBADF) + } + + if err := rw.Context.Err(); err != nil { + // Early exit due to context cancel. + return *new(T), os.NewSyscallError(rw.Op, err) + } + + var ( + // The read or write function used to access the runtime network poller. + poll func(func(uintptr) bool) error + + // The read or write function used to set the matching deadline. + deadline func(time.Time) error + ) + + if rw.Type == write { + poll = c.rc.Write + deadline = c.SetWriteDeadline + } else { + poll = c.rc.Read + deadline = c.SetReadDeadline + } + + var ( + // Whether or not the context carried a deadline we are actively using + // for cancelation. + setDeadline bool + + // Signals for the cancelation watcher goroutine. + wg sync.WaitGroup + doneC = make(chan struct{}) + + // Atomic: reports whether we have to disarm the deadline. + // + // TODO(mdlayher): switch back to atomic.Bool when we drop support for + // Go 1.18. + needDisarm int64 + ) + + // On cancel, clean up the watcher. + defer func() { + close(doneC) + wg.Wait() + }() + + if d, ok := rw.Context.Deadline(); ok { + // The context has an explicit deadline. We will use it for cancelation + // but disarm it after poll for the next call. + if err := deadline(d); err != nil { + return *new(T), err + } + setDeadline = true + atomic.AddInt64(&needDisarm, 1) + } else { + // The context does not have an explicit deadline. We have to watch for + // cancelation so we can propagate that signal to immediately unblock + // the runtime network poller. + // + // TODO(mdlayher): is it possible to detect a background context vs a + // context with possible future cancel? + wg.Add(1) + go func() { + defer wg.Done() + + select { + case <-rw.Context.Done(): + // Cancel the operation. Make the caller disarm after poll + // returns. + atomic.AddInt64(&needDisarm, 1) + _ = deadline(time.Unix(0, 1)) + case <-doneC: + // Nothing to do. + } + }() + } + + var ( + t T + err error + ) + + pollErr := poll(func(fd uintptr) bool { + t, err = rw.Do(int(fd)) + return ready(err) + }) + + if atomic.LoadInt64(&needDisarm) > 0 { + _ = deadline(time.Time{}) + } + + if pollErr != nil { + if rw.Context.Err() != nil || (setDeadline && errors.Is(pollErr, os.ErrDeadlineExceeded)) { + // The caller canceled the operation or we set a deadline internally + // and it was reached. + // + // Unpack a plain context error. We wait for the context to be done + // to synchronize state externally. Otherwise we have noticed I/O + // timeout wakeups when we set a deadline but the context was not + // yet marked done. + <-rw.Context.Done() + return *new(T), os.NewSyscallError(rw.Op, rw.Context.Err()) + } + + // Error from syscall.RawConn methods. Conventionally the standard + // library does not wrap internal/poll errors in os.NewSyscallError. + return *new(T), pollErr + } + + // Result from user function. + return t, os.NewSyscallError(rw.Op, err) +} + +// control executes Conn.control for op using the input function. +func (c *Conn) control(ctx context.Context, op string, f func(fd int) error) error { + _, err := controlT(c, ctx, op, func(fd int) (struct{}, error) { + return struct{}{}, f(fd) + }) + return err +} + +// controlT executes c.rc.Control for op using the input function, returning a +// newly allocated result T. +func controlT[T any](c *Conn, ctx context.Context, op string, f func(fd int) (T, error)) (T, error) { + if atomic.LoadUint32(&c.closed) != 0 { + // If the file descriptor is already closed, do nothing. + return *new(T), os.NewSyscallError(op, unix.EBADF) + } + + var ( + t T + err error + ) + + doErr := c.rc.Control(func(fd uintptr) { + // Repeatedly attempt the syscall(s) invoked by f until completion is + // indicated by the return value of ready or the context is canceled. + // + // The last values for t and err are captured outside of the closure for + // use when the loop breaks. + for { + if err = ctx.Err(); err != nil { + // Early exit due to context cancel. + return + } + + t, err = f(int(fd)) + if ready(err) { + return + } + } + }) + if doErr != nil { + // Error from syscall.RawConn methods. Conventionally the standard + // library does not wrap internal/poll errors in os.NewSyscallError. + return *new(T), doErr + } + + // Result from user function. + return t, os.NewSyscallError(op, err) +} + +// ready indicates readiness based on the value of err. +func ready(err error) bool { + switch err { + case unix.EAGAIN, unix.EINPROGRESS, unix.EINTR: + // When a socket is in non-blocking mode, we might see a variety of errors: + // - EAGAIN: most common case for a socket read not being ready + // - EINPROGRESS: reported by some sockets when first calling connect + // - EINTR: system call interrupted, more frequently occurs in Go 1.14+ + // because goroutines can be asynchronously preempted + // + // Return false to let the poller wait for readiness. See the source code + // for internal/poll.FD.RawRead for more details. + return false + default: + // Ready regardless of whether there was an error or no error. + return true + } +} + +// Darwin and FreeBSD can't read or write 2GB+ files at a time, +// even on 64-bit systems. +// The same is true of socket implementations on many systems. +// See golang.org/issue/7812 and golang.org/issue/16266. +// Use 1GB instead of, say, 2GB-1, to keep subsequent reads aligned. +const maxRW = 1 << 30 diff --git a/vendor/github.com/mdlayher/socket/conn_linux.go b/vendor/github.com/mdlayher/socket/conn_linux.go new file mode 100644 index 0000000000..37579d4a0c --- /dev/null +++ b/vendor/github.com/mdlayher/socket/conn_linux.go @@ -0,0 +1,118 @@ +//go:build linux +// +build linux + +package socket + +import ( + "context" + "os" + "unsafe" + + "golang.org/x/net/bpf" + "golang.org/x/sys/unix" +) + +// IoctlKCMClone wraps ioctl(2) for unix.KCMClone values, but returns a Conn +// rather than a raw file descriptor. +func (c *Conn) IoctlKCMClone() (*Conn, error) { + info, err := controlT(c, context.Background(), "ioctl", unix.IoctlKCMClone) + if err != nil { + return nil, err + } + + // Successful clone, wrap in a Conn for use by the caller. + return New(int(info.Fd), c.name) +} + +// IoctlKCMAttach wraps ioctl(2) for unix.KCMAttach values. +func (c *Conn) IoctlKCMAttach(info unix.KCMAttach) error { + return c.control(context.Background(), "ioctl", func(fd int) error { + return unix.IoctlKCMAttach(fd, info) + }) +} + +// IoctlKCMUnattach wraps ioctl(2) for unix.KCMUnattach values. +func (c *Conn) IoctlKCMUnattach(info unix.KCMUnattach) error { + return c.control(context.Background(), "ioctl", func(fd int) error { + return unix.IoctlKCMUnattach(fd, info) + }) +} + +// PidfdGetfd wraps pidfd_getfd(2) for a Conn which wraps a pidfd, but returns a +// Conn rather than a raw file descriptor. +func (c *Conn) PidfdGetfd(targetFD, flags int) (*Conn, error) { + outFD, err := controlT(c, context.Background(), "pidfd_getfd", func(fd int) (int, error) { + return unix.PidfdGetfd(fd, targetFD, flags) + }) + if err != nil { + return nil, err + } + + // Successful getfd, wrap in a Conn for use by the caller. + return New(outFD, c.name) +} + +// PidfdSendSignal wraps pidfd_send_signal(2) for a Conn which wraps a Linux +// pidfd. +func (c *Conn) PidfdSendSignal(sig unix.Signal, info *unix.Siginfo, flags int) error { + return c.control(context.Background(), "pidfd_send_signal", func(fd int) error { + return unix.PidfdSendSignal(fd, sig, info, flags) + }) +} + +// SetBPF attaches an assembled BPF program to a Conn. +func (c *Conn) SetBPF(filter []bpf.RawInstruction) error { + // We can't point to the first instruction in the array if no instructions + // are present. + if len(filter) == 0 { + return os.NewSyscallError("setsockopt", unix.EINVAL) + } + + prog := unix.SockFprog{ + Len: uint16(len(filter)), + Filter: (*unix.SockFilter)(unsafe.Pointer(&filter[0])), + } + + return c.SetsockoptSockFprog(unix.SOL_SOCKET, unix.SO_ATTACH_FILTER, &prog) +} + +// RemoveBPF removes a BPF filter from a Conn. +func (c *Conn) RemoveBPF() error { + // 0 argument is ignored. + return c.SetsockoptInt(unix.SOL_SOCKET, unix.SO_DETACH_FILTER, 0) +} + +// SetsockoptPacketMreq wraps setsockopt(2) for unix.PacketMreq values. +func (c *Conn) SetsockoptPacketMreq(level, opt int, mreq *unix.PacketMreq) error { + return c.control(context.Background(), "setsockopt", func(fd int) error { + return unix.SetsockoptPacketMreq(fd, level, opt, mreq) + }) +} + +// SetsockoptSockFprog wraps setsockopt(2) for unix.SockFprog values. +func (c *Conn) SetsockoptSockFprog(level, opt int, fprog *unix.SockFprog) error { + return c.control(context.Background(), "setsockopt", func(fd int) error { + return unix.SetsockoptSockFprog(fd, level, opt, fprog) + }) +} + +// GetsockoptTpacketStats wraps getsockopt(2) for unix.TpacketStats values. +func (c *Conn) GetsockoptTpacketStats(level, name int) (*unix.TpacketStats, error) { + return controlT(c, context.Background(), "getsockopt", func(fd int) (*unix.TpacketStats, error) { + return unix.GetsockoptTpacketStats(fd, level, name) + }) +} + +// GetsockoptTpacketStatsV3 wraps getsockopt(2) for unix.TpacketStatsV3 values. +func (c *Conn) GetsockoptTpacketStatsV3(level, name int) (*unix.TpacketStatsV3, error) { + return controlT(c, context.Background(), "getsockopt", func(fd int) (*unix.TpacketStatsV3, error) { + return unix.GetsockoptTpacketStatsV3(fd, level, name) + }) +} + +// Waitid wraps waitid(2). +func (c *Conn) Waitid(idType int, info *unix.Siginfo, options int, rusage *unix.Rusage) error { + return c.read(context.Background(), "waitid", func(fd int) error { + return unix.Waitid(idType, fd, info, options, rusage) + }) +} diff --git a/vendor/github.com/mdlayher/socket/doc.go b/vendor/github.com/mdlayher/socket/doc.go new file mode 100644 index 0000000000..7d4566c90b --- /dev/null +++ b/vendor/github.com/mdlayher/socket/doc.go @@ -0,0 +1,13 @@ +// Package socket provides a low-level network connection type which integrates +// with Go's runtime network poller to provide asynchronous I/O and deadline +// support. +// +// This package focuses on UNIX-like operating systems which make use of BSD +// sockets system call APIs. It is meant to be used as a foundation for the +// creation of operating system-specific socket packages, for socket families +// such as Linux's AF_NETLINK, AF_PACKET, or AF_VSOCK. This package should not +// be used directly in end user applications. +// +// Any use of package socket should be guarded by build tags, as one would also +// use when importing the syscall or golang.org/x/sys packages. +package socket diff --git a/vendor/github.com/mdlayher/socket/netns_linux.go b/vendor/github.com/mdlayher/socket/netns_linux.go new file mode 100644 index 0000000000..b29115ad1c --- /dev/null +++ b/vendor/github.com/mdlayher/socket/netns_linux.go @@ -0,0 +1,150 @@ +//go:build linux +// +build linux + +package socket + +import ( + "errors" + "fmt" + "os" + "runtime" + + "golang.org/x/sync/errgroup" + "golang.org/x/sys/unix" +) + +// errNetNSDisabled is returned when network namespaces are unavailable on +// a given system. +var errNetNSDisabled = errors.New("socket: Linux network namespaces are not enabled on this system") + +// withNetNS invokes fn within the context of the network namespace specified by +// fd, while also managing the logic required to safely do so by manipulating +// thread-local state. +func withNetNS(fd int, fn func() (*Conn, error)) (*Conn, error) { + var ( + eg errgroup.Group + conn *Conn + ) + + eg.Go(func() error { + // Retrieve and store the calling OS thread's network namespace so the + // thread can be reassigned to it after creating a socket in another network + // namespace. + runtime.LockOSThread() + + ns, err := threadNetNS() + if err != nil { + // No thread-local manipulation, unlock. + runtime.UnlockOSThread() + return err + } + defer ns.Close() + + // Beyond this point, the thread's network namespace is poisoned. Do not + // unlock the OS thread until all network namespace manipulation completes + // to avoid returning to the caller with altered thread-local state. + + // Assign the current OS thread the goroutine is locked to to the given + // network namespace. + if err := ns.Set(fd); err != nil { + return err + } + + // Attempt Conn creation and unconditionally restore the original namespace. + c, err := fn() + if nerr := ns.Restore(); nerr != nil { + // Failed to restore original namespace. Return an error and allow the + // runtime to terminate the thread. + if err == nil { + _ = c.Close() + } + + return nerr + } + + // No more thread-local state manipulation; return the new Conn. + runtime.UnlockOSThread() + conn = c + return nil + }) + + if err := eg.Wait(); err != nil { + return nil, err + } + + return conn, nil +} + +// A netNS is a handle that can manipulate network namespaces. +// +// Operations performed on a netNS must use runtime.LockOSThread before +// manipulating any network namespaces. +type netNS struct { + // The handle to a network namespace. + f *os.File + + // Indicates if network namespaces are disabled on this system, and thus + // operations should become a no-op or return errors. + disabled bool +} + +// threadNetNS constructs a netNS using the network namespace of the calling +// thread. If the namespace is not the default namespace, runtime.LockOSThread +// should be invoked first. +func threadNetNS() (*netNS, error) { + return fileNetNS(fmt.Sprintf("/proc/self/task/%d/ns/net", unix.Gettid())) +} + +// fileNetNS opens file and creates a netNS. fileNetNS should only be called +// directly in tests. +func fileNetNS(file string) (*netNS, error) { + f, err := os.Open(file) + switch { + case err == nil: + return &netNS{f: f}, nil + case os.IsNotExist(err): + // Network namespaces are not enabled on this system. Use this signal + // to return errors elsewhere if the caller explicitly asks for a + // network namespace to be set. + return &netNS{disabled: true}, nil + default: + return nil, err + } +} + +// Close releases the handle to a network namespace. +func (n *netNS) Close() error { + return n.do(func() error { return n.f.Close() }) +} + +// FD returns a file descriptor which represents the network namespace. +func (n *netNS) FD() int { + if n.disabled { + // No reasonable file descriptor value in this case, so specify a + // non-existent one. + return -1 + } + + return int(n.f.Fd()) +} + +// Restore restores the original network namespace for the calling thread. +func (n *netNS) Restore() error { + return n.do(func() error { return n.Set(n.FD()) }) +} + +// Set sets a new network namespace for the current thread using fd. +func (n *netNS) Set(fd int) error { + return n.do(func() error { + return os.NewSyscallError("setns", unix.Setns(fd, unix.CLONE_NEWNET)) + }) +} + +// do runs fn if network namespaces are enabled on this system. +func (n *netNS) do(fn func() error) error { + if n.disabled { + return errNetNSDisabled + } + + return fn() +} diff --git a/vendor/github.com/mdlayher/socket/netns_others.go b/vendor/github.com/mdlayher/socket/netns_others.go new file mode 100644 index 0000000000..4cceb3d047 --- /dev/null +++ b/vendor/github.com/mdlayher/socket/netns_others.go @@ -0,0 +1,14 @@ +//go:build !linux +// +build !linux + +package socket + +import ( + "fmt" + "runtime" +) + +// withNetNS returns an error on non-Linux systems. +func withNetNS(_ int, _ func() (*Conn, error)) (*Conn, error) { + return nil, fmt.Errorf("socket: Linux network namespace support is not available on %s", runtime.GOOS) +} diff --git a/vendor/github.com/mdlayher/socket/setbuffer_linux.go b/vendor/github.com/mdlayher/socket/setbuffer_linux.go new file mode 100644 index 0000000000..0d4aa4417c --- /dev/null +++ b/vendor/github.com/mdlayher/socket/setbuffer_linux.go @@ -0,0 +1,24 @@ +//go:build linux +// +build linux + +package socket + +import "golang.org/x/sys/unix" + +// setReadBuffer wraps the SO_RCVBUF{,FORCE} setsockopt(2) options. +func (c *Conn) setReadBuffer(bytes int) error { + err := c.SetsockoptInt(unix.SOL_SOCKET, unix.SO_RCVBUFFORCE, bytes) + if err != nil { + err = c.SetsockoptInt(unix.SOL_SOCKET, unix.SO_RCVBUF, bytes) + } + return err +} + +// setWriteBuffer wraps the SO_SNDBUF{,FORCE} setsockopt(2) options. +func (c *Conn) setWriteBuffer(bytes int) error { + err := c.SetsockoptInt(unix.SOL_SOCKET, unix.SO_SNDBUFFORCE, bytes) + if err != nil { + err = c.SetsockoptInt(unix.SOL_SOCKET, unix.SO_SNDBUF, bytes) + } + return err +} diff --git a/vendor/github.com/mdlayher/socket/setbuffer_others.go b/vendor/github.com/mdlayher/socket/setbuffer_others.go new file mode 100644 index 0000000000..72b36dbe31 --- /dev/null +++ b/vendor/github.com/mdlayher/socket/setbuffer_others.go @@ -0,0 +1,16 @@ +//go:build !linux +// +build !linux + +package socket + +import "golang.org/x/sys/unix" + +// setReadBuffer wraps the SO_RCVBUF setsockopt(2) option. +func (c *Conn) setReadBuffer(bytes int) error { + return c.SetsockoptInt(unix.SOL_SOCKET, unix.SO_RCVBUF, bytes) +} + +// setWriteBuffer wraps the SO_SNDBUF setsockopt(2) option. +func (c *Conn) setWriteBuffer(bytes int) error { + return c.SetsockoptInt(unix.SOL_SOCKET, unix.SO_SNDBUF, bytes) +} diff --git a/vendor/github.com/mdlayher/socket/typ_cloexec_nonblock.go b/vendor/github.com/mdlayher/socket/typ_cloexec_nonblock.go new file mode 100644 index 0000000000..40e834310b --- /dev/null +++ b/vendor/github.com/mdlayher/socket/typ_cloexec_nonblock.go @@ -0,0 +1,12 @@ +//go:build !darwin +// +build !darwin + +package socket + +import "golang.org/x/sys/unix" + +const ( + // These operating systems support CLOEXEC and NONBLOCK socket options. + flagCLOEXEC = true + socketFlags = unix.SOCK_CLOEXEC | unix.SOCK_NONBLOCK +) diff --git a/vendor/github.com/mdlayher/socket/typ_none.go b/vendor/github.com/mdlayher/socket/typ_none.go new file mode 100644 index 0000000000..9bbb1aab5f --- /dev/null +++ b/vendor/github.com/mdlayher/socket/typ_none.go @@ -0,0 +1,11 @@ +//go:build darwin +// +build darwin + +package socket + +const ( + // These operating systems do not support CLOEXEC and NONBLOCK socket + // options. + flagCLOEXEC = false + socketFlags = 0 +) diff --git a/vendor/github.com/mdlayher/vsock/.gitignore b/vendor/github.com/mdlayher/vsock/.gitignore new file mode 100644 index 0000000000..8130d4158a --- /dev/null +++ b/vendor/github.com/mdlayher/vsock/.gitignore @@ -0,0 +1,4 @@ +cover.out +vsock.test +cmd/vscp/vscp +cmd/vsockhttp/vsockhttp diff --git a/vendor/github.com/mdlayher/vsock/CHANGELOG.md b/vendor/github.com/mdlayher/vsock/CHANGELOG.md new file mode 100644 index 0000000000..c64a797bc2 --- /dev/null +++ b/vendor/github.com/mdlayher/vsock/CHANGELOG.md @@ -0,0 +1,53 @@ +# CHANGELOG + +# v1.2.1 + +- [Improvement]: updated dependencies, test with Go 1.20. + +# v1.2.0 + +**This is the first release of package vsock that only supports Go 1.18+. Users +on older versions of Go must use v1.1.1.** + +- [Improvement]: drop support for older versions of Go so we can begin using + modern versions of `x/sys` and other dependencies. + +## v1.1.1 + +**This is the last release of package vsock that supports Go 1.17 and below.** + +- [Bug Fix] [commit](https://github.com/mdlayher/vsock/commit/ead86435c244d5d6baad549a6df0557ada3f4401): + fix build on non-UNIX platforms such as Windows. This is a no-op change on + Linux but provides a friendlier experience for non-Linux users. + +## v1.1.0 + +- [New API] [commit](https://github.com/mdlayher/vsock/commit/44cd82dc5f7de644436f22236b111ab97fa9a14f): + `vsock.FileListener` can be used to create a `vsock.Listener` from an existing + `os.File`, which may be provided by systemd socket activation or another + external mechanism. + +## v1.0.1 + +- [Bug Fix] [commit](https://github.com/mdlayher/vsock/commit/99a6dccdebad21d1fa5f757d228d677ccb1412dc): + upgrade `github.com/mdlayher/socket` to handle non-blocking `connect(2)` + errors (called in `vsock.Dial`) properly by checking the `SO_ERROR` socket + option. Lock in this behavior with a new test. +- [Improvement] [commit](https://github.com/mdlayher/vsock/commit/375f3bbcc363500daf367ec511638a4655471719): + downgrade the version of `golang.org/x/net` in use to support Go 1.12. We + don't need the latest version for this package. + +## v1.0.0 + +**This is the first release of package vsock that only supports Go 1.12+. +Users on older versions of Go must use an unstable release.** + +- Initial stable commit! +- [API change]: the `vsock.Dial` and `vsock.Listen` constructors now accept an + optional `*vsock.Config` parameter to enable future expansion in v1.x.x + without prompting further breaking API changes. Because `vsock.Config` has no + options as of this release, `nil` may be passed in all call sites to fix + existing code upon upgrading to v1.0.0. +- [New API]: the `vsock.ListenContextID` function can be used to create a + `*vsock.Listener` which is bound to an explicit context ID address, rather + than inferring one automatically as `vsock.Listen` does. diff --git a/vendor/github.com/mdlayher/vsock/LICENSE.md b/vendor/github.com/mdlayher/vsock/LICENSE.md new file mode 100644 index 0000000000..9fa6774b14 --- /dev/null +++ b/vendor/github.com/mdlayher/vsock/LICENSE.md @@ -0,0 +1,9 @@ +# MIT License + +Copyright (C) 2017-2022 Matt Layher + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/mdlayher/vsock/README.md b/vendor/github.com/mdlayher/vsock/README.md new file mode 100644 index 0000000000..b1ec4cfbe1 --- /dev/null +++ b/vendor/github.com/mdlayher/vsock/README.md @@ -0,0 +1,21 @@ +# vsock [![Test Status](https://github.com/mdlayher/vsock/workflows/Linux%20Test/badge.svg)](https://github.com/mdlayher/vsock/actions) [![Go Reference](https://pkg.go.dev/badge/github.com/mdlayher/vsock.svg)](https://pkg.go.dev/github.com/mdlayher/vsock) [![Go Report Card](https://goreportcard.com/badge/github.com/mdlayher/vsock)](https://goreportcard.com/report/github.com/mdlayher/vsock) + +Package `vsock` provides access to Linux VM sockets (`AF_VSOCK`) for +communication between a hypervisor and its virtual machines. MIT Licensed. + +For more information about VM sockets, see my blog about +[Linux VM sockets in Go](https://mdlayher.com/blog/linux-vm-sockets-in-go/) or +the [QEMU wiki page on virtio-vsock](http://wiki.qemu-project.org/Features/VirtioVsock). + +## Stability + +See the [CHANGELOG](./CHANGELOG.md) file for a description of changes between +releases. + +This package has a stable v1 API and any future breaking changes will prompt +the release of a new major version. Features and bug fixes will continue to +occur in the v1.x.x series. + +This package only supports the two most recent major versions of Go, mirroring +Go's own release policy. Older versions of Go may lack critical features and bug +fixes which are necessary for this package to function correctly. diff --git a/vendor/github.com/mdlayher/vsock/conn_linux.go b/vendor/github.com/mdlayher/vsock/conn_linux.go new file mode 100644 index 0000000000..6029d547e5 --- /dev/null +++ b/vendor/github.com/mdlayher/vsock/conn_linux.go @@ -0,0 +1,62 @@ +//go:build linux +// +build linux + +package vsock + +import ( + "context" + + "github.com/mdlayher/socket" + "golang.org/x/sys/unix" +) + +// A conn is the net.Conn implementation for connection-oriented VM sockets. +// We can use socket.Conn directly on Linux to implement all of the necessary +// methods. +type conn = socket.Conn + +// dial is the entry point for Dial on Linux. +func dial(cid, port uint32, _ *Config) (*Conn, error) { + // TODO(mdlayher): Config default nil check and initialize. Pass options to + // socket.Config where necessary. + + c, err := socket.Socket(unix.AF_VSOCK, unix.SOCK_STREAM, 0, "vsock", nil) + if err != nil { + return nil, err + } + + sa := &unix.SockaddrVM{CID: cid, Port: port} + rsa, err := c.Connect(context.Background(), sa) + if err != nil { + _ = c.Close() + return nil, err + } + + // TODO(mdlayher): getpeername(2) appears to return nil in the GitHub CI + // environment, so in the event of a nil sockaddr, fall back to the previous + // method of synthesizing the remote address. + if rsa == nil { + rsa = sa + } + + lsa, err := c.Getsockname() + if err != nil { + _ = c.Close() + return nil, err + } + + lsavm := lsa.(*unix.SockaddrVM) + rsavm := rsa.(*unix.SockaddrVM) + + return &Conn{ + c: c, + local: &Addr{ + ContextID: lsavm.CID, + Port: lsavm.Port, + }, + remote: &Addr{ + ContextID: rsavm.CID, + Port: rsavm.Port, + }, + }, nil +} diff --git a/vendor/github.com/mdlayher/vsock/doc.go b/vendor/github.com/mdlayher/vsock/doc.go new file mode 100644 index 0000000000..e158b18361 --- /dev/null +++ b/vendor/github.com/mdlayher/vsock/doc.go @@ -0,0 +1,10 @@ +// Package vsock provides access to Linux VM sockets (AF_VSOCK) for +// communication between a hypervisor and its virtual machines. +// +// The types in this package implement interfaces provided by package net and +// may be used in applications that expect a net.Listener or net.Conn. +// +// - *Addr implements net.Addr +// - *Conn implements net.Conn +// - *Listener implements net.Listener +package vsock diff --git a/vendor/github.com/mdlayher/vsock/fd_linux.go b/vendor/github.com/mdlayher/vsock/fd_linux.go new file mode 100644 index 0000000000..531e53f928 --- /dev/null +++ b/vendor/github.com/mdlayher/vsock/fd_linux.go @@ -0,0 +1,36 @@ +package vsock + +import ( + "fmt" + "os" + + "golang.org/x/sys/unix" +) + +// contextID retrieves the local context ID for this system. +func contextID() (uint32, error) { + f, err := os.Open(devVsock) + if err != nil { + return 0, err + } + defer f.Close() + + return unix.IoctlGetUint32(int(f.Fd()), unix.IOCTL_VM_SOCKETS_GET_LOCAL_CID) +} + +// isErrno determines if an error a matches UNIX error number. +func isErrno(err error, errno int) bool { + switch errno { + case ebadf: + return err == unix.EBADF + case enotconn: + return err == unix.ENOTCONN + default: + panicf("vsock: isErrno called with unhandled error number parameter: %d", errno) + return false + } +} + +func panicf(format string, a ...interface{}) { + panic(fmt.Sprintf(format, a...)) +} diff --git a/vendor/github.com/mdlayher/vsock/listener_linux.go b/vendor/github.com/mdlayher/vsock/listener_linux.go new file mode 100644 index 0000000000..50fa1b7a49 --- /dev/null +++ b/vendor/github.com/mdlayher/vsock/listener_linux.go @@ -0,0 +1,133 @@ +//go:build linux +// +build linux + +package vsock + +import ( + "context" + "net" + "os" + "time" + + "github.com/mdlayher/socket" + "golang.org/x/sys/unix" +) + +var _ net.Listener = &listener{} + +// A listener is the net.Listener implementation for connection-oriented +// VM sockets. +type listener struct { + c *socket.Conn + addr *Addr +} + +// Addr and Close implement the net.Listener interface for listener. +func (l *listener) Addr() net.Addr { return l.addr } +func (l *listener) Close() error { return l.c.Close() } +func (l *listener) SetDeadline(t time.Time) error { return l.c.SetDeadline(t) } + +// Accept accepts a single connection from the listener, and sets up +// a net.Conn backed by conn. +func (l *listener) Accept() (net.Conn, error) { + c, rsa, err := l.c.Accept(context.Background(), 0) + if err != nil { + return nil, err + } + + savm := rsa.(*unix.SockaddrVM) + remote := &Addr{ + ContextID: savm.CID, + Port: savm.Port, + } + + return &Conn{ + c: c, + local: l.addr, + remote: remote, + }, nil +} + +// name is the socket name passed to package socket. +const name = "vsock" + +// listen is the entry point for Listen on Linux. +func listen(cid, port uint32, _ *Config) (*Listener, error) { + // TODO(mdlayher): Config default nil check and initialize. Pass options to + // socket.Config where necessary. + + c, err := socket.Socket(unix.AF_VSOCK, unix.SOCK_STREAM, 0, name, nil) + if err != nil { + return nil, err + } + + // Be sure to close the Conn if any of the system calls fail before we + // return the Conn to the caller. + + if port == 0 { + port = unix.VMADDR_PORT_ANY + } + + if err := c.Bind(&unix.SockaddrVM{CID: cid, Port: port}); err != nil { + _ = c.Close() + return nil, err + } + + if err := c.Listen(unix.SOMAXCONN); err != nil { + _ = c.Close() + return nil, err + } + + l, err := newListener(c) + if err != nil { + _ = c.Close() + return nil, err + } + + return l, nil +} + +// fileListener is the entry point for FileListener on Linux. +func fileListener(f *os.File) (*Listener, error) { + c, err := socket.FileConn(f, name) + if err != nil { + return nil, err + } + + l, err := newListener(c) + if err != nil { + _ = c.Close() + return nil, err + } + + return l, nil +} + +// newListener creates a Listener from a raw socket.Conn. +func newListener(c *socket.Conn) (*Listener, error) { + lsa, err := c.Getsockname() + if err != nil { + return nil, err + } + + // Now that the library can also accept arbitrary os.Files, we have to + // verify the address family so we don't accidentally create a + // *vsock.Listener backed by TCP or some other socket type. + lsavm, ok := lsa.(*unix.SockaddrVM) + if !ok { + // All errors should wrapped with os.SyscallError. + return nil, os.NewSyscallError("listen", unix.EINVAL) + } + + addr := &Addr{ + ContextID: lsavm.CID, + Port: lsavm.Port, + } + + return &Listener{ + l: &listener{ + c: c, + addr: addr, + }, + }, nil +} diff --git a/vendor/github.com/mdlayher/vsock/vsock.go b/vendor/github.com/mdlayher/vsock/vsock.go new file mode 100644 index 0000000000..78763936ae --- /dev/null +++ b/vendor/github.com/mdlayher/vsock/vsock.go @@ -0,0 +1,435 @@ +package vsock + +import ( + "errors" + "fmt" + "io" + "net" + "os" + "strings" + "syscall" + "time" +) + +const ( + // Hypervisor specifies that a socket should communicate with the hypervisor + // process. Note that this is _not_ the same as a socket owned by a process + // running on the hypervisor. Most users should probably use Host instead. + Hypervisor = 0x0 + + // Local specifies that a socket should communicate with a matching socket + // on the same machine. This provides an alternative to UNIX sockets or + // similar and may be useful in testing VM sockets applications. + Local = 0x1 + + // Host specifies that a socket should communicate with processes other than + // the hypervisor on the host machine. This is the correct choice to + // communicate with a process running on a hypervisor using a socket dialed + // from a guest. + Host = 0x2 + + // Error numbers we recognize, copied here to avoid importing x/sys/unix in + // cross-platform code. + ebadf = 9 + enotconn = 107 + + // devVsock is the location of /dev/vsock. It is exposed on both the + // hypervisor and on virtual machines. + devVsock = "/dev/vsock" + + // network is the vsock network reported in net.OpError. + network = "vsock" + + // Operation names which may be returned in net.OpError. + opAccept = "accept" + opClose = "close" + opDial = "dial" + opListen = "listen" + opRawControl = "raw-control" + opRawRead = "raw-read" + opRawWrite = "raw-write" + opRead = "read" + opSet = "set" + opSyscallConn = "syscall-conn" + opWrite = "write" +) + +// TODO(mdlayher): plumb through socket.Config.NetNS if it makes sense. + +// Config contains options for a Conn or Listener. +type Config struct{} + +// Listen opens a connection-oriented net.Listener for incoming VM sockets +// connections. The port parameter specifies the port for the Listener. Config +// specifies optional configuration for the Listener. If config is nil, a +// default configuration will be used. +// +// To allow the server to assign a port automatically, specify 0 for port. The +// address of the server can be retrieved using the Addr method. +// +// Listen automatically infers the appropriate context ID for this machine by +// calling ContextID and passing that value to ListenContextID. Callers with +// advanced use cases (such as using the Local context ID) may wish to use +// ListenContextID directly. +// +// When the Listener is no longer needed, Close must be called to free +// resources. +func Listen(port uint32, cfg *Config) (*Listener, error) { + cid, err := ContextID() + if err != nil { + // No addresses available. + return nil, opError(opListen, err, nil, nil) + } + + return ListenContextID(cid, port, cfg) +} + +// ListenContextID is the same as Listen, but also accepts an explicit context +// ID parameter. This function is intended for advanced use cases and most +// callers should use Listen instead. +// +// See the documentation of Listen for more details. +func ListenContextID(contextID, port uint32, cfg *Config) (*Listener, error) { + l, err := listen(contextID, port, cfg) + if err != nil { + // No remote address available. + return nil, opError(opListen, err, &Addr{ + ContextID: contextID, + Port: port, + }, nil) + } + + return l, nil +} + +// FileListener returns a copy of the network listener corresponding to an open +// os.File. It is the caller's responsibility to close the Listener when +// finished. Closing the Listener does not affect the os.File, and closing the +// os.File does not affect the Listener. +// +// This function is intended for advanced use cases and most callers should use +// Listen instead. +func FileListener(f *os.File) (*Listener, error) { + l, err := fileListener(f) + if err != nil { + // No addresses available. + return nil, opError(opListen, err, nil, nil) + } + + return l, nil +} + +var _ net.Listener = &Listener{} + +// A Listener is a VM sockets implementation of a net.Listener. +type Listener struct { + l *listener +} + +// Accept implements the Accept method in the net.Listener interface; it waits +// for the next call and returns a generic net.Conn. The returned net.Conn will +// always be of type *Conn. +func (l *Listener) Accept() (net.Conn, error) { + c, err := l.l.Accept() + if err != nil { + return nil, l.opError(opAccept, err) + } + + return c, nil +} + +// Addr returns the listener's network address, a *Addr. The Addr returned is +// shared by all invocations of Addr, so do not modify it. +func (l *Listener) Addr() net.Addr { return l.l.Addr() } + +// Close stops listening on the VM sockets address. Already Accepted connections +// are not closed. +func (l *Listener) Close() error { + return l.opError(opClose, l.l.Close()) +} + +// SetDeadline sets the deadline associated with the listener. A zero time value +// disables the deadline. +func (l *Listener) SetDeadline(t time.Time) error { + return l.opError(opSet, l.l.SetDeadline(t)) +} + +// opError is a convenience for the function opError that also passes the local +// address of the Listener. +func (l *Listener) opError(op string, err error) error { + // No remote address for a Listener. + return opError(op, err, l.Addr(), nil) +} + +// Dial dials a connection-oriented net.Conn to a VM sockets listener. The +// context ID and port parameters specify the address of the listener. Config +// specifies optional configuration for the Conn. If config is nil, a default +// configuration will be used. +// +// If dialing a connection from the hypervisor to a virtual machine, the VM's +// context ID should be specified. +// +// If dialing from a VM to the hypervisor, Hypervisor should be used to +// communicate with the hypervisor process, or Host should be used to +// communicate with other processes on the host machine. +// +// When the connection is no longer needed, Close must be called to free +// resources. +func Dial(contextID, port uint32, cfg *Config) (*Conn, error) { + c, err := dial(contextID, port, cfg) + if err != nil { + // No local address, but we have a remote address we can return. + return nil, opError(opDial, err, nil, &Addr{ + ContextID: contextID, + Port: port, + }) + } + + return c, nil +} + +var ( + _ net.Conn = &Conn{} + _ syscall.Conn = &Conn{} +) + +// A Conn is a VM sockets implementation of a net.Conn. +type Conn struct { + c *conn + local *Addr + remote *Addr +} + +// Close closes the connection. +func (c *Conn) Close() error { + return c.opError(opClose, c.c.Close()) +} + +// CloseRead shuts down the reading side of the VM sockets connection. Most +// callers should just use Close. +func (c *Conn) CloseRead() error { + return c.opError(opClose, c.c.CloseRead()) +} + +// CloseWrite shuts down the writing side of the VM sockets connection. Most +// callers should just use Close. +func (c *Conn) CloseWrite() error { + return c.opError(opClose, c.c.CloseWrite()) +} + +// LocalAddr returns the local network address. The Addr returned is shared by +// all invocations of LocalAddr, so do not modify it. +func (c *Conn) LocalAddr() net.Addr { return c.local } + +// RemoteAddr returns the remote network address. The Addr returned is shared by +// all invocations of RemoteAddr, so do not modify it. +func (c *Conn) RemoteAddr() net.Addr { return c.remote } + +// Read implements the net.Conn Read method. +func (c *Conn) Read(b []byte) (int, error) { + n, err := c.c.Read(b) + if err != nil { + return n, c.opError(opRead, err) + } + + return n, nil +} + +// Write implements the net.Conn Write method. +func (c *Conn) Write(b []byte) (int, error) { + n, err := c.c.Write(b) + if err != nil { + return n, c.opError(opWrite, err) + } + + return n, nil +} + +// SetDeadline implements the net.Conn SetDeadline method. +func (c *Conn) SetDeadline(t time.Time) error { + return c.opError(opSet, c.c.SetDeadline(t)) +} + +// SetReadDeadline implements the net.Conn SetReadDeadline method. +func (c *Conn) SetReadDeadline(t time.Time) error { + return c.opError(opSet, c.c.SetReadDeadline(t)) +} + +// SetWriteDeadline implements the net.Conn SetWriteDeadline method. +func (c *Conn) SetWriteDeadline(t time.Time) error { + return c.opError(opSet, c.c.SetWriteDeadline(t)) +} + +// SyscallConn returns a raw network connection. This implements the +// syscall.Conn interface. +func (c *Conn) SyscallConn() (syscall.RawConn, error) { + rc, err := c.c.SyscallConn() + if err != nil { + return nil, c.opError(opSyscallConn, err) + } + + return &rawConn{ + rc: rc, + local: c.local, + remote: c.remote, + }, nil +} + +// opError is a convenience for the function opError that also passes the local +// and remote addresses of the Conn. +func (c *Conn) opError(op string, err error) error { + return opError(op, err, c.local, c.remote) +} + +// TODO(mdlayher): see if we can port smarter net.OpError with local/remote +// address error logic into socket.Conn's SyscallConn type to avoid the need for +// this wrapper. + +var _ syscall.RawConn = &rawConn{} + +// A rawConn is a syscall.RawConn that wraps an internal syscall.RawConn in order +// to produce net.OpError error values. +type rawConn struct { + rc syscall.RawConn + local, remote *Addr +} + +// Control implements the syscall.RawConn Control method. +func (rc *rawConn) Control(fn func(fd uintptr)) error { + return rc.opError(opRawControl, rc.rc.Control(fn)) +} + +// Control implements the syscall.RawConn Read method. +func (rc *rawConn) Read(fn func(fd uintptr) (done bool)) error { + return rc.opError(opRawRead, rc.rc.Read(fn)) +} + +// Control implements the syscall.RawConn Write method. +func (rc *rawConn) Write(fn func(fd uintptr) (done bool)) error { + return rc.opError(opRawWrite, rc.rc.Write(fn)) +} + +// opError is a convenience for the function opError that also passes the local +// and remote addresses of the rawConn. +func (rc *rawConn) opError(op string, err error) error { + return opError(op, err, rc.local, rc.remote) +} + +var _ net.Addr = &Addr{} + +// An Addr is the address of a VM sockets endpoint. +type Addr struct { + ContextID, Port uint32 +} + +// Network returns the address's network name, "vsock". +func (a *Addr) Network() string { return network } + +// String returns a human-readable representation of Addr, and indicates if +// ContextID is meant to be used for a hypervisor, host, VM, etc. +func (a *Addr) String() string { + var host string + + switch a.ContextID { + case Hypervisor: + host = fmt.Sprintf("hypervisor(%d)", a.ContextID) + case Local: + host = fmt.Sprintf("local(%d)", a.ContextID) + case Host: + host = fmt.Sprintf("host(%d)", a.ContextID) + default: + host = fmt.Sprintf("vm(%d)", a.ContextID) + } + + return fmt.Sprintf("%s:%d", host, a.Port) +} + +// fileName returns a file name for use with os.NewFile for Addr. +func (a *Addr) fileName() string { + return fmt.Sprintf("%s:%s", a.Network(), a.String()) +} + +// ContextID retrieves the local VM sockets context ID for this system. +// ContextID can be used to directly determine if a system is capable of using +// VM sockets. +// +// If the kernel module is unavailable, access to the kernel module is denied, +// or VM sockets are unsupported on this system, it returns an error. +func ContextID() (uint32, error) { + return contextID() +} + +// opError unpacks err if possible, producing a net.OpError with the input +// parameters in order to implement net.Conn. As a convenience, opError returns +// nil if the input error is nil. +func opError(op string, err error, local, remote net.Addr) error { + if err == nil { + return nil + } + + // TODO(mdlayher): this entire function is suspect and should probably be + // looked at carefully, especially with Go 1.13+ error wrapping. + // + // Eventually this *net.OpError logic should probably be ported into + // mdlayher/socket because similar checks are necessary to comply with + // nettest.TestConn. + + // Unwrap inner errors from error types. + // + // TODO(mdlayher): errors.Cause or similar in Go 1.13. + switch xerr := err.(type) { + // os.PathError produced by os.File method calls. + case *os.PathError: + // Although we could make use of xerr.Op here, we're passing it manually + // for consistency, since some of the Conn calls we are making don't + // wrap an os.File, which would return an Op for us. + // + // As a special case, if the error is related to access to the /dev/vsock + // device, we don't unwrap it, so the caller has more context as to why + // their operation actually failed than "permission denied" or similar. + if xerr.Path != devVsock { + err = xerr.Err + } + } + + switch { + case err == io.EOF, isErrno(err, enotconn): + // We may see a literal io.EOF as happens with x/net/nettest, but + // "transport not connected" also means io.EOF in Go. + return io.EOF + case err == os.ErrClosed, isErrno(err, ebadf), strings.Contains(err.Error(), "use of closed"): + // Different operations may return different errors that all effectively + // indicate a closed file. + // + // To rectify the differences, net.TCPConn uses an error with this text + // from internal/poll for the backing file already being closed. + err = errors.New("use of closed network connection") + default: + // Nothing to do, return this directly. + } + + // Determine source and addr using the rules defined by net.OpError's + // documentation: https://golang.org/pkg/net/#OpError. + var source, addr net.Addr + switch op { + case opClose, opDial, opRawRead, opRawWrite, opRead, opWrite: + if local != nil { + source = local + } + if remote != nil { + addr = remote + } + case opAccept, opListen, opRawControl, opSet, opSyscallConn: + if local != nil { + addr = local + } + } + + return &net.OpError{ + Op: op, + Net: network, + Source: source, + Addr: addr, + Err: err, + } +} diff --git a/vendor/github.com/mdlayher/vsock/vsock_others.go b/vendor/github.com/mdlayher/vsock/vsock_others.go new file mode 100644 index 0000000000..5c1e88e398 --- /dev/null +++ b/vendor/github.com/mdlayher/vsock/vsock_others.go @@ -0,0 +1,45 @@ +//go:build !linux +// +build !linux + +package vsock + +import ( + "fmt" + "net" + "os" + "runtime" + "syscall" + "time" +) + +// errUnimplemented is returned by all functions on platforms that +// cannot make use of VM sockets. +var errUnimplemented = fmt.Errorf("vsock: not implemented on %s", runtime.GOOS) + +func fileListener(_ *os.File) (*Listener, error) { return nil, errUnimplemented } +func listen(_, _ uint32, _ *Config) (*Listener, error) { return nil, errUnimplemented } + +type listener struct{} + +func (*listener) Accept() (net.Conn, error) { return nil, errUnimplemented } +func (*listener) Addr() net.Addr { return nil } +func (*listener) Close() error { return errUnimplemented } +func (*listener) SetDeadline(_ time.Time) error { return errUnimplemented } + +func dial(_, _ uint32, _ *Config) (*Conn, error) { return nil, errUnimplemented } + +type conn struct{} + +func (*conn) Close() error { return errUnimplemented } +func (*conn) CloseRead() error { return errUnimplemented } +func (*conn) CloseWrite() error { return errUnimplemented } +func (*conn) Read(_ []byte) (int, error) { return 0, errUnimplemented } +func (*conn) Write(_ []byte) (int, error) { return 0, errUnimplemented } +func (*conn) SetDeadline(_ time.Time) error { return errUnimplemented } +func (*conn) SetReadDeadline(_ time.Time) error { return errUnimplemented } +func (*conn) SetWriteDeadline(_ time.Time) error { return errUnimplemented } +func (*conn) SyscallConn() (syscall.RawConn, error) { return nil, errUnimplemented } + +func contextID() (uint32, error) { return 0, errUnimplemented } + +func isErrno(_ error, _ int) bool { return false } diff --git a/vendor/github.com/u-root/uio/LICENSE b/vendor/github.com/u-root/uio/LICENSE new file mode 100644 index 0000000000..652ff7e777 --- /dev/null +++ b/vendor/github.com/u-root/uio/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2012-2021, u-root Authors +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/u-root/uio/ulog/log.go b/vendor/github.com/u-root/uio/ulog/log.go new file mode 100644 index 0000000000..fbf00f720e --- /dev/null +++ b/vendor/github.com/u-root/uio/ulog/log.go @@ -0,0 +1,31 @@ +// Copyright 2019 the u-root Authors. All rights reserved +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package ulog exposes logging via a Go interface. +// +// ulog has three implementations of the Logger interface: a Go standard +// library "log" package Logger and a test Logger that logs via a test's +// testing.TB.Logf. To use the test logger import "ulog/ulogtest". +package ulog + +import "log" + +// Logger is a log receptacle. +// +// It puts your information somewhere for safekeeping. +type Logger interface { + Printf(format string, v ...interface{}) + Print(v ...interface{}) +} + +// Log is a Logger that prints to the log package's default logger. +var Log Logger = log.Default() + +type emptyLogger struct{} + +func (emptyLogger) Printf(format string, v ...interface{}) {} +func (emptyLogger) Print(v ...interface{}) {} + +// Null is a logger that prints nothing. +var Null Logger = emptyLogger{} diff --git a/vendor/golang.org/x/net/bpf/asm.go b/vendor/golang.org/x/net/bpf/asm.go new file mode 100644 index 0000000000..15e21b1812 --- /dev/null +++ b/vendor/golang.org/x/net/bpf/asm.go @@ -0,0 +1,41 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bpf + +import "fmt" + +// Assemble converts insts into raw instructions suitable for loading +// into a BPF virtual machine. +// +// Currently, no optimization is attempted, the assembled program flow +// is exactly as provided. +func Assemble(insts []Instruction) ([]RawInstruction, error) { + ret := make([]RawInstruction, len(insts)) + var err error + for i, inst := range insts { + ret[i], err = inst.Assemble() + if err != nil { + return nil, fmt.Errorf("assembling instruction %d: %s", i+1, err) + } + } + return ret, nil +} + +// Disassemble attempts to parse raw back into +// Instructions. Unrecognized RawInstructions are assumed to be an +// extension not implemented by this package, and are passed through +// unchanged to the output. The allDecoded value reports whether insts +// contains no RawInstructions. +func Disassemble(raw []RawInstruction) (insts []Instruction, allDecoded bool) { + insts = make([]Instruction, len(raw)) + allDecoded = true + for i, r := range raw { + insts[i] = r.Disassemble() + if _, ok := insts[i].(RawInstruction); ok { + allDecoded = false + } + } + return insts, allDecoded +} diff --git a/vendor/golang.org/x/net/bpf/constants.go b/vendor/golang.org/x/net/bpf/constants.go new file mode 100644 index 0000000000..12f3ee835a --- /dev/null +++ b/vendor/golang.org/x/net/bpf/constants.go @@ -0,0 +1,222 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bpf + +// A Register is a register of the BPF virtual machine. +type Register uint16 + +const ( + // RegA is the accumulator register. RegA is always the + // destination register of ALU operations. + RegA Register = iota + // RegX is the indirection register, used by LoadIndirect + // operations. + RegX +) + +// An ALUOp is an arithmetic or logic operation. +type ALUOp uint16 + +// ALU binary operation types. +const ( + ALUOpAdd ALUOp = iota << 4 + ALUOpSub + ALUOpMul + ALUOpDiv + ALUOpOr + ALUOpAnd + ALUOpShiftLeft + ALUOpShiftRight + aluOpNeg // Not exported because it's the only unary ALU operation, and gets its own instruction type. + ALUOpMod + ALUOpXor +) + +// A JumpTest is a comparison operator used in conditional jumps. +type JumpTest uint16 + +// Supported operators for conditional jumps. +// K can be RegX for JumpIfX +const ( + // K == A + JumpEqual JumpTest = iota + // K != A + JumpNotEqual + // K > A + JumpGreaterThan + // K < A + JumpLessThan + // K >= A + JumpGreaterOrEqual + // K <= A + JumpLessOrEqual + // K & A != 0 + JumpBitsSet + // K & A == 0 + JumpBitsNotSet +) + +// An Extension is a function call provided by the kernel that +// performs advanced operations that are expensive or impossible +// within the BPF virtual machine. +// +// Extensions are only implemented by the Linux kernel. +// +// TODO: should we prune this list? Some of these extensions seem +// either broken or near-impossible to use correctly, whereas other +// (len, random, ifindex) are quite useful. +type Extension int + +// Extension functions available in the Linux kernel. +const ( + // extOffset is the negative maximum number of instructions used + // to load instructions by overloading the K argument. + extOffset = -0x1000 + // ExtLen returns the length of the packet. + ExtLen Extension = 1 + // ExtProto returns the packet's L3 protocol type. + ExtProto Extension = 0 + // ExtType returns the packet's type (skb->pkt_type in the kernel) + // + // TODO: better documentation. How nice an API do we want to + // provide for these esoteric extensions? + ExtType Extension = 4 + // ExtPayloadOffset returns the offset of the packet payload, or + // the first protocol header that the kernel does not know how to + // parse. + ExtPayloadOffset Extension = 52 + // ExtInterfaceIndex returns the index of the interface on which + // the packet was received. + ExtInterfaceIndex Extension = 8 + // ExtNetlinkAttr returns the netlink attribute of type X at + // offset A. + ExtNetlinkAttr Extension = 12 + // ExtNetlinkAttrNested returns the nested netlink attribute of + // type X at offset A. + ExtNetlinkAttrNested Extension = 16 + // ExtMark returns the packet's mark value. + ExtMark Extension = 20 + // ExtQueue returns the packet's assigned hardware queue. + ExtQueue Extension = 24 + // ExtLinkLayerType returns the packet's hardware address type + // (e.g. Ethernet, Infiniband). + ExtLinkLayerType Extension = 28 + // ExtRXHash returns the packets receive hash. + // + // TODO: figure out what this rxhash actually is. + ExtRXHash Extension = 32 + // ExtCPUID returns the ID of the CPU processing the current + // packet. + ExtCPUID Extension = 36 + // ExtVLANTag returns the packet's VLAN tag. + ExtVLANTag Extension = 44 + // ExtVLANTagPresent returns non-zero if the packet has a VLAN + // tag. + // + // TODO: I think this might be a lie: it reads bit 0x1000 of the + // VLAN header, which changed meaning in recent revisions of the + // spec - this extension may now return meaningless information. + ExtVLANTagPresent Extension = 48 + // ExtVLANProto returns 0x8100 if the frame has a VLAN header, + // 0x88a8 if the frame has a "Q-in-Q" double VLAN header, or some + // other value if no VLAN information is present. + ExtVLANProto Extension = 60 + // ExtRand returns a uniformly random uint32. + ExtRand Extension = 56 +) + +// The following gives names to various bit patterns used in opcode construction. + +const ( + opMaskCls uint16 = 0x7 + // opClsLoad masks + opMaskLoadDest = 0x01 + opMaskLoadWidth = 0x18 + opMaskLoadMode = 0xe0 + // opClsALU & opClsJump + opMaskOperand = 0x08 + opMaskOperator = 0xf0 +) + +const ( + // +---------------+-----------------+---+---+---+ + // | AddrMode (3b) | LoadWidth (2b) | 0 | 0 | 0 | + // +---------------+-----------------+---+---+---+ + opClsLoadA uint16 = iota + // +---------------+-----------------+---+---+---+ + // | AddrMode (3b) | LoadWidth (2b) | 0 | 0 | 1 | + // +---------------+-----------------+---+---+---+ + opClsLoadX + // +---+---+---+---+---+---+---+---+ + // | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | + // +---+---+---+---+---+---+---+---+ + opClsStoreA + // +---+---+---+---+---+---+---+---+ + // | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | + // +---+---+---+---+---+---+---+---+ + opClsStoreX + // +---------------+-----------------+---+---+---+ + // | Operator (4b) | OperandSrc (1b) | 1 | 0 | 0 | + // +---------------+-----------------+---+---+---+ + opClsALU + // +-----------------------------+---+---+---+---+ + // | TestOperator (4b) | 0 | 1 | 0 | 1 | + // +-----------------------------+---+---+---+---+ + opClsJump + // +---+-------------------------+---+---+---+---+ + // | 0 | 0 | 0 | RetSrc (1b) | 0 | 1 | 1 | 0 | + // +---+-------------------------+---+---+---+---+ + opClsReturn + // +---+-------------------------+---+---+---+---+ + // | 0 | 0 | 0 | TXAorTAX (1b) | 0 | 1 | 1 | 1 | + // +---+-------------------------+---+---+---+---+ + opClsMisc +) + +const ( + opAddrModeImmediate uint16 = iota << 5 + opAddrModeAbsolute + opAddrModeIndirect + opAddrModeScratch + opAddrModePacketLen // actually an extension, not an addressing mode. + opAddrModeMemShift +) + +const ( + opLoadWidth4 uint16 = iota << 3 + opLoadWidth2 + opLoadWidth1 +) + +// Operand for ALU and Jump instructions +type opOperand uint16 + +// Supported operand sources. +const ( + opOperandConstant opOperand = iota << 3 + opOperandX +) + +// An jumpOp is a conditional jump condition. +type jumpOp uint16 + +// Supported jump conditions. +const ( + opJumpAlways jumpOp = iota << 4 + opJumpEqual + opJumpGT + opJumpGE + opJumpSet +) + +const ( + opRetSrcConstant uint16 = iota << 4 + opRetSrcA +) + +const ( + opMiscTAX = 0x00 + opMiscTXA = 0x80 +) diff --git a/vendor/golang.org/x/net/bpf/doc.go b/vendor/golang.org/x/net/bpf/doc.go new file mode 100644 index 0000000000..04ec1c8ab5 --- /dev/null +++ b/vendor/golang.org/x/net/bpf/doc.go @@ -0,0 +1,80 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* +Package bpf implements marshaling and unmarshaling of programs for the +Berkeley Packet Filter virtual machine, and provides a Go implementation +of the virtual machine. + +BPF's main use is to specify a packet filter for network taps, so that +the kernel doesn't have to expensively copy every packet it sees to +userspace. However, it's been repurposed to other areas where running +user code in-kernel is needed. For example, Linux's seccomp uses BPF +to apply security policies to system calls. For simplicity, this +documentation refers only to packets, but other uses of BPF have their +own data payloads. + +BPF programs run in a restricted virtual machine. It has almost no +access to kernel functions, and while conditional branches are +allowed, they can only jump forwards, to guarantee that there are no +infinite loops. + +# The virtual machine + +The BPF VM is an accumulator machine. Its main register, called +register A, is an implicit source and destination in all arithmetic +and logic operations. The machine also has 16 scratch registers for +temporary storage, and an indirection register (register X) for +indirect memory access. All registers are 32 bits wide. + +Each run of a BPF program is given one packet, which is placed in the +VM's read-only "main memory". LoadAbsolute and LoadIndirect +instructions can fetch up to 32 bits at a time into register A for +examination. + +The goal of a BPF program is to produce and return a verdict (uint32), +which tells the kernel what to do with the packet. In the context of +packet filtering, the returned value is the number of bytes of the +packet to forward to userspace, or 0 to ignore the packet. Other +contexts like seccomp define their own return values. + +In order to simplify programs, attempts to read past the end of the +packet terminate the program execution with a verdict of 0 (ignore +packet). This means that the vast majority of BPF programs don't need +to do any explicit bounds checking. + +In addition to the bytes of the packet, some BPF programs have access +to extensions, which are essentially calls to kernel utility +functions. Currently, the only extensions supported by this package +are the Linux packet filter extensions. + +# Examples + +This packet filter selects all ARP packets. + + bpf.Assemble([]bpf.Instruction{ + // Load "EtherType" field from the ethernet header. + bpf.LoadAbsolute{Off: 12, Size: 2}, + // Skip over the next instruction if EtherType is not ARP. + bpf.JumpIf{Cond: bpf.JumpNotEqual, Val: 0x0806, SkipTrue: 1}, + // Verdict is "send up to 4k of the packet to userspace." + bpf.RetConstant{Val: 4096}, + // Verdict is "ignore packet." + bpf.RetConstant{Val: 0}, + }) + +This packet filter captures a random 1% sample of traffic. + + bpf.Assemble([]bpf.Instruction{ + // Get a 32-bit random number from the Linux kernel. + bpf.LoadExtension{Num: bpf.ExtRand}, + // 1% dice roll? + bpf.JumpIf{Cond: bpf.JumpLessThan, Val: 2^32/100, SkipFalse: 1}, + // Capture. + bpf.RetConstant{Val: 4096}, + // Ignore. + bpf.RetConstant{Val: 0}, + }) +*/ +package bpf // import "golang.org/x/net/bpf" diff --git a/vendor/golang.org/x/net/bpf/instructions.go b/vendor/golang.org/x/net/bpf/instructions.go new file mode 100644 index 0000000000..3cffcaa014 --- /dev/null +++ b/vendor/golang.org/x/net/bpf/instructions.go @@ -0,0 +1,726 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bpf + +import "fmt" + +// An Instruction is one instruction executed by the BPF virtual +// machine. +type Instruction interface { + // Assemble assembles the Instruction into a RawInstruction. + Assemble() (RawInstruction, error) +} + +// A RawInstruction is a raw BPF virtual machine instruction. +type RawInstruction struct { + // Operation to execute. + Op uint16 + // For conditional jump instructions, the number of instructions + // to skip if the condition is true/false. + Jt uint8 + Jf uint8 + // Constant parameter. The meaning depends on the Op. + K uint32 +} + +// Assemble implements the Instruction Assemble method. +func (ri RawInstruction) Assemble() (RawInstruction, error) { return ri, nil } + +// Disassemble parses ri into an Instruction and returns it. If ri is +// not recognized by this package, ri itself is returned. +func (ri RawInstruction) Disassemble() Instruction { + switch ri.Op & opMaskCls { + case opClsLoadA, opClsLoadX: + reg := Register(ri.Op & opMaskLoadDest) + sz := 0 + switch ri.Op & opMaskLoadWidth { + case opLoadWidth4: + sz = 4 + case opLoadWidth2: + sz = 2 + case opLoadWidth1: + sz = 1 + default: + return ri + } + switch ri.Op & opMaskLoadMode { + case opAddrModeImmediate: + if sz != 4 { + return ri + } + return LoadConstant{Dst: reg, Val: ri.K} + case opAddrModeScratch: + if sz != 4 || ri.K > 15 { + return ri + } + return LoadScratch{Dst: reg, N: int(ri.K)} + case opAddrModeAbsolute: + if ri.K > extOffset+0xffffffff { + return LoadExtension{Num: Extension(-extOffset + ri.K)} + } + return LoadAbsolute{Size: sz, Off: ri.K} + case opAddrModeIndirect: + return LoadIndirect{Size: sz, Off: ri.K} + case opAddrModePacketLen: + if sz != 4 { + return ri + } + return LoadExtension{Num: ExtLen} + case opAddrModeMemShift: + return LoadMemShift{Off: ri.K} + default: + return ri + } + + case opClsStoreA: + if ri.Op != opClsStoreA || ri.K > 15 { + return ri + } + return StoreScratch{Src: RegA, N: int(ri.K)} + + case opClsStoreX: + if ri.Op != opClsStoreX || ri.K > 15 { + return ri + } + return StoreScratch{Src: RegX, N: int(ri.K)} + + case opClsALU: + switch op := ALUOp(ri.Op & opMaskOperator); op { + case ALUOpAdd, ALUOpSub, ALUOpMul, ALUOpDiv, ALUOpOr, ALUOpAnd, ALUOpShiftLeft, ALUOpShiftRight, ALUOpMod, ALUOpXor: + switch operand := opOperand(ri.Op & opMaskOperand); operand { + case opOperandX: + return ALUOpX{Op: op} + case opOperandConstant: + return ALUOpConstant{Op: op, Val: ri.K} + default: + return ri + } + case aluOpNeg: + return NegateA{} + default: + return ri + } + + case opClsJump: + switch op := jumpOp(ri.Op & opMaskOperator); op { + case opJumpAlways: + return Jump{Skip: ri.K} + case opJumpEqual, opJumpGT, opJumpGE, opJumpSet: + cond, skipTrue, skipFalse := jumpOpToTest(op, ri.Jt, ri.Jf) + switch operand := opOperand(ri.Op & opMaskOperand); operand { + case opOperandX: + return JumpIfX{Cond: cond, SkipTrue: skipTrue, SkipFalse: skipFalse} + case opOperandConstant: + return JumpIf{Cond: cond, Val: ri.K, SkipTrue: skipTrue, SkipFalse: skipFalse} + default: + return ri + } + default: + return ri + } + + case opClsReturn: + switch ri.Op { + case opClsReturn | opRetSrcA: + return RetA{} + case opClsReturn | opRetSrcConstant: + return RetConstant{Val: ri.K} + default: + return ri + } + + case opClsMisc: + switch ri.Op { + case opClsMisc | opMiscTAX: + return TAX{} + case opClsMisc | opMiscTXA: + return TXA{} + default: + return ri + } + + default: + panic("unreachable") // switch is exhaustive on the bit pattern + } +} + +func jumpOpToTest(op jumpOp, skipTrue uint8, skipFalse uint8) (JumpTest, uint8, uint8) { + var test JumpTest + + // Decode "fake" jump conditions that don't appear in machine code + // Ensures the Assemble -> Disassemble stage recreates the same instructions + // See https://github.com/golang/go/issues/18470 + if skipTrue == 0 { + switch op { + case opJumpEqual: + test = JumpNotEqual + case opJumpGT: + test = JumpLessOrEqual + case opJumpGE: + test = JumpLessThan + case opJumpSet: + test = JumpBitsNotSet + } + + return test, skipFalse, 0 + } + + switch op { + case opJumpEqual: + test = JumpEqual + case opJumpGT: + test = JumpGreaterThan + case opJumpGE: + test = JumpGreaterOrEqual + case opJumpSet: + test = JumpBitsSet + } + + return test, skipTrue, skipFalse +} + +// LoadConstant loads Val into register Dst. +type LoadConstant struct { + Dst Register + Val uint32 +} + +// Assemble implements the Instruction Assemble method. +func (a LoadConstant) Assemble() (RawInstruction, error) { + return assembleLoad(a.Dst, 4, opAddrModeImmediate, a.Val) +} + +// String returns the instruction in assembler notation. +func (a LoadConstant) String() string { + switch a.Dst { + case RegA: + return fmt.Sprintf("ld #%d", a.Val) + case RegX: + return fmt.Sprintf("ldx #%d", a.Val) + default: + return fmt.Sprintf("unknown instruction: %#v", a) + } +} + +// LoadScratch loads scratch[N] into register Dst. +type LoadScratch struct { + Dst Register + N int // 0-15 +} + +// Assemble implements the Instruction Assemble method. +func (a LoadScratch) Assemble() (RawInstruction, error) { + if a.N < 0 || a.N > 15 { + return RawInstruction{}, fmt.Errorf("invalid scratch slot %d", a.N) + } + return assembleLoad(a.Dst, 4, opAddrModeScratch, uint32(a.N)) +} + +// String returns the instruction in assembler notation. +func (a LoadScratch) String() string { + switch a.Dst { + case RegA: + return fmt.Sprintf("ld M[%d]", a.N) + case RegX: + return fmt.Sprintf("ldx M[%d]", a.N) + default: + return fmt.Sprintf("unknown instruction: %#v", a) + } +} + +// LoadAbsolute loads packet[Off:Off+Size] as an integer value into +// register A. +type LoadAbsolute struct { + Off uint32 + Size int // 1, 2 or 4 +} + +// Assemble implements the Instruction Assemble method. +func (a LoadAbsolute) Assemble() (RawInstruction, error) { + return assembleLoad(RegA, a.Size, opAddrModeAbsolute, a.Off) +} + +// String returns the instruction in assembler notation. +func (a LoadAbsolute) String() string { + switch a.Size { + case 1: // byte + return fmt.Sprintf("ldb [%d]", a.Off) + case 2: // half word + return fmt.Sprintf("ldh [%d]", a.Off) + case 4: // word + if a.Off > extOffset+0xffffffff { + return LoadExtension{Num: Extension(a.Off + 0x1000)}.String() + } + return fmt.Sprintf("ld [%d]", a.Off) + default: + return fmt.Sprintf("unknown instruction: %#v", a) + } +} + +// LoadIndirect loads packet[X+Off:X+Off+Size] as an integer value +// into register A. +type LoadIndirect struct { + Off uint32 + Size int // 1, 2 or 4 +} + +// Assemble implements the Instruction Assemble method. +func (a LoadIndirect) Assemble() (RawInstruction, error) { + return assembleLoad(RegA, a.Size, opAddrModeIndirect, a.Off) +} + +// String returns the instruction in assembler notation. +func (a LoadIndirect) String() string { + switch a.Size { + case 1: // byte + return fmt.Sprintf("ldb [x + %d]", a.Off) + case 2: // half word + return fmt.Sprintf("ldh [x + %d]", a.Off) + case 4: // word + return fmt.Sprintf("ld [x + %d]", a.Off) + default: + return fmt.Sprintf("unknown instruction: %#v", a) + } +} + +// LoadMemShift multiplies the first 4 bits of the byte at packet[Off] +// by 4 and stores the result in register X. +// +// This instruction is mainly useful to load into X the length of an +// IPv4 packet header in a single instruction, rather than have to do +// the arithmetic on the header's first byte by hand. +type LoadMemShift struct { + Off uint32 +} + +// Assemble implements the Instruction Assemble method. +func (a LoadMemShift) Assemble() (RawInstruction, error) { + return assembleLoad(RegX, 1, opAddrModeMemShift, a.Off) +} + +// String returns the instruction in assembler notation. +func (a LoadMemShift) String() string { + return fmt.Sprintf("ldx 4*([%d]&0xf)", a.Off) +} + +// LoadExtension invokes a linux-specific extension and stores the +// result in register A. +type LoadExtension struct { + Num Extension +} + +// Assemble implements the Instruction Assemble method. +func (a LoadExtension) Assemble() (RawInstruction, error) { + if a.Num == ExtLen { + return assembleLoad(RegA, 4, opAddrModePacketLen, 0) + } + return assembleLoad(RegA, 4, opAddrModeAbsolute, uint32(extOffset+a.Num)) +} + +// String returns the instruction in assembler notation. +func (a LoadExtension) String() string { + switch a.Num { + case ExtLen: + return "ld #len" + case ExtProto: + return "ld #proto" + case ExtType: + return "ld #type" + case ExtPayloadOffset: + return "ld #poff" + case ExtInterfaceIndex: + return "ld #ifidx" + case ExtNetlinkAttr: + return "ld #nla" + case ExtNetlinkAttrNested: + return "ld #nlan" + case ExtMark: + return "ld #mark" + case ExtQueue: + return "ld #queue" + case ExtLinkLayerType: + return "ld #hatype" + case ExtRXHash: + return "ld #rxhash" + case ExtCPUID: + return "ld #cpu" + case ExtVLANTag: + return "ld #vlan_tci" + case ExtVLANTagPresent: + return "ld #vlan_avail" + case ExtVLANProto: + return "ld #vlan_tpid" + case ExtRand: + return "ld #rand" + default: + return fmt.Sprintf("unknown instruction: %#v", a) + } +} + +// StoreScratch stores register Src into scratch[N]. +type StoreScratch struct { + Src Register + N int // 0-15 +} + +// Assemble implements the Instruction Assemble method. +func (a StoreScratch) Assemble() (RawInstruction, error) { + if a.N < 0 || a.N > 15 { + return RawInstruction{}, fmt.Errorf("invalid scratch slot %d", a.N) + } + var op uint16 + switch a.Src { + case RegA: + op = opClsStoreA + case RegX: + op = opClsStoreX + default: + return RawInstruction{}, fmt.Errorf("invalid source register %v", a.Src) + } + + return RawInstruction{ + Op: op, + K: uint32(a.N), + }, nil +} + +// String returns the instruction in assembler notation. +func (a StoreScratch) String() string { + switch a.Src { + case RegA: + return fmt.Sprintf("st M[%d]", a.N) + case RegX: + return fmt.Sprintf("stx M[%d]", a.N) + default: + return fmt.Sprintf("unknown instruction: %#v", a) + } +} + +// ALUOpConstant executes A = A Val. +type ALUOpConstant struct { + Op ALUOp + Val uint32 +} + +// Assemble implements the Instruction Assemble method. +func (a ALUOpConstant) Assemble() (RawInstruction, error) { + return RawInstruction{ + Op: opClsALU | uint16(opOperandConstant) | uint16(a.Op), + K: a.Val, + }, nil +} + +// String returns the instruction in assembler notation. +func (a ALUOpConstant) String() string { + switch a.Op { + case ALUOpAdd: + return fmt.Sprintf("add #%d", a.Val) + case ALUOpSub: + return fmt.Sprintf("sub #%d", a.Val) + case ALUOpMul: + return fmt.Sprintf("mul #%d", a.Val) + case ALUOpDiv: + return fmt.Sprintf("div #%d", a.Val) + case ALUOpMod: + return fmt.Sprintf("mod #%d", a.Val) + case ALUOpAnd: + return fmt.Sprintf("and #%d", a.Val) + case ALUOpOr: + return fmt.Sprintf("or #%d", a.Val) + case ALUOpXor: + return fmt.Sprintf("xor #%d", a.Val) + case ALUOpShiftLeft: + return fmt.Sprintf("lsh #%d", a.Val) + case ALUOpShiftRight: + return fmt.Sprintf("rsh #%d", a.Val) + default: + return fmt.Sprintf("unknown instruction: %#v", a) + } +} + +// ALUOpX executes A = A X +type ALUOpX struct { + Op ALUOp +} + +// Assemble implements the Instruction Assemble method. +func (a ALUOpX) Assemble() (RawInstruction, error) { + return RawInstruction{ + Op: opClsALU | uint16(opOperandX) | uint16(a.Op), + }, nil +} + +// String returns the instruction in assembler notation. +func (a ALUOpX) String() string { + switch a.Op { + case ALUOpAdd: + return "add x" + case ALUOpSub: + return "sub x" + case ALUOpMul: + return "mul x" + case ALUOpDiv: + return "div x" + case ALUOpMod: + return "mod x" + case ALUOpAnd: + return "and x" + case ALUOpOr: + return "or x" + case ALUOpXor: + return "xor x" + case ALUOpShiftLeft: + return "lsh x" + case ALUOpShiftRight: + return "rsh x" + default: + return fmt.Sprintf("unknown instruction: %#v", a) + } +} + +// NegateA executes A = -A. +type NegateA struct{} + +// Assemble implements the Instruction Assemble method. +func (a NegateA) Assemble() (RawInstruction, error) { + return RawInstruction{ + Op: opClsALU | uint16(aluOpNeg), + }, nil +} + +// String returns the instruction in assembler notation. +func (a NegateA) String() string { + return fmt.Sprintf("neg") +} + +// Jump skips the following Skip instructions in the program. +type Jump struct { + Skip uint32 +} + +// Assemble implements the Instruction Assemble method. +func (a Jump) Assemble() (RawInstruction, error) { + return RawInstruction{ + Op: opClsJump | uint16(opJumpAlways), + K: a.Skip, + }, nil +} + +// String returns the instruction in assembler notation. +func (a Jump) String() string { + return fmt.Sprintf("ja %d", a.Skip) +} + +// JumpIf skips the following Skip instructions in the program if A +// Val is true. +type JumpIf struct { + Cond JumpTest + Val uint32 + SkipTrue uint8 + SkipFalse uint8 +} + +// Assemble implements the Instruction Assemble method. +func (a JumpIf) Assemble() (RawInstruction, error) { + return jumpToRaw(a.Cond, opOperandConstant, a.Val, a.SkipTrue, a.SkipFalse) +} + +// String returns the instruction in assembler notation. +func (a JumpIf) String() string { + return jumpToString(a.Cond, fmt.Sprintf("#%d", a.Val), a.SkipTrue, a.SkipFalse) +} + +// JumpIfX skips the following Skip instructions in the program if A +// X is true. +type JumpIfX struct { + Cond JumpTest + SkipTrue uint8 + SkipFalse uint8 +} + +// Assemble implements the Instruction Assemble method. +func (a JumpIfX) Assemble() (RawInstruction, error) { + return jumpToRaw(a.Cond, opOperandX, 0, a.SkipTrue, a.SkipFalse) +} + +// String returns the instruction in assembler notation. +func (a JumpIfX) String() string { + return jumpToString(a.Cond, "x", a.SkipTrue, a.SkipFalse) +} + +// jumpToRaw assembles a jump instruction into a RawInstruction +func jumpToRaw(test JumpTest, operand opOperand, k uint32, skipTrue, skipFalse uint8) (RawInstruction, error) { + var ( + cond jumpOp + flip bool + ) + switch test { + case JumpEqual: + cond = opJumpEqual + case JumpNotEqual: + cond, flip = opJumpEqual, true + case JumpGreaterThan: + cond = opJumpGT + case JumpLessThan: + cond, flip = opJumpGE, true + case JumpGreaterOrEqual: + cond = opJumpGE + case JumpLessOrEqual: + cond, flip = opJumpGT, true + case JumpBitsSet: + cond = opJumpSet + case JumpBitsNotSet: + cond, flip = opJumpSet, true + default: + return RawInstruction{}, fmt.Errorf("unknown JumpTest %v", test) + } + jt, jf := skipTrue, skipFalse + if flip { + jt, jf = jf, jt + } + return RawInstruction{ + Op: opClsJump | uint16(cond) | uint16(operand), + Jt: jt, + Jf: jf, + K: k, + }, nil +} + +// jumpToString converts a jump instruction to assembler notation +func jumpToString(cond JumpTest, operand string, skipTrue, skipFalse uint8) string { + switch cond { + // K == A + case JumpEqual: + return conditionalJump(operand, skipTrue, skipFalse, "jeq", "jneq") + // K != A + case JumpNotEqual: + return fmt.Sprintf("jneq %s,%d", operand, skipTrue) + // K > A + case JumpGreaterThan: + return conditionalJump(operand, skipTrue, skipFalse, "jgt", "jle") + // K < A + case JumpLessThan: + return fmt.Sprintf("jlt %s,%d", operand, skipTrue) + // K >= A + case JumpGreaterOrEqual: + return conditionalJump(operand, skipTrue, skipFalse, "jge", "jlt") + // K <= A + case JumpLessOrEqual: + return fmt.Sprintf("jle %s,%d", operand, skipTrue) + // K & A != 0 + case JumpBitsSet: + if skipFalse > 0 { + return fmt.Sprintf("jset %s,%d,%d", operand, skipTrue, skipFalse) + } + return fmt.Sprintf("jset %s,%d", operand, skipTrue) + // K & A == 0, there is no assembler instruction for JumpBitNotSet, use JumpBitSet and invert skips + case JumpBitsNotSet: + return jumpToString(JumpBitsSet, operand, skipFalse, skipTrue) + default: + return fmt.Sprintf("unknown JumpTest %#v", cond) + } +} + +func conditionalJump(operand string, skipTrue, skipFalse uint8, positiveJump, negativeJump string) string { + if skipTrue > 0 { + if skipFalse > 0 { + return fmt.Sprintf("%s %s,%d,%d", positiveJump, operand, skipTrue, skipFalse) + } + return fmt.Sprintf("%s %s,%d", positiveJump, operand, skipTrue) + } + return fmt.Sprintf("%s %s,%d", negativeJump, operand, skipFalse) +} + +// RetA exits the BPF program, returning the value of register A. +type RetA struct{} + +// Assemble implements the Instruction Assemble method. +func (a RetA) Assemble() (RawInstruction, error) { + return RawInstruction{ + Op: opClsReturn | opRetSrcA, + }, nil +} + +// String returns the instruction in assembler notation. +func (a RetA) String() string { + return fmt.Sprintf("ret a") +} + +// RetConstant exits the BPF program, returning a constant value. +type RetConstant struct { + Val uint32 +} + +// Assemble implements the Instruction Assemble method. +func (a RetConstant) Assemble() (RawInstruction, error) { + return RawInstruction{ + Op: opClsReturn | opRetSrcConstant, + K: a.Val, + }, nil +} + +// String returns the instruction in assembler notation. +func (a RetConstant) String() string { + return fmt.Sprintf("ret #%d", a.Val) +} + +// TXA copies the value of register X to register A. +type TXA struct{} + +// Assemble implements the Instruction Assemble method. +func (a TXA) Assemble() (RawInstruction, error) { + return RawInstruction{ + Op: opClsMisc | opMiscTXA, + }, nil +} + +// String returns the instruction in assembler notation. +func (a TXA) String() string { + return fmt.Sprintf("txa") +} + +// TAX copies the value of register A to register X. +type TAX struct{} + +// Assemble implements the Instruction Assemble method. +func (a TAX) Assemble() (RawInstruction, error) { + return RawInstruction{ + Op: opClsMisc | opMiscTAX, + }, nil +} + +// String returns the instruction in assembler notation. +func (a TAX) String() string { + return fmt.Sprintf("tax") +} + +func assembleLoad(dst Register, loadSize int, mode uint16, k uint32) (RawInstruction, error) { + var ( + cls uint16 + sz uint16 + ) + switch dst { + case RegA: + cls = opClsLoadA + case RegX: + cls = opClsLoadX + default: + return RawInstruction{}, fmt.Errorf("invalid target register %v", dst) + } + switch loadSize { + case 1: + sz = opLoadWidth1 + case 2: + sz = opLoadWidth2 + case 4: + sz = opLoadWidth4 + default: + return RawInstruction{}, fmt.Errorf("invalid load byte length %d", sz) + } + return RawInstruction{ + Op: cls | sz | mode, + K: k, + }, nil +} diff --git a/vendor/golang.org/x/net/bpf/setter.go b/vendor/golang.org/x/net/bpf/setter.go new file mode 100644 index 0000000000..43e35f0ac2 --- /dev/null +++ b/vendor/golang.org/x/net/bpf/setter.go @@ -0,0 +1,10 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bpf + +// A Setter is a type which can attach a compiled BPF filter to itself. +type Setter interface { + SetBPF(filter []RawInstruction) error +} diff --git a/vendor/golang.org/x/net/bpf/vm.go b/vendor/golang.org/x/net/bpf/vm.go new file mode 100644 index 0000000000..73f57f1f72 --- /dev/null +++ b/vendor/golang.org/x/net/bpf/vm.go @@ -0,0 +1,150 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bpf + +import ( + "errors" + "fmt" +) + +// A VM is an emulated BPF virtual machine. +type VM struct { + filter []Instruction +} + +// NewVM returns a new VM using the input BPF program. +func NewVM(filter []Instruction) (*VM, error) { + if len(filter) == 0 { + return nil, errors.New("one or more Instructions must be specified") + } + + for i, ins := range filter { + check := len(filter) - (i + 1) + switch ins := ins.(type) { + // Check for out-of-bounds jumps in instructions + case Jump: + if check <= int(ins.Skip) { + return nil, fmt.Errorf("cannot jump %d instructions; jumping past program bounds", ins.Skip) + } + case JumpIf: + if check <= int(ins.SkipTrue) { + return nil, fmt.Errorf("cannot jump %d instructions in true case; jumping past program bounds", ins.SkipTrue) + } + if check <= int(ins.SkipFalse) { + return nil, fmt.Errorf("cannot jump %d instructions in false case; jumping past program bounds", ins.SkipFalse) + } + case JumpIfX: + if check <= int(ins.SkipTrue) { + return nil, fmt.Errorf("cannot jump %d instructions in true case; jumping past program bounds", ins.SkipTrue) + } + if check <= int(ins.SkipFalse) { + return nil, fmt.Errorf("cannot jump %d instructions in false case; jumping past program bounds", ins.SkipFalse) + } + // Check for division or modulus by zero + case ALUOpConstant: + if ins.Val != 0 { + break + } + + switch ins.Op { + case ALUOpDiv, ALUOpMod: + return nil, errors.New("cannot divide by zero using ALUOpConstant") + } + // Check for unknown extensions + case LoadExtension: + switch ins.Num { + case ExtLen: + default: + return nil, fmt.Errorf("extension %d not implemented", ins.Num) + } + } + } + + // Make sure last instruction is a return instruction + switch filter[len(filter)-1].(type) { + case RetA, RetConstant: + default: + return nil, errors.New("BPF program must end with RetA or RetConstant") + } + + // Though our VM works using disassembled instructions, we + // attempt to assemble the input filter anyway to ensure it is compatible + // with an operating system VM. + _, err := Assemble(filter) + + return &VM{ + filter: filter, + }, err +} + +// Run runs the VM's BPF program against the input bytes. +// Run returns the number of bytes accepted by the BPF program, and any errors +// which occurred while processing the program. +func (v *VM) Run(in []byte) (int, error) { + var ( + // Registers of the virtual machine + regA uint32 + regX uint32 + regScratch [16]uint32 + + // OK is true if the program should continue processing the next + // instruction, or false if not, causing the loop to break + ok = true + ) + + // TODO(mdlayher): implement: + // - NegateA: + // - would require a change from uint32 registers to int32 + // registers + + // TODO(mdlayher): add interop tests that check signedness of ALU + // operations against kernel implementation, and make sure Go + // implementation matches behavior + + for i := 0; i < len(v.filter) && ok; i++ { + ins := v.filter[i] + + switch ins := ins.(type) { + case ALUOpConstant: + regA = aluOpConstant(ins, regA) + case ALUOpX: + regA, ok = aluOpX(ins, regA, regX) + case Jump: + i += int(ins.Skip) + case JumpIf: + jump := jumpIf(ins, regA) + i += jump + case JumpIfX: + jump := jumpIfX(ins, regA, regX) + i += jump + case LoadAbsolute: + regA, ok = loadAbsolute(ins, in) + case LoadConstant: + regA, regX = loadConstant(ins, regA, regX) + case LoadExtension: + regA = loadExtension(ins, in) + case LoadIndirect: + regA, ok = loadIndirect(ins, in, regX) + case LoadMemShift: + regX, ok = loadMemShift(ins, in) + case LoadScratch: + regA, regX = loadScratch(ins, regScratch, regA, regX) + case RetA: + return int(regA), nil + case RetConstant: + return int(ins.Val), nil + case StoreScratch: + regScratch = storeScratch(ins, regScratch, regA, regX) + case TAX: + regX = regA + case TXA: + regA = regX + default: + return 0, fmt.Errorf("unknown Instruction at index %d: %T", i, ins) + } + } + + return 0, nil +} diff --git a/vendor/golang.org/x/net/bpf/vm_instructions.go b/vendor/golang.org/x/net/bpf/vm_instructions.go new file mode 100644 index 0000000000..0aa307c061 --- /dev/null +++ b/vendor/golang.org/x/net/bpf/vm_instructions.go @@ -0,0 +1,182 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bpf + +import ( + "encoding/binary" + "fmt" +) + +func aluOpConstant(ins ALUOpConstant, regA uint32) uint32 { + return aluOpCommon(ins.Op, regA, ins.Val) +} + +func aluOpX(ins ALUOpX, regA uint32, regX uint32) (uint32, bool) { + // Guard against division or modulus by zero by terminating + // the program, as the OS BPF VM does + if regX == 0 { + switch ins.Op { + case ALUOpDiv, ALUOpMod: + return 0, false + } + } + + return aluOpCommon(ins.Op, regA, regX), true +} + +func aluOpCommon(op ALUOp, regA uint32, value uint32) uint32 { + switch op { + case ALUOpAdd: + return regA + value + case ALUOpSub: + return regA - value + case ALUOpMul: + return regA * value + case ALUOpDiv: + // Division by zero not permitted by NewVM and aluOpX checks + return regA / value + case ALUOpOr: + return regA | value + case ALUOpAnd: + return regA & value + case ALUOpShiftLeft: + return regA << value + case ALUOpShiftRight: + return regA >> value + case ALUOpMod: + // Modulus by zero not permitted by NewVM and aluOpX checks + return regA % value + case ALUOpXor: + return regA ^ value + default: + return regA + } +} + +func jumpIf(ins JumpIf, regA uint32) int { + return jumpIfCommon(ins.Cond, ins.SkipTrue, ins.SkipFalse, regA, ins.Val) +} + +func jumpIfX(ins JumpIfX, regA uint32, regX uint32) int { + return jumpIfCommon(ins.Cond, ins.SkipTrue, ins.SkipFalse, regA, regX) +} + +func jumpIfCommon(cond JumpTest, skipTrue, skipFalse uint8, regA uint32, value uint32) int { + var ok bool + + switch cond { + case JumpEqual: + ok = regA == value + case JumpNotEqual: + ok = regA != value + case JumpGreaterThan: + ok = regA > value + case JumpLessThan: + ok = regA < value + case JumpGreaterOrEqual: + ok = regA >= value + case JumpLessOrEqual: + ok = regA <= value + case JumpBitsSet: + ok = (regA & value) != 0 + case JumpBitsNotSet: + ok = (regA & value) == 0 + } + + if ok { + return int(skipTrue) + } + + return int(skipFalse) +} + +func loadAbsolute(ins LoadAbsolute, in []byte) (uint32, bool) { + offset := int(ins.Off) + size := ins.Size + + return loadCommon(in, offset, size) +} + +func loadConstant(ins LoadConstant, regA uint32, regX uint32) (uint32, uint32) { + switch ins.Dst { + case RegA: + regA = ins.Val + case RegX: + regX = ins.Val + } + + return regA, regX +} + +func loadExtension(ins LoadExtension, in []byte) uint32 { + switch ins.Num { + case ExtLen: + return uint32(len(in)) + default: + panic(fmt.Sprintf("unimplemented extension: %d", ins.Num)) + } +} + +func loadIndirect(ins LoadIndirect, in []byte, regX uint32) (uint32, bool) { + offset := int(ins.Off) + int(regX) + size := ins.Size + + return loadCommon(in, offset, size) +} + +func loadMemShift(ins LoadMemShift, in []byte) (uint32, bool) { + offset := int(ins.Off) + + // Size of LoadMemShift is always 1 byte + if !inBounds(len(in), offset, 1) { + return 0, false + } + + // Mask off high 4 bits and multiply low 4 bits by 4 + return uint32(in[offset]&0x0f) * 4, true +} + +func inBounds(inLen int, offset int, size int) bool { + return offset+size <= inLen +} + +func loadCommon(in []byte, offset int, size int) (uint32, bool) { + if !inBounds(len(in), offset, size) { + return 0, false + } + + switch size { + case 1: + return uint32(in[offset]), true + case 2: + return uint32(binary.BigEndian.Uint16(in[offset : offset+size])), true + case 4: + return uint32(binary.BigEndian.Uint32(in[offset : offset+size])), true + default: + panic(fmt.Sprintf("invalid load size: %d", size)) + } +} + +func loadScratch(ins LoadScratch, regScratch [16]uint32, regA uint32, regX uint32) (uint32, uint32) { + switch ins.Dst { + case RegA: + regA = regScratch[ins.N] + case RegX: + regX = regScratch[ins.N] + } + + return regA, regX +} + +func storeScratch(ins StoreScratch, regScratch [16]uint32, regA uint32, regX uint32) [16]uint32 { + switch ins.Src { + case RegA: + regScratch[ins.N] = regA + case RegX: + regScratch[ins.N] = regX + } + + return regScratch +} diff --git a/vendor/modules.txt b/vendor/modules.txt index e22ba4e281..a91bc891ae 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -678,6 +678,15 @@ github.com/hashicorp/go-multierror # github.com/hashicorp/go-retryablehttp v0.7.4 ## explicit; go 1.13 github.com/hashicorp/go-retryablehttp +# github.com/hugelgupf/p9 v0.3.1-0.20230822151754-54f5c5530921 +## explicit; go 1.20 +github.com/hugelgupf/p9/fsimpl/localfs +github.com/hugelgupf/p9/fsimpl/templatefs +github.com/hugelgupf/p9/fsimpl/xattr +github.com/hugelgupf/p9/internal +github.com/hugelgupf/p9/linux +github.com/hugelgupf/p9/p9 +github.com/hugelgupf/p9/vecnet # github.com/inconshreveable/mousetrap v1.1.0 ## explicit; go 1.18 github.com/inconshreveable/mousetrap @@ -720,6 +729,9 @@ github.com/letsencrypt/boulder/goodkey github.com/letsencrypt/boulder/identifier github.com/letsencrypt/boulder/probs github.com/letsencrypt/boulder/revocation +# github.com/linuxkit/virtsock v0.0.0-20220523201153-1a23e78aa7a2 +## explicit; go 1.17 +github.com/linuxkit/virtsock/pkg/hvsock # github.com/mailru/easyjson v0.7.7 ## explicit; go 1.12 github.com/mailru/easyjson/buffer @@ -742,6 +754,12 @@ github.com/mattn/go-shellwords # github.com/mattn/go-sqlite3 v1.14.17 ## explicit; go 1.16 github.com/mattn/go-sqlite3 +# github.com/mdlayher/socket v0.4.1 +## explicit; go 1.20 +github.com/mdlayher/socket +# github.com/mdlayher/vsock v1.2.1 +## explicit; go 1.20 +github.com/mdlayher/vsock # github.com/miekg/pkcs11 v1.1.1 ## explicit; go 1.12 github.com/miekg/pkcs11 @@ -993,6 +1011,9 @@ github.com/twitchyliquid64/golang-asm/objabi github.com/twitchyliquid64/golang-asm/src github.com/twitchyliquid64/golang-asm/sys github.com/twitchyliquid64/golang-asm/unsafeheader +# github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63 +## explicit; go 1.16 +github.com/u-root/uio/ulog # github.com/ugorji/go/codec v1.2.11 ## explicit; go 1.11 github.com/ugorji/go/codec @@ -1107,6 +1128,7 @@ golang.org/x/mod/semver golang.org/x/mod/sumdb/note # golang.org/x/net v0.17.0 ## explicit; go 1.17 +golang.org/x/net/bpf golang.org/x/net/context golang.org/x/net/html golang.org/x/net/html/atom