From 252562d00a757ee8d6913a9ba2c71d00e7fd8541 Mon Sep 17 00:00:00 2001 From: nielash Date: Mon, 4 Mar 2024 02:48:34 -0500 Subject: [PATCH] combine: fix CopyDirMetadata error on upstream root Before this change, operations.CopyDirMetadata would fail with: `internal error: expecting directory string from combine root '' to have SetMetadata method: optional feature not implemented` if the dst was the root directory of a combine upstream. This is because combine was returning a *fs.Dir, which does not satisfy the fs.SetMetadataer interface. While it is true that combine cannot set metadata on the root of an upstream (see also #7652), this should not be considered an error that causes sync to do high-level retries, abort without doing deletes, etc. This change addresses the issue by creating a new type of DirWrapper that is allowed to fail silently, for exceptional cases such as this where certain special directories have more limited abilities than what the Fs usually supports. It is possible that other similar wrapping backends (Union?) may need this same fix. --- backend/combine/combine.go | 4 ++-- fs/dir_wrapper.go | 23 +++++++++++++++++++++-- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/backend/combine/combine.go b/backend/combine/combine.go index 3fdceb128..c00f2314e 100644 --- a/backend/combine/combine.go +++ b/backend/combine/combine.go @@ -813,7 +813,7 @@ func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err e if f.root == "" && dir == "" { entries = make(fs.DirEntries, 0, len(f.upstreams)) for combineDir := range f.upstreams { - d := fs.NewDir(combineDir, f.when) + d := fs.NewLimitedDirWrapper(combineDir, fs.NewDir(combineDir, f.when)) entries = append(entries, d) } return entries, nil @@ -1002,7 +1002,7 @@ func (f *Fs) DirSetModTime(ctx context.Context, dir string, modTime time.Time) e return err } if uDir == "" { - fs.Debugf(dir, "can't set modtime on upstream root. skipping.") + fs.Debugf(dir, "Can't set modtime on upstream root. skipping.") return nil } if do := u.f.Features().DirSetModTime; do != nil { diff --git a/fs/dir_wrapper.go b/fs/dir_wrapper.go index 9fe135703..1560961e2 100644 --- a/fs/dir_wrapper.go +++ b/fs/dir_wrapper.go @@ -7,8 +7,9 @@ import ( // DirWrapper wraps a Directory object so the Remote can be overridden type DirWrapper struct { - Directory // Directory we are wrapping - remote string // name of the directory + Directory // Directory we are wrapping + remote string // name of the directory + failSilently bool // if set, ErrorNotImplemented should not be considered an error for this directory } // NewDirWrapper creates a wrapper for a directory object @@ -22,6 +23,16 @@ func NewDirWrapper(remote string, d Directory) *DirWrapper { } } +// NewLimitedDirWrapper creates a DirWrapper that should fail silently instead of erroring for ErrorNotImplemented. +// +// Intended for exceptional dirs lacking abilities that the Fs otherwise usually supports +// (ex. a Combine root which can't set metadata/modtime, regardless of support by wrapped backend) +func NewLimitedDirWrapper(remote string, d Directory) *DirWrapper { + dw := NewDirWrapper(remote, d) + dw.failSilently = true + return dw +} + // String returns the name func (d *DirWrapper) String() string { return d.remote @@ -55,6 +66,10 @@ func (d *DirWrapper) Metadata(ctx context.Context) (Metadata, error) { func (d *DirWrapper) SetMetadata(ctx context.Context, metadata Metadata) error { do, ok := d.Directory.(SetMetadataer) if !ok { + if d.failSilently { + Debugf(d, "Can't SetMetadata for this directory (%T from %v) -- skipping", d.Directory, d.Fs()) + return nil + } return ErrorNotImplemented } return do.SetMetadata(ctx, metadata) @@ -66,6 +81,10 @@ func (d *DirWrapper) SetMetadata(ctx context.Context, metadata Metadata) error { func (d *DirWrapper) SetModTime(ctx context.Context, t time.Time) error { do, ok := d.Directory.(SetModTimer) if !ok { + if d.failSilently { + Debugf(d, "Can't SetModTime for this directory (%T from %v) -- skipping", d.Directory, d.Fs()) + return nil + } return ErrorNotImplemented } return do.SetModTime(ctx, t)