diff --git a/cmd/erasure-healing-common_test.go b/cmd/erasure-healing-common_test.go
index 8012dae1f..eaa2467a9 100644
--- a/cmd/erasure-healing-common_test.go
+++ b/cmd/erasure-healing-common_test.go
@@ -20,7 +20,6 @@ package cmd
import (
"bytes"
"context"
- "errors"
"fmt"
"os"
"path/filepath"
@@ -426,7 +425,7 @@ func TestListOnlineDisksSmallObjects(t *testing.T) {
}
partsMetadata, errs := readAllFileInfo(ctx, erasureDisks, bucket, object, "", true)
- _, err = getLatestFileInfo(ctx, partsMetadata, z.serverPools[0].sets[0].defaultParityCount, errs)
+ fi, err := getLatestFileInfo(ctx, partsMetadata, z.serverPools[0].sets[0].defaultParityCount, errs)
if err != nil {
t.Fatalf("Failed to getLatestFileInfo %v", err)
}
@@ -484,11 +483,6 @@ func TestListOnlineDisksSmallObjects(t *testing.T) {
}
}
- partsMetadata, errs = readAllFileInfo(ctx, erasureDisks, bucket, object, "", true)
- fi, err := getLatestFileInfo(ctx, partsMetadata, z.serverPools[0].sets[0].defaultParityCount, errs)
- if !errors.Is(err, errErasureReadQuorum) {
- t.Fatalf("Failed to getLatestFileInfo, expected %v, got %v", errErasureReadQuorum, err)
- }
rQuorum := len(errs) - z.serverPools[0].sets[0].defaultParityCount
onlineDisks, modTime, _ := listOnlineDisks(erasureDisks, partsMetadata, test.errs, rQuorum)
diff --git a/cmd/erasure-multipart.go b/cmd/erasure-multipart.go
index 6d6f3f622..f7a482098 100644
--- a/cmd/erasure-multipart.go
+++ b/cmd/erasure-multipart.go
@@ -644,9 +644,8 @@ func (er erasureObjects) PutObjectPart(ctx context.Context, bucket, object, uplo
tmpPartPath := pathJoin(tmpPart, partSuffix)
// Delete the temporary object part. If PutObjectPart succeeds there would be nothing to delete.
- var online int
defer func() {
- if online != len(onlineDisks) {
+ if countOnlineDisks(onlineDisks) != len(onlineDisks) {
er.deleteAll(context.Background(), minioMetaTmpBucket, tmpPart)
}
}()
@@ -1214,6 +1213,7 @@ func (er erasureObjects) CompleteMultipartUpload(ctx context.Context, bucket str
partsMetadata[index].Metadata = fi.Metadata
partsMetadata[index].Parts = fi.Parts
partsMetadata[index].Checksum = fi.Checksum
+ partsMetadata[index].Versioned = opts.Versioned || opts.VersionSuspended
}
}
@@ -1228,6 +1228,9 @@ func (er erasureObjects) CompleteMultipartUpload(ctx context.Context, bucket str
// Remove parts that weren't present in CompleteMultipartUpload request.
for _, curpart := range currentFI.Parts {
+ // Remove part.meta which is not needed anymore.
+ er.removePartMeta(bucket, object, uploadID, currentFI.DataDir, curpart.Number)
+
if objectPartIndex(fi.Parts, curpart.Number) == -1 {
// Delete the missing part files. e.g,
// Request 1: NewMultipart
@@ -1239,10 +1242,7 @@ func (er erasureObjects) CompleteMultipartUpload(ctx context.Context, bucket str
}
}
- // Remove part.meta which is not needed anymore.
- for _, part := range currentFI.Parts {
- er.removePartMeta(bucket, object, uploadID, currentFI.DataDir, part.Number)
- }
+ defer er.deleteAll(context.Background(), minioMetaMultipartBucket, uploadIDPath)
// Rename the multipart object to final location.
onlineDisks, versionsDisparity, err := renameData(ctx, onlineDisks, minioMetaMultipartBucket, uploadIDPath,
diff --git a/cmd/erasure-object.go b/cmd/erasure-object.go
index b1ef3fa54..39bf4b978 100644
--- a/cmd/erasure-object.go
+++ b/cmd/erasure-object.go
@@ -1196,15 +1196,7 @@ func (er erasureObjects) putObject(ctx context.Context, bucket string, object st
partName := "part.1"
tempErasureObj := pathJoin(uniqueID, fi.DataDir, partName)
- // Delete temporary object in the event of failure.
- // If PutObject succeeded there would be no temporary
- // object to delete.
- var online int
- defer func() {
- if online != len(onlineDisks) {
- er.deleteAll(context.Background(), minioMetaTmpBucket, tempObj)
- }
- }()
+ defer er.deleteAll(context.Background(), minioMetaTmpBucket, tempObj)
shardFileSize := erasure.ShardFileSize(data.Size())
writers := make([]io.Writer, len(onlineDisks))
@@ -1309,6 +1301,7 @@ func (er erasureObjects) putObject(ctx context.Context, bucket string, object st
Algorithm: DefaultBitrotAlgorithm,
Hash: bitrotWriterSum(w),
})
+ partsMetadata[i].Versioned = opts.Versioned || opts.VersionSuspended
}
userDefined["etag"] = r.MD5CurrentHexString()
@@ -1389,7 +1382,6 @@ func (er erasureObjects) putObject(ctx context.Context, bucket string, object st
}
fi.ReplicationState = opts.PutReplicationState()
- online = countOnlineDisks(onlineDisks)
// we are adding a new version to this object under the namespace lock, so this is the latest version.
fi.IsLatest = true
diff --git a/cmd/os-instrumented.go b/cmd/os-instrumented.go
index ddc78ad28..08e752a40 100644
--- a/cmd/os-instrumented.go
+++ b/cmd/os-instrumented.go
@@ -49,6 +49,7 @@ const (
osMetricReadDirent
osMetricFdatasync
osMetricSync
+ osMetricRename2 // Linux specific
// .... add more
osMetricLast
@@ -86,7 +87,11 @@ func (o *osMetrics) incTime(s osMetric, d time.Duration) {
o.latency[s].add(d)
}
-func osTrace(s osMetric, startTime time.Time, duration time.Duration, path string) madmin.TraceInfo {
+func osTrace(s osMetric, startTime time.Time, duration time.Duration, path string, err error) madmin.TraceInfo {
+ var errStr string
+ if err != nil {
+ errStr = err.Error()
+ }
return madmin.TraceInfo{
TraceType: madmin.TraceOS,
Time: startTime,
@@ -94,53 +99,54 @@ func osTrace(s osMetric, startTime time.Time, duration time.Duration, path strin
FuncName: "os." + s.String(),
Duration: duration,
Path: path,
+ Error: errStr,
}
}
-func updateOSMetrics(s osMetric, paths ...string) func() {
+func updateOSMetrics(s osMetric, paths ...string) func(err error) {
if globalTrace.NumSubscribers(madmin.TraceOS) == 0 {
- return globalOSMetrics.time(s)
+ return func(err error) { globalOSMetrics.time(s) }
}
startTime := time.Now()
- return func() {
+ return func(err error) {
duration := time.Since(startTime)
globalOSMetrics.incTime(s, duration)
- globalTrace.Publish(osTrace(s, startTime, duration, strings.Join(paths, " -> ")))
+ globalTrace.Publish(osTrace(s, startTime, duration, strings.Join(paths, " -> "), err))
}
}
// RemoveAll captures time taken to call the underlying os.RemoveAll
-func RemoveAll(dirPath string) error {
- defer updateOSMetrics(osMetricRemoveAll, dirPath)()
+func RemoveAll(dirPath string) (err error) {
+ defer updateOSMetrics(osMetricRemoveAll, dirPath)(err)
return os.RemoveAll(dirPath)
}
// Mkdir captures time taken to call os.Mkdir
-func Mkdir(dirPath string, mode os.FileMode) error {
- defer updateOSMetrics(osMetricMkdir, dirPath)()
+func Mkdir(dirPath string, mode os.FileMode) (err error) {
+ defer updateOSMetrics(osMetricMkdir, dirPath)(err)
return os.Mkdir(dirPath, mode)
}
// MkdirAll captures time taken to call os.MkdirAll
-func MkdirAll(dirPath string, mode os.FileMode) error {
- defer updateOSMetrics(osMetricMkdirAll, dirPath)()
+func MkdirAll(dirPath string, mode os.FileMode) (err error) {
+ defer updateOSMetrics(osMetricMkdirAll, dirPath)(err)
return osMkdirAll(dirPath, mode)
}
// Rename captures time taken to call os.Rename
-func Rename(src, dst string) error {
- defer updateOSMetrics(osMetricRename, src, dst)()
+func Rename(src, dst string) (err error) {
+ defer updateOSMetrics(osMetricRename, src, dst)(err)
return os.Rename(src, dst)
}
// OpenFile captures time taken to call os.OpenFile
-func OpenFile(name string, flag int, perm os.FileMode) (*os.File, error) {
+func OpenFile(name string, flag int, perm os.FileMode) (f *os.File, err error) {
switch flag & writeMode {
case writeMode:
- defer updateOSMetrics(osMetricOpenFileW, name)()
+ defer updateOSMetrics(osMetricOpenFileW, name)(err)
default:
- defer updateOSMetrics(osMetricOpenFileR, name)()
+ defer updateOSMetrics(osMetricOpenFileR, name)(err)
}
return os.OpenFile(name, flag, perm)
}
@@ -148,54 +154,54 @@ func OpenFile(name string, flag int, perm os.FileMode) (*os.File, error) {
// Access captures time taken to call syscall.Access()
// on windows, plan9 and solaris syscall.Access uses
// os.Lstat()
-func Access(name string) error {
- defer updateOSMetrics(osMetricAccess, name)()
+func Access(name string) (err error) {
+ defer updateOSMetrics(osMetricAccess, name)(err)
return access(name)
}
// Open captures time taken to call os.Open
-func Open(name string) (*os.File, error) {
- defer updateOSMetrics(osMetricOpen, name)()
+func Open(name string) (f *os.File, err error) {
+ defer updateOSMetrics(osMetricOpen, name)(err)
return os.Open(name)
}
// OpenFileDirectIO captures time taken to call disk.OpenFileDirectIO
-func OpenFileDirectIO(name string, flag int, perm os.FileMode) (*os.File, error) {
- defer updateOSMetrics(osMetricOpenFileDirectIO, name)()
+func OpenFileDirectIO(name string, flag int, perm os.FileMode) (f *os.File, err error) {
+ defer updateOSMetrics(osMetricOpenFileDirectIO, name)(err)
return disk.OpenFileDirectIO(name, flag, perm)
}
// Lstat captures time taken to call os.Lstat
-func Lstat(name string) (os.FileInfo, error) {
- defer updateOSMetrics(osMetricLstat, name)()
+func Lstat(name string) (info os.FileInfo, err error) {
+ defer updateOSMetrics(osMetricLstat, name)(err)
return os.Lstat(name)
}
// Remove captures time taken to call os.Remove
-func Remove(deletePath string) error {
- defer updateOSMetrics(osMetricRemove, deletePath)()
+func Remove(deletePath string) (err error) {
+ defer updateOSMetrics(osMetricRemove, deletePath)(err)
return os.Remove(deletePath)
}
// Stat captures time taken to call os.Stat
-func Stat(name string) (os.FileInfo, error) {
- defer updateOSMetrics(osMetricStat, name)()
+func Stat(name string) (info os.FileInfo, err error) {
+ defer updateOSMetrics(osMetricStat, name)(err)
return os.Stat(name)
}
// Create captures time taken to call os.Create
-func Create(name string) (*os.File, error) {
- defer updateOSMetrics(osMetricCreate, name)()
+func Create(name string) (f *os.File, err error) {
+ defer updateOSMetrics(osMetricCreate, name)(err)
return os.Create(name)
}
// Fdatasync captures time taken to call Fdatasync
-func Fdatasync(f *os.File) error {
+func Fdatasync(f *os.File) (err error) {
fn := ""
if f != nil {
fn = f.Name()
}
- defer updateOSMetrics(osMetricFdatasync, fn)()
+ defer updateOSMetrics(osMetricFdatasync, fn)(err)
return disk.Fdatasync(f)
}
diff --git a/cmd/os-rename_linux.go b/cmd/os-rename_linux.go
new file mode 100644
index 000000000..533fb35eb
--- /dev/null
+++ b/cmd/os-rename_linux.go
@@ -0,0 +1,31 @@
+//go:build linux
+// +build linux
+
+// Copyright (c) 2015-2023 MinIO, Inc.
+//
+// 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 .
+
+package cmd
+
+import (
+ "golang.org/x/sys/unix"
+)
+
+// Rename2 captures time taken to call os.Rename
+func Rename2(src, dst string) (err error) {
+ defer updateOSMetrics(osMetricRename2, src, dst)(err)
+ return unix.Renameat2(unix.AT_FDCWD, src, unix.AT_FDCWD, dst, uint(2)) // RENAME_EXCHANGE from 'man renameat2'
+}
diff --git a/cmd/os-rename_nolinux.go b/cmd/os-rename_nolinux.go
new file mode 100644
index 000000000..d4a3284b8
--- /dev/null
+++ b/cmd/os-rename_nolinux.go
@@ -0,0 +1,29 @@
+//go:build !linux
+// +build !linux
+
+// Copyright (c) 2015-2023 MinIO, Inc.
+//
+// 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 .
+
+package cmd
+
+import "errors"
+
+// Rename captures time taken to call os.Rename
+func Rename2(src, dst string) (err error) {
+ defer updateOSMetrics(osMetricRename2, src, dst)(errors.New("not implemented, skipping"))
+ return errSkipFile
+}
diff --git a/cmd/osmetric_string.go b/cmd/osmetric_string.go
index 38ac65bab..6b05c6151 100644
--- a/cmd/osmetric_string.go
+++ b/cmd/osmetric_string.go
@@ -24,12 +24,13 @@ func _() {
_ = x[osMetricReadDirent-13]
_ = x[osMetricFdatasync-14]
_ = x[osMetricSync-15]
- _ = x[osMetricLast-16]
+ _ = x[osMetricRename2-16]
+ _ = x[osMetricLast-17]
}
-const _osMetric_name = "RemoveAllMkdirAllMkdirRenameOpenFileWOpenFileROpenOpenFileDirectIOLstatRemoveStatAccessCreateReadDirentFdatasyncSyncLast"
+const _osMetric_name = "RemoveAllMkdirAllMkdirRenameOpenFileWOpenFileROpenOpenFileDirectIOLstatRemoveStatAccessCreateReadDirentFdatasyncSyncRename2Last"
-var _osMetric_index = [...]uint8{0, 9, 17, 22, 28, 37, 46, 50, 66, 71, 77, 81, 87, 93, 103, 112, 116, 120}
+var _osMetric_index = [...]uint8{0, 9, 17, 22, 28, 37, 46, 50, 66, 71, 77, 81, 87, 93, 103, 112, 116, 123, 127}
func (i osMetric) String() string {
if i >= osMetric(len(_osMetric_index)-1) {
diff --git a/cmd/storage-datatypes.go b/cmd/storage-datatypes.go
index 1528f0dcc..96ebaa227 100644
--- a/cmd/storage-datatypes.go
+++ b/cmd/storage-datatypes.go
@@ -235,6 +235,9 @@ type FileInfo struct {
// Combined checksum when object was uploaded.
Checksum []byte `msg:"cs,allownil"`
+
+ // Versioned - indicates if this file is versioned or not.
+ Versioned bool `msg:"vs"`
}
// WriteQuorum returns expected write quorum for this FileInfo
diff --git a/cmd/storage-datatypes_gen.go b/cmd/storage-datatypes_gen.go
index df4a1f0c5..f590df115 100644
--- a/cmd/storage-datatypes_gen.go
+++ b/cmd/storage-datatypes_gen.go
@@ -669,8 +669,8 @@ func (z *FileInfo) DecodeMsg(dc *msgp.Reader) (err error) {
err = msgp.WrapError(err)
return
}
- if zb0001 != 28 {
- err = msgp.ArrayError{Wanted: 28, Got: zb0001}
+ if zb0001 != 29 {
+ err = msgp.ArrayError{Wanted: 29, Got: zb0001}
return
}
z.Volume, err = dc.ReadString()
@@ -850,13 +850,18 @@ func (z *FileInfo) DecodeMsg(dc *msgp.Reader) (err error) {
err = msgp.WrapError(err, "Checksum")
return
}
+ z.Versioned, err = dc.ReadBool()
+ if err != nil {
+ err = msgp.WrapError(err, "Versioned")
+ return
+ }
return
}
// EncodeMsg implements msgp.Encodable
func (z *FileInfo) EncodeMsg(en *msgp.Writer) (err error) {
- // array header, size 28
- err = en.Append(0xdc, 0x0, 0x1c)
+ // array header, size 29
+ err = en.Append(0xdc, 0x0, 0x1d)
if err != nil {
return
}
@@ -1019,14 +1024,19 @@ func (z *FileInfo) EncodeMsg(en *msgp.Writer) (err error) {
err = msgp.WrapError(err, "Checksum")
return
}
+ err = en.WriteBool(z.Versioned)
+ if err != nil {
+ err = msgp.WrapError(err, "Versioned")
+ return
+ }
return
}
// MarshalMsg implements msgp.Marshaler
func (z *FileInfo) MarshalMsg(b []byte) (o []byte, err error) {
o = msgp.Require(b, z.Msgsize())
- // array header, size 28
- o = append(o, 0xdc, 0x0, 0x1c)
+ // array header, size 29
+ o = append(o, 0xdc, 0x0, 0x1d)
o = msgp.AppendString(o, z.Volume)
o = msgp.AppendString(o, z.Name)
o = msgp.AppendString(o, z.VersionID)
@@ -1074,6 +1084,7 @@ func (z *FileInfo) MarshalMsg(b []byte) (o []byte, err error) {
o = msgp.AppendInt(o, z.Idx)
o = msgp.AppendTime(o, z.DiskMTime)
o = msgp.AppendBytes(o, z.Checksum)
+ o = msgp.AppendBool(o, z.Versioned)
return
}
@@ -1085,8 +1096,8 @@ func (z *FileInfo) UnmarshalMsg(bts []byte) (o []byte, err error) {
err = msgp.WrapError(err)
return
}
- if zb0001 != 28 {
- err = msgp.ArrayError{Wanted: 28, Got: zb0001}
+ if zb0001 != 29 {
+ err = msgp.ArrayError{Wanted: 29, Got: zb0001}
return
}
z.Volume, bts, err = msgp.ReadStringBytes(bts)
@@ -1266,6 +1277,11 @@ func (z *FileInfo) UnmarshalMsg(bts []byte) (o []byte, err error) {
err = msgp.WrapError(err, "Checksum")
return
}
+ z.Versioned, bts, err = msgp.ReadBoolBytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "Versioned")
+ return
+ }
o = bts
return
}
@@ -1283,7 +1299,7 @@ func (z *FileInfo) Msgsize() (s int) {
for za0003 := range z.Parts {
s += z.Parts[za0003].Msgsize()
}
- s += z.Erasure.Msgsize() + msgp.BoolSize + z.ReplicationState.Msgsize() + msgp.BytesPrefixSize + len(z.Data) + msgp.IntSize + msgp.TimeSize + msgp.BoolSize + msgp.IntSize + msgp.TimeSize + msgp.BytesPrefixSize + len(z.Checksum)
+ s += z.Erasure.Msgsize() + msgp.BoolSize + z.ReplicationState.Msgsize() + msgp.BytesPrefixSize + len(z.Data) + msgp.IntSize + msgp.TimeSize + msgp.BoolSize + msgp.IntSize + msgp.TimeSize + msgp.BytesPrefixSize + len(z.Checksum) + msgp.BoolSize
return
}
diff --git a/cmd/xl-storage.go b/cmd/xl-storage.go
index 9772afc82..388085b4b 100644
--- a/cmd/xl-storage.go
+++ b/cmd/xl-storage.go
@@ -2485,6 +2485,13 @@ func (s *xlStorage) RenameData(ctx context.Context, srcVolume, srcPath string, f
}
diskHealthCheckOK(ctx, err)
+ if !fi.Versioned && !fi.Healing() {
+ // Use https://man7.org/linux/man-pages/man2/rename.2.html if possible on unversioned bucket.
+ if err := Rename2(pathutil.Join(srcVolumeDir, srcPath), pathutil.Join(dstVolumeDir, dstPath)); err == nil {
+ return sign, nil
+ } // if Rename2 is not successful fallback.
+ }
+
// renameAll only for objects that have xl.meta not saved inline.
if len(fi.Data) == 0 && fi.Size > 0 {
s.moveToTrash(dstDataPath, true, false)