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
+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)