Skip to content

Commit

Permalink
Fix incorrect merging of slash-suffixed objects (minio#19699)
Browse files Browse the repository at this point in the history
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 minio#15772
  • Loading branch information
klauspost authored May 9, 2024
1 parent b534dc6 commit 2f7a10a
Showing 1 changed file with 28 additions and 9 deletions.
37 changes: 28 additions & 9 deletions cmd/metacache-entries.go
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down

0 comments on commit 2f7a10a

Please sign in to comment.