1
0
mirror of https://github.com/rclone/rclone.git synced 2025-12-16 16:23:22 +00:00

Compare commits

..

2 Commits

Author SHA1 Message Date
albertony
0093e23e42 mount: changed handling of volume name (Windows and OSX)
Fixes an issue on Windows where mounting the local filesystem in network mode failed
when not using option --volname. Reason was that the volume name in network mode
is a network share path in the basic UNC format, and characters that are invalid
in regular file and directory names are also invalid in such a path. And the default
volume name would typically include a '?', which is invalid, from the unc path of
the local, e.g. "\\server\\? C  Temp".

The fix is to use an encoder to encode invalid characters such as '?' with the unicode
equivalent, similar to how rclone encodes filesystem paths in normal operations,
when mounting in network mode. Also performs some automatic cleanup of path separators,
but in general, tries to be conservative on restrictions, and instead rely on --volname
being set to something realistic.

Existing strategy to replace the two characters ':' and '/' with space, regardless of
mounting mode variant, was removed. For network mode the new approach handles these in
a better way. Also the existing method did not apply at all when using the implicit
network mode where volume names are taken from mountpath instead of volname option
("rclone mount remote:path/to/files \\cloud\remote"). For non-network mode they were not
needed.

Default volume names, when not specified by user, will be different with this change.

See: #6234
2023-03-03 20:59:45 +01:00
albertony
11443e4491 cmount: use network mode by default on windows 2023-03-03 20:59:45 +01:00
24 changed files with 99 additions and 270 deletions

View File

@@ -761,7 +761,7 @@ func (f *Fs) shouldRetry(ctx context.Context, err error) (bool, error) {
} else if f.opt.StopOnDownloadLimit && reason == "downloadQuotaExceeded" {
fs.Errorf(f, "Received download limit error: %v", err)
return false, fserrors.FatalError(err)
} else if f.opt.StopOnUploadLimit && (reason == "quotaExceeded" || reason == "storageQuotaExceeded") {
} else if f.opt.StopOnUploadLimit && reason == "quotaExceeded" {
fs.Errorf(f, "Received upload limit error: %v", err)
return false, fserrors.FatalError(err)
} else if f.opt.StopOnUploadLimit && reason == "teamDriveFileLimitExceeded" {

View File

@@ -243,15 +243,6 @@ func (f *Fs) InternalTestShouldRetry(t *testing.T) {
quotaExceededRetry, quotaExceededError := f.shouldRetry(ctx, &generic403)
assert.False(t, quotaExceededRetry)
assert.Equal(t, quotaExceededError, expectedQuotaError)
sqEItem := googleapi.ErrorItem{
Reason: "storageQuotaExceeded",
}
generic403.Errors[0] = sqEItem
expectedStorageQuotaError := fserrors.FatalError(&generic403)
storageQuotaExceededRetry, storageQuotaExceededError := f.shouldRetry(ctx, &generic403)
assert.False(t, storageQuotaExceededRetry)
assert.Equal(t, storageQuotaExceededError, expectedStorageQuotaError)
}
func (f *Fs) InternalTestDocumentImport(t *testing.T) {

View File

@@ -15,7 +15,7 @@ import (
"sync"
"time"
"github.com/jlaffaye/ftp"
"github.com/rclone/ftp"
"github.com/rclone/rclone/fs"
"github.com/rclone/rclone/fs/accounting"
"github.com/rclone/rclone/fs/config"
@@ -315,27 +315,19 @@ func (dl *debugLog) Write(p []byte) (n int, err error) {
return len(p), nil
}
// returns true if this FTP error should be retried
func isRetriableFtpError(err error) bool {
switch errX := err.(type) {
case *textproto.Error:
switch errX.Code {
case ftp.StatusNotAvailable, ftp.StatusTransfertAborted:
return true
}
}
return false
}
// shouldRetry returns a boolean as to whether this err deserve to be
// retried. It returns the err as a convenience
func shouldRetry(ctx context.Context, err error) (bool, error) {
if fserrors.ContextError(ctx, &err) {
return false, err
}
if isRetriableFtpError(err) {
switch errX := err.(type) {
case *textproto.Error:
switch errX.Code {
case ftp.StatusNotAvailable:
return true, err
}
}
return fserrors.ShouldRetry(err), err
}
@@ -1194,26 +1186,15 @@ func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (rc io.Read
}
}
}
var (
fd *ftp.Response
c *ftp.ServerConn
)
err = o.fs.pacer.Call(func() (bool, error) {
c, err = o.fs.getFtpConnection(ctx)
if err != nil {
return false, err // getFtpConnection has retries already
}
fd, err = c.RetrFrom(o.fs.opt.Enc.FromStandardPath(path), uint64(offset))
if err != nil {
o.fs.putFtpConnection(&c, err)
}
return shouldRetry(ctx, err)
})
c, err := o.fs.getFtpConnection(ctx)
if err != nil {
return nil, fmt.Errorf("open: %w", err)
}
fd, err := c.RetrFrom(o.fs.opt.Enc.FromStandardPath(path), uint64(offset))
if err != nil {
o.fs.putFtpConnection(&c, err)
return nil, fmt.Errorf("open: %w", err)
}
rc = &ftpReadCloser{rc: readers.NewLimitedReadCloser(fd, limit), c: c, f: o.fs}
return rc, nil
}

View File

@@ -82,8 +82,7 @@ func init() {
saFile, _ := m.Get("service_account_file")
saCreds, _ := m.Get("service_account_credentials")
anonymous, _ := m.Get("anonymous")
envAuth, _ := m.Get("env_auth")
if saFile != "" || saCreds != "" || anonymous == "true" || envAuth == "true" {
if saFile != "" || saCreds != "" || anonymous == "true" {
return nil, nil
}
return oauthutil.ConfigOut("", &oauthutil.Options{
@@ -331,17 +330,6 @@ can't check the size and hash but the file contents will be decompressed.
Default: (encoder.Base |
encoder.EncodeCrLf |
encoder.EncodeInvalidUtf8),
}, {
Name: "env_auth",
Help: "Get GCP IAM credentials from runtime (environment variables or instance meta data if no env vars).\n\nOnly applies if service_account_file and service_account_credentials is blank.",
Default: false,
Examples: []fs.OptionExample{{
Value: "false",
Help: "Enter credentials in the next step.",
}, {
Value: "true",
Help: "Get GCP IAM credentials from the environment (env vars or IAM).",
}},
}}...),
})
}
@@ -361,7 +349,6 @@ type Options struct {
Decompress bool `config:"decompress"`
Endpoint string `config:"endpoint"`
Enc encoder.MultiEncoder `config:"encoding"`
EnvAuth bool `config:"env_auth"`
}
// Fs represents a remote storage server
@@ -513,11 +500,6 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
if err != nil {
return nil, fmt.Errorf("failed configuring Google Cloud Storage Service Account: %w", err)
}
} else if opt.EnvAuth {
oAuthClient, err = google.DefaultClient(ctx, storage.DevstorageFullControlScope)
if err != nil {
return nil, fmt.Errorf("failed to configure Google Cloud Storage: %w", err)
}
} else {
oAuthClient, _, err = oauthutil.NewClient(ctx, name, m, storageConfig)
if err != nil {

View File

@@ -524,10 +524,6 @@ func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err e
if f.opt.FollowSymlinks && (mode&os.ModeSymlink) != 0 {
localPath := filepath.Join(fsDirPath, name)
fi, err = os.Stat(localPath)
// Quietly skip errors on excluded files and directories
if err != nil && useFilter && !filter.IncludeRemote(newRemote) {
continue
}
if os.IsNotExist(err) || isCircularSymlinkError(err) {
// Skip bad symlinks and circular symlinks
err = fserrors.NoRetryError(fmt.Errorf("symlink: %w", err))

View File

@@ -14,7 +14,6 @@ import (
"time"
"github.com/rclone/rclone/fs"
"github.com/rclone/rclone/fs/accounting"
"github.com/rclone/rclone/fs/config/configmap"
"github.com/rclone/rclone/fs/filter"
"github.com/rclone/rclone/fs/hash"
@@ -396,73 +395,3 @@ func TestFilter(t *testing.T) {
sort.Sort(entries)
require.Equal(t, "[included]", fmt.Sprint(entries))
}
func TestFilterSymlink(t *testing.T) {
ctx := context.Background()
r := fstest.NewRun(t)
defer r.Finalise()
when := time.Now()
f := r.Flocal.(*Fs)
// Create a file, a directory, a symlink to a file, a symlink to a directory and a dangling symlink
r.WriteFile("included.file", "included file", when)
r.WriteFile("included.dir/included.sub.file", "included sub file", when)
require.NoError(t, os.Symlink("included.file", filepath.Join(r.LocalName, "included.file.link")))
require.NoError(t, os.Symlink("included.dir", filepath.Join(r.LocalName, "included.dir.link")))
require.NoError(t, os.Symlink("dangling", filepath.Join(r.LocalName, "dangling.link")))
// Set fs into "-L" mode
f.opt.FollowSymlinks = true
f.opt.TranslateSymlinks = false
f.lstat = os.Stat
// Set fs into "-l" mode
// f.opt.FollowSymlinks = false
// f.opt.TranslateSymlinks = true
// f.lstat = os.Lstat
// Check set up for filtering
assert.True(t, f.Features().FilterAware)
// Reset global error count
accounting.Stats(ctx).ResetErrors()
assert.Equal(t, int64(0), accounting.Stats(ctx).GetErrors(), "global errors found")
// Add a filter
ctx, fi := filter.AddConfig(ctx)
require.NoError(t, fi.AddRule("+ included.file"))
require.NoError(t, fi.AddRule("+ included.file.link"))
require.NoError(t, fi.AddRule("+ included.dir/**"))
require.NoError(t, fi.AddRule("+ included.dir.link/**"))
require.NoError(t, fi.AddRule("- *"))
// Check listing without use filter flag
entries, err := f.List(ctx, "")
require.NoError(t, err)
// Check 1 global errors one for each dangling symlink
assert.Equal(t, int64(1), accounting.Stats(ctx).GetErrors(), "global errors found")
accounting.Stats(ctx).ResetErrors()
sort.Sort(entries)
require.Equal(t, "[included.dir included.dir.link included.file included.file.link]", fmt.Sprint(entries))
// Add user filter flag
ctx = filter.SetUseFilter(ctx, true)
// Check listing with use filter flag
entries, err = f.List(ctx, "")
require.NoError(t, err)
assert.Equal(t, int64(0), accounting.Stats(ctx).GetErrors(), "global errors found")
sort.Sort(entries)
require.Equal(t, "[included.dir included.dir.link included.file included.file.link]", fmt.Sprint(entries))
// Check listing through a symlink still works
entries, err = f.List(ctx, "included.dir")
require.NoError(t, err)
assert.Equal(t, int64(0), accounting.Stats(ctx).GetErrors(), "global errors found")
sort.Sort(entries)
require.Equal(t, "[included.dir/included.sub.file]", fmt.Sprint(entries))
}

View File

@@ -5160,7 +5160,7 @@ func (o *Object) uploadMultipart(ctx context.Context, req *s3.PutObjectInput, si
// create checksum of buffer for integrity checking
md5sumBinary := md5.Sum(buf)
//addMd5(&md5sumBinary, partNum-1)
addMd5(&md5sumBinary, partNum-1)
md5sum := base64.StdEncoding.EncodeToString(md5sumBinary[:])
err = f.pacer.Call(func() (bool, error) {
@@ -5191,15 +5191,7 @@ func (o *Object) uploadMultipart(ctx context.Context, req *s3.PutObjectInput, si
ETag: uout.ETag,
})
partsMu.Unlock()
if uout.ETag != nil {
etag := strings.Trim(strings.ToLower(*uout.ETag), `"`)
etagBinary, err := hex.DecodeString(etag)
if err != nil || len(etagBinary) != md5.Size {
fs.Errorf(o, "Failed to decode ETag %q: %v", etag, err)
} else {
addMd5((*[md5.Size]byte)(etagBinary), partNum-1)
}
}
return false, nil
})
if err != nil {
@@ -5594,7 +5586,7 @@ func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, op
o.setMetaData(head)
// Check multipart upload ETag if required
if o.fs.opt.UseMultipartEtag.Value /*&& !o.fs.etagIsNotMD5*/ && wantETag != "" && head.ETag != nil && *head.ETag != "" {
if o.fs.opt.UseMultipartEtag.Value && !o.fs.etagIsNotMD5 && wantETag != "" && head.ETag != nil && *head.ETag != "" {
gotETag := strings.Trim(strings.ToLower(*head.ETag), `"`)
if wantETag != gotETag {
return fmt.Errorf("multipart upload corrupted: Etag differ: expecting %s but got %s", wantETag, gotETag)

View File

@@ -37,7 +37,6 @@ func (f *Fs) dial(ctx context.Context, network, addr string) (*conn, error) {
User: f.opt.User,
Password: pass,
Domain: f.opt.Domain,
TargetSPN: f.opt.SPN,
},
}

View File

@@ -60,17 +60,6 @@ func init() {
Name: "domain",
Help: "Domain name for NTLM authentication.",
Default: "WORKGROUP",
}, {
Name: "spn",
Help: `Service principal name.
Rclone presents this name to the server. Some servers use this as further
authentication, and it often needs to be set for clusters. For example:
cifs/remotehost:1020
Leave blank if not sure.
`,
}, {
Name: "idle_timeout",
Default: fs.Duration(60 * time.Second),
@@ -120,7 +109,6 @@ type Options struct {
User string `config:"user"`
Pass string `config:"pass"`
Domain string `config:"domain"`
SPN string `config:"spn"`
HideSpecial bool `config:"hide_special_share"`
CaseInsensitive bool `config:"case_insensitive"`
IdleTimeout fs.Duration `config:"idle_timeout"`

View File

@@ -26,5 +26,6 @@ func getMountpoint(f fs.Fs, mountPath string, opt *mountlib.Options) (string, er
if err = mountlib.CheckAllowNonEmpty(mountPath, opt); err != nil {
return "", err
}
opt.VolumeName = mountlib.MakeVolumeNameValidOnUnix(opt.VolumeName)
return mountPath, nil
}

View File

@@ -9,9 +9,11 @@ import (
"os"
"path/filepath"
"regexp"
"strings"
"github.com/rclone/rclone/cmd/mountlib"
"github.com/rclone/rclone/fs"
"github.com/rclone/rclone/lib/encoder"
"github.com/rclone/rclone/lib/file"
)
@@ -19,10 +21,13 @@ 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(`^\\\\[^\\\?]+\\[^\\]`)
var isAnyPathSeparatorRegex = regexp.MustCompile(`[/\\]+`) // Matches any path separators, slash or backslash, or sequences of them
// 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).
// isNetworkSharePath returns true if the given string is a network share path,
// in the basic UNC format "\\Server\Share\Path". The first two path components
// are required ("\\Server\Share"), and represents the volume. The rest of the
// string can be anything, i.e. can be a nested path ("\\Server\Share\Path\Path\Path").
// Actual validity of the path, e.g. if it contains invalid characters, is not considered.
// 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
@@ -111,7 +116,7 @@ func handleLocalMountpath(f fs.Fs, mountpath string, opt *mountlib.Options) (str
// 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")
fs.Debugf(nil, "Ignoring --network-mode as it is not supported with directory mountpoint")
opt.NetworkMode = false
}
var err error
@@ -132,30 +137,47 @@ func handleLocalMountpath(f fs.Fs, mountpath string, opt *mountlib.Options) (str
return mountpath, nil
}
// networkSharePathEncoder is an encoder used to make strings valid as (part of) Windows network share UNC paths
const networkSharePathEncoder = (encoder.EncodeZero | // NUL(0x00)
encoder.EncodeCtl | // CTRL(0x01-0x1F)
encoder.EncodeDel | // DEL(0x7F)
encoder.EncodeWin | // :?"*<>|
encoder.EncodeInvalidUtf8) // Also encode invalid UTF-8 bytes as Go can't convert them to UTF-16.
// encodeNetworkSharePath makes a string valid to use as (part of) a Windows network share UNC path.
// Using backslash as path separator here, but forward slashes would also be treated as
// path separators by the library, and therefore does not encode either of them. For convenience,
// normalizes to backslashes-only. UNC paths always start with two path separators, but WinFsp
// requires volume prefix as UNC-like path but with only a single backslash prefix, and multiple
// separators are not valid in any other parts of network share paths, so therefore (unlike what
// filepath.FromSlash would do) replaces multiple separators with a single one (like filpath.Clean
// would do, but it does also more). A trailing path separator would just be ignored, but we
// remove it here as well for convenience.
func encodeNetworkSharePath(volumeName string) string {
return networkSharePathEncoder.Encode(strings.TrimRight(isAnyPathSeparatorRegex.ReplaceAllString(volumeName, `\`), `\`))
}
// 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,
func handleVolumeName(opt *mountlib.Options) {
// 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
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
opt.VolumeName = encodeNetworkSharePath(opt.VolumeName[1:]) // We know from isNetworkSharePath it has a duplicate path separator prefix, so removes that right away (but encodeNetworkSharePath would remove it also)
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
// Specified volume name is not a valid network share UNC path, but network mode is enabled, so append to a hard coded server prefix and use it as volume prefix
opt.VolumeName = `\server\` + strings.TrimLeft(encodeNetworkSharePath(opt.VolumeName), `\`)
}
} else if opt.NetworkMode {
// Hard coded default
opt.VolumeName = "\\server\\share"
// Use hard coded default
opt.VolumeName = `\server\share`
}
}
@@ -174,22 +196,27 @@ func getMountpoint(f fs.Fs, mountpath string, opt *mountlib.Options) (mountpoint
}
// Handle mountpath
var volumeName string
if isDefaultPath(mountpath) {
// Mount path indicates defaults, which will automatically pick an unused drive letter.
mountpoint, err = handleDefaultMountpath()
if mountpoint, err = handleDefaultMountpath(); err != nil {
return
}
} 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
if mountpoint, err = handleNetworkShareMountpath(mountpath, opt); err != nil {
return
}
// In this case the volume name is taken from the mount path, it replaces any existing volume name option.
opt.VolumeName = mountpath
} else {
// Mount path is drive letter or directory path.
mountpoint, err = handleLocalMountpath(f, mountpath, opt)
if mountpoint, err = handleLocalMountpath(f, mountpath, opt); err != nil {
return
}
}
// Handle volume name
handleVolumeName(opt, volumeName)
handleVolumeName(opt)
// Done, return mountpoint to be used, together with updated mount options.
if opt.NetworkMode {

View File

@@ -79,6 +79,7 @@ func mount(VFS *vfs.VFS, mountpoint string, opt *mountlib.Options) (<-chan error
if err := mountlib.CheckAllowNonEmpty(mountpoint, opt); err != nil {
return nil, nil, err
}
opt.VolumeName = mountlib.MakeVolumeNameValidOnUnix(opt.VolumeName)
fs.Debugf(f, "Mounting on %q", mountpoint)
if opt.DebugFUSE {

View File

@@ -151,6 +151,7 @@ func mount(VFS *vfs.VFS, mountpoint string, opt *mountlib.Options) (<-chan error
if err := mountlib.CheckAllowNonEmpty(mountpoint, opt); err != nil {
return nil, nil, err
}
opt.VolumeName = mountlib.MakeVolumeNameValidOnUnix(opt.VolumeName)
fs.Debugf(f, "Mounting on %q", mountpoint)
fsys := NewFS(VFS, opt)

View File

@@ -57,6 +57,7 @@ var DefaultOpt = Options{
NoAppleDouble: true, // use noappledouble by default
NoAppleXattr: false, // do not use noapplexattr by default
AsyncRead: true, // do async reads by default
NetworkMode: true, // use network mode by default (Windows only)
}
type (
@@ -239,8 +240,12 @@ func NewMountCommand(commandName string, hidden bool, mount MountFn) *cobra.Comm
func (m *MountPoint) Mount() (daemon *os.Process, err error) {
// Ensure sensible defaults
m.SetVolumeName(m.MountOpt.VolumeName)
m.SetDeviceName(m.MountOpt.DeviceName)
if m.MountOpt.VolumeName == "" {
m.MountOpt.VolumeName = fs.ConfigString(m.Fs)
}
if m.MountOpt.DeviceName == "" {
m.MountOpt.DeviceName = fs.ConfigString(m.Fs)
}
// Start background task if --daemon is specified
if m.MountOpt.Daemon {

View File

@@ -97,29 +97,10 @@ func checkMountEmpty(mountpoint string) error {
return fmt.Errorf(msg+": %w", mountpoint, err)
}
// SetVolumeName with sensible default
func (m *MountPoint) SetVolumeName(vol string) {
if vol == "" {
vol = fs.ConfigString(m.Fs)
}
m.MountOpt.SetVolumeName(vol)
}
// SetVolumeName removes special characters from volume name if necessary
func (o *Options) SetVolumeName(vol string) {
vol = strings.ReplaceAll(vol, ":", " ")
vol = strings.ReplaceAll(vol, "/", " ")
vol = strings.TrimSpace(vol)
if runtime.GOOS == "windows" && len(vol) > 32 {
vol = vol[:32]
}
o.VolumeName = vol
}
// SetDeviceName with sensible default
func (m *MountPoint) SetDeviceName(dev string) {
if dev == "" {
dev = fs.ConfigString(m.Fs)
}
m.MountOpt.DeviceName = dev
// MakeVolumeNameValidOnUnix takes a volume name and returns a variant that is valid on unix systems.
func MakeVolumeNameValidOnUnix(volumeName string) string {
volumeName = strings.ReplaceAll(volumeName, ":", " ")
volumeName = strings.ReplaceAll(volumeName, "/", " ")
volumeName = strings.TrimSpace(volumeName)
return volumeName
}

View File

@@ -18,7 +18,6 @@ import (
"github.com/rclone/rclone/fs/dirtree"
"github.com/rclone/rclone/fs/log"
"github.com/rclone/rclone/fs/walk"
"github.com/rclone/rclone/lib/encoder"
"github.com/rclone/rclone/lib/terminal"
"github.com/spf13/cobra"
)
@@ -28,7 +27,6 @@ var (
outFileName string
noReport bool
sort string
enc = encoder.OS
)
func init() {
@@ -165,7 +163,7 @@ type FileInfo struct {
// Name is base name of the file
func (to *FileInfo) Name() string {
return enc.FromStandardName(path.Base(to.entry.Remote()))
return path.Base(to.entry.Remote())
}
// Size in bytes for regular files; system-dependent for others
@@ -199,7 +197,7 @@ func (to *FileInfo) Sys() interface{} {
// String returns the full path
func (to *FileInfo) String() string {
return filepath.FromSlash(enc.FromStandardPath(to.entry.Remote()))
return to.entry.Remote()
}
// Fs maps an fs.Fs into a tree.Fs
@@ -214,7 +212,6 @@ func NewFs(dirs dirtree.DirTree) Fs {
func (dirs Fs) Stat(filePath string) (fi os.FileInfo, err error) {
defer log.Trace(nil, "filePath=%q", filePath)("fi=%+v, err=%v", &fi, &err)
filePath = filepath.ToSlash(filePath)
filePath = enc.ToStandardPath(filePath)
filePath = strings.TrimLeft(filePath, "/")
if filePath == "" {
return &FileInfo{fs.NewDir("", time.Now())}, nil
@@ -230,14 +227,13 @@ func (dirs Fs) Stat(filePath string) (fi os.FileInfo, err error) {
func (dirs Fs) ReadDir(dir string) (names []string, err error) {
defer log.Trace(nil, "dir=%s", dir)("names=%+v, err=%v", &names, &err)
dir = filepath.ToSlash(dir)
dir = enc.ToStandardPath(dir)
dir = strings.TrimLeft(dir, "/")
entries, ok := dirs[dir]
if !ok {
return nil, fmt.Errorf("couldn't find directory %q", dir)
}
for _, entry := range entries {
names = append(names, enc.FromStandardName(path.Base(entry.Remote())))
names = append(names, path.Base(entry.Remote()))
}
return
}

View File

@@ -689,7 +689,3 @@ put them back in again.` >}}
* Gerard Bosch <30733556+gerardbosch@users.noreply.github.com>
* ToBeFree <github@tfrei.de>
* NodudeWasTaken <75137537+NodudeWasTaken@users.noreply.github.com>
* Peter Brunner <peter@lugoues.net>
* Ninh Pham <dongian.rapclubkhtn@gmail.com>
* Ryan Caezar Itang <sitiom@proton.me>
* Peter Brunner <peter@psykhe.com>

View File

@@ -165,19 +165,6 @@ developers so it may be out of date. Its current version is as below.
[![Chocolatey package](https://repology.org/badge/version-for-repo/chocolatey/rclone.svg)](https://repology.org/project/rclone/versions)
### Scoop package manager {#windows-scoop}
Make sure you have [Scoop](https://scoop.sh/) installed
```
scoop install rclone
```
Note that this is a third party installer not controlled by the rclone
developers so it may be out of date. Its current version is as below.
[![Scoop package](https://repology.org/badge/version-for-repo/scoop/rclone.svg)](https://repology.org/project/rclone/versions)
## Package manager installation {#package-manager}
Many Linux, Windows, macOS and other OS distributions package and

4
go.mod
View File

@@ -32,7 +32,6 @@ require (
github.com/hirochachacha/go-smb2 v1.1.0
github.com/iguanesolutions/go-systemd/v5 v5.1.0
github.com/jcmturner/gokrb5/v8 v8.4.3
github.com/jlaffaye/ftp v0.1.1-0.20230214004652-d84bf4be2b6e
github.com/jzelinskie/whirlpool v0.0.0-20201016144138-0675e54bb004
github.com/klauspost/compress v1.15.14
github.com/koofr/go-httpclient v0.0.0-20221124135700-2eb26cff5dd8
@@ -48,6 +47,7 @@ require (
github.com/pmezard/go-difflib v1.0.0
github.com/prometheus/client_golang v1.14.0
github.com/putdotio/go-putio/putio v0.0.0-20200123120452-16d982cac2b8
github.com/rclone/ftp v0.0.0-20221014110213-e44dedbc76c6
github.com/rfjakob/eme v1.1.2
github.com/rivo/uniseg v0.4.3
github.com/shirou/gopsutil/v3 v3.22.12
@@ -62,7 +62,7 @@ require (
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a
github.com/yunify/qingstor-sdk-go/v3 v3.2.0
go.etcd.io/bbolt v1.3.6
goftp.io/server v0.4.2-0.20210615155358-d07a820aac35
goftp.io/server v0.4.1
golang.org/x/crypto v0.5.0
golang.org/x/net v0.7.0
golang.org/x/oauth2 v0.4.0

9
go.sum
View File

@@ -277,9 +277,8 @@ github.com/jcmturner/gokrb5/v8 v8.4.3 h1:iTonLeSJOn7MVUtyMT+arAn5AKAPrkilzhGw8wE
github.com/jcmturner/gokrb5/v8 v8.4.3/go.mod h1:dqRwJGXznQrzw6cWmyo6kH+E7jksEQG/CyVWsJEsJO0=
github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
github.com/jlaffaye/ftp v0.0.0-20190624084859-c1312a7102bf h1:2IYBd5TD/maMqTU2YUzp2tJL4cNaOYQ9EBullN9t9pk=
github.com/jlaffaye/ftp v0.0.0-20190624084859-c1312a7102bf/go.mod h1:lli8NYPQOFy3O++YmYbqVgOcQ1JPCwdOy+5zSjKJ9qY=
github.com/jlaffaye/ftp v0.1.1-0.20230214004652-d84bf4be2b6e h1:Xofa5zcfulLjSb9ZNpb7MI9TFCpVkPCy3JSwrL7xoWE=
github.com/jlaffaye/ftp v0.1.1-0.20230214004652-d84bf4be2b6e/go.mod h1:sRSt+7UoQ5BgrZhwta4kr7N5SenQsoIZHMJHY7+zqJg=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
@@ -408,6 +407,8 @@ github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5
github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
github.com/putdotio/go-putio/putio v0.0.0-20200123120452-16d982cac2b8 h1:Y258uzXU/potCYnQd1r6wlAnoMB68BiCkCcCnKx1SH8=
github.com/putdotio/go-putio/putio v0.0.0-20200123120452-16d982cac2b8/go.mod h1:bSJjRokAHHOhA+XFxplld8w2R/dXLH7Z3BZ532vhFwU=
github.com/rclone/ftp v0.0.0-20221014110213-e44dedbc76c6 h1:J832KfU2Z44Ck3XR5bvw2UxShP0QnjueruNQ6dTYH+g=
github.com/rclone/ftp v0.0.0-20221014110213-e44dedbc76c6/go.mod h1:qRpxqlna6CaIq9fSRud1bDC5S7EEUEou0j8nMZ0lxO8=
github.com/rfjakob/eme v1.1.2 h1:SxziR8msSOElPayZNFfQw4Tjx/Sbaeeh3eRvrHVMUs4=
github.com/rfjakob/eme v1.1.2/go.mod h1:cVvpasglm/G3ngEfcfT/Wt0GwhkuO32pf/poW6Nyk1k=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
@@ -505,8 +506,8 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
goftp.io/server v0.4.2-0.20210615155358-d07a820aac35 h1:D4DhKKOtievTsshtbA6W0XL/gBjERF5/vu6Vhmb4sBw=
goftp.io/server v0.4.2-0.20210615155358-d07a820aac35/go.mod h1:hFZeR656ErRt3ojMKt7H10vQ5nuWV1e0YeUTeorlR6k=
goftp.io/server v0.4.1 h1:x7KG4HIxSMdK/rpYhExMinRN/aO/T9icvaG/B5e/XfY=
goftp.io/server v0.4.1/go.mod h1:hFZeR656ErRt3ojMKt7H10vQ5nuWV1e0YeUTeorlR6k=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=

View File

@@ -325,15 +325,10 @@ func (d *Dir) renameTree(dirPath string) {
d.entry = fs.NewDirCopy(context.TODO(), d.entry).SetRemote(dirPath)
}
// Do the same to any child directories and files
// Do the same to any child directories
for leaf, node := range d.items {
switch x := node.(type) {
case *Dir:
x.renameTree(path.Join(dirPath, leaf))
case *File:
x.renameDir(dirPath)
default:
panic("bad dir entry")
if dir, ok := node.(*Dir); ok {
dir.renameTree(path.Join(dirPath, leaf))
}
}
}

View File

@@ -7,7 +7,6 @@ import (
"sort"
"testing"
"time"
"unsafe"
"github.com/rclone/rclone/fs"
"github.com/rclone/rclone/fs/operations"
@@ -578,7 +577,3 @@ func TestDirRename(t *testing.T) {
err = dir.Rename("potato", "tuba", dir)
assert.Equal(t, EROFS, err)
}
func TestDirStructSize(t *testing.T) {
t.Logf("Dir struct has size %d bytes", unsafe.Sizeof(Dir{}))
}

View File

@@ -141,13 +141,6 @@ func (f *File) Node() Node {
return f
}
// renameDir - call when parent directory has been renamed
func (f *File) renameDir(dPath string) {
f.mu.RLock()
f.dPath = dPath
f.mu.RUnlock()
}
// applyPendingRename runs a previously set rename operation if there are no
// more remaining writers. Call without lock held.
func (f *File) applyPendingRename() {
@@ -303,9 +296,6 @@ func (f *File) activeWriters() int {
// It should be called with the lock held
func (f *File) _roundModTime(modTime time.Time) time.Time {
precision := f.d.f.Precision()
if precision == fs.ModTimeNotSupported {
return modTime
}
return modTime.Truncate(precision)
}

View File

@@ -6,7 +6,6 @@ import (
"io"
"os"
"testing"
"unsafe"
"github.com/rclone/rclone/fs"
"github.com/rclone/rclone/fs/operations"
@@ -412,7 +411,3 @@ func TestFileRename(t *testing.T) {
})
}
}
func TestFileStructSize(t *testing.T) {
t.Logf("File struct has size %d bytes", unsafe.Sizeof(File{}))
}