allow passing open /dev/fuse file descriptors (#124)
allows passing open /dev/fuse file descriptors so that the FUSE process can run fully unprivileged. uses the /dev/fd/N mountpoint format from libfuse3.notifications
parent
37d63df227
commit
21122235c7
31
mount.go
31
mount.go
|
@ -20,6 +20,7 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -41,16 +42,8 @@ func Mount(
|
||||||
config *MountConfig) (*MountedFileSystem, error) {
|
config *MountConfig) (*MountedFileSystem, error) {
|
||||||
// Sanity check: make sure the mount point exists and is a directory. This
|
// Sanity check: make sure the mount point exists and is a directory. This
|
||||||
// saves us from some confusing errors later on OS X.
|
// saves us from some confusing errors later on OS X.
|
||||||
fi, err := os.Stat(dir)
|
if err := checkMountPoint(dir); err != nil {
|
||||||
switch {
|
|
||||||
case os.IsNotExist(err):
|
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
||||||
case err != nil:
|
|
||||||
return nil, fmt.Errorf("Statting mount point: %v", err)
|
|
||||||
|
|
||||||
case !fi.IsDir():
|
|
||||||
return nil, fmt.Errorf("Mount point %s is not a directory", dir)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the struct.
|
// Initialize the struct.
|
||||||
|
@ -97,6 +90,26 @@ func Mount(
|
||||||
return mfs, nil
|
return mfs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkMountPoint(dir string) error {
|
||||||
|
if strings.HasPrefix(dir, "/dev/fd") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
fi, err := os.Stat(dir)
|
||||||
|
switch {
|
||||||
|
case os.IsNotExist(err):
|
||||||
|
return err
|
||||||
|
|
||||||
|
case err != nil:
|
||||||
|
return fmt.Errorf("Statting mount point: %v", err)
|
||||||
|
|
||||||
|
case !fi.IsDir():
|
||||||
|
return fmt.Errorf("Mount point %s is not a directory", dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func fusermount(binary string, argv []string, additionalEnv []string, wait bool) (*os.File, error) {
|
func fusermount(binary string, argv []string, additionalEnv []string, wait bool) (*os.File, error) {
|
||||||
// Create a socket pair.
|
// Create a socket pair.
|
||||||
fds, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0)
|
fds, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0)
|
||||||
|
|
|
@ -5,6 +5,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
|
@ -106,6 +108,14 @@ func mount(dir string, cfg *MountConfig, ready chan<- error) (*os.File, error) {
|
||||||
// On linux, mounting is never delayed.
|
// On linux, mounting is never delayed.
|
||||||
ready <- nil
|
ready <- nil
|
||||||
|
|
||||||
|
// If the mountpoint is /dev/fd/N, assume that the file descriptor N is an
|
||||||
|
// already open FUSE channel. Parse it, cast it to an fd, and don't do any
|
||||||
|
// other part of the mount dance.
|
||||||
|
if fd, err := parseFuseFd(dir); err == nil {
|
||||||
|
dev := os.NewFile(uintptr(fd), "/dev/fuse")
|
||||||
|
return dev, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Try mounting without fusermount(1) first: we might be running as root or
|
// Try mounting without fusermount(1) first: we might be running as root or
|
||||||
// have the CAP_SYS_ADMIN capability.
|
// have the CAP_SYS_ADMIN capability.
|
||||||
dev, err := directmount(dir, cfg)
|
dev, err := directmount(dir, cfg)
|
||||||
|
@ -123,3 +133,16 @@ func mount(dir string, cfg *MountConfig, ready chan<- error) (*os.File, error) {
|
||||||
}
|
}
|
||||||
return dev, err
|
return dev, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseFuseFd(dir string) (int, error) {
|
||||||
|
if !strings.HasPrefix(dir, "/dev/fd/") {
|
||||||
|
return -1, fmt.Errorf("not a /dev/fd path")
|
||||||
|
}
|
||||||
|
|
||||||
|
fd, err := strconv.ParseUint(strings.TrimPrefix(dir, "/dev/fd/"), 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return -1, fmt.Errorf("invalid /dev/fd/N path: N must be a positive integer")
|
||||||
|
}
|
||||||
|
|
||||||
|
return int(fd), nil
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
package fuse
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_parseFuseFd(t *testing.T) {
|
||||||
|
t.Run("valid", func(t *testing.T) {
|
||||||
|
fd, err := parseFuseFd("/dev/fd/42")
|
||||||
|
if fd != 42 {
|
||||||
|
t.Errorf("expected 42, got %d", fd)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error, got %#v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("negative", func(t *testing.T) {
|
||||||
|
fd, err := parseFuseFd("/dev/fd/-42")
|
||||||
|
if fd != -1 {
|
||||||
|
t.Errorf("expected an invalid fd, got %d", fd)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("expected an error, nil")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("not an int", func(t *testing.T) {
|
||||||
|
fd, err := parseFuseFd("/dev/fd/3.14159")
|
||||||
|
if fd != -1 {
|
||||||
|
t.Errorf("expected an invalid fd, got %d", fd)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("expected an error, nil")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
Loading…
Reference in New Issue