storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/os-reliable.go (about) 1 /* 2 * MinIO Cloud Storage, (C) 2018 MinIO, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package cmd 18 19 import ( 20 "fmt" 21 "os" 22 "path" 23 ) 24 25 // Wrapper functions to os.RemoveAll, which calls reliableRemoveAll 26 // this is to ensure that if there is a racy parent directory 27 // create in between we can simply retry the operation. 28 func removeAll(dirPath string) (err error) { 29 if dirPath == "" { 30 return errInvalidArgument 31 } 32 33 if err = checkPathLength(dirPath); err != nil { 34 return err 35 } 36 37 if err = reliableRemoveAll(dirPath); err != nil { 38 switch { 39 case isSysErrNotDir(err): 40 // File path cannot be verified since one of 41 // the parents is a file. 42 return errFileAccessDenied 43 case isSysErrPathNotFound(err): 44 // This is a special case should be handled only for 45 // windows, because windows API does not return "not a 46 // directory" error message. Handle this specifically 47 // here. 48 return errFileAccessDenied 49 } 50 } 51 return err 52 } 53 54 // Reliably retries os.RemoveAll if for some reason os.RemoveAll returns 55 // syscall.ENOTEMPTY (children has files). 56 func reliableRemoveAll(dirPath string) (err error) { 57 i := 0 58 for { 59 // Removes all the directories and files. 60 if err = RemoveAll(dirPath); err != nil { 61 // Retry only for the first retryable error. 62 if isSysErrNotEmpty(err) && i == 0 { 63 i++ 64 continue 65 } 66 } 67 break 68 } 69 return err 70 } 71 72 // Wrapper functions to os.MkdirAll, which calls reliableMkdirAll 73 // this is to ensure that if there is a racy parent directory 74 // delete in between we can simply retry the operation. 75 func mkdirAll(dirPath string, mode os.FileMode) (err error) { 76 if dirPath == "" { 77 return errInvalidArgument 78 } 79 80 if err = checkPathLength(dirPath); err != nil { 81 return err 82 } 83 84 if err = reliableMkdirAll(dirPath, mode); err != nil { 85 // File path cannot be verified since one of the parents is a file. 86 if isSysErrNotDir(err) { 87 return errFileAccessDenied 88 } else if isSysErrPathNotFound(err) { 89 // This is a special case should be handled only for 90 // windows, because windows API does not return "not a 91 // directory" error message. Handle this specifically here. 92 return errFileAccessDenied 93 } 94 } 95 return err 96 } 97 98 // Reliably retries os.MkdirAll if for some reason os.MkdirAll returns 99 // syscall.ENOENT (parent does not exist). 100 func reliableMkdirAll(dirPath string, mode os.FileMode) (err error) { 101 i := 0 102 for { 103 // Creates all the parent directories, with mode 0777 mkdir honors system umask. 104 if err = MkdirAll(dirPath, mode); err != nil { 105 // Retry only for the first retryable error. 106 if osIsNotExist(err) && i == 0 { 107 i++ 108 continue 109 } 110 } 111 break 112 } 113 return err 114 } 115 116 // Wrapper function to os.Rename, which calls reliableMkdirAll 117 // and reliableRenameAll. This is to ensure that if there is a 118 // racy parent directory delete in between we can simply retry 119 // the operation. 120 func renameAll(srcFilePath, dstFilePath string) (err error) { 121 if srcFilePath == "" || dstFilePath == "" { 122 return errInvalidArgument 123 } 124 125 if err = checkPathLength(srcFilePath); err != nil { 126 return err 127 } 128 if err = checkPathLength(dstFilePath); err != nil { 129 return err 130 } 131 132 if err = reliableRename(srcFilePath, dstFilePath); err != nil { 133 switch { 134 case isSysErrNotDir(err) && !osIsNotExist(err): 135 // Windows can have both isSysErrNotDir(err) and osIsNotExist(err) returning 136 // true if the source file path contains an inexistant directory. In that case, 137 // we want to return errFileNotFound instead, which will honored in subsequent 138 // switch cases 139 return errFileAccessDenied 140 case isSysErrPathNotFound(err): 141 // This is a special case should be handled only for 142 // windows, because windows API does not return "not a 143 // directory" error message. Handle this specifically here. 144 return errFileAccessDenied 145 case isSysErrCrossDevice(err): 146 return fmt.Errorf("%w (%s)->(%s)", errCrossDeviceLink, srcFilePath, dstFilePath) 147 case osIsNotExist(err): 148 return errFileNotFound 149 case osIsExist(err): 150 // This is returned only when destination is a directory and we 151 // are attempting a rename from file to directory. 152 return errIsNotRegular 153 default: 154 return err 155 } 156 } 157 return nil 158 } 159 160 // Reliably retries os.RenameAll if for some reason os.RenameAll returns 161 // syscall.ENOENT (parent does not exist). 162 func reliableRename(srcFilePath, dstFilePath string) (err error) { 163 if err = reliableMkdirAll(path.Dir(dstFilePath), 0777); err != nil { 164 return err 165 } 166 i := 0 167 for { 168 // After a successful parent directory create attempt a renameAll. 169 if err = Rename(srcFilePath, dstFilePath); err != nil { 170 // Retry only for the first retryable error. 171 if osIsNotExist(err) && i == 0 { 172 i++ 173 continue 174 } 175 } 176 break 177 } 178 return err 179 }