@@ -22,6 +22,7 @@ import (
22
22
"context"
23
23
"encoding/base64"
24
24
"fmt"
25
+ "hash/crc32"
25
26
"io"
26
27
"net/http"
27
28
"net/url"
@@ -38,9 +39,8 @@ import (
38
39
//
39
40
// Following code handles these types of readers.
40
41
//
41
- // - *minio.Object
42
- // - Any reader which has a method 'ReadAt()'
43
- //
42
+ // - *minio.Object
43
+ // - Any reader which has a method 'ReadAt()'
44
44
func (c * Client ) putObjectMultipartStream (ctx context.Context , bucketName , objectName string ,
45
45
reader io.Reader , size int64 , opts PutObjectOptions ,
46
46
) (info UploadInfo , err error ) {
@@ -184,12 +184,7 @@ func (c *Client) putObjectMultipartStreamFromReadAt(ctx context.Context, bucketN
184
184
sectionReader := newHook (io .NewSectionReader (reader , readOffset , partSize ), opts .Progress )
185
185
186
186
// Proceed to upload the part.
187
- objPart , err := c .uploadPart (ctx , bucketName , objectName ,
188
- uploadID , sectionReader , uploadReq .PartNum ,
189
- "" , "" , partSize ,
190
- opts .ServerSideEncryption ,
191
- ! opts .DisableContentSha256 ,
192
- )
187
+ objPart , err := c .uploadPart (ctx , bucketName , objectName , uploadID , sectionReader , uploadReq .PartNum , "" , "" , partSize , opts .ServerSideEncryption , ! opts .DisableContentSha256 , nil )
193
188
if err != nil {
194
189
uploadedPartsCh <- uploadedPartRes {
195
190
Error : err ,
@@ -260,6 +255,13 @@ func (c *Client) putObjectMultipartStreamOptionalChecksum(ctx context.Context, b
260
255
return UploadInfo {}, err
261
256
}
262
257
258
+ if ! opts .SendContentMd5 {
259
+ if opts .UserMetadata == nil {
260
+ opts .UserMetadata = make (map [string ]string , 1 )
261
+ }
262
+ opts .UserMetadata ["X-Amz-Checksum-Algorithm" ] = "CRC32C"
263
+ }
264
+
263
265
// Calculate the optimal parts info for a given size.
264
266
totalPartsCount , partSize , lastPartSize , err := OptimalPartInfo (size , opts .PartSize )
265
267
if err != nil {
@@ -270,6 +272,7 @@ func (c *Client) putObjectMultipartStreamOptionalChecksum(ctx context.Context, b
270
272
if err != nil {
271
273
return UploadInfo {}, err
272
274
}
275
+ delete (opts .UserMetadata , "X-Amz-Checksum-Algorithm" )
273
276
274
277
// Aborts the multipart upload if the function returns
275
278
// any error, since we do not resume we should purge
@@ -281,6 +284,14 @@ func (c *Client) putObjectMultipartStreamOptionalChecksum(ctx context.Context, b
281
284
}
282
285
}()
283
286
287
+ // Create checksums
288
+ // CRC32C is ~50% faster on AMD64 @ 30GB/s
289
+ var crcBytes []byte
290
+ customHeader := make (http.Header )
291
+ crc := crc32 .New (crc32 .MakeTable (crc32 .Castagnoli ))
292
+ md5Hash := c .md5Hasher ()
293
+ defer md5Hash .Close ()
294
+
284
295
// Total data read and written to server. should be equal to 'size' at the end of the call.
285
296
var totalUploadedSize int64
286
297
@@ -292,7 +303,6 @@ func (c *Client) putObjectMultipartStreamOptionalChecksum(ctx context.Context, b
292
303
293
304
// Avoid declaring variables in the for loop
294
305
var md5Base64 string
295
- var hookReader io.Reader
296
306
297
307
// Part number always starts with '1'.
298
308
var partNumber int
@@ -303,37 +313,34 @@ func (c *Client) putObjectMultipartStreamOptionalChecksum(ctx context.Context, b
303
313
partSize = lastPartSize
304
314
}
305
315
306
- if opts .SendContentMd5 {
307
- length , rerr := readFull (reader , buf )
308
- if rerr == io .EOF && partNumber > 1 {
309
- break
310
- }
311
-
312
- if rerr != nil && rerr != io .ErrUnexpectedEOF && err != io .EOF {
313
- return UploadInfo {}, rerr
314
- }
316
+ length , rerr := readFull (reader , buf )
317
+ if rerr == io .EOF && partNumber > 1 {
318
+ break
319
+ }
315
320
316
- // Calculate md5sum.
317
- hash := c .md5Hasher ()
318
- hash .Write (buf [:length ])
319
- md5Base64 = base64 .StdEncoding .EncodeToString (hash .Sum (nil ))
320
- hash .Close ()
321
+ if rerr != nil && rerr != io .ErrUnexpectedEOF && err != io .EOF {
322
+ return UploadInfo {}, rerr
323
+ }
321
324
322
- // Update progress reader appropriately to the latest offset
323
- // as we read from the source.
324
- hookReader = newHook (bytes .NewReader (buf [:length ]), opts .Progress )
325
+ // Calculate md5sum.
326
+ if opts .SendContentMd5 {
327
+ md5Hash .Reset ()
328
+ md5Hash .Write (buf [:length ])
329
+ md5Base64 = base64 .StdEncoding .EncodeToString (md5Hash .Sum (nil ))
325
330
} else {
326
- // Update progress reader appropriately to the latest offset
327
- // as we read from the source.
328
- hookReader = newHook (reader , opts .Progress )
331
+ // Add CRC32C instead.
332
+ crc .Reset ()
333
+ crc .Write (buf [:length ])
334
+ cSum := crc .Sum (nil )
335
+ customHeader .Set ("x-amz-checksum-crc32c" , base64 .StdEncoding .EncodeToString (cSum ))
336
+ crcBytes = append (crcBytes , cSum ... )
329
337
}
330
338
331
- objPart , uerr := c .uploadPart (ctx , bucketName , objectName , uploadID ,
332
- io .LimitReader (hookReader , partSize ),
333
- partNumber , md5Base64 , "" , partSize ,
334
- opts .ServerSideEncryption ,
335
- ! opts .DisableContentSha256 ,
336
- )
339
+ // Update progress reader appropriately to the latest offset
340
+ // as we read from the source.
341
+ hooked := newHook (bytes .NewReader (buf [:length ]), opts .Progress )
342
+
343
+ objPart , uerr := c .uploadPart (ctx , bucketName , objectName , uploadID , hooked , partNumber , md5Base64 , "" , partSize , opts .ServerSideEncryption , ! opts .DisableContentSha256 , customHeader )
337
344
if uerr != nil {
338
345
return UploadInfo {}, uerr
339
346
}
@@ -363,15 +370,26 @@ func (c *Client) putObjectMultipartStreamOptionalChecksum(ctx context.Context, b
363
370
return UploadInfo {}, errInvalidArgument (fmt .Sprintf ("Missing part number %d" , i ))
364
371
}
365
372
complMultipartUpload .Parts = append (complMultipartUpload .Parts , CompletePart {
366
- ETag : part .ETag ,
367
- PartNumber : part .PartNumber ,
373
+ ETag : part .ETag ,
374
+ PartNumber : part .PartNumber ,
375
+ ChecksumCRC32 : part .ChecksumCRC32 ,
376
+ ChecksumCRC32C : part .ChecksumCRC32C ,
377
+ ChecksumSHA1 : part .ChecksumSHA1 ,
378
+ ChecksumSHA256 : part .ChecksumSHA256 ,
368
379
})
369
380
}
370
381
371
382
// Sort all completed parts.
372
383
sort .Sort (completedParts (complMultipartUpload .Parts ))
373
384
374
- uploadInfo , err := c .completeMultipartUpload (ctx , bucketName , objectName , uploadID , complMultipartUpload , PutObjectOptions {})
385
+ opts = PutObjectOptions {}
386
+ if len (crcBytes ) > 0 {
387
+ // Add hash of hashes.
388
+ crc .Reset ()
389
+ crc .Write (crcBytes )
390
+ opts .UserMetadata = map [string ]string {"X-Amz-Checksum-Crc32c" : base64 .StdEncoding .EncodeToString (crc .Sum (nil ))}
391
+ }
392
+ uploadInfo , err := c .completeMultipartUpload (ctx , bucketName , objectName , uploadID , complMultipartUpload , opts )
375
393
if err != nil {
376
394
return UploadInfo {}, err
377
395
}
@@ -490,14 +508,20 @@ func (c *Client) putObjectDo(ctx context.Context, bucketName, objectName string,
490
508
491
509
// extract lifecycle expiry date and rule ID
492
510
expTime , ruleID := amzExpirationToExpiryDateRuleID (resp .Header .Get (amzExpiration ))
493
-
511
+ h := resp . Header
494
512
return UploadInfo {
495
513
Bucket : bucketName ,
496
514
Key : objectName ,
497
- ETag : trimEtag (resp . Header .Get ("ETag" )),
498
- VersionID : resp . Header .Get (amzVersionID ),
515
+ ETag : trimEtag (h .Get ("ETag" )),
516
+ VersionID : h .Get (amzVersionID ),
499
517
Size : size ,
500
518
Expiration : expTime ,
501
519
ExpirationRuleID : ruleID ,
520
+
521
+ // Checksum values
522
+ ChecksumCRC32 : h .Get ("x-amz-checksum-crc32" ),
523
+ ChecksumCRC32C : h .Get ("x-amz-checksum-crc32c" ),
524
+ ChecksumSHA1 : h .Get ("x-amz-checksum-sha1" ),
525
+ ChecksumSHA256 : h .Get ("x-amz-checksum-sha256" ),
502
526
}, nil
503
527
}
0 commit comments