// 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 . 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 = MkdirAll(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 inexistant 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), 0777); 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 }