From 20d11900b7485a57651bca8655ba9761d7286873 Mon Sep 17 00:00:00 2001 From: jiefenghuang Date: Fri, 20 Sep 2024 10:08:15 +0800 Subject: [PATCH] Revert "fuse: support special /dev/fd/N mountpoint" --- fuse/api.go | 39 ------------------------ fuse/mount_linux.go | 63 ++++++++------------------------------ fuse/mount_linux_test.go | 66 ---------------------------------------- fuse/server.go | 21 +------------ 4 files changed, 14 insertions(+), 175 deletions(-) delete mode 100644 fuse/mount_linux_test.go diff --git a/fuse/api.go b/fuse/api.go index fbdebfab4..7db5f0d0d 100644 --- a/fuse/api.go +++ b/fuse/api.go @@ -88,45 +88,6 @@ // filesystems in terms of path names. Working with path names is somewhat // easier compared to inodes, however renames can be racy. Do not use pathfs if // you care about correctness. -// -// # Mount styles -// -// The NewServer() handles mounting the filesystem, which -// involves opening `/dev/fuse` and calling the -// `mount(2)` syscall. The latter needs root permissions. -// This is handled in one of three ways: -// -// 1) go-fuse opens `/dev/fuse` and executes the `fusermount` -// setuid-root helper to call `mount(2)` for us. This is the default. -// Does not need root permissions but needs `fusermount` installed. -// -// 2) If `MountOptions.DirectMount` is set, go-fuse calls `mount(2)` itself. -// Needs root permissions, but works without `fusermount`. -// -// 3) If `mountPoint` has the magic `/dev/fd/N` syntax, it means that that a -// privileged parent process: -// -// * Opened /dev/fuse -// -// * Called mount(2) on a real mountpoint directory that we don't know about -// -// * Inherited the fd to /dev/fuse to us -// -// * Informs us about the fd number via /dev/fd/N -// -// This magic syntax originates from libfuse [1] and allows the FUSE server to -// run without any privileges and without needing `fusermount`, as the parent -// process performs all privileged operations. -// -// The "privileged parent" is usually a container manager like Singularity [2], -// but for testing, it can also be the `mount.fuse3` helper with the -// `drop_privileges,setuid=$USER` flags. Example below for gocryptfs: -// -// $ sudo mount.fuse3 "/usr/local/bin/gocryptfs#/tmp/cipher" /tmp/mnt -o drop_privileges,setuid=$USER -// -// [1] https://github.com/libfuse/libfuse/commit/64e11073b9347fcf9c6d1eea143763ba9e946f70 -// -// [2] https://sylabs.io/guides/3.7/user-guide/bind_paths_and_mounts.html#fuse-mounts package fuse // Types for users to implement. diff --git a/fuse/mount_linux.go b/fuse/mount_linux.go index eab3d99d9..a8a37eb49 100644 --- a/fuse/mount_linux.go +++ b/fuse/mount_linux.go @@ -12,7 +12,6 @@ import ( "os/exec" "path" "path/filepath" - "strconv" "strings" "syscall" "unsafe" @@ -117,12 +116,18 @@ func mountDirect(mountPoint string, opts *MountOptions, ready chan<- error) (fd return } -// callFusermount calls the `fusermount` suid helper with the right options so -// that it: -// * opens `/dev/fuse` -// * mount()s this file descriptor to `mountPoint` -// * passes this file descriptor back to use via a unix domain socket -func callFusermount(mountPoint string, opts *MountOptions) (fd int, err error) { +// Create a FUSE FS on the specified mount point. The returned +// mount point is always absolute. +func mount(mountPoint string, opts *MountOptions, ready chan<- error) (fd int, err error) { + if opts.DirectMount { + fd, err := mountDirect(mountPoint, opts, ready) + if err == nil { + return fd, nil + } else if opts.Debug { + log.Printf("mount: failed to do direct mount: %s", err) + } + } + local, remote, err := unixgramSocketpair() if err != nil { return @@ -164,53 +169,11 @@ func callFusermount(mountPoint string, opts *MountOptions) (fd int, err error) { return -1, err } - return -} - -// parseFuseFd checks if `mountPoint` is the special form /dev/fd/N (with N >= 0), -// and returns N in this case. Returns -1 otherwise. -func parseFuseFd(mountPoint string) (fd int) { - dir, file := path.Split(mountPoint) - if dir != "/dev/fd/" { - return -1 - } - fd, err := strconv.Atoi(file) - if err != nil || fd <= 0 { - return -1 - } - return fd -} - -// Create a FUSE FS on the specified mount point. The returned -// mount point is always absolute. -func mount(mountPoint string, opts *MountOptions, ready chan<- error) (fd int, err error) { - if opts.DirectMount { - fd, err := mountDirect(mountPoint, opts, ready) - if err == nil { - return fd, nil - } else if opts.Debug { - log.Printf("mount: failed to do direct mount: %s", err) - } - } - - // Magic `/dev/fd/N` mountpoint. See the docs for NewServer() for how this - // works. - fd = parseFuseFd(mountPoint) - if fd >= 0 { - if opts.Debug { - log.Printf("mount: magic mountpoint %q, using fd %d", mountPoint, fd) - } - } else { - // Usual case: mount via the `fusermount` suid helper - fd, err = callFusermount(mountPoint, opts) - if err != nil { - return - } - } // golang sets CLOEXEC on file descriptors when they are // acquired through normal operations (e.g. open). // Buf for fd, we have to set CLOEXEC manually syscall.CloseOnExec(fd) + close(ready) return fd, err } diff --git a/fuse/mount_linux_test.go b/fuse/mount_linux_test.go deleted file mode 100644 index 06ff08a6f..000000000 --- a/fuse/mount_linux_test.go +++ /dev/null @@ -1,66 +0,0 @@ -package fuse - -import ( - "fmt" - "io/ioutil" - "syscall" - "testing" -) - -// TestMountDevFd tests the special `/dev/fd/N` mountpoint syntax, where a -// privileged parent process opens /dev/fuse and calls mount() for us. -// -// In this test, we simulate a privileged parent by using the `fusermount` suid -// helper. -func TestMountDevFd(t *testing.T) { - realMountPoint, err := ioutil.TempDir("", t.Name()) - if err != nil { - t.Fatal(err) - } - defer syscall.Rmdir(realMountPoint) - - // Call the fusermount suid helper to obtain the file descriptor in place - // of a privileged parent. - var fuOpts MountOptions - fd, err := callFusermount(realMountPoint, &fuOpts) - if err != nil { - t.Fatal(err) - } - fdMountPoint := fmt.Sprintf("/dev/fd/%d", fd) - - // Real test starts here: - // See if we can feed fdMountPoint to NewServer - fs := NewDefaultRawFileSystem() - opts := MountOptions{ - Debug: true, - } - srv, err := NewServer(fs, fdMountPoint, &opts) - if err != nil { - t.Fatal(err) - } - - go srv.Serve() - if err := srv.WaitMount(); err != nil { - t.Fatal(err) - } - - // If we are actually mounted, we should get ENOSYS. - // - // This won't deadlock despite pollHack not working for `/dev/fd/N` mounts - // because functions in the syscall package don't use the poller. - var st syscall.Stat_t - err = syscall.Stat(realMountPoint, &st) - if err != syscall.ENOSYS { - t.Errorf("expected ENOSYS, got %v", err) - } - - // Cleanup is somewhat tricky because `srv` does not know about - // `realMountPoint`, so `srv.Unmount()` cannot work. - // - // A normal user has to call `fusermount -u` for themselves to unmount. - // But in this test we can monkey-patch `srv.mountPoint`. - srv.mountPoint = realMountPoint - if err := srv.Unmount(); err != nil { - t.Error(err) - } -} diff --git a/fuse/server.go b/fuse/server.go index 8003c395a..98fd4d0cf 100644 --- a/fuse/server.go +++ b/fuse/server.go @@ -118,20 +118,10 @@ func (ms *Server) RecordLatencies(l LatencyMap) { // Unmount calls fusermount -u on the mount. This has the effect of // shutting down the filesystem. After the Server is unmounted, it // should be discarded. -// -// Does not work when we were mounted with the magic /dev/fd/N mountpoint syntax, -// as we do not know the real mountpoint. Unmount using -// -// fusermount -u /path/to/real/mountpoint -// -/// in this case. func (ms *Server) Unmount() (err error) { if ms.mountPoint == "" { return nil } - if parseFuseFd(ms.mountPoint) >= 0 { - return fmt.Errorf("Cannot unmount magic mountpoint %q. Please use `fusermount -u REALMOUNTPOINT` instead.", ms.mountPoint) - } delay := time.Duration(0) for try := 0; try < 5; try++ { err = unmount(ms.mountPoint, ms.opts) @@ -154,11 +144,7 @@ func (ms *Server) Unmount() (err error) { return err } -// NewServer creates a FUSE server and attaches ("mounts") it to the -// `mountPoint` directory. -// -// See the "Mount styles" section in the package documentation if you want to -// know about the inner workings of the mount process. Usually you do not. +// NewServer creates a server and attaches it to the given directory. func NewServer(fs RawFileSystem, mountPoint string, opts *MountOptions) (*Server, error) { if opts == nil { opts = &MountOptions{ @@ -1131,10 +1117,5 @@ func (ms *Server) WaitMount() error { if err != nil { return err } - if parseFuseFd(ms.mountPoint) >= 0 { - // Magic `/dev/fd/N` mountpoint. We don't know the real mountpoint, so - // we cannot run the poll hack. - return nil - } return pollHack(ms.mountPoint) }