A PUT which failed part way through removed the object at the destination key. As well as differing from real S3 (where a failed PUT never affects the stored object), this raced with the client's automatic retry of the same PUT: the retry stored the object and returned 200 OK, then the failed first attempt's cleanup deleted it, silently losing an acknowledged upload. The interrupted upload could also be committed as a truncated object, since closing the write handle gave the streaming upload a clean end of stream. Now a failed or interrupted PUT never disturbs the object at the key: - The object at the key is never removed on error. - On backends where a partial upload is visible at its final name (PartialUploads), and when the VFS cache mode is writes or above, the upload is written to a temporary object which is renamed into place on success and removed on failure, as streamed multipart uploads already do. Backends which upload atomically are still streamed straight to the destination. - An interrupted or short body fails the upload via WriteFileHandle.CloseWithError instead of committing truncated data, and a body which ends cleanly short of its declared size is rejected with IncompleteBody.
61 lines
1.4 KiB
Go
61 lines
1.4 KiB
Go
package s3
|
|
|
|
import (
|
|
"path"
|
|
"strings"
|
|
|
|
"github.com/rclone/gofakes3"
|
|
"github.com/rclone/rclone/vfs"
|
|
)
|
|
|
|
func (b *s3Backend) entryListR(_vfs *vfs.VFS, bucketName, fdPath, name string, addPrefix bool, response *gofakes3.ObjectList) error {
|
|
fp, err := bucketDirPath(bucketName, fdPath)
|
|
if err != nil {
|
|
// A listing prefix that can't be represented as a path matches nothing.
|
|
return gofakes3.ErrNoSuchKey
|
|
}
|
|
|
|
dirEntries, err := getDirEntries(fp, _vfs)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, entry := range dirEntries {
|
|
object := entry.Name()
|
|
|
|
// Hide the temporary objects of in-progress uploads
|
|
if strings.HasPrefix(object, multipartUploadPrefix) || strings.HasPrefix(object, putObjectPrefix) {
|
|
continue
|
|
}
|
|
|
|
// workaround for control-chars detect
|
|
objectPath := path.Join(fdPath, object)
|
|
|
|
if !strings.HasPrefix(object, name) {
|
|
continue
|
|
}
|
|
|
|
if entry.IsDir() {
|
|
if addPrefix {
|
|
prefixWithTrailingSlash := objectPath + "/"
|
|
response.AddPrefix(prefixWithTrailingSlash)
|
|
continue
|
|
}
|
|
err := b.entryListR(_vfs, bucketName, path.Join(fdPath, object), "", false, response)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
item := &gofakes3.Content{
|
|
Key: objectPath,
|
|
LastModified: gofakes3.NewContentTime(entry.ModTime()),
|
|
ETag: getFileHash(entry, b.s.etagHashType),
|
|
Size: entry.Size(),
|
|
StorageClass: gofakes3.StorageStandard,
|
|
}
|
|
response.Add(item)
|
|
}
|
|
}
|
|
return nil
|
|
}
|