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/...
# -p 1 .......... Run tests serially, which also means we get live output
# instead of per-package buffering.
# -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
go test ./benchmark -test.bench '.*' -test.cpu 1,2
......@@ -401,3 +401,15 @@ func TestRoMount(t *testing.T) {
tc := newTestCase(t, &testOptions{ro: true})
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 {
suppressDebug bool
testDir string
ro bool
directMount bool // sets MountOptions.DirectMount
directMountStrict bool // sets MountOptions.DirectMountStrict
}
// newTestCase creates the directories `orig` and `mnt` inside a temporary
......@@ -107,7 +109,10 @@ func newTestCase(t *testing.T, opts *testOptions) *testCase {
Logger: log.New(os.Stderr, "", 0),
})
mOpts := &fuse.MountOptions{}
mOpts := &fuse.MountOptions{
DirectMount: opts.directMount,
DirectMountStrict: opts.directMountStrict,
}
if !opts.suppressDebug {
mOpts.Debug = testutil.VerboseTest()
}
......
......@@ -247,8 +247,15 @@ type MountOptions struct {
// If set, fuse will first attempt to use syscall.Mount instead of
// fusermount to mount the filesystem. This will not update /etc/mtab
// but might be needed if fusermount is not available.
// Also, Server.Unmount will attempt syscall.Unmount before calling
// fusermount.
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
// is syscall.MS_NOSUID|syscall.MS_NODEV
DirectMountFlags uintptr
......
......@@ -57,6 +57,10 @@ func mountDirect(mountPoint string, opts *MountOptions, ready chan<- error) (fd
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, ","))
if err != nil {
syscall.Close(fd)
......@@ -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
// mount point is always absolute.
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)
if err == nil {
return fd, nil
} else if opts.Debug {
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
......@@ -157,12 +164,15 @@ func mount(mountPoint string, opts *MountOptions, ready chan<- error) (fd int, e
}
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
err := syscall.Unmount(mountPoint, 0)
if err == nil {
return nil
}
if opts.DirectMountStrict {
return err
}
}
bin, err := fusermountBinary()
......
......@@ -3,8 +3,11 @@ package fuse
import (
"fmt"
"io/ioutil"
"os"
"syscall"
"testing"
"github.com/moby/sys/mountinfo"
)
// TestMountDevFd tests the special `/dev/fd/N` mountpoint syntax, where a
......@@ -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
require (
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/sys v0.0.0-20180830151530-49385e6e1522
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a
)
go 1.13
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/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/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-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
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