2023-08-17 12:37:55 -04:00
// Copyright (c) 2015-2023 MinIO, Inc.
2021-04-18 15:41:13 -04:00
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
2016-05-20 23:48:47 -04:00
2016-08-18 19:23:42 -04:00
package cmd
2016-05-20 23:48:47 -04:00
import (
2018-03-14 15:01:47 -04:00
"context"
2022-10-25 13:52:29 -04:00
"encoding/base64"
2022-07-14 19:47:09 -04:00
"errors"
2016-05-20 23:48:47 -04:00
"fmt"
2019-01-17 07:58:18 -05:00
"io"
2021-02-26 12:52:27 -05:00
"os"
2016-05-20 23:48:47 -04:00
"path"
2018-03-15 16:55:23 -04:00
"sort"
2018-09-27 23:36:17 -04:00
"strconv"
2016-05-24 16:35:43 -04:00
"strings"
2021-02-11 13:22:03 -05:00
"sync"
2020-08-22 00:39:54 -04:00
"time"
2016-05-24 16:35:43 -04:00
2022-01-14 13:01:25 -05:00
"github.com/klauspost/readahead"
2020-07-14 12:38:05 -04:00
"github.com/minio/minio-go/v7/pkg/set"
2024-02-28 20:44:30 -05:00
"github.com/minio/minio/internal/config/storageclass"
2022-09-24 00:17:08 -04:00
"github.com/minio/minio/internal/crypto"
2022-08-29 19:57:16 -04:00
"github.com/minio/minio/internal/hash"
2021-06-01 17:59:40 -04:00
xhttp "github.com/minio/minio/internal/http"
2023-08-14 15:28:13 -04:00
xioutil "github.com/minio/minio/internal/ioutil"
2021-06-01 17:59:40 -04:00
"github.com/minio/minio/internal/logger"
2024-05-24 19:05:23 -04:00
"github.com/minio/pkg/v3/mimedb"
"github.com/minio/pkg/v3/sync/errgroup"
2024-09-11 14:35:37 -04:00
"github.com/minio/sio"
2016-05-20 23:48:47 -04:00
)
2020-06-12 23:04:01 -04:00
func ( er erasureObjects ) getUploadIDDir ( bucket , object , uploadID string ) string {
2022-10-25 13:52:29 -04:00
uploadUUID := uploadID
2022-11-09 19:41:16 -05:00
uploadBytes , err := base64 . RawURLEncoding . DecodeString ( uploadID )
2022-10-25 13:52:29 -04:00
if err == nil {
slc := strings . SplitN ( string ( uploadBytes ) , "." , 2 )
if len ( slc ) == 2 {
uploadUUID = slc [ 1 ]
}
}
return pathJoin ( er . getMultipartSHADir ( bucket , object ) , uploadUUID )
2017-01-26 15:51:12 -05:00
}
2020-06-12 23:04:01 -04:00
func ( er erasureObjects ) getMultipartSHADir ( bucket , object string ) string {
2018-03-15 16:55:23 -04:00
return getSHA256Hash ( [ ] byte ( pathJoin ( bucket , object ) ) )
2017-01-26 15:51:12 -05:00
}
2019-04-23 17:54:28 -04:00
// checkUploadIDExists - verify if a given uploadID exists and is valid.
2022-09-15 15:43:49 -04:00
func ( er erasureObjects ) checkUploadIDExists ( ctx context . Context , bucket , object , uploadID string , write bool ) ( fi FileInfo , metArr [ ] FileInfo , err error ) {
2021-03-26 14:17:23 -04:00
defer func ( ) {
2024-01-30 15:43:25 -05:00
if errors . Is ( err , errFileNotFound ) {
2021-03-26 14:17:23 -04:00
err = errUploadIDNotFound
}
} ( )
2022-09-15 15:43:49 -04:00
uploadIDPath := er . getUploadIDDir ( bucket , object , uploadID )
storageDisks := er . getDisks ( )
2020-10-26 13:30:46 -04:00
// Read metadata associated with the object from all disks.
2024-01-30 15:43:25 -05:00
partsMetadata , errs := readAllFileInfo ( ctx , storageDisks , bucket , minioMetaMultipartBucket ,
2023-11-21 00:33:47 -05:00
uploadIDPath , "" , false , false )
2020-10-26 13:30:46 -04:00
2022-09-15 15:43:49 -04:00
readQuorum , writeQuorum , err := objectQuorumFromMeta ( ctx , partsMetadata , errs , er . defaultParityCount )
2020-10-26 13:30:46 -04:00
if err != nil {
2022-09-15 15:43:49 -04:00
return fi , nil , err
2020-10-26 13:30:46 -04:00
}
2024-08-12 04:38:15 -04:00
if readQuorum < 0 {
return fi , nil , errErasureReadQuorum
}
if writeQuorum < 0 {
return fi , nil , errErasureWriteQuorum
}
2023-04-25 13:13:57 -04:00
quorum := readQuorum
if write {
quorum = writeQuorum
}
2023-06-17 22:18:20 -04:00
2022-09-15 15:43:49 -04:00
// List all online disks.
2023-06-17 22:18:20 -04:00
_ , modTime , etag := listOnlineDisks ( storageDisks , partsMetadata , errs , quorum )
2022-09-15 15:43:49 -04:00
if write {
2024-08-12 04:38:15 -04:00
err = reduceWriteQuorumErrs ( ctx , errs , objectOpIgnoredErrs , writeQuorum )
2022-09-15 15:43:49 -04:00
} else {
2024-08-12 04:38:15 -04:00
err = reduceReadQuorumErrs ( ctx , errs , objectOpIgnoredErrs , readQuorum )
2024-01-30 15:43:25 -05:00
}
2024-08-12 04:38:15 -04:00
if err != nil {
return fi , nil , err
2020-10-26 13:30:46 -04:00
}
2022-09-15 15:43:49 -04:00
// Pick one from the first valid metadata.
2023-06-17 22:18:20 -04:00
fi , err = pickValidFileInfo ( ctx , partsMetadata , modTime , etag , quorum )
2022-09-15 15:43:49 -04:00
return fi , partsMetadata , err
2017-01-26 15:51:12 -05:00
}
2024-08-21 16:14:24 -04:00
// cleanupMultipartPath removes all extraneous files and parts from the multipart folder, this is used per CompleteMultipart.
2024-07-29 21:56:40 -04:00
// do not use this function outside of completeMultipartUpload()
func ( er erasureObjects ) cleanupMultipartPath ( ctx context . Context , paths ... string ) {
2020-06-12 23:04:01 -04:00
storageDisks := er . getDisks ( )
2019-10-14 12:44:51 -04:00
g := errgroup . WithNErrs ( len ( storageDisks ) )
for index , disk := range storageDisks {
2017-01-26 15:51:12 -05:00
if disk == nil {
continue
}
2019-10-14 12:44:51 -04:00
index := index
g . Go ( func ( ) error {
2024-07-29 21:56:40 -04:00
_ = storageDisks [ index ] . DeleteBulk ( ctx , minioMetaMultipartBucket , paths ... )
2019-10-14 12:44:51 -04:00
return nil
} , index )
2017-01-26 15:51:12 -05:00
}
2019-10-14 12:44:51 -04:00
g . Wait ( )
2017-01-26 15:51:12 -05:00
}
2020-08-22 00:39:54 -04:00
// Clean-up the old multipart uploads. Should be run in a Go routine.
2024-07-30 15:01:06 -04:00
func ( er erasureObjects ) cleanupStaleUploads ( ctx context . Context ) {
2020-12-10 10:28:37 -05:00
// run multiple cleanup's local to this server.
2021-02-11 13:22:03 -05:00
var wg sync . WaitGroup
2023-10-25 02:33:25 -04:00
for _ , disk := range er . getLocalDisks ( ) {
2020-12-10 10:28:37 -05:00
if disk != nil {
2021-02-11 13:22:03 -05:00
wg . Add ( 1 )
go func ( disk StorageAPI ) {
defer wg . Done ( )
2024-07-30 15:01:06 -04:00
er . cleanupStaleUploadsOnDisk ( ctx , disk )
2021-02-11 13:22:03 -05:00
} ( disk )
2020-08-22 00:39:54 -04:00
}
}
2021-02-11 13:22:03 -05:00
wg . Wait ( )
}
func ( er erasureObjects ) deleteAll ( ctx context . Context , bucket , prefix string ) {
var wg sync . WaitGroup
for _ , disk := range er . getDisks ( ) {
if disk == nil {
continue
}
wg . Add ( 1 )
go func ( disk StorageAPI ) {
defer wg . Done ( )
2022-07-11 12:15:54 -04:00
disk . Delete ( ctx , bucket , prefix , DeleteOptions {
Recursive : true ,
2023-11-29 01:35:16 -05:00
Immediate : false ,
2022-07-11 12:15:54 -04:00
} )
2021-02-11 13:22:03 -05:00
} ( disk )
}
wg . Wait ( )
2020-08-22 00:39:54 -04:00
}
// Remove the old multipart uploads on the given disk.
2024-07-30 15:01:06 -04:00
func ( er erasureObjects ) cleanupStaleUploadsOnDisk ( ctx context . Context , disk StorageAPI ) {
2024-04-15 06:02:39 -04:00
drivePath := disk . Endpoint ( ) . Path
2021-02-26 12:52:27 -05:00
2024-04-15 06:02:39 -04:00
readDirFn ( pathJoin ( drivePath , minioMetaMultipartBucket ) , func ( shaDir string , typ os . FileMode ) error {
readDirFn ( pathJoin ( drivePath , minioMetaMultipartBucket , shaDir ) , func ( uploadIDDir string , typ os . FileMode ) error {
2020-08-22 00:39:54 -04:00
uploadIDPath := pathJoin ( shaDir , uploadIDDir )
2024-05-17 12:40:09 -04:00
var modTime time . Time
// Upload IDs are of the form base64_url(<UUID>x<UnixNano>), we can extract the time from the UUID.
if b64 , err := base64 . RawURLEncoding . DecodeString ( uploadIDDir ) ; err == nil {
if split := strings . Split ( string ( b64 ) , "x" ) ; len ( split ) == 2 {
t , err := strconv . ParseInt ( split [ 1 ] , 10 , 64 )
if err == nil {
modTime = time . Unix ( 0 , t )
}
}
}
// Fallback for older uploads without time in the ID.
if modTime . IsZero ( ) {
wait := deleteMultipartCleanupSleeper . Timer ( ctx )
fi , err := disk . ReadVersion ( ctx , "" , minioMetaMultipartBucket , uploadIDPath , "" , ReadOptions { } )
if err != nil {
return nil
}
modTime = fi . ModTime
wait ( )
}
2024-07-30 15:01:06 -04:00
if time . Since ( modTime ) < globalAPIConfig . getStaleUploadsExpiry ( ) {
2021-02-26 12:52:27 -05:00
return nil
2020-08-22 00:39:54 -04:00
}
2023-11-27 12:15:06 -05:00
w := xioutil . NewDeadlineWorker ( globalDriveConfig . GetMaxTimeout ( ) )
2023-08-14 15:28:13 -04:00
return w . Run ( func ( ) error {
2024-04-15 06:02:39 -04:00
wait := deleteMultipartCleanupSleeper . Timer ( ctx )
2024-05-17 12:40:09 -04:00
pathUUID := mustGetUUID ( )
targetPath := pathJoin ( drivePath , minioMetaTmpDeletedBucket , pathUUID )
renameAll ( pathJoin ( drivePath , minioMetaMultipartBucket , uploadIDPath ) , targetPath , pathJoin ( drivePath , minioMetaBucket ) )
2023-08-14 15:28:13 -04:00
wait ( )
return nil
} )
2021-02-26 12:52:27 -05:00
} )
2024-05-17 12:40:09 -04:00
// Get the modtime of the shaDir.
2023-05-30 12:56:50 -04:00
vi , err := disk . StatVol ( ctx , pathJoin ( minioMetaMultipartBucket , shaDir ) )
if err != nil {
return nil
}
2024-05-17 12:40:09 -04:00
// Modtime is returned in the Created field. See (*xlStorage).StatVol
2024-07-30 15:01:06 -04:00
if time . Since ( vi . Created ) < globalAPIConfig . getStaleUploadsExpiry ( ) {
2024-05-17 12:40:09 -04:00
return nil
}
2023-11-27 12:15:06 -05:00
w := xioutil . NewDeadlineWorker ( globalDriveConfig . GetMaxTimeout ( ) )
2023-08-14 15:28:13 -04:00
return w . Run ( func ( ) error {
2024-04-15 06:02:39 -04:00
wait := deleteMultipartCleanupSleeper . Timer ( ctx )
2024-05-17 12:40:09 -04:00
pathUUID := mustGetUUID ( )
targetPath := pathJoin ( drivePath , minioMetaTmpDeletedBucket , pathUUID )
2024-04-15 06:02:39 -04:00
2024-05-17 12:40:09 -04:00
// We are not deleting shaDir recursively here, if shaDir is empty
// and its older then we can happily delete it.
Rename ( pathJoin ( drivePath , minioMetaMultipartBucket , shaDir ) , targetPath )
2023-08-14 15:28:13 -04:00
wait ( )
return nil
} )
2021-02-26 12:52:27 -05:00
} )
2024-04-15 06:02:39 -04:00
readDirFn ( pathJoin ( drivePath , minioMetaTmpBucket ) , func ( tmpDir string , typ os . FileMode ) error {
if strings . HasPrefix ( tmpDir , ".trash" ) {
// do not remove .trash/ here, it has its own routines
2021-02-26 12:52:27 -05:00
return nil
2020-08-22 00:39:54 -04:00
}
2021-02-11 13:22:03 -05:00
vi , err := disk . StatVol ( ctx , pathJoin ( minioMetaTmpBucket , tmpDir ) )
2020-09-08 18:55:40 -04:00
if err != nil {
2021-02-26 12:52:27 -05:00
return nil
2020-09-08 18:55:40 -04:00
}
2023-11-27 12:15:06 -05:00
w := xioutil . NewDeadlineWorker ( globalDriveConfig . GetMaxTimeout ( ) )
2023-08-14 15:28:13 -04:00
return w . Run ( func ( ) error {
2024-04-15 06:02:39 -04:00
wait := deleteMultipartCleanupSleeper . Timer ( ctx )
2024-07-30 15:01:06 -04:00
if time . Since ( vi . Created ) > globalAPIConfig . getStaleUploadsExpiry ( ) {
2024-04-15 06:02:39 -04:00
pathUUID := mustGetUUID ( )
targetPath := pathJoin ( drivePath , minioMetaTmpDeletedBucket , pathUUID )
renameAll ( pathJoin ( drivePath , minioMetaTmpBucket , tmpDir ) , targetPath , pathJoin ( drivePath , minioMetaBucket ) )
2023-08-14 15:28:13 -04:00
}
wait ( )
return nil
} )
2021-02-26 12:52:27 -05:00
} )
2020-08-22 00:39:54 -04:00
}
2017-11-30 18:58:46 -05:00
// ListMultipartUploads - lists all the pending multipart
// uploads for a particular object in a bucket.
//
// Implements minimal S3 compatible ListMultipartUploads API. We do
// not support prefix based listing, this is a deliberate attempt
// towards simplification of multipart APIs.
// The resulting ListMultipartsInfo structure is unmarshalled directly as XML.
2020-07-03 22:27:13 -04:00
func ( er erasureObjects ) ListMultipartUploads ( ctx context . Context , bucket , object , keyMarker , uploadIDMarker , delimiter string , maxUploads int ) ( result ListMultipartsInfo , err error ) {
2024-08-13 18:22:04 -04:00
auditObjectErasureSet ( ctx , "ListMultipartUploads" , object , & er )
2022-05-04 03:45:27 -04:00
2017-11-30 18:58:46 -05:00
result . MaxUploads = maxUploads
result . KeyMarker = keyMarker
result . Prefix = object
result . Delimiter = delimiter
2020-07-03 22:27:13 -04:00
var uploadIDs [ ] string
2020-09-28 22:39:32 -04:00
var disk StorageAPI
2023-10-25 02:33:25 -04:00
disks := er . getOnlineLocalDisks ( )
2023-09-15 11:34:03 -04:00
if len ( disks ) == 0 {
2024-05-17 12:40:09 -04:00
// If no local, get non-healing disks.
var ok bool
if disks , ok = er . getOnlineDisksWithHealing ( false ) ; ! ok {
disks = er . getOnlineDisks ( )
}
2023-09-15 11:34:03 -04:00
}
2024-05-17 12:40:09 -04:00
2023-09-15 11:34:03 -04:00
for _ , disk = range disks {
2023-10-10 16:47:35 -04:00
if disk == nil {
continue
}
if ! disk . IsOnline ( ) {
continue
}
2024-01-30 15:43:25 -05:00
uploadIDs , err = disk . ListDir ( ctx , bucket , minioMetaMultipartBucket , er . getMultipartSHADir ( bucket , object ) , - 1 )
2016-06-01 19:43:31 -04:00
if err != nil {
2022-07-14 19:47:09 -04:00
if errors . Is ( err , errDiskNotFound ) {
2020-07-03 22:27:13 -04:00
continue
}
2024-01-30 15:43:25 -05:00
if errors . Is ( err , errFileNotFound ) {
2018-03-15 16:55:23 -04:00
return result , nil
}
2020-07-03 22:27:13 -04:00
return result , toObjectErr ( err , bucket , object )
2016-06-01 19:43:31 -04:00
}
2020-07-03 22:27:13 -04:00
break
}
for i := range uploadIDs {
uploadIDs [ i ] = strings . TrimSuffix ( uploadIDs [ i ] , SlashSeparator )
}
// S3 spec says uploadIDs should be sorted based on initiated time, we need
// to read the metadata entry.
var uploads [ ] MultipartInfo
2024-02-08 18:22:16 -05:00
populatedUploadIDs := set . NewStringSet ( )
2020-07-03 22:27:13 -04:00
2020-09-28 22:39:32 -04:00
for _ , uploadID := range uploadIDs {
2024-02-08 18:22:16 -05:00
if populatedUploadIDs . Contains ( uploadID ) {
2020-07-03 22:27:13 -04:00
continue
2016-06-01 19:43:31 -04:00
}
2023-10-24 15:06:06 -04:00
// If present, use time stored in ID.
startTime := time . Now ( )
if split := strings . Split ( uploadID , "x" ) ; len ( split ) == 2 {
t , err := strconv . ParseInt ( split [ 1 ] , 10 , 64 )
if err == nil {
startTime = time . Unix ( 0 , t )
2022-07-16 16:25:58 -04:00
}
2017-11-30 18:58:46 -05:00
}
2020-09-28 22:39:32 -04:00
uploads = append ( uploads , MultipartInfo {
2023-10-24 15:06:06 -04:00
Bucket : bucket ,
2020-09-28 22:39:32 -04:00
Object : object ,
2023-10-18 11:06:57 -04:00
UploadID : base64 . RawURLEncoding . EncodeToString ( [ ] byte ( fmt . Sprintf ( "%s.%s" , globalDeploymentID ( ) , uploadID ) ) ) ,
2023-10-24 15:06:06 -04:00
Initiated : startTime ,
2020-09-28 22:39:32 -04:00
} )
2024-02-08 18:22:16 -05:00
populatedUploadIDs . Add ( uploadID )
2016-05-20 23:48:47 -04:00
}
2016-12-02 02:15:17 -05:00
2020-07-03 22:27:13 -04:00
sort . Slice ( uploads , func ( i int , j int ) bool {
return uploads [ i ] . Initiated . Before ( uploads [ j ] . Initiated )
} )
uploadIndex := 0
if uploadIDMarker != "" {
for uploadIndex < len ( uploads ) {
if uploads [ uploadIndex ] . UploadID != uploadIDMarker {
uploadIndex ++
continue
}
if uploads [ uploadIndex ] . UploadID == uploadIDMarker {
uploadIndex ++
break
}
uploadIndex ++
}
}
for uploadIndex < len ( uploads ) {
result . Uploads = append ( result . Uploads , uploads [ uploadIndex ] )
result . NextUploadIDMarker = uploads [ uploadIndex ] . UploadID
uploadIndex ++
if len ( result . Uploads ) == maxUploads {
break
}
}
result . IsTruncated = uploadIndex < len ( uploads )
if ! result . IsTruncated {
result . NextKeyMarker = ""
result . NextUploadIDMarker = ""
}
2017-11-30 18:58:46 -05:00
return result , nil
2016-06-01 19:43:31 -04:00
}
2016-05-20 23:48:47 -04:00
2016-06-01 19:43:31 -04:00
// newMultipartUpload - wrapper for initializing a new multipart
2016-10-24 20:37:18 -04:00
// request; returns a unique upload id.
2016-06-01 19:43:31 -04:00
//
// Internally this function creates 'uploads.json' associated for the
2016-10-24 20:37:18 -04:00
// incoming object at
// '.minio.sys/multipart/bucket/object/uploads.json' on all the
// disks. `uploads.json` carries metadata regarding on-going multipart
// operation(s) on the object.
2022-08-29 19:57:16 -04:00
func ( er erasureObjects ) newMultipartUpload ( ctx context . Context , bucket string , object string , opts ObjectOptions ) ( * NewMultipartUploadResult , error ) {
2022-09-14 21:44:04 -04:00
if opts . CheckPrecondFn != nil {
2024-05-10 20:31:22 -04:00
if ! opts . NoLock {
ns := er . NewNSLock ( bucket , object )
lkctx , err := ns . GetLock ( ctx , globalOperationTimeout )
if err != nil {
return nil , err
}
ctx = lkctx . Context ( )
defer ns . Unlock ( lkctx )
opts . NoLock = true
2022-09-14 21:44:04 -04:00
}
2024-05-10 20:31:22 -04:00
obj , err := er . getObjectInfo ( ctx , bucket , object , opts )
if err == nil && opts . CheckPrecondFn ( obj ) {
2022-09-14 21:44:04 -04:00
return nil , PreConditionFailed { }
}
2024-05-10 20:31:22 -04:00
if err != nil && ! isErrVersionNotFound ( err ) && ! isErrObjectNotFound ( err ) && ! isErrReadQuorum ( err ) {
return nil , err
}
2022-09-14 21:44:04 -04:00
}
2022-05-05 07:14:41 -04:00
userDefined := cloneMSS ( opts . UserDefined )
2022-11-26 17:43:32 -05:00
if opts . PreserveETag != "" {
userDefined [ "etag" ] = opts . PreserveETag
}
2020-06-12 23:04:01 -04:00
onlineDisks := er . getDisks ( )
2023-05-23 10:57:57 -04:00
// Get parity and data drive count based on storage class metadata
2022-05-05 07:14:41 -04:00
parityDrives := globalStorageClass . GetParityForSC ( userDefined [ xhttp . AmzStorageClass ] )
2022-06-27 23:22:18 -04:00
if parityDrives < 0 {
2021-04-21 22:06:08 -04:00
parityDrives = er . defaultParityCount
2019-10-07 01:50:24 -04:00
}
2017-12-22 06:28:13 -05:00
2024-03-14 06:38:33 -04:00
if globalStorageClass . AvailabilityOptimized ( ) {
// If we have offline disks upgrade the number of erasure codes for this object.
parityOrig := parityDrives
var offlineDrives int
for _ , disk := range onlineDisks {
if disk == nil || ! disk . IsOnline ( ) {
parityDrives ++
offlineDrives ++
continue
}
2021-05-27 14:38:09 -04:00
}
2023-05-23 10:57:57 -04:00
2024-03-14 06:38:33 -04:00
if offlineDrives >= ( len ( onlineDisks ) + 1 ) / 2 {
// if offline drives are more than 50% of the drives
// we have no quorum, we shouldn't proceed just
// fail at that point.
return nil , toObjectErr ( errErasureWriteQuorum , bucket , object )
}
2023-05-23 10:57:57 -04:00
2024-03-14 06:38:33 -04:00
if parityDrives >= len ( onlineDisks ) / 2 {
parityDrives = len ( onlineDisks ) / 2
}
2023-10-10 16:47:35 -04:00
2024-03-14 06:38:33 -04:00
if parityOrig != parityDrives {
userDefined [ minIOErasureUpgraded ] = strconv . Itoa ( parityOrig ) + "->" + strconv . Itoa ( parityDrives )
}
2021-05-27 14:38:09 -04:00
}
2023-05-23 10:57:57 -04:00
2021-04-21 22:06:08 -04:00
dataDrives := len ( onlineDisks ) - parityDrives
2021-05-27 14:38:09 -04:00
2017-12-22 06:28:13 -05:00
// we now know the number of blocks this object needs for data and parity.
// establish the writeQuorum using this data
2021-04-21 22:06:08 -04:00
writeQuorum := dataDrives
if dataDrives == parityDrives {
2021-01-16 15:08:02 -05:00
writeQuorum ++
2020-06-09 22:19:03 -04:00
}
2017-12-22 06:28:13 -05:00
2021-04-21 22:06:08 -04:00
// Initialize parts metadata
partsMetadata := make ( [ ] FileInfo , len ( onlineDisks ) )
2020-06-12 23:04:01 -04:00
2021-04-21 22:06:08 -04:00
fi := newFileInfo ( pathJoin ( bucket , object ) , dataDrives , parityDrives )
2021-04-24 22:07:27 -04:00
fi . VersionID = opts . VersionID
if opts . Versioned && fi . VersionID == "" {
fi . VersionID = mustGetUUID ( )
2016-05-20 23:48:47 -04:00
}
2020-06-12 23:04:01 -04:00
fi . DataDir = mustGetUUID ( )
2016-05-20 23:48:47 -04:00
2024-09-13 16:26:02 -04:00
if ckSum := userDefined [ ReplicationSsecChecksumHeader ] ; ckSum != "" {
v , err := base64 . StdEncoding . DecodeString ( ckSum )
if err == nil {
fi . Checksum = v
}
2024-06-13 02:56:12 -04:00
delete ( userDefined , ReplicationSsecChecksumHeader )
}
2021-04-21 22:06:08 -04:00
// Initialize erasure metadata.
for index := range partsMetadata {
partsMetadata [ index ] = fi
}
2017-08-14 21:08:42 -04:00
2021-04-21 22:06:08 -04:00
// Guess content-type from the extension if possible.
2022-05-05 07:14:41 -04:00
if userDefined [ "content-type" ] == "" {
userDefined [ "content-type" ] = mimedb . TypeByExtension ( path . Ext ( object ) )
2021-04-21 22:06:08 -04:00
}
2019-04-25 10:33:26 -04:00
2024-02-28 20:44:30 -05:00
// if storageClass is standard no need to save it as part of metadata.
if userDefined [ xhttp . AmzStorageClass ] == storageclass . STANDARD {
delete ( userDefined , xhttp . AmzStorageClass )
}
2022-08-29 19:57:16 -04:00
if opts . WantChecksum != nil && opts . WantChecksum . Type . IsSet ( ) {
userDefined [ hash . MinIOMultipartChecksum ] = opts . WantChecksum . Type . String ( )
}
2021-04-21 22:06:08 -04:00
modTime := opts . MTime
if opts . MTime . IsZero ( ) {
modTime = UTCNow ( )
2019-09-11 13:22:12 -04:00
}
2021-04-21 22:06:08 -04:00
onlineDisks , partsMetadata = shuffleDisksAndPartsMetadata ( onlineDisks , partsMetadata , fi )
2020-10-28 03:09:15 -04:00
2021-04-21 22:06:08 -04:00
// Fill all the necessary metadata.
// Update `xl.meta` content on each disks.
for index := range partsMetadata {
2021-08-10 14:12:22 -04:00
partsMetadata [ index ] . Fresh = true
2021-04-21 22:06:08 -04:00
partsMetadata [ index ] . ModTime = modTime
2022-05-05 07:14:41 -04:00
partsMetadata [ index ] . Metadata = userDefined
2016-05-20 23:48:47 -04:00
}
2024-05-17 12:40:09 -04:00
uploadUUID := fmt . Sprintf ( "%sx%d" , mustGetUUID ( ) , modTime . UnixNano ( ) )
2023-10-18 11:06:57 -04:00
uploadID := base64 . RawURLEncoding . EncodeToString ( [ ] byte ( fmt . Sprintf ( "%s.%s" , globalDeploymentID ( ) , uploadUUID ) ) )
2022-10-25 13:52:29 -04:00
uploadIDPath := er . getUploadIDDir ( bucket , object , uploadUUID )
2021-04-21 22:06:08 -04:00
// Write updated `xl.meta` to all disks.
2024-08-12 04:38:15 -04:00
if _ , err := writeAllMetadata ( ctx , onlineDisks , bucket , minioMetaMultipartBucket , uploadIDPath , partsMetadata , writeQuorum ) ; err != nil {
2024-01-30 15:43:25 -05:00
return nil , toObjectErr ( err , bucket , object )
2016-10-24 20:37:18 -04:00
}
2024-08-12 04:38:15 -04:00
2022-08-29 19:57:16 -04:00
return & NewMultipartUploadResult {
UploadID : uploadID ,
ChecksumAlgo : userDefined [ hash . MinIOMultipartChecksum ] ,
} , nil
2016-05-20 23:48:47 -04:00
}
2016-06-01 19:43:31 -04:00
// NewMultipartUpload - initialize a new multipart upload, returns a
// unique id. The unique id returned here is of UUID form, for each
// subsequent request each UUID is unique.
//
// Implements S3 compatible initiate multipart API.
2022-08-29 19:57:16 -04:00
func ( er erasureObjects ) NewMultipartUpload ( ctx context . Context , bucket , object string , opts ObjectOptions ) ( * NewMultipartUploadResult , error ) {
2024-03-06 06:43:16 -05:00
if ! opts . NoAuditLog {
2024-08-13 18:22:04 -04:00
auditObjectErasureSet ( ctx , "NewMultipartUpload" , object , & er )
2024-03-06 06:43:16 -05:00
}
2022-05-04 03:45:27 -04:00
2020-06-12 23:04:01 -04:00
return er . newMultipartUpload ( ctx , bucket , object , opts )
2016-06-01 19:43:31 -04:00
}
2022-01-13 14:07:41 -05:00
// renamePart - renames multipart part to its relevant location under uploadID.
2024-08-12 04:38:15 -04:00
func ( er erasureObjects ) renamePart ( ctx context . Context , disks [ ] StorageAPI , srcBucket , srcEntry , dstBucket , dstEntry string , optsMeta [ ] byte , writeQuorum int ) ( [ ] StorageAPI , error ) {
2024-09-24 07:26:41 -04:00
paths := [ ] string {
dstEntry ,
dstEntry + ".meta" ,
}
// cleanup existing paths first across all drives.
er . cleanupMultipartPath ( ctx , paths ... )
2022-01-13 14:07:41 -05:00
g := errgroup . WithNErrs ( len ( disks ) )
// Rename file on all underlying storage disks.
for index := range disks {
index := index
g . Go ( func ( ) error {
if disks [ index ] == nil {
return errDiskNotFound
}
2024-08-12 04:38:15 -04:00
return disks [ index ] . RenamePart ( ctx , srcBucket , srcEntry , dstBucket , dstEntry , optsMeta )
2022-01-13 14:07:41 -05:00
} , index )
}
// Wait for all renames to finish.
errs := g . Wait ( )
2022-07-19 11:35:29 -04:00
err := reduceWriteQuorumErrs ( ctx , errs , objectOpIgnoredErrs , writeQuorum )
2024-08-12 04:38:15 -04:00
if err != nil {
er . cleanupMultipartPath ( ctx , paths ... )
2022-07-19 11:35:29 -04:00
}
2024-08-12 04:38:15 -04:00
// We can safely allow RenameFile errors up to len(er.getDisks()) - writeQuorum
// otherwise return failure. Cleanup successful renames.
2022-07-19 11:35:29 -04:00
return evalDisks ( disks , errs ) , err
}
2016-07-05 04:04:50 -04:00
// PutObjectPart - reads incoming stream and internally erasure codes
// them. This call is similar to single put operation but it is part
2016-08-05 01:01:58 -04:00
// of the multipart transaction.
2016-07-05 04:04:50 -04:00
//
// Implements S3 compatible Upload Part API.
2020-09-14 18:57:13 -04:00
func ( er erasureObjects ) PutObjectPart ( ctx context . Context , bucket , object , uploadID string , partID int , r * PutObjReader , opts ObjectOptions ) ( pi PartInfo , err error ) {
2024-03-06 06:43:16 -05:00
if ! opts . NoAuditLog {
2024-08-13 18:22:04 -04:00
auditObjectErasureSet ( ctx , "PutObjectPart" , object , & er )
2024-03-06 06:43:16 -05:00
}
2022-05-04 03:45:27 -04:00
2024-01-30 15:43:25 -05:00
data := r . Reader
// Validate input data size and it can never be less than zero.
if data . Size ( ) < - 1 {
2024-04-04 08:04:40 -04:00
bugLogIf ( ctx , errInvalidArgument , logger . ErrorKind )
2024-01-30 15:43:25 -05:00
return pi , toObjectErr ( errInvalidArgument )
}
2022-11-18 06:09:35 -05:00
uploadIDPath := er . getUploadIDDir ( bucket , object , uploadID )
// Validates if upload ID exists.
2024-09-29 18:40:36 -04:00
fi , _ , err := er . checkUploadIDExists ( ctx , bucket , object , uploadID , true )
2022-11-18 06:09:35 -05:00
if err != nil {
2024-01-30 15:43:25 -05:00
if errors . Is ( err , errVolumeNotFound ) {
return pi , toObjectErr ( err , bucket )
}
2022-11-18 06:09:35 -05:00
return pi , toObjectErr ( err , bucket , object , uploadID )
}
2022-09-15 15:43:49 -04:00
onlineDisks := er . getDisks ( )
writeQuorum := fi . WriteQuorum ( er . defaultWQuorum ( ) )
2016-07-14 17:59:01 -04:00
2022-08-29 19:57:16 -04:00
if cs := fi . Metadata [ hash . MinIOMultipartChecksum ] ; cs != "" {
if r . ContentCRCType ( ) . String ( ) != cs {
return pi , InvalidArgument {
Bucket : bucket ,
Object : fi . Name ,
2023-05-05 22:53:12 -04:00
Err : fmt . Errorf ( "checksum missing, want %q, got %q" , cs , r . ContentCRCType ( ) . String ( ) ) ,
2022-08-29 19:57:16 -04:00
}
}
}
2020-06-12 23:04:01 -04:00
onlineDisks = shuffleDisks ( onlineDisks , fi . Erasure . Distribution )
2016-05-26 22:55:48 -04:00
2016-07-11 20:24:49 -04:00
// Need a unique name for the part being written in minioMetaBucket to
// accommodate concurrent PutObjectPart requests
2016-07-12 18:20:31 -04:00
2016-06-28 00:42:33 -04:00
partSuffix := fmt . Sprintf ( "part.%d" , partID )
2023-10-24 15:06:06 -04:00
// Random UUID and timestamp for temporary part file.
tmpPart := fmt . Sprintf ( "%sx%d" , mustGetUUID ( ) , time . Now ( ) . UnixNano ( ) )
2020-06-12 23:04:01 -04:00
tmpPartPath := pathJoin ( tmpPart , partSuffix )
2016-05-20 23:48:47 -04:00
2016-10-20 01:52:03 -04:00
// Delete the temporary object part. If PutObjectPart succeeds there would be nothing to delete.
2021-03-24 17:19:52 -04:00
defer func ( ) {
2023-08-27 12:57:11 -04:00
if countOnlineDisks ( onlineDisks ) != len ( onlineDisks ) {
2022-11-01 11:00:02 -04:00
er . deleteAll ( context . Background ( ) , minioMetaTmpBucket , tmpPart )
2021-03-24 17:19:52 -04:00
}
} ( )
2018-11-26 16:20:21 -05:00
2024-05-22 19:07:14 -04:00
erasure , err := NewErasure ( ctx , fi . Erasure . DataBlocks , fi . Erasure . ParityBlocks , fi . Erasure . BlockSize )
2017-08-14 21:08:42 -04:00
if err != nil {
return pi , toObjectErr ( err , bucket , object )
}
2018-02-15 20:45:57 -05:00
// Fetch buffer for I/O, returns from the pool if not allocates a new one and returns.
2018-06-13 14:55:12 -04:00
var buffer [ ] byte
switch size := data . Size ( ) ; {
case size == 0 :
2024-01-18 02:03:17 -05:00
buffer = make ( [ ] byte , 1 ) // Allocate at least a byte to reach EOF
2021-03-03 19:28:10 -05:00
case size == - 1 :
if size := data . ActualSize ( ) ; size > 0 && size < fi . Erasure . BlockSize {
2023-05-26 13:57:07 -04:00
// Account for padding and forced compression overhead and encryption.
buffer = make ( [ ] byte , data . ActualSize ( ) + 256 + 32 + 32 , data . ActualSize ( ) * 2 + 512 )
2021-03-03 19:28:10 -05:00
} else {
2024-04-19 12:44:59 -04:00
buffer = globalBytePoolCap . Load ( ) . Get ( )
defer globalBytePoolCap . Load ( ) . Put ( buffer )
2021-03-03 19:28:10 -05:00
}
case size >= fi . Erasure . BlockSize :
2024-04-19 12:44:59 -04:00
buffer = globalBytePoolCap . Load ( ) . Get ( )
defer globalBytePoolCap . Load ( ) . Put ( buffer )
2020-06-12 23:04:01 -04:00
case size < fi . Erasure . BlockSize :
// No need to allocate fully fi.Erasure.BlockSize buffer if the incoming data is smaller.
buffer = make ( [ ] byte , size , 2 * size + int64 ( fi . Erasure . ParityBlocks + fi . Erasure . DataBlocks - 1 ) )
2018-06-13 14:55:12 -04:00
}
2018-02-15 20:45:57 -05:00
2020-06-12 23:04:01 -04:00
if len ( buffer ) > int ( fi . Erasure . BlockSize ) {
buffer = buffer [ : fi . Erasure . BlockSize ]
2018-08-06 18:14:08 -04:00
}
2019-01-17 07:58:18 -05:00
writers := make ( [ ] io . Writer , len ( onlineDisks ) )
2018-08-06 18:14:08 -04:00
for i , disk := range onlineDisks {
if disk == nil {
continue
}
2024-01-30 15:43:25 -05:00
writers [ i ] = newBitrotWriter ( disk , bucket , minioMetaTmpBucket , tmpPartPath , erasure . ShardFileSize ( data . Size ( ) ) , DefaultBitrotAlgorithm , erasure . ShardSize ( ) )
2018-08-06 18:14:08 -04:00
}
2018-11-26 16:20:21 -05:00
2022-01-14 13:01:25 -05:00
toEncode := io . Reader ( data )
if data . Size ( ) > bigFileThreshold {
// Add input readahead.
// We use 2 buffers, so we always have a full buffer of input.
2024-04-19 12:44:59 -04:00
pool := globalBytePoolCap . Load ( )
bufA := pool . Get ( )
bufB := pool . Get ( )
defer pool . Put ( bufA )
defer pool . Put ( bufB )
2022-01-14 13:01:25 -05:00
ra , err := readahead . NewReaderBuffer ( data , [ ] [ ] byte { bufA [ : fi . Erasure . BlockSize ] , bufB [ : fi . Erasure . BlockSize ] } )
if err == nil {
toEncode = ra
defer ra . Close ( )
}
}
2024-05-22 19:07:14 -04:00
n , err := erasure . Encode ( ctx , toEncode , writers , buffer , writeQuorum )
2019-01-17 07:58:18 -05:00
closeBitrotWriters ( writers )
2016-07-18 22:06:48 -04:00
if err != nil {
2017-06-21 22:53:09 -04:00
return pi , toObjectErr ( err , bucket , object )
2016-07-18 22:06:48 -04:00
}
2016-10-20 01:52:03 -04:00
2016-07-18 22:06:48 -04:00
// Should return IncompleteBody{} error when reader has fewer bytes
// than specified in request header.
2018-08-06 18:14:08 -04:00
if n < data . Size ( ) {
2020-09-08 17:22:04 -04:00
return pi , IncompleteBody { Bucket : bucket , Object : object }
2016-07-18 22:06:48 -04:00
}
2016-07-01 17:33:28 -04:00
2018-08-06 18:14:08 -04:00
for i := range writers {
if writers [ i ] == nil {
onlineDisks [ i ] = nil
}
}
2016-05-29 18:38:14 -04:00
// Rename temporary part file to its final location.
2020-06-12 23:04:01 -04:00
partPath := pathJoin ( uploadIDPath , fi . DataDir , partSuffix )
2016-05-26 06:15:01 -04:00
2018-11-14 20:36:41 -05:00
md5hex := r . MD5CurrentHexString ( )
2022-07-16 22:35:24 -04:00
if opts . PreserveETag != "" {
md5hex = opts . PreserveETag
}
2022-07-19 11:35:29 -04:00
2022-07-11 20:30:56 -04:00
var index [ ] byte
if opts . IndexCB != nil {
index = opts . IndexCB ( )
}
2017-10-22 01:30:34 -04:00
2024-09-11 14:35:37 -04:00
actualSize := data . ActualSize ( )
if actualSize < 0 {
_ , encrypted := crypto . IsEncrypted ( fi . Metadata )
compressed := fi . IsCompressed ( )
switch {
case compressed :
// ... nothing changes for compressed stream.
2024-09-12 08:24:04 -04:00
// if actualSize is -1 we have no known way to
// determine what is the actualSize.
2024-09-11 14:35:37 -04:00
case encrypted :
decSize , err := sio . DecryptedSize ( uint64 ( n ) )
if err == nil {
actualSize = int64 ( decSize )
}
default :
actualSize = n
}
}
2022-09-15 15:43:49 -04:00
partInfo := ObjectPartInfo {
2022-07-19 21:56:24 -04:00
Number : partID ,
ETag : md5hex ,
Size : n ,
2024-09-11 14:35:37 -04:00
ActualSize : actualSize ,
2022-07-19 21:56:24 -04:00
ModTime : UTCNow ( ) ,
Index : index ,
2022-08-29 19:57:16 -04:00
Checksums : r . ContentCRC ( ) ,
2022-07-19 21:56:24 -04:00
}
2023-05-05 22:53:12 -04:00
2024-08-12 04:38:15 -04:00
partFI , err := partInfo . MarshalMsg ( nil )
2022-07-19 11:35:29 -04:00
if err != nil {
return pi , toObjectErr ( err , minioMetaMultipartBucket , partPath )
2016-05-31 23:23:31 -04:00
}
2024-09-29 18:40:36 -04:00
// Serialize concurrent part uploads.
2024-09-11 14:35:37 -04:00
partIDLock := er . NewNSLock ( bucket , pathJoin ( object , uploadID , strconv . Itoa ( partID ) ) )
plkctx , err := partIDLock . GetLock ( ctx , globalOperationTimeout )
if err != nil {
return PartInfo { } , err
}
ctx = plkctx . Context ( )
defer partIDLock . Unlock ( plkctx )
2024-08-12 04:38:15 -04:00
onlineDisks , err = er . renamePart ( ctx , onlineDisks , minioMetaTmpBucket , tmpPartPath , minioMetaMultipartBucket , partPath , partFI , writeQuorum )
2022-07-19 11:35:29 -04:00
if err != nil {
2024-08-12 04:38:15 -04:00
if errors . Is ( err , errFileNotFound ) {
// An in-quorum errFileNotFound means that client stream
// prematurely closed and we do not find any xl.meta or
// part.1's - in such a scenario we must return as if client
// disconnected. This means that erasure.Encode() CreateFile()
// did not do anything.
return pi , IncompleteBody { Bucket : bucket , Object : object }
}
2022-07-19 11:35:29 -04:00
return pi , toObjectErr ( err , minioMetaMultipartBucket , partPath )
2017-01-31 12:38:34 -05:00
}
2016-05-28 18:13:15 -04:00
// Return success.
2017-01-31 12:38:34 -05:00
return PartInfo {
2022-09-15 15:43:49 -04:00
PartNumber : partInfo . Number ,
ETag : partInfo . ETag ,
LastModified : partInfo . ModTime ,
Size : partInfo . Size ,
ActualSize : partInfo . ActualSize ,
ChecksumCRC32 : partInfo . Checksums [ "CRC32" ] ,
ChecksumCRC32C : partInfo . Checksums [ "CRC32C" ] ,
ChecksumSHA1 : partInfo . Checksums [ "SHA1" ] ,
ChecksumSHA256 : partInfo . Checksums [ "SHA256" ] ,
2017-01-31 12:38:34 -05:00
} , nil
2016-05-20 23:48:47 -04:00
}
2020-05-28 15:36:20 -04:00
// GetMultipartInfo returns multipart metadata uploaded during newMultipartUpload, used
// by callers to verify object states
// - encrypted
// - compressed
2022-07-19 11:35:29 -04:00
// Does not contain currently uploaded parts by design.
2020-06-12 23:04:01 -04:00
func ( er erasureObjects ) GetMultipartInfo ( ctx context . Context , bucket , object , uploadID string , opts ObjectOptions ) ( MultipartInfo , error ) {
2024-03-06 06:43:16 -05:00
if ! opts . NoAuditLog {
2024-08-13 18:22:04 -04:00
auditObjectErasureSet ( ctx , "GetMultipartInfo" , object , & er )
2024-03-06 06:43:16 -05:00
}
2022-05-04 03:45:27 -04:00
2020-05-28 15:36:20 -04:00
result := MultipartInfo {
Bucket : bucket ,
Object : object ,
UploadID : uploadID ,
}
2022-09-15 15:43:49 -04:00
fi , _ , err := er . checkUploadIDExists ( ctx , bucket , object , uploadID , false )
2020-05-28 15:36:20 -04:00
if err != nil {
2024-01-30 15:43:25 -05:00
if errors . Is ( err , errVolumeNotFound ) {
return result , toObjectErr ( err , bucket )
}
2022-09-15 15:43:49 -04:00
return result , toObjectErr ( err , bucket , object , uploadID )
2020-05-28 15:36:20 -04:00
}
2020-09-10 14:37:22 -04:00
result . UserDefined = cloneMSS ( fi . Metadata )
2020-05-28 15:36:20 -04:00
return result , nil
}
2024-09-06 05:42:21 -04:00
func ( er erasureObjects ) listParts ( ctx context . Context , onlineDisks [ ] StorageAPI , partPath string , readQuorum int ) ( [ ] int , error ) {
g := errgroup . WithNErrs ( len ( onlineDisks ) )
objectParts := make ( [ ] [ ] string , len ( onlineDisks ) )
// List uploaded parts from drives.
for index := range onlineDisks {
index := index
g . Go ( func ( ) ( err error ) {
if onlineDisks [ index ] == nil {
return errDiskNotFound
}
objectParts [ index ] , err = onlineDisks [ index ] . ListDir ( ctx , minioMetaMultipartBucket , minioMetaMultipartBucket , partPath , - 1 )
return err
} , index )
}
if err := reduceReadQuorumErrs ( ctx , g . Wait ( ) , objectOpIgnoredErrs , readQuorum ) ; err != nil {
return nil , err
}
partQuorumMap := make ( map [ int ] int )
for _ , driveParts := range objectParts {
partsWithMetaCount := make ( map [ int ] int , len ( driveParts ) )
// part files can be either part.N or part.N.meta
for _ , partPath := range driveParts {
var partNum int
if _ , err := fmt . Sscanf ( partPath , "part.%d" , & partNum ) ; err == nil {
partsWithMetaCount [ partNum ] ++
continue
}
if _ , err := fmt . Sscanf ( partPath , "part.%d.meta" , & partNum ) ; err == nil {
partsWithMetaCount [ partNum ] ++
}
}
// Include only part.N.meta files with corresponding part.N
for partNum , cnt := range partsWithMetaCount {
if cnt < 2 {
continue
}
partQuorumMap [ partNum ] ++
}
}
var partNums [ ] int
for partNum , count := range partQuorumMap {
if count < readQuorum {
continue
}
partNums = append ( partNums , partNum )
}
sort . Ints ( partNums )
return partNums , nil
}
2018-03-15 16:55:23 -04:00
// ListObjectParts - lists all previously uploaded parts for a given
// object and uploadID. Takes additional input of part-number-marker
// to indicate where the listing should begin from.
//
// Implements S3 compatible ListObjectParts API. The resulting
2018-06-28 19:02:02 -04:00
// ListPartsInfo structure is marshaled directly into XML and
2018-03-15 16:55:23 -04:00
// replied back to the client.
2021-03-03 21:36:43 -05:00
func ( er erasureObjects ) ListObjectParts ( ctx context . Context , bucket , object , uploadID string , partNumberMarker , maxParts int , opts ObjectOptions ) ( result ListPartsInfo , err error ) {
2024-03-06 06:43:16 -05:00
if ! opts . NoAuditLog {
2024-08-13 18:22:04 -04:00
auditObjectErasureSet ( ctx , "ListObjectParts" , object , & er )
2024-03-06 06:43:16 -05:00
}
2022-05-04 03:45:27 -04:00
2022-09-15 15:43:49 -04:00
fi , _ , err := er . checkUploadIDExists ( ctx , bucket , object , uploadID , false )
if err != nil {
2019-04-23 17:54:28 -04:00
return result , toObjectErr ( err , bucket , object , uploadID )
2018-03-15 16:55:23 -04:00
}
2016-05-20 23:48:47 -04:00
2020-06-12 23:04:01 -04:00
uploadIDPath := er . getUploadIDDir ( bucket , object , uploadID )
2024-09-06 05:42:21 -04:00
if partNumberMarker < 0 {
partNumberMarker = 0
}
// Limit output to maxPartsList.
if maxParts > maxPartsList {
maxParts = maxPartsList
}
2016-05-31 23:23:31 -04:00
2023-10-24 16:51:57 -04:00
// Populate the result stub.
result . Bucket = bucket
result . Object = object
result . UploadID = uploadID
result . MaxParts = maxParts
result . PartNumberMarker = partNumberMarker
result . UserDefined = cloneMSS ( fi . Metadata )
result . ChecksumAlgorithm = fi . Metadata [ hash . MinIOMultipartChecksum ]
2022-07-21 19:47:58 -04:00
2023-10-24 16:51:57 -04:00
if maxParts == 0 {
return result , nil
}
2024-09-06 05:42:21 -04:00
onlineDisks := er . getDisks ( )
readQuorum := fi . ReadQuorum ( er . defaultRQuorum ( ) )
2022-07-19 11:35:29 -04:00
// Read Part info for all parts
2024-09-06 05:42:21 -04:00
partPath := pathJoin ( uploadIDPath , fi . DataDir ) + SlashSeparator
2022-07-19 11:35:29 -04:00
2024-09-06 05:42:21 -04:00
// List parts in quorum
partNums , err := er . listParts ( ctx , onlineDisks , partPath , readQuorum )
if err != nil {
// This means that fi.DataDir, is not yet populated so we
// return an empty response.
if errors . Is ( err , errFileNotFound ) {
return result , nil
}
return result , toObjectErr ( err , bucket , object , uploadID )
2022-07-19 11:35:29 -04:00
}
2024-09-06 05:42:21 -04:00
if len ( partNums ) == 0 {
return result , nil
2022-07-19 11:35:29 -04:00
}
2024-09-06 05:42:21 -04:00
start := objectPartIndexNums ( partNums , partNumberMarker )
if start != - 1 {
partNums = partNums [ start + 1 : ]
2016-05-25 00:24:20 -04:00
}
2024-09-06 05:42:21 -04:00
result . Parts = make ( [ ] PartInfo , 0 , len ( partNums ) )
partMetaPaths := make ( [ ] string , len ( partNums ) )
for i , part := range partNums {
partMetaPaths [ i ] = pathJoin ( partPath , fmt . Sprintf ( "part.%d.meta" , part ) )
2023-10-25 02:33:25 -04:00
}
2022-07-19 11:35:29 -04:00
2024-09-06 05:42:21 -04:00
// Read parts in quorum
objParts , err := readParts ( ctx , onlineDisks , minioMetaMultipartBucket , partMetaPaths ,
partNums , readQuorum )
if err != nil {
return result , toObjectErr ( err , bucket , object , uploadID )
2022-07-19 11:35:29 -04:00
}
2024-09-06 05:42:21 -04:00
count := maxParts
for _ , objPart := range objParts {
2017-01-31 12:38:34 -05:00
result . Parts = append ( result . Parts , PartInfo {
2024-09-06 05:42:21 -04:00
PartNumber : objPart . Number ,
LastModified : objPart . ModTime ,
ETag : objPart . ETag ,
Size : objPart . Size ,
ActualSize : objPart . ActualSize ,
ChecksumCRC32 : objPart . Checksums [ "CRC32" ] ,
ChecksumCRC32C : objPart . Checksums [ "CRC32C" ] ,
ChecksumSHA1 : objPart . Checksums [ "SHA1" ] ,
ChecksumSHA256 : objPart . Checksums [ "SHA256" ] ,
2016-05-20 23:48:47 -04:00
} )
2024-09-06 05:42:21 -04:00
count --
if count == 0 {
2016-05-20 23:48:47 -04:00
break
}
}
2022-07-21 19:47:58 -04:00
2024-09-06 05:42:21 -04:00
if len ( objParts ) > len ( result . Parts ) {
2016-05-20 23:48:47 -04:00
result . IsTruncated = true
2024-09-06 05:42:21 -04:00
// Make sure to fill next part number marker if IsTruncated is true for subsequent listing.
result . NextPartNumberMarker = result . Parts [ len ( result . Parts ) - 1 ] . PartNumber
2016-05-20 23:48:47 -04:00
}
2024-09-06 05:42:21 -04:00
2016-05-20 23:48:47 -04:00
return result , nil
}
2024-09-06 05:42:21 -04:00
func readParts ( ctx context . Context , disks [ ] StorageAPI , bucket string , partMetaPaths [ ] string , partNumbers [ ] int , readQuorum int ) ( [ ] ObjectPartInfo , error ) {
2024-08-12 04:38:15 -04:00
g := errgroup . WithNErrs ( len ( disks ) )
objectPartInfos := make ( [ ] [ ] * ObjectPartInfo , len ( disks ) )
// Rename file on all underlying storage disks.
for index := range disks {
index := index
g . Go ( func ( ) ( err error ) {
if disks [ index ] == nil {
return errDiskNotFound
}
objectPartInfos [ index ] , err = disks [ index ] . ReadParts ( ctx , bucket , partMetaPaths ... )
return err
} , index )
}
if err := reduceReadQuorumErrs ( ctx , g . Wait ( ) , objectOpIgnoredErrs , readQuorum ) ; err != nil {
return nil , err
}
2024-09-06 05:42:21 -04:00
partInfosInQuorum := make ( [ ] ObjectPartInfo , len ( partMetaPaths ) )
2024-08-12 04:38:15 -04:00
for pidx := range partMetaPaths {
2024-09-06 06:51:23 -04:00
// partMetaQuorumMap uses
// - path/to/part.N as key to collate errors from failed drives.
// - part ETag to collate part metadata
partMetaQuorumMap := make ( map [ string ] int , len ( partNumbers ) )
2024-08-12 04:38:15 -04:00
var pinfos [ ] * ObjectPartInfo
for idx := range disks {
2024-09-06 06:51:23 -04:00
if len ( objectPartInfos [ idx ] ) != len ( partMetaPaths ) {
2024-08-12 04:38:15 -04:00
partMetaQuorumMap [ partMetaPaths [ pidx ] ] ++
continue
}
pinfo := objectPartInfos [ idx ] [ pidx ]
2024-09-06 06:51:23 -04:00
if pinfo != nil && pinfo . ETag != "" {
2024-08-12 04:38:15 -04:00
pinfos = append ( pinfos , pinfo )
partMetaQuorumMap [ pinfo . ETag ] ++
2024-09-06 06:51:23 -04:00
continue
2024-08-12 04:38:15 -04:00
}
2024-09-06 06:51:23 -04:00
partMetaQuorumMap [ partMetaPaths [ pidx ] ] ++
2024-08-12 04:38:15 -04:00
}
var maxQuorum int
var maxETag string
var maxPartMeta string
for etag , quorum := range partMetaQuorumMap {
if maxQuorum < quorum {
maxQuorum = quorum
maxETag = etag
maxPartMeta = etag
}
}
2024-09-06 06:51:23 -04:00
// found is a representative ObjectPartInfo which either has the maximally occurring ETag or an error.
var found * ObjectPartInfo
for _ , pinfo := range pinfos {
if pinfo == nil {
continue
}
if maxETag != "" && pinfo . ETag == maxETag {
found = pinfo
2024-08-12 04:38:15 -04:00
break
}
2024-09-06 06:51:23 -04:00
if pinfo . ETag == "" && maxPartMeta != "" && path . Base ( maxPartMeta ) == fmt . Sprintf ( "part.%d.meta" , pinfo . Number ) {
found = pinfo
2024-08-12 04:38:15 -04:00
break
}
}
2024-09-06 06:51:23 -04:00
if found != nil && found . ETag != "" && partMetaQuorumMap [ maxETag ] >= readQuorum {
partInfosInQuorum [ pidx ] = * found
2024-08-12 04:38:15 -04:00
continue
}
2024-09-06 06:51:23 -04:00
partInfosInQuorum [ pidx ] = ObjectPartInfo {
Number : partNumbers [ pidx ] ,
Error : InvalidPart {
PartNumber : partNumbers [ pidx ] ,
} . Error ( ) ,
2024-08-12 04:38:15 -04:00
}
2024-09-06 06:51:23 -04:00
2024-08-12 04:38:15 -04:00
}
return partInfosInQuorum , nil
}
2024-09-06 06:51:23 -04:00
func objPartToPartErr ( part ObjectPartInfo ) error {
if strings . Contains ( part . Error , "file not found" ) {
return InvalidPart { PartNumber : part . Number }
2024-08-12 04:38:15 -04:00
}
2024-09-06 06:51:23 -04:00
if strings . Contains ( part . Error , "Specified part could not be found" ) {
return InvalidPart { PartNumber : part . Number }
2024-08-12 04:38:15 -04:00
}
2024-09-06 06:51:23 -04:00
if strings . Contains ( part . Error , errErasureReadQuorum . Error ( ) ) {
2024-08-12 04:38:15 -04:00
return errErasureReadQuorum
}
2024-09-06 06:51:23 -04:00
return errors . New ( part . Error )
2024-08-12 04:38:15 -04:00
}
2016-06-01 19:43:31 -04:00
// CompleteMultipartUpload - completes an ongoing multipart
// transaction after receiving all the parts indicated by the client.
// Returns an md5sum calculated by concatenating all the individual
// md5sums of all the parts.
//
// Implements S3 compatible Complete multipart API.
2020-09-14 18:57:13 -04:00
func ( er erasureObjects ) CompleteMultipartUpload ( ctx context . Context , bucket string , object string , uploadID string , parts [ ] CompletePart , opts ObjectOptions ) ( oi ObjectInfo , err error ) {
2024-03-06 06:43:16 -05:00
if ! opts . NoAuditLog {
2024-08-13 18:22:04 -04:00
auditObjectErasureSet ( ctx , "CompleteMultipartUpload" , object , & er )
2024-03-06 06:43:16 -05:00
}
2022-05-04 03:45:27 -04:00
2024-05-10 20:31:22 -04:00
if opts . CheckPrecondFn != nil {
if ! opts . NoLock {
ns := er . NewNSLock ( bucket , object )
lkctx , err := ns . GetLock ( ctx , globalOperationTimeout )
if err != nil {
return ObjectInfo { } , err
}
ctx = lkctx . Context ( )
defer ns . Unlock ( lkctx )
opts . NoLock = true
}
obj , err := er . getObjectInfo ( ctx , bucket , object , opts )
if err == nil && opts . CheckPrecondFn ( obj ) {
return ObjectInfo { } , PreConditionFailed { }
}
if err != nil && ! isErrVersionNotFound ( err ) && ! isErrObjectNotFound ( err ) && ! isErrReadQuorum ( err ) {
return ObjectInfo { } , err
}
}
2023-08-17 12:37:55 -04:00
fi , partsMetadata , err := er . checkUploadIDExists ( ctx , bucket , object , uploadID , true )
2022-09-15 15:43:49 -04:00
if err != nil {
2024-01-30 15:43:25 -05:00
if errors . Is ( err , errVolumeNotFound ) {
return oi , toObjectErr ( err , bucket )
}
2019-04-23 17:54:28 -04:00
return oi , toObjectErr ( err , bucket , object , uploadID )
2016-05-28 00:50:09 -04:00
}
2017-02-21 22:43:44 -05:00
2020-06-12 23:04:01 -04:00
uploadIDPath := er . getUploadIDDir ( bucket , object , uploadID )
2022-09-15 15:43:49 -04:00
onlineDisks := er . getDisks ( )
writeQuorum := fi . WriteQuorum ( er . defaultWQuorum ( ) )
2024-08-12 04:38:15 -04:00
readQuorum := fi . ReadQuorum ( er . defaultRQuorum ( ) )
2022-07-19 11:35:29 -04:00
// Read Part info for all parts
2024-09-05 16:37:19 -04:00
partPath := pathJoin ( uploadIDPath , fi . DataDir ) + SlashSeparator
2024-08-12 04:38:15 -04:00
partMetaPaths := make ( [ ] string , len ( parts ) )
partNumbers := make ( [ ] int , len ( parts ) )
for idx , part := range parts {
partMetaPaths [ idx ] = pathJoin ( partPath , fmt . Sprintf ( "part.%d.meta" , part . PartNumber ) )
partNumbers [ idx ] = part . PartNumber
2022-07-19 11:35:29 -04:00
}
2024-08-12 04:38:15 -04:00
partInfoFiles , err := readParts ( ctx , onlineDisks , minioMetaMultipartBucket , partMetaPaths , partNumbers , readQuorum )
2021-04-21 22:06:08 -04:00
if err != nil {
return oi , err
}
2024-08-12 04:38:15 -04:00
2022-07-19 11:35:29 -04:00
if len ( partInfoFiles ) != len ( parts ) {
// Should only happen through internal error
err := fmt . Errorf ( "unexpected part result count: %d, want %d" , len ( partInfoFiles ) , len ( parts ) )
2024-04-04 08:04:40 -04:00
bugLogIf ( ctx , err )
2022-07-19 11:35:29 -04:00
return oi , toObjectErr ( err , bucket , object )
}
2022-08-29 19:57:16 -04:00
// Checksum type set when upload started.
var checksumType hash . ChecksumType
if cs := fi . Metadata [ hash . MinIOMultipartChecksum ] ; cs != "" {
checksumType = hash . NewChecksumType ( cs )
if opts . WantChecksum != nil && ! opts . WantChecksum . Type . Is ( checksumType ) {
return oi , InvalidArgument {
Bucket : bucket ,
Object : fi . Name ,
Err : fmt . Errorf ( "checksum type mismatch" ) ,
}
}
}
2022-09-24 00:17:08 -04:00
2022-08-29 19:57:16 -04:00
var checksumCombined [ ] byte
2022-09-24 00:17:08 -04:00
// However, in case of encryption, the persisted part ETags don't match
// what we have sent to the client during PutObjectPart. The reason is
// that ETags are encrypted. Hence, the client will send a list of complete
2024-09-11 14:35:37 -04:00
// part ETags of which may not match the ETag of any part. For example
2022-09-24 00:17:08 -04:00
// ETag (client): 30902184f4e62dd8f98f0aaff810c626
// ETag (server-internal): 20000f00ce5dc16e3f3b124f586ae1d88e9caa1c598415c2759bbb50e84a59f630902184f4e62dd8f98f0aaff810c626
//
// Therefore, we adjust all ETags sent by the client to match what is stored
// on the backend.
2022-12-07 13:18:18 -05:00
kind , _ := crypto . IsEncrypted ( fi . Metadata )
2022-09-24 00:17:08 -04:00
var objectEncryptionKey [ ] byte
2022-12-07 13:18:18 -05:00
switch kind {
case crypto . SSEC :
if checksumType . IsSet ( ) {
if opts . EncryptFn == nil {
return oi , crypto . ErrMissingCustomerKey
}
baseKey := opts . EncryptFn ( "" , nil )
if len ( baseKey ) != 32 {
return oi , crypto . ErrInvalidCustomerKey
}
objectEncryptionKey , err = decryptObjectMeta ( baseKey , bucket , object , fi . Metadata )
if err != nil {
return oi , err
}
}
case crypto . S3 , crypto . S3KMS :
2022-09-24 00:17:08 -04:00
objectEncryptionKey , err = decryptObjectMeta ( nil , bucket , object , fi . Metadata )
if err != nil {
return oi , err
}
}
2022-12-07 13:18:18 -05:00
if len ( objectEncryptionKey ) == 32 {
var key crypto . ObjectKey
copy ( key [ : ] , objectEncryptionKey )
opts . EncryptFn = metadataEncrypter ( key )
}
2022-09-24 00:17:08 -04:00
2024-08-12 04:38:15 -04:00
for idx , part := range partInfoFiles {
if part . Error != "" {
2024-09-06 06:51:23 -04:00
err = objPartToPartErr ( part )
2024-04-04 08:04:40 -04:00
bugLogIf ( ctx , err )
2024-08-12 04:38:15 -04:00
return oi , err
2022-07-19 11:35:29 -04:00
}
2022-07-19 21:56:24 -04:00
2024-08-12 04:38:15 -04:00
if parts [ idx ] . PartNumber != part . Number {
internalLogIf ( ctx , fmt . Errorf ( "part.%d.meta has incorrect corresponding part number: expected %d, got %d" , parts [ idx ] . PartNumber , parts [ idx ] . PartNumber , part . Number ) )
2022-07-19 11:35:29 -04:00
return oi , InvalidPart {
2024-08-12 04:38:15 -04:00
PartNumber : part . Number ,
2022-07-19 11:35:29 -04:00
}
}
2022-07-19 21:56:24 -04:00
2022-07-19 11:35:29 -04:00
// Add the current part.
2024-08-12 04:38:15 -04:00
fi . AddObjectPart ( part . Number , part . ETag , part . Size , part . ActualSize , part . ModTime , part . Index , part . Checksums )
2022-07-19 11:35:29 -04:00
}
2016-07-14 17:59:01 -04:00
2016-05-31 23:23:31 -04:00
// Calculate full object size.
2016-05-20 23:48:47 -04:00
var objectSize int64
2016-05-25 00:24:20 -04:00
2018-09-27 23:36:17 -04:00
// Calculate consolidated actual size.
var objectActualSize int64
2016-07-27 14:57:08 -04:00
// Order online disks in accordance with distribution order.
// Order parts metadata in accordance with distribution order.
2021-03-15 23:03:13 -04:00
onlineDisks , partsMetadata = shuffleDisksAndPartsMetadataByIndex ( onlineDisks , partsMetadata , fi )
2016-07-27 14:57:08 -04:00
2020-06-12 23:04:01 -04:00
// Save current erasure metadata for validation.
2022-01-02 12:15:06 -05:00
currentFI := fi
2016-05-25 00:24:20 -04:00
// Allocate parts similar to incoming slice.
2020-06-12 23:04:01 -04:00
fi . Parts = make ( [ ] ObjectPartInfo , len ( parts ) )
2016-05-25 00:24:20 -04:00
2016-06-01 19:43:31 -04:00
// Validate each part and then commit to disk.
2016-05-20 23:48:47 -04:00
for i , part := range parts {
2020-06-12 23:04:01 -04:00
partIdx := objectPartIndex ( currentFI . Parts , part . PartNumber )
2016-06-19 17:51:20 -04:00
// All parts should have same part number.
2016-05-25 00:24:20 -04:00
if partIdx == - 1 {
2018-08-14 21:35:30 -04:00
invp := InvalidPart {
PartNumber : part . PartNumber ,
GotETag : part . ETag ,
}
return oi , invp
2016-05-20 23:48:47 -04:00
}
2022-09-24 00:17:08 -04:00
expPart := currentFI . Parts [ partIdx ]
2016-06-19 17:51:20 -04:00
2020-06-12 23:04:01 -04:00
// ensure that part ETag is canonicalized to strip off extraneous quotes
part . ETag = canonicalizeETag ( part . ETag )
2023-06-05 16:08:51 -04:00
expETag := tryDecryptETag ( objectEncryptionKey , expPart . ETag , kind == crypto . S3 )
2022-09-24 00:17:08 -04:00
if expETag != part . ETag {
2018-08-14 21:35:30 -04:00
invp := InvalidPart {
PartNumber : part . PartNumber ,
2022-09-24 00:17:08 -04:00
ExpETag : expETag ,
2018-08-14 21:35:30 -04:00
GotETag : part . ETag ,
}
return oi , invp
2016-05-25 00:24:20 -04:00
}
2016-06-19 17:51:20 -04:00
2022-08-29 19:57:16 -04:00
if checksumType . IsSet ( ) {
2022-09-24 00:17:08 -04:00
crc := expPart . Checksums [ checksumType . String ( ) ]
2022-08-29 19:57:16 -04:00
if crc == "" {
return oi , InvalidPart {
PartNumber : part . PartNumber ,
}
}
wantCS := map [ string ] string {
hash . ChecksumCRC32 . String ( ) : part . ChecksumCRC32 ,
hash . ChecksumCRC32C . String ( ) : part . ChecksumCRC32C ,
hash . ChecksumSHA1 . String ( ) : part . ChecksumSHA1 ,
hash . ChecksumSHA256 . String ( ) : part . ChecksumSHA256 ,
}
if wantCS [ checksumType . String ( ) ] != crc {
return oi , InvalidPart {
PartNumber : part . PartNumber ,
ExpETag : wantCS [ checksumType . String ( ) ] ,
GotETag : crc ,
}
}
cs := hash . NewChecksumString ( checksumType . String ( ) , crc )
if ! cs . Valid ( ) {
return oi , InvalidPart {
PartNumber : part . PartNumber ,
}
}
2022-10-15 14:58:47 -04:00
checksumCombined = append ( checksumCombined , cs . Raw ... )
2022-08-29 19:57:16 -04:00
}
2022-07-19 11:35:29 -04:00
// All parts except the last part has to be at least 5MB.
2020-06-12 23:04:01 -04:00
if ( i < len ( parts ) - 1 ) && ! isMinAllowedPartSize ( currentFI . Parts [ partIdx ] . ActualSize ) {
2018-04-05 18:04:40 -04:00
return oi , PartTooSmall {
PartNumber : part . PartNumber ,
2022-09-24 00:17:08 -04:00
PartSize : expPart . ActualSize ,
2018-04-05 18:04:40 -04:00
PartETag : part . ETag ,
}
2016-05-20 23:48:47 -04:00
}
2016-05-25 00:24:20 -04:00
// Save for total object size.
2022-09-24 00:17:08 -04:00
objectSize += expPart . Size
2016-05-25 00:24:20 -04:00
2018-09-27 23:36:17 -04:00
// Save the consolidated actual size.
2022-09-24 00:17:08 -04:00
objectActualSize += expPart . ActualSize
2018-09-27 23:36:17 -04:00
2016-05-25 00:24:20 -04:00
// Add incoming parts.
2020-06-12 23:04:01 -04:00
fi . Parts [ i ] = ObjectPartInfo {
2018-09-27 23:36:17 -04:00
Number : part . PartNumber ,
2022-09-24 00:17:08 -04:00
Size : expPart . Size ,
ActualSize : expPart . ActualSize ,
ModTime : expPart . ModTime ,
Index : expPart . Index ,
2022-08-29 19:57:16 -04:00
Checksums : nil , // Not transferred since we do not need it.
}
}
if opts . WantChecksum != nil {
2024-05-08 12:18:34 -04:00
err := opts . WantChecksum . Matches ( checksumCombined , len ( parts ) )
2022-08-29 19:57:16 -04:00
if err != nil {
return oi , err
2016-05-25 00:24:20 -04:00
}
2016-05-20 23:48:47 -04:00
}
2023-10-20 22:28:05 -04:00
2024-06-13 02:56:12 -04:00
// Accept encrypted checksum from incoming request.
if opts . UserDefined [ ReplicationSsecChecksumHeader ] != "" {
if v , err := base64 . StdEncoding . DecodeString ( opts . UserDefined [ ReplicationSsecChecksumHeader ] ) ; err == nil {
fi . Checksum = v
}
delete ( opts . UserDefined , ReplicationSsecChecksumHeader )
}
2022-08-29 19:57:16 -04:00
if checksumType . IsSet ( ) {
2023-04-28 11:26:32 -04:00
checksumType |= hash . ChecksumMultipart | hash . ChecksumIncludesMultipart
2024-06-06 05:36:42 -04:00
var cs * hash . Checksum
cs = hash . NewChecksumFromData ( checksumType , checksumCombined )
2023-04-28 11:26:32 -04:00
fi . Checksum = cs . AppendTo ( nil , checksumCombined )
2022-08-31 11:13:23 -04:00
if opts . EncryptFn != nil {
fi . Checksum = opts . EncryptFn ( "object-checksum" , fi . Checksum )
}
2022-08-29 19:57:16 -04:00
}
2024-06-13 02:56:12 -04:00
delete ( fi . Metadata , hash . MinIOMultipartChecksum ) // Not needed in final object.
2016-05-20 23:48:47 -04:00
// Save the final object size and modtime.
2020-06-12 23:04:01 -04:00
fi . Size = objectSize
2020-07-08 20:36:56 -04:00
fi . ModTime = opts . MTime
if opts . MTime . IsZero ( ) {
fi . ModTime = UTCNow ( )
}
2016-05-20 23:48:47 -04:00
// Save successfully calculated md5sum.
2022-11-26 17:43:32 -05:00
// for replica, newMultipartUpload would have already sent the replication ETag
2021-09-09 01:25:23 -04:00
if fi . Metadata [ "etag" ] == "" {
2022-11-26 17:43:32 -05:00
if opts . UserDefined [ "etag" ] != "" {
fi . Metadata [ "etag" ] = opts . UserDefined [ "etag" ]
} else { // fallback if not already calculated in handler.
fi . Metadata [ "etag" ] = getCompleteMultipartMD5 ( parts )
}
2020-08-12 20:32:24 -04:00
}
2017-10-22 01:30:34 -04:00
2018-09-27 23:36:17 -04:00
// Save the consolidated actual size.
2024-03-28 13:44:56 -04:00
if opts . ReplicationRequest {
2024-08-08 11:29:58 -04:00
if v := opts . UserDefined [ ReservedMetadataPrefix + "Actual-Object-Size" ] ; v != "" {
fi . Metadata [ ReservedMetadataPrefix + "actual-size" ] = v
}
2024-03-28 13:44:56 -04:00
} else {
fi . Metadata [ ReservedMetadataPrefix + "actual-size" ] = strconv . FormatInt ( objectActualSize , 10 )
}
2018-09-27 23:36:17 -04:00
2024-02-03 17:03:30 -05:00
if opts . DataMovement {
fi . SetDataMov ( )
}
2020-06-12 23:04:01 -04:00
// Update all erasure metadata, make sure to not modify fields like
2016-05-31 23:23:31 -04:00
// checksum which are different on each disks.
for index := range partsMetadata {
2020-11-20 12:10:48 -05:00
if partsMetadata [ index ] . IsValid ( ) {
partsMetadata [ index ] . Size = fi . Size
partsMetadata [ index ] . ModTime = fi . ModTime
partsMetadata [ index ] . Metadata = fi . Metadata
partsMetadata [ index ] . Parts = fi . Parts
2022-10-26 21:14:58 -04:00
partsMetadata [ index ] . Checksum = fi . Checksum
2023-08-27 12:57:11 -04:00
partsMetadata [ index ] . Versioned = opts . Versioned || opts . VersionSuspended
2020-11-20 12:10:48 -05:00
}
2016-05-20 23:48:47 -04:00
}
2016-08-31 14:39:08 -04:00
2024-07-29 21:56:40 -04:00
paths := make ( [ ] string , 0 , len ( currentFI . Parts ) )
2016-06-10 09:13:16 -04:00
// Remove parts that weren't present in CompleteMultipartUpload request.
2020-06-12 23:04:01 -04:00
for _ , curpart := range currentFI . Parts {
2024-07-29 21:56:40 -04:00
paths = append ( paths , pathJoin ( uploadIDPath , currentFI . DataDir , fmt . Sprintf ( "part.%d.meta" , curpart . Number ) ) )
2023-08-27 12:57:11 -04:00
2020-06-12 23:04:01 -04:00
if objectPartIndex ( fi . Parts , curpart . Number ) == - 1 {
2016-05-28 16:23:08 -04:00
// Delete the missing part files. e.g,
// Request 1: NewMultipart
// Request 2: PutObjectPart 1
// Request 3: PutObjectPart 2
// Request 4: CompleteMultipartUpload --part 2
// N.B. 1st part is not present. This part should be removed from the storage.
2024-07-29 21:56:40 -04:00
paths = append ( paths , pathJoin ( uploadIDPath , currentFI . DataDir , fmt . Sprintf ( "part.%d" , curpart . Number ) ) )
2016-05-28 16:23:08 -04:00
}
}
2024-09-13 16:26:02 -04:00
if ! opts . NoLock {
lk := er . NewNSLock ( bucket , object )
lkctx , err := lk . GetLock ( ctx , globalOperationTimeout )
if err != nil {
return ObjectInfo { } , err
}
ctx = lkctx . Context ( )
defer lk . Unlock ( lkctx )
}
2024-07-29 21:56:40 -04:00
er . cleanupMultipartPath ( ctx , paths ... ) // cleanup all part.N.meta, and skipped part.N's before final rename().
2023-09-29 13:28:19 -04:00
defer func ( ) {
if err == nil {
er . deleteAll ( context . Background ( ) , minioMetaMultipartBucket , uploadIDPath )
}
} ( )
2022-07-19 11:35:29 -04:00
2016-05-29 03:42:09 -04:00
// Rename the multipart object to final location.
2024-04-23 13:15:52 -04:00
onlineDisks , versions , oldDataDir , err := renameData ( ctx , onlineDisks , minioMetaMultipartBucket , uploadIDPath ,
2022-12-16 11:52:12 -05:00
partsMetadata , bucket , object , writeQuorum )
if err != nil {
2024-04-28 13:53:50 -04:00
return oi , toObjectErr ( err , bucket , object , uploadID )
2016-05-20 23:48:47 -04:00
}
2022-08-02 19:57:39 -04:00
2024-05-14 06:29:17 -04:00
if err = er . commitRenameDataDir ( ctx , bucket , object , oldDataDir , onlineDisks , writeQuorum ) ; err != nil {
2024-04-28 13:53:50 -04:00
return ObjectInfo { } , toObjectErr ( err , bucket , object , uploadID )
2024-04-23 13:15:52 -04:00
}
if ! opts . Speedtest && len ( versions ) > 0 {
2024-08-13 18:26:05 -04:00
globalMRFState . addPartialOp ( PartialOperation {
Bucket : bucket ,
Object : object ,
Queued : time . Now ( ) ,
Versions : versions ,
SetIndex : er . setIndex ,
PoolIndex : er . poolIndex ,
2023-06-24 22:31:04 -04:00
} )
2022-12-16 11:52:12 -05:00
}
2024-04-23 13:15:52 -04:00
if ! opts . Speedtest && len ( versions ) == 0 {
2023-12-04 14:13:50 -05:00
// Check if there is any offline disk and add it to the MRF list
for _ , disk := range onlineDisks {
if disk != nil && disk . IsOnline ( ) {
continue
}
er . addPartial ( bucket , object , fi . VersionID )
break
2020-01-15 21:30:32 -05:00
}
}
2020-06-12 23:04:01 -04:00
for i := 0 ; i < len ( onlineDisks ) ; i ++ {
2020-12-22 12:16:43 -05:00
if onlineDisks [ i ] != nil && onlineDisks [ i ] . IsOnline ( ) {
// Object info is the same in all disks, so we can pick
// the first meta from online disk
fi = partsMetadata [ i ]
break
2020-06-12 23:04:01 -04:00
}
}
2021-09-22 22:17:09 -04:00
// we are adding a new version to this object under the namespace lock, so this is the latest version.
fi . IsLatest = true
2017-01-16 22:23:43 -05:00
// Success, return object info.
2022-05-31 05:57:57 -04:00
return fi . ToObjectInfo ( bucket , object , opts . Versioned || opts . VersionSuspended ) , nil
2016-05-20 23:48:47 -04:00
}
2016-06-01 19:43:31 -04:00
// AbortMultipartUpload - aborts an ongoing multipart operation
// signified by the input uploadID. This is an atomic operation
// doesn't require clients to initiate multiple such requests.
//
// All parts are purged from all disks and reference to the uploadID
// would be removed from the system, rollback is not possible on this
// operation.
2021-03-03 21:36:43 -05:00
func ( er erasureObjects ) AbortMultipartUpload ( ctx context . Context , bucket , object , uploadID string , opts ObjectOptions ) ( err error ) {
2024-03-06 06:43:16 -05:00
if ! opts . NoAuditLog {
2024-08-13 18:22:04 -04:00
auditObjectErasureSet ( ctx , "AbortMultipartUpload" , object , & er )
2024-03-06 06:43:16 -05:00
}
2022-05-04 03:45:27 -04:00
2019-04-23 17:54:28 -04:00
// Validates if upload ID exists.
2022-09-15 15:43:49 -04:00
if _ , _ , err = er . checkUploadIDExists ( ctx , bucket , object , uploadID , false ) ; err != nil {
2024-01-30 15:43:25 -05:00
if errors . Is ( err , errVolumeNotFound ) {
return toObjectErr ( err , bucket )
}
2019-04-23 17:54:28 -04:00
return toObjectErr ( err , bucket , object , uploadID )
2016-05-28 16:23:08 -04:00
}
2018-03-15 16:55:23 -04:00
// Cleanup all uploaded parts.
2022-11-01 11:00:02 -04:00
er . deleteAll ( ctx , minioMetaMultipartBucket , er . getUploadIDDir ( bucket , object , uploadID ) )
2018-03-15 16:55:23 -04:00
// Successfully purged.
return nil
}