github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/cmd/os-reliable.go (about) 1 // Copyright (c) 2015-2021 MinIO, Inc. 2 // 3 // This file is part of MinIO Object Storage stack 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package cmd 19 20 import ( 21 "fmt" 22 "os" 23 "path" 24 ) 25 26 // Wrapper functions to os.RemoveAll, which calls reliableRemoveAll 27 // this is to ensure that if there is a racy parent directory 28 // create in between we can simply retry the operation. 29 func removeAll(dirPath string) (err error) { 30 if dirPath == "" { 31 return errInvalidArgument 32 } 33 34 if err = checkPathLength(dirPath); err != nil { 35 return err 36 } 37 38 if err = reliableRemoveAll(dirPath); err != nil { 39 switch { 40 case isSysErrNotDir(err): 41 // File path cannot be verified since one of 42 // the parents is a file. 43 return errFileAccessDenied 44 case isSysErrPathNotFound(err): 45 // This is a special case should be handled only for 46 // windows, because windows API does not return "not a 47 // directory" error message. Handle this specifically 48 // here. 49 return errFileAccessDenied 50 } 51 } 52 return err 53 } 54 55 // Reliably retries os.RemoveAll if for some reason os.RemoveAll returns 56 // syscall.ENOTEMPTY (children has files). 57 func reliableRemoveAll(dirPath string) (err error) { 58 i := 0 59 for { 60 // Removes all the directories and files. 61 if err = RemoveAll(dirPath); err != nil { 62 // Retry only for the first retryable error. 63 if isSysErrNotEmpty(err) && i == 0 { 64 i++ 65 continue 66 } 67 } 68 break 69 } 70 return err 71 } 72 73 // Wrapper functions to os.MkdirAll, which calls reliableMkdirAll 74 // this is to ensure that if there is a racy parent directory 75 // delete in between we can simply retry the operation. 76 func mkdirAll(dirPath string, mode os.FileMode, baseDir string) (err error) { 77 if dirPath == "" { 78 return errInvalidArgument 79 } 80 81 if err = checkPathLength(dirPath); err != nil { 82 return err 83 } 84 85 if err = reliableMkdirAll(dirPath, mode, baseDir); err != nil { 86 // File path cannot be verified since one of the parents is a file. 87 if isSysErrNotDir(err) { 88 return errFileAccessDenied 89 } else if isSysErrPathNotFound(err) { 90 // This is a special case should be handled only for 91 // windows, because windows API does not return "not a 92 // directory" error message. Handle this specifically here. 93 return errFileAccessDenied 94 } 95 return osErrToFileErr(err) 96 } 97 98 return nil 99 } 100 101 // Reliably retries os.MkdirAll if for some reason os.MkdirAll returns 102 // syscall.ENOENT (parent does not exist). 103 func reliableMkdirAll(dirPath string, mode os.FileMode, baseDir string) (err error) { 104 i := 0 105 for { 106 // Creates all the parent directories, with mode 0777 mkdir honors system umask. 107 if err = osMkdirAll(dirPath, mode, baseDir); err != nil { 108 // Retry only for the first retryable error. 109 if osIsNotExist(err) && i == 0 { 110 i++ 111 continue 112 } 113 } 114 break 115 } 116 return err 117 } 118 119 // Wrapper function to os.Rename, which calls reliableMkdirAll 120 // and reliableRenameAll. This is to ensure that if there is a 121 // racy parent directory delete in between we can simply retry 122 // the operation. 123 func renameAll(srcFilePath, dstFilePath, baseDir string) (err error) { 124 if srcFilePath == "" || dstFilePath == "" { 125 return errInvalidArgument 126 } 127 128 if err = checkPathLength(srcFilePath); err != nil { 129 return err 130 } 131 if err = checkPathLength(dstFilePath); err != nil { 132 return err 133 } 134 135 if err = reliableRename(srcFilePath, dstFilePath, baseDir); err != nil { 136 switch { 137 case isSysErrNotDir(err) && !osIsNotExist(err): 138 // Windows can have both isSysErrNotDir(err) and osIsNotExist(err) returning 139 // true if the source file path contains an non-existent directory. In that case, 140 // we want to return errFileNotFound instead, which will honored in subsequent 141 // switch cases 142 return errFileAccessDenied 143 case isSysErrPathNotFound(err): 144 // This is a special case should be handled only for 145 // windows, because windows API does not return "not a 146 // directory" error message. Handle this specifically here. 147 return errFileAccessDenied 148 case isSysErrCrossDevice(err): 149 return fmt.Errorf("%w (%s)->(%s)", errCrossDeviceLink, srcFilePath, dstFilePath) 150 case osIsNotExist(err): 151 return errFileNotFound 152 case osIsExist(err): 153 // This is returned only when destination is a directory and we 154 // are attempting a rename from file to directory. 155 return errIsNotRegular 156 default: 157 return err 158 } 159 } 160 return nil 161 } 162 163 // Reliably retries os.RenameAll if for some reason os.RenameAll returns 164 // syscall.ENOENT (parent does not exist). 165 func reliableRename(srcFilePath, dstFilePath, baseDir string) (err error) { 166 if err = reliableMkdirAll(path.Dir(dstFilePath), 0o777, baseDir); err != nil { 167 return err 168 } 169 170 i := 0 171 for { 172 // After a successful parent directory create attempt a renameAll. 173 if err = Rename(srcFilePath, dstFilePath); err != nil { 174 // Retry only for the first retryable error. 175 if osIsNotExist(err) && i == 0 { 176 i++ 177 continue 178 } 179 } 180 break 181 } 182 return err 183 }