mirror of https://github.com/minio/minio.git
				
				
				
			Use GetObjectNInfo in CopyObject and CopyObjectPart (#6489)
This commit is contained in:
		
							parent
							
								
									1e5ac39ff3
								
							
						
					
					
						commit
						aa4e2b1542
					
				|  | @ -43,6 +43,9 @@ func setCommonHeaders(w http.ResponseWriter) { | ||||||
| 		w.Header().Set("X-Amz-Bucket-Region", region) | 		w.Header().Set("X-Amz-Bucket-Region", region) | ||||||
| 	} | 	} | ||||||
| 	w.Header().Set("Accept-Ranges", "bytes") | 	w.Header().Set("Accept-Ranges", "bytes") | ||||||
|  | 
 | ||||||
|  | 	// Remove sensitive information
 | ||||||
|  | 	crypto.RemoveSensitiveHeaders(w.Header()) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Encodes the response headers into XML format.
 | // Encodes the response headers into XML format.
 | ||||||
|  |  | ||||||
|  | @ -68,3 +68,29 @@ func parseCopyPartRange(rangeString string, resourceSize int64) (offset, length | ||||||
| 
 | 
 | ||||||
| 	return hrange.GetOffsetLength(resourceSize) | 	return hrange.GetOffsetLength(resourceSize) | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | // parseCopyPartRangeSpec transforms a range string (e.g. bytes=3-4) to HTTPRangeSpec
 | ||||||
|  | // and returns errors if weird values
 | ||||||
|  | func parseCopyPartRangeSpec(rangeString string) (hrange *HTTPRangeSpec, err error) { | ||||||
|  | 	hrange, err = parseRequestRangeSpec(rangeString) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	if hrange.IsSuffixLength || hrange.Start < 0 || hrange.End < 0 { | ||||||
|  | 		return nil, errInvalidRange | ||||||
|  | 	} | ||||||
|  | 	return hrange, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // checkCopyPartRangeWithSize adds more check to the range string in case of
 | ||||||
|  | // copy object part. This API requires having specific start and end  range values
 | ||||||
|  | // e.g. 'bytes=3-10'. Other use cases will be rejected.
 | ||||||
|  | func checkCopyPartRangeWithSize(rs *HTTPRangeSpec, resourceSize int64) (err error) { | ||||||
|  | 	if rs == nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	if rs.IsSuffixLength || rs.Start >= resourceSize || rs.End >= resourceSize { | ||||||
|  | 		return errInvalidRangeSource | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -172,7 +172,6 @@ func (ssecCopy) IsRequested(h http.Header) bool { | ||||||
| // ParseHTTP parses the SSE-C headers and returns the SSE-C client key
 | // ParseHTTP parses the SSE-C headers and returns the SSE-C client key
 | ||||||
| // on success. SSE-C copy headers are ignored.
 | // on success. SSE-C copy headers are ignored.
 | ||||||
| func (ssec) ParseHTTP(h http.Header) (key [32]byte, err error) { | func (ssec) ParseHTTP(h http.Header) (key [32]byte, err error) { | ||||||
| 	defer h.Del(SSECKey) // remove SSE-C key from headers after parsing
 |  | ||||||
| 	if h.Get(SSECAlgorithm) != SSEAlgorithmAES256 { | 	if h.Get(SSECAlgorithm) != SSEAlgorithmAES256 { | ||||||
| 		return key, ErrInvalidCustomerAlgorithm | 		return key, ErrInvalidCustomerAlgorithm | ||||||
| 	} | 	} | ||||||
|  | @ -198,7 +197,6 @@ func (ssec) ParseHTTP(h http.Header) (key [32]byte, err error) { | ||||||
| // ParseHTTP parses the SSE-C copy headers and returns the SSE-C client key
 | // ParseHTTP parses the SSE-C copy headers and returns the SSE-C client key
 | ||||||
| // on success. Regular SSE-C headers are ignored.
 | // on success. Regular SSE-C headers are ignored.
 | ||||||
| func (ssecCopy) ParseHTTP(h http.Header) (key [32]byte, err error) { | func (ssecCopy) ParseHTTP(h http.Header) (key [32]byte, err error) { | ||||||
| 	defer h.Del(SSECopyKey) // remove SSE-C copy key of source object from headers after parsing
 |  | ||||||
| 	if h.Get(SSECopyAlgorithm) != SSEAlgorithmAES256 { | 	if h.Get(SSECopyAlgorithm) != SSEAlgorithmAES256 { | ||||||
| 		return key, ErrInvalidCustomerAlgorithm | 		return key, ErrInvalidCustomerAlgorithm | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -251,9 +251,6 @@ func TestSSECParse(t *testing.T) { | ||||||
| 		if err == nil && key == zeroKey { | 		if err == nil && key == zeroKey { | ||||||
| 			t.Errorf("Test %d: parsed client key is zero key", i) | 			t.Errorf("Test %d: parsed client key is zero key", i) | ||||||
| 		} | 		} | ||||||
| 		if _, ok := test.Header[SSECKey]; ok { |  | ||||||
| 			t.Errorf("Test %d: client key is not removed from HTTP headers after parsing", i) |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -58,7 +58,7 @@ type cacheObjects struct { | ||||||
| 	// file path patterns to exclude from cache
 | 	// file path patterns to exclude from cache
 | ||||||
| 	exclude []string | 	exclude []string | ||||||
| 	// Object functions pointing to the corresponding functions of backend implementation.
 | 	// Object functions pointing to the corresponding functions of backend implementation.
 | ||||||
| 	GetObjectNInfoFn          func(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header) (gr *GetObjectReader, err error) | 	GetObjectNInfoFn          func(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, lockType LockType) (gr *GetObjectReader, err error) | ||||||
| 	GetObjectFn               func(ctx context.Context, bucket, object string, startOffset int64, length int64, writer io.Writer, etag string, opts ObjectOptions) (err error) | 	GetObjectFn               func(ctx context.Context, bucket, object string, startOffset int64, length int64, writer io.Writer, etag string, opts ObjectOptions) (err error) | ||||||
| 	GetObjectInfoFn           func(ctx context.Context, bucket, object string, opts ObjectOptions) (objInfo ObjectInfo, err error) | 	GetObjectInfoFn           func(ctx context.Context, bucket, object string, opts ObjectOptions) (objInfo ObjectInfo, err error) | ||||||
| 	PutObjectFn               func(ctx context.Context, bucket, object string, data *hash.Reader, metadata map[string]string, opts ObjectOptions) (objInfo ObjectInfo, err error) | 	PutObjectFn               func(ctx context.Context, bucket, object string, data *hash.Reader, metadata map[string]string, opts ObjectOptions) (objInfo ObjectInfo, err error) | ||||||
|  | @ -90,7 +90,7 @@ type CacheObjectLayer interface { | ||||||
| 	ListBuckets(ctx context.Context) (buckets []BucketInfo, err error) | 	ListBuckets(ctx context.Context) (buckets []BucketInfo, err error) | ||||||
| 	DeleteBucket(ctx context.Context, bucket string) error | 	DeleteBucket(ctx context.Context, bucket string) error | ||||||
| 	// Object operations.
 | 	// Object operations.
 | ||||||
| 	GetObjectNInfo(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header) (gr *GetObjectReader, err error) | 	GetObjectNInfo(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, lockType LockType) (gr *GetObjectReader, err error) | ||||||
| 	GetObject(ctx context.Context, bucket, object string, startOffset int64, length int64, writer io.Writer, etag string, opts ObjectOptions) (err error) | 	GetObject(ctx context.Context, bucket, object string, startOffset int64, length int64, writer io.Writer, etag string, opts ObjectOptions) (err error) | ||||||
| 	GetObjectInfo(ctx context.Context, bucket, object string, opts ObjectOptions) (objInfo ObjectInfo, err error) | 	GetObjectInfo(ctx context.Context, bucket, object string, opts ObjectOptions) (objInfo ObjectInfo, err error) | ||||||
| 	PutObject(ctx context.Context, bucket, object string, data *hash.Reader, metadata map[string]string, opts ObjectOptions) (objInfo ObjectInfo, err error) | 	PutObject(ctx context.Context, bucket, object string, data *hash.Reader, metadata map[string]string, opts ObjectOptions) (objInfo ObjectInfo, err error) | ||||||
|  | @ -183,9 +183,9 @@ func (c cacheObjects) getMetadata(objInfo ObjectInfo) map[string]string { | ||||||
| 	return metadata | 	return metadata | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (c cacheObjects) GetObjectNInfo(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header) (gr *GetObjectReader, err error) { | func (c cacheObjects) GetObjectNInfo(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, lockType LockType) (gr *GetObjectReader, err error) { | ||||||
| 
 | 
 | ||||||
| 	bkReader, bkErr := c.GetObjectNInfoFn(ctx, bucket, object, rs, h) | 	bkReader, bkErr := c.GetObjectNInfoFn(ctx, bucket, object, rs, h, writeLock) | ||||||
| 
 | 
 | ||||||
| 	if c.isCacheExclude(bucket, object) || !bkReader.ObjInfo.IsCacheable() { | 	if c.isCacheExclude(bucket, object) || !bkReader.ObjInfo.IsCacheable() { | ||||||
| 		return bkReader, bkErr | 		return bkReader, bkErr | ||||||
|  | @ -210,7 +210,7 @@ func (c cacheObjects) GetObjectNInfo(ctx context.Context, bucket, object string, | ||||||
| 		return bkReader, bkErr | 		return bkReader, bkErr | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if cacheReader, cacheErr := dcache.GetObjectNInfo(ctx, bucket, object, rs, h); cacheErr == nil { | 	if cacheReader, cacheErr := dcache.GetObjectNInfo(ctx, bucket, object, rs, h, lockType); cacheErr == nil { | ||||||
| 		if backendDown { | 		if backendDown { | ||||||
| 			// If the backend is down, serve the request from cache.
 | 			// If the backend is down, serve the request from cache.
 | ||||||
| 			return cacheReader, nil | 			return cacheReader, nil | ||||||
|  |  | ||||||
|  | @ -60,7 +60,7 @@ func (api *DummyObjectLayer) ListObjectsV2(ctx context.Context, bucket, prefix, | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (api *DummyObjectLayer) GetObjectNInfo(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header) (gr *GetObjectReader, err error) { | func (api *DummyObjectLayer) GetObjectNInfo(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, lock LockType) (gr *GetObjectReader, err error) { | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -123,8 +123,6 @@ func ParseSSECustomerHeader(header http.Header) (key []byte, err error) { | ||||||
| 
 | 
 | ||||||
| // This function rotates old to new key.
 | // This function rotates old to new key.
 | ||||||
| func rotateKey(oldKey []byte, newKey []byte, bucket, object string, metadata map[string]string) error { | func rotateKey(oldKey []byte, newKey []byte, bucket, object string, metadata map[string]string) error { | ||||||
| 	delete(metadata, crypto.SSECKey) // make sure we do not save the key by accident
 |  | ||||||
| 
 |  | ||||||
| 	switch { | 	switch { | ||||||
| 	default: | 	default: | ||||||
| 		return errObjectTampered | 		return errObjectTampered | ||||||
|  | @ -155,8 +153,6 @@ func rotateKey(oldKey []byte, newKey []byte, bucket, object string, metadata map | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func newEncryptMetadata(key []byte, bucket, object string, metadata map[string]string, sseS3 bool) ([]byte, error) { | func newEncryptMetadata(key []byte, bucket, object string, metadata map[string]string, sseS3 bool) ([]byte, error) { | ||||||
| 	delete(metadata, crypto.SSECKey) // make sure we do not save the key by accident
 |  | ||||||
| 
 |  | ||||||
| 	var sealedKey crypto.SealedKey | 	var sealedKey crypto.SealedKey | ||||||
| 	if sseS3 { | 	if sseS3 { | ||||||
| 		if globalKMS == nil { | 		if globalKMS == nil { | ||||||
|  | @ -245,7 +241,6 @@ func DecryptCopyRequest(client io.Writer, r *http.Request, bucket, object string | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	delete(metadata, crypto.SSECopyKey) // make sure we do not save the key by accident
 |  | ||||||
| 	return newDecryptWriter(client, key, bucket, object, 0, metadata) | 	return newDecryptWriter(client, key, bucket, object, 0, metadata) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -325,7 +320,6 @@ func DecryptRequestWithSequenceNumberR(client io.Reader, h http.Header, bucket, | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	delete(metadata, crypto.SSECKey) // make sure we do not save the key by accident
 |  | ||||||
| 	return newDecryptReader(client, key, bucket, object, seqNumber, metadata) | 	return newDecryptReader(client, key, bucket, object, seqNumber, metadata) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -342,7 +336,6 @@ func DecryptCopyRequestR(client io.Reader, h http.Header, bucket, object string, | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	delete(metadata, crypto.SSECopyKey) // make sure we do not save the key by accident
 |  | ||||||
| 	return newDecryptReader(client, key, bucket, object, 0, metadata) | 	return newDecryptReader(client, key, bucket, object, 0, metadata) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -444,7 +437,6 @@ func DecryptRequestWithSequenceNumber(client io.Writer, r *http.Request, bucket, | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	delete(metadata, crypto.SSECKey) // make sure we do not save the key by accident
 |  | ||||||
| 	return newDecryptWriter(client, key, bucket, object, seqNumber, metadata) | 	return newDecryptWriter(client, key, bucket, object, seqNumber, metadata) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -514,13 +506,6 @@ func (d *DecryptBlocksReader) buildDecrypter(partID int) error { | ||||||
| 	mac.Write(partIDbin[:]) | 	mac.Write(partIDbin[:]) | ||||||
| 	partEncryptionKey := mac.Sum(nil) | 	partEncryptionKey := mac.Sum(nil) | ||||||
| 
 | 
 | ||||||
| 	// make sure we do not save the key by accident
 |  | ||||||
| 	if d.copySource { |  | ||||||
| 		delete(m, crypto.SSECopyKey) |  | ||||||
| 	} else { |  | ||||||
| 		delete(m, crypto.SSECKey) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Limit the reader, so the decryptor doesnt receive bytes
 | 	// Limit the reader, so the decryptor doesnt receive bytes
 | ||||||
| 	// from the next part (different DARE stream)
 | 	// from the next part (different DARE stream)
 | ||||||
| 	encLenToRead := d.parts[d.partIndex].Size - d.partEncRelOffset | 	encLenToRead := d.parts[d.partIndex].Size - d.partEncRelOffset | ||||||
|  | @ -636,13 +621,6 @@ func (w *DecryptBlocksWriter) buildDecrypter(partID int) error { | ||||||
| 	mac.Write(partIDbin[:]) | 	mac.Write(partIDbin[:]) | ||||||
| 	partEncryptionKey := mac.Sum(nil) | 	partEncryptionKey := mac.Sum(nil) | ||||||
| 
 | 
 | ||||||
| 	// make sure we do not save the key by accident
 |  | ||||||
| 	if w.copySource { |  | ||||||
| 		delete(m, crypto.SSECopyKey) |  | ||||||
| 	} else { |  | ||||||
| 		delete(m, crypto.SSECKey) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// make sure to provide a NopCloser such that a Close
 | 	// make sure to provide a NopCloser such that a Close
 | ||||||
| 	// on sio.decryptWriter doesn't close the underlying writer's
 | 	// on sio.decryptWriter doesn't close the underlying writer's
 | ||||||
| 	// close which perhaps can close the stream prematurely.
 | 	// close which perhaps can close the stream prematurely.
 | ||||||
|  |  | ||||||
|  | @ -210,11 +210,6 @@ func TestParseSSECustomerRequest(t *testing.T) { | ||||||
| 		if err != test.err { | 		if err != test.err { | ||||||
| 			t.Errorf("Test %d: Parse returned: %v want: %v", i, err, test.err) | 			t.Errorf("Test %d: Parse returned: %v want: %v", i, err, test.err) | ||||||
| 		} | 		} | ||||||
| 		key := request.Header.Get(crypto.SSECKey) |  | ||||||
| 		if (err == nil || err == crypto.ErrCustomerKeyMD5Mismatch) && key != "" { |  | ||||||
| 			t.Errorf("Test %d: Client key survived parsing - found key: %v", i, key) |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -331,10 +326,6 @@ func TestParseSSECopyCustomerRequest(t *testing.T) { | ||||||
| 		if err != test.err { | 		if err != test.err { | ||||||
| 			t.Errorf("Test %d: Parse returned: %v want: %v", i, err, test.err) | 			t.Errorf("Test %d: Parse returned: %v want: %v", i, err, test.err) | ||||||
| 		} | 		} | ||||||
| 		key := request.Header.Get(crypto.SSECopyKey) |  | ||||||
| 		if (err == nil || err == crypto.ErrCustomerKeyMD5Mismatch) && key != "" { |  | ||||||
| 			t.Errorf("Test %d: Client key survived parsing - found key: %v", i, key) |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -376,9 +367,6 @@ func TestEncryptRequest(t *testing.T) { | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			t.Fatalf("Test %d: Failed to encrypt request: %v", i, err) | 			t.Fatalf("Test %d: Failed to encrypt request: %v", i, err) | ||||||
| 		} | 		} | ||||||
| 		if key, ok := test.metadata[crypto.SSECKey]; ok { |  | ||||||
| 			t.Errorf("Test %d: Client provided key survived in metadata - key: %s", i, key) |  | ||||||
| 		} |  | ||||||
| 		if kdf, ok := test.metadata[crypto.SSESealAlgorithm]; !ok { | 		if kdf, ok := test.metadata[crypto.SSESealAlgorithm]; !ok { | ||||||
| 			t.Errorf("Test %d: ServerSideEncryptionKDF must be part of metadata: %v", i, kdf) | 			t.Errorf("Test %d: ServerSideEncryptionKDF must be part of metadata: %v", i, kdf) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | @ -20,6 +20,7 @@ import ( | ||||||
| 	"bytes" | 	"bytes" | ||||||
| 	"context" | 	"context" | ||||||
| 	"io" | 	"io" | ||||||
|  | 	"strings" | ||||||
| 
 | 
 | ||||||
| 	"github.com/klauspost/reedsolomon" | 	"github.com/klauspost/reedsolomon" | ||||||
| 	"github.com/minio/minio/cmd/logger" | 	"github.com/minio/minio/cmd/logger" | ||||||
|  | @ -90,7 +91,9 @@ func writeDataBlocks(ctx context.Context, dst io.Writer, enBlocks [][]byte, data | ||||||
| 		// Copy the block.
 | 		// Copy the block.
 | ||||||
| 		n, err := io.Copy(dst, bytes.NewReader(block)) | 		n, err := io.Copy(dst, bytes.NewReader(block)) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			logger.LogIf(ctx, err) | 			if !strings.Contains(err.Error(), "read/write on closed pipe") { | ||||||
|  | 				logger.LogIf(ctx, err) | ||||||
|  | 			} | ||||||
| 			return 0, err | 			return 0, err | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -255,22 +255,6 @@ func (fs *FSObjects) CopyObjectPart(ctx context.Context, srcBucket, srcObject, d | ||||||
| 		return pi, toObjectErr(err) | 		return pi, toObjectErr(err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Initialize pipe.
 |  | ||||||
| 	go func() { |  | ||||||
| 		if gerr := fs.GetObject(ctx, srcBucket, srcObject, startOffset, length, srcInfo.Writer, srcInfo.ETag, srcOpts); gerr != nil { |  | ||||||
| 			if gerr = srcInfo.Writer.Close(); gerr != nil { |  | ||||||
| 				logger.LogIf(ctx, gerr) |  | ||||||
| 				return |  | ||||||
| 			} |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 		// Close writer explicitly signaling we wrote all data.
 |  | ||||||
| 		if gerr := srcInfo.Writer.Close(); gerr != nil { |  | ||||||
| 			logger.LogIf(ctx, gerr) |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 	}() |  | ||||||
| 
 |  | ||||||
| 	partInfo, err := fs.PutObjectPart(ctx, dstBucket, dstObject, uploadID, partID, srcInfo.Reader, dstOpts) | 	partInfo, err := fs.PutObjectPart(ctx, dstBucket, dstObject, uploadID, partID, srcInfo.Reader, dstOpts) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return pi, toObjectErr(err, dstBucket, dstObject) | 		return pi, toObjectErr(err, dstBucket, dstObject) | ||||||
|  |  | ||||||
							
								
								
									
										68
									
								
								cmd/fs-v1.go
								
								
								
								
							
							
						
						
									
										68
									
								
								cmd/fs-v1.go
								
								
								
								
							|  | @ -418,35 +418,19 @@ func (fs *FSObjects) DeleteBucket(ctx context.Context, bucket string) error { | ||||||
| // update metadata.
 | // update metadata.
 | ||||||
| func (fs *FSObjects) CopyObject(ctx context.Context, srcBucket, srcObject, dstBucket, dstObject string, srcInfo ObjectInfo, srcOpts, dstOpts ObjectOptions) (oi ObjectInfo, e error) { | func (fs *FSObjects) CopyObject(ctx context.Context, srcBucket, srcObject, dstBucket, dstObject string, srcInfo ObjectInfo, srcOpts, dstOpts ObjectOptions) (oi ObjectInfo, e error) { | ||||||
| 	cpSrcDstSame := isStringEqual(pathJoin(srcBucket, srcObject), pathJoin(dstBucket, dstObject)) | 	cpSrcDstSame := isStringEqual(pathJoin(srcBucket, srcObject), pathJoin(dstBucket, dstObject)) | ||||||
| 	// Hold write lock on destination since in both cases
 |  | ||||||
| 	// - if source and destination are same
 |  | ||||||
| 	// - if source and destination are different
 |  | ||||||
| 	// it is the sole mutating state.
 |  | ||||||
| 	objectDWLock := fs.nsMutex.NewNSLock(dstBucket, dstObject) |  | ||||||
| 	if err := objectDWLock.GetLock(globalObjectTimeout); err != nil { |  | ||||||
| 		return oi, err |  | ||||||
| 	} |  | ||||||
| 	defer objectDWLock.Unlock() |  | ||||||
| 	// if source and destination are different, we have to hold
 |  | ||||||
| 	// additional read lock as well to protect against writes on
 |  | ||||||
| 	// source.
 |  | ||||||
| 	if !cpSrcDstSame { | 	if !cpSrcDstSame { | ||||||
| 		// Hold read locks on source object only if we are
 | 		objectDWLock := fs.nsMutex.NewNSLock(dstBucket, dstObject) | ||||||
| 		// going to read data from source object.
 | 		if err := objectDWLock.GetLock(globalObjectTimeout); err != nil { | ||||||
| 		objectSRLock := fs.nsMutex.NewNSLock(srcBucket, srcObject) |  | ||||||
| 		if err := objectSRLock.GetRLock(globalObjectTimeout); err != nil { |  | ||||||
| 			return oi, err | 			return oi, err | ||||||
| 		} | 		} | ||||||
| 		defer objectSRLock.RUnlock() | 		defer objectDWLock.Unlock() | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
| 	if _, err := fs.statBucketDir(ctx, srcBucket); err != nil { | 	if _, err := fs.statBucketDir(ctx, srcBucket); err != nil { | ||||||
| 		return oi, toObjectErr(err, srcBucket) | 		return oi, toObjectErr(err, srcBucket) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if cpSrcDstSame && srcInfo.metadataOnly { | 	if cpSrcDstSame && srcInfo.metadataOnly { | ||||||
| 		// Close any writer which was initialized.
 |  | ||||||
| 		defer srcInfo.Writer.Close() |  | ||||||
| 
 |  | ||||||
| 		fsMetaPath := pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix, srcBucket, srcObject, fs.metaJSONFile) | 		fsMetaPath := pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix, srcBucket, srcObject, fs.metaJSONFile) | ||||||
| 		wlk, err := fs.rwPool.Write(fsMetaPath) | 		wlk, err := fs.rwPool.Write(fsMetaPath) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
|  | @ -478,20 +462,6 @@ func (fs *FSObjects) CopyObject(ctx context.Context, srcBucket, srcObject, dstBu | ||||||
| 		return fsMeta.ToObjectInfo(srcBucket, srcObject, fi), nil | 		return fsMeta.ToObjectInfo(srcBucket, srcObject, fi), nil | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	go func() { |  | ||||||
| 		if gerr := fs.getObject(ctx, srcBucket, srcObject, 0, srcInfo.Size, srcInfo.Writer, srcInfo.ETag, !cpSrcDstSame); gerr != nil { |  | ||||||
| 			if gerr = srcInfo.Writer.Close(); gerr != nil { |  | ||||||
| 				logger.LogIf(ctx, gerr) |  | ||||||
| 			} |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 		// Close writer explicitly signaling we wrote all data.
 |  | ||||||
| 		if gerr := srcInfo.Writer.Close(); gerr != nil { |  | ||||||
| 			logger.LogIf(ctx, gerr) |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 	}() |  | ||||||
| 
 |  | ||||||
| 	objInfo, err := fs.putObject(ctx, dstBucket, dstObject, srcInfo.Reader, srcInfo.UserDefined) | 	objInfo, err := fs.putObject(ctx, dstBucket, dstObject, srcInfo.Reader, srcInfo.UserDefined) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return oi, toObjectErr(err, dstBucket, dstObject) | 		return oi, toObjectErr(err, dstBucket, dstObject) | ||||||
|  | @ -502,7 +472,8 @@ func (fs *FSObjects) CopyObject(ctx context.Context, srcBucket, srcObject, dstBu | ||||||
| 
 | 
 | ||||||
| // GetObjectNInfo - returns object info and a reader for object
 | // GetObjectNInfo - returns object info and a reader for object
 | ||||||
| // content.
 | // content.
 | ||||||
| func (fs *FSObjects) GetObjectNInfo(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header) (gr *GetObjectReader, err error) { | func (fs *FSObjects) GetObjectNInfo(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, lockType LockType) (gr *GetObjectReader, err error) { | ||||||
|  | 
 | ||||||
| 	if err = checkGetObjArgs(ctx, bucket, object); err != nil { | 	if err = checkGetObjArgs(ctx, bucket, object); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  | @ -511,13 +482,26 @@ func (fs *FSObjects) GetObjectNInfo(ctx context.Context, bucket, object string, | ||||||
| 		return nil, toObjectErr(err, bucket) | 		return nil, toObjectErr(err, bucket) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Lock the object before reading.
 | 	var nsUnlocker = func() {} | ||||||
| 	lock := fs.nsMutex.NewNSLock(bucket, object) | 
 | ||||||
| 	if err = lock.GetRLock(globalObjectTimeout); err != nil { | 	if lockType != noLock { | ||||||
| 		logger.LogIf(ctx, err) | 		// Lock the object before reading.
 | ||||||
| 		return nil, err | 		lock := fs.nsMutex.NewNSLock(bucket, object) | ||||||
|  | 		switch lockType { | ||||||
|  | 		case writeLock: | ||||||
|  | 			if err = lock.GetLock(globalObjectTimeout); err != nil { | ||||||
|  | 				logger.LogIf(ctx, err) | ||||||
|  | 				return nil, err | ||||||
|  | 			} | ||||||
|  | 			nsUnlocker = lock.Unlock | ||||||
|  | 		case readLock: | ||||||
|  | 			if err = lock.GetRLock(globalObjectTimeout); err != nil { | ||||||
|  | 				logger.LogIf(ctx, err) | ||||||
|  | 				return nil, err | ||||||
|  | 			} | ||||||
|  | 			nsUnlocker = lock.RUnlock | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| 	nsUnlocker := lock.RUnlock |  | ||||||
| 
 | 
 | ||||||
| 	// For a directory, we need to send an reader that returns no bytes.
 | 	// For a directory, we need to send an reader that returns no bytes.
 | ||||||
| 	if hasSuffix(object, slashSeparator) { | 	if hasSuffix(object, slashSeparator) { | ||||||
|  | @ -535,7 +519,7 @@ func (fs *FSObjects) GetObjectNInfo(ctx context.Context, bucket, object string, | ||||||
| 
 | 
 | ||||||
| 	// Take a rwPool lock for NFS gateway type deployment
 | 	// Take a rwPool lock for NFS gateway type deployment
 | ||||||
| 	rwPoolUnlocker := func() {} | 	rwPoolUnlocker := func() {} | ||||||
| 	if bucket != minioMetaBucket { | 	if bucket != minioMetaBucket && lockType != noLock { | ||||||
| 		fsMetaPath := pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix, bucket, object, fs.metaJSONFile) | 		fsMetaPath := pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix, bucket, object, fs.metaJSONFile) | ||||||
| 		_, err = fs.rwPool.Open(fsMetaPath) | 		_, err = fs.rwPool.Open(fsMetaPath) | ||||||
| 		if err != nil && err != errFileNotFound { | 		if err != nil && err != errFileNotFound { | ||||||
|  |  | ||||||
|  | @ -616,7 +616,7 @@ func (a *azureObjects) ListObjectsV2(ctx context.Context, bucket, prefix, contin | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // GetObjectNInfo - returns object info and locked object ReadCloser
 | // GetObjectNInfo - returns object info and locked object ReadCloser
 | ||||||
| func (a *azureObjects) GetObjectNInfo(ctx context.Context, bucket, object string, rs *minio.HTTPRangeSpec, h http.Header) (gr *minio.GetObjectReader, err error) { | func (a *azureObjects) GetObjectNInfo(ctx context.Context, bucket, object string, rs *minio.HTTPRangeSpec, h http.Header, lockType minio.LockType) (gr *minio.GetObjectReader, err error) { | ||||||
| 	var objInfo minio.ObjectInfo | 	var objInfo minio.ObjectInfo | ||||||
| 	objInfo, err = a.GetObjectInfo(ctx, bucket, object, minio.ObjectOptions{}) | 	objInfo, err = a.GetObjectInfo(ctx, bucket, object, minio.ObjectOptions{}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  |  | ||||||
|  | @ -396,7 +396,7 @@ func (l *b2Objects) ListObjectsV2(ctx context.Context, bucket, prefix, continuat | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // GetObjectNInfo - returns object info and locked object ReadCloser
 | // GetObjectNInfo - returns object info and locked object ReadCloser
 | ||||||
| func (l *b2Objects) GetObjectNInfo(ctx context.Context, bucket, object string, rs *minio.HTTPRangeSpec, h http.Header) (gr *minio.GetObjectReader, err error) { | func (l *b2Objects) GetObjectNInfo(ctx context.Context, bucket, object string, rs *minio.HTTPRangeSpec, h http.Header, lockType minio.LockType) (gr *minio.GetObjectReader, err error) { | ||||||
| 	var objInfo minio.ObjectInfo | 	var objInfo minio.ObjectInfo | ||||||
| 	objInfo, err = l.GetObjectInfo(ctx, bucket, object, minio.ObjectOptions{}) | 	objInfo, err = l.GetObjectInfo(ctx, bucket, object, minio.ObjectOptions{}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  |  | ||||||
|  | @ -737,7 +737,7 @@ func (l *gcsGateway) ListObjectsV2(ctx context.Context, bucket, prefix, continua | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // GetObjectNInfo - returns object info and locked object ReadCloser
 | // GetObjectNInfo - returns object info and locked object ReadCloser
 | ||||||
| func (l *gcsGateway) GetObjectNInfo(ctx context.Context, bucket, object string, rs *minio.HTTPRangeSpec, h http.Header) (gr *minio.GetObjectReader, err error) { | func (l *gcsGateway) GetObjectNInfo(ctx context.Context, bucket, object string, rs *minio.HTTPRangeSpec, h http.Header, lockType minio.LockType) (gr *minio.GetObjectReader, err error) { | ||||||
| 	var objInfo minio.ObjectInfo | 	var objInfo minio.ObjectInfo | ||||||
| 	objInfo, err = l.GetObjectInfo(ctx, bucket, object, minio.ObjectOptions{}) | 	objInfo, err = l.GetObjectInfo(ctx, bucket, object, minio.ObjectOptions{}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  |  | ||||||
|  | @ -507,7 +507,7 @@ func (t *tritonObjects) ListObjectsV2(ctx context.Context, bucket, prefix, conti | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // GetObjectNInfo - returns object info and locked object ReadCloser
 | // GetObjectNInfo - returns object info and locked object ReadCloser
 | ||||||
| func (t *tritonObjects) GetObjectNInfo(ctx context.Context, bucket, object string, rs *minio.HTTPRangeSpec, h http.Header) (gr *minio.GetObjectReader, err error) { | func (t *tritonObjects) GetObjectNInfo(ctx context.Context, bucket, object string, rs *minio.HTTPRangeSpec, h http.Header, lockType minio.LockType) (gr *minio.GetObjectReader, err error) { | ||||||
| 	var objInfo minio.ObjectInfo | 	var objInfo minio.ObjectInfo | ||||||
| 	objInfo, err = t.GetObjectInfo(ctx, bucket, object, minio.ObjectOptions{}) | 	objInfo, err = t.GetObjectInfo(ctx, bucket, object, minio.ObjectOptions{}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  |  | ||||||
|  | @ -547,7 +547,7 @@ func ossGetObject(ctx context.Context, client *oss.Client, bucket, key string, s | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // GetObjectNInfo - returns object info and locked object ReadCloser
 | // GetObjectNInfo - returns object info and locked object ReadCloser
 | ||||||
| func (l *ossObjects) GetObjectNInfo(ctx context.Context, bucket, object string, rs *minio.HTTPRangeSpec, h http.Header) (gr *minio.GetObjectReader, err error) { | func (l *ossObjects) GetObjectNInfo(ctx context.Context, bucket, object string, rs *minio.HTTPRangeSpec, h http.Header, lockType minio.LockType) (gr *minio.GetObjectReader, err error) { | ||||||
| 	var objInfo minio.ObjectInfo | 	var objInfo minio.ObjectInfo | ||||||
| 	objInfo, err = l.GetObjectInfo(ctx, bucket, object, minio.ObjectOptions{}) | 	objInfo, err = l.GetObjectInfo(ctx, bucket, object, minio.ObjectOptions{}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  |  | ||||||
|  | @ -328,7 +328,7 @@ func (l *s3Objects) ListObjectsV2(ctx context.Context, bucket, prefix, continuat | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // GetObjectNInfo - returns object info and locked object ReadCloser
 | // GetObjectNInfo - returns object info and locked object ReadCloser
 | ||||||
| func (l *s3Objects) GetObjectNInfo(ctx context.Context, bucket, object string, rs *minio.HTTPRangeSpec, h http.Header) (gr *minio.GetObjectReader, err error) { | func (l *s3Objects) GetObjectNInfo(ctx context.Context, bucket, object string, rs *minio.HTTPRangeSpec, h http.Header, lockType minio.LockType) (gr *minio.GetObjectReader, err error) { | ||||||
| 	var objInfo minio.ObjectInfo | 	var objInfo minio.ObjectInfo | ||||||
| 	objInfo, err = l.GetObjectInfo(ctx, bucket, object, minio.ObjectOptions{}) | 	objInfo, err = l.GetObjectInfo(ctx, bucket, object, minio.ObjectOptions{}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  |  | ||||||
|  | @ -432,7 +432,7 @@ func (s *siaObjects) ListObjects(ctx context.Context, bucket string, prefix stri | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // GetObjectNInfo - returns object info and locked object ReadCloser
 | // GetObjectNInfo - returns object info and locked object ReadCloser
 | ||||||
| func (s *siaObjects) GetObjectNInfo(ctx context.Context, bucket, object string, rs *minio.HTTPRangeSpec, h http.Header) (gr *minio.GetObjectReader, err error) { | func (s *siaObjects) GetObjectNInfo(ctx context.Context, bucket, object string, rs *minio.HTTPRangeSpec, h http.Header, lockType minio.LockType) (gr *minio.GetObjectReader, err error) { | ||||||
| 	var objInfo minio.ObjectInfo | 	var objInfo minio.ObjectInfo | ||||||
| 	objInfo, err = s.GetObjectInfo(ctx, bucket, object, minio.ObjectOptions{}) | 	objInfo, err = s.GetObjectInfo(ctx, bucket, object, minio.ObjectOptions{}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  |  | ||||||
|  | @ -32,6 +32,15 @@ type ObjectOptions struct { | ||||||
| 	ServerSideEncryption encrypt.ServerSide | 	ServerSideEncryption encrypt.ServerSide | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // LockType represents required locking for ObjectLayer operations
 | ||||||
|  | type LockType int | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	noLock LockType = iota | ||||||
|  | 	readLock | ||||||
|  | 	writeLock | ||||||
|  | ) | ||||||
|  | 
 | ||||||
| // ObjectLayer implements primitives for object API layer.
 | // ObjectLayer implements primitives for object API layer.
 | ||||||
| type ObjectLayer interface { | type ObjectLayer interface { | ||||||
| 	// Storage operations.
 | 	// Storage operations.
 | ||||||
|  | @ -54,7 +63,7 @@ type ObjectLayer interface { | ||||||
| 	//
 | 	//
 | ||||||
| 	// IMPORTANTLY, when implementations return err != nil, this
 | 	// IMPORTANTLY, when implementations return err != nil, this
 | ||||||
| 	// function MUST NOT return a non-nil ReadCloser.
 | 	// function MUST NOT return a non-nil ReadCloser.
 | ||||||
| 	GetObjectNInfo(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header) (reader *GetObjectReader, err error) | 	GetObjectNInfo(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, lockType LockType) (reader *GetObjectReader, err error) | ||||||
| 	GetObject(ctx context.Context, bucket, object string, startOffset int64, length int64, writer io.Writer, etag string, opts ObjectOptions) (err error) | 	GetObject(ctx context.Context, bucket, object string, startOffset int64, length int64, writer io.Writer, etag string, opts ObjectOptions) (err error) | ||||||
| 	GetObjectInfo(ctx context.Context, bucket, object string, opts ObjectOptions) (objInfo ObjectInfo, err error) | 	GetObjectInfo(ctx context.Context, bucket, object string, opts ObjectOptions) (objInfo ObjectInfo, err error) | ||||||
| 	PutObject(ctx context.Context, bucket, object string, data *hash.Reader, metadata map[string]string, opts ObjectOptions) (objInfo ObjectInfo, err error) | 	PutObject(ctx context.Context, bucket, object string, data *hash.Reader, metadata map[string]string, opts ObjectOptions) (objInfo ObjectInfo, err error) | ||||||
|  |  | ||||||
|  | @ -379,11 +379,14 @@ func NewGetObjectReader(rs *HTTPRangeSpec, oi ObjectInfo, cleanUpFns ...func()) | ||||||
| 		// encrypted bytes. The header parameter is used to
 | 		// encrypted bytes. The header parameter is used to
 | ||||||
| 		// provide encryption parameters.
 | 		// provide encryption parameters.
 | ||||||
| 		fn = func(inputReader io.Reader, h http.Header, cFns ...func()) (r *GetObjectReader, err error) { | 		fn = func(inputReader io.Reader, h http.Header, cFns ...func()) (r *GetObjectReader, err error) { | ||||||
|  | 
 | ||||||
|  | 			copySource := h.Get(crypto.SSECopyAlgorithm) != "" | ||||||
|  | 
 | ||||||
| 			cFns = append(cleanUpFns, cFns...) | 			cFns = append(cleanUpFns, cFns...) | ||||||
| 			// Attach decrypter on inputReader
 | 			// Attach decrypter on inputReader
 | ||||||
| 			var decReader io.Reader | 			var decReader io.Reader | ||||||
| 			decReader, err = DecryptBlocksRequestR(inputReader, h, | 			decReader, err = DecryptBlocksRequestR(inputReader, h, | ||||||
| 				off, length, seqNumber, partStart, oi, false) | 				off, length, seqNumber, partStart, oi, copySource) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				// Call the cleanup funcs
 | 				// Call the cleanup funcs
 | ||||||
| 				for i := len(cFns) - 1; i >= 0; i-- { | 				for i := len(cFns) - 1; i >= 0; i-- { | ||||||
|  |  | ||||||
|  | @ -22,7 +22,6 @@ import ( | ||||||
| 	"encoding/binary" | 	"encoding/binary" | ||||||
| 	"encoding/hex" | 	"encoding/hex" | ||||||
| 	"encoding/xml" | 	"encoding/xml" | ||||||
| 	"fmt" |  | ||||||
| 	"io" | 	"io" | ||||||
| 	goioutil "io/ioutil" | 	goioutil "io/ioutil" | ||||||
| 	"net" | 	"net" | ||||||
|  | @ -72,7 +71,6 @@ func setHeadGetRespHeaders(w http.ResponseWriter, reqParams url.Values) { | ||||||
| // on an SQL expression. In the request, along with the sql expression, you must
 | // on an SQL expression. In the request, along with the sql expression, you must
 | ||||||
| // also specify a data serialization format (JSON, CSV) of the object.
 | // also specify a data serialization format (JSON, CSV) of the object.
 | ||||||
| func (api objectAPIHandlers) SelectObjectContentHandler(w http.ResponseWriter, r *http.Request) { | func (api objectAPIHandlers) SelectObjectContentHandler(w http.ResponseWriter, r *http.Request) { | ||||||
| 
 |  | ||||||
| 	ctx := newContext(r, w, "SelectObject") | 	ctx := newContext(r, w, "SelectObject") | ||||||
| 
 | 
 | ||||||
| 	// Fetch object stat info.
 | 	// Fetch object stat info.
 | ||||||
|  | @ -156,7 +154,7 @@ func (api objectAPIHandlers) SelectObjectContentHandler(w http.ResponseWriter, r | ||||||
| 		getObjectNInfo = api.CacheAPI().GetObjectNInfo | 		getObjectNInfo = api.CacheAPI().GetObjectNInfo | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	gr, err := getObjectNInfo(ctx, bucket, object, nil, r.Header) | 	gr, err := getObjectNInfo(ctx, bucket, object, nil, r.Header, readLock) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		writeErrorResponse(w, toAPIErrorCode(err), r.URL) | 		writeErrorResponse(w, toAPIErrorCode(err), r.URL) | ||||||
| 		return | 		return | ||||||
|  | @ -351,7 +349,7 @@ func (api objectAPIHandlers) GetObjectHandler(w http.ResponseWriter, r *http.Req | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	gr, err := getObjectNInfo(ctx, bucket, object, rs, r.Header) | 	gr, err := getObjectNInfo(ctx, bucket, object, rs, r.Header, readLock) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		writeErrorResponse(w, toAPIErrorCode(err), r.URL) | 		writeErrorResponse(w, toAPIErrorCode(err), r.URL) | ||||||
| 		return | 		return | ||||||
|  | @ -604,6 +602,11 @@ func getCpObjMetadataFromHeader(ctx context.Context, r *http.Request, userMeta m | ||||||
| // ----------
 | // ----------
 | ||||||
| // This implementation of the PUT operation adds an object to a bucket
 | // This implementation of the PUT operation adds an object to a bucket
 | ||||||
| // while reading the object from another source.
 | // while reading the object from another source.
 | ||||||
|  | // Notice: The S3 client can send secret keys in headers for encryption related jobs,
 | ||||||
|  | // the handler should ensure to remove these keys before sending them to the object layer.
 | ||||||
|  | // Currently these keys are:
 | ||||||
|  | //   - X-Amz-Server-Side-Encryption-Customer-Key
 | ||||||
|  | //   - X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key
 | ||||||
| func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Request) { | func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Request) { | ||||||
| 	ctx := newContext(r, w, "CopyObject") | 	ctx := newContext(r, w, "CopyObject") | ||||||
| 
 | 
 | ||||||
|  | @ -649,12 +652,6 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	var srcOpts, dstOpts ObjectOptions | 	var srcOpts, dstOpts ObjectOptions | ||||||
| 	cpSrcDstSame := isStringEqual(pathJoin(srcBucket, srcObject), pathJoin(dstBucket, dstObject)) |  | ||||||
| 	srcInfo, err := objectAPI.GetObjectInfo(ctx, srcBucket, srcObject, srcOpts) |  | ||||||
| 	if err != nil { |  | ||||||
| 		writeErrorResponse(w, toAPIErrorCode(err), r.URL) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	// Deny if WORM is enabled
 | 	// Deny if WORM is enabled
 | ||||||
| 	if globalWORMEnabled { | 	if globalWORMEnabled { | ||||||
|  | @ -664,13 +661,45 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if objectAPI.IsEncryptionSupported() { | 	cpSrcDstSame := isStringEqual(pathJoin(srcBucket, srcObject), pathJoin(dstBucket, dstObject)) | ||||||
| 		if apiErr, _ := DecryptCopyObjectInfo(&srcInfo, r.Header); apiErr != ErrNone { | 
 | ||||||
| 			writeErrorResponse(w, apiErr, r.URL) | 	getObjectNInfo := objectAPI.GetObjectNInfo | ||||||
| 			return | 	if api.CacheAPI() != nil { | ||||||
|  | 		getObjectNInfo = api.CacheAPI().GetObjectNInfo | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Get request range.
 | ||||||
|  | 	var rs *HTTPRangeSpec | ||||||
|  | 	rangeHeader := r.Header.Get("x-amz-copy-source-range") | ||||||
|  | 	if rangeHeader != "" { | ||||||
|  | 		var parseRangeErr error | ||||||
|  | 		if rs, parseRangeErr = parseRequestRangeSpec(rangeHeader); parseRangeErr != nil { | ||||||
|  | 			// Handle only errInvalidRange. Ignore other
 | ||||||
|  | 			// parse error and treat it as regular Get
 | ||||||
|  | 			// request like Amazon S3.
 | ||||||
|  | 			if parseRangeErr == errInvalidRange { | ||||||
|  | 				writeErrorResponse(w, ErrInvalidRange, r.URL) | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			// log the error.
 | ||||||
|  | 			logger.LogIf(ctx, parseRangeErr) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	var lock = noLock | ||||||
|  | 	if !cpSrcDstSame { | ||||||
|  | 		lock = readLock | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	gr, err := getObjectNInfo(ctx, srcBucket, srcObject, rs, r.Header, lock) | ||||||
|  | 	if err != nil { | ||||||
|  | 		writeErrorResponse(w, toAPIErrorCode(err), r.URL) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	defer gr.Close() | ||||||
|  | 	srcInfo := gr.ObjInfo | ||||||
|  | 
 | ||||||
| 	// Verify before x-amz-copy-source preconditions before continuing with CopyObject.
 | 	// Verify before x-amz-copy-source preconditions before continuing with CopyObject.
 | ||||||
| 	if checkCopyObjectPreconditions(w, r, srcInfo) { | 	if checkCopyObjectPreconditions(w, r, srcInfo) { | ||||||
| 		return | 		return | ||||||
|  | @ -682,21 +711,16 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Initialize pipe.
 |  | ||||||
| 	pipeReader, pipeWriter := io.Pipe() |  | ||||||
| 
 |  | ||||||
| 	// We have to copy metadata only if source and destination are same.
 | 	// We have to copy metadata only if source and destination are same.
 | ||||||
| 	// this changes for encryption which can be observed below.
 | 	// this changes for encryption which can be observed below.
 | ||||||
| 	if cpSrcDstSame { | 	if cpSrcDstSame { | ||||||
| 		srcInfo.metadataOnly = true | 		srcInfo.metadataOnly = true | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	var writer io.WriteCloser = pipeWriter | 	var reader io.Reader = gr | ||||||
| 	var reader io.Reader = pipeReader |  | ||||||
| 
 | 
 | ||||||
| 	srcInfo.Reader, err = hash.NewReader(reader, srcInfo.Size, "", "") | 	srcInfo.Reader, err = hash.NewReader(reader, srcInfo.Size, "", "") | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		pipeWriter.CloseWithError(err) |  | ||||||
| 		writeErrorResponse(w, toAPIErrorCode(err), r.URL) | 		writeErrorResponse(w, toAPIErrorCode(err), r.URL) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  | @ -717,7 +741,6 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re | ||||||
| 				newKey, err = ParseSSECustomerRequest(r) | 				newKey, err = ParseSSECustomerRequest(r) | ||||||
| 			} | 			} | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				pipeWriter.CloseWithError(err) |  | ||||||
| 				writeErrorResponse(w, toAPIErrorCode(err), r.URL) | 				writeErrorResponse(w, toAPIErrorCode(err), r.URL) | ||||||
| 				return | 				return | ||||||
| 			} | 			} | ||||||
|  | @ -729,7 +752,6 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re | ||||||
| 			// Get the old key which needs to be rotated.
 | 			// Get the old key which needs to be rotated.
 | ||||||
| 			oldKey, err = ParseSSECopyCustomerRequest(r.Header, srcInfo.UserDefined) | 			oldKey, err = ParseSSECopyCustomerRequest(r.Header, srcInfo.UserDefined) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				pipeWriter.CloseWithError(err) |  | ||||||
| 				writeErrorResponse(w, toAPIErrorCode(err), r.URL) | 				writeErrorResponse(w, toAPIErrorCode(err), r.URL) | ||||||
| 				return | 				return | ||||||
| 			} | 			} | ||||||
|  | @ -737,7 +759,6 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re | ||||||
| 				encMetadata[k] = v | 				encMetadata[k] = v | ||||||
| 			} | 			} | ||||||
| 			if err = rotateKey(oldKey, newKey, srcBucket, srcObject, encMetadata); err != nil { | 			if err = rotateKey(oldKey, newKey, srcBucket, srcObject, encMetadata); err != nil { | ||||||
| 				pipeWriter.CloseWithError(err) |  | ||||||
| 				writeErrorResponse(w, toAPIErrorCode(err), r.URL) | 				writeErrorResponse(w, toAPIErrorCode(err), r.URL) | ||||||
| 				return | 				return | ||||||
| 			} | 			} | ||||||
|  | @ -746,14 +767,6 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re | ||||||
| 			srcInfo.metadataOnly = true | 			srcInfo.metadataOnly = true | ||||||
| 		} else { | 		} else { | ||||||
| 			if sseCopyC || sseCopyS3 { | 			if sseCopyC || sseCopyS3 { | ||||||
| 				// Source is encrypted make sure to save the encrypted size.
 |  | ||||||
| 				writer = ioutil.LimitedWriter(writer, 0, srcInfo.Size) |  | ||||||
| 				writer, srcInfo.Size, err = DecryptAllBlocksCopyRequest(writer, r, srcBucket, srcObject, srcInfo) |  | ||||||
| 				if err != nil { |  | ||||||
| 					pipeWriter.CloseWithError(err) |  | ||||||
| 					writeErrorResponse(w, toAPIErrorCode(err), r.URL) |  | ||||||
| 					return |  | ||||||
| 				} |  | ||||||
| 				// We are not only copying just metadata instead
 | 				// We are not only copying just metadata instead
 | ||||||
| 				// we are creating a new object at this point, even
 | 				// we are creating a new object at this point, even
 | ||||||
| 				// if source and destination are same objects.
 | 				// if source and destination are same objects.
 | ||||||
|  | @ -765,7 +778,6 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re | ||||||
| 			if sseC || sseS3 { | 			if sseC || sseS3 { | ||||||
| 				reader, err = newEncryptReader(reader, newKey, dstBucket, dstObject, encMetadata, sseS3) | 				reader, err = newEncryptReader(reader, newKey, dstBucket, dstObject, encMetadata, sseS3) | ||||||
| 				if err != nil { | 				if err != nil { | ||||||
| 					pipeWriter.CloseWithError(err) |  | ||||||
| 					writeErrorResponse(w, toAPIErrorCode(err), r.URL) | 					writeErrorResponse(w, toAPIErrorCode(err), r.URL) | ||||||
| 					return | 					return | ||||||
| 				} | 				} | ||||||
|  | @ -776,20 +788,29 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re | ||||||
| 				if !sseCopyC && !sseCopyS3 { | 				if !sseCopyC && !sseCopyS3 { | ||||||
| 					size = srcInfo.EncryptedSize() | 					size = srcInfo.EncryptedSize() | ||||||
| 				} | 				} | ||||||
|  | 			} else { | ||||||
|  | 				if sseCopyC || sseCopyS3 { | ||||||
|  | 					size, _ = srcInfo.DecryptedSize() | ||||||
|  | 					delete(srcInfo.UserDefined, crypto.SSEIV) | ||||||
|  | 					delete(srcInfo.UserDefined, crypto.SSESealAlgorithm) | ||||||
|  | 					delete(srcInfo.UserDefined, crypto.SSECSealedKey) | ||||||
|  | 					delete(srcInfo.UserDefined, crypto.SSEMultipart) | ||||||
|  | 					delete(srcInfo.UserDefined, crypto.S3SealedKey) | ||||||
|  | 					delete(srcInfo.UserDefined, crypto.S3KMSSealedKey) | ||||||
|  | 					delete(srcInfo.UserDefined, crypto.S3KMSKeyID) | ||||||
|  | 				} | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			srcInfo.Reader, err = hash.NewReader(reader, size, "", "") // do not try to verify encrypted content
 | 			srcInfo.Reader, err = hash.NewReader(reader, size, "", "") // do not try to verify encrypted content
 | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				pipeWriter.CloseWithError(err) |  | ||||||
| 				writeErrorResponse(w, toAPIErrorCode(err), r.URL) | 				writeErrorResponse(w, toAPIErrorCode(err), r.URL) | ||||||
| 				return | 				return | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	srcInfo.Writer = writer | 
 | ||||||
| 	srcInfo.UserDefined, err = getCpObjMetadataFromHeader(ctx, r, srcInfo.UserDefined) | 	srcInfo.UserDefined, err = getCpObjMetadataFromHeader(ctx, r, srcInfo.UserDefined) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		pipeWriter.CloseWithError(err) |  | ||||||
| 		writeErrorResponse(w, ErrInternalError, r.URL) | 		writeErrorResponse(w, ErrInternalError, r.URL) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  | @ -800,13 +821,15 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re | ||||||
| 		srcInfo.UserDefined[k] = v | 		srcInfo.UserDefined[k] = v | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	// Ensure that metadata does not contain sensitive information
 | ||||||
|  | 	crypto.RemoveSensitiveEntries(srcInfo.UserDefined) | ||||||
|  | 
 | ||||||
| 	// Check if x-amz-metadata-directive was not set to REPLACE and source,
 | 	// Check if x-amz-metadata-directive was not set to REPLACE and source,
 | ||||||
| 	// desination are same objects. Apply this restriction also when
 | 	// desination are same objects. Apply this restriction also when
 | ||||||
| 	// metadataOnly is true indicating that we are not overwriting the object.
 | 	// metadataOnly is true indicating that we are not overwriting the object.
 | ||||||
| 	// if encryption is enabled we do not need explicit "REPLACE" metadata to
 | 	// if encryption is enabled we do not need explicit "REPLACE" metadata to
 | ||||||
| 	// be enabled as well - this is to allow for key-rotation.
 | 	// be enabled as well - this is to allow for key-rotation.
 | ||||||
| 	if !isMetadataReplace(r.Header) && srcInfo.metadataOnly && !crypto.SSEC.IsEncrypted(srcInfo.UserDefined) { | 	if !isMetadataReplace(r.Header) && srcInfo.metadataOnly && !crypto.SSEC.IsEncrypted(srcInfo.UserDefined) { | ||||||
| 		pipeWriter.CloseWithError(fmt.Errorf("invalid copy dest")) |  | ||||||
| 		// If x-amz-metadata-directive is not set to REPLACE then we need
 | 		// If x-amz-metadata-directive is not set to REPLACE then we need
 | ||||||
| 		// to error out if source and destination are same.
 | 		// to error out if source and destination are same.
 | ||||||
| 		writeErrorResponse(w, ErrInvalidCopyDest, r.URL) | 		writeErrorResponse(w, ErrInvalidCopyDest, r.URL) | ||||||
|  | @ -844,27 +867,15 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re | ||||||
| 		} | 		} | ||||||
| 		var dstRecords []dns.SrvRecord | 		var dstRecords []dns.SrvRecord | ||||||
| 		if dstRecords, err = globalDNSConfig.Get(dstBucket); err == nil { | 		if dstRecords, err = globalDNSConfig.Get(dstBucket); err == nil { | ||||||
| 			go func() { |  | ||||||
| 				if gerr := objectAPI.GetObject(ctx, srcBucket, srcObject, 0, srcInfo.Size, srcInfo.Writer, srcInfo.ETag, srcOpts); gerr != nil { |  | ||||||
| 					pipeWriter.CloseWithError(gerr) |  | ||||||
| 					writeErrorResponse(w, ErrInternalError, r.URL) |  | ||||||
| 					return |  | ||||||
| 				} |  | ||||||
| 				// Close writer explicitly to indicate data has been written
 |  | ||||||
| 				srcInfo.Writer.Close() |  | ||||||
| 			}() |  | ||||||
| 
 |  | ||||||
| 			// Send PutObject request to appropriate instance (in federated deployment)
 | 			// Send PutObject request to appropriate instance (in federated deployment)
 | ||||||
| 			host, port := getRandomHostPort(dstRecords) | 			host, port := getRandomHostPort(dstRecords) | ||||||
| 			client, rerr := getRemoteInstanceClient(host, port) | 			client, rerr := getRemoteInstanceClient(host, port) | ||||||
| 			if rerr != nil { | 			if rerr != nil { | ||||||
| 				pipeWriter.CloseWithError(rerr) |  | ||||||
| 				writeErrorResponse(w, ErrInternalError, r.URL) | 				writeErrorResponse(w, ErrInternalError, r.URL) | ||||||
| 				return | 				return | ||||||
| 			} | 			} | ||||||
| 			remoteObjInfo, rerr := client.PutObject(dstBucket, dstObject, srcInfo.Reader, srcInfo.Size, "", "", srcInfo.UserDefined, dstOpts.ServerSideEncryption) | 			remoteObjInfo, rerr := client.PutObject(dstBucket, dstObject, srcInfo.Reader, srcInfo.Size, "", "", srcInfo.UserDefined, dstOpts.ServerSideEncryption) | ||||||
| 			if rerr != nil { | 			if rerr != nil { | ||||||
| 				pipeWriter.CloseWithError(rerr) |  | ||||||
| 				writeErrorResponse(w, ErrInternalError, r.URL) | 				writeErrorResponse(w, ErrInternalError, r.URL) | ||||||
| 				return | 				return | ||||||
| 			} | 			} | ||||||
|  | @ -876,14 +887,11 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re | ||||||
| 		// object is same then only metadata is updated.
 | 		// object is same then only metadata is updated.
 | ||||||
| 		objInfo, err = objectAPI.CopyObject(ctx, srcBucket, srcObject, dstBucket, dstObject, srcInfo, srcOpts, dstOpts) | 		objInfo, err = objectAPI.CopyObject(ctx, srcBucket, srcObject, dstBucket, dstObject, srcInfo, srcOpts, dstOpts) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			pipeWriter.CloseWithError(err) |  | ||||||
| 			writeErrorResponse(w, toAPIErrorCode(err), r.URL) | 			writeErrorResponse(w, toAPIErrorCode(err), r.URL) | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	pipeReader.Close() |  | ||||||
| 
 |  | ||||||
| 	response := generateCopyObjectResponse(objInfo.ETag, objInfo.ModTime) | 	response := generateCopyObjectResponse(objInfo.ETag, objInfo.ModTime) | ||||||
| 	encodedSuccessResponse := encodeResponse(response) | 	encodedSuccessResponse := encodeResponse(response) | ||||||
| 
 | 
 | ||||||
|  | @ -911,6 +919,11 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re | ||||||
| // PutObjectHandler - PUT Object
 | // PutObjectHandler - PUT Object
 | ||||||
| // ----------
 | // ----------
 | ||||||
| // This implementation of the PUT operation adds an object to a bucket.
 | // This implementation of the PUT operation adds an object to a bucket.
 | ||||||
|  | // Notice: The S3 client can send secret keys in headers for encryption related jobs,
 | ||||||
|  | // the handler should ensure to remove these keys before sending them to the object layer.
 | ||||||
|  | // Currently these keys are:
 | ||||||
|  | //   - X-Amz-Server-Side-Encryption-Customer-Key
 | ||||||
|  | //   - X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key
 | ||||||
| func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Request) { | func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Request) { | ||||||
| 	ctx := newContext(r, w, "PutObject") | 	ctx := newContext(r, w, "PutObject") | ||||||
| 
 | 
 | ||||||
|  | @ -1080,6 +1093,9 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	// Ensure that metadata does not contain sensitive information
 | ||||||
|  | 	crypto.RemoveSensitiveEntries(metadata) | ||||||
|  | 
 | ||||||
| 	if api.CacheAPI() != nil && !hasServerSideEncryptionHeader(r.Header) { | 	if api.CacheAPI() != nil && !hasServerSideEncryptionHeader(r.Header) { | ||||||
| 		putObject = api.CacheAPI().PutObject | 		putObject = api.CacheAPI().PutObject | ||||||
| 	} | 	} | ||||||
|  | @ -1127,6 +1143,11 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req | ||||||
| /// Multipart objectAPIHandlers
 | /// Multipart objectAPIHandlers
 | ||||||
| 
 | 
 | ||||||
| // NewMultipartUploadHandler - New multipart upload.
 | // NewMultipartUploadHandler - New multipart upload.
 | ||||||
|  | // Notice: The S3 client can send secret keys in headers for encryption related jobs,
 | ||||||
|  | // the handler should ensure to remove these keys before sending them to the object layer.
 | ||||||
|  | // Currently these keys are:
 | ||||||
|  | //   - X-Amz-Server-Side-Encryption-Customer-Key
 | ||||||
|  | //   - X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key
 | ||||||
| func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r *http.Request) { | func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r *http.Request) { | ||||||
| 	ctx := newContext(r, w, "NewMultipartUpload") | 	ctx := newContext(r, w, "NewMultipartUpload") | ||||||
| 
 | 
 | ||||||
|  | @ -1193,6 +1214,9 @@ func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r | ||||||
| 		metadata[k] = v | 		metadata[k] = v | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	// Ensure that metadata does not contain sensitive information
 | ||||||
|  | 	crypto.RemoveSensitiveEntries(metadata) | ||||||
|  | 
 | ||||||
| 	newMultipartUpload := objectAPI.NewMultipartUpload | 	newMultipartUpload := objectAPI.NewMultipartUpload | ||||||
| 	if api.CacheAPI() != nil && !hasServerSideEncryptionHeader(r.Header) { | 	if api.CacheAPI() != nil && !hasServerSideEncryptionHeader(r.Header) { | ||||||
| 		newMultipartUpload = api.CacheAPI().NewMultipartUpload | 		newMultipartUpload = api.CacheAPI().NewMultipartUpload | ||||||
|  | @ -1262,11 +1286,6 @@ func (api objectAPIHandlers) CopyObjectPartHandler(w http.ResponseWriter, r *htt | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	var srcOpts, dstOpts ObjectOptions | 	var srcOpts, dstOpts ObjectOptions | ||||||
| 	srcInfo, err := objectAPI.GetObjectInfo(ctx, srcBucket, srcObject, srcOpts) |  | ||||||
| 	if err != nil { |  | ||||||
| 		writeErrorResponse(w, toAPIErrorCode(err), r.URL) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	// Deny if WORM is enabled
 | 	// Deny if WORM is enabled
 | ||||||
| 	if globalWORMEnabled { | 	if globalWORMEnabled { | ||||||
|  | @ -1276,20 +1295,38 @@ func (api objectAPIHandlers) CopyObjectPartHandler(w http.ResponseWriter, r *htt | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if objectAPI.IsEncryptionSupported() { | 	getObjectNInfo := objectAPI.GetObjectNInfo | ||||||
| 		if apiErr, _ := DecryptCopyObjectInfo(&srcInfo, r.Header); apiErr != ErrNone { | 	if api.CacheAPI() != nil { | ||||||
| 			writeErrorResponse(w, apiErr, r.URL) | 		getObjectNInfo = api.CacheAPI().GetObjectNInfo | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Get request range.
 | 	// Get request range.
 | ||||||
| 	var startOffset, length int64 | 	var rs *HTTPRangeSpec | ||||||
| 	rangeHeader := r.Header.Get("x-amz-copy-source-range") | 	rangeHeader := r.Header.Get("x-amz-copy-source-range") | ||||||
| 	if startOffset, length, err = parseCopyPartRange(rangeHeader, srcInfo.Size); err != nil { | 	if rangeHeader != "" { | ||||||
| 		logger.GetReqInfo(ctx).AppendTags("rangeHeader", rangeHeader) | 		var parseRangeErr error | ||||||
| 		logger.LogIf(ctx, err) | 		if rs, parseRangeErr = parseCopyPartRangeSpec(rangeHeader); parseRangeErr != nil { | ||||||
| 		writeCopyPartErr(w, err, r.URL) | 			// Handle only errInvalidRange
 | ||||||
|  | 			// Ignore other parse error and treat it as regular Get request like Amazon S3.
 | ||||||
|  | 			logger.GetReqInfo(ctx).AppendTags("rangeHeader", rangeHeader) | ||||||
|  | 			logger.LogIf(ctx, parseRangeErr) | ||||||
|  | 			writeCopyPartErr(w, parseRangeErr, r.URL) | ||||||
|  | 			return | ||||||
|  | 
 | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	gr, err := getObjectNInfo(ctx, srcBucket, srcObject, rs, r.Header, readLock) | ||||||
|  | 	if err != nil { | ||||||
|  | 		writeErrorResponse(w, toAPIErrorCode(err), r.URL) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	defer gr.Close() | ||||||
|  | 	srcInfo := gr.ObjInfo | ||||||
|  | 
 | ||||||
|  | 	// Special care for CopyObjectPart
 | ||||||
|  | 	if partRangeErr := checkCopyPartRangeWithSize(rs, srcInfo.Size); partRangeErr != nil { | ||||||
|  | 		writeCopyPartErr(w, partRangeErr, r.URL) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -1298,21 +1335,30 @@ func (api objectAPIHandlers) CopyObjectPartHandler(w http.ResponseWriter, r *htt | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	// Get the object offset & length
 | ||||||
|  | 	startOffset, length, _ := rs.GetOffsetLength(srcInfo.Size) | ||||||
|  | 
 | ||||||
|  | 	if objectAPI.IsEncryptionSupported() { | ||||||
|  | 		if crypto.IsEncrypted(srcInfo.UserDefined) { | ||||||
|  | 			decryptedSize, decryptErr := srcInfo.DecryptedSize() | ||||||
|  | 			if decryptErr != nil { | ||||||
|  | 				writeErrorResponse(w, toAPIErrorCode(err), r.URL) | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 			startOffset, length, _ = rs.GetOffsetLength(decryptedSize) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	/// maximum copy size for multipart objects in a single operation
 | 	/// maximum copy size for multipart objects in a single operation
 | ||||||
| 	if isMaxAllowedPartSize(length) { | 	if isMaxAllowedPartSize(length) { | ||||||
| 		writeErrorResponse(w, ErrEntityTooLarge, r.URL) | 		writeErrorResponse(w, ErrEntityTooLarge, r.URL) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Initialize pipe.
 | 	var reader io.Reader = gr | ||||||
| 	pipeReader, pipeWriter := io.Pipe() |  | ||||||
| 
 |  | ||||||
| 	var writer io.WriteCloser = pipeWriter |  | ||||||
| 	var reader io.Reader = pipeReader |  | ||||||
| 	var getLength = length | 	var getLength = length | ||||||
| 	srcInfo.Reader, err = hash.NewReader(reader, length, "", "") | 	srcInfo.Reader, err = hash.NewReader(reader, length, "", "") | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		pipeWriter.CloseWithError(err) |  | ||||||
| 		writeErrorResponse(w, toAPIErrorCode(err), r.URL) | 		writeErrorResponse(w, toAPIErrorCode(err), r.URL) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  | @ -1320,23 +1366,9 @@ func (api objectAPIHandlers) CopyObjectPartHandler(w http.ResponseWriter, r *htt | ||||||
| 		var li ListPartsInfo | 		var li ListPartsInfo | ||||||
| 		li, err = objectAPI.ListObjectParts(ctx, dstBucket, dstObject, uploadID, 0, 1) | 		li, err = objectAPI.ListObjectParts(ctx, dstBucket, dstObject, uploadID, 0, 1) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			pipeWriter.CloseWithError(err) |  | ||||||
| 			writeErrorResponse(w, toAPIErrorCode(err), r.URL) | 			writeErrorResponse(w, toAPIErrorCode(err), r.URL) | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
| 		sseCopyC := crypto.SSECopy.IsRequested(r.Header) |  | ||||||
| 		sseCopyS3 := crypto.S3.IsEncrypted(srcInfo.UserDefined) |  | ||||||
| 		if sseCopyC || sseCopyS3 { |  | ||||||
| 			// Response writer should be limited early on for decryption upto required length,
 |  | ||||||
| 			// additionally also skipping mod(offset)64KiB boundaries.
 |  | ||||||
| 			writer = ioutil.LimitedWriter(writer, startOffset%(64*1024), length) |  | ||||||
| 			writer, startOffset, getLength, err = DecryptBlocksRequest(writer, r, srcBucket, srcObject, startOffset, length, srcInfo, true) |  | ||||||
| 			if err != nil { |  | ||||||
| 				pipeWriter.CloseWithError(err) |  | ||||||
| 				writeErrorResponse(w, toAPIErrorCode(err), r.URL) |  | ||||||
| 				return |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		if crypto.IsEncrypted(li.UserDefined) { | 		if crypto.IsEncrypted(li.UserDefined) { | ||||||
| 			if !hasServerSideEncryptionHeader(r.Header) { | 			if !hasServerSideEncryptionHeader(r.Header) { | ||||||
| 				writeErrorResponse(w, ErrSSEMultipartEncrypted, r.URL) | 				writeErrorResponse(w, ErrSSEMultipartEncrypted, r.URL) | ||||||
|  | @ -1346,7 +1378,6 @@ func (api objectAPIHandlers) CopyObjectPartHandler(w http.ResponseWriter, r *htt | ||||||
| 			if crypto.SSEC.IsRequested(r.Header) { | 			if crypto.SSEC.IsRequested(r.Header) { | ||||||
| 				key, err = ParseSSECustomerRequest(r) | 				key, err = ParseSSECustomerRequest(r) | ||||||
| 				if err != nil { | 				if err != nil { | ||||||
| 					pipeWriter.CloseWithError(err) |  | ||||||
| 					writeErrorResponse(w, toAPIErrorCode(err), r.URL) | 					writeErrorResponse(w, toAPIErrorCode(err), r.URL) | ||||||
| 					return | 					return | ||||||
| 				} | 				} | ||||||
|  | @ -1354,7 +1385,6 @@ func (api objectAPIHandlers) CopyObjectPartHandler(w http.ResponseWriter, r *htt | ||||||
| 			var objectEncryptionKey []byte | 			var objectEncryptionKey []byte | ||||||
| 			objectEncryptionKey, err = decryptObjectInfo(key, dstBucket, dstObject, li.UserDefined) | 			objectEncryptionKey, err = decryptObjectInfo(key, dstBucket, dstObject, li.UserDefined) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				pipeWriter.CloseWithError(err) |  | ||||||
| 				writeErrorResponse(w, toAPIErrorCode(err), r.URL) | 				writeErrorResponse(w, toAPIErrorCode(err), r.URL) | ||||||
| 				return | 				return | ||||||
| 			} | 			} | ||||||
|  | @ -1367,7 +1397,6 @@ func (api objectAPIHandlers) CopyObjectPartHandler(w http.ResponseWriter, r *htt | ||||||
| 			partEncryptionKey := mac.Sum(nil) | 			partEncryptionKey := mac.Sum(nil) | ||||||
| 			reader, err = sio.EncryptReader(reader, sio.Config{Key: partEncryptionKey}) | 			reader, err = sio.EncryptReader(reader, sio.Config{Key: partEncryptionKey}) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				pipeWriter.CloseWithError(err) |  | ||||||
| 				writeErrorResponse(w, toAPIErrorCode(err), r.URL) | 				writeErrorResponse(w, toAPIErrorCode(err), r.URL) | ||||||
| 				return | 				return | ||||||
| 			} | 			} | ||||||
|  | @ -1376,13 +1405,11 @@ func (api objectAPIHandlers) CopyObjectPartHandler(w http.ResponseWriter, r *htt | ||||||
| 			size := info.EncryptedSize() | 			size := info.EncryptedSize() | ||||||
| 			srcInfo.Reader, err = hash.NewReader(reader, size, "", "") | 			srcInfo.Reader, err = hash.NewReader(reader, size, "", "") | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				pipeWriter.CloseWithError(err) |  | ||||||
| 				writeErrorResponse(w, toAPIErrorCode(err), r.URL) | 				writeErrorResponse(w, toAPIErrorCode(err), r.URL) | ||||||
| 				return | 				return | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	srcInfo.Writer = writer |  | ||||||
| 	// Copy source object to destination, if source and destination
 | 	// Copy source object to destination, if source and destination
 | ||||||
| 	// object is same then only metadata is updated.
 | 	// object is same then only metadata is updated.
 | ||||||
| 	partInfo, err := objectAPI.CopyObjectPart(ctx, srcBucket, srcObject, dstBucket, | 	partInfo, err := objectAPI.CopyObjectPart(ctx, srcBucket, srcObject, dstBucket, | ||||||
|  | @ -1392,9 +1419,6 @@ func (api objectAPIHandlers) CopyObjectPartHandler(w http.ResponseWriter, r *htt | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Close the pipe after successful operation.
 |  | ||||||
| 	pipeReader.Close() |  | ||||||
| 
 |  | ||||||
| 	response := generateCopyObjectPartResponse(partInfo.ETag, partInfo.LastModified) | 	response := generateCopyObjectPartResponse(partInfo.ETag, partInfo.LastModified) | ||||||
| 	encodedSuccessResponse := encodeResponse(response) | 	encodedSuccessResponse := encodeResponse(response) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -580,8 +580,8 @@ func (s *xlSets) ListBuckets(ctx context.Context) (buckets []BucketInfo, err err | ||||||
| // --- Object Operations ---
 | // --- Object Operations ---
 | ||||||
| 
 | 
 | ||||||
| // GetObjectNInfo - returns object info and locked object ReadCloser
 | // GetObjectNInfo - returns object info and locked object ReadCloser
 | ||||||
| func (s *xlSets) GetObjectNInfo(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header) (gr *GetObjectReader, err error) { | func (s *xlSets) GetObjectNInfo(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, lockType LockType) (gr *GetObjectReader, err error) { | ||||||
| 	return s.getHashedSet(object).GetObjectNInfo(ctx, bucket, object, rs, h) | 	return s.getHashedSet(object).GetObjectNInfo(ctx, bucket, object, rs, h, lockType) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // GetObject - reads an object from the hashedSet based on the object name.
 | // GetObject - reads an object from the hashedSet based on the object name.
 | ||||||
|  | @ -615,42 +615,14 @@ func (s *xlSets) CopyObject(ctx context.Context, srcBucket, srcObject, destBucke | ||||||
| 		return srcSet.CopyObject(ctx, srcBucket, srcObject, destBucket, destObject, srcInfo, srcOpts, dstOpts) | 		return srcSet.CopyObject(ctx, srcBucket, srcObject, destBucket, destObject, srcInfo, srcOpts, dstOpts) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Hold write lock on destination since in both cases
 |  | ||||||
| 	// - if source and destination are same
 |  | ||||||
| 	// - if source and destination are different
 |  | ||||||
| 	// it is the sole mutating state.
 |  | ||||||
| 	objectDWLock := destSet.nsMutex.NewNSLock(destBucket, destObject) |  | ||||||
| 	if err := objectDWLock.GetLock(globalObjectTimeout); err != nil { |  | ||||||
| 		return objInfo, err |  | ||||||
| 	} |  | ||||||
| 	defer objectDWLock.Unlock() |  | ||||||
| 	// if source and destination are different, we have to hold
 |  | ||||||
| 	// additional read lock as well to protect against writes on
 |  | ||||||
| 	// source.
 |  | ||||||
| 	if !cpSrcDstSame { | 	if !cpSrcDstSame { | ||||||
| 		// Hold read locks on source object only if we are
 | 		objectDWLock := destSet.nsMutex.NewNSLock(destBucket, destObject) | ||||||
| 		// going to read data from source object.
 | 		if err := objectDWLock.GetLock(globalObjectTimeout); err != nil { | ||||||
| 		objectSRLock := srcSet.nsMutex.NewNSLock(srcBucket, srcObject) |  | ||||||
| 		if err := objectSRLock.GetRLock(globalObjectTimeout); err != nil { |  | ||||||
| 			return objInfo, err | 			return objInfo, err | ||||||
| 		} | 		} | ||||||
| 		defer objectSRLock.RUnlock() | 		defer objectDWLock.Unlock() | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	go func() { |  | ||||||
| 		if gerr := srcSet.getObject(ctx, srcBucket, srcObject, 0, srcInfo.Size, srcInfo.Writer, srcInfo.ETag, srcOpts); gerr != nil { |  | ||||||
| 			if gerr = srcInfo.Writer.Close(); gerr != nil { |  | ||||||
| 				logger.LogIf(ctx, gerr) |  | ||||||
| 			} |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 		// Close writer explicitly signaling we wrote all data.
 |  | ||||||
| 		if gerr := srcInfo.Writer.Close(); gerr != nil { |  | ||||||
| 			logger.LogIf(ctx, gerr) |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 	}() |  | ||||||
| 
 |  | ||||||
| 	return destSet.putObject(ctx, destBucket, destObject, srcInfo.Reader, srcInfo.UserDefined, dstOpts) | 	return destSet.putObject(ctx, destBucket, destObject, srcInfo.Reader, srcInfo.UserDefined, dstOpts) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -846,23 +818,8 @@ func (s *xlSets) NewMultipartUpload(ctx context.Context, bucket, object string, | ||||||
| // Copies a part of an object from source hashedSet to destination hashedSet.
 | // Copies a part of an object from source hashedSet to destination hashedSet.
 | ||||||
| func (s *xlSets) CopyObjectPart(ctx context.Context, srcBucket, srcObject, destBucket, destObject string, uploadID string, partID int, | func (s *xlSets) CopyObjectPart(ctx context.Context, srcBucket, srcObject, destBucket, destObject string, uploadID string, partID int, | ||||||
| 	startOffset int64, length int64, srcInfo ObjectInfo, srcOpts, dstOpts ObjectOptions) (partInfo PartInfo, err error) { | 	startOffset int64, length int64, srcInfo ObjectInfo, srcOpts, dstOpts ObjectOptions) (partInfo PartInfo, err error) { | ||||||
| 
 |  | ||||||
| 	srcSet := s.getHashedSet(srcObject) |  | ||||||
| 	destSet := s.getHashedSet(destObject) | 	destSet := s.getHashedSet(destObject) | ||||||
| 
 | 
 | ||||||
| 	go func() { |  | ||||||
| 		if gerr := srcSet.GetObject(ctx, srcBucket, srcObject, startOffset, length, srcInfo.Writer, srcInfo.ETag, srcOpts); gerr != nil { |  | ||||||
| 			if gerr = srcInfo.Writer.Close(); gerr != nil { |  | ||||||
| 				logger.LogIf(ctx, gerr) |  | ||||||
| 				return |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		if gerr := srcInfo.Writer.Close(); gerr != nil { |  | ||||||
| 			logger.LogIf(ctx, gerr) |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 	}() |  | ||||||
| 
 |  | ||||||
| 	return destSet.PutObjectPart(ctx, destBucket, destObject, uploadID, partID, srcInfo.Reader, dstOpts) | 	return destSet.PutObjectPart(ctx, destBucket, destObject, uploadID, partID, srcInfo.Reader, dstOpts) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -269,20 +269,6 @@ func (xl xlObjects) CopyObjectPart(ctx context.Context, srcBucket, srcObject, ds | ||||||
| 		return pi, err | 		return pi, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	go func() { |  | ||||||
| 		if gerr := xl.getObject(ctx, srcBucket, srcObject, startOffset, length, srcInfo.Writer, srcInfo.ETag, srcOpts); gerr != nil { |  | ||||||
| 			if gerr = srcInfo.Writer.Close(); gerr != nil { |  | ||||||
| 				logger.LogIf(ctx, gerr) |  | ||||||
| 			} |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 		// Close writer explicitly signaling we wrote all data.
 |  | ||||||
| 		if gerr := srcInfo.Writer.Close(); gerr != nil { |  | ||||||
| 			logger.LogIf(ctx, gerr) |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 	}() |  | ||||||
| 
 |  | ||||||
| 	partInfo, err := xl.PutObjectPart(ctx, dstBucket, dstObject, uploadID, partID, srcInfo.Reader, dstOpts) | 	partInfo, err := xl.PutObjectPart(ctx, dstBucket, dstObject, uploadID, partID, srcInfo.Reader, dstOpts) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return pi, toObjectErr(err, dstBucket, dstObject) | 		return pi, toObjectErr(err, dstBucket, dstObject) | ||||||
|  |  | ||||||
|  | @ -166,13 +166,25 @@ func (xl xlObjects) CopyObject(ctx context.Context, srcBucket, srcObject, dstBuc | ||||||
| 
 | 
 | ||||||
| // GetObjectNInfo - returns object info and an object
 | // GetObjectNInfo - returns object info and an object
 | ||||||
| // Read(Closer). When err != nil, the returned reader is always nil.
 | // Read(Closer). When err != nil, the returned reader is always nil.
 | ||||||
| func (xl xlObjects) GetObjectNInfo(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header) (gr *GetObjectReader, err error) { | func (xl xlObjects) GetObjectNInfo(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, lockType LockType) (gr *GetObjectReader, err error) { | ||||||
|  | 	var nsUnlocker = func() {} | ||||||
|  | 
 | ||||||
| 	// Acquire lock
 | 	// Acquire lock
 | ||||||
| 	lock := xl.nsMutex.NewNSLock(bucket, object) | 	if lockType != noLock { | ||||||
| 	if err = lock.GetRLock(globalObjectTimeout); err != nil { | 		lock := xl.nsMutex.NewNSLock(bucket, object) | ||||||
| 		return nil, err | 		switch lockType { | ||||||
|  | 		case writeLock: | ||||||
|  | 			if err = lock.GetLock(globalObjectTimeout); err != nil { | ||||||
|  | 				return nil, err | ||||||
|  | 			} | ||||||
|  | 			nsUnlocker = lock.Unlock | ||||||
|  | 		case readLock: | ||||||
|  | 			if err = lock.GetRLock(globalObjectTimeout); err != nil { | ||||||
|  | 				return nil, err | ||||||
|  | 			} | ||||||
|  | 			nsUnlocker = lock.RUnlock | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| 	nsUnlocker := lock.RUnlock |  | ||||||
| 
 | 
 | ||||||
| 	if err = checkGetObjArgs(ctx, bucket, object); err != nil { | 	if err = checkGetObjArgs(ctx, bucket, object); err != nil { | ||||||
| 		nsUnlocker() | 		nsUnlocker() | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue