e2afd00118
See: #6234
201 lines
8.8 KiB
Go
201 lines
8.8 KiB
Go
//go:build cmount && windows
|
|
// +build cmount,windows
|
|
|
|
package cmount
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
|
|
"github.com/rclone/rclone/cmd/mountlib"
|
|
"github.com/rclone/rclone/fs"
|
|
"github.com/rclone/rclone/lib/file"
|
|
)
|
|
|
|
var isDriveRegex = regexp.MustCompile(`^[a-zA-Z]\:$`)
|
|
var isDriveRootPathRegex = regexp.MustCompile(`^[a-zA-Z]\:\\$`)
|
|
var isDriveOrRootPathRegex = regexp.MustCompile(`^[a-zA-Z]\:\\?$`)
|
|
var isNetworkSharePathRegex = regexp.MustCompile(`^\\\\[^\\\?]+\\[^\\]`)
|
|
|
|
// isNetworkSharePath returns true if the given string is a valid network share path,
|
|
// in the basic UNC format "\\Server\Share\Path", where the first two path components
|
|
// are required ("\\Server\Share", which represents the volume).
|
|
// Extended-length UNC format "\\?\UNC\Server\Share\Path" is not considered, as it is
|
|
// not supported by cgofuse/winfsp, so returns false for any paths with prefix "\\?\".
|
|
// Note: There is a UNCPath function in lib/file, but it refers to any extended-length
|
|
// paths using prefix "\\?\", and not necessarily network resource UNC paths.
|
|
func isNetworkSharePath(l string) bool {
|
|
return isNetworkSharePathRegex.MatchString(l)
|
|
}
|
|
|
|
// isDrive returns true if given string is a drive letter followed by the volume separator, e.g. "X:".
|
|
// This is the format supported by cgofuse/winfsp for mounting as drive.
|
|
// Extended-length format "\\?\X:" is not considered, as it is not supported by cgofuse/winfsp.
|
|
func isDrive(l string) bool {
|
|
return isDriveRegex.MatchString(l)
|
|
}
|
|
|
|
// isDriveRootPath returns true if given string is a drive letter followed by the volume separator,
|
|
// as well as a path separator, e.g. "X:\". This is a format often used instead of the format without the
|
|
// trailing path separator to denote a drive or volume, in addition to representing the drive's root directory.
|
|
// This format is not accepted by cgofuse/winfsp for mounting as drive, but can easily be by trimming off
|
|
// the path separator. Extended-length format "\\?\X:\" is not considered.
|
|
func isDriveRootPath(l string) bool {
|
|
return isDriveRootPathRegex.MatchString(l)
|
|
}
|
|
|
|
// isDriveOrRootPath returns true if given string is a drive letter followed by the volume separator,
|
|
// and optionally a path separator. See isDrive and isDriveRootPath functions.
|
|
func isDriveOrRootPath(l string) bool {
|
|
return isDriveOrRootPathRegex.MatchString(l)
|
|
}
|
|
|
|
// isDefaultPath returns true if given string is a special keyword used to trigger default mount.
|
|
func isDefaultPath(l string) bool {
|
|
return l == "" || l == "*"
|
|
}
|
|
|
|
// getUnusedDrive find unused drive letter and returns string with drive letter followed by volume separator.
|
|
func getUnusedDrive() (string, error) {
|
|
driveLetter := file.FindUnusedDriveLetter()
|
|
if driveLetter == 0 {
|
|
return "", errors.New("could not find unused drive letter")
|
|
}
|
|
mountpoint := string(driveLetter) + ":" // Drive letter with volume separator only, no trailing backslash, which is what cgofuse/winfsp expects
|
|
fs.Logf(nil, "Assigning drive letter %q", mountpoint)
|
|
return mountpoint, nil
|
|
}
|
|
|
|
// handleDefaultMountpath handles the case where mount path is not set, or set to a special keyword.
|
|
// This will automatically pick an unused drive letter to use as mountpoint.
|
|
func handleDefaultMountpath() (string, error) {
|
|
return getUnusedDrive()
|
|
}
|
|
|
|
// handleNetworkShareMountpath handles the case where mount path is a network share path.
|
|
// Sets volume name option and returns a mountpoint string.
|
|
func handleNetworkShareMountpath(mountpath string, opt *mountlib.Options) (string, error) {
|
|
// Assuming mount path is a valid network share path (UNC format, "\\Server\Share").
|
|
// Always mount as network drive, regardless of the NetworkMode option.
|
|
// Find an unused drive letter to use as mountpoint, the supplied path can
|
|
// be used as volume prefix (network share path) instead of mountpoint.
|
|
if !opt.NetworkMode {
|
|
fs.Debugf(nil, "Forcing --network-mode because mountpoint path is network share UNC format")
|
|
opt.NetworkMode = true
|
|
}
|
|
mountpoint, err := getUnusedDrive()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return mountpoint, nil
|
|
}
|
|
|
|
// handleLocalMountpath handles the case where mount path is a local file system path.
|
|
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.
|
|
// 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.
|
|
if _, err := os.Stat(mountpath); err == nil {
|
|
return "", errors.New("mountpoint path already exists: " + mountpath)
|
|
} else if !os.IsNotExist(err) {
|
|
return "", fmt.Errorf("failed to retrieve mountpoint path information: %w", err)
|
|
}
|
|
if isDriveRootPath(mountpath) { // Assume intention with "X:\" was "X:"
|
|
mountpath = mountpath[:len(mountpath)-1] // WinFsp needs drive mountpoints without trailing path separator
|
|
}
|
|
if !isDrive(mountpath) {
|
|
// Assuming directory path, since it is not a pure drive letter string such as "X:".
|
|
// Drive letter string can be used as is, since we have already checked it does not exist,
|
|
// but directory path needs more checks.
|
|
if opt.NetworkMode {
|
|
fs.Errorf(nil, "Ignoring --network-mode as it is not supported with directory mountpoint")
|
|
opt.NetworkMode = false
|
|
}
|
|
var err error
|
|
if mountpath, err = filepath.Abs(mountpath); err != nil { // Ensures parent is found but also more informative log messages
|
|
return "", fmt.Errorf("mountpoint path is not valid: %s: %w", mountpath, err)
|
|
}
|
|
parent := filepath.Join(mountpath, "..")
|
|
if _, err = os.Stat(parent); err != nil {
|
|
if os.IsNotExist(err) {
|
|
return "", errors.New("parent of mountpoint directory does not exist: " + parent)
|
|
}
|
|
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
|
|
}
|
|
|
|
// handleVolumeName handles the volume name option.
|
|
func handleVolumeName(opt *mountlib.Options, volumeName string) {
|
|
// If volumeName parameter is set, then just set that into options replacing any existing value.
|
|
// Else, ensure the volume name option is a valid network share UNC path if network mode,
|
|
// and ensure network mode if configured volume name is already UNC path.
|
|
if volumeName != "" {
|
|
opt.VolumeName = volumeName
|
|
} else if opt.VolumeName != "" { // Should always be true due to code in mountlib caller
|
|
// Use value of given volume name option, but check if it is disk volume name or network volume prefix
|
|
if isNetworkSharePath(opt.VolumeName) {
|
|
// Specified volume name is network share UNC path, assume network mode and use it as volume prefix
|
|
opt.VolumeName = opt.VolumeName[1:] // WinFsp requires volume prefix as UNC-like path but with only a single backslash
|
|
if !opt.NetworkMode {
|
|
// Specified volume name is network share UNC path, force network mode and use it as volume prefix
|
|
fs.Debugf(nil, "Forcing network mode due to network share (UNC) volume name")
|
|
opt.NetworkMode = true
|
|
}
|
|
} else if opt.NetworkMode {
|
|
// Plain volume name treated as share name in network mode, append to hard coded "\\server" prefix to get full volume prefix.
|
|
opt.VolumeName = "\\server\\" + opt.VolumeName
|
|
}
|
|
} else if opt.NetworkMode {
|
|
// Hard coded default
|
|
opt.VolumeName = "\\server\\share"
|
|
}
|
|
}
|
|
|
|
// getMountpoint handles mounting details on Windows,
|
|
// where disk and network based file systems are treated different.
|
|
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")
|
|
}
|
|
|
|
// Handle mountpath
|
|
var volumeName string
|
|
if isDefaultPath(mountpath) {
|
|
// Mount path indicates defaults, which will automatically pick an unused drive letter.
|
|
mountpoint, err = handleDefaultMountpath()
|
|
} else if isNetworkSharePath(mountpath) {
|
|
// Mount path is a valid network share path (UNC format, "\\Server\Share" prefix).
|
|
mountpoint, err = handleNetworkShareMountpath(mountpath, opt)
|
|
// In this case the volume name is taken from the mount path, will replace any existing volume name option.
|
|
volumeName = mountpath[1:] // WinFsp requires volume prefix as UNC-like path but with only a single backslash
|
|
} else {
|
|
// Mount path is drive letter or directory path.
|
|
mountpoint, err = handleLocalMountpath(f, mountpath, opt)
|
|
}
|
|
|
|
// Handle volume name
|
|
handleVolumeName(opt, volumeName)
|
|
|
|
// Done, return mountpoint to be used, together with updated mount options.
|
|
if opt.NetworkMode {
|
|
fs.Debugf(nil, "Network mode mounting is enabled")
|
|
} else {
|
|
fs.Debugf(nil, "Network mode mounting is disabled")
|
|
}
|
|
return
|
|
}
|