mount: refactor mount remote control to use ParseOptions

This updates `mountRc` in `cmd/mountlib/rc.go` to parse options using the
unified `rc.ParseOptions` helper. It also enforces parameter validation by
calling `rc.CheckParamsUsed`.

- Replace custom options parsing with rc.ParseOptions for vfsOpt and mountOpt.
- Delete consumed params (mountPoint, mountType, fs) and call CheckParamsUsed
  before initiating FUSE mount to reject unknown parameters.
- Clean up duplication tests (TestRcFlatOptions, TestRcFlatOptionsNull) in rc_test.go.
- Update TestRc in rc_test.go to pass a clean params map to unmount.Fn.
This commit is contained in:
Hakan İSMAİL
2026-07-29 19:42:45 +01:00
committed by Nick Craig-Wood
parent 01495c8ded
commit e8ff1b123b
3 changed files with 14 additions and 191 deletions
-105
View File
@@ -1,105 +0,0 @@
package mountlib
import (
"reflect"
"sync"
"github.com/rclone/rclone/fs/config/configstruct"
"github.com/rclone/rclone/fs/rc"
"github.com/rclone/rclone/vfs/vfscommon"
)
var (
vfsOptionsOnce sync.Once
vfsOptionsMap map[string]bool
mountOptionsOnce sync.Once
mountOptionsMap map[string]bool
)
func initVfsOptions() {
vfsOptionsOnce.Do(func() {
vfsOptionsMap = make(map[string]bool, len(vfscommon.OptionsInfo))
for _, opt := range vfscommon.OptionsInfo {
vfsOptionsMap[opt.Name] = true
}
})
}
func initMountOptions() {
mountOptionsOnce.Do(func() {
mountOptionsMap = make(map[string]bool, len(OptionsInfo))
for _, opt := range OptionsInfo {
mountOptionsMap[opt.Name] = true
}
})
}
// isMap returns true if v's underlying type is a map
func isMap(v any) bool {
if v == nil {
return false
}
t := reflect.TypeOf(v)
for t.Kind() == reflect.Pointer {
t = t.Elem()
}
return t.Kind() == reflect.Map
}
// parseVfsOptions parses VFS options from in (both flat and nested) and updates vfsOpt
func parseVfsOptions(in rc.Params, vfsOpt *vfscommon.Options) error {
initVfsOptions()
flatVfs := make(map[string]any)
for k, v := range in {
if vfsOptionsMap[k] {
if isMap(v) {
continue
}
flatVfs[k] = v
}
}
if len(flatVfs) > 0 {
err := configstruct.SetAny(flatVfs, vfsOpt)
if err != nil {
return err
}
for k := range flatVfs {
delete(in, k)
}
}
err := in.GetStructMissingOK("vfsOpt", vfsOpt)
if err != nil {
return err
}
delete(in, "vfsOpt")
return nil
}
// parseMountOptions parses Mount options from in (both flat and nested) and updates mountOpt
func parseMountOptions(in rc.Params, mountOpt *Options) error {
initMountOptions()
flatMount := make(map[string]any)
for k, v := range in {
if mountOptionsMap[k] {
if isMap(v) {
continue
}
flatMount[k] = v
}
}
if len(flatMount) > 0 {
err := configstruct.SetAny(flatMount, mountOpt)
if err != nil {
return err
}
for k := range flatMount {
delete(in, k)
}
}
err := in.GetStructMissingOK("mountOpt", mountOpt)
if err != nil {
return err
}
delete(in, "mountOpt")
return nil
}
+11 -2
View File
@@ -105,13 +105,13 @@ func mountRc(ctx context.Context, in rc.Params) (out rc.Params, err error) {
}
vfsOpt := vfscommon.Opt
err = parseVfsOptions(in, &vfsOpt)
err = rc.ParseOptions(in, "vfsOpt", &vfsOpt)
if err != nil {
return nil, err
}
mountOpt := Opt
err = parseMountOptions(in, &mountOpt)
err = rc.ParseOptions(in, "mountOpt", &mountOpt)
if err != nil {
return nil, err
}
@@ -139,6 +139,15 @@ func mountRc(ctx context.Context, in rc.Params) (out rc.Params, err error) {
return nil, err
}
// Clean up consumed keys and check for leftovers
delete(in, "mountPoint")
delete(in, "mountType")
delete(in, "fs")
err = rc.CheckParamsUsed(in)
if err != nil {
return nil, err
}
mnt := NewMountPoint(mountFn, mountPoint, fdst, &mountOpt, &vfsOpt)
_, err = mnt.Mount()
if err != nil {
+3 -84
View File
@@ -121,92 +121,11 @@ func TestRc(t *testing.T) {
time.Sleep(100 * time.Millisecond)
t.Run("Unmount", func(t *testing.T) {
_, err := unmount.Fn(ctx, in)
_, err := unmount.Fn(ctx, rc.Params{
"mountPoint": mountPoint,
})
require.NoError(t, err)
assert.Equal(t, 0, len(checkMountList()))
})
})
}
func TestRcFlatOptions(t *testing.T) {
// Disable tests under macOS and the CI since they are locking up
if runtime.GOOS == "darwin" {
testy.SkipUnreliable(t)
}
ctx := context.Background()
configfile.Install()
mount := rc.Calls.Get("mount/mount")
assert.NotNil(t, mount)
unmount := rc.Calls.Get("mount/unmount")
assert.NotNil(t, unmount)
getMountTypes := rc.Calls.Get("mount/types")
assert.NotNil(t, getMountTypes)
localDir := t.TempDir()
err := os.WriteFile(filepath.Join(localDir, "file.txt"), []byte("hello"), 0666)
require.NoError(t, err)
out, err := getMountTypes.Fn(ctx, nil)
require.NoError(t, err)
var mountTypes []string
err = out.GetStruct("mountTypes", &mountTypes)
require.NoError(t, err)
if len(mountTypes) == 0 {
t.Skip("Can't mount")
}
mountPointFlat := t.TempDir()
if runtime.GOOS == "windows" {
require.NoError(t, os.RemoveAll(mountPointFlat))
}
in := rc.Params{
"fs": localDir,
"mountPoint": mountPointFlat,
"file_perms": 0400, // flat VFS option
"volname": "MyTestVolume", // flat Mount option
}
// mount
out, err = mount.Fn(ctx, in)
if err != nil {
t.Skipf("Mount failed - skipping test: %v", err)
}
// check the returned mount point matches what we asked for
returnedMountPoint, err := out.GetString("mountPoint")
require.NoError(t, err)
assert.Equal(t, mountPointFlat, returnedMountPoint)
// check that the flat options were consumed and removed from parameter map
_, ok := in["file_perms"]
assert.False(t, ok, "file_perms flat option should have been deleted")
_, ok = in["volname"]
assert.False(t, ok, "volname flat option should have been deleted")
// unmount
_, err = unmount.Fn(ctx, rc.Params{
"mountPoint": mountPointFlat,
})
require.NoError(t, err)
// FIXME wait a moment for the OS to release the mount point
time.Sleep(100 * time.Millisecond)
}
func TestRcFlatOptionsNull(t *testing.T) {
ctx := context.Background()
configfile.Install()
mount := rc.Calls.Get("mount/mount")
assert.NotNil(t, mount)
in := rc.Params{
"fs": "some_fs",
"mountPoint": "some_mount_point",
"vfs_cache_mode": nil, // flat VFS option set to null
}
_, err := mount.Fn(ctx, in)
assert.Error(t, err)
assert.Contains(t, err.Error(), "interpreting <nil> as string failed")
}