mount: avoid incorrect or premature overlap check on windows

See: #6234
This commit is contained in:
albertony 2022-06-11 16:18:48 +02:00
parent 5b82576dbf
commit e2afd00118
7 changed files with 60 additions and 41 deletions

View File

@ -149,14 +149,14 @@ func waitFor(fn func() bool) (ok bool) {
// report an error when fusermount is called. // 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, error) {
// Get mountpoint using OS specific logic // Get mountpoint using OS specific logic
mountpoint, err := getMountpoint(mountPath, opt) f := VFS.Fs()
mountpoint, err := getMountpoint(f, mountPath, opt)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
fs.Debugf(nil, "Mounting on %q (%q)", mountpoint, opt.VolumeName) fs.Debugf(nil, "Mounting on %q (%q)", mountpoint, opt.VolumeName)
// Create underlying FS // Create underlying FS
f := VFS.Fs()
fsys := NewFS(VFS) fsys := NewFS(VFS)
host := fuse.NewFileSystemHost(fsys) host := fuse.NewFileSystemHost(fsys)
host.SetCapReaddirPlus(true) // only works on Windows host.SetCapReaddirPlus(true) // only works on Windows

View File

@ -9,9 +9,10 @@ import (
"os" "os"
"github.com/rclone/rclone/cmd/mountlib" "github.com/rclone/rclone/cmd/mountlib"
"github.com/rclone/rclone/fs"
) )
func getMountpoint(mountPath string, opt *mountlib.Options) (string, error) { func getMountpoint(f fs.Fs, mountPath string, opt *mountlib.Options) (string, error) {
fi, err := os.Stat(mountPath) fi, err := os.Stat(mountPath)
if err != nil { if err != nil {
return "", fmt.Errorf("failed to retrieve mount path information: %w", err) return "", fmt.Errorf("failed to retrieve mount path information: %w", err)
@ -19,5 +20,11 @@ func getMountpoint(mountPath string, opt *mountlib.Options) (string, error) {
if !fi.IsDir() { if !fi.IsDir() {
return "", errors.New("mount path is not a directory") return "", errors.New("mount path is not a directory")
} }
if err = mountlib.CheckOverlap(f, mountPath); err != nil {
return "", err
}
if err = mountlib.CheckAllowNonEmpty(mountPath, opt); err != nil {
return "", err
}
return mountPath, nil return mountPath, nil
} }

View File

@ -94,7 +94,7 @@ func handleNetworkShareMountpath(mountpath string, opt *mountlib.Options) (strin
} }
// handleLocalMountpath handles the case where mount path is a local file system path. // handleLocalMountpath handles the case where mount path is a local file system path.
func handleLocalMountpath(mountpath string, opt *mountlib.Options) (string, error) { func handleLocalMountpath(f fs.Fs, mountpath string, opt *mountlib.Options) (string, error) {
// Assuming path is drive letter or directory path, not network share (UNC) path. // Assuming path is drive letter or directory path, not network share (UNC) path.
// If drive letter: Must be given as a single character followed by ":" and nothing else. // If drive letter: Must be given as a single character followed by ":" and nothing else.
// Else, assume directory path: Directory must not exist, but its parent must. // Else, assume directory path: Directory must not exist, but its parent must.
@ -125,6 +125,9 @@ func handleLocalMountpath(mountpath string, opt *mountlib.Options) (string, erro
} }
return "", fmt.Errorf("failed to retrieve mountpoint directory parent information: %w", err) return "", fmt.Errorf("failed to retrieve mountpoint directory parent information: %w", err)
} }
if err = mountlib.CheckOverlap(f, mountpath); err != nil {
return "", err
}
} }
return mountpath, nil return mountpath, nil
} }
@ -158,9 +161,19 @@ func handleVolumeName(opt *mountlib.Options, volumeName string) {
// getMountpoint handles mounting details on Windows, // getMountpoint handles mounting details on Windows,
// where disk and network based file systems are treated different. // where disk and network based file systems are treated different.
func getMountpoint(mountpath string, opt *mountlib.Options) (mountpoint string, err error) { func getMountpoint(f fs.Fs, mountpath string, opt *mountlib.Options) (mountpoint string, err error) {
// Inform about some options not relevant in this mode
if opt.AllowNonEmpty {
fs.Logf(nil, "--allow-non-empty flag does nothing on Windows")
}
if opt.AllowRoot {
fs.Logf(nil, "--allow-root flag does nothing on Windows")
}
if opt.AllowOther {
fs.Logf(nil, "--allow-other flag does nothing on Windows")
}
// First handle mountpath // Handle mountpath
var volumeName string var volumeName string
if isDefaultPath(mountpath) { if isDefaultPath(mountpath) {
// Mount path indicates defaults, which will automatically pick an unused drive letter. // Mount path indicates defaults, which will automatically pick an unused drive letter.
@ -172,10 +185,10 @@ func getMountpoint(mountpath string, opt *mountlib.Options) (mountpoint string,
volumeName = mountpath[1:] // WinFsp requires volume prefix as UNC-like path but with only a single backslash volumeName = mountpath[1:] // WinFsp requires volume prefix as UNC-like path but with only a single backslash
} else { } else {
// Mount path is drive letter or directory path. // Mount path is drive letter or directory path.
mountpoint, err = handleLocalMountpath(mountpath, opt) mountpoint, err = handleLocalMountpath(f, mountpath, opt)
} }
// Second handle volume name // Handle volume name
handleVolumeName(opt, volumeName) handleVolumeName(opt, volumeName)
// Done, return mountpoint to be used, together with updated mount options. // Done, return mountpoint to be used, together with updated mount options.

View File

@ -69,9 +69,17 @@ func mountOptions(VFS *vfs.VFS, device string, opt *mountlib.Options) (options [
// returns an error, and an error channel for the serve process to // returns an error, and an error channel for the serve process to
// report an error when fusermount is called. // 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, error) {
f := VFS.Fs()
if runtime.GOOS == "darwin" { if runtime.GOOS == "darwin" {
fs.Logf(nil, "macOS users: please try \"rclone cmount\" as it will be the default in v1.54") fs.Logf(nil, "macOS users: please try \"rclone cmount\" as it will be the default in v1.54")
} }
if err := mountlib.CheckOverlap(f, mountpoint); err != nil {
return nil, nil, err
}
if err := mountlib.CheckAllowNonEmpty(mountpoint, opt); err != nil {
return nil, nil, err
}
fs.Debugf(f, "Mounting on %q", mountpoint)
if opt.DebugFUSE { if opt.DebugFUSE {
fuse.Debug = func(msg interface{}) { fuse.Debug = func(msg interface{}) {
@ -79,8 +87,6 @@ func mount(VFS *vfs.VFS, mountpoint string, opt *mountlib.Options) (<-chan error
} }
} }
f := VFS.Fs()
fs.Debugf(f, "Mounting on %q", mountpoint)
c, err := fuse.Mount(mountpoint, mountOptions(VFS, opt.DeviceName, opt)...) c, err := fuse.Mount(mountpoint, mountOptions(VFS, opt.DeviceName, opt)...)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err

View File

@ -145,9 +145,16 @@ func mountOptions(fsys *FS, f fs.Fs, opt *mountlib.Options) (mountOpts *fuse.Mou
// report an error when fusermount is called. // 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, error) {
f := VFS.Fs() f := VFS.Fs()
if err := mountlib.CheckOverlap(f, mountpoint); err != nil {
return nil, nil, err
}
if err := mountlib.CheckAllowNonEmpty(mountpoint, opt); err != nil {
return nil, nil, err
}
fs.Debugf(f, "Mounting on %q", mountpoint) fs.Debugf(f, "Mounting on %q", mountpoint)
fsys := NewFS(VFS, opt) fsys := NewFS(VFS, opt)
// nodeFsOpts := &fusefs.PathNodeFsOptions{ // nodeFsOpts := &fusefs.PathNodeFsOptions{
// ClientInodes: false, // ClientInodes: false,
// Debug: mountlib.DebugFUSE, // Debug: mountlib.DebugFUSE,

View File

@ -237,13 +237,8 @@ func NewMountCommand(commandName string, hidden bool, mount MountFn) *cobra.Comm
// Mount the remote at mountpoint // Mount the remote at mountpoint
func (m *MountPoint) Mount() (daemon *os.Process, err error) { func (m *MountPoint) Mount() (daemon *os.Process, err error) {
if err = m.CheckOverlap(); err != nil {
return nil, err
}
if err = m.CheckAllowed(); err != nil { // Ensure sensible defaults
return nil, err
}
m.SetVolumeName(m.MountOpt.VolumeName) m.SetVolumeName(m.MountOpt.VolumeName)
m.SetDeviceName(m.MountOpt.DeviceName) m.SetDeviceName(m.MountOpt.DeviceName)

View File

@ -34,22 +34,22 @@ func ClipBlocks(b *uint64) {
} }
} }
// CheckOverlap checks that root doesn't overlap with mountpoint // CheckOverlap checks that root doesn't overlap with a mountpoint
func (m *MountPoint) CheckOverlap() error { func CheckOverlap(f fs.Fs, mountpoint string) error {
name := m.Fs.Name() name := f.Name()
if name != "" && name != "local" { if name != "" && name != "local" {
return nil return nil
} }
rootAbs := absPath(m.Fs.Root()) rootAbs := absPath(f.Root())
mountpointAbs := absPath(m.MountPoint) mountpointAbs := absPath(mountpoint)
if strings.HasPrefix(rootAbs, mountpointAbs) || strings.HasPrefix(mountpointAbs, rootAbs) { if strings.HasPrefix(rootAbs, mountpointAbs) || strings.HasPrefix(mountpointAbs, rootAbs) {
const msg = "mount point %q and directory to be mounted %q mustn't overlap" const msg = "mount point %q (%q) and directory to be mounted %q (%q) mustn't overlap"
return fmt.Errorf(msg, m.MountPoint, m.Fs.Root()) return fmt.Errorf(msg, mountpoint, mountpointAbs, f.Root(), rootAbs)
} }
return nil return nil
} }
// absPath is a helper function for MountPoint.CheckOverlap // absPath is a helper function for CheckOverlap
func absPath(path string) string { func absPath(path string) string {
if abs, err := filepath.EvalSymlinks(path); err == nil { if abs, err := filepath.EvalSymlinks(path); err == nil {
path = abs path = abs
@ -58,30 +58,21 @@ func absPath(path string) string {
path = abs path = abs
} }
path = filepath.ToSlash(path) path = filepath.ToSlash(path)
if runtime.GOOS == "windows" {
// Removes any UNC long path prefix to make sure a simple HasPrefix test
// in CheckOverlap works when one is UNC (root) and one is not (mountpoint).
path = strings.TrimPrefix(path, `//?/`)
}
if !strings.HasSuffix(path, "/") { if !strings.HasSuffix(path, "/") {
path += "/" path += "/"
} }
return path return path
} }
// CheckAllowed informs about ignored flags on Windows. If not on Windows // CheckAllowNonEmpty checks --allow-non-empty flag, and if not used verifies that mountpoint is empty.
// and not --allow-non-empty flag is used, verify that mountpoint is empty. func CheckAllowNonEmpty(mountpoint string, opt *Options) error {
func (m *MountPoint) CheckAllowed() error {
opt := &m.MountOpt
if runtime.GOOS == "windows" {
if opt.AllowNonEmpty {
fs.Logf(nil, "--allow-non-empty flag does nothing on Windows")
}
if opt.AllowRoot {
fs.Logf(nil, "--allow-root flag does nothing on Windows")
}
if opt.AllowOther {
fs.Logf(nil, "--allow-other flag does nothing on Windows")
}
return nil
}
if !opt.AllowNonEmpty { if !opt.AllowNonEmpty {
return CheckMountEmpty(m.MountPoint) return CheckMountEmpty(mountpoint)
} }
return nil return nil
} }