From 2f7a10ab3165fdb3d4ab098402f828fe9833d3ff Mon Sep 17 00:00:00 2001 From: Klaus Post Date: Thu, 9 May 2024 11:05:45 -0700 Subject: [PATCH] Fix incorrect merging of slash-suffixed objects (#19699) If two objects share everything but one object has a slash prefix, those would be merged in listings, with secondary properties used for a tiebreak. Example: An object with the key `prefix/obj` would be merged with an object named `prefix/obj/`. While this violates the [no object can be a prefix of another](https://min.io/docs/minio/linux/operations/concepts/thresholds.html#conflicting-objects), let's resolve these. If we have an object with 'name' and a directory named 'name/' discard the directory only - but allow objects of 'name' and 'name/' (xldir) to be uniquely returned. Regression from #15772 --- cmd/metacache-entries.go | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/cmd/metacache-entries.go b/cmd/metacache-entries.go index 38ace73d2..0c3989712 100644 --- a/cmd/metacache-entries.go +++ b/cmd/metacache-entries.go @@ -736,16 +736,35 @@ func mergeEntryChannels(ctx context.Context, in []chan metaCacheEntry, out chan< bestIdx = otherIdx continue } - // We should make sure to avoid objects and directories - // of this fashion such as - // - foo-1 - // - foo-1/ - // we should avoid this situation by making sure that - // we compare the `foo-1/` after path.Clean() to - // de-dup the entries. if path.Clean(best.name) == path.Clean(other.name) { - toMerge = append(toMerge, otherIdx) - continue + // We may be in a situation where we have a directory and an object with the same name. + // In that case we will drop the directory entry. + // This should however not be confused with an object with a trailing slash. + dirMatches := best.isDir() == other.isDir() + suffixMatches := strings.HasSuffix(best.name, slashSeparator) == strings.HasSuffix(other.name, slashSeparator) + + // Simple case. Both are same type with same suffix. + if dirMatches && suffixMatches { + toMerge = append(toMerge, otherIdx) + continue + } + + if !dirMatches && !suffixMatches { + // We have an object `name` and a directory `name/`. + if other.isDir() { + // Discard the directory. + if err := selectFrom(otherIdx); err != nil { + return err + } + continue + } + // Replace directory with object. + toMerge = toMerge[:0] + best = other + bestIdx = otherIdx + continue + } + // Return as separate entries. } if best.name > other.name { toMerge = toMerge[:0]