mirror of
https://github.com/rclone/rclone.git
synced 2026-11-07 02:02:53 -05:00
serve s3: allow copying directory markers
CopyObject with a directory marker such as "dir/" as its source failed with InvalidArgument, although HeadObject and GetObject found it. It now copies the marker, making the directory of another marker or an empty object. See #10082
This commit is contained in:
1 parent
617476d0e7
commit
79c19dc1d3
3 files changed
+32
-5
No files matched your search
@@ -824,15 +824,22 @@ func (b *s3Backend) CopyObject(ctx context.Context, srcBucket, srcKey, dstBucket
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
fp, err := bucketObjectPath(srcBucket, srcKey)
|
||||
fp, isMarker, err := bucketDirMarkerPath(srcBucket, srcKey)
|
||||
if !isMarker {
|
||||
fp, err = bucketObjectPath(srcBucket, srcKey)
|
||||
}
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
cStat, err := _vfs.Stat(fp)
|
||||
if err != nil || !cStat.IsFile() {
|
||||
if err != nil || cStat.IsDir() != isMarker {
|
||||
return result, gofakes3.KeyNotFound(srcKey)
|
||||
}
|
||||
if srcBucket == dstBucket && srcKey == dstKey {
|
||||
if isMarker {
|
||||
// Directory markers have no metadata to replace
|
||||
return result, nil
|
||||
}
|
||||
return result, b.tenant(ctx).storeMeta(_vfs, fp, meta)
|
||||
}
|
||||
|
||||
|
||||
@@ -408,6 +408,25 @@ func TestDirMarkers(t *testing.T) {
|
||||
assert.True(t, gofakes3.HasErrorCode(err, gofakes3.ErrNoSuchKey), "%s: %v", key, err)
|
||||
}
|
||||
|
||||
// Copying a marker makes the directory of another marker, or an
|
||||
// empty object
|
||||
_, err = b.CopyObject(ctx, "bucket", "dir/sub/", "bucket", "copy/", map[string]string{})
|
||||
require.NoError(t, err)
|
||||
assert.True(t, isDir("copy"))
|
||||
_, err = b.CopyObject(ctx, "bucket", "dir/sub/", "bucket", "empty.txt", map[string]string{})
|
||||
require.NoError(t, err)
|
||||
obj, err = b.HeadObject(ctx, "bucket", "empty.txt")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(0), obj.Size)
|
||||
_, err = b.CopyObject(ctx, "bucket", "dir/", "bucket", "dir/", map[string]string{})
|
||||
require.NoError(t, err)
|
||||
|
||||
// A copy needs the "/" for a directory and none for an object
|
||||
for _, key := range []string{"dir", "empty.txt/", "missing/"} {
|
||||
_, err = b.CopyObject(ctx, "bucket", key, "bucket", "other.txt", map[string]string{})
|
||||
assert.True(t, gofakes3.HasErrorCode(err, gofakes3.ErrNoSuchKey), "%s: %v", key, err)
|
||||
}
|
||||
|
||||
// Deleting a marker removes its directory only if empty
|
||||
require.NoError(t, put("dir/sub/file.txt", "data"))
|
||||
require.NoError(t, b.deleteObject(ctx, "bucket", "dir/sub/"))
|
||||
|
||||
@@ -493,9 +493,10 @@ A key ending in `/`, such as `dir/`, is a directory marker, standing
|
||||
for the directory `dir`. Putting an empty directory marker creates the
|
||||
directory (one with data is rejected with `400 Bad Request`), and
|
||||
deleting it removes the directory if it is empty. Directory markers
|
||||
are found by `HeadObject` and `GetObject` for any directory, but only
|
||||
the markers of empty directories are listed as objects, as other
|
||||
directories may not have been made by putting a marker.
|
||||
are found by `HeadObject`, `GetObject` and `CopyObject` for any
|
||||
directory, but only the markers of empty directories are listed as
|
||||
objects, as other directories may not have been made by putting a
|
||||
marker.
|
||||
|
||||
As keys are file paths, a key can't be both an object and a directory
|
||||
holding other objects, for example `a/b` and `a/b/c`. Writing an
|
||||
|
||||
Reference in new issue
Block a user