2021-04-18 15:41:13 -04:00
|
|
|
// Copyright (c) 2015-2021 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 <http://www.gnu.org/licenses/>.
|
2015-10-16 14:26:01 -04:00
|
|
|
|
2016-08-18 19:23:42 -04:00
|
|
|
package cmd
|
2015-10-16 14:26:01 -04:00
|
|
|
|
|
|
|
import (
|
2020-06-12 13:28:21 -04:00
|
|
|
"bytes"
|
2018-04-05 18:04:40 -04:00
|
|
|
"context"
|
2019-11-22 12:50:17 -05:00
|
|
|
"crypto/rand"
|
|
|
|
"encoding/hex"
|
2019-09-11 13:21:43 -04:00
|
|
|
"errors"
|
2020-06-12 23:04:01 -04:00
|
|
|
"fmt"
|
2016-04-08 13:37:38 -04:00
|
|
|
"io"
|
2016-06-25 17:51:06 -04:00
|
|
|
"io/ioutil"
|
2020-08-25 13:55:15 -04:00
|
|
|
"net/url"
|
2016-04-08 20:13:16 -04:00
|
|
|
"os"
|
2021-02-08 13:15:12 -05:00
|
|
|
pathutil "path"
|
2016-05-28 18:13:15 -04:00
|
|
|
"path/filepath"
|
2016-06-20 09:18:47 -04:00
|
|
|
"runtime"
|
2018-02-20 18:33:26 -05:00
|
|
|
"strings"
|
2016-10-26 20:14:05 -04:00
|
|
|
"sync"
|
2021-10-02 12:31:05 -04:00
|
|
|
"sync/atomic"
|
2021-08-21 03:12:29 -04:00
|
|
|
"syscall"
|
2018-05-23 06:11:29 -04:00
|
|
|
"time"
|
2015-10-16 14:26:01 -04:00
|
|
|
|
2020-09-12 03:08:12 -04:00
|
|
|
"github.com/dustin/go-humanize"
|
2019-10-25 13:37:53 -04:00
|
|
|
jsoniter "github.com/json-iterator/go"
|
2021-06-01 17:59:40 -04:00
|
|
|
"github.com/minio/minio/internal/bucket/lifecycle"
|
|
|
|
"github.com/minio/minio/internal/color"
|
|
|
|
"github.com/minio/minio/internal/config"
|
|
|
|
"github.com/minio/minio/internal/disk"
|
|
|
|
xioutil "github.com/minio/minio/internal/ioutil"
|
|
|
|
"github.com/minio/minio/internal/logger"
|
2021-05-28 18:17:01 -04:00
|
|
|
"github.com/minio/pkg/console"
|
|
|
|
"github.com/minio/pkg/env"
|
2021-10-01 14:50:00 -04:00
|
|
|
"github.com/yargevad/filepathx"
|
2015-10-16 14:26:01 -04:00
|
|
|
)
|
|
|
|
|
2016-04-08 13:37:38 -04:00
|
|
|
const (
|
2021-09-29 19:40:28 -04:00
|
|
|
nullVersionID = "null"
|
2021-05-15 15:56:58 -04:00
|
|
|
// Really large streams threshold per shard.
|
|
|
|
reallyLargeFileThreshold = 64 * humanize.MiByte // Optimized for HDDs
|
|
|
|
|
2021-01-12 13:20:39 -05:00
|
|
|
// Small file threshold below which data accompanies metadata from storage layer.
|
|
|
|
smallFileThreshold = 128 * humanize.KiByte // Optimized for NVMe/SSDs
|
|
|
|
// For hardrives it is possible to set this to a lower value to avoid any
|
|
|
|
// spike in latency. But currently we are simply keeping it optimal for SSDs.
|
2021-01-07 22:27:31 -05:00
|
|
|
|
2020-06-12 23:04:01 -04:00
|
|
|
// XL metadata file carries per object metadata.
|
|
|
|
xlStorageFormatFile = "xl.meta"
|
2016-04-08 13:37:38 -04:00
|
|
|
)
|
|
|
|
|
2021-03-18 17:09:55 -04:00
|
|
|
var alignedBuf []byte
|
|
|
|
|
|
|
|
func init() {
|
2021-09-29 19:40:28 -04:00
|
|
|
alignedBuf = disk.AlignedBlock(xioutil.DirectioAlignSize)
|
2021-03-18 17:09:55 -04:00
|
|
|
_, _ = rand.Read(alignedBuf)
|
|
|
|
}
|
|
|
|
|
2018-06-07 03:01:40 -04:00
|
|
|
// isValidVolname verifies a volname name in accordance with object
|
|
|
|
// layer requirements.
|
|
|
|
func isValidVolname(volname string) bool {
|
|
|
|
if len(volname) < 3 {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
// Volname shouldn't have reserved characters in Windows.
|
|
|
|
return !strings.ContainsAny(volname, `\:*?\"<>|`)
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2020-06-12 23:04:01 -04:00
|
|
|
// xlStorage - implements StorageAPI interface.
|
|
|
|
type xlStorage struct {
|
2019-10-25 13:37:53 -04:00
|
|
|
diskPath string
|
2020-09-28 22:39:32 -04:00
|
|
|
endpoint Endpoint
|
2020-05-19 17:27:20 -04:00
|
|
|
|
2020-08-04 17:55:53 -04:00
|
|
|
globalSync bool
|
|
|
|
|
2020-08-18 17:37:26 -04:00
|
|
|
rootDisk bool
|
2018-06-27 21:59:38 -04:00
|
|
|
|
2019-10-25 13:37:53 -04:00
|
|
|
diskID string
|
|
|
|
|
2021-03-04 17:36:23 -05:00
|
|
|
// Indexes, will be -1 until assigned a set.
|
|
|
|
poolIndex, setIndex, diskIndex int
|
|
|
|
|
posix: cache disk ID for a short while (#8564)
`*posix.getDiskID()` takes up to 30% of all CPU due to the `os.Stat` call on `GET` calls.
Before:
```
Operation: GET - Concurrency: 12
* Average: 1333.97 MB/s, 1365.99 obj/s, 1365.98 ops ended/s (4m59.975s)
* First Byte: Average: 7.801487ms, Median: 7.9974ms, Best: 1.9822ms, Worst: 110.0021ms
Aggregated, split into 299 x 1s time segments:
* Fastest: 1453.50 MB/s, 1488.38 obj/s, 1492.00 ops ended/s (1s)
* 50% Median: 1360.47 MB/s, 1393.12 obj/s, 1393.00 ops ended/s (1s)
* Slowest: 978.68 MB/s, 1002.17 obj/s, 1004.00 ops ended/s (1s)
```
After:
```
Operation: GET - Concurrency: 12
* Average: 1706.07 MB/s, 1747.02 obj/s, 1747.01 ops ended/s (4m59.985s)
* First Byte: Average: 5.797886ms, Median: 5.9959ms, Best: 996.3µs, Worst: 84.0007ms
Aggregated, split into 299 x 1s time segments:
* Fastest: 1830.03 MB/s, 1873.96 obj/s, 1872.00 ops ended/s (1s)
* 50% Median: 1735.04 MB/s, 1776.68 obj/s, 1776.00 ops ended/s (1s)
* Slowest: 994.94 MB/s, 1018.82 obj/s, 1018.00 ops ended/s (1s)
```
TLDR; `os.Stat` is not free.
2019-11-29 05:57:14 -05:00
|
|
|
formatFileInfo os.FileInfo
|
2021-01-11 05:27:04 -05:00
|
|
|
formatLegacy bool
|
posix: cache disk ID for a short while (#8564)
`*posix.getDiskID()` takes up to 30% of all CPU due to the `os.Stat` call on `GET` calls.
Before:
```
Operation: GET - Concurrency: 12
* Average: 1333.97 MB/s, 1365.99 obj/s, 1365.98 ops ended/s (4m59.975s)
* First Byte: Average: 7.801487ms, Median: 7.9974ms, Best: 1.9822ms, Worst: 110.0021ms
Aggregated, split into 299 x 1s time segments:
* Fastest: 1453.50 MB/s, 1488.38 obj/s, 1492.00 ops ended/s (1s)
* 50% Median: 1360.47 MB/s, 1393.12 obj/s, 1393.00 ops ended/s (1s)
* Slowest: 978.68 MB/s, 1002.17 obj/s, 1004.00 ops ended/s (1s)
```
After:
```
Operation: GET - Concurrency: 12
* Average: 1706.07 MB/s, 1747.02 obj/s, 1747.01 ops ended/s (4m59.985s)
* First Byte: Average: 5.797886ms, Median: 5.9959ms, Best: 996.3µs, Worst: 84.0007ms
Aggregated, split into 299 x 1s time segments:
* Fastest: 1830.03 MB/s, 1873.96 obj/s, 1872.00 ops ended/s (1s)
* 50% Median: 1735.04 MB/s, 1776.68 obj/s, 1776.00 ops ended/s (1s)
* Slowest: 994.94 MB/s, 1018.82 obj/s, 1018.00 ops ended/s (1s)
```
TLDR; `os.Stat` is not free.
2019-11-29 05:57:14 -05:00
|
|
|
formatLastCheck time.Time
|
2019-10-25 13:37:53 -04:00
|
|
|
|
2020-09-29 12:54:41 -04:00
|
|
|
diskInfoCache timedValue
|
2019-10-25 13:37:53 -04:00
|
|
|
sync.RWMutex
|
2021-08-18 21:10:36 -04:00
|
|
|
|
|
|
|
// mutex to prevent concurrent read operations overloading walks.
|
2021-09-17 15:14:12 -04:00
|
|
|
walkMu sync.Mutex
|
|
|
|
walkReadMu sync.Mutex
|
2016-03-28 12:52:09 -04:00
|
|
|
}
|
|
|
|
|
2016-05-11 15:55:02 -04:00
|
|
|
// checkPathLength - returns error if given path name length more than 255
|
|
|
|
func checkPathLength(pathName string) error {
|
2016-07-03 14:17:08 -04:00
|
|
|
// Apple OS X path length is limited to 1016
|
|
|
|
if runtime.GOOS == "darwin" && len(pathName) > 1016 {
|
|
|
|
return errFileNameTooLong
|
|
|
|
}
|
|
|
|
|
2020-04-28 20:32:46 -04:00
|
|
|
// Disallow more than 1024 characters on windows, there
|
|
|
|
// are no known name_max limits on Windows.
|
2020-05-14 02:55:38 -04:00
|
|
|
if runtime.GOOS == "windows" && len(pathName) > 1024 {
|
2020-05-04 16:11:56 -04:00
|
|
|
return errFileNameTooLong
|
2018-11-26 00:05:14 -05:00
|
|
|
}
|
2016-10-31 12:34:44 -04:00
|
|
|
|
2020-04-28 20:32:46 -04:00
|
|
|
// On Unix we reject paths if they are just '.', '..' or '/'
|
|
|
|
if pathName == "." || pathName == ".." || pathName == slashSeparator {
|
|
|
|
return errFileAccessDenied
|
|
|
|
}
|
2016-05-11 15:55:02 -04:00
|
|
|
|
2020-04-28 20:32:46 -04:00
|
|
|
// Check each path segment length is > 255 on all Unix
|
|
|
|
// platforms, look for this value as NAME_MAX in
|
|
|
|
// /usr/include/linux/limits.h
|
|
|
|
var count int64
|
|
|
|
for _, p := range pathName {
|
|
|
|
switch p {
|
|
|
|
case '/':
|
|
|
|
count = 0 // Reset
|
2020-05-14 02:55:38 -04:00
|
|
|
case '\\':
|
|
|
|
if runtime.GOOS == globalWindowsOSName {
|
|
|
|
count = 0
|
|
|
|
}
|
2020-04-28 20:32:46 -04:00
|
|
|
default:
|
|
|
|
count++
|
|
|
|
if count > 255 {
|
|
|
|
return errFileNameTooLong
|
|
|
|
}
|
2016-05-11 15:55:02 -04:00
|
|
|
}
|
2016-06-13 05:53:09 -04:00
|
|
|
} // Success.
|
2016-05-11 15:55:02 -04:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-01-22 18:38:21 -05:00
|
|
|
func getValidPath(path string) (string, error) {
|
2018-04-09 23:56:09 -04:00
|
|
|
if path == "" {
|
|
|
|
return path, errInvalidArgument
|
|
|
|
}
|
|
|
|
|
|
|
|
var err error
|
|
|
|
// Disallow relative paths, figure out absolute paths.
|
|
|
|
path, err = filepath.Abs(path)
|
|
|
|
if err != nil {
|
|
|
|
return path, err
|
|
|
|
}
|
|
|
|
|
2021-03-23 17:51:27 -04:00
|
|
|
fi, err := Lstat(path)
|
2020-11-23 11:36:49 -05:00
|
|
|
if err != nil && !osIsNotExist(err) {
|
2018-04-09 23:56:09 -04:00
|
|
|
return path, err
|
|
|
|
}
|
2020-11-23 11:36:49 -05:00
|
|
|
if osIsNotExist(err) {
|
2018-04-09 23:56:09 -04:00
|
|
|
// Disk not found create it.
|
2022-01-02 12:15:06 -05:00
|
|
|
if err = mkdirAll(path, 0o777); err != nil {
|
2018-04-09 23:56:09 -04:00
|
|
|
return path, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if fi != nil && !fi.IsDir() {
|
2020-06-12 23:04:01 -04:00
|
|
|
return path, errDiskNotDir
|
2018-04-09 23:56:09 -04:00
|
|
|
}
|
|
|
|
|
2018-06-12 19:36:31 -04:00
|
|
|
return path, nil
|
2018-04-09 23:56:09 -04:00
|
|
|
}
|
|
|
|
|
2016-04-08 13:37:38 -04:00
|
|
|
// isDirEmpty - returns whether given directory is empty or not.
|
2016-05-08 04:58:05 -04:00
|
|
|
func isDirEmpty(dirname string) bool {
|
2020-11-04 16:05:21 -05:00
|
|
|
entries, err := readDirN(dirname, 1)
|
2016-05-08 04:58:05 -04:00
|
|
|
if err != nil {
|
2020-11-04 16:05:21 -05:00
|
|
|
if err != errFileNotFound {
|
2020-04-09 12:30:02 -04:00
|
|
|
logger.LogIf(GlobalContext, err)
|
2017-08-04 13:43:51 -04:00
|
|
|
}
|
2016-05-08 04:58:05 -04:00
|
|
|
return false
|
2016-04-08 13:37:38 -04:00
|
|
|
}
|
2020-11-04 16:05:21 -05:00
|
|
|
return len(entries) == 0
|
2016-02-18 03:38:58 -05:00
|
|
|
}
|
|
|
|
|
2016-04-08 13:37:38 -04:00
|
|
|
// Initialize a new storage disk.
|
2020-08-25 13:55:15 -04:00
|
|
|
func newLocalXLStorage(path string) (*xlStorage, error) {
|
|
|
|
u := url.URL{Path: path}
|
|
|
|
return newXLStorage(Endpoint{
|
|
|
|
URL: &u,
|
|
|
|
IsLocal: true,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-06-16 17:26:26 -04:00
|
|
|
// Sanitize - sanitizes the `format.json`, cleanup tmp.
|
|
|
|
// all other future cleanups should be added here.
|
|
|
|
func (s *xlStorage) Sanitize() error {
|
|
|
|
if err := formatErasureMigrate(s.diskPath); err != nil && !errors.Is(err, os.ErrNotExist) {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return formatErasureCleanupTmp(s.diskPath)
|
|
|
|
}
|
|
|
|
|
2020-08-25 13:55:15 -04:00
|
|
|
// Initialize a new storage disk.
|
|
|
|
func newXLStorage(ep Endpoint) (*xlStorage, error) {
|
|
|
|
path := ep.Path
|
2018-04-09 23:56:09 -04:00
|
|
|
var err error
|
2021-01-22 18:38:21 -05:00
|
|
|
if path, err = getValidPath(path); err != nil {
|
2016-06-13 05:53:09 -04:00
|
|
|
return nil, err
|
|
|
|
}
|
2020-06-12 23:04:01 -04:00
|
|
|
|
2020-12-12 19:10:07 -05:00
|
|
|
var rootDisk bool
|
|
|
|
if env.Get("MINIO_CI_CD", "") != "" {
|
|
|
|
rootDisk = true
|
|
|
|
} else {
|
2021-05-08 18:40:29 -04:00
|
|
|
rootDisk, err = disk.IsRootDisk(path, SlashSeparator)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if !rootDisk {
|
|
|
|
// If for some reason we couldn't detect the
|
|
|
|
// root disk use - MINIO_ROOTDISK_THRESHOLD_SIZE
|
|
|
|
// to figure out if the disk is root disk or not.
|
|
|
|
if rootDiskSize := env.Get(config.EnvRootDiskThresholdSize, ""); rootDiskSize != "" {
|
|
|
|
info, err := disk.GetInfo(path)
|
2021-02-22 13:32:21 -05:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2021-05-08 18:40:29 -04:00
|
|
|
size, err := humanize.ParseBytes(rootDiskSize)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
// size of the disk is less than the threshold or
|
|
|
|
// equal to the size of the disk at path, treat
|
|
|
|
// such disks as rootDisks and reject them.
|
|
|
|
rootDisk = info.Total <= size
|
2021-02-22 13:32:21 -05:00
|
|
|
}
|
2020-12-12 19:10:07 -05:00
|
|
|
}
|
2020-08-18 17:37:26 -04:00
|
|
|
}
|
|
|
|
|
2020-06-12 23:04:01 -04:00
|
|
|
p := &xlStorage{
|
2021-05-17 20:49:48 -04:00
|
|
|
diskPath: path,
|
|
|
|
endpoint: ep,
|
2021-03-18 23:16:50 -04:00
|
|
|
globalSync: env.Get(config.EnvFSOSync, config.EnableOff) == config.EnableOn,
|
|
|
|
rootDisk: rootDisk,
|
|
|
|
poolIndex: -1,
|
|
|
|
setIndex: -1,
|
|
|
|
diskIndex: -1,
|
2018-06-27 21:59:38 -04:00
|
|
|
}
|
2018-05-23 06:11:29 -04:00
|
|
|
|
2021-01-22 18:38:21 -05:00
|
|
|
// Create all necessary bucket folders if possible.
|
2021-07-27 15:55:56 -04:00
|
|
|
if err = p.MakeVolBulk(context.TODO(), minioMetaBucket, minioMetaTmpBucket, minioMetaMultipartBucket, dataUsageBucket, minioMetaSpeedTestBucket); err != nil {
|
2021-01-22 18:38:21 -05:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if backend is writable and supports O_DIRECT
|
|
|
|
var rnd [8]byte
|
|
|
|
_, _ = rand.Read(rnd[:])
|
|
|
|
tmpFile := ".writable-check-" + hex.EncodeToString(rnd[:]) + ".tmp"
|
2021-03-18 17:09:55 -04:00
|
|
|
filePath := pathJoin(p.diskPath, minioMetaTmpBucket, tmpFile)
|
2022-01-02 12:15:06 -05:00
|
|
|
w, err := OpenFileDirectIO(filePath, os.O_CREATE|os.O_WRONLY|os.O_EXCL, 0o666)
|
2021-03-18 17:09:55 -04:00
|
|
|
if err != nil {
|
2021-12-03 12:25:17 -05:00
|
|
|
switch {
|
|
|
|
case isSysErrInvalidArg(err):
|
|
|
|
return p, errUnsupportedDisk
|
|
|
|
case osIsPermission(err):
|
|
|
|
return p, errDiskAccessDenied
|
|
|
|
case isSysErrIO(err):
|
|
|
|
return p, errFaultyDisk
|
|
|
|
case isSysErrNotDir(err):
|
|
|
|
return p, errDiskNotDir
|
|
|
|
}
|
2021-03-18 17:09:55 -04:00
|
|
|
return p, err
|
|
|
|
}
|
2021-11-16 12:28:29 -05:00
|
|
|
if _, err = w.Write(alignedBuf); err != nil {
|
2021-03-18 17:09:55 -04:00
|
|
|
w.Close()
|
2021-12-03 12:25:17 -05:00
|
|
|
if isSysErrInvalidArg(err) {
|
|
|
|
return p, errUnsupportedDisk
|
|
|
|
}
|
2021-01-22 18:38:21 -05:00
|
|
|
return p, err
|
|
|
|
}
|
2021-03-18 17:09:55 -04:00
|
|
|
w.Close()
|
2021-06-05 12:10:32 -04:00
|
|
|
Remove(filePath)
|
|
|
|
|
2017-07-10 21:14:48 -04:00
|
|
|
// Success.
|
2018-06-06 04:51:56 -04:00
|
|
|
return p, nil
|
2016-04-08 13:37:38 -04:00
|
|
|
}
|
2016-01-25 02:03:38 -05:00
|
|
|
|
2016-06-29 14:25:35 -04:00
|
|
|
// getDiskInfo returns given disk information.
|
|
|
|
func getDiskInfo(diskPath string) (di disk.Info, err error) {
|
|
|
|
if err = checkPathLength(diskPath); err == nil {
|
|
|
|
di, err = disk.GetInfo(diskPath)
|
|
|
|
}
|
|
|
|
|
2019-07-25 16:35:27 -04:00
|
|
|
switch {
|
2020-11-23 11:36:49 -05:00
|
|
|
case osIsNotExist(err):
|
2016-06-29 14:25:35 -04:00
|
|
|
err = errDiskNotFound
|
2019-07-25 16:35:27 -04:00
|
|
|
case isSysErrTooLong(err):
|
|
|
|
err = errFileNameTooLong
|
|
|
|
case isSysErrIO(err):
|
|
|
|
err = errFaultyDisk
|
2016-06-29 14:25:35 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
return di, err
|
|
|
|
}
|
|
|
|
|
2016-10-05 15:48:07 -04:00
|
|
|
// Implements stringer compatible interface.
|
2020-06-12 23:04:01 -04:00
|
|
|
func (s *xlStorage) String() string {
|
2016-10-27 06:30:52 -04:00
|
|
|
return s.diskPath
|
2016-10-05 15:48:07 -04:00
|
|
|
}
|
|
|
|
|
2020-06-12 23:04:01 -04:00
|
|
|
func (s *xlStorage) Hostname() string {
|
2020-09-28 22:39:32 -04:00
|
|
|
return s.endpoint.Host
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *xlStorage) Endpoint() Endpoint {
|
|
|
|
return s.endpoint
|
2020-01-13 16:09:10 -05:00
|
|
|
}
|
|
|
|
|
2020-06-12 23:04:01 -04:00
|
|
|
func (*xlStorage) Close() error {
|
2016-11-23 18:48:10 -05:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-06-12 23:04:01 -04:00
|
|
|
func (s *xlStorage) IsOnline() bool {
|
2019-10-25 13:37:53 -04:00
|
|
|
return true
|
2016-11-23 18:48:10 -05:00
|
|
|
}
|
|
|
|
|
2021-05-11 12:19:15 -04:00
|
|
|
func (s *xlStorage) LastConn() time.Time {
|
|
|
|
return time.Time{}
|
|
|
|
}
|
|
|
|
|
2020-06-12 23:04:01 -04:00
|
|
|
func (s *xlStorage) IsLocal() bool {
|
2020-05-19 17:27:20 -04:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2021-03-04 17:36:23 -05:00
|
|
|
// Retrieve location indexes.
|
|
|
|
func (s *xlStorage) GetDiskLoc() (poolIdx, setIdx, diskIdx int) {
|
2021-04-06 14:33:42 -04:00
|
|
|
s.RLock()
|
|
|
|
defer s.RUnlock()
|
2021-03-04 17:36:23 -05:00
|
|
|
// If unset, see if we can locate it.
|
|
|
|
if s.poolIndex < 0 || s.setIndex < 0 || s.diskIndex < 0 {
|
|
|
|
return getXLDiskLoc(s.diskID)
|
|
|
|
}
|
|
|
|
return s.poolIndex, s.setIndex, s.diskIndex
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set location indexes.
|
|
|
|
func (s *xlStorage) SetDiskLoc(poolIdx, setIdx, diskIdx int) {
|
|
|
|
s.poolIndex = poolIdx
|
|
|
|
s.setIndex = setIdx
|
|
|
|
s.diskIndex = diskIdx
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *xlStorage) Healing() *healingTracker {
|
2020-09-28 22:39:32 -04:00
|
|
|
healingFile := pathJoin(s.diskPath, minioMetaBucket,
|
|
|
|
bucketMetaPrefix, healingTrackerFilename)
|
2021-09-28 13:02:56 -04:00
|
|
|
b, err := xioutil.ReadFile(healingFile)
|
2021-03-04 17:36:23 -05:00
|
|
|
if err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
var h healingTracker
|
|
|
|
_, err = h.UnmarshalMsg(b)
|
|
|
|
logger.LogIf(GlobalContext, err)
|
|
|
|
return &h
|
2020-09-28 22:39:32 -04:00
|
|
|
}
|
|
|
|
|
2021-12-21 13:08:26 -05:00
|
|
|
// readsMetadata and returns disk mTime information for xl.meta
|
|
|
|
func (s *xlStorage) readMetadataWithDMTime(ctx context.Context, itemPath string) ([]byte, time.Time, error) {
|
2021-09-17 17:11:01 -04:00
|
|
|
if contextCanceled(ctx) {
|
2021-12-21 13:08:26 -05:00
|
|
|
return nil, time.Time{}, ctx.Err()
|
2021-09-17 17:11:01 -04:00
|
|
|
}
|
|
|
|
|
2021-08-26 19:23:12 -04:00
|
|
|
if err := checkPathLength(itemPath); err != nil {
|
2021-12-21 13:08:26 -05:00
|
|
|
return nil, time.Time{}, err
|
2021-08-26 19:23:12 -04:00
|
|
|
}
|
|
|
|
|
2021-06-15 17:34:26 -04:00
|
|
|
f, err := OpenFile(itemPath, readMode, 0)
|
2021-05-21 14:41:25 -04:00
|
|
|
if err != nil {
|
2021-12-21 13:08:26 -05:00
|
|
|
return nil, time.Time{}, err
|
2021-05-21 14:41:25 -04:00
|
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
stat, err := f.Stat()
|
|
|
|
if err != nil {
|
2021-12-21 13:08:26 -05:00
|
|
|
return nil, time.Time{}, err
|
2021-05-21 14:41:25 -04:00
|
|
|
}
|
2021-08-21 03:12:29 -04:00
|
|
|
if stat.IsDir() {
|
2021-12-21 13:08:26 -05:00
|
|
|
return nil, time.Time{}, &os.PathError{
|
2021-08-21 03:12:29 -04:00
|
|
|
Op: "open",
|
|
|
|
Path: itemPath,
|
|
|
|
Err: syscall.EISDIR,
|
|
|
|
}
|
|
|
|
}
|
2021-12-21 13:08:26 -05:00
|
|
|
buf, err := readXLMetaNoData(f, stat.Size())
|
|
|
|
return buf, stat.ModTime().UTC(), err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *xlStorage) readMetadata(ctx context.Context, itemPath string) ([]byte, error) {
|
|
|
|
buf, _, err := s.readMetadataWithDMTime(ctx, itemPath)
|
|
|
|
return buf, err
|
2021-05-21 14:41:25 -04:00
|
|
|
}
|
|
|
|
|
2021-05-19 17:38:30 -04:00
|
|
|
func (s *xlStorage) NSScanner(ctx context.Context, cache dataUsageCache, updates chan<- dataUsageEntry) (dataUsageCache, error) {
|
|
|
|
// Updates must be closed before we return.
|
|
|
|
defer close(updates)
|
2020-12-27 01:58:06 -05:00
|
|
|
var lc *lifecycle.Lifecycle
|
|
|
|
var err error
|
|
|
|
|
2020-06-12 13:28:21 -04:00
|
|
|
// Check if the current bucket has a configured lifecycle policy
|
2020-12-27 01:58:06 -05:00
|
|
|
if globalLifecycleSys != nil {
|
|
|
|
lc, err = globalLifecycleSys.Get(cache.Info.Name)
|
|
|
|
if err == nil && lc.HasActiveRules("", true) {
|
|
|
|
cache.Info.lifeCycle = lc
|
|
|
|
if intDataUpdateTracker.debug {
|
2021-02-26 18:11:42 -05:00
|
|
|
console.Debugln(color.Green("scannerDisk:") + " lifecycle: Active rules found")
|
2020-12-27 01:58:06 -05:00
|
|
|
}
|
2020-12-13 15:05:54 -05:00
|
|
|
}
|
2020-06-12 13:28:21 -04:00
|
|
|
}
|
|
|
|
|
2021-06-01 22:59:11 -04:00
|
|
|
// Check if the current bucket has replication configuration
|
|
|
|
if rcfg, err := globalBucketMetadataSys.GetReplicationConfig(ctx, cache.Info.Name); err == nil {
|
|
|
|
if rcfg.HasActiveRules("", true) {
|
2021-09-18 16:31:35 -04:00
|
|
|
tgts, err := globalBucketTargetSys.ListBucketTargets(ctx, cache.Info.Name)
|
|
|
|
if err == nil {
|
|
|
|
cache.Info.replication = replicationConfig{
|
|
|
|
Config: rcfg,
|
|
|
|
remotes: tgts,
|
|
|
|
}
|
|
|
|
if intDataUpdateTracker.debug {
|
|
|
|
console.Debugln(color.Green("scannerDisk:") + " replication: Active rules found")
|
|
|
|
}
|
|
|
|
|
2021-06-01 22:59:11 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-12-27 01:58:06 -05:00
|
|
|
// return initialized object layer
|
2020-10-09 12:59:52 -04:00
|
|
|
objAPI := newObjectLayerFn()
|
2021-04-27 11:24:44 -04:00
|
|
|
// object layer not initialized, return.
|
|
|
|
if objAPI == nil {
|
|
|
|
return cache, errServerNotInitialized
|
|
|
|
}
|
2020-12-04 12:32:35 -05:00
|
|
|
|
2021-05-19 17:38:30 -04:00
|
|
|
cache.Info.updates = updates
|
2020-06-12 23:04:01 -04:00
|
|
|
|
2021-02-26 18:11:42 -05:00
|
|
|
dataUsageInfo, err := scanDataFolder(ctx, s.diskPath, cache, func(item scannerItem) (sizeSummary, error) {
|
2020-06-12 23:04:01 -04:00
|
|
|
// Look for `xl.meta/xl.json' at the leaf.
|
|
|
|
if !strings.HasSuffix(item.Path, SlashSeparator+xlStorageFormatFile) &&
|
|
|
|
!strings.HasSuffix(item.Path, SlashSeparator+xlStorageFormatFileV1) {
|
|
|
|
// if no xl.meta/xl.json found, skip the file.
|
2020-12-07 16:47:48 -05:00
|
|
|
return sizeSummary{}, errSkipFile
|
2019-12-12 09:02:37 -05:00
|
|
|
}
|
|
|
|
|
2021-09-17 17:11:01 -04:00
|
|
|
buf, err := s.readMetadata(ctx, item.Path)
|
2020-01-21 17:07:49 -05:00
|
|
|
if err != nil {
|
2020-12-13 15:05:54 -05:00
|
|
|
if intDataUpdateTracker.debug {
|
2021-02-26 18:11:42 -05:00
|
|
|
console.Debugf(color.Green("scannerBucket:")+" object path missing: %v: %w\n", item.Path, err)
|
2020-12-13 15:05:54 -05:00
|
|
|
}
|
2020-12-07 16:47:48 -05:00
|
|
|
return sizeSummary{}, errSkipFile
|
2019-12-12 09:02:37 -05:00
|
|
|
}
|
2021-08-25 11:25:26 -04:00
|
|
|
defer metaDataPoolPut(buf)
|
2019-12-12 09:02:37 -05:00
|
|
|
|
2020-06-12 23:04:01 -04:00
|
|
|
// Remove filename which is the meta file.
|
|
|
|
item.transformMetaDir()
|
|
|
|
|
2021-11-19 20:54:10 -05:00
|
|
|
fivs, err := getFileInfoVersions(buf, item.bucket, item.objectPath())
|
2020-05-24 14:19:17 -04:00
|
|
|
if err != nil {
|
2020-12-13 15:05:54 -05:00
|
|
|
if intDataUpdateTracker.debug {
|
2021-02-26 18:11:42 -05:00
|
|
|
console.Debugf(color.Green("scannerBucket:")+" reading xl.meta failed: %v: %w\n", item.Path, err)
|
2020-12-13 15:05:54 -05:00
|
|
|
}
|
2020-12-07 16:47:48 -05:00
|
|
|
return sizeSummary{}, errSkipFile
|
2020-05-24 14:19:17 -04:00
|
|
|
}
|
2020-12-07 16:47:48 -05:00
|
|
|
sizeS := sizeSummary{}
|
2021-10-23 21:38:33 -04:00
|
|
|
var noTiers bool
|
|
|
|
if noTiers = globalTierConfigMgr.Empty(); !noTiers {
|
|
|
|
sizeS.tiers = make(map[string]tierStats)
|
|
|
|
}
|
2021-10-02 12:31:05 -04:00
|
|
|
atomic.AddUint64(&globalScannerStats.accTotalObjects, 1)
|
2021-11-19 20:54:10 -05:00
|
|
|
fivs.Versions, err = item.applyVersionActions(ctx, objAPI, fivs.Versions)
|
|
|
|
if err != nil {
|
|
|
|
if intDataUpdateTracker.debug {
|
|
|
|
console.Debugf(color.Green("scannerBucket:")+" applying version actions failed: %v: %w\n", item.Path, err)
|
|
|
|
}
|
|
|
|
return sizeSummary{}, errSkipFile
|
|
|
|
}
|
2021-02-01 12:52:11 -05:00
|
|
|
for _, version := range fivs.Versions {
|
2021-10-02 12:31:05 -04:00
|
|
|
atomic.AddUint64(&globalScannerStats.accTotalVersions, 1)
|
2020-09-02 03:19:03 -04:00
|
|
|
oi := version.ToObjectInfo(item.bucket, item.objectPath())
|
2021-08-26 17:06:04 -04:00
|
|
|
sz := item.applyActions(ctx, objAPI, oi, &sizeS)
|
2021-05-11 21:36:15 -04:00
|
|
|
if !oi.DeleteMarker && sz == oi.Size {
|
|
|
|
sizeS.versions++
|
|
|
|
}
|
|
|
|
sizeS.totalSize += sz
|
2021-10-23 21:38:33 -04:00
|
|
|
|
|
|
|
// Skip tier accounting if,
|
|
|
|
// 1. no tiers configured
|
|
|
|
// 2. object version is a delete-marker or a free-version
|
|
|
|
// tracking deleted transitioned objects
|
|
|
|
switch {
|
|
|
|
case noTiers, oi.DeleteMarker, oi.TransitionedObject.FreeVersion:
|
|
|
|
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
tier := minioHotTier
|
|
|
|
if oi.TransitionedObject.Status == lifecycle.TransitionComplete {
|
|
|
|
tier = oi.TransitionedObject.Tier
|
|
|
|
}
|
|
|
|
sizeS.tiers[tier] = sizeS.tiers[tier].add(oi.tierStats())
|
2020-07-21 20:49:56 -04:00
|
|
|
}
|
2021-11-19 20:54:10 -05:00
|
|
|
|
|
|
|
// apply tier sweep action on free versions
|
|
|
|
for _, freeVersion := range fivs.FreeVersions {
|
|
|
|
oi := freeVersion.ToObjectInfo(item.bucket, item.objectPath())
|
|
|
|
item.applyTierObjSweep(ctx, objAPI, oi)
|
|
|
|
}
|
2020-12-07 16:47:48 -05:00
|
|
|
return sizeS, nil
|
2020-01-21 17:07:49 -05:00
|
|
|
})
|
2020-03-18 19:19:29 -04:00
|
|
|
if err != nil {
|
|
|
|
return dataUsageInfo, err
|
|
|
|
}
|
2020-06-12 13:28:21 -04:00
|
|
|
|
2020-03-18 19:19:29 -04:00
|
|
|
dataUsageInfo.Info.LastUpdate = time.Now()
|
2019-12-12 09:02:37 -05:00
|
|
|
return dataUsageInfo, nil
|
|
|
|
}
|
|
|
|
|
2016-08-25 20:16:34 -04:00
|
|
|
// DiskInfo provides current information about disk space usage,
|
|
|
|
// total free inodes and underlying filesystem.
|
2020-09-04 12:45:06 -04:00
|
|
|
func (s *xlStorage) DiskInfo(context.Context) (info DiskInfo, err error) {
|
2020-09-29 12:54:41 -04:00
|
|
|
s.diskInfoCache.Once.Do(func() {
|
|
|
|
s.diskInfoCache.TTL = time.Second
|
|
|
|
s.diskInfoCache.Update = func() (interface{}, error) {
|
|
|
|
dcinfo := DiskInfo{
|
|
|
|
RootDisk: s.rootDisk,
|
|
|
|
MountPath: s.diskPath,
|
|
|
|
Endpoint: s.endpoint.String(),
|
|
|
|
}
|
|
|
|
di, err := getDiskInfo(s.diskPath)
|
|
|
|
if err != nil {
|
|
|
|
return dcinfo, err
|
|
|
|
}
|
|
|
|
dcinfo.Total = di.Total
|
|
|
|
dcinfo.Free = di.Free
|
2020-09-30 01:54:02 -04:00
|
|
|
dcinfo.Used = di.Used
|
2020-12-27 01:58:06 -05:00
|
|
|
dcinfo.UsedInodes = di.Files - di.Ffree
|
2021-05-06 02:03:40 -04:00
|
|
|
dcinfo.FreeInodes = di.Ffree
|
2020-09-29 12:54:41 -04:00
|
|
|
dcinfo.FSType = di.FSType
|
|
|
|
diskID, err := s.GetDiskID()
|
|
|
|
if errors.Is(err, errUnformattedDisk) {
|
|
|
|
// if we found an unformatted disk then
|
|
|
|
// healing is automatically true.
|
|
|
|
dcinfo.Healing = true
|
|
|
|
} else {
|
|
|
|
// Check if the disk is being healed if GetDiskID
|
|
|
|
// returned any error other than fresh disk
|
2021-03-04 17:36:23 -05:00
|
|
|
dcinfo.Healing = s.Healing() != nil
|
2020-09-29 12:54:41 -04:00
|
|
|
}
|
|
|
|
dcinfo.ID = diskID
|
|
|
|
return dcinfo, err
|
|
|
|
}
|
|
|
|
})
|
2020-07-13 12:51:07 -04:00
|
|
|
|
2020-09-29 12:54:41 -04:00
|
|
|
v, err := s.diskInfoCache.Get()
|
|
|
|
info = v.(DiskInfo)
|
2020-07-13 12:51:07 -04:00
|
|
|
return info, err
|
2016-08-25 20:16:34 -04:00
|
|
|
}
|
|
|
|
|
2016-05-18 00:22:27 -04:00
|
|
|
// getVolDir - will convert incoming volume names to
|
2016-04-13 14:32:47 -04:00
|
|
|
// corresponding valid volume names on the backend in a platform
|
|
|
|
// compatible way for all operating systems. If volume is not found
|
|
|
|
// an error is generated.
|
2020-06-12 23:04:01 -04:00
|
|
|
func (s *xlStorage) getVolDir(volume string) (string, error) {
|
2018-04-23 23:27:33 -04:00
|
|
|
if volume == "" || volume == "." || volume == ".." {
|
|
|
|
return "", errVolumeNotFound
|
2016-04-13 14:32:47 -04:00
|
|
|
}
|
2016-05-05 04:39:26 -04:00
|
|
|
volumeDir := pathJoin(s.diskPath, volume)
|
2016-05-18 00:22:27 -04:00
|
|
|
return volumeDir, nil
|
2016-04-13 14:32:47 -04:00
|
|
|
}
|
2016-04-08 13:37:38 -04:00
|
|
|
|
2021-07-25 01:03:38 -04:00
|
|
|
func (s *xlStorage) checkFormatJSON() (os.FileInfo, error) {
|
2019-10-25 13:37:53 -04:00
|
|
|
formatFile := pathJoin(s.diskPath, minioMetaBucket, formatConfigFile)
|
2021-03-23 17:51:27 -04:00
|
|
|
fi, err := Lstat(formatFile)
|
2019-10-25 13:37:53 -04:00
|
|
|
if err != nil {
|
|
|
|
// If the disk is still not initialized.
|
2020-11-23 11:36:49 -05:00
|
|
|
if osIsNotExist(err) {
|
2021-03-29 11:07:23 -04:00
|
|
|
if err = Access(s.diskPath); err == nil {
|
2020-07-21 16:54:06 -04:00
|
|
|
// Disk is present but missing `format.json`
|
2021-07-25 01:03:38 -04:00
|
|
|
return nil, errUnformattedDisk
|
2020-07-13 12:51:07 -04:00
|
|
|
}
|
2020-11-23 11:36:49 -05:00
|
|
|
if osIsNotExist(err) {
|
2021-07-25 01:03:38 -04:00
|
|
|
return nil, errDiskNotFound
|
2020-11-23 11:36:49 -05:00
|
|
|
} else if osIsPermission(err) {
|
2021-07-25 01:03:38 -04:00
|
|
|
return nil, errDiskAccessDenied
|
2020-07-13 12:51:07 -04:00
|
|
|
}
|
2020-08-03 21:17:48 -04:00
|
|
|
logger.LogIf(GlobalContext, err) // log unexpected errors
|
2021-07-25 01:03:38 -04:00
|
|
|
return nil, errCorruptedFormat
|
2020-11-23 11:36:49 -05:00
|
|
|
} else if osIsPermission(err) {
|
2021-07-25 01:03:38 -04:00
|
|
|
return nil, errDiskAccessDenied
|
2020-03-27 17:48:30 -04:00
|
|
|
}
|
2020-08-03 21:17:48 -04:00
|
|
|
logger.LogIf(GlobalContext, err) // log unexpected errors
|
2021-07-25 01:03:38 -04:00
|
|
|
return nil, errCorruptedFormat
|
|
|
|
}
|
|
|
|
return fi, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetDiskID - returns the cached disk uuid
|
|
|
|
func (s *xlStorage) GetDiskID() (string, error) {
|
|
|
|
s.RLock()
|
|
|
|
diskID := s.diskID
|
|
|
|
fileInfo := s.formatFileInfo
|
|
|
|
lastCheck := s.formatLastCheck
|
|
|
|
|
|
|
|
// check if we have a valid disk ID that is less than 1 second old.
|
|
|
|
if fileInfo != nil && diskID != "" && time.Since(lastCheck) <= time.Second {
|
|
|
|
s.RUnlock()
|
|
|
|
return diskID, nil
|
|
|
|
}
|
|
|
|
s.RUnlock()
|
|
|
|
|
|
|
|
fi, err := s.checkFormatJSON()
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
2019-10-25 13:37:53 -04:00
|
|
|
}
|
|
|
|
|
2020-03-27 17:48:30 -04:00
|
|
|
if xioutil.SameFile(fi, fileInfo) && diskID != "" {
|
2021-01-11 05:27:04 -05:00
|
|
|
s.Lock()
|
2019-10-25 13:37:53 -04:00
|
|
|
// If the file has not changed, just return the cached diskID information.
|
posix: cache disk ID for a short while (#8564)
`*posix.getDiskID()` takes up to 30% of all CPU due to the `os.Stat` call on `GET` calls.
Before:
```
Operation: GET - Concurrency: 12
* Average: 1333.97 MB/s, 1365.99 obj/s, 1365.98 ops ended/s (4m59.975s)
* First Byte: Average: 7.801487ms, Median: 7.9974ms, Best: 1.9822ms, Worst: 110.0021ms
Aggregated, split into 299 x 1s time segments:
* Fastest: 1453.50 MB/s, 1488.38 obj/s, 1492.00 ops ended/s (1s)
* 50% Median: 1360.47 MB/s, 1393.12 obj/s, 1393.00 ops ended/s (1s)
* Slowest: 978.68 MB/s, 1002.17 obj/s, 1004.00 ops ended/s (1s)
```
After:
```
Operation: GET - Concurrency: 12
* Average: 1706.07 MB/s, 1747.02 obj/s, 1747.01 ops ended/s (4m59.985s)
* First Byte: Average: 5.797886ms, Median: 5.9959ms, Best: 996.3µs, Worst: 84.0007ms
Aggregated, split into 299 x 1s time segments:
* Fastest: 1830.03 MB/s, 1873.96 obj/s, 1872.00 ops ended/s (1s)
* 50% Median: 1735.04 MB/s, 1776.68 obj/s, 1776.00 ops ended/s (1s)
* Slowest: 994.94 MB/s, 1018.82 obj/s, 1018.00 ops ended/s (1s)
```
TLDR; `os.Stat` is not free.
2019-11-29 05:57:14 -05:00
|
|
|
s.formatLastCheck = time.Now()
|
2021-01-11 05:27:04 -05:00
|
|
|
s.Unlock()
|
2019-10-25 13:37:53 -04:00
|
|
|
return diskID, nil
|
2018-02-15 20:45:57 -05:00
|
|
|
}
|
2019-10-25 13:37:53 -04:00
|
|
|
|
2021-07-25 01:03:38 -04:00
|
|
|
formatFile := pathJoin(s.diskPath, minioMetaBucket, formatConfigFile)
|
2021-02-24 03:14:16 -05:00
|
|
|
b, err := xioutil.ReadFile(formatFile)
|
2016-10-31 12:34:44 -04:00
|
|
|
if err != nil {
|
2020-08-03 21:17:48 -04:00
|
|
|
// If the disk is still not initialized.
|
2020-11-23 11:36:49 -05:00
|
|
|
if osIsNotExist(err) {
|
2021-03-29 11:07:23 -04:00
|
|
|
if err = Access(s.diskPath); err == nil {
|
2020-08-03 21:17:48 -04:00
|
|
|
// Disk is present but missing `format.json`
|
|
|
|
return "", errUnformattedDisk
|
|
|
|
}
|
2020-11-23 11:36:49 -05:00
|
|
|
if osIsNotExist(err) {
|
2020-08-03 21:17:48 -04:00
|
|
|
return "", errDiskNotFound
|
2020-11-23 11:36:49 -05:00
|
|
|
} else if osIsPermission(err) {
|
2020-08-03 21:17:48 -04:00
|
|
|
return "", errDiskAccessDenied
|
|
|
|
}
|
|
|
|
logger.LogIf(GlobalContext, err) // log unexpected errors
|
|
|
|
return "", errCorruptedFormat
|
2020-11-23 11:36:49 -05:00
|
|
|
} else if osIsPermission(err) {
|
2020-08-03 21:17:48 -04:00
|
|
|
return "", errDiskAccessDenied
|
|
|
|
}
|
|
|
|
logger.LogIf(GlobalContext, err) // log unexpected errors
|
2020-03-27 17:48:30 -04:00
|
|
|
return "", errCorruptedFormat
|
2016-10-31 12:34:44 -04:00
|
|
|
}
|
2020-08-03 21:17:48 -04:00
|
|
|
|
2020-06-12 23:04:01 -04:00
|
|
|
format := &formatErasureV3{}
|
2022-01-02 12:15:06 -05:00
|
|
|
json := jsoniter.ConfigCompatibleWithStandardLibrary
|
2019-10-25 13:37:53 -04:00
|
|
|
if err = json.Unmarshal(b, &format); err != nil {
|
2020-08-03 21:17:48 -04:00
|
|
|
logger.LogIf(GlobalContext, err) // log unexpected errors
|
2020-03-27 17:48:30 -04:00
|
|
|
return "", errCorruptedFormat
|
2019-02-20 16:32:29 -05:00
|
|
|
}
|
2020-08-03 21:17:48 -04:00
|
|
|
|
2021-01-11 05:27:04 -05:00
|
|
|
s.Lock()
|
|
|
|
defer s.Unlock()
|
2020-06-12 23:04:01 -04:00
|
|
|
s.diskID = format.Erasure.This
|
2021-01-16 15:08:02 -05:00
|
|
|
s.formatLegacy = format.Erasure.DistributionAlgo == formatErasureVersionV2DistributionAlgoV1
|
2019-10-25 13:37:53 -04:00
|
|
|
s.formatFileInfo = fi
|
posix: cache disk ID for a short while (#8564)
`*posix.getDiskID()` takes up to 30% of all CPU due to the `os.Stat` call on `GET` calls.
Before:
```
Operation: GET - Concurrency: 12
* Average: 1333.97 MB/s, 1365.99 obj/s, 1365.98 ops ended/s (4m59.975s)
* First Byte: Average: 7.801487ms, Median: 7.9974ms, Best: 1.9822ms, Worst: 110.0021ms
Aggregated, split into 299 x 1s time segments:
* Fastest: 1453.50 MB/s, 1488.38 obj/s, 1492.00 ops ended/s (1s)
* 50% Median: 1360.47 MB/s, 1393.12 obj/s, 1393.00 ops ended/s (1s)
* Slowest: 978.68 MB/s, 1002.17 obj/s, 1004.00 ops ended/s (1s)
```
After:
```
Operation: GET - Concurrency: 12
* Average: 1706.07 MB/s, 1747.02 obj/s, 1747.01 ops ended/s (4m59.985s)
* First Byte: Average: 5.797886ms, Median: 5.9959ms, Best: 996.3µs, Worst: 84.0007ms
Aggregated, split into 299 x 1s time segments:
* Fastest: 1830.03 MB/s, 1873.96 obj/s, 1872.00 ops ended/s (1s)
* 50% Median: 1735.04 MB/s, 1776.68 obj/s, 1776.00 ops ended/s (1s)
* Slowest: 994.94 MB/s, 1018.82 obj/s, 1018.00 ops ended/s (1s)
```
TLDR; `os.Stat` is not free.
2019-11-29 05:57:14 -05:00
|
|
|
s.formatLastCheck = time.Now()
|
2019-10-25 13:37:53 -04:00
|
|
|
return s.diskID, nil
|
2016-10-31 12:34:44 -04:00
|
|
|
}
|
|
|
|
|
2019-10-25 13:37:53 -04:00
|
|
|
// Make a volume entry.
|
2020-06-12 23:04:01 -04:00
|
|
|
func (s *xlStorage) SetDiskID(id string) {
|
|
|
|
// NO-OP for xlStorage as it is handled either by xlStorageDiskIDCheck{} for local disks or
|
2019-10-25 13:37:53 -04:00
|
|
|
// storage rest server for remote disks.
|
|
|
|
}
|
|
|
|
|
2021-01-22 18:38:21 -05:00
|
|
|
func (s *xlStorage) MakeVolBulk(ctx context.Context, volumes ...string) error {
|
2019-12-23 19:31:03 -05:00
|
|
|
for _, volume := range volumes {
|
2021-12-09 18:55:42 -05:00
|
|
|
if err := s.MakeVol(ctx, volume); err != nil && !errors.Is(err, errVolumeExists) {
|
|
|
|
return err
|
2019-12-23 19:31:03 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-04-13 14:32:47 -04:00
|
|
|
// Make a volume entry.
|
2021-01-22 18:38:21 -05:00
|
|
|
func (s *xlStorage) MakeVol(ctx context.Context, volume string) error {
|
2018-04-23 23:27:33 -04:00
|
|
|
if !isValidVolname(volume) {
|
|
|
|
return errInvalidArgument
|
|
|
|
}
|
|
|
|
|
2016-05-18 00:22:27 -04:00
|
|
|
volumeDir, err := s.getVolDir(volume)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2016-04-08 13:37:38 -04:00
|
|
|
}
|
2018-01-29 21:43:13 -05:00
|
|
|
|
2021-03-29 11:07:23 -04:00
|
|
|
if err = Access(volumeDir); err != nil {
|
2018-01-29 21:43:13 -05:00
|
|
|
// Volume does not exist we proceed to create.
|
2020-11-23 11:36:49 -05:00
|
|
|
if osIsNotExist(err) {
|
2018-01-29 21:43:13 -05:00
|
|
|
// Make a volume entry, with mode 0777 mkdir honors system umask.
|
2022-01-02 12:15:06 -05:00
|
|
|
err = mkdirAll(volumeDir, 0o777)
|
2018-01-29 21:43:13 -05:00
|
|
|
}
|
2020-11-23 11:36:49 -05:00
|
|
|
if osIsPermission(err) {
|
2016-07-02 04:59:28 -04:00
|
|
|
return errDiskAccessDenied
|
2018-07-27 18:32:19 -04:00
|
|
|
} else if isSysErrIO(err) {
|
|
|
|
return errFaultyDisk
|
2016-07-02 04:59:28 -04:00
|
|
|
}
|
|
|
|
return err
|
2016-04-13 14:32:47 -04:00
|
|
|
}
|
2018-01-29 21:43:13 -05:00
|
|
|
|
|
|
|
// Stat succeeds we return errVolumeExists.
|
|
|
|
return errVolumeExists
|
2016-04-08 13:37:38 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// ListVols - list volumes.
|
2020-09-04 12:45:06 -04:00
|
|
|
func (s *xlStorage) ListVols(context.Context) (volsInfo []VolInfo, err error) {
|
2020-10-08 15:32:32 -04:00
|
|
|
return listVols(s.diskPath)
|
2015-10-16 14:26:01 -04:00
|
|
|
}
|
config/main: Re-write config files - add to new config v3
- New config format.
```
{
"version": "3",
"address": ":9000",
"backend": {
"type": "fs",
"disk": "/path"
},
"credential": {
"accessKey": "WLGDGYAQYIGI833EV05A",
"secretKey": "BYvgJM101sHngl2uzjXS/OBF/aMxAN06JrJ3qJlF"
},
"region": "us-east-1",
"logger": {
"file": {
"enable": false,
"fileName": "",
"level": "error"
},
"syslog": {
"enable": false,
"address": "",
"level": "debug"
},
"console": {
"enable": true,
"level": "fatal"
}
}
}
```
New command lines in lieu of supporting XL.
Minio initialize filesystem backend.
~~~
$ minio init fs <path>
~~~
Minio initialize XL backend.
~~~
$ minio init xl <url1>...<url16>
~~~
For 'fs' backend it starts the server.
~~~
$ minio server
~~~
For 'xl' backend it waits for servers to join.
~~~
$ minio server
... [PROGRESS BAR] of servers connecting
~~~
Now on other servers execute 'join' and they connect.
~~~
....
minio join <url1> -- from <url2> && minio server
minio join <url1> -- from <url3> && minio server
...
...
minio join <url1> -- from <url16> && minio server
~~~
2016-02-12 18:27:10 -05:00
|
|
|
|
2016-08-11 22:57:14 -04:00
|
|
|
// List all the volumes from diskPath.
|
|
|
|
func listVols(dirPath string) ([]VolInfo, error) {
|
|
|
|
if err := checkPathLength(dirPath); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
entries, err := readDir(dirPath)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errDiskNotFound
|
|
|
|
}
|
2020-09-14 23:44:18 -04:00
|
|
|
volsInfo := make([]VolInfo, 0, len(entries))
|
2016-08-11 22:57:14 -04:00
|
|
|
for _, entry := range entries {
|
2021-02-08 13:15:12 -05:00
|
|
|
if !HasSuffix(entry, SlashSeparator) || !isValidVolname(pathutil.Clean(entry)) {
|
2016-08-11 22:57:14 -04:00
|
|
|
// Skip if entry is neither a directory not a valid volume name.
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
volsInfo = append(volsInfo, VolInfo{
|
2021-02-08 13:15:12 -05:00
|
|
|
Name: pathutil.Clean(entry),
|
2016-08-11 22:57:14 -04:00
|
|
|
})
|
|
|
|
}
|
|
|
|
return volsInfo, nil
|
|
|
|
}
|
|
|
|
|
2016-04-08 13:37:38 -04:00
|
|
|
// StatVol - get volume info.
|
2020-09-04 12:45:06 -04:00
|
|
|
func (s *xlStorage) StatVol(ctx context.Context, volume string) (vol VolInfo, err error) {
|
2016-04-13 14:32:47 -04:00
|
|
|
// Verify if volume is valid and it exists.
|
2016-05-18 00:22:27 -04:00
|
|
|
volumeDir, err := s.getVolDir(volume)
|
2016-04-13 14:32:47 -04:00
|
|
|
if err != nil {
|
|
|
|
return VolInfo{}, err
|
2016-04-08 13:37:38 -04:00
|
|
|
}
|
2021-03-29 11:07:23 -04:00
|
|
|
|
2016-04-08 13:37:38 -04:00
|
|
|
// Stat a volume entry.
|
|
|
|
var st os.FileInfo
|
2021-03-23 17:51:27 -04:00
|
|
|
st, err = Lstat(volumeDir)
|
2016-04-08 13:37:38 -04:00
|
|
|
if err != nil {
|
fix: use buffers only when necessary for io.Copy() (#11229)
Use separate sync.Pool for writes/reads
Avoid passing buffers for io.CopyBuffer()
if the writer or reader implement io.WriteTo or io.ReadFrom
respectively then its useless for sync.Pool to allocate
buffers on its own since that will be completely ignored
by the io.CopyBuffer Go implementation.
Improve this wherever we see this to be optimal.
This allows us to be more efficient on memory usage.
```
385 // copyBuffer is the actual implementation of Copy and CopyBuffer.
386 // if buf is nil, one is allocated.
387 func copyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error) {
388 // If the reader has a WriteTo method, use it to do the copy.
389 // Avoids an allocation and a copy.
390 if wt, ok := src.(WriterTo); ok {
391 return wt.WriteTo(dst)
392 }
393 // Similarly, if the writer has a ReadFrom method, use it to do the copy.
394 if rt, ok := dst.(ReaderFrom); ok {
395 return rt.ReadFrom(src)
396 }
```
From readahead package
```
// WriteTo writes data to w until there's no more data to write or when an error occurs.
// The return value n is the number of bytes written.
// Any error encountered during the write is also returned.
func (a *reader) WriteTo(w io.Writer) (n int64, err error) {
if a.err != nil {
return 0, a.err
}
n = 0
for {
err = a.fill()
if err != nil {
return n, err
}
n2, err := w.Write(a.cur.buffer())
a.cur.inc(n2)
n += int64(n2)
if err != nil {
return n, err
}
```
2021-01-06 12:36:55 -05:00
|
|
|
switch {
|
|
|
|
case osIsNotExist(err):
|
2016-04-08 13:37:38 -04:00
|
|
|
return VolInfo{}, errVolumeNotFound
|
fix: use buffers only when necessary for io.Copy() (#11229)
Use separate sync.Pool for writes/reads
Avoid passing buffers for io.CopyBuffer()
if the writer or reader implement io.WriteTo or io.ReadFrom
respectively then its useless for sync.Pool to allocate
buffers on its own since that will be completely ignored
by the io.CopyBuffer Go implementation.
Improve this wherever we see this to be optimal.
This allows us to be more efficient on memory usage.
```
385 // copyBuffer is the actual implementation of Copy and CopyBuffer.
386 // if buf is nil, one is allocated.
387 func copyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error) {
388 // If the reader has a WriteTo method, use it to do the copy.
389 // Avoids an allocation and a copy.
390 if wt, ok := src.(WriterTo); ok {
391 return wt.WriteTo(dst)
392 }
393 // Similarly, if the writer has a ReadFrom method, use it to do the copy.
394 if rt, ok := dst.(ReaderFrom); ok {
395 return rt.ReadFrom(src)
396 }
```
From readahead package
```
// WriteTo writes data to w until there's no more data to write or when an error occurs.
// The return value n is the number of bytes written.
// Any error encountered during the write is also returned.
func (a *reader) WriteTo(w io.Writer) (n int64, err error) {
if a.err != nil {
return 0, a.err
}
n = 0
for {
err = a.fill()
if err != nil {
return n, err
}
n2, err := w.Write(a.cur.buffer())
a.cur.inc(n2)
n += int64(n2)
if err != nil {
return n, err
}
```
2021-01-06 12:36:55 -05:00
|
|
|
case osIsPermission(err):
|
|
|
|
return VolInfo{}, errDiskAccessDenied
|
|
|
|
case isSysErrIO(err):
|
2018-07-27 18:32:19 -04:00
|
|
|
return VolInfo{}, errFaultyDisk
|
fix: use buffers only when necessary for io.Copy() (#11229)
Use separate sync.Pool for writes/reads
Avoid passing buffers for io.CopyBuffer()
if the writer or reader implement io.WriteTo or io.ReadFrom
respectively then its useless for sync.Pool to allocate
buffers on its own since that will be completely ignored
by the io.CopyBuffer Go implementation.
Improve this wherever we see this to be optimal.
This allows us to be more efficient on memory usage.
```
385 // copyBuffer is the actual implementation of Copy and CopyBuffer.
386 // if buf is nil, one is allocated.
387 func copyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error) {
388 // If the reader has a WriteTo method, use it to do the copy.
389 // Avoids an allocation and a copy.
390 if wt, ok := src.(WriterTo); ok {
391 return wt.WriteTo(dst)
392 }
393 // Similarly, if the writer has a ReadFrom method, use it to do the copy.
394 if rt, ok := dst.(ReaderFrom); ok {
395 return rt.ReadFrom(src)
396 }
```
From readahead package
```
// WriteTo writes data to w until there's no more data to write or when an error occurs.
// The return value n is the number of bytes written.
// Any error encountered during the write is also returned.
func (a *reader) WriteTo(w io.Writer) (n int64, err error) {
if a.err != nil {
return 0, a.err
}
n = 0
for {
err = a.fill()
if err != nil {
return n, err
}
n2, err := w.Write(a.cur.buffer())
a.cur.inc(n2)
n += int64(n2)
if err != nil {
return n, err
}
```
2021-01-06 12:36:55 -05:00
|
|
|
default:
|
|
|
|
return VolInfo{}, err
|
2016-04-08 13:37:38 -04:00
|
|
|
}
|
2016-04-08 20:13:16 -04:00
|
|
|
}
|
2021-02-05 13:12:25 -05:00
|
|
|
// As os.Lstat() doesn't carry other than ModTime(), use ModTime()
|
2016-04-16 15:48:41 -04:00
|
|
|
// as CreatedTime.
|
2016-04-13 14:32:47 -04:00
|
|
|
createdTime := st.ModTime()
|
2016-04-08 13:37:38 -04:00
|
|
|
return VolInfo{
|
2016-04-13 14:32:47 -04:00
|
|
|
Name: volume,
|
|
|
|
Created: createdTime,
|
2016-04-08 13:37:38 -04:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// DeleteVol - delete a volume.
|
2020-09-04 12:45:06 -04:00
|
|
|
func (s *xlStorage) DeleteVol(ctx context.Context, volume string, forceDelete bool) (err error) {
|
2016-04-13 14:32:47 -04:00
|
|
|
// Verify if volume is valid and it exists.
|
2016-05-18 00:22:27 -04:00
|
|
|
volumeDir, err := s.getVolDir(volume)
|
2016-04-13 14:32:47 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
2016-04-08 13:37:38 -04:00
|
|
|
}
|
2020-03-28 00:52:59 -04:00
|
|
|
|
|
|
|
if forceDelete {
|
2021-10-12 12:24:00 -04:00
|
|
|
err = s.moveToTrash(volumeDir, true)
|
2020-03-28 00:52:59 -04:00
|
|
|
} else {
|
2021-03-23 17:51:27 -04:00
|
|
|
err = Remove(volumeDir)
|
2020-03-28 00:52:59 -04:00
|
|
|
}
|
|
|
|
|
2016-04-16 15:48:41 -04:00
|
|
|
if err != nil {
|
2018-08-06 13:26:40 -04:00
|
|
|
switch {
|
2021-10-12 12:24:00 -04:00
|
|
|
case errors.Is(err, errFileNotFound):
|
|
|
|
return errVolumeNotFound
|
2020-11-23 11:36:49 -05:00
|
|
|
case osIsNotExist(err):
|
2016-04-16 15:48:41 -04:00
|
|
|
return errVolumeNotFound
|
2018-08-06 13:26:40 -04:00
|
|
|
case isSysErrNotEmpty(err):
|
2016-04-16 15:48:41 -04:00
|
|
|
return errVolumeNotEmpty
|
2020-11-23 11:36:49 -05:00
|
|
|
case osIsPermission(err):
|
2018-04-09 23:56:09 -04:00
|
|
|
return errDiskAccessDenied
|
2018-08-06 13:26:40 -04:00
|
|
|
case isSysErrIO(err):
|
2018-07-27 18:32:19 -04:00
|
|
|
return errFaultyDisk
|
2018-08-06 13:26:40 -04:00
|
|
|
default:
|
|
|
|
return err
|
2016-04-16 15:48:41 -04:00
|
|
|
}
|
2016-04-08 13:37:38 -04:00
|
|
|
}
|
2016-03-28 00:52:38 -04:00
|
|
|
return nil
|
2016-04-08 13:37:38 -04:00
|
|
|
}
|
|
|
|
|
2016-05-05 15:51:56 -04:00
|
|
|
// ListDir - return all the entries at the given directory path.
|
2019-08-06 15:08:58 -04:00
|
|
|
// If an entry is a directory it will be returned with a trailing SlashSeparator.
|
2020-09-04 12:45:06 -04:00
|
|
|
func (s *xlStorage) ListDir(ctx context.Context, volume, dirPath string, count int) (entries []string, err error) {
|
2021-09-17 17:11:01 -04:00
|
|
|
if contextCanceled(ctx) {
|
|
|
|
return nil, ctx.Err()
|
|
|
|
}
|
|
|
|
|
2016-04-13 14:32:47 -04:00
|
|
|
// Verify if volume is valid and it exists.
|
2016-05-18 00:22:27 -04:00
|
|
|
volumeDir, err := s.getVolDir(volume)
|
2016-04-13 14:32:47 -04:00
|
|
|
if err != nil {
|
2016-05-05 15:51:56 -04:00
|
|
|
return nil, err
|
2016-04-08 13:37:38 -04:00
|
|
|
}
|
2020-04-08 22:42:57 -04:00
|
|
|
|
2020-06-17 16:58:38 -04:00
|
|
|
dirPathAbs := pathJoin(volumeDir, dirPath)
|
2018-05-08 22:08:21 -04:00
|
|
|
if count > 0 {
|
2020-06-17 16:58:38 -04:00
|
|
|
entries, err = readDirN(dirPathAbs, count)
|
2019-04-23 17:54:28 -04:00
|
|
|
} else {
|
2020-06-17 16:58:38 -04:00
|
|
|
entries, err = readDir(dirPathAbs)
|
2018-05-08 22:08:21 -04:00
|
|
|
}
|
2020-04-08 22:42:57 -04:00
|
|
|
if err != nil {
|
fix: use buffers only when necessary for io.Copy() (#11229)
Use separate sync.Pool for writes/reads
Avoid passing buffers for io.CopyBuffer()
if the writer or reader implement io.WriteTo or io.ReadFrom
respectively then its useless for sync.Pool to allocate
buffers on its own since that will be completely ignored
by the io.CopyBuffer Go implementation.
Improve this wherever we see this to be optimal.
This allows us to be more efficient on memory usage.
```
385 // copyBuffer is the actual implementation of Copy and CopyBuffer.
386 // if buf is nil, one is allocated.
387 func copyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error) {
388 // If the reader has a WriteTo method, use it to do the copy.
389 // Avoids an allocation and a copy.
390 if wt, ok := src.(WriterTo); ok {
391 return wt.WriteTo(dst)
392 }
393 // Similarly, if the writer has a ReadFrom method, use it to do the copy.
394 if rt, ok := dst.(ReaderFrom); ok {
395 return rt.ReadFrom(src)
396 }
```
From readahead package
```
// WriteTo writes data to w until there's no more data to write or when an error occurs.
// The return value n is the number of bytes written.
// Any error encountered during the write is also returned.
func (a *reader) WriteTo(w io.Writer) (n int64, err error) {
if a.err != nil {
return 0, a.err
}
n = 0
for {
err = a.fill()
if err != nil {
return n, err
}
n2, err := w.Write(a.cur.buffer())
a.cur.inc(n2)
n += int64(n2)
if err != nil {
return n, err
}
```
2021-01-06 12:36:55 -05:00
|
|
|
if err == errFileNotFound {
|
2021-03-29 11:07:23 -04:00
|
|
|
if ierr := Access(volumeDir); ierr != nil {
|
|
|
|
if osIsNotExist(ierr) {
|
fix: use buffers only when necessary for io.Copy() (#11229)
Use separate sync.Pool for writes/reads
Avoid passing buffers for io.CopyBuffer()
if the writer or reader implement io.WriteTo or io.ReadFrom
respectively then its useless for sync.Pool to allocate
buffers on its own since that will be completely ignored
by the io.CopyBuffer Go implementation.
Improve this wherever we see this to be optimal.
This allows us to be more efficient on memory usage.
```
385 // copyBuffer is the actual implementation of Copy and CopyBuffer.
386 // if buf is nil, one is allocated.
387 func copyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error) {
388 // If the reader has a WriteTo method, use it to do the copy.
389 // Avoids an allocation and a copy.
390 if wt, ok := src.(WriterTo); ok {
391 return wt.WriteTo(dst)
392 }
393 // Similarly, if the writer has a ReadFrom method, use it to do the copy.
394 if rt, ok := dst.(ReaderFrom); ok {
395 return rt.ReadFrom(src)
396 }
```
From readahead package
```
// WriteTo writes data to w until there's no more data to write or when an error occurs.
// The return value n is the number of bytes written.
// Any error encountered during the write is also returned.
func (a *reader) WriteTo(w io.Writer) (n int64, err error) {
if a.err != nil {
return 0, a.err
}
n = 0
for {
err = a.fill()
if err != nil {
return n, err
}
n2, err := w.Write(a.cur.buffer())
a.cur.inc(n2)
n += int64(n2)
if err != nil {
return n, err
}
```
2021-01-06 12:36:55 -05:00
|
|
|
return nil, errVolumeNotFound
|
2021-03-29 11:07:23 -04:00
|
|
|
} else if isSysErrIO(ierr) {
|
fix: use buffers only when necessary for io.Copy() (#11229)
Use separate sync.Pool for writes/reads
Avoid passing buffers for io.CopyBuffer()
if the writer or reader implement io.WriteTo or io.ReadFrom
respectively then its useless for sync.Pool to allocate
buffers on its own since that will be completely ignored
by the io.CopyBuffer Go implementation.
Improve this wherever we see this to be optimal.
This allows us to be more efficient on memory usage.
```
385 // copyBuffer is the actual implementation of Copy and CopyBuffer.
386 // if buf is nil, one is allocated.
387 func copyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error) {
388 // If the reader has a WriteTo method, use it to do the copy.
389 // Avoids an allocation and a copy.
390 if wt, ok := src.(WriterTo); ok {
391 return wt.WriteTo(dst)
392 }
393 // Similarly, if the writer has a ReadFrom method, use it to do the copy.
394 if rt, ok := dst.(ReaderFrom); ok {
395 return rt.ReadFrom(src)
396 }
```
From readahead package
```
// WriteTo writes data to w until there's no more data to write or when an error occurs.
// The return value n is the number of bytes written.
// Any error encountered during the write is also returned.
func (a *reader) WriteTo(w io.Writer) (n int64, err error) {
if a.err != nil {
return 0, a.err
}
n = 0
for {
err = a.fill()
if err != nil {
return n, err
}
n2, err := w.Write(a.cur.buffer())
a.cur.inc(n2)
n += int64(n2)
if err != nil {
return n, err
}
```
2021-01-06 12:36:55 -05:00
|
|
|
return nil, errFaultyDisk
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-04-08 22:42:57 -04:00
|
|
|
return nil, err
|
|
|
|
}
|
2019-04-23 17:54:28 -04:00
|
|
|
|
2020-06-12 23:04:01 -04:00
|
|
|
return entries, nil
|
|
|
|
}
|
|
|
|
|
2021-11-01 13:50:07 -04:00
|
|
|
func (s *xlStorage) deleteVersions(ctx context.Context, volume, path string, fis ...FileInfo) error {
|
|
|
|
buf, err := s.ReadAll(ctx, volume, pathJoin(path, xlStorageFormatFile))
|
|
|
|
if err != nil {
|
|
|
|
if err != errFileNotFound {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
metaDataPoolPut(buf) // Never used, return it
|
|
|
|
|
|
|
|
buf, err = s.ReadAll(ctx, volume, pathJoin(path, xlStorageFormatFileV1))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(buf) == 0 {
|
|
|
|
return errFileNotFound
|
|
|
|
}
|
|
|
|
|
|
|
|
volumeDir, err := s.getVolDir(volume)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if !isXL2V1Format(buf) {
|
|
|
|
// Delete the meta file, if there are no more versions the
|
|
|
|
// top level parent is automatically removed.
|
|
|
|
return s.deleteFile(volumeDir, pathJoin(volumeDir, path), true)
|
|
|
|
}
|
|
|
|
|
|
|
|
var xlMeta xlMetaV2
|
|
|
|
if err = xlMeta.Load(buf); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
dataDir string
|
|
|
|
lastVersion bool
|
|
|
|
)
|
|
|
|
|
|
|
|
for _, fi := range fis {
|
|
|
|
dataDir, lastVersion, err = xlMeta.DeleteVersion(fi)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if dataDir != "" {
|
|
|
|
versionID := fi.VersionID
|
|
|
|
if versionID == "" {
|
|
|
|
versionID = nullVersionID
|
|
|
|
}
|
|
|
|
// PR #11758 used DataDir, preserve it
|
|
|
|
// for users who might have used master
|
|
|
|
// branch
|
|
|
|
if !xlMeta.data.remove(versionID, dataDir) {
|
|
|
|
filePath := pathJoin(volumeDir, path, dataDir)
|
|
|
|
if err = checkPathLength(filePath); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err = s.moveToTrash(filePath, true); err != nil {
|
|
|
|
if err != errFileNotFound {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !lastVersion {
|
|
|
|
buf, err = xlMeta.AppendTo(metaDataPoolGet())
|
|
|
|
defer metaDataPoolPut(buf)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return s.WriteAll(ctx, volume, pathJoin(path, xlStorageFormatFile), buf)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Move xl.meta to trash
|
|
|
|
filePath := pathJoin(volumeDir, path, xlStorageFormatFile)
|
|
|
|
if err = checkPathLength(filePath); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = s.moveToTrash(filePath, false)
|
|
|
|
if err == nil || err == errFileNotFound {
|
|
|
|
s.deleteFile(volumeDir, pathJoin(volumeDir, path), false)
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-06-12 23:04:01 -04:00
|
|
|
// DeleteVersions deletes slice of versions, it can be same object
|
|
|
|
// or multiple objects.
|
2021-11-01 13:50:07 -04:00
|
|
|
func (s *xlStorage) DeleteVersions(ctx context.Context, volume string, versions []FileInfoVersions) []error {
|
2020-06-12 23:04:01 -04:00
|
|
|
errs := make([]error, len(versions))
|
2021-03-24 12:08:05 -04:00
|
|
|
|
2021-11-01 13:50:07 -04:00
|
|
|
for i, fiv := range versions {
|
|
|
|
if err := s.deleteVersions(ctx, volume, fiv.Name, fiv.Versions...); err != nil {
|
2020-06-12 23:04:01 -04:00
|
|
|
errs[i] = err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return errs
|
|
|
|
}
|
|
|
|
|
2021-10-12 12:24:00 -04:00
|
|
|
func (s *xlStorage) moveToTrash(filePath string, recursive bool) error {
|
|
|
|
pathUUID := mustGetUUID()
|
|
|
|
if recursive {
|
|
|
|
return renameAll(filePath, pathutil.Join(s.diskPath, minioMetaTmpDeletedBucket, pathUUID))
|
|
|
|
}
|
|
|
|
return Rename(filePath, pathutil.Join(s.diskPath, minioMetaTmpDeletedBucket, pathUUID))
|
|
|
|
}
|
|
|
|
|
2021-02-03 13:33:43 -05:00
|
|
|
// DeleteVersion - deletes FileInfo metadata for path at `xl.meta`. forceDelMarker
|
|
|
|
// will force creating a new `xl.meta` to create a new delete marker
|
|
|
|
func (s *xlStorage) DeleteVersion(ctx context.Context, volume, path string, fi FileInfo, forceDelMarker bool) error {
|
2020-06-12 23:04:01 -04:00
|
|
|
if HasSuffix(path, SlashSeparator) {
|
2020-10-28 12:18:35 -04:00
|
|
|
return s.Delete(ctx, volume, path, false)
|
2020-06-12 23:04:01 -04:00
|
|
|
}
|
|
|
|
|
2020-09-04 12:45:06 -04:00
|
|
|
buf, err := s.ReadAll(ctx, volume, pathJoin(path, xlStorageFormatFile))
|
2020-06-12 23:04:01 -04:00
|
|
|
if err != nil {
|
2021-02-01 16:23:50 -05:00
|
|
|
if err != errFileNotFound {
|
|
|
|
return err
|
2020-11-29 00:15:45 -05:00
|
|
|
}
|
2021-11-30 21:30:06 -05:00
|
|
|
metaDataPoolPut(buf) // Never used, return it
|
2021-02-03 13:33:43 -05:00
|
|
|
if fi.Deleted && forceDelMarker {
|
2021-02-01 16:23:50 -05:00
|
|
|
// Create a new xl.meta with a delete marker in it
|
|
|
|
return s.WriteMetadata(ctx, volume, path, fi)
|
|
|
|
}
|
2021-10-28 20:02:22 -04:00
|
|
|
|
2021-09-17 22:34:48 -04:00
|
|
|
buf, err = s.ReadAll(ctx, volume, pathJoin(path, xlStorageFormatFileV1))
|
|
|
|
if err != nil {
|
|
|
|
if err == errFileNotFound && fi.VersionID != "" {
|
|
|
|
return errFileVersionNotFound
|
|
|
|
}
|
|
|
|
return err
|
2021-02-01 16:23:50 -05:00
|
|
|
}
|
2020-06-12 23:04:01 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if len(buf) == 0 {
|
2020-11-29 00:15:45 -05:00
|
|
|
if fi.VersionID != "" {
|
|
|
|
return errFileVersionNotFound
|
|
|
|
}
|
2020-06-12 23:04:01 -04:00
|
|
|
return errFileNotFound
|
|
|
|
}
|
|
|
|
|
|
|
|
volumeDir, err := s.getVolDir(volume)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if !isXL2V1Format(buf) {
|
|
|
|
// Delete the meta file, if there are no more versions the
|
|
|
|
// top level parent is automatically removed.
|
2021-03-24 17:19:52 -04:00
|
|
|
return s.deleteFile(volumeDir, pathJoin(volumeDir, path), true)
|
2020-06-12 23:04:01 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
var xlMeta xlMetaV2
|
2021-11-18 15:15:22 -05:00
|
|
|
if err := xlMeta.Load(buf); err != nil {
|
2020-06-12 23:04:01 -04:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
dataDir, lastVersion, err := xlMeta.DeleteVersion(fi)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-04-19 13:30:42 -04:00
|
|
|
if dataDir != "" {
|
|
|
|
versionID := fi.VersionID
|
|
|
|
if versionID == "" {
|
|
|
|
versionID = nullVersionID
|
|
|
|
}
|
|
|
|
// PR #11758 used DataDir, preserve it
|
|
|
|
// for users who might have used master
|
|
|
|
// branch
|
2021-08-03 16:26:57 -04:00
|
|
|
if !xlMeta.data.remove(versionID, dataDir) {
|
|
|
|
filePath := pathJoin(volumeDir, path, dataDir)
|
|
|
|
if err = checkPathLength(filePath); err != nil {
|
2021-03-29 20:00:55 -04:00
|
|
|
return err
|
|
|
|
}
|
2021-10-12 12:24:00 -04:00
|
|
|
if err = s.moveToTrash(filePath, true); err != nil {
|
2021-08-03 16:26:57 -04:00
|
|
|
if err != errFileNotFound {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2020-06-12 23:04:01 -04:00
|
|
|
}
|
2021-04-19 13:30:42 -04:00
|
|
|
}
|
2021-11-30 21:30:06 -05:00
|
|
|
|
2021-04-19 13:30:42 -04:00
|
|
|
if !lastVersion {
|
2021-08-23 14:17:27 -04:00
|
|
|
buf, err = xlMeta.AppendTo(metaDataPoolGet())
|
|
|
|
defer metaDataPoolPut(buf)
|
2021-03-29 20:00:55 -04:00
|
|
|
if err != nil {
|
2020-06-12 23:04:01 -04:00
|
|
|
return err
|
|
|
|
}
|
2021-01-20 16:12:12 -05:00
|
|
|
|
2020-11-02 19:14:31 -05:00
|
|
|
return s.WriteAll(ctx, volume, pathJoin(path, xlStorageFormatFile), buf)
|
2020-06-12 23:04:01 -04:00
|
|
|
}
|
|
|
|
|
2021-08-03 16:26:57 -04:00
|
|
|
// Move xl.meta to trash
|
|
|
|
filePath := pathJoin(volumeDir, path, xlStorageFormatFile)
|
2020-06-12 23:04:01 -04:00
|
|
|
if err = checkPathLength(filePath); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-10-12 12:24:00 -04:00
|
|
|
err = s.moveToTrash(filePath, false)
|
2021-08-03 16:26:57 -04:00
|
|
|
if err == nil || err == errFileNotFound {
|
|
|
|
s.deleteFile(volumeDir, pathJoin(volumeDir, path), false)
|
2021-03-29 20:00:55 -04:00
|
|
|
}
|
|
|
|
return err
|
2020-06-12 23:04:01 -04:00
|
|
|
}
|
|
|
|
|
2021-04-04 16:32:31 -04:00
|
|
|
// Updates only metadata for a given version.
|
|
|
|
func (s *xlStorage) UpdateMetadata(ctx context.Context, volume, path string, fi FileInfo) error {
|
|
|
|
if len(fi.Metadata) == 0 {
|
|
|
|
return errInvalidArgument
|
|
|
|
}
|
|
|
|
|
|
|
|
buf, err := s.ReadAll(ctx, volume, pathJoin(path, xlStorageFormatFile))
|
|
|
|
if err != nil {
|
|
|
|
if err == errFileNotFound {
|
|
|
|
if fi.VersionID != "" {
|
|
|
|
return errFileVersionNotFound
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
2021-11-18 15:15:22 -05:00
|
|
|
defer metaDataPoolPut(buf)
|
2021-04-04 16:32:31 -04:00
|
|
|
|
|
|
|
if !isXL2V1Format(buf) {
|
|
|
|
return errFileVersionNotFound
|
|
|
|
}
|
|
|
|
|
|
|
|
var xlMeta xlMetaV2
|
|
|
|
if err = xlMeta.Load(buf); err != nil {
|
|
|
|
logger.LogIf(ctx, err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err = xlMeta.UpdateObjectVersion(fi); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-11-18 15:15:22 -05:00
|
|
|
wbuf, err := xlMeta.AppendTo(metaDataPoolGet())
|
2021-04-04 16:32:31 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-11-18 15:15:22 -05:00
|
|
|
defer metaDataPoolPut(wbuf)
|
2021-04-04 16:32:31 -04:00
|
|
|
|
2021-11-18 15:15:22 -05:00
|
|
|
return s.WriteAll(ctx, volume, pathJoin(path, xlStorageFormatFile), wbuf)
|
2021-04-04 16:32:31 -04:00
|
|
|
}
|
|
|
|
|
2020-06-12 23:04:01 -04:00
|
|
|
// WriteMetadata - writes FileInfo metadata for path at `xl.meta`
|
2020-09-04 12:45:06 -04:00
|
|
|
func (s *xlStorage) WriteMetadata(ctx context.Context, volume, path string, fi FileInfo) error {
|
2021-08-10 14:12:22 -04:00
|
|
|
if fi.Fresh {
|
|
|
|
var xlMeta xlMetaV2
|
|
|
|
if err := xlMeta.AddVersion(fi); err != nil {
|
|
|
|
logger.LogIf(ctx, err)
|
|
|
|
return err
|
|
|
|
}
|
2021-08-23 14:17:27 -04:00
|
|
|
buf, err := xlMeta.AppendTo(metaDataPoolGet())
|
|
|
|
defer metaDataPoolPut(buf)
|
2021-08-10 14:12:22 -04:00
|
|
|
if err != nil {
|
|
|
|
logger.LogIf(ctx, err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
// First writes for special situations do not write to stable storage.
|
|
|
|
// this is currently used by
|
|
|
|
// - emphemeral objects such as objects created during listObjects() calls
|
|
|
|
// - newMultipartUpload() call..
|
|
|
|
return s.writeAll(ctx, volume, pathJoin(path, xlStorageFormatFile), buf, false)
|
|
|
|
}
|
|
|
|
|
2020-09-04 12:45:06 -04:00
|
|
|
buf, err := s.ReadAll(ctx, volume, pathJoin(path, xlStorageFormatFile))
|
2020-06-12 23:04:01 -04:00
|
|
|
if err != nil && err != errFileNotFound {
|
|
|
|
return err
|
|
|
|
}
|
2021-08-23 14:17:27 -04:00
|
|
|
defer metaDataPoolPut(buf)
|
2020-06-12 23:04:01 -04:00
|
|
|
|
|
|
|
var xlMeta xlMetaV2
|
|
|
|
if !isXL2V1Format(buf) {
|
2021-09-14 14:34:25 -04:00
|
|
|
// This is both legacy and without proper version.
|
2021-05-07 12:11:05 -04:00
|
|
|
err = xlMeta.AddVersion(fi)
|
2020-06-12 23:04:01 -04:00
|
|
|
if err != nil {
|
2021-03-29 20:00:55 -04:00
|
|
|
logger.LogIf(ctx, err)
|
2020-06-12 23:04:01 -04:00
|
|
|
return err
|
|
|
|
}
|
2021-04-03 12:03:42 -04:00
|
|
|
|
2021-08-23 14:17:27 -04:00
|
|
|
buf, err = xlMeta.AppendTo(metaDataPoolGet())
|
|
|
|
defer metaDataPoolPut(buf)
|
2020-06-12 23:04:01 -04:00
|
|
|
if err != nil {
|
2021-03-29 20:00:55 -04:00
|
|
|
logger.LogIf(ctx, err)
|
2020-06-12 23:04:01 -04:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if err = xlMeta.Load(buf); err != nil {
|
2021-03-29 20:00:55 -04:00
|
|
|
logger.LogIf(ctx, err)
|
2021-09-14 14:34:25 -04:00
|
|
|
// Corrupted data, reset and write.
|
|
|
|
xlMeta = xlMetaV2{}
|
2020-06-12 23:04:01 -04:00
|
|
|
}
|
2021-04-03 12:03:42 -04:00
|
|
|
|
2020-06-12 23:04:01 -04:00
|
|
|
if err = xlMeta.AddVersion(fi); err != nil {
|
2021-03-29 20:00:55 -04:00
|
|
|
logger.LogIf(ctx, err)
|
2020-06-12 23:04:01 -04:00
|
|
|
return err
|
|
|
|
}
|
2021-04-03 12:03:42 -04:00
|
|
|
|
2021-08-23 14:17:27 -04:00
|
|
|
buf, err = xlMeta.AppendTo(metaDataPoolGet())
|
|
|
|
defer metaDataPoolPut(buf)
|
2020-06-12 23:04:01 -04:00
|
|
|
if err != nil {
|
2021-03-29 20:00:55 -04:00
|
|
|
logger.LogIf(ctx, err)
|
2020-06-12 23:04:01 -04:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-02 19:14:31 -05:00
|
|
|
return s.WriteAll(ctx, volume, pathJoin(path, xlStorageFormatFile), buf)
|
2020-06-12 23:04:01 -04:00
|
|
|
}
|
|
|
|
|
2021-01-11 05:27:04 -05:00
|
|
|
func (s *xlStorage) renameLegacyMetadata(volumeDir, path string) (err error) {
|
|
|
|
s.RLock()
|
|
|
|
legacy := s.formatLegacy
|
|
|
|
s.RUnlock()
|
|
|
|
if !legacy {
|
|
|
|
// if its not a legacy backend then this function is
|
|
|
|
// a no-op always returns errFileNotFound
|
|
|
|
return errFileNotFound
|
|
|
|
}
|
|
|
|
|
2020-06-12 23:04:01 -04:00
|
|
|
// Validate file path length, before reading.
|
|
|
|
filePath := pathJoin(volumeDir, path)
|
|
|
|
if err = checkPathLength(filePath); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
srcFilePath := pathJoin(filePath, xlStorageFormatFileV1)
|
|
|
|
dstFilePath := pathJoin(filePath, xlStorageFormatFile)
|
2020-06-19 13:58:17 -04:00
|
|
|
|
|
|
|
// Renaming xl.json to xl.meta should be fully synced to disk.
|
2020-07-11 12:37:34 -04:00
|
|
|
defer func() {
|
|
|
|
if err == nil {
|
2020-08-04 17:55:53 -04:00
|
|
|
if s.globalSync {
|
|
|
|
// Sync to disk only upon success.
|
|
|
|
globalSync()
|
|
|
|
}
|
2020-07-11 12:37:34 -04:00
|
|
|
}
|
|
|
|
}()
|
2020-06-19 13:58:17 -04:00
|
|
|
|
2021-03-23 17:51:27 -04:00
|
|
|
if err = Rename(srcFilePath, dstFilePath); err != nil {
|
2020-06-12 23:04:01 -04:00
|
|
|
switch {
|
|
|
|
case isSysErrNotDir(err):
|
|
|
|
return errFileNotFound
|
|
|
|
case isSysErrPathNotFound(err):
|
|
|
|
return errFileNotFound
|
|
|
|
case isSysErrCrossDevice(err):
|
|
|
|
return fmt.Errorf("%w (%s)->(%s)", errCrossDeviceLink, srcFilePath, dstFilePath)
|
2020-11-23 11:36:49 -05:00
|
|
|
case osIsNotExist(err):
|
2020-06-12 23:04:01 -04:00
|
|
|
return errFileNotFound
|
2020-11-23 11:36:49 -05:00
|
|
|
case osIsExist(err):
|
2020-06-12 23:04:01 -04:00
|
|
|
// This is returned only when destination is a directory and we
|
|
|
|
// are attempting a rename from file to directory.
|
|
|
|
return errIsNotRegular
|
|
|
|
default:
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ReadVersion - reads metadata and returns FileInfo at path `xl.meta`
|
2021-01-07 22:27:31 -05:00
|
|
|
// for all objects less than `32KiB` this call returns data as well
|
|
|
|
// along with metadata.
|
|
|
|
func (s *xlStorage) ReadVersion(ctx context.Context, volume, path, versionID string, readData bool) (fi FileInfo, err error) {
|
|
|
|
volumeDir, err := s.getVolDir(volume)
|
|
|
|
if err != nil {
|
|
|
|
return fi, err
|
|
|
|
}
|
2021-12-21 13:08:26 -05:00
|
|
|
// Validate file path length, before reading.
|
|
|
|
filePath := pathJoin(volumeDir, path)
|
|
|
|
if err = checkPathLength(filePath); err != nil {
|
|
|
|
return fi, err
|
|
|
|
}
|
|
|
|
|
2021-08-12 13:05:24 -04:00
|
|
|
var buf []byte
|
2021-12-21 13:08:26 -05:00
|
|
|
var dmTime time.Time
|
2021-08-12 13:05:24 -04:00
|
|
|
if readData {
|
2021-12-21 13:08:26 -05:00
|
|
|
buf, dmTime, err = s.readAllData(ctx, volumeDir, pathJoin(filePath, xlStorageFormatFile))
|
2021-08-12 13:05:24 -04:00
|
|
|
} else {
|
2021-12-21 13:08:26 -05:00
|
|
|
buf, dmTime, err = s.readMetadataWithDMTime(ctx, pathJoin(filePath, xlStorageFormatFile))
|
2021-08-12 13:05:24 -04:00
|
|
|
if err != nil {
|
|
|
|
if osIsNotExist(err) {
|
2021-10-11 22:48:42 -04:00
|
|
|
if aerr := Access(volumeDir); aerr != nil && osIsNotExist(aerr) {
|
2021-08-12 13:05:24 -04:00
|
|
|
return fi, errVolumeNotFound
|
|
|
|
}
|
|
|
|
}
|
|
|
|
err = osErrToFileErr(err)
|
|
|
|
}
|
|
|
|
}
|
2021-08-12 17:27:22 -04:00
|
|
|
|
2020-06-12 23:04:01 -04:00
|
|
|
if err != nil {
|
|
|
|
if err == errFileNotFound {
|
2021-12-21 13:08:26 -05:00
|
|
|
buf, dmTime, err = s.readAllData(ctx, volumeDir, pathJoin(filePath, xlStorageFormatFileV1))
|
2020-06-12 23:04:01 -04:00
|
|
|
if err != nil {
|
2020-11-29 00:15:45 -05:00
|
|
|
if err == errFileNotFound {
|
|
|
|
if versionID != "" {
|
|
|
|
return fi, errFileVersionNotFound
|
|
|
|
}
|
|
|
|
return fi, errFileNotFound
|
|
|
|
}
|
2020-06-12 23:04:01 -04:00
|
|
|
return fi, err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return fi, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(buf) == 0 {
|
|
|
|
if versionID != "" {
|
|
|
|
return fi, errFileVersionNotFound
|
2019-04-23 17:54:28 -04:00
|
|
|
}
|
2020-06-12 23:04:01 -04:00
|
|
|
return fi, errFileNotFound
|
2019-04-23 17:54:28 -04:00
|
|
|
}
|
|
|
|
|
2021-03-29 20:00:55 -04:00
|
|
|
fi, err = getFileInfo(buf, volume, path, versionID, readData)
|
2021-01-07 22:27:31 -05:00
|
|
|
if err != nil {
|
|
|
|
return fi, err
|
|
|
|
}
|
2021-12-21 13:08:26 -05:00
|
|
|
fi.DiskMTime = dmTime
|
2021-01-07 22:27:31 -05:00
|
|
|
|
2021-08-23 14:17:27 -04:00
|
|
|
if len(fi.Data) == 0 {
|
2021-08-12 17:27:22 -04:00
|
|
|
// We did not read inline data, so we have no references.
|
2021-08-23 14:17:27 -04:00
|
|
|
defer metaDataPoolPut(buf)
|
2021-08-12 17:27:22 -04:00
|
|
|
}
|
|
|
|
|
2021-01-07 22:27:31 -05:00
|
|
|
if readData {
|
2021-03-29 20:00:55 -04:00
|
|
|
if len(fi.Data) > 0 || fi.Size == 0 {
|
2021-08-13 11:25:54 -04:00
|
|
|
if fi.InlineData() {
|
|
|
|
// If written with header we are fine.
|
|
|
|
return fi, nil
|
|
|
|
}
|
|
|
|
if fi.Size == 0 || !(fi.VersionID != "" && fi.VersionID != nullVersionID) {
|
|
|
|
// If versioned we have no conflicts.
|
|
|
|
fi.SetInlineData()
|
|
|
|
return fi, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// For overwritten objects without header we might have a conflict with
|
|
|
|
// data written later.
|
|
|
|
// Check the data path if there is a part with data.
|
|
|
|
partPath := fmt.Sprintf("part.%d", fi.Parts[0].Number)
|
|
|
|
dataPath := pathJoin(path, fi.DataDir, partPath)
|
2021-10-01 14:50:00 -04:00
|
|
|
_, err = s.StatInfoFile(ctx, volume, dataPath, false)
|
2021-08-13 11:25:54 -04:00
|
|
|
if err != nil {
|
|
|
|
// Set the inline header, our inlined data is fine.
|
|
|
|
fi.SetInlineData()
|
|
|
|
return fi, nil
|
2021-05-25 19:33:06 -04:00
|
|
|
}
|
2021-08-13 11:25:54 -04:00
|
|
|
// Data exists on disk, remove the version from metadata.
|
|
|
|
fi.Data = nil
|
2021-03-29 20:00:55 -04:00
|
|
|
}
|
2021-04-03 12:03:42 -04:00
|
|
|
|
2021-01-07 22:27:31 -05:00
|
|
|
// Reading data for small objects when
|
|
|
|
// - object has not yet transitioned
|
2021-04-04 16:32:31 -04:00
|
|
|
// - object size lesser than 128KiB
|
2021-01-07 22:27:31 -05:00
|
|
|
// - object has maximum of 1 parts
|
2021-07-28 14:20:16 -04:00
|
|
|
if fi.TransitionStatus == "" &&
|
|
|
|
fi.DataDir != "" && fi.Size <= smallFileThreshold &&
|
|
|
|
len(fi.Parts) == 1 {
|
2021-01-22 18:38:21 -05:00
|
|
|
partPath := fmt.Sprintf("part.%d", fi.Parts[0].Number)
|
2021-07-28 14:20:16 -04:00
|
|
|
dataPath := pathJoin(volumeDir, path, fi.DataDir, partPath)
|
2021-12-21 13:08:26 -05:00
|
|
|
fi.Data, _, err = s.readAllData(ctx, volumeDir, dataPath)
|
2021-01-07 22:27:31 -05:00
|
|
|
if err != nil {
|
|
|
|
return FileInfo{}, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return fi, nil
|
|
|
|
}
|
|
|
|
|
2021-12-21 13:08:26 -05:00
|
|
|
func (s *xlStorage) readAllData(ctx context.Context, volumeDir string, filePath string) (buf []byte, dmTime time.Time, err error) {
|
|
|
|
if contextCanceled(ctx) {
|
|
|
|
return nil, time.Time{}, ctx.Err()
|
|
|
|
}
|
|
|
|
|
2022-01-02 12:15:06 -05:00
|
|
|
f, err := OpenFileDirectIO(filePath, readMode, 0o666)
|
2021-01-07 22:27:31 -05:00
|
|
|
if err != nil {
|
|
|
|
if osIsNotExist(err) {
|
|
|
|
// Check if the object doesn't exist because its bucket
|
|
|
|
// is missing in order to return the correct error.
|
2021-03-29 11:07:23 -04:00
|
|
|
if err = Access(volumeDir); err != nil && osIsNotExist(err) {
|
2021-12-21 13:08:26 -05:00
|
|
|
return nil, dmTime, errVolumeNotFound
|
2021-01-07 22:27:31 -05:00
|
|
|
}
|
2021-12-21 13:08:26 -05:00
|
|
|
return nil, dmTime, errFileNotFound
|
2021-01-07 22:27:31 -05:00
|
|
|
} else if osIsPermission(err) {
|
2021-12-21 13:08:26 -05:00
|
|
|
return nil, dmTime, errFileAccessDenied
|
2021-01-07 22:27:31 -05:00
|
|
|
} else if isSysErrNotDir(err) || isSysErrIsDir(err) {
|
2021-12-21 13:08:26 -05:00
|
|
|
return nil, dmTime, errFileNotFound
|
2021-01-07 22:27:31 -05:00
|
|
|
} else if isSysErrHandleInvalid(err) {
|
|
|
|
// This case is special and needs to be handled for windows.
|
2021-12-21 13:08:26 -05:00
|
|
|
return nil, dmTime, errFileNotFound
|
2021-01-07 22:27:31 -05:00
|
|
|
} else if isSysErrIO(err) {
|
2021-12-21 13:08:26 -05:00
|
|
|
return nil, dmTime, errFaultyDisk
|
2021-01-07 22:27:31 -05:00
|
|
|
} else if isSysErrTooManyFiles(err) {
|
2021-12-21 13:08:26 -05:00
|
|
|
return nil, dmTime, errTooManyOpenFiles
|
2021-01-07 22:27:31 -05:00
|
|
|
} else if isSysErrInvalidArg(err) {
|
2021-03-23 17:51:27 -04:00
|
|
|
st, _ := Lstat(filePath)
|
2021-02-24 03:14:16 -05:00
|
|
|
if st != nil && st.IsDir() {
|
|
|
|
// Linux returns InvalidArg for directory O_DIRECT
|
|
|
|
// we need to keep this fallback code to return correct
|
|
|
|
// errors upwards.
|
2021-12-21 13:08:26 -05:00
|
|
|
return nil, dmTime, errFileNotFound
|
2021-02-24 03:14:16 -05:00
|
|
|
}
|
2021-12-21 13:08:26 -05:00
|
|
|
return nil, dmTime, errUnsupportedDisk
|
2021-01-07 22:27:31 -05:00
|
|
|
}
|
2021-12-21 13:08:26 -05:00
|
|
|
return nil, dmTime, err
|
2021-01-07 22:27:31 -05:00
|
|
|
}
|
2021-09-29 19:40:28 -04:00
|
|
|
r := &xioutil.ODirectReader{
|
|
|
|
File: f,
|
|
|
|
SmallFile: true,
|
2021-09-28 13:02:56 -04:00
|
|
|
}
|
2021-04-07 11:32:59 -04:00
|
|
|
defer r.Close()
|
2021-10-28 20:02:22 -04:00
|
|
|
|
|
|
|
// Get size for precise allocation.
|
|
|
|
stat, err := f.Stat()
|
2021-01-22 18:38:21 -05:00
|
|
|
if err != nil {
|
2021-10-28 20:02:22 -04:00
|
|
|
buf, err = ioutil.ReadAll(r)
|
2021-12-21 13:08:26 -05:00
|
|
|
return buf, dmTime, osErrToFileErr(err)
|
2021-10-28 20:02:22 -04:00
|
|
|
}
|
|
|
|
if stat.IsDir() {
|
2021-12-21 13:08:26 -05:00
|
|
|
return nil, dmTime, errFileNotFound
|
2021-10-28 20:02:22 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Read into appropriate buffer.
|
|
|
|
sz := stat.Size()
|
|
|
|
if sz <= metaDataReadDefault {
|
|
|
|
buf = metaDataPoolGet()
|
|
|
|
buf = buf[:sz]
|
|
|
|
} else {
|
|
|
|
buf = make([]byte, sz)
|
2021-01-22 18:38:21 -05:00
|
|
|
}
|
2021-10-28 20:02:22 -04:00
|
|
|
// Read file...
|
|
|
|
_, err = io.ReadFull(r, buf)
|
2021-04-05 11:52:28 -04:00
|
|
|
|
2021-12-21 13:08:26 -05:00
|
|
|
return buf, stat.ModTime().UTC(), osErrToFileErr(err)
|
2016-04-08 13:37:38 -04:00
|
|
|
}
|
|
|
|
|
2016-06-25 17:51:06 -04:00
|
|
|
// ReadAll reads from r until an error or EOF and returns the data it read.
|
|
|
|
// A successful call returns err == nil, not err == EOF. Because ReadAll is
|
|
|
|
// defined to read from src until EOF, it does not treat an EOF from Read
|
|
|
|
// as an error to be reported.
|
|
|
|
// This API is meant to be used on files which have small memory footprint, do
|
|
|
|
// not use this on large files as it would cause server to crash.
|
2020-09-04 12:45:06 -04:00
|
|
|
func (s *xlStorage) ReadAll(ctx context.Context, volume string, path string) (buf []byte, err error) {
|
2016-06-25 17:51:06 -04:00
|
|
|
volumeDir, err := s.getVolDir(volume)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-06-12 23:04:01 -04:00
|
|
|
|
2016-06-25 17:51:06 -04:00
|
|
|
// Validate file path length, before reading.
|
|
|
|
filePath := pathJoin(volumeDir, path)
|
2020-06-12 23:04:01 -04:00
|
|
|
if err = checkPathLength(filePath); err != nil {
|
2016-06-25 17:51:06 -04:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2021-12-21 13:08:26 -05:00
|
|
|
buf, _, err = s.readAllData(ctx, volumeDir, filePath)
|
|
|
|
return buf, err
|
2016-06-25 17:51:06 -04:00
|
|
|
}
|
|
|
|
|
2016-05-28 18:13:15 -04:00
|
|
|
// ReadFile reads exactly len(buf) bytes into buf. It returns the
|
|
|
|
// number of bytes copied. The error is EOF only if no bytes were
|
|
|
|
// read. On return, n == len(buf) if and only if err == nil. n == 0
|
2016-11-21 02:42:53 -05:00
|
|
|
// for io.EOF.
|
2017-05-16 17:21:52 -04:00
|
|
|
//
|
2016-11-21 02:42:53 -05:00
|
|
|
// If an EOF happens after reading some but not all the bytes,
|
2017-09-25 14:32:56 -04:00
|
|
|
// ReadFile returns ErrUnexpectedEOF.
|
|
|
|
//
|
|
|
|
// If the BitrotVerifier is not nil or not verified ReadFile
|
|
|
|
// tries to verify whether the disk has bitrot.
|
2017-05-16 17:21:52 -04:00
|
|
|
//
|
|
|
|
// Additionally ReadFile also starts reading from an offset. ReadFile
|
|
|
|
// semantics are same as io.ReadFull.
|
2020-09-04 12:45:06 -04:00
|
|
|
func (s *xlStorage) ReadFile(ctx context.Context, volume string, path string, offset int64, buffer []byte, verifier *BitrotVerifier) (int64, error) {
|
2018-08-06 18:14:08 -04:00
|
|
|
if offset < 0 {
|
|
|
|
return 0, errInvalidArgument
|
|
|
|
}
|
|
|
|
|
2016-05-18 00:22:27 -04:00
|
|
|
volumeDir, err := s.getVolDir(volume)
|
|
|
|
if err != nil {
|
2016-05-28 18:13:15 -04:00
|
|
|
return 0, err
|
2016-05-18 00:22:27 -04:00
|
|
|
}
|
2020-01-17 16:34:43 -05:00
|
|
|
|
|
|
|
var n int
|
|
|
|
|
2016-05-18 00:22:27 -04:00
|
|
|
// Stat a volume entry.
|
2021-03-29 11:07:23 -04:00
|
|
|
if err = Access(volumeDir); err != nil {
|
2020-11-23 11:36:49 -05:00
|
|
|
if osIsNotExist(err) {
|
2016-05-28 18:13:15 -04:00
|
|
|
return 0, errVolumeNotFound
|
2018-07-27 18:32:19 -04:00
|
|
|
} else if isSysErrIO(err) {
|
|
|
|
return 0, errFaultyDisk
|
2021-03-29 11:07:23 -04:00
|
|
|
} else if osIsPermission(err) {
|
|
|
|
return 0, errFileAccessDenied
|
2016-05-18 00:22:27 -04:00
|
|
|
}
|
2016-05-28 18:13:15 -04:00
|
|
|
return 0, err
|
2016-04-08 13:37:38 -04:00
|
|
|
}
|
2016-04-13 14:32:47 -04:00
|
|
|
|
2016-06-25 17:51:06 -04:00
|
|
|
// Validate effective path length before reading.
|
2016-05-05 04:39:26 -04:00
|
|
|
filePath := pathJoin(volumeDir, path)
|
2020-06-12 23:04:01 -04:00
|
|
|
if err = checkPathLength(filePath); err != nil {
|
2016-05-28 18:13:15 -04:00
|
|
|
return 0, err
|
2016-05-11 15:55:02 -04:00
|
|
|
}
|
2016-06-25 17:51:06 -04:00
|
|
|
|
|
|
|
// Open the file for reading.
|
2021-03-23 17:51:27 -04:00
|
|
|
file, err := Open(filePath)
|
2016-04-08 13:37:38 -04:00
|
|
|
if err != nil {
|
2018-08-06 13:26:40 -04:00
|
|
|
switch {
|
2020-11-23 11:36:49 -05:00
|
|
|
case osIsNotExist(err):
|
2016-05-28 18:13:15 -04:00
|
|
|
return 0, errFileNotFound
|
2020-11-23 11:36:49 -05:00
|
|
|
case osIsPermission(err):
|
2016-05-28 18:13:15 -04:00
|
|
|
return 0, errFileAccessDenied
|
2018-08-06 13:26:40 -04:00
|
|
|
case isSysErrNotDir(err):
|
2016-07-29 00:57:11 -04:00
|
|
|
return 0, errFileAccessDenied
|
2018-08-06 13:26:40 -04:00
|
|
|
case isSysErrIO(err):
|
2018-07-27 18:32:19 -04:00
|
|
|
return 0, errFaultyDisk
|
2019-05-02 10:09:57 -04:00
|
|
|
case isSysErrTooManyFiles(err):
|
|
|
|
return 0, errTooManyOpenFiles
|
2018-08-06 13:26:40 -04:00
|
|
|
default:
|
|
|
|
return 0, err
|
2016-04-08 13:37:38 -04:00
|
|
|
}
|
|
|
|
}
|
2016-07-27 22:22:32 -04:00
|
|
|
|
|
|
|
// Close the file descriptor.
|
|
|
|
defer file.Close()
|
|
|
|
|
2016-04-08 13:37:38 -04:00
|
|
|
st, err := file.Stat()
|
|
|
|
if err != nil {
|
2016-05-28 18:13:15 -04:00
|
|
|
return 0, err
|
2016-04-08 13:37:38 -04:00
|
|
|
}
|
2016-07-29 00:57:11 -04:00
|
|
|
|
2017-05-16 17:21:52 -04:00
|
|
|
// Verify it is a regular file, otherwise subsequent Seek is
|
|
|
|
// undefined.
|
2016-04-08 13:37:38 -04:00
|
|
|
if !st.Mode().IsRegular() {
|
2016-07-29 00:57:11 -04:00
|
|
|
return 0, errIsNotRegular
|
2016-04-08 13:37:38 -04:00
|
|
|
}
|
2016-07-29 00:57:11 -04:00
|
|
|
|
2018-08-06 18:14:08 -04:00
|
|
|
if verifier == nil {
|
|
|
|
n, err = file.ReadAt(buffer, offset)
|
|
|
|
return int64(n), err
|
|
|
|
}
|
2017-05-16 17:21:52 -04:00
|
|
|
|
2018-08-06 18:14:08 -04:00
|
|
|
h := verifier.algorithm.New()
|
fix: use buffers only when necessary for io.Copy() (#11229)
Use separate sync.Pool for writes/reads
Avoid passing buffers for io.CopyBuffer()
if the writer or reader implement io.WriteTo or io.ReadFrom
respectively then its useless for sync.Pool to allocate
buffers on its own since that will be completely ignored
by the io.CopyBuffer Go implementation.
Improve this wherever we see this to be optimal.
This allows us to be more efficient on memory usage.
```
385 // copyBuffer is the actual implementation of Copy and CopyBuffer.
386 // if buf is nil, one is allocated.
387 func copyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error) {
388 // If the reader has a WriteTo method, use it to do the copy.
389 // Avoids an allocation and a copy.
390 if wt, ok := src.(WriterTo); ok {
391 return wt.WriteTo(dst)
392 }
393 // Similarly, if the writer has a ReadFrom method, use it to do the copy.
394 if rt, ok := dst.(ReaderFrom); ok {
395 return rt.ReadFrom(src)
396 }
```
From readahead package
```
// WriteTo writes data to w until there's no more data to write or when an error occurs.
// The return value n is the number of bytes written.
// Any error encountered during the write is also returned.
func (a *reader) WriteTo(w io.Writer) (n int64, err error) {
if a.err != nil {
return 0, a.err
}
n = 0
for {
err = a.fill()
if err != nil {
return n, err
}
n2, err := w.Write(a.cur.buffer())
a.cur.inc(n2)
n += int64(n2)
if err != nil {
return n, err
}
```
2021-01-06 12:36:55 -05:00
|
|
|
if _, err = io.Copy(h, io.LimitReader(file, offset)); err != nil {
|
2018-08-06 18:14:08 -04:00
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if n, err = io.ReadFull(file, buffer); err != nil {
|
|
|
|
return int64(n), err
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, err = h.Write(buffer); err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
fix: use buffers only when necessary for io.Copy() (#11229)
Use separate sync.Pool for writes/reads
Avoid passing buffers for io.CopyBuffer()
if the writer or reader implement io.WriteTo or io.ReadFrom
respectively then its useless for sync.Pool to allocate
buffers on its own since that will be completely ignored
by the io.CopyBuffer Go implementation.
Improve this wherever we see this to be optimal.
This allows us to be more efficient on memory usage.
```
385 // copyBuffer is the actual implementation of Copy and CopyBuffer.
386 // if buf is nil, one is allocated.
387 func copyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error) {
388 // If the reader has a WriteTo method, use it to do the copy.
389 // Avoids an allocation and a copy.
390 if wt, ok := src.(WriterTo); ok {
391 return wt.WriteTo(dst)
392 }
393 // Similarly, if the writer has a ReadFrom method, use it to do the copy.
394 if rt, ok := dst.(ReaderFrom); ok {
395 return rt.ReadFrom(src)
396 }
```
From readahead package
```
// WriteTo writes data to w until there's no more data to write or when an error occurs.
// The return value n is the number of bytes written.
// Any error encountered during the write is also returned.
func (a *reader) WriteTo(w io.Writer) (n int64, err error) {
if a.err != nil {
return 0, a.err
}
n = 0
for {
err = a.fill()
if err != nil {
return n, err
}
n2, err := w.Write(a.cur.buffer())
a.cur.inc(n2)
n += int64(n2)
if err != nil {
return n, err
}
```
2021-01-06 12:36:55 -05:00
|
|
|
if _, err = io.Copy(h, file); err != nil {
|
2018-08-06 18:14:08 -04:00
|
|
|
return 0, err
|
2017-05-16 17:21:52 -04:00
|
|
|
}
|
2016-05-28 18:13:15 -04:00
|
|
|
|
2019-02-13 07:59:36 -05:00
|
|
|
if !bytes.Equal(h.Sum(nil), verifier.sum) {
|
2019-10-01 16:12:15 -04:00
|
|
|
return 0, errFileCorrupt
|
2017-08-14 21:08:42 -04:00
|
|
|
}
|
2018-08-06 18:14:08 -04:00
|
|
|
|
|
|
|
return int64(len(buffer)), nil
|
2016-04-08 20:13:16 -04:00
|
|
|
}
|
|
|
|
|
2021-08-10 14:12:22 -04:00
|
|
|
func (s *xlStorage) openFileSync(filePath string, mode int) (f *os.File, err error) {
|
2021-02-24 03:14:16 -05:00
|
|
|
// Create top level directories if they don't exist.
|
|
|
|
// with mode 0777 mkdir honors system umask.
|
2022-01-02 12:15:06 -05:00
|
|
|
if err = mkdirAll(pathutil.Dir(filePath), 0o777); err != nil {
|
2021-03-17 12:38:38 -04:00
|
|
|
return nil, osErrToFileErr(err)
|
2016-05-28 18:13:15 -04:00
|
|
|
}
|
2016-06-23 23:19:27 -04:00
|
|
|
|
2022-01-02 12:15:06 -05:00
|
|
|
w, err := OpenFile(filePath, mode|writeMode, 0o666)
|
2016-05-04 15:18:40 -04:00
|
|
|
if err != nil {
|
|
|
|
// File path cannot be verified since one of the parents is a file.
|
2018-08-06 13:26:40 -04:00
|
|
|
switch {
|
2021-02-24 03:14:16 -05:00
|
|
|
case isSysErrIsDir(err):
|
|
|
|
return nil, errIsNotRegular
|
2020-11-23 11:36:49 -05:00
|
|
|
case osIsPermission(err):
|
2018-04-09 23:56:09 -04:00
|
|
|
return nil, errFileAccessDenied
|
2018-08-06 13:26:40 -04:00
|
|
|
case isSysErrIO(err):
|
2018-07-27 18:32:19 -04:00
|
|
|
return nil, errFaultyDisk
|
2019-05-02 10:09:57 -04:00
|
|
|
case isSysErrTooManyFiles(err):
|
|
|
|
return nil, errTooManyOpenFiles
|
2018-08-06 13:26:40 -04:00
|
|
|
default:
|
|
|
|
return nil, err
|
2016-05-04 15:18:40 -04:00
|
|
|
}
|
2016-10-29 15:44:44 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
return w, nil
|
|
|
|
}
|
|
|
|
|
2021-08-10 14:12:22 -04:00
|
|
|
func (s *xlStorage) openFileNoSync(filePath string, mode int) (f *os.File, err error) {
|
|
|
|
// Create top level directories if they don't exist.
|
|
|
|
// with mode 0777 mkdir honors system umask.
|
2022-01-02 12:15:06 -05:00
|
|
|
if err = mkdirAll(pathutil.Dir(filePath), 0o777); err != nil {
|
2021-08-10 14:12:22 -04:00
|
|
|
return nil, osErrToFileErr(err)
|
|
|
|
}
|
|
|
|
|
2022-01-02 12:15:06 -05:00
|
|
|
w, err := OpenFile(filePath, mode, 0o666)
|
2021-08-10 14:12:22 -04:00
|
|
|
if err != nil {
|
|
|
|
// File path cannot be verified since one of the parents is a file.
|
|
|
|
switch {
|
|
|
|
case isSysErrIsDir(err):
|
|
|
|
return nil, errIsNotRegular
|
|
|
|
case osIsPermission(err):
|
|
|
|
return nil, errFileAccessDenied
|
|
|
|
case isSysErrIO(err):
|
|
|
|
return nil, errFaultyDisk
|
|
|
|
case isSysErrTooManyFiles(err):
|
|
|
|
return nil, errTooManyOpenFiles
|
|
|
|
default:
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return w, nil
|
|
|
|
}
|
|
|
|
|
2019-01-17 07:58:18 -05:00
|
|
|
// ReadFileStream - Returns the read stream of the file.
|
2020-09-04 12:45:06 -04:00
|
|
|
func (s *xlStorage) ReadFileStream(ctx context.Context, volume, path string, offset, length int64) (io.ReadCloser, error) {
|
2019-01-17 07:58:18 -05:00
|
|
|
if offset < 0 {
|
|
|
|
return nil, errInvalidArgument
|
|
|
|
}
|
|
|
|
|
|
|
|
volumeDir, err := s.getVolDir(volume)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Validate effective path length before reading.
|
|
|
|
filePath := pathJoin(volumeDir, path)
|
2020-06-12 23:04:01 -04:00
|
|
|
if err = checkPathLength(filePath); err != nil {
|
2019-01-17 07:58:18 -05:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2022-01-02 12:15:06 -05:00
|
|
|
file, err := OpenFileDirectIO(filePath, readMode, 0o666)
|
2019-01-17 07:58:18 -05:00
|
|
|
if err != nil {
|
|
|
|
switch {
|
2020-11-23 11:36:49 -05:00
|
|
|
case osIsNotExist(err):
|
2021-03-29 11:07:23 -04:00
|
|
|
if err = Access(volumeDir); err != nil && osIsNotExist(err) {
|
2021-01-01 12:44:36 -05:00
|
|
|
return nil, errVolumeNotFound
|
|
|
|
}
|
2019-01-17 07:58:18 -05:00
|
|
|
return nil, errFileNotFound
|
2020-11-23 11:36:49 -05:00
|
|
|
case osIsPermission(err):
|
2019-01-17 07:58:18 -05:00
|
|
|
return nil, errFileAccessDenied
|
|
|
|
case isSysErrNotDir(err):
|
|
|
|
return nil, errFileAccessDenied
|
|
|
|
case isSysErrIO(err):
|
|
|
|
return nil, errFaultyDisk
|
2019-05-02 10:09:57 -04:00
|
|
|
case isSysErrTooManyFiles(err):
|
|
|
|
return nil, errTooManyOpenFiles
|
2021-01-22 18:38:21 -05:00
|
|
|
case isSysErrInvalidArg(err):
|
|
|
|
return nil, errUnsupportedDisk
|
2019-01-17 07:58:18 -05:00
|
|
|
default:
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
st, err := file.Stat()
|
|
|
|
if err != nil {
|
2021-02-28 18:33:03 -05:00
|
|
|
file.Close()
|
2019-01-17 07:58:18 -05:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Verify it is a regular file, otherwise subsequent Seek is
|
|
|
|
// undefined.
|
|
|
|
if !st.Mode().IsRegular() {
|
2021-02-28 18:33:03 -05:00
|
|
|
file.Close()
|
2019-01-17 07:58:18 -05:00
|
|
|
return nil, errIsNotRegular
|
|
|
|
}
|
|
|
|
|
2021-10-13 22:49:14 -04:00
|
|
|
if st.Size() < offset+length {
|
|
|
|
// Expected size cannot be satisfied for
|
|
|
|
// requested offset and length
|
|
|
|
file.Close()
|
|
|
|
return nil, errFileCorrupt
|
|
|
|
}
|
|
|
|
|
2021-09-29 19:40:28 -04:00
|
|
|
alignment := offset%xioutil.DirectioAlignSize == 0
|
|
|
|
if !alignment {
|
|
|
|
if err = disk.DisableDirectIO(file); err != nil {
|
|
|
|
file.Close()
|
|
|
|
return nil, err
|
2021-01-07 22:27:31 -05:00
|
|
|
}
|
2019-01-17 07:58:18 -05:00
|
|
|
}
|
2019-05-22 16:47:15 -04:00
|
|
|
|
2021-02-28 18:33:03 -05:00
|
|
|
if offset > 0 {
|
|
|
|
if _, err = file.Seek(offset, io.SeekStart); err != nil {
|
2021-09-29 19:40:28 -04:00
|
|
|
file.Close()
|
2021-02-28 18:33:03 -05:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-29 19:40:28 -04:00
|
|
|
or := &xioutil.ODirectReader{
|
|
|
|
File: file,
|
|
|
|
SmallFile: false,
|
|
|
|
}
|
|
|
|
|
|
|
|
if length <= smallFileThreshold {
|
|
|
|
or = &xioutil.ODirectReader{
|
|
|
|
File: file,
|
|
|
|
SmallFile: true,
|
2020-02-06 23:13:55 -05:00
|
|
|
}
|
Remove read-ahead for small files (#8522)
We should only read ahead if we are reading big files. We enable it for files >= 16MB.
Benchmark on 64KB objects.
Before:
```
Operation: GET
Errors: 0
Average: 59.976s, 87.13 MB/s, 1394.07 ops ended/s.
Fastest: 1s, 90.99 MB/s, 1455.00 ops ended/s.
50% Median: 1s, 87.53 MB/s, 1401.00 ops ended/s.
Slowest: 1s, 81.39 MB/s, 1301.00 ops ended/s.
```
After:
```
Operation: GET
Errors: 0
Average: 59.992s, 207.99 MB/s, 3327.85 ops ended/s.
Fastest: 1s, 219.20 MB/s, 3507.00 ops ended/s.
50% Median: 1s, 210.54 MB/s, 3368.00 ops ended/s.
Slowest: 1s, 179.14 MB/s, 2865.00 ops ended/s.
```
The 64KB buffer is actually a small disadvantage for this case, but I believe it will be better in general than no buffer.
2019-11-14 15:58:41 -05:00
|
|
|
}
|
2019-05-22 16:47:15 -04:00
|
|
|
|
2021-09-29 19:40:28 -04:00
|
|
|
r := struct {
|
|
|
|
io.Reader
|
|
|
|
io.Closer
|
|
|
|
}{Reader: io.LimitReader(or, length), Closer: closeWrapper(func() error {
|
|
|
|
if !alignment || offset+length%xioutil.DirectioAlignSize != 0 {
|
|
|
|
// invalidate page-cache for unaligned reads.
|
|
|
|
disk.FadviseDontNeed(file)
|
|
|
|
}
|
|
|
|
return or.Close()
|
|
|
|
})}
|
|
|
|
|
Remove read-ahead for small files (#8522)
We should only read ahead if we are reading big files. We enable it for files >= 16MB.
Benchmark on 64KB objects.
Before:
```
Operation: GET
Errors: 0
Average: 59.976s, 87.13 MB/s, 1394.07 ops ended/s.
Fastest: 1s, 90.99 MB/s, 1455.00 ops ended/s.
50% Median: 1s, 87.53 MB/s, 1401.00 ops ended/s.
Slowest: 1s, 81.39 MB/s, 1301.00 ops ended/s.
```
After:
```
Operation: GET
Errors: 0
Average: 59.992s, 207.99 MB/s, 3327.85 ops ended/s.
Fastest: 1s, 219.20 MB/s, 3507.00 ops ended/s.
50% Median: 1s, 210.54 MB/s, 3368.00 ops ended/s.
Slowest: 1s, 179.14 MB/s, 2865.00 ops ended/s.
```
The 64KB buffer is actually a small disadvantage for this case, but I believe it will be better in general than no buffer.
2019-11-14 15:58:41 -05:00
|
|
|
return r, nil
|
2019-01-17 07:58:18 -05:00
|
|
|
}
|
|
|
|
|
2020-02-06 23:13:55 -05:00
|
|
|
// closeWrapper converts a function to an io.Closer
|
|
|
|
type closeWrapper func() error
|
|
|
|
|
|
|
|
// Close calls the wrapped function.
|
|
|
|
func (c closeWrapper) Close() error {
|
|
|
|
return c()
|
|
|
|
}
|
|
|
|
|
2019-01-17 07:58:18 -05:00
|
|
|
// CreateFile - creates the file.
|
2020-09-04 12:45:06 -04:00
|
|
|
func (s *xlStorage) CreateFile(ctx context.Context, volume, path string, fileSize int64, r io.Reader) (err error) {
|
2019-04-30 19:27:31 -04:00
|
|
|
if fileSize < -1 {
|
2016-10-29 15:44:44 -04:00
|
|
|
return errInvalidArgument
|
|
|
|
}
|
|
|
|
|
2021-03-24 12:08:05 -04:00
|
|
|
volumeDir, err := s.getVolDir(volume)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
filePath := pathJoin(volumeDir, path)
|
|
|
|
if err = checkPathLength(filePath); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
parentFilePath := pathutil.Dir(filePath)
|
|
|
|
defer func() {
|
|
|
|
if err != nil {
|
2021-03-24 12:35:45 -04:00
|
|
|
if volume == minioMetaTmpBucket {
|
2021-04-20 13:44:39 -04:00
|
|
|
// only cleanup parent path if the
|
|
|
|
// parent volume name is minioMetaTmpBucket
|
2021-03-24 12:35:45 -04:00
|
|
|
removeAll(parentFilePath)
|
|
|
|
}
|
2021-03-24 12:08:05 -04:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2021-03-18 17:09:55 -04:00
|
|
|
if fileSize >= 0 && fileSize <= smallFileThreshold {
|
2021-03-17 12:38:38 -04:00
|
|
|
// For streams smaller than 128KiB we simply write them as O_DSYNC (fdatasync)
|
|
|
|
// and not O_DIRECT to avoid the complexities of aligned I/O.
|
2021-08-10 14:12:22 -04:00
|
|
|
w, err := s.openFileSync(filePath, os.O_CREATE|os.O_WRONLY|os.O_EXCL)
|
2021-03-17 12:38:38 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer w.Close()
|
2021-01-07 22:27:31 -05:00
|
|
|
|
2021-03-17 12:38:38 -04:00
|
|
|
written, err := io.Copy(w, r)
|
|
|
|
if err != nil {
|
|
|
|
return osErrToFileErr(err)
|
2019-04-24 00:25:06 -04:00
|
|
|
}
|
2021-03-17 12:38:38 -04:00
|
|
|
|
2021-04-20 13:44:39 -04:00
|
|
|
if written < fileSize {
|
|
|
|
return errLessData
|
|
|
|
} else if written > fileSize {
|
2021-03-17 12:38:38 -04:00
|
|
|
return errMoreData
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
2019-04-24 00:25:06 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Create top level directories if they don't exist.
|
|
|
|
// with mode 0777 mkdir honors system umask.
|
2022-01-02 12:15:06 -05:00
|
|
|
if err = mkdirAll(parentFilePath, 0o777); err != nil {
|
2021-03-17 12:38:38 -04:00
|
|
|
return osErrToFileErr(err)
|
2019-04-24 00:25:06 -04:00
|
|
|
}
|
|
|
|
|
2022-01-02 12:15:06 -05:00
|
|
|
w, err := OpenFileDirectIO(filePath, os.O_CREATE|os.O_WRONLY|os.O_EXCL, 0o666)
|
2019-04-24 00:25:06 -04:00
|
|
|
if err != nil {
|
2021-03-17 12:38:38 -04:00
|
|
|
return osErrToFileErr(err)
|
2016-10-29 15:44:44 -04:00
|
|
|
}
|
2019-01-17 07:58:18 -05:00
|
|
|
|
2020-07-01 13:57:23 -04:00
|
|
|
defer func() {
|
|
|
|
disk.Fdatasync(w) // Only interested in flushing the size_t not mtime/atime
|
|
|
|
w.Close()
|
|
|
|
}()
|
2019-05-22 16:47:15 -04:00
|
|
|
|
2021-05-15 15:56:58 -04:00
|
|
|
var bufp *[]byte
|
|
|
|
if fileSize > 0 && fileSize >= reallyLargeFileThreshold {
|
|
|
|
// use a larger 4MiB buffer for really large streams.
|
2021-09-29 19:40:28 -04:00
|
|
|
bufp = xioutil.ODirectPoolXLarge.Get().(*[]byte)
|
|
|
|
defer xioutil.ODirectPoolXLarge.Put(bufp)
|
2021-05-15 15:56:58 -04:00
|
|
|
} else {
|
2021-09-29 19:40:28 -04:00
|
|
|
bufp = xioutil.ODirectPoolLarge.Get().(*[]byte)
|
|
|
|
defer xioutil.ODirectPoolLarge.Put(bufp)
|
2021-05-15 15:56:58 -04:00
|
|
|
}
|
2019-01-17 07:58:18 -05:00
|
|
|
|
2019-08-23 18:36:46 -04:00
|
|
|
written, err := xioutil.CopyAligned(w, r, *bufp, fileSize)
|
2019-05-22 16:47:15 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
2019-01-17 07:58:18 -05:00
|
|
|
}
|
2019-04-24 00:25:06 -04:00
|
|
|
|
2021-03-18 17:09:55 -04:00
|
|
|
if written < fileSize && fileSize >= 0 {
|
2019-05-22 16:47:15 -04:00
|
|
|
return errLessData
|
2021-03-18 17:09:55 -04:00
|
|
|
} else if written > fileSize && fileSize >= 0 {
|
2019-05-22 16:47:15 -04:00
|
|
|
return errMoreData
|
2019-01-17 07:58:18 -05:00
|
|
|
}
|
2019-05-22 16:47:15 -04:00
|
|
|
|
|
|
|
return nil
|
2016-10-29 15:44:44 -04:00
|
|
|
}
|
|
|
|
|
2021-08-10 14:12:22 -04:00
|
|
|
func (s *xlStorage) writeAll(ctx context.Context, volume string, path string, b []byte, sync bool) (err error) {
|
2021-03-24 12:08:05 -04:00
|
|
|
volumeDir, err := s.getVolDir(volume)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
filePath := pathJoin(volumeDir, path)
|
|
|
|
if err = checkPathLength(filePath); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-08-10 14:12:22 -04:00
|
|
|
var w *os.File
|
|
|
|
if sync {
|
|
|
|
w, err = s.openFileSync(filePath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC)
|
|
|
|
} else {
|
|
|
|
w, err = s.openFileNoSync(filePath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC)
|
|
|
|
}
|
2018-11-14 09:18:35 -05:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-05-22 16:47:15 -04:00
|
|
|
defer w.Close()
|
2021-02-24 03:14:16 -05:00
|
|
|
|
2020-11-02 19:14:31 -05:00
|
|
|
n, err := w.Write(b)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-02-24 03:14:16 -05:00
|
|
|
|
2020-11-02 19:14:31 -05:00
|
|
|
if n != len(b) {
|
|
|
|
return io.ErrShortWrite
|
|
|
|
}
|
2021-02-24 03:14:16 -05:00
|
|
|
|
2020-11-02 19:14:31 -05:00
|
|
|
return nil
|
2018-11-14 09:18:35 -05:00
|
|
|
}
|
|
|
|
|
2021-08-10 14:12:22 -04:00
|
|
|
func (s *xlStorage) WriteAll(ctx context.Context, volume string, path string, b []byte) (err error) {
|
|
|
|
return s.writeAll(ctx, volume, path, b, true)
|
|
|
|
}
|
|
|
|
|
2016-10-29 15:44:44 -04:00
|
|
|
// AppendFile - append a byte array at path, if file doesn't exist at
|
|
|
|
// path this call explicitly creates it.
|
2020-09-04 12:45:06 -04:00
|
|
|
func (s *xlStorage) AppendFile(ctx context.Context, volume string, path string, buf []byte) (err error) {
|
2021-02-24 03:14:16 -05:00
|
|
|
volumeDir, err := s.getVolDir(volume)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Stat a volume entry.
|
2021-03-29 11:07:23 -04:00
|
|
|
if err = Access(volumeDir); err != nil {
|
2021-02-24 03:14:16 -05:00
|
|
|
if osIsNotExist(err) {
|
|
|
|
return errVolumeNotFound
|
2021-03-29 11:07:23 -04:00
|
|
|
} else if osIsPermission(err) {
|
|
|
|
return errVolumeAccessDenied
|
|
|
|
} else if isSysErrIO(err) {
|
|
|
|
return errFaultyDisk
|
2021-02-24 03:14:16 -05:00
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-03-24 12:08:05 -04:00
|
|
|
filePath := pathJoin(volumeDir, path)
|
|
|
|
if err = checkPathLength(filePath); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-12-11 19:22:56 -05:00
|
|
|
var w *os.File
|
2019-04-24 00:25:06 -04:00
|
|
|
// Create file if not found. Not doing O_DIRECT here to avoid the code that does buffer aligned writes.
|
|
|
|
// AppendFile() is only used by healing code to heal objects written in old format.
|
2021-08-10 14:12:22 -04:00
|
|
|
w, err = s.openFileSync(filePath, os.O_CREATE|os.O_APPEND|os.O_WRONLY)
|
2016-10-29 15:44:44 -04:00
|
|
|
if err != nil {
|
2016-06-19 18:31:13 -04:00
|
|
|
return err
|
2016-05-04 15:18:40 -04:00
|
|
|
}
|
2021-02-24 03:14:16 -05:00
|
|
|
defer w.Close()
|
2018-11-14 09:18:35 -05:00
|
|
|
|
2021-02-24 03:14:16 -05:00
|
|
|
n, err := w.Write(buf)
|
|
|
|
if err != nil {
|
2018-11-14 09:18:35 -05:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-02-24 03:14:16 -05:00
|
|
|
if n != len(buf) {
|
|
|
|
return io.ErrShortWrite
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
2016-04-08 13:37:38 -04:00
|
|
|
}
|
|
|
|
|
2020-06-12 23:04:01 -04:00
|
|
|
// CheckParts check if path has necessary parts available.
|
2020-09-04 12:45:06 -04:00
|
|
|
func (s *xlStorage) CheckParts(ctx context.Context, volume string, path string, fi FileInfo) error {
|
2016-05-18 00:22:27 -04:00
|
|
|
volumeDir, err := s.getVolDir(volume)
|
|
|
|
if err != nil {
|
2020-06-12 23:04:01 -04:00
|
|
|
return err
|
2016-05-18 00:22:27 -04:00
|
|
|
}
|
2020-06-12 23:04:01 -04:00
|
|
|
|
2016-05-18 00:22:27 -04:00
|
|
|
// Stat a volume entry.
|
2021-03-29 11:07:23 -04:00
|
|
|
if err = Access(volumeDir); err != nil {
|
2020-11-23 11:36:49 -05:00
|
|
|
if osIsNotExist(err) {
|
2020-06-12 23:04:01 -04:00
|
|
|
return errVolumeNotFound
|
2016-05-18 00:22:27 -04:00
|
|
|
}
|
2020-06-12 23:04:01 -04:00
|
|
|
return err
|
2016-04-08 13:37:38 -04:00
|
|
|
}
|
|
|
|
|
2020-06-12 23:04:01 -04:00
|
|
|
for _, part := range fi.Parts {
|
|
|
|
partPath := pathJoin(path, fi.DataDir, fmt.Sprintf("part.%d", part.Number))
|
|
|
|
filePath := pathJoin(volumeDir, partPath)
|
|
|
|
if err = checkPathLength(filePath); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-03-23 17:51:27 -04:00
|
|
|
st, err := Lstat(filePath)
|
2020-06-12 23:04:01 -04:00
|
|
|
if err != nil {
|
|
|
|
return osErrToFileErr(err)
|
|
|
|
}
|
|
|
|
if st.Mode().IsDir() {
|
|
|
|
return errFileNotFound
|
|
|
|
}
|
2020-09-01 15:06:45 -04:00
|
|
|
// Check if shard is truncated.
|
|
|
|
if st.Size() < fi.Erasure.ShardFileSize(part.Size) {
|
|
|
|
return errFileCorrupt
|
|
|
|
}
|
2016-05-11 15:55:02 -04:00
|
|
|
}
|
2020-06-12 23:04:01 -04:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-03-11 11:56:36 -04:00
|
|
|
// deleteFile deletes a file or a directory if its empty unless recursive
|
|
|
|
// is set to true. If the target is successfully deleted, it will recursively
|
|
|
|
// move up the tree, deleting empty parent directories until it finds one
|
|
|
|
// with files in it. Returns nil for a non-empty directory even when
|
|
|
|
// recursive is set to false.
|
2021-03-24 17:19:52 -04:00
|
|
|
func (s *xlStorage) deleteFile(basePath, deletePath string, recursive bool) error {
|
2020-03-11 11:56:36 -04:00
|
|
|
if basePath == "" || deletePath == "" {
|
|
|
|
return nil
|
|
|
|
}
|
2020-06-12 23:04:01 -04:00
|
|
|
isObjectDir := HasSuffix(deletePath, SlashSeparator)
|
2021-02-08 13:15:12 -05:00
|
|
|
basePath = pathutil.Clean(basePath)
|
|
|
|
deletePath = pathutil.Clean(deletePath)
|
2020-03-11 11:56:36 -04:00
|
|
|
if !strings.HasPrefix(deletePath, basePath) || deletePath == basePath {
|
2016-04-08 13:37:38 -04:00
|
|
|
return nil
|
|
|
|
}
|
2017-08-03 23:04:28 -04:00
|
|
|
|
2020-03-11 11:56:36 -04:00
|
|
|
var err error
|
|
|
|
if recursive {
|
2021-10-12 12:24:00 -04:00
|
|
|
err = s.moveToTrash(deletePath, true)
|
2020-03-11 11:56:36 -04:00
|
|
|
} else {
|
2021-03-23 17:51:27 -04:00
|
|
|
err = Remove(deletePath)
|
2020-03-11 11:56:36 -04:00
|
|
|
}
|
|
|
|
if err != nil {
|
2019-03-18 10:46:20 -04:00
|
|
|
switch {
|
|
|
|
case isSysErrNotEmpty(err):
|
2020-06-12 23:04:01 -04:00
|
|
|
// if object is a directory, but if its not empty
|
|
|
|
// return FileNotFound to indicate its an empty prefix.
|
|
|
|
if isObjectDir {
|
|
|
|
return errFileNotFound
|
|
|
|
}
|
2019-03-18 10:46:20 -04:00
|
|
|
// Ignore errors if the directory is not empty. The server relies on
|
|
|
|
// this functionality, and sometimes uses recursion that should not
|
|
|
|
// error on parent directories.
|
2017-08-03 23:04:28 -04:00
|
|
|
return nil
|
2020-11-23 11:36:49 -05:00
|
|
|
case osIsNotExist(err):
|
2021-06-07 12:35:08 -04:00
|
|
|
return nil
|
2021-10-12 12:24:00 -04:00
|
|
|
case errors.Is(err, errFileNotFound):
|
|
|
|
return nil
|
2020-11-23 11:36:49 -05:00
|
|
|
case osIsPermission(err):
|
2016-10-17 19:38:46 -04:00
|
|
|
return errFileAccessDenied
|
2019-03-18 10:46:20 -04:00
|
|
|
case isSysErrIO(err):
|
2018-07-27 18:32:19 -04:00
|
|
|
return errFaultyDisk
|
2019-03-18 10:46:20 -04:00
|
|
|
default:
|
|
|
|
return err
|
2016-10-17 19:38:46 -04:00
|
|
|
}
|
2016-04-08 13:37:38 -04:00
|
|
|
}
|
2017-08-03 23:04:28 -04:00
|
|
|
|
2021-02-08 13:15:12 -05:00
|
|
|
deletePath = pathutil.Dir(deletePath)
|
2018-02-20 18:33:26 -05:00
|
|
|
|
2020-03-11 11:56:36 -04:00
|
|
|
// Delete parent directory obviously not recursively. Errors for
|
|
|
|
// parent directories shouldn't trickle down.
|
2021-03-24 17:19:52 -04:00
|
|
|
s.deleteFile(basePath, deletePath, false)
|
posix: do not upstream errors in deleteFile (#4771)
This commit changes posix's deleteFile() to not upstream errors from
removing parent directories. This fixes a race condition.
The race condition occurs when multiple deleteFile()s are called on the
same parent directory, but different child files. Because deleteFile()
recursively removes parent directories if they are empty, but
deleteFile() errors if the selected deletePath does not exist, there was
an opportunity for a race condition. The two processes would remove the
child directories successfully, then depend on the parent directory
still existing. In some cases this is an invalid assumption, because
other processes can remove the parent directory beforehand. This commit
changes deleteFile() to not upstream an error if one occurs, because the
only required error should be from the immediate deletePath, not from a
parent path.
In the specific bug report, multiple CompleteMultipartUpload requests
would launch multiple deleteFile() requests. Because they chain up on
parent directories, ultimately at the end, there would be multiple
remove files for the ultimate parent directory,
.minio.sys/multipart/{bucket}. Because only one will succeed and one
will fail, an error would be upstreamed saying that the file does not
exist, and the CompleteMultipartUpload code interpreted this as
NoSuchKey, or that the object/part id doesn't exist. This was faulty
behavior and is now fixed.
The added test fails before this change and passes after this change.
Fixes: https://github.com/minio/minio/issues/4727
2017-08-04 19:51:20 -04:00
|
|
|
|
|
|
|
return nil
|
2016-04-08 20:13:16 -04:00
|
|
|
}
|
|
|
|
|
2016-04-08 13:37:38 -04:00
|
|
|
// DeleteFile - delete a file at path.
|
2020-10-28 12:18:35 -04:00
|
|
|
func (s *xlStorage) Delete(ctx context.Context, volume string, path string, recursive bool) (err error) {
|
2016-05-18 00:22:27 -04:00
|
|
|
volumeDir, err := s.getVolDir(volume)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-10-01 16:12:15 -04:00
|
|
|
|
2016-05-18 00:22:27 -04:00
|
|
|
// Stat a volume entry.
|
2021-03-29 11:07:23 -04:00
|
|
|
if err = Access(volumeDir); err != nil {
|
2020-11-23 11:36:49 -05:00
|
|
|
if osIsNotExist(err) {
|
2016-05-18 00:22:27 -04:00
|
|
|
return errVolumeNotFound
|
2020-11-23 11:36:49 -05:00
|
|
|
} else if osIsPermission(err) {
|
2019-10-01 16:12:15 -04:00
|
|
|
return errVolumeAccessDenied
|
2018-07-27 18:32:19 -04:00
|
|
|
} else if isSysErrIO(err) {
|
|
|
|
return errFaultyDisk
|
2016-05-18 00:22:27 -04:00
|
|
|
}
|
2016-04-13 14:32:47 -04:00
|
|
|
return err
|
2016-04-08 13:37:38 -04:00
|
|
|
}
|
|
|
|
|
2019-08-06 15:08:58 -04:00
|
|
|
// Following code is needed so that we retain SlashSeparator suffix if any in
|
2016-04-13 14:32:47 -04:00
|
|
|
// path argument.
|
2016-05-05 04:39:26 -04:00
|
|
|
filePath := pathJoin(volumeDir, path)
|
2020-03-11 11:56:36 -04:00
|
|
|
if err = checkPathLength(filePath); err != nil {
|
2016-05-11 15:55:02 -04:00
|
|
|
return err
|
|
|
|
}
|
2016-04-08 13:37:38 -04:00
|
|
|
|
2020-10-28 12:18:35 -04:00
|
|
|
// Delete file and delete parent directory as well if it's empty.
|
2021-03-24 17:19:52 -04:00
|
|
|
return s.deleteFile(volumeDir, filePath, recursive)
|
2019-05-13 15:25:49 -04:00
|
|
|
}
|
|
|
|
|
2020-06-12 23:04:01 -04:00
|
|
|
// RenameData - rename source path to destination path atomically, metadata and data directory.
|
2021-04-20 13:44:39 -04:00
|
|
|
func (s *xlStorage) RenameData(ctx context.Context, srcVolume, srcPath string, fi FileInfo, dstVolume, dstPath string) (err error) {
|
2021-04-05 11:52:28 -04:00
|
|
|
defer func() {
|
2021-11-21 13:41:30 -05:00
|
|
|
if err != nil {
|
2021-12-15 12:18:09 -05:00
|
|
|
logger.LogIf(ctx, fmt.Errorf("srcVolume: %s, srcPath: %s, dstVolume: %s:, dstPath: %s - error %v",
|
|
|
|
srcVolume, srcPath,
|
|
|
|
dstVolume, dstPath,
|
|
|
|
err))
|
2021-11-21 13:41:30 -05:00
|
|
|
}
|
2021-04-05 11:52:28 -04:00
|
|
|
if err == nil {
|
|
|
|
if s.globalSync {
|
|
|
|
globalSync()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2020-06-12 23:04:01 -04:00
|
|
|
srcVolumeDir, err := s.getVolDir(srcVolume)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
dstVolumeDir, err := s.getVolDir(dstVolume)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Stat a volume entry.
|
2021-03-29 11:07:23 -04:00
|
|
|
if err = Access(srcVolumeDir); err != nil {
|
2020-11-23 11:36:49 -05:00
|
|
|
if osIsNotExist(err) {
|
2020-06-12 23:04:01 -04:00
|
|
|
return errVolumeNotFound
|
|
|
|
} else if isSysErrIO(err) {
|
|
|
|
return errFaultyDisk
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
2020-12-18 11:51:09 -05:00
|
|
|
|
2021-03-29 11:07:23 -04:00
|
|
|
if err = Access(dstVolumeDir); err != nil {
|
2020-11-23 11:36:49 -05:00
|
|
|
if osIsNotExist(err) {
|
2020-06-12 23:04:01 -04:00
|
|
|
return errVolumeNotFound
|
|
|
|
} else if isSysErrIO(err) {
|
|
|
|
return errFaultyDisk
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-02-08 13:15:12 -05:00
|
|
|
srcFilePath := pathutil.Join(srcVolumeDir, pathJoin(srcPath, xlStorageFormatFile))
|
|
|
|
dstFilePath := pathutil.Join(dstVolumeDir, pathJoin(dstPath, xlStorageFormatFile))
|
2020-06-12 23:04:01 -04:00
|
|
|
|
2021-04-02 01:12:03 -04:00
|
|
|
var srcDataPath string
|
|
|
|
var dstDataPath string
|
2021-08-23 16:14:55 -04:00
|
|
|
var dataDir string
|
|
|
|
if !fi.IsRemote() {
|
|
|
|
dataDir = retainSlash(fi.DataDir)
|
|
|
|
}
|
2021-04-26 21:24:06 -04:00
|
|
|
if dataDir != "" {
|
2021-04-02 01:12:03 -04:00
|
|
|
srcDataPath = retainSlash(pathJoin(srcVolumeDir, srcPath, dataDir))
|
|
|
|
// make sure to always use path.Join here, do not use pathJoin as
|
|
|
|
// it would additionally add `/` at the end and it comes in the
|
|
|
|
// way of renameAll(), parentDir creation.
|
|
|
|
dstDataPath = pathutil.Join(dstVolumeDir, dstPath, dataDir)
|
|
|
|
}
|
2020-06-12 23:04:01 -04:00
|
|
|
|
|
|
|
if err = checkPathLength(srcFilePath); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err = checkPathLength(dstFilePath); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-02-24 03:14:16 -05:00
|
|
|
dstBuf, err := xioutil.ReadFile(dstFilePath)
|
2020-06-19 13:58:17 -04:00
|
|
|
if err != nil {
|
2021-08-03 16:26:57 -04:00
|
|
|
// handle situations when dstFilePath is 'file'
|
|
|
|
// for example such as someone is trying to
|
|
|
|
// upload an object such as `prefix/object/xl.meta`
|
|
|
|
// where `prefix/object` is already an object
|
|
|
|
if isSysErrNotDir(err) && runtime.GOOS != globalWindowsOSName {
|
|
|
|
// NOTE: On windows the error happens at
|
|
|
|
// next line and returns appropriate error.
|
|
|
|
return errFileAccessDenied
|
|
|
|
}
|
2020-11-23 11:36:49 -05:00
|
|
|
if !osIsNotExist(err) {
|
2020-06-19 13:58:17 -04:00
|
|
|
return osErrToFileErr(err)
|
|
|
|
}
|
2021-01-11 05:27:04 -05:00
|
|
|
// errFileNotFound comes here.
|
|
|
|
err = s.renameLegacyMetadata(dstVolumeDir, dstPath)
|
2020-06-19 13:58:17 -04:00
|
|
|
if err != nil && err != errFileNotFound {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err == nil {
|
2021-02-24 03:14:16 -05:00
|
|
|
dstBuf, err = xioutil.ReadFile(dstFilePath)
|
2020-11-23 11:36:49 -05:00
|
|
|
if err != nil && !osIsNotExist(err) {
|
2020-06-19 13:58:17 -04:00
|
|
|
return osErrToFileErr(err)
|
|
|
|
}
|
|
|
|
}
|
2020-06-12 23:04:01 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
var xlMeta xlMetaV2
|
|
|
|
var legacyPreserved bool
|
|
|
|
if len(dstBuf) > 0 {
|
|
|
|
if isXL2V1Format(dstBuf) {
|
|
|
|
if err = xlMeta.Load(dstBuf); err != nil {
|
2021-11-21 13:41:30 -05:00
|
|
|
logger.LogIf(ctx, err)
|
2021-09-14 14:34:25 -04:00
|
|
|
// Data appears corrupt. Drop data.
|
|
|
|
xlMeta = xlMetaV2{}
|
2020-06-12 23:04:01 -04:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// This code-path is to preserve the legacy data.
|
|
|
|
xlMetaLegacy := &xlMetaV1Object{}
|
2022-01-02 12:15:06 -05:00
|
|
|
json := jsoniter.ConfigCompatibleWithStandardLibrary
|
2020-06-12 23:04:01 -04:00
|
|
|
if err := json.Unmarshal(dstBuf, xlMetaLegacy); err != nil {
|
2021-11-21 13:41:30 -05:00
|
|
|
logger.LogIf(ctx, err)
|
2021-09-14 14:34:25 -04:00
|
|
|
// Data appears corrupt. Drop data.
|
|
|
|
} else {
|
2021-12-02 14:29:16 -05:00
|
|
|
xlMetaLegacy.DataDir = legacyDataDir
|
2021-09-14 14:34:25 -04:00
|
|
|
if err = xlMeta.AddLegacy(xlMetaLegacy); err != nil {
|
2021-11-21 13:41:30 -05:00
|
|
|
logger.LogIf(ctx, err)
|
2021-09-14 14:34:25 -04:00
|
|
|
}
|
|
|
|
legacyPreserved = true
|
2020-06-12 23:04:01 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2021-04-06 14:33:42 -04:00
|
|
|
s.RLock()
|
|
|
|
formatLegacy := s.formatLegacy
|
|
|
|
s.RUnlock()
|
2020-06-12 23:04:01 -04:00
|
|
|
// It is possible that some drives may not have `xl.meta` file
|
|
|
|
// in such scenarios verify if atleast `part.1` files exist
|
|
|
|
// to verify for legacy version.
|
2021-04-06 14:33:42 -04:00
|
|
|
if formatLegacy {
|
2021-01-19 13:01:06 -05:00
|
|
|
// We only need this code if we are moving
|
|
|
|
// from `xl.json` to `xl.meta`, we can avoid
|
|
|
|
// one extra readdir operation here for all
|
|
|
|
// new deployments.
|
|
|
|
currentDataPath := pathJoin(dstVolumeDir, dstPath)
|
|
|
|
entries, err := readDirN(currentDataPath, 1)
|
|
|
|
if err != nil && err != errFileNotFound {
|
|
|
|
return osErrToFileErr(err)
|
2020-06-12 23:04:01 -04:00
|
|
|
}
|
2021-01-19 13:01:06 -05:00
|
|
|
for _, entry := range entries {
|
|
|
|
if entry == xlStorageFormatFile || strings.HasSuffix(entry, slashSeparator) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if strings.HasPrefix(entry, "part.") {
|
|
|
|
legacyPreserved = true
|
|
|
|
break
|
|
|
|
}
|
2020-06-12 23:04:01 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-07 12:35:08 -04:00
|
|
|
legacyDataPath := pathJoin(dstVolumeDir, dstPath, legacyDataDir)
|
2020-06-12 23:04:01 -04:00
|
|
|
if legacyPreserved {
|
|
|
|
// Preserve all the legacy data, could be slow, but at max there can be 10,000 parts.
|
|
|
|
currentDataPath := pathJoin(dstVolumeDir, dstPath)
|
|
|
|
entries, err := readDir(currentDataPath)
|
|
|
|
if err != nil {
|
|
|
|
return osErrToFileErr(err)
|
|
|
|
}
|
2020-08-05 16:31:12 -04:00
|
|
|
|
2020-06-12 23:04:01 -04:00
|
|
|
// legacy data dir means its old content, honor system umask.
|
2022-01-02 12:15:06 -05:00
|
|
|
if err = mkdirAll(legacyDataPath, 0o777); err != nil {
|
2021-06-07 12:35:08 -04:00
|
|
|
// any failed mkdir-calls delete them.
|
|
|
|
s.deleteFile(dstVolumeDir, legacyDataPath, true)
|
2020-06-12 23:04:01 -04:00
|
|
|
return osErrToFileErr(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, entry := range entries {
|
2020-08-05 16:31:12 -04:00
|
|
|
// Skip xl.meta renames further, also ignore any directories such as `legacyDataDir`
|
2020-08-07 16:22:53 -04:00
|
|
|
if entry == xlStorageFormatFile || strings.HasSuffix(entry, slashSeparator) {
|
2020-06-12 23:04:01 -04:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2021-03-23 17:51:27 -04:00
|
|
|
if err = Rename(pathJoin(currentDataPath, entry), pathJoin(legacyDataPath, entry)); err != nil {
|
2021-06-07 12:35:08 -04:00
|
|
|
// Any failed rename calls un-roll previous transaction.
|
|
|
|
s.deleteFile(dstVolumeDir, legacyDataPath, true)
|
|
|
|
|
2020-06-12 23:04:01 -04:00
|
|
|
return osErrToFileErr(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var oldDstDataPath string
|
|
|
|
if fi.VersionID == "" {
|
|
|
|
// return the latest "null" versionId info
|
|
|
|
ofi, err := xlMeta.ToFileInfo(dstVolume, dstPath, nullVersionID)
|
2020-09-02 03:19:03 -04:00
|
|
|
if err == nil && !ofi.Deleted {
|
2021-04-15 11:44:05 -04:00
|
|
|
if xlMeta.SharedDataDirCountStr(nullVersionID, ofi.DataDir) == 0 {
|
|
|
|
// Purge the destination path as we are not preserving anything
|
|
|
|
// versioned object was not requested.
|
|
|
|
oldDstDataPath = pathJoin(dstVolumeDir, dstPath, ofi.DataDir)
|
2021-07-26 18:15:34 -04:00
|
|
|
// if old destination path is same as new destination path
|
|
|
|
// there is nothing to purge, this is true in case of healing
|
|
|
|
// avoid setting oldDstDataPath at that point.
|
|
|
|
if oldDstDataPath == dstDataPath {
|
|
|
|
oldDstDataPath = ""
|
|
|
|
}
|
2021-05-25 19:33:06 -04:00
|
|
|
xlMeta.data.remove(nullVersionID, ofi.DataDir)
|
2021-04-15 11:44:05 -04:00
|
|
|
}
|
2020-06-12 23:04:01 -04:00
|
|
|
}
|
2021-06-30 22:32:07 -04:00
|
|
|
// Empty fi.VersionID indicates that versioning is either
|
|
|
|
// suspended or disabled on this bucket. RenameData will replace
|
|
|
|
// the 'null' version. We add a free-version to track its tiered
|
|
|
|
// content for asynchronous deletion.
|
|
|
|
xlMeta.AddFreeVersion(fi)
|
2020-06-12 23:04:01 -04:00
|
|
|
}
|
|
|
|
|
2021-11-17 18:49:12 -05:00
|
|
|
// indicates if RenameData() is called by healing.
|
|
|
|
// healing doesn't preserve the dataDir as 'legacy'
|
|
|
|
healing := fi.XLV1 && fi.DataDir != legacyDataDir
|
|
|
|
|
2020-06-12 23:04:01 -04:00
|
|
|
if err = xlMeta.AddVersion(fi); err != nil {
|
2021-06-07 12:35:08 -04:00
|
|
|
if legacyPreserved {
|
|
|
|
// Any failed rename calls un-roll previous transaction.
|
|
|
|
s.deleteFile(dstVolumeDir, legacyDataPath, true)
|
|
|
|
}
|
2020-06-12 23:04:01 -04:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-08-30 11:38:11 -04:00
|
|
|
dstBuf, err = xlMeta.AppendTo(metaDataPoolGet())
|
|
|
|
defer metaDataPoolPut(dstBuf)
|
2020-06-12 23:04:01 -04:00
|
|
|
if err != nil {
|
2021-03-29 20:00:55 -04:00
|
|
|
logger.LogIf(ctx, err)
|
2021-06-07 12:35:08 -04:00
|
|
|
if legacyPreserved {
|
|
|
|
// Any failed rename calls un-roll previous transaction.
|
|
|
|
s.deleteFile(dstVolumeDir, legacyDataPath, true)
|
|
|
|
}
|
2020-06-12 23:04:01 -04:00
|
|
|
return errFileCorrupt
|
|
|
|
}
|
|
|
|
|
2021-04-02 01:12:03 -04:00
|
|
|
if srcDataPath != "" {
|
|
|
|
if err = s.WriteAll(ctx, srcVolume, pathJoin(srcPath, xlStorageFormatFile), dstBuf); err != nil {
|
2021-06-07 12:35:08 -04:00
|
|
|
if legacyPreserved {
|
|
|
|
// Any failed rename calls un-roll previous transaction.
|
|
|
|
s.deleteFile(dstVolumeDir, legacyDataPath, true)
|
|
|
|
}
|
2021-04-02 01:12:03 -04:00
|
|
|
return err
|
|
|
|
}
|
2021-03-29 20:00:55 -04:00
|
|
|
|
2021-04-02 01:12:03 -04:00
|
|
|
// renameAll only for objects that have xl.meta not saved inline.
|
|
|
|
if len(fi.Data) == 0 && fi.Size > 0 {
|
2021-10-12 12:24:00 -04:00
|
|
|
s.moveToTrash(dstDataPath, true)
|
2021-11-17 18:49:12 -05:00
|
|
|
if healing {
|
|
|
|
// If we are healing we should purge any legacyDataPath content,
|
|
|
|
// that was previously preserved during PutObject() call
|
|
|
|
// on a versioned bucket.
|
|
|
|
s.moveToTrash(legacyDataPath, true)
|
|
|
|
}
|
2021-04-02 01:12:03 -04:00
|
|
|
if err = renameAll(srcDataPath, dstDataPath); err != nil {
|
2021-06-07 12:35:08 -04:00
|
|
|
if legacyPreserved {
|
|
|
|
// Any failed rename calls un-roll previous transaction.
|
|
|
|
s.deleteFile(dstVolumeDir, legacyDataPath, true)
|
|
|
|
}
|
|
|
|
s.deleteFile(dstVolumeDir, dstDataPath, false)
|
2021-10-20 14:31:09 -04:00
|
|
|
if err != errFileNotFound {
|
|
|
|
logger.LogIf(ctx, err)
|
|
|
|
}
|
2021-04-02 01:12:03 -04:00
|
|
|
return osErrToFileErr(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Commit meta-file
|
|
|
|
if err = renameAll(srcFilePath, dstFilePath); err != nil {
|
2021-06-07 12:35:08 -04:00
|
|
|
if legacyPreserved {
|
|
|
|
// Any failed rename calls un-roll previous transaction.
|
|
|
|
s.deleteFile(dstVolumeDir, legacyDataPath, true)
|
|
|
|
}
|
|
|
|
s.deleteFile(dstVolumeDir, dstFilePath, false)
|
|
|
|
|
2021-10-20 14:31:09 -04:00
|
|
|
if err != errFileNotFound {
|
|
|
|
logger.LogIf(ctx, err)
|
|
|
|
}
|
2021-04-02 01:12:03 -04:00
|
|
|
return osErrToFileErr(err)
|
|
|
|
}
|
2021-06-07 12:35:08 -04:00
|
|
|
|
|
|
|
// additionally only purge older data at the end of the transaction of new data-dir
|
|
|
|
// movement, this is to ensure that previous data references can co-exist for
|
|
|
|
// any recoverability.
|
|
|
|
if oldDstDataPath != "" {
|
2021-10-12 12:24:00 -04:00
|
|
|
s.moveToTrash(oldDstDataPath, true)
|
2021-06-07 12:35:08 -04:00
|
|
|
}
|
2021-04-02 01:12:03 -04:00
|
|
|
} else {
|
|
|
|
// Write meta-file directly, no data
|
|
|
|
if err = s.WriteAll(ctx, dstVolume, pathJoin(dstPath, xlStorageFormatFile), dstBuf); err != nil {
|
2021-06-07 12:35:08 -04:00
|
|
|
if legacyPreserved {
|
|
|
|
// Any failed rename calls un-roll previous transaction.
|
|
|
|
s.deleteFile(dstVolumeDir, legacyDataPath, true)
|
|
|
|
}
|
|
|
|
s.deleteFile(dstVolumeDir, dstFilePath, false)
|
|
|
|
|
2021-04-20 13:44:39 -04:00
|
|
|
logger.LogIf(ctx, err)
|
2021-04-02 01:12:03 -04:00
|
|
|
return err
|
|
|
|
}
|
2020-10-24 00:54:58 -04:00
|
|
|
}
|
|
|
|
|
2021-06-07 12:35:08 -04:00
|
|
|
// srcFilePath is always in minioMetaTmpBucket, an attempt to
|
|
|
|
// remove the temporary folder is enough since at this point
|
|
|
|
// ideally all transaction should be complete.
|
|
|
|
|
|
|
|
Remove(pathutil.Dir(srcFilePath))
|
2020-06-12 23:04:01 -04:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-05-28 18:13:15 -04:00
|
|
|
// RenameFile - rename source path to destination path atomically.
|
2020-09-04 12:45:06 -04:00
|
|
|
func (s *xlStorage) RenameFile(ctx context.Context, srcVolume, srcPath, dstVolume, dstPath string) (err error) {
|
2016-05-18 00:22:27 -04:00
|
|
|
srcVolumeDir, err := s.getVolDir(srcVolume)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
dstVolumeDir, err := s.getVolDir(dstVolume)
|
2016-04-29 15:17:48 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2016-05-18 00:22:27 -04:00
|
|
|
// Stat a volume entry.
|
2021-03-29 11:07:23 -04:00
|
|
|
if err = Access(srcVolumeDir); err != nil {
|
2020-11-23 11:36:49 -05:00
|
|
|
if osIsNotExist(err) {
|
2016-05-18 00:22:27 -04:00
|
|
|
return errVolumeNotFound
|
2018-07-27 18:32:19 -04:00
|
|
|
} else if isSysErrIO(err) {
|
|
|
|
return errFaultyDisk
|
2016-05-18 00:22:27 -04:00
|
|
|
}
|
2016-04-29 15:17:48 -04:00
|
|
|
return err
|
|
|
|
}
|
2021-03-29 11:07:23 -04:00
|
|
|
|
|
|
|
if err = Access(dstVolumeDir); err != nil {
|
2020-11-23 11:36:49 -05:00
|
|
|
if osIsNotExist(err) {
|
2016-05-18 00:22:27 -04:00
|
|
|
return errVolumeNotFound
|
2018-07-27 18:32:19 -04:00
|
|
|
} else if isSysErrIO(err) {
|
|
|
|
return errFaultyDisk
|
2016-05-18 00:22:27 -04:00
|
|
|
}
|
2020-04-11 14:15:30 -04:00
|
|
|
return err
|
2016-05-18 00:22:27 -04:00
|
|
|
}
|
|
|
|
|
2019-12-06 02:16:06 -05:00
|
|
|
srcIsDir := HasSuffix(srcPath, SlashSeparator)
|
|
|
|
dstIsDir := HasSuffix(dstPath, SlashSeparator)
|
2016-05-16 17:31:28 -04:00
|
|
|
// Either src and dst have to be directories or files, else return error.
|
2016-05-13 14:52:36 -04:00
|
|
|
if !(srcIsDir && dstIsDir || !srcIsDir && !dstIsDir) {
|
|
|
|
return errFileAccessDenied
|
|
|
|
}
|
2021-02-08 13:15:12 -05:00
|
|
|
srcFilePath := pathutil.Join(srcVolumeDir, srcPath)
|
2018-02-20 15:20:18 -05:00
|
|
|
if err = checkPathLength(srcFilePath); err != nil {
|
2016-06-17 14:57:51 -04:00
|
|
|
return err
|
|
|
|
}
|
2021-02-08 13:15:12 -05:00
|
|
|
dstFilePath := pathutil.Join(dstVolumeDir, dstPath)
|
2018-02-20 15:20:18 -05:00
|
|
|
if err = checkPathLength(dstFilePath); err != nil {
|
2016-06-17 14:57:51 -04:00
|
|
|
return err
|
|
|
|
}
|
2016-05-13 14:52:36 -04:00
|
|
|
if srcIsDir {
|
2018-02-20 15:20:18 -05:00
|
|
|
// If source is a directory, we expect the destination to be non-existent but we
|
|
|
|
// we still need to allow overwriting an empty directory since it represents
|
|
|
|
// an object empty directory.
|
2021-03-23 17:51:27 -04:00
|
|
|
dirInfo, err := Lstat(dstFilePath)
|
2018-07-27 18:32:19 -04:00
|
|
|
if isSysErrIO(err) {
|
|
|
|
return errFaultyDisk
|
|
|
|
}
|
2021-03-19 18:42:01 -04:00
|
|
|
if err != nil {
|
|
|
|
if !osIsNotExist(err) {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if !dirInfo.IsDir() {
|
|
|
|
return errFileAccessDenied
|
|
|
|
}
|
2021-03-23 17:51:27 -04:00
|
|
|
if err = Remove(dstFilePath); err != nil {
|
2019-03-26 17:57:44 -04:00
|
|
|
if isSysErrNotEmpty(err) {
|
|
|
|
return errFileAccessDenied
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2016-05-13 14:52:36 -04:00
|
|
|
}
|
2018-01-13 12:13:02 -05:00
|
|
|
|
|
|
|
if err = renameAll(srcFilePath, dstFilePath); err != nil {
|
2020-08-07 16:22:53 -04:00
|
|
|
return osErrToFileErr(err)
|
2016-05-03 19:10:24 -04:00
|
|
|
}
|
2016-11-21 19:34:57 -05:00
|
|
|
|
|
|
|
// Remove parent dir of the source file if empty
|
2021-03-19 18:42:01 -04:00
|
|
|
parentDir := pathutil.Dir(srcFilePath)
|
2021-03-24 17:19:52 -04:00
|
|
|
s.deleteFile(srcVolumeDir, parentDir, false)
|
2016-11-21 19:34:57 -05:00
|
|
|
|
2016-05-03 19:10:24 -04:00
|
|
|
return nil
|
2016-04-29 15:17:48 -04:00
|
|
|
}
|
2019-07-08 16:51:18 -04:00
|
|
|
|
2020-06-12 23:04:01 -04:00
|
|
|
func (s *xlStorage) bitrotVerify(partPath string, partSize int64, algo BitrotAlgorithm, sum []byte, shardSize int64) error {
|
2019-07-08 16:51:18 -04:00
|
|
|
// Open the file for reading.
|
2021-03-23 17:51:27 -04:00
|
|
|
file, err := Open(partPath)
|
2019-07-08 16:51:18 -04:00
|
|
|
if err != nil {
|
2020-06-12 23:04:01 -04:00
|
|
|
return osErrToFileErr(err)
|
2019-07-08 16:51:18 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Close the file descriptor.
|
|
|
|
defer file.Close()
|
|
|
|
fi, err := file.Stat()
|
|
|
|
if err != nil {
|
2019-10-01 16:12:15 -04:00
|
|
|
// Unable to stat on the file, return an expected error
|
|
|
|
// for healing code to fix this file.
|
2019-07-08 16:51:18 -04:00
|
|
|
return err
|
|
|
|
}
|
2021-03-29 20:00:55 -04:00
|
|
|
return bitrotVerify(file, fi.Size(), partSize, algo, sum, shardSize)
|
2019-07-08 16:51:18 -04:00
|
|
|
}
|
2020-06-12 23:04:01 -04:00
|
|
|
|
2020-09-04 12:45:06 -04:00
|
|
|
func (s *xlStorage) VerifyFile(ctx context.Context, volume, path string, fi FileInfo) (err error) {
|
2020-06-12 23:04:01 -04:00
|
|
|
volumeDir, err := s.getVolDir(volume)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Stat a volume entry.
|
2021-03-29 11:07:23 -04:00
|
|
|
if err = Access(volumeDir); err != nil {
|
2020-11-23 11:36:49 -05:00
|
|
|
if osIsNotExist(err) {
|
2020-06-12 23:04:01 -04:00
|
|
|
return errVolumeNotFound
|
|
|
|
} else if isSysErrIO(err) {
|
|
|
|
return errFaultyDisk
|
2020-11-23 11:36:49 -05:00
|
|
|
} else if osIsPermission(err) {
|
2020-06-12 23:04:01 -04:00
|
|
|
return errVolumeAccessDenied
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
erasure := fi.Erasure
|
|
|
|
for _, part := range fi.Parts {
|
|
|
|
checksumInfo := erasure.GetChecksumInfo(part.Number)
|
|
|
|
partPath := pathJoin(volumeDir, path, fi.DataDir, fmt.Sprintf("part.%d", part.Number))
|
|
|
|
if err := s.bitrotVerify(partPath,
|
|
|
|
erasure.ShardFileSize(part.Size),
|
|
|
|
checksumInfo.Algorithm,
|
|
|
|
checksumInfo.Hash, erasure.ShardSize()); err != nil {
|
|
|
|
if !IsErr(err, []error{
|
|
|
|
errFileNotFound,
|
|
|
|
errVolumeNotFound,
|
|
|
|
errFileCorrupt,
|
|
|
|
}...) {
|
2021-11-21 13:41:30 -05:00
|
|
|
logger.GetReqInfo(ctx).AppendTags("disk", s.String())
|
|
|
|
logger.LogIf(ctx, err)
|
2020-06-12 23:04:01 -04:00
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2021-07-09 14:29:16 -04:00
|
|
|
|
2021-10-01 14:50:00 -04:00
|
|
|
func (s *xlStorage) StatInfoFile(ctx context.Context, volume, path string, glob bool) (stat []StatInfo, err error) {
|
2021-07-09 14:29:16 -04:00
|
|
|
volumeDir, err := s.getVolDir(volume)
|
|
|
|
if err != nil {
|
|
|
|
return stat, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Stat a volume entry.
|
|
|
|
if err = Access(volumeDir); err != nil {
|
|
|
|
if osIsNotExist(err) {
|
|
|
|
return stat, errVolumeNotFound
|
|
|
|
} else if isSysErrIO(err) {
|
|
|
|
return stat, errFaultyDisk
|
|
|
|
} else if osIsPermission(err) {
|
|
|
|
return stat, errVolumeAccessDenied
|
|
|
|
}
|
|
|
|
return stat, err
|
|
|
|
}
|
2022-01-02 12:15:06 -05:00
|
|
|
files := []string{pathJoin(volumeDir, path)}
|
2021-10-01 14:50:00 -04:00
|
|
|
if glob {
|
|
|
|
files, err = filepathx.Glob(pathJoin(volumeDir, path))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2021-07-09 14:29:16 -04:00
|
|
|
}
|
2021-10-01 14:50:00 -04:00
|
|
|
for _, filePath := range files {
|
|
|
|
if err := checkPathLength(filePath); err != nil {
|
|
|
|
return stat, err
|
|
|
|
}
|
|
|
|
st, _ := Lstat(filePath)
|
|
|
|
if st == nil {
|
|
|
|
return stat, errPathNotFound
|
|
|
|
}
|
|
|
|
name, err := filepath.Rel(volumeDir, filePath)
|
|
|
|
if err != nil {
|
|
|
|
name = filePath
|
|
|
|
}
|
2021-11-16 12:28:29 -05:00
|
|
|
stat = append(stat, StatInfo{
|
|
|
|
Name: filepath.ToSlash(name),
|
|
|
|
Size: st.Size(),
|
|
|
|
Dir: st.IsDir(),
|
|
|
|
Mode: uint32(st.Mode()),
|
|
|
|
ModTime: st.ModTime(),
|
|
|
|
})
|
2021-07-09 14:29:16 -04:00
|
|
|
}
|
2021-10-01 14:50:00 -04:00
|
|
|
return stat, nil
|
2021-07-09 14:29:16 -04:00
|
|
|
}
|