From 5ad942ed870b7119f7ff8f04c4868cb85c3db634 Mon Sep 17 00:00:00 2001 From: Nick Craig-Wood Date: Mon, 27 Feb 2023 12:16:49 +0000 Subject: [PATCH] local: fix exclusion of dangling symlinks with -L/--copy-links Before this fix, a dangling symlink was erroring the sync. It was writing an ERROR log and causing rclone to exit with an error. The List method wasn't returning an error though. This fix makes sure that we don't log or report a global error on a file/directory that has been excluded. This feature was first implemented in: a61d219bc local: fix -L/--copy-links with filters missing directories Then fixed in: 8d1fff9a8 local: obey file filters in listing to fix errors on excluded files This commit also adds test cases for the failure modes of those commits. See #6376 --- backend/local/local.go | 4 ++ backend/local/local_internal_test.go | 71 ++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/backend/local/local.go b/backend/local/local.go index 25ff7fce5..48f646bce 100644 --- a/backend/local/local.go +++ b/backend/local/local.go @@ -524,6 +524,10 @@ 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)) diff --git a/backend/local/local_internal_test.go b/backend/local/local_internal_test.go index 0b5d12480..0fa78cd9f 100644 --- a/backend/local/local_internal_test.go +++ b/backend/local/local_internal_test.go @@ -14,6 +14,7 @@ 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" @@ -395,3 +396,73 @@ 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)) +}