Commit 05c0aea6 authored by Jakob Unterwurzacher's avatar Jakob Unterwurzacher Committed by Han-Wen Nienhuys

fuse, fs: add DirectMount tests & DirectMountStrict option

Reason for adding DirectMountStrict is making the DirectMount
functionality testable, though it may have value for the user
in some cases.

Add defaultRawFileSystem & loopback tests for DirectMount
and DirectMountStrict.

The tests fail right now due to bugs in DirectMount that will be fixed
shortly:

	go-fuse/fuse$ sudo /usr/local/go/bin/go test -run TestDirectMount
	[...]
	2022/12/28 20:19:21 mountDirect: calling syscall.Mount("", "/tmp/TestDirectMount3242971772", "fuse./tmp/go-build1215740", 0x0, "fd=7,rootmode=40000,user_id=0,group_id=0")
	2022/12/28 20:19:21 mount: failed to do direct mount: invalid argument
	[...]

Change-Id: Ibfa2fa141cb43e1f8c7319233c454a3e85fa435e
parent 0f41d79d
...@@ -13,7 +13,11 @@ GOOS=darwin go build ./fuse/... ./fs/... ./example/loopback/... ...@@ -13,7 +13,11 @@ GOOS=darwin go build ./fuse/... ./fs/... ./example/loopback/...
# -p 1 .......... Run tests serially, which also means we get live output # -p 1 .......... Run tests serially, which also means we get live output
# instead of per-package buffering. # instead of per-package buffering.
# -count 1 ...... Disable result caching, so we can see flakey tests # -count 1 ...... Disable result caching, so we can see flakey tests
go test -timeout 5m -p 1 -count 1 ./... GO_TEST="go test -timeout 5m -p 1 -count 1"
# Run all tests as current user
$GO_TEST ./...
# Direct-mount tests need to run as root
sudo env PATH=$PATH $GO_TEST -run TestDirectMount ./fs ./fuse
make -C benchmark make -C benchmark
go test ./benchmark -test.bench '.*' -test.cpu 1,2 go test ./benchmark -test.bench '.*' -test.cpu 1,2
...@@ -401,3 +401,15 @@ func TestRoMount(t *testing.T) { ...@@ -401,3 +401,15 @@ func TestRoMount(t *testing.T) {
tc := newTestCase(t, &testOptions{ro: true}) tc := newTestCase(t, &testOptions{ro: true})
defer tc.Clean() defer tc.Clean()
} }
func TestDirectMount(t *testing.T) {
opts := &testOptions{
directMount: true,
}
if os.Geteuid() == 0 {
t.Log("running as root, setting DirectMountStrict")
opts.directMountStrict = true
}
tc := newTestCase(t, opts)
defer tc.Clean()
}
...@@ -61,6 +61,8 @@ type testOptions struct { ...@@ -61,6 +61,8 @@ type testOptions struct {
suppressDebug bool suppressDebug bool
testDir string testDir string
ro bool ro bool
directMount bool // sets MountOptions.DirectMount
directMountStrict bool // sets MountOptions.DirectMountStrict
} }
// newTestCase creates the directories `orig` and `mnt` inside a temporary // newTestCase creates the directories `orig` and `mnt` inside a temporary
...@@ -107,7 +109,10 @@ func newTestCase(t *testing.T, opts *testOptions) *testCase { ...@@ -107,7 +109,10 @@ func newTestCase(t *testing.T, opts *testOptions) *testCase {
Logger: log.New(os.Stderr, "", 0), Logger: log.New(os.Stderr, "", 0),
}) })
mOpts := &fuse.MountOptions{} mOpts := &fuse.MountOptions{
DirectMount: opts.directMount,
DirectMountStrict: opts.directMountStrict,
}
if !opts.suppressDebug { if !opts.suppressDebug {
mOpts.Debug = testutil.VerboseTest() mOpts.Debug = testutil.VerboseTest()
} }
......
...@@ -247,8 +247,15 @@ type MountOptions struct { ...@@ -247,8 +247,15 @@ type MountOptions struct {
// If set, fuse will first attempt to use syscall.Mount instead of // If set, fuse will first attempt to use syscall.Mount instead of
// fusermount to mount the filesystem. This will not update /etc/mtab // fusermount to mount the filesystem. This will not update /etc/mtab
// but might be needed if fusermount is not available. // but might be needed if fusermount is not available.
// Also, Server.Unmount will attempt syscall.Unmount before calling
// fusermount.
DirectMount bool DirectMount bool
// DirectMountStrict is like DirectMount but no fallback to fusermount is
// performed. If both DirectMount and DirectMountStrict are set,
// DirectMountStrict wins.
DirectMountStrict bool
// Options passed to syscall.Mount, the default value used by fusermount // Options passed to syscall.Mount, the default value used by fusermount
// is syscall.MS_NOSUID|syscall.MS_NODEV // is syscall.MS_NOSUID|syscall.MS_NODEV
DirectMountFlags uintptr DirectMountFlags uintptr
......
...@@ -57,6 +57,10 @@ func mountDirect(mountPoint string, opts *MountOptions, ready chan<- error) (fd ...@@ -57,6 +57,10 @@ func mountDirect(mountPoint string, opts *MountOptions, ready chan<- error) (fd
r = append(r, "allow_other") r = append(r, "allow_other")
} }
if opts.Debug {
log.Printf("mountDirect: calling syscall.Mount(%q, %q, %q, %#x, %q)",
opts.FsName, mountPoint, "fuse."+opts.Name, opts.DirectMountFlags, strings.Join(r, ","))
}
err = syscall.Mount(opts.FsName, mountPoint, "fuse."+opts.Name, opts.DirectMountFlags, strings.Join(r, ",")) err = syscall.Mount(opts.FsName, mountPoint, "fuse."+opts.Name, opts.DirectMountFlags, strings.Join(r, ","))
if err != nil { if err != nil {
syscall.Close(fd) syscall.Close(fd)
...@@ -125,13 +129,16 @@ func callFusermount(mountPoint string, opts *MountOptions) (fd int, err error) { ...@@ -125,13 +129,16 @@ func callFusermount(mountPoint string, opts *MountOptions) (fd int, err error) {
// Create a FUSE FS on the specified mount point. The returned // Create a FUSE FS on the specified mount point. The returned
// mount point is always absolute. // mount point is always absolute.
func mount(mountPoint string, opts *MountOptions, ready chan<- error) (fd int, err error) { func mount(mountPoint string, opts *MountOptions, ready chan<- error) (fd int, err error) {
if opts.DirectMount { if opts.DirectMount || opts.DirectMountStrict {
fd, err := mountDirect(mountPoint, opts, ready) fd, err := mountDirect(mountPoint, opts, ready)
if err == nil { if err == nil {
return fd, nil return fd, nil
} else if opts.Debug { } else if opts.Debug {
log.Printf("mount: failed to do direct mount: %s", err) log.Printf("mount: failed to do direct mount: %s", err)
} }
if opts.DirectMountStrict {
return -1, err
}
} }
// Magic `/dev/fd/N` mountpoint. See the docs for NewServer() for how this // Magic `/dev/fd/N` mountpoint. See the docs for NewServer() for how this
...@@ -157,12 +164,15 @@ func mount(mountPoint string, opts *MountOptions, ready chan<- error) (fd int, e ...@@ -157,12 +164,15 @@ func mount(mountPoint string, opts *MountOptions, ready chan<- error) (fd int, e
} }
func unmount(mountPoint string, opts *MountOptions) (err error) { func unmount(mountPoint string, opts *MountOptions) (err error) {
if opts.DirectMount { if opts.DirectMount || opts.DirectMountStrict {
// Attempt to directly unmount, if fails fallback to fusermount method // Attempt to directly unmount, if fails fallback to fusermount method
err := syscall.Unmount(mountPoint, 0) err := syscall.Unmount(mountPoint, 0)
if err == nil { if err == nil {
return nil return nil
} }
if opts.DirectMountStrict {
return err
}
} }
bin, err := fusermountBinary() bin, err := fusermountBinary()
......
...@@ -3,8 +3,11 @@ package fuse ...@@ -3,8 +3,11 @@ package fuse
import ( import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os"
"syscall" "syscall"
"testing" "testing"
"github.com/moby/sys/mountinfo"
) )
// TestMountDevFd tests the special `/dev/fd/N` mountpoint syntax, where a // TestMountDevFd tests the special `/dev/fd/N` mountpoint syntax, where a
...@@ -98,3 +101,77 @@ func TestMountMaxWrite(t *testing.T) { ...@@ -98,3 +101,77 @@ func TestMountMaxWrite(t *testing.T) {
}) })
} }
} }
// mountCheckOptions mounts a defaultRawFileSystem and extracts the resulting effective
// mount options from /proc/self/mounts.
// The mount options are a comma-separated string like this:
// rw,nosuid,nodev,relatime,user_id=1026,group_id=1026
func mountCheckOptions(t *testing.T, opts MountOptions) (info mountinfo.Info) {
mnt, err := ioutil.TempDir("", t.Name())
if err != nil {
t.Fatal(err)
}
fs := NewDefaultRawFileSystem()
srv, err := NewServer(fs, mnt, &opts)
if err != nil {
t.Fatal(err)
}
// Check mount options
mounts, err := mountinfo.GetMounts(mountinfo.SingleEntryFilter(mnt))
if err != nil {
t.Error(err)
}
if len(mounts) != 1 {
t.Errorf("Could not find mountpoint %q in /proc/self/mountinfo", mnt)
}
orig := *mounts[0]
t.Logf("full mountinfo: %#v", orig)
// We are only interested in some fields, as the others are arbitrary id numbers
// or contain random strings like "/tmp/TestDirectMount1126361240".
//
// What are all those fields: Look for "/proc/[pid]/mountinfo" in
// https://man7.org/linux/man-pages/man5/proc.5.html .
info = mountinfo.Info{
Options: orig.Options,
Source: orig.Source,
FSType: orig.FSType,
VFSOptions: orig.VFSOptions,
Optional: orig.Optional,
}
// server needs to run for Unmount to work
go srv.Serve()
err = srv.Unmount()
if err != nil {
t.Error(err)
}
return info
}
// TestDirectMount checks that DirectMount and DirectMountStrict work and show the
// same effective mount options in /proc/self/mounts
func TestDirectMount(t *testing.T) {
opts := MountOptions{
Debug: true,
}
// Without DirectMount - i.e. using fusermount
t.Log("Normal fusermount mount")
o1 := mountCheckOptions(t, opts)
// With DirectMount
t.Log("DirectMount")
opts.DirectMount = true
o2 := mountCheckOptions(t, opts)
if o2 != o1 {
t.Errorf("Effective mount options differ between DirectMount and fusermount mount:\n%#v\n%#v",
o2, o1)
}
// With DirectMountStrict
if os.Geteuid() == 0 {
t.Log("DirectMountStrict")
opts.DirectMountStrict = true
o3 := mountCheckOptions(t, opts)
if o3 != o1 {
t.Errorf("Effective mount options differ between DirectMountStrict and fusermount mount: \n%#v\n%#v",
o3, o1)
}
}
}
...@@ -2,8 +2,9 @@ module github.com/hanwen/go-fuse/v2 ...@@ -2,8 +2,9 @@ module github.com/hanwen/go-fuse/v2
require ( require (
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348
github.com/moby/sys/mountinfo v0.6.2
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a golang.org/x/sync v0.0.0-20201207232520-09787c993a3a
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a
) )
go 1.13 go 1.13
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4=
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78=
github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522 h1:Ve1ORMCxvRmSXBwJK+t3Oy+V2vRW2OetUQBq4rJIkZE= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment