diff --git a/cmd/all/all.go b/cmd/all/all.go index 569d1821e..5fb87ed16 100644 --- a/cmd/all/all.go +++ b/cmd/all/all.go @@ -40,6 +40,7 @@ import ( _ "github.com/rclone/rclone/cmd/move" _ "github.com/rclone/rclone/cmd/moveto" _ "github.com/rclone/rclone/cmd/ncdu" + _ "github.com/rclone/rclone/cmd/nfsmount" _ "github.com/rclone/rclone/cmd/obscure" _ "github.com/rclone/rclone/cmd/purge" _ "github.com/rclone/rclone/cmd/rc" diff --git a/cmd/cmount/mount_test.go b/cmd/cmount/mount_test.go index 7c9c852f6..8da6116e2 100644 --- a/cmd/cmount/mount_test.go +++ b/cmd/cmount/mount_test.go @@ -15,6 +15,7 @@ import ( "testing" "github.com/rclone/rclone/fstest/testy" + "github.com/rclone/rclone/vfs/vfscommon" "github.com/rclone/rclone/vfs/vfstest" ) @@ -23,5 +24,5 @@ func TestMount(t *testing.T) { if runtime.GOOS == "darwin" { testy.SkipUnreliable(t) } - vfstest.RunTests(t, false, mount) + vfstest.RunTests(t, false, vfscommon.CacheModeOff, true, mount) } diff --git a/cmd/mount/mount_test.go b/cmd/mount/mount_test.go index ec5422cc9..4e1d832a9 100644 --- a/cmd/mount/mount_test.go +++ b/cmd/mount/mount_test.go @@ -6,9 +6,10 @@ package mount import ( "testing" + "github.com/rclone/rclone/vfs/vfscommon" "github.com/rclone/rclone/vfs/vfstest" ) func TestMount(t *testing.T) { - vfstest.RunTests(t, false, mount) + vfstest.RunTests(t, false, vfscommon.CacheModeOff, true, mount) } diff --git a/cmd/mount2/mount_test.go b/cmd/mount2/mount_test.go index e90632f02..5ff74ff3c 100644 --- a/cmd/mount2/mount_test.go +++ b/cmd/mount2/mount_test.go @@ -6,9 +6,10 @@ package mount2 import ( "testing" + "github.com/rclone/rclone/vfs/vfscommon" "github.com/rclone/rclone/vfs/vfstest" ) func TestMount(t *testing.T) { - vfstest.RunTests(t, false, mount) + vfstest.RunTests(t, false, vfscommon.CacheModeOff, true, mount) } diff --git a/cmd/nfsmount/nfsmount.go b/cmd/nfsmount/nfsmount.go new file mode 100644 index 000000000..755197fa2 --- /dev/null +++ b/cmd/nfsmount/nfsmount.go @@ -0,0 +1,69 @@ +//go:build darwin && !cmount +// +build darwin,!cmount + +// Package nfsmount implements mounting functionality using serve nfs command +// +// NFS mount is only needed for macOS since it has no +// support for FUSE-based file systems +package nfsmount + +import ( + "context" + "fmt" + "net" + "os/exec" + "runtime" + "strings" + + "github.com/rclone/rclone/cmd/mountlib" + "github.com/rclone/rclone/cmd/serve/nfs" + "github.com/rclone/rclone/vfs" +) + +func init() { + cmd := mountlib.NewMountCommand("mount", false, mount) + cmd.Aliases = append(cmd.Aliases, "nfsmount") + mountlib.AddRc("nfsmount", mount) +} + +func mount(VFS *vfs.VFS, mountpoint string, opt *mountlib.Options) (asyncerrors <-chan error, unmount func() error, err error) { + s, err := nfs.NewServer(context.Background(), VFS, &nfs.Options{}) + if err != nil { + return + } + errChan := make(chan error, 1) + go func() { + errChan <- s.Serve() + }() + // The port is always picked at random after the NFS server has started + // we need to query the server for the port number so we can mount it + _, port, err := net.SplitHostPort(s.Addr().String()) + if err != nil { + err = fmt.Errorf("cannot find port number in %s", s.Addr().String()) + return + } + optionsString := strings.Join(opt.ExtraOptions, ",") + err = exec.Command("mount", fmt.Sprintf("-oport=%s,mountport=%s,%s", port, port, optionsString), "localhost:", mountpoint).Run() + if err != nil { + err = fmt.Errorf("failed to mount NFS volume %e", err) + return + } + asyncerrors = errChan + unmount = func() error { + var umountErr error + if runtime.GOOS == "darwin" { + umountErr = exec.Command("diskutil", "umount", "force", mountpoint).Run() + } else { + umountErr = exec.Command("umount", "-f", mountpoint).Run() + } + shutdownErr := s.Shutdown() + VFS.Shutdown() + if umountErr != nil { + return fmt.Errorf("failed to umount the NFS volume %e", umountErr) + } else if shutdownErr != nil { + return fmt.Errorf("failed to shutdown NFS server: %e", shutdownErr) + } + return nil + } + return +} diff --git a/cmd/nfsmount/nfsmount_test.go b/cmd/nfsmount/nfsmount_test.go new file mode 100644 index 000000000..d4c9c3441 --- /dev/null +++ b/cmd/nfsmount/nfsmount_test.go @@ -0,0 +1,15 @@ +//go:build darwin && !cmount +// +build darwin,!cmount + +package nfsmount + +import ( + "testing" + + "github.com/rclone/rclone/vfs/vfscommon" + "github.com/rclone/rclone/vfs/vfstest" +) + +func TestMount(t *testing.T) { + vfstest.RunTests(t, false, vfscommon.CacheModeMinimal, false, mount) +} diff --git a/cmd/nfsmount/nfsmount_unsupported.go b/cmd/nfsmount/nfsmount_unsupported.go new file mode 100644 index 000000000..d24115837 --- /dev/null +++ b/cmd/nfsmount/nfsmount_unsupported.go @@ -0,0 +1,8 @@ +// Build for nfsmount for unsupported platforms to stop go complaining +// about "no buildable Go source files " + +//go:build !darwin || cmount +// +build !darwin cmount + +// Package nfsmount implements mount command using NFS, not needed on most platforms +package nfsmount diff --git a/vfs/vfstest/fs.go b/vfs/vfstest/fs.go index 6781fbe31..d895a7eaa 100644 --- a/vfs/vfstest/fs.go +++ b/vfs/vfstest/fs.go @@ -42,7 +42,7 @@ const ( // // If useVFS is not set then it runs the mount in a subprocess in // order to avoid kernel deadlocks. -func RunTests(t *testing.T, useVFS bool, mountFn mountlib.MountFn) { +func RunTests(t *testing.T, useVFS bool, minimumRequiredCacheMode vfscommon.CacheMode, enableCacheTests bool, mountFn mountlib.MountFn) { flag.Parse() if isSubProcess() { startMount(mountFn, useVFS, *runMount) @@ -59,6 +59,9 @@ func RunTests(t *testing.T, useVFS bool, mountFn mountlib.MountFn) { {cacheMode: vfscommon.CacheModeFull, writeBack: 100 * time.Millisecond}, } for _, test := range tests { + if test.cacheMode < minimumRequiredCacheMode { + continue + } vfsOpt := vfsflags.Opt vfsOpt.CacheMode = test.cacheMode vfsOpt.WriteBack = test.writeBack @@ -78,7 +81,9 @@ func RunTests(t *testing.T, useVFS bool, mountFn mountlib.MountFn) { t.Run("TestDirRenameEmptyDir", TestDirRenameEmptyDir) t.Run("TestDirRenameFullDir", TestDirRenameFullDir) t.Run("TestDirModTime", TestDirModTime) - t.Run("TestDirCacheFlush", TestDirCacheFlush) + if enableCacheTests { + t.Run("TestDirCacheFlush", TestDirCacheFlush) + } t.Run("TestDirCacheFlushOnDirRename", TestDirCacheFlushOnDirRename) t.Run("TestFileModTime", TestFileModTime) t.Run("TestFileModTimeWithOpenWriters", TestFileModTimeWithOpenWriters) @@ -310,7 +315,7 @@ func writeFile(filename string, data []byte, perm os.FileMode) error { func (r *Run) createFile(t *testing.T, filepath string, contents string) { filepath = r.path(filepath) - err := writeFile(filepath, []byte(contents), 0600) + err := writeFile(filepath, []byte(contents), 0644) require.NoError(t, err) r.waitForWriters() } @@ -324,7 +329,7 @@ func (r *Run) readFile(t *testing.T, filepath string) string { func (r *Run) mkdir(t *testing.T, filepath string) { filepath = r.path(filepath) - err := r.os.Mkdir(filepath, 0700) + err := r.os.Mkdir(filepath, 0755) require.NoError(t, err) } diff --git a/vfs/vfstest_test.go b/vfs/vfstest_test.go index babcdb4ff..df8fec685 100644 --- a/vfs/vfstest_test.go +++ b/vfs/vfstest_test.go @@ -9,6 +9,7 @@ import ( "github.com/rclone/rclone/cmd/mountlib" "github.com/rclone/rclone/fstest" "github.com/rclone/rclone/vfs" + "github.com/rclone/rclone/vfs/vfscommon" "github.com/rclone/rclone/vfs/vfstest" ) @@ -18,7 +19,7 @@ func TestFunctional(t *testing.T) { if *fstest.RemoteName != "" { t.Skip("Skip on non local") } - vfstest.RunTests(t, true, func(VFS *vfs.VFS, mountpoint string, opt *mountlib.Options) (unmountResult <-chan error, unmount func() error, err error) { + vfstest.RunTests(t, true, vfscommon.CacheModeOff, true, func(VFS *vfs.VFS, mountpoint string, opt *mountlib.Options) (unmountResult <-chan error, unmount func() error, err error) { unmountResultChan := make(chan (error), 1) unmount = func() error { unmountResultChan <- nil