storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/fs-v1-helpers.go (about) 1 /* 2 * MinIO Cloud Storage, (C) 2016, 2017, 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 "context" 21 "io" 22 "os" 23 pathutil "path" 24 "runtime" 25 "strings" 26 27 "storj.io/minio/cmd/logger" 28 "storj.io/minio/pkg/lock" 29 ) 30 31 // Removes only the file at given path does not remove 32 // any parent directories, handles long paths for 33 // windows automatically. 34 func fsRemoveFile(ctx context.Context, filePath string) (err error) { 35 if filePath == "" { 36 logger.LogIf(ctx, errInvalidArgument) 37 return errInvalidArgument 38 } 39 40 if err = checkPathLength(filePath); err != nil { 41 logger.LogIf(ctx, err) 42 return err 43 } 44 45 if err = os.Remove(filePath); err != nil { 46 if err = osErrToFileErr(err); err != errFileNotFound { 47 logger.LogIf(ctx, err) 48 } 49 } 50 51 return err 52 } 53 54 // Removes all files and folders at a given path, handles 55 // long paths for windows automatically. 56 func fsRemoveAll(ctx context.Context, dirPath string) (err error) { 57 if dirPath == "" { 58 logger.LogIf(ctx, errInvalidArgument) 59 return errInvalidArgument 60 } 61 62 if err = checkPathLength(dirPath); err != nil { 63 logger.LogIf(ctx, err) 64 return err 65 } 66 67 if err = removeAll(dirPath); err != nil { 68 if osIsPermission(err) { 69 logger.LogIf(ctx, errVolumeAccessDenied) 70 return errVolumeAccessDenied 71 } else if isSysErrNotEmpty(err) { 72 logger.LogIf(ctx, errVolumeNotEmpty) 73 return errVolumeNotEmpty 74 } 75 logger.LogIf(ctx, err) 76 return err 77 } 78 79 return nil 80 } 81 82 // Removes a directory only if its empty, handles long 83 // paths for windows automatically. 84 func fsRemoveDir(ctx context.Context, dirPath string) (err error) { 85 if dirPath == "" { 86 logger.LogIf(ctx, errInvalidArgument) 87 return errInvalidArgument 88 } 89 90 if err = checkPathLength(dirPath); err != nil { 91 logger.LogIf(ctx, err) 92 return err 93 } 94 95 if err = os.Remove((dirPath)); err != nil { 96 if osIsNotExist(err) { 97 return errVolumeNotFound 98 } else if isSysErrNotEmpty(err) { 99 return errVolumeNotEmpty 100 } 101 logger.LogIf(ctx, err) 102 return err 103 } 104 105 return nil 106 } 107 108 // Creates a new directory, parent dir should exist 109 // otherwise returns an error. If directory already 110 // exists returns an error. Windows long paths 111 // are handled automatically. 112 func fsMkdir(ctx context.Context, dirPath string) (err error) { 113 if dirPath == "" { 114 logger.LogIf(ctx, errInvalidArgument) 115 return errInvalidArgument 116 } 117 118 if err = checkPathLength(dirPath); err != nil { 119 logger.LogIf(ctx, err) 120 return err 121 } 122 123 if err = os.Mkdir((dirPath), 0777); err != nil { 124 switch { 125 case osIsExist(err): 126 return errVolumeExists 127 case osIsPermission(err): 128 logger.LogIf(ctx, errDiskAccessDenied) 129 return errDiskAccessDenied 130 case isSysErrNotDir(err): 131 // File path cannot be verified since 132 // one of the parents is a file. 133 logger.LogIf(ctx, errDiskAccessDenied) 134 return errDiskAccessDenied 135 case isSysErrPathNotFound(err): 136 // Add specific case for windows. 137 logger.LogIf(ctx, errDiskAccessDenied) 138 return errDiskAccessDenied 139 default: 140 logger.LogIf(ctx, err) 141 return err 142 } 143 } 144 145 return nil 146 } 147 148 // fsStat is a low level call which validates input arguments 149 // and checks input length upto supported maximum. Does 150 // not perform any higher layer interpretation of files v/s 151 // directories. For higher level interpretation look at 152 // fsStatFileDir, fsStatFile, fsStatDir. 153 func fsStat(ctx context.Context, statLoc string) (os.FileInfo, error) { 154 if statLoc == "" { 155 logger.LogIf(ctx, errInvalidArgument) 156 return nil, errInvalidArgument 157 } 158 if err := checkPathLength(statLoc); err != nil { 159 logger.LogIf(ctx, err) 160 return nil, err 161 } 162 fi, err := os.Stat(statLoc) 163 if err != nil { 164 return nil, err 165 } 166 167 return fi, nil 168 } 169 170 // Lookup if volume exists, returns volume attributes upon success. 171 func fsStatVolume(ctx context.Context, volume string) (os.FileInfo, error) { 172 fi, err := fsStat(ctx, volume) 173 if err != nil { 174 if osIsNotExist(err) { 175 return nil, errVolumeNotFound 176 } else if osIsPermission(err) { 177 return nil, errVolumeAccessDenied 178 } 179 return nil, err 180 } 181 182 if !fi.IsDir() { 183 return nil, errVolumeAccessDenied 184 } 185 186 return fi, nil 187 } 188 189 // Lookup if directory exists, returns directory attributes upon success. 190 func fsStatDir(ctx context.Context, statDir string) (os.FileInfo, error) { 191 fi, err := fsStat(ctx, statDir) 192 if err != nil { 193 err = osErrToFileErr(err) 194 if err != errFileNotFound { 195 logger.LogIf(ctx, err) 196 } 197 return nil, err 198 } 199 if !fi.IsDir() { 200 return nil, errFileNotFound 201 } 202 return fi, nil 203 } 204 205 // Lookup if file exists, returns file attributes upon success. 206 func fsStatFile(ctx context.Context, statFile string) (os.FileInfo, error) { 207 fi, err := fsStat(ctx, statFile) 208 if err != nil { 209 err = osErrToFileErr(err) 210 if err != errFileNotFound { 211 logger.LogIf(ctx, err) 212 } 213 return nil, err 214 } 215 if fi.IsDir() { 216 return nil, errFileNotFound 217 } 218 return fi, nil 219 } 220 221 // Returns if the filePath is a regular file. 222 func fsIsFile(ctx context.Context, filePath string) bool { 223 fi, err := fsStat(ctx, filePath) 224 if err != nil { 225 return false 226 } 227 return fi.Mode().IsRegular() 228 } 229 230 // Opens the file at given path, optionally from an offset. Upon success returns 231 // a readable stream and the size of the readable stream. 232 func fsOpenFile(ctx context.Context, readPath string, offset int64) (io.ReadCloser, int64, error) { 233 if readPath == "" || offset < 0 { 234 logger.LogIf(ctx, errInvalidArgument) 235 return nil, 0, errInvalidArgument 236 } 237 if err := checkPathLength(readPath); err != nil { 238 logger.LogIf(ctx, err) 239 return nil, 0, err 240 } 241 242 fr, err := os.Open(readPath) 243 if err != nil { 244 return nil, 0, osErrToFileErr(err) 245 } 246 247 // Stat to get the size of the file at path. 248 st, err := fr.Stat() 249 if err != nil { 250 err = osErrToFileErr(err) 251 if err != errFileNotFound { 252 logger.LogIf(ctx, err) 253 } 254 return nil, 0, err 255 } 256 257 // Verify if its not a regular file, since subsequent Seek is undefined. 258 if !st.Mode().IsRegular() { 259 return nil, 0, errIsNotRegular 260 } 261 262 // Seek to the requested offset. 263 if offset > 0 { 264 _, err = fr.Seek(offset, io.SeekStart) 265 if err != nil { 266 logger.LogIf(ctx, err) 267 return nil, 0, err 268 } 269 } 270 271 // Success. 272 return fr, st.Size(), nil 273 } 274 275 // Creates a file and copies data from incoming reader. 276 func fsCreateFile(ctx context.Context, filePath string, reader io.Reader, fallocSize int64) (int64, error) { 277 if filePath == "" || reader == nil { 278 logger.LogIf(ctx, errInvalidArgument) 279 return 0, errInvalidArgument 280 } 281 282 if err := checkPathLength(filePath); err != nil { 283 logger.LogIf(ctx, err) 284 return 0, err 285 } 286 287 if err := mkdirAll(pathutil.Dir(filePath), 0777); err != nil { 288 switch { 289 case osIsPermission(err): 290 return 0, errFileAccessDenied 291 case osIsExist(err): 292 return 0, errFileAccessDenied 293 case isSysErrIO(err): 294 return 0, errFaultyDisk 295 case isSysErrInvalidArg(err): 296 return 0, errUnsupportedDisk 297 case isSysErrNoSpace(err): 298 return 0, errDiskFull 299 } 300 return 0, err 301 } 302 303 flags := os.O_CREATE | os.O_WRONLY 304 if globalFSOSync { 305 flags = flags | os.O_SYNC 306 } 307 writer, err := lock.Open(filePath, flags, 0666) 308 if err != nil { 309 return 0, osErrToFileErr(err) 310 } 311 defer writer.Close() 312 313 // Fallocate only if the size is final object is known. 314 if fallocSize > 0 { 315 if err = fsFAllocate(int(writer.Fd()), 0, fallocSize); err != nil { 316 logger.LogIf(ctx, err) 317 return 0, err 318 } 319 } 320 321 bytesWritten, err := io.Copy(writer, reader) 322 if err != nil { 323 logger.LogIf(ctx, err) 324 return 0, err 325 } 326 327 return bytesWritten, nil 328 } 329 330 // fsFAllocate is similar to Fallocate but provides a convenient 331 // wrapper to handle various operating system specific errors. 332 func fsFAllocate(fd int, offset int64, len int64) (err error) { 333 e := Fallocate(fd, offset, len) 334 if e != nil { 335 switch { 336 case isSysErrNoSpace(e): 337 err = errDiskFull 338 case isSysErrNoSys(e) || isSysErrOpNotSupported(e): 339 // Ignore errors when Fallocate is not supported in the current system 340 case isSysErrInvalidArg(e): 341 // Workaround for Windows Docker Engine 19.03.8. 342 // See https://github.com/minio/minio/issues/9726 343 case isSysErrIO(e): 344 err = e 345 default: 346 // For errors: EBADF, EINTR, EINVAL, ENODEV, EPERM, ESPIPE and ETXTBSY 347 // Appending was failed anyway, returns unexpected error 348 err = errUnexpected 349 } 350 return err 351 } 352 353 return nil 354 } 355 356 // Renames source path to destination path, fails if the destination path 357 // parents are not already created. 358 func fsSimpleRenameFile(ctx context.Context, sourcePath, destPath string) error { 359 if err := checkPathLength(sourcePath); err != nil { 360 logger.LogIf(ctx, err) 361 return err 362 } 363 if err := checkPathLength(destPath); err != nil { 364 logger.LogIf(ctx, err) 365 return err 366 } 367 368 if err := os.Rename(sourcePath, destPath); err != nil { 369 logger.LogIf(ctx, err) 370 return osErrToFileErr(err) 371 } 372 373 return nil 374 } 375 376 // Renames source path to destination path, creates all the 377 // missing parents if they don't exist. 378 func fsRenameFile(ctx context.Context, sourcePath, destPath string) error { 379 if err := checkPathLength(sourcePath); err != nil { 380 logger.LogIf(ctx, err) 381 return err 382 } 383 if err := checkPathLength(destPath); err != nil { 384 logger.LogIf(ctx, err) 385 return err 386 } 387 388 if err := renameAll(sourcePath, destPath); err != nil { 389 logger.LogIf(ctx, err) 390 return err 391 } 392 393 return nil 394 } 395 396 func deleteFile(basePath, deletePath string, recursive bool) error { 397 if basePath == "" || deletePath == "" { 398 return nil 399 } 400 isObjectDir := HasSuffix(deletePath, SlashSeparator) 401 basePath = pathutil.Clean(basePath) 402 deletePath = pathutil.Clean(deletePath) 403 if !strings.HasPrefix(deletePath, basePath) || deletePath == basePath { 404 return nil 405 } 406 407 var err error 408 if recursive { 409 os.RemoveAll(deletePath) 410 } else { 411 err = os.Remove(deletePath) 412 } 413 if err != nil { 414 switch { 415 case isSysErrNotEmpty(err): 416 // if object is a directory, but if its not empty 417 // return FileNotFound to indicate its an empty prefix. 418 if isObjectDir { 419 return errFileNotFound 420 } 421 // Ignore errors if the directory is not empty. The server relies on 422 // this functionality, and sometimes uses recursion that should not 423 // error on parent directories. 424 return nil 425 case osIsNotExist(err): 426 return errFileNotFound 427 case osIsPermission(err): 428 return errFileAccessDenied 429 case isSysErrIO(err): 430 return errFaultyDisk 431 default: 432 return err 433 } 434 } 435 436 deletePath = pathutil.Dir(deletePath) 437 438 // Delete parent directory obviously not recursively. Errors for 439 // parent directories shouldn't trickle down. 440 deleteFile(basePath, deletePath, false) 441 442 return nil 443 } 444 445 // fsDeleteFile is a wrapper for deleteFile(), after checking the path length. 446 func fsDeleteFile(ctx context.Context, basePath, deletePath string) error { 447 if err := checkPathLength(basePath); err != nil { 448 logger.LogIf(ctx, err) 449 return err 450 } 451 452 if err := checkPathLength(deletePath); err != nil { 453 logger.LogIf(ctx, err) 454 return err 455 } 456 457 if err := deleteFile(basePath, deletePath, false); err != nil { 458 if err != errFileNotFound { 459 logger.LogIf(ctx, err) 460 } 461 return err 462 } 463 return nil 464 } 465 466 // fsRemoveMeta safely removes a locked file and takes care of Windows special case 467 func fsRemoveMeta(ctx context.Context, basePath, deletePath, tmpDir string) error { 468 // Special case for windows please read through. 469 if runtime.GOOS == globalWindowsOSName { 470 // Ordinarily windows does not permit deletion or renaming of files still 471 // in use, but if all open handles to that file were opened with FILE_SHARE_DELETE 472 // then it can permit renames and deletions of open files. 473 // 474 // There are however some gotchas with this, and it is worth listing them here. 475 // Firstly, Windows never allows you to really delete an open file, rather it is 476 // flagged as delete pending and its entry in its directory remains visible 477 // (though no new file handles may be opened to it) and when the very last 478 // open handle to the file in the system is closed, only then is it truly 479 // deleted. Well, actually only sort of truly deleted, because Windows only 480 // appears to remove the file entry from the directory, but in fact that 481 // entry is merely hidden and actually still exists and attempting to create 482 // a file with the same name will return an access denied error. How long it 483 // silently exists for depends on a range of factors, but put it this way: 484 // if your code loops creating and deleting the same file name as you might 485 // when operating a lock file, you're going to see lots of random spurious 486 // access denied errors and truly dismal lock file performance compared to POSIX. 487 // 488 // We work-around these un-POSIX file semantics by taking a dual step to 489 // deleting files. Firstly, it renames the file to tmp location into multipartTmpBucket 490 // We always open files with FILE_SHARE_DELETE permission enabled, with that 491 // flag Windows permits renaming and deletion, and because the name was changed 492 // to a very random name somewhere not in its origin directory before deletion, 493 // you don't see those unexpected random errors when creating files with the 494 // same name as a recently deleted file as you do anywhere else on Windows. 495 // Because the file is probably not in its original containing directory any more, 496 // deletions of that directory will not fail with "directory not empty" as they 497 // otherwise normally would either. 498 499 tmpPath := pathJoin(tmpDir, mustGetUUID()) 500 501 fsRenameFile(ctx, deletePath, tmpPath) 502 503 // Proceed to deleting the directory if empty 504 fsDeleteFile(ctx, basePath, pathutil.Dir(deletePath)) 505 506 // Finally delete the renamed file. 507 return fsDeleteFile(ctx, tmpDir, tmpPath) 508 } 509 return fsDeleteFile(ctx, basePath, deletePath) 510 }