mirror of
https://github.com/rclone/rclone.git
synced 2024-11-30 12:34:04 +08:00
190 lines
8.4 KiB
Go
190 lines
8.4 KiB
Go
|
// +build cmount
|
||
|
// +build cgo
|
||
|
// +build windows
|
||
|
|
||
|
package cmount
|
||
|
|
||
|
import (
|
||
|
"os"
|
||
|
"path/filepath"
|
||
|
"regexp"
|
||
|
|
||
|
"github.com/pkg/errors"
|
||
|
"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.
|
||
|
// 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 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(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 "", errors.Wrap(err, "failed to retrieve mountpoint path information")
|
||
|
}
|
||
|
//if isDriveRootPath(mountpath) { // Assume intention with "X:\" was "X:"
|
||
|
// mountpoint = 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
|
||
|
}
|
||
|
parent := filepath.Join(mountpath, "..")
|
||
|
if parent == "" || parent == "." {
|
||
|
return "", errors.New("mountpoint directory is not valid: " + parent)
|
||
|
}
|
||
|
if os.IsPathSeparator(parent[len(parent)-1]) { // Ends in a separator only if it is the root directory
|
||
|
return "", errors.New("mountpoint directory is at root: " + parent)
|
||
|
}
|
||
|
if _, err := os.Stat(parent); err != nil {
|
||
|
if os.IsNotExist(err) {
|
||
|
return "", errors.New("parent of mountpoint directory does not exist: " + parent)
|
||
|
}
|
||
|
return "", errors.Wrap(err, "failed to retrieve mountpoint directory parent information")
|
||
|
}
|
||
|
}
|
||
|
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(mountpath string, opt *mountlib.Options) (mountpoint string, err error) {
|
||
|
|
||
|
// First 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(mountpath, opt)
|
||
|
}
|
||
|
|
||
|
// Second 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
|
||
|
}
|