From 07cf5f1d25cd6ed4ef3f6a62dfa093c7386b7b59 Mon Sep 17 00:00:00 2001 From: Nick Craig-Wood Date: Sat, 1 Jul 2023 17:18:21 +0100 Subject: [PATCH] operations: fix .rclonelink files not being converted back to symlinks Before this change the new partial downloads code was causing symlinks to be copied as regular files. This was because the partial isn't named .rclonelink so the local backend saves it as a normal file and renaming it to .rclonelink doesn't cause it to become a symlink. This fixes the problem by not copying .rclonelink files using the partials mechanism but reverting to the previous --inplace behaviour. This could potentially be fixed better in the future by changing the local backend Move to change files to and from symlinks depending on their name. However this was deemed too complicated for a point release. This also adds a test in the local backend. This test should ideally be in operations but it isn't easy to put it there as operations knows nothing of symlinks. Fixes #7101 See: https://forum.rclone.org/t/reggression-in-v1-63-0-links-drops-the-rclonelink-extension/39483 --- backend/local/local_internal_test.go | 41 ++++++++++++++++++++++++++++ fs/operations/operations.go | 2 +- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/backend/local/local_internal_test.go b/backend/local/local_internal_test.go index 2e2cfbca6..06e47bed2 100644 --- a/backend/local/local_internal_test.go +++ b/backend/local/local_internal_test.go @@ -19,6 +19,7 @@ import ( "github.com/rclone/rclone/fs/filter" "github.com/rclone/rclone/fs/hash" "github.com/rclone/rclone/fs/object" + "github.com/rclone/rclone/fs/operations" "github.com/rclone/rclone/fstest" "github.com/rclone/rclone/lib/file" "github.com/rclone/rclone/lib/readers" @@ -514,3 +515,43 @@ func TestFilterSymlinkCopyLinks(t *testing.T) { func TestFilterSymlinkLinks(t *testing.T) { testFilterSymlink(t, false) } + +func TestCopySymlink(t *testing.T) { + ctx := context.Background() + r := fstest.NewRun(t) + defer r.Finalise() + when := time.Now() + f := r.Flocal.(*Fs) + + // Create a file and a symlink to it + r.WriteFile("src/file.txt", "hello world", when) + require.NoError(t, os.Symlink("file.txt", filepath.Join(r.LocalName, "src", "link.txt"))) + defer func() { + // Reset -L/-l mode + f.opt.FollowSymlinks = false + f.opt.TranslateSymlinks = false + f.lstat = os.Lstat + }() + + // Set fs into "-l/--links" mode + f.opt.FollowSymlinks = false + f.opt.TranslateSymlinks = true + f.lstat = os.Lstat + + // Create dst + require.NoError(t, f.Mkdir(ctx, "dst")) + + // Do copy from src into dst + src, err := f.NewObject(ctx, "src/link.txt.rclonelink") + require.NoError(t, err) + require.NotNil(t, src) + dst, err := operations.Copy(ctx, f, nil, "dst/link.txt.rclonelink", src) + require.NoError(t, err) + require.NotNil(t, dst) + + // Test that we made a symlink and it has the right contents + dstPath := filepath.Join(r.LocalName, "dst", "link.txt") + linkContents, err := os.Readlink(dstPath) + require.NoError(t, err) + assert.Equal(t, "file.txt", linkContents) +} diff --git a/fs/operations/operations.go b/fs/operations/operations.go index 3c89bbc97..2c8463b3c 100644 --- a/fs/operations/operations.go +++ b/fs/operations/operations.go @@ -358,7 +358,7 @@ func Copy(ctx context.Context, f fs.Fs, dst fs.Object, remote string, src fs.Obj inplace = true remotePartial = remote ) - if !ci.Inplace && f.Features().Move != nil && f.Features().PartialUploads { + if !ci.Inplace && f.Features().Move != nil && f.Features().PartialUploads && !strings.HasSuffix(remote, ".rclonelink") { // Avoid making the leaf name longer if it's already lengthy to avoid // trouble with file name length limits. suffix := "." + random.String(8) + ".partial"