mirror of
https://github.com/minio/minio.git
synced 2024-12-26 15:15:55 -05:00
7725425e05
a/b/c/d/ where `a/b/c/` exists results in additional syscalls such as an Lstat() call to verify if the `a/b/c/` exists and its a directory. We do not need to do this on MinIO since the parent prefixes if exist, we can simply return success without spending additional syscalls. Also this implementation attempts to simply use Access() calls to avoid os.Stat() calls since the latter does memory allocation for things we do not need to use. Access() is simpler since we have a predictable structure on the backend and we know exactly how our path structures are.
184 lines
5.3 KiB
Go
184 lines
5.3 KiB
Go
// 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/>.
|
|
|
|
package cmd
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path"
|
|
)
|
|
|
|
// Wrapper functions to os.RemoveAll, which calls reliableRemoveAll
|
|
// this is to ensure that if there is a racy parent directory
|
|
// create in between we can simply retry the operation.
|
|
func removeAll(dirPath string) (err error) {
|
|
if dirPath == "" {
|
|
return errInvalidArgument
|
|
}
|
|
|
|
if err = checkPathLength(dirPath); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = reliableRemoveAll(dirPath); err != nil {
|
|
switch {
|
|
case isSysErrNotDir(err):
|
|
// File path cannot be verified since one of
|
|
// the parents is a file.
|
|
return errFileAccessDenied
|
|
case isSysErrPathNotFound(err):
|
|
// This is a special case should be handled only for
|
|
// windows, because windows API does not return "not a
|
|
// directory" error message. Handle this specifically
|
|
// here.
|
|
return errFileAccessDenied
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
// Reliably retries os.RemoveAll if for some reason os.RemoveAll returns
|
|
// syscall.ENOTEMPTY (children has files).
|
|
func reliableRemoveAll(dirPath string) (err error) {
|
|
i := 0
|
|
for {
|
|
// Removes all the directories and files.
|
|
if err = RemoveAll(dirPath); err != nil {
|
|
// Retry only for the first retryable error.
|
|
if isSysErrNotEmpty(err) && i == 0 {
|
|
i++
|
|
continue
|
|
}
|
|
}
|
|
break
|
|
}
|
|
return err
|
|
}
|
|
|
|
// Wrapper functions to os.MkdirAll, which calls reliableMkdirAll
|
|
// this is to ensure that if there is a racy parent directory
|
|
// delete in between we can simply retry the operation.
|
|
func mkdirAll(dirPath string, mode os.FileMode) (err error) {
|
|
if dirPath == "" {
|
|
return errInvalidArgument
|
|
}
|
|
|
|
if err = checkPathLength(dirPath); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = reliableMkdirAll(dirPath, mode); err != nil {
|
|
// File path cannot be verified since one of the parents is a file.
|
|
if isSysErrNotDir(err) {
|
|
return errFileAccessDenied
|
|
} else if isSysErrPathNotFound(err) {
|
|
// This is a special case should be handled only for
|
|
// windows, because windows API does not return "not a
|
|
// directory" error message. Handle this specifically here.
|
|
return errFileAccessDenied
|
|
}
|
|
return osErrToFileErr(err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Reliably retries os.MkdirAll if for some reason os.MkdirAll returns
|
|
// syscall.ENOENT (parent does not exist).
|
|
func reliableMkdirAll(dirPath string, mode os.FileMode) (err error) {
|
|
i := 0
|
|
for {
|
|
// Creates all the parent directories, with mode 0777 mkdir honors system umask.
|
|
if err = osMkdirAll(dirPath, mode); err != nil {
|
|
// Retry only for the first retryable error.
|
|
if osIsNotExist(err) && i == 0 {
|
|
i++
|
|
continue
|
|
}
|
|
}
|
|
break
|
|
}
|
|
return err
|
|
}
|
|
|
|
// Wrapper function to os.Rename, which calls reliableMkdirAll
|
|
// and reliableRenameAll. This is to ensure that if there is a
|
|
// racy parent directory delete in between we can simply retry
|
|
// the operation.
|
|
func renameAll(srcFilePath, dstFilePath string) (err error) {
|
|
if srcFilePath == "" || dstFilePath == "" {
|
|
return errInvalidArgument
|
|
}
|
|
|
|
if err = checkPathLength(srcFilePath); err != nil {
|
|
return err
|
|
}
|
|
if err = checkPathLength(dstFilePath); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = reliableRename(srcFilePath, dstFilePath); err != nil {
|
|
switch {
|
|
case isSysErrNotDir(err) && !osIsNotExist(err):
|
|
// Windows can have both isSysErrNotDir(err) and osIsNotExist(err) returning
|
|
// true if the source file path contains an non-existent directory. In that case,
|
|
// we want to return errFileNotFound instead, which will honored in subsequent
|
|
// switch cases
|
|
return errFileAccessDenied
|
|
case isSysErrPathNotFound(err):
|
|
// This is a special case should be handled only for
|
|
// windows, because windows API does not return "not a
|
|
// directory" error message. Handle this specifically here.
|
|
return errFileAccessDenied
|
|
case isSysErrCrossDevice(err):
|
|
return fmt.Errorf("%w (%s)->(%s)", errCrossDeviceLink, srcFilePath, dstFilePath)
|
|
case osIsNotExist(err):
|
|
return errFileNotFound
|
|
case osIsExist(err):
|
|
// 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
|
|
}
|
|
|
|
// Reliably retries os.RenameAll if for some reason os.RenameAll returns
|
|
// syscall.ENOENT (parent does not exist).
|
|
func reliableRename(srcFilePath, dstFilePath string) (err error) {
|
|
if err = reliableMkdirAll(path.Dir(dstFilePath), 0o777); err != nil {
|
|
return err
|
|
}
|
|
|
|
i := 0
|
|
for {
|
|
// After a successful parent directory create attempt a renameAll.
|
|
if err = Rename(srcFilePath, dstFilePath); err != nil {
|
|
// Retry only for the first retryable error.
|
|
if osIsNotExist(err) && i == 0 {
|
|
i++
|
|
continue
|
|
}
|
|
}
|
|
break
|
|
}
|
|
return err
|
|
}
|