mountlib: rc: fix mounts created with mountPoint "*" overwriting each other

On Windows, passing "*" as mountPoint to the mount/mount RC command
auto-assigns a drive letter (e.g. "Z:"), but the resolved letter was
never propagated back to mountlib. This caused liveMounts to be keyed
on the literal "*", breaking tracking of multiple mounts and making
unmount unreliable.

Change MountFn to return the actual mount point as an additional
return value. Update MountPoint.Mount() to store the resolved value,
and mountRc() to use it as the liveMounts key. The mount/mount RC
response now returns the actual mountPoint so callers can discover
which drive letter was assigned.
This commit is contained in:
Nick Craig-Wood
2026-04-27 15:09:14 +01:00
parent d2b8b73ea3
commit 6b67be9d48
9 changed files with 60 additions and 29 deletions
+4 -4
View File
@@ -124,12 +124,12 @@ func waitFor(fn func() bool) (ok bool) {
//
// returns an error, and an error channel for the serve process to
// report an error when fusermount is called.
func mount(VFS *vfs.VFS, mountPath string, opt *mountlib.Options) (<-chan error, func() error, error) {
func mount(VFS *vfs.VFS, mountPath string, opt *mountlib.Options) (<-chan error, func() error, string, error) {
// Get mountpoint using OS specific logic
f := VFS.Fs()
mountpoint, err := getMountpoint(f, mountPath, opt)
if err != nil {
return nil, nil, err
return nil, nil, "", err
}
fs.Debugf(nil, "Mounting on %q (%q)", mountpoint, opt.VolumeName)
@@ -205,7 +205,7 @@ func mount(VFS *vfs.VFS, mountPath string, opt *mountlib.Options) (<-chan error,
select {
case err := <-errChan:
err = fmt.Errorf("mount stopped before calling Init: %w", err)
return nil, nil, err
return nil, nil, "", err
case <-fsys.ready:
}
@@ -220,5 +220,5 @@ func mount(VFS *vfs.VFS, mountPath string, opt *mountlib.Options) (<-chan error,
}
}
return errChan, unmount, nil
return errChan, unmount, mountpoint, nil
}
+2 -2
View File
@@ -26,8 +26,8 @@ func init() {
//
// returns an error, and an error channel for the serve process to
// report an error when fusermount is called.
func mount(_ *vfs.VFS, _ string, _ *mountlib.Options) (<-chan error, func() error, error) {
return nil, nil, errors.New("rclone mount is not supported on MacOS when rclone is installed via Homebrew. " +
func mount(_ *vfs.VFS, _ string, _ *mountlib.Options) (<-chan error, func() error, string, error) {
return nil, nil, "", errors.New("rclone mount is not supported on MacOS when rclone is installed via Homebrew. " +
"Please install the rclone binaries available at https://rclone.org/downloads/ " +
"instead if you want to use the rclone mount command")
}
+5 -5
View File
@@ -67,13 +67,13 @@ func mountOptions(VFS *vfs.VFS, device string, opt *mountlib.Options) (options [
//
// returns an error, and an error channel for the serve process to
// report an error when fusermount is called.
func mount(VFS *vfs.VFS, mountpoint string, opt *mountlib.Options) (<-chan error, func() error, error) {
func mount(VFS *vfs.VFS, mountpoint string, opt *mountlib.Options) (<-chan error, func() error, string, error) {
f := VFS.Fs()
if err := mountlib.CheckOverlap(f, mountpoint); err != nil {
return nil, nil, err
return nil, nil, "", err
}
if err := mountlib.CheckAllowNonEmpty(mountpoint, opt); err != nil {
return nil, nil, err
return nil, nil, "", err
}
fs.Debugf(f, "Mounting on %q", mountpoint)
@@ -85,7 +85,7 @@ func mount(VFS *vfs.VFS, mountpoint string, opt *mountlib.Options) (<-chan error
c, err := fuse.Mount(mountpoint, mountOptions(VFS, opt.DeviceName, opt)...)
if err != nil {
return nil, nil, err
return nil, nil, "", err
}
filesys := NewFS(VFS, opt)
@@ -108,5 +108,5 @@ func mount(VFS *vfs.VFS, mountpoint string, opt *mountlib.Options) (<-chan error
return fuse.Unmount(mountpoint)
}
return errChan, unmount, nil
return errChan, unmount, mountpoint, nil
}
+8 -8
View File
@@ -184,13 +184,13 @@ func mountOptions(fsys *FS, f fs.Fs, opt *mountlib.Options) (mountOpts *fuse.Mou
//
// returns an error, and an error channel for the serve process to
// report an error when fusermount is called.
func mount(VFS *vfs.VFS, mountpoint string, opt *mountlib.Options) (<-chan error, func() error, error) {
func mount(VFS *vfs.VFS, mountpoint string, opt *mountlib.Options) (<-chan error, func() error, string, error) {
f := VFS.Fs()
if err := mountlib.CheckOverlap(f, mountpoint); err != nil {
return nil, nil, err
return nil, nil, "", err
}
if err := mountlib.CheckAllowNonEmpty(mountpoint, opt); err != nil {
return nil, nil, err
return nil, nil, "", err
}
fs.Debugf(f, "Mounting on %q", mountpoint)
@@ -223,20 +223,20 @@ func mount(VFS *vfs.VFS, mountpoint string, opt *mountlib.Options) (<-chan error
root, err := fsys.Root()
if err != nil {
return nil, nil, err
return nil, nil, "", err
}
rawFS := fusefs.NewNodeFS(root, &opts)
server, err := fuse.NewServer(rawFS, mountpoint, &opts.MountOptions)
if err != nil {
return nil, nil, err
return nil, nil, "", err
}
//mountOpts := &fuse.MountOptions{}
//server, err := fusefs.Mount(mountpoint, fsys, &opts)
// server, err := fusefs.Mount(mountpoint, root, &opts)
// if err != nil {
// return nil, nil, err
// return nil, nil, "", err
// }
umount := func() error {
@@ -258,9 +258,9 @@ func mount(VFS *vfs.VFS, mountpoint string, opt *mountlib.Options) (<-chan error
fs.Debugf(f, "Waiting for the mount to start...")
err = server.WaitMount()
if err != nil {
return nil, nil, err
return nil, nil, "", err
}
fs.Debugf(f, "Mount started")
return errs, umount, nil
return errs, umount, mountpoint, nil
}
+10 -2
View File
@@ -195,7 +195,11 @@ type (
// UnmountFn is called to unmount the file system
UnmountFn func() error
// MountFn is called to mount the file system
MountFn func(VFS *vfs.VFS, mountpoint string, opt *Options) (<-chan error, func() error, error)
//
// It returns the errChan, unmount function, the actual mountpoint
// (which may differ from the input, e.g. on Windows when "*" is
// used to auto-assign a drive letter) and an error.
MountFn func(VFS *vfs.VFS, mountpoint string, opt *Options) (<-chan error, func() error, string, error)
)
// MountPoint represents a mount with options and runtime state
@@ -371,13 +375,17 @@ func (m *MountPoint) Mount() (mountDaemon *os.Process, err error) {
m.VFS = vfs.New(context.Background(), m.Fs, &m.VFSOpt)
m.ErrChan, m.UnmountFn, err = m.MountFn(m.VFS, m.MountPoint, &m.MountOpt)
var actualMountpoint string
m.ErrChan, m.UnmountFn, actualMountpoint, err = m.MountFn(m.VFS, m.MountPoint, &m.MountOpt)
if err != nil {
if len(os.Args) > 0 && strings.HasPrefix(os.Args[0], "/snap/") {
return nil, fmt.Errorf("mounting is not supported when running from snap")
}
return nil, fmt.Errorf("failed to mount FUSE fs: %w", err)
}
if actualMountpoint != "" {
m.MountPoint = actualMountpoint
}
m.MountedOn = time.Now()
return nil, nil
}
+21 -4
View File
@@ -62,12 +62,24 @@ This takes the following parameters:
- mountOpt: a JSON object with Mount options in.
- vfsOpt: a JSON object with VFS options in.
On Windows mountPoint may be set to "*" to assign the next available
drive letter automatically, or a network share UNC path (e.g.
"\\server\share") to mount as a network drive. In these cases the
actual drive letter is chosen at mount time.
This returns the following values:
- mountPoint: the actual mount point that was used (this may differ
from the input, e.g. on Windows when "*" is passed the allocated
drive letter is returned)
Example:
` + "```console" + `
rclone rc mount/mount fs=mydrive: mountPoint=/home/<user>/mountPoint
rclone rc mount/mount fs=mydrive: mountPoint=/home/<user>/mountPoint mountType=mount
rclone rc mount/mount fs=TestDrive: mountPoint=/mnt/tmp vfsOpt='{"CacheMode": 2}' mountOpt='{"AllowOther": true}'
rclone rc mount/mount fs=mydrive: mountPoint=* mountType=cmount
` + "```" + `
The vfsOpt are as described in options/get and can be seen in the
@@ -128,6 +140,9 @@ func mountRc(ctx context.Context, in rc.Params) (out rc.Params, err error) {
fs.Logf(nil, "mount FAILED: %v", err)
return nil, err
}
// mnt.MountPoint may have been updated by MountFn (e.g. on
// Windows when "*" is resolved to an actual drive letter)
actualMountPoint := mnt.MountPoint
go func() {
if err = mnt.Wait(); err != nil {
fs.Logf(nil, "unmount FAILED: %v", err)
@@ -135,13 +150,15 @@ func mountRc(ctx context.Context, in rc.Params) (out rc.Params, err error) {
}
mountMu.Lock()
defer mountMu.Unlock()
delete(liveMounts, mountPoint)
delete(liveMounts, actualMountPoint)
}()
// Add mount to list if mount point was successfully created
liveMounts[mountPoint] = mnt
liveMounts[actualMountPoint] = mnt
fs.Debugf(nil, "Mount for %s created at %s using %s", fdst.String(), mountPoint, mountType)
return nil, nil
fs.Debugf(nil, "Mount for %s created at %s using %s", fdst.String(), actualMountPoint, mountType)
return rc.Params{
"mountPoint": actualMountPoint,
}, nil
}
func init() {
+6 -1
View File
@@ -82,11 +82,16 @@ func TestRc(t *testing.T) {
require.True(t, os.IsNotExist(err))
// mount
_, err = mount.Fn(ctx, in)
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, mountPoint, returnedMountPoint)
// check file.txt is there now
fi, err := os.Stat(filePath)
require.NoError(t, err)
+2 -1
View File
@@ -35,7 +35,7 @@ func init() {
nfs.AddFlags(cmdFlags)
}
func mount(VFS *vfs.VFS, mountpoint string, opt *mountlib.Options) (asyncerrors <-chan error, unmount func() error, err error) {
func mount(VFS *vfs.VFS, mountpoint string, opt *mountlib.Options) (asyncerrors <-chan error, unmount func() error, actualMountpoint string, err error) {
s, err := nfs.NewServer(context.Background(), VFS, &nfs.Opt)
if err != nil {
return
@@ -112,5 +112,6 @@ func mount(VFS *vfs.VFS, mountpoint string, opt *mountlib.Options) (asyncerrors
VFS.Shutdown()
}
actualMountpoint = mountpoint
return
}
+2 -2
View File
@@ -19,12 +19,12 @@ func TestFunctional(t *testing.T) {
if *fstest.RemoteName != "" {
t.Skip("Skip on non local")
}
vfstest.RunTests(t, true, vfscommon.CacheModeOff, 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, actualMountpoint string, err error) {
unmountResultChan := make(chan (error), 1)
unmount = func() error {
unmountResultChan <- nil
return nil
}
return unmountResultChan, unmount, nil
return unmountResultChan, unmount, mountpoint, nil
})
}