storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/xl-storage.go (about) 1 /* 2 * MinIO Cloud Storage, (C) 2016-2020 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 "bufio" 21 "bytes" 22 "context" 23 "crypto/rand" 24 "encoding/hex" 25 "errors" 26 "fmt" 27 "io" 28 "io/ioutil" 29 "net/url" 30 "os" 31 pathutil "path" 32 "path/filepath" 33 "runtime" 34 "strings" 35 "sync" 36 "time" 37 38 "github.com/dustin/go-humanize" 39 "github.com/google/uuid" 40 jsoniter "github.com/json-iterator/go" 41 "github.com/klauspost/readahead" 42 43 "storj.io/minio/cmd/config" 44 "storj.io/minio/cmd/config/storageclass" 45 "storj.io/minio/cmd/logger" 46 "storj.io/minio/pkg/bucket/lifecycle" 47 "storj.io/minio/pkg/color" 48 "storj.io/minio/pkg/console" 49 "storj.io/minio/pkg/disk" 50 "storj.io/minio/pkg/env" 51 xioutil "storj.io/minio/pkg/ioutil" 52 ) 53 54 const ( 55 nullVersionID = "null" 56 blockSizeLarge = 2 * humanize.MiByte // Default r/w block size for larger objects. 57 blockSizeSmall = 128 * humanize.KiByte // Default r/w block size for smaller objects. 58 59 // On regular files bigger than this; 60 readAheadSize = 16 << 20 61 // Read this many buffers ahead. 62 readAheadBuffers = 4 63 // Size of each buffer. 64 readAheadBufSize = 1 << 20 65 66 // Small file threshold below which data accompanies metadata from storage layer. 67 smallFileThreshold = 128 * humanize.KiByte // Optimized for NVMe/SSDs 68 // For hardrives it is possible to set this to a lower value to avoid any 69 // spike in latency. But currently we are simply keeping it optimal for SSDs. 70 71 // XL metadata file carries per object metadata. 72 xlStorageFormatFile = "xl.meta" 73 ) 74 75 var alignedBuf []byte 76 77 func init() { 78 alignedBuf = disk.AlignedBlock(4096) 79 _, _ = rand.Read(alignedBuf) 80 } 81 82 // isValidVolname verifies a volname name in accordance with object 83 // layer requirements. 84 func isValidVolname(volname string) bool { 85 if len(volname) < 3 { 86 return false 87 } 88 89 if runtime.GOOS == "windows" { 90 // Volname shouldn't have reserved characters in Windows. 91 return !strings.ContainsAny(volname, `\:*?\"<>|`) 92 } 93 94 return true 95 } 96 97 // xlStorage - implements StorageAPI interface. 98 type xlStorage struct { 99 diskPath string 100 endpoint Endpoint 101 102 globalSync bool 103 104 poolLarge sync.Pool 105 poolSmall sync.Pool 106 107 rootDisk bool 108 109 diskID string 110 111 // Indexes, will be -1 until assigned a set. 112 poolIndex, setIndex, diskIndex int 113 114 formatFileInfo os.FileInfo 115 formatLegacy bool 116 formatLastCheck time.Time 117 118 diskInfoCache timedValue 119 120 ctx context.Context 121 sync.RWMutex 122 } 123 124 // checkPathLength - returns error if given path name length more than 255 125 func checkPathLength(pathName string) error { 126 // Apple OS X path length is limited to 1016 127 if runtime.GOOS == "darwin" && len(pathName) > 1016 { 128 return errFileNameTooLong 129 } 130 131 // Disallow more than 1024 characters on windows, there 132 // are no known name_max limits on Windows. 133 if runtime.GOOS == "windows" && len(pathName) > 1024 { 134 return errFileNameTooLong 135 } 136 137 // On Unix we reject paths if they are just '.', '..' or '/' 138 if pathName == "." || pathName == ".." || pathName == slashSeparator { 139 return errFileAccessDenied 140 } 141 142 // Check each path segment length is > 255 on all Unix 143 // platforms, look for this value as NAME_MAX in 144 // /usr/include/linux/limits.h 145 var count int64 146 for _, p := range pathName { 147 switch p { 148 case '/': 149 count = 0 // Reset 150 case '\\': 151 if runtime.GOOS == globalWindowsOSName { 152 count = 0 153 } 154 default: 155 count++ 156 if count > 255 { 157 return errFileNameTooLong 158 } 159 } 160 } // Success. 161 return nil 162 } 163 164 func getValidPath(path string) (string, error) { 165 if path == "" { 166 return path, errInvalidArgument 167 } 168 169 var err error 170 // Disallow relative paths, figure out absolute paths. 171 path, err = filepath.Abs(path) 172 if err != nil { 173 return path, err 174 } 175 176 fi, err := Lstat(path) 177 if err != nil && !osIsNotExist(err) { 178 return path, err 179 } 180 if osIsNotExist(err) { 181 // Disk not found create it. 182 if err = reliableMkdirAll(path, 0777); err != nil { 183 return path, err 184 } 185 } 186 if fi != nil && !fi.IsDir() { 187 return path, errDiskNotDir 188 } 189 190 return path, nil 191 } 192 193 // isDirEmpty - returns whether given directory is empty or not. 194 func isDirEmpty(dirname string) bool { 195 entries, err := readDirN(dirname, 1) 196 if err != nil { 197 if err != errFileNotFound { 198 logger.LogIf(GlobalContext, err) 199 } 200 return false 201 } 202 return len(entries) == 0 203 } 204 205 // Initialize a new storage disk. 206 func newLocalXLStorage(path string) (*xlStorage, error) { 207 u := url.URL{Path: path} 208 return newXLStorage(Endpoint{ 209 URL: &u, 210 IsLocal: true, 211 }) 212 } 213 214 // Initialize a new storage disk. 215 func newXLStorage(ep Endpoint) (*xlStorage, error) { 216 path := ep.Path 217 var err error 218 if path, err = getValidPath(path); err != nil { 219 return nil, err 220 } 221 222 var rootDisk bool 223 if env.Get("MINIO_CI_CD", "") != "" { 224 rootDisk = true 225 } else { 226 if IsDocker() || IsKubernetes() { 227 // Start with overlay "/" to check if 228 // possible the path has device id as 229 // "overlay" that would mean the path 230 // is emphemeral and we should treat it 231 // as root disk from the baremetal 232 // terminology. 233 rootDisk, err = disk.IsRootDisk(path, SlashSeparator) 234 if err != nil { 235 return nil, err 236 } 237 if !rootDisk { 238 // No root disk was found, its possible that 239 // path is referenced at "/etc/hosts" which has 240 // different device ID that points to the original 241 // "/" on the host system, fall back to that instead 242 // to verify of the device id is same. 243 rootDisk, err = disk.IsRootDisk(path, "/etc/hosts") 244 if err != nil { 245 return nil, err 246 } 247 } 248 249 } else { 250 // On baremetal setups its always "/" is the root disk. 251 rootDisk, err = disk.IsRootDisk(path, SlashSeparator) 252 if err != nil { 253 return nil, err 254 } 255 } 256 } 257 258 p := &xlStorage{ 259 diskPath: path, 260 endpoint: ep, 261 poolLarge: sync.Pool{ 262 New: func() interface{} { 263 b := disk.AlignedBlock(blockSizeLarge) 264 return &b 265 }, 266 }, 267 poolSmall: sync.Pool{ 268 New: func() interface{} { 269 b := disk.AlignedBlock(blockSizeSmall) 270 return &b 271 }, 272 }, 273 globalSync: env.Get(config.EnvFSOSync, config.EnableOff) == config.EnableOn, 274 ctx: GlobalContext, 275 rootDisk: rootDisk, 276 poolIndex: -1, 277 setIndex: -1, 278 diskIndex: -1, 279 } 280 281 // Create all necessary bucket folders if possible. 282 if err = p.MakeVolBulk(context.TODO(), minioMetaBucket, minioMetaTmpBucket, minioMetaMultipartBucket, dataUsageBucket); err != nil { 283 return nil, err 284 } 285 286 // Check if backend is writable and supports O_DIRECT 287 var rnd [8]byte 288 _, _ = rand.Read(rnd[:]) 289 tmpFile := ".writable-check-" + hex.EncodeToString(rnd[:]) + ".tmp" 290 filePath := pathJoin(p.diskPath, minioMetaTmpBucket, tmpFile) 291 w, err := disk.OpenFileDirectIO(filePath, os.O_CREATE|os.O_WRONLY|os.O_EXCL, 0666) 292 if err != nil { 293 return p, err 294 } 295 if _, err = w.Write(alignedBuf[:]); err != nil { 296 w.Close() 297 return p, err 298 } 299 w.Close() 300 defer Remove(filePath) 301 302 // Success. 303 return p, nil 304 } 305 306 // getDiskInfo returns given disk information. 307 func getDiskInfo(diskPath string) (di disk.Info, err error) { 308 if err = checkPathLength(diskPath); err == nil { 309 di, err = disk.GetInfo(diskPath) 310 } 311 312 switch { 313 case osIsNotExist(err): 314 err = errDiskNotFound 315 case isSysErrTooLong(err): 316 err = errFileNameTooLong 317 case isSysErrIO(err): 318 err = errFaultyDisk 319 } 320 321 return di, err 322 } 323 324 // Implements stringer compatible interface. 325 func (s *xlStorage) String() string { 326 return s.diskPath 327 } 328 329 func (s *xlStorage) Hostname() string { 330 return s.endpoint.Host 331 } 332 333 func (s *xlStorage) Endpoint() Endpoint { 334 return s.endpoint 335 } 336 337 func (*xlStorage) Close() error { 338 return nil 339 } 340 341 func (s *xlStorage) IsOnline() bool { 342 return true 343 } 344 345 func (s *xlStorage) IsLocal() bool { 346 return true 347 } 348 349 // Retrieve location indexes. 350 func (s *xlStorage) GetDiskLoc() (poolIdx, setIdx, diskIdx int) { 351 s.RLock() 352 defer s.RUnlock() 353 // If unset, see if we can locate it. 354 if s.poolIndex < 0 || s.setIndex < 0 || s.diskIndex < 0 { 355 return getXLDiskLoc(s.diskID) 356 } 357 return s.poolIndex, s.setIndex, s.diskIndex 358 } 359 360 // Set location indexes. 361 func (s *xlStorage) SetDiskLoc(poolIdx, setIdx, diskIdx int) { 362 s.poolIndex = poolIdx 363 s.setIndex = setIdx 364 s.diskIndex = diskIdx 365 } 366 367 func (s *xlStorage) Healing() *healingTracker { 368 healingFile := pathJoin(s.diskPath, minioMetaBucket, 369 bucketMetaPrefix, healingTrackerFilename) 370 b, err := ioutil.ReadFile(healingFile) 371 if err != nil { 372 return nil 373 } 374 var h healingTracker 375 _, err = h.UnmarshalMsg(b) 376 logger.LogIf(GlobalContext, err) 377 return &h 378 } 379 380 func (s *xlStorage) NSScanner(ctx context.Context, cache dataUsageCache) (dataUsageCache, error) { 381 var lc *lifecycle.Lifecycle 382 var err error 383 384 // Check if the current bucket has a configured lifecycle policy 385 if globalLifecycleSys != nil { 386 lc, err = globalLifecycleSys.Get(cache.Info.Name) 387 if err == nil && lc.HasActiveRules("", true) { 388 cache.Info.lifeCycle = lc 389 if intDataUpdateTracker.debug { 390 console.Debugln(color.Green("scannerDisk:") + " lifecycle: Active rules found") 391 } 392 } 393 } 394 395 // return initialized object layer 396 objAPI := newObjectLayerFn() 397 398 globalHealConfigMu.Lock() 399 healOpts := globalHealConfig 400 globalHealConfigMu.Unlock() 401 402 dataUsageInfo, err := scanDataFolder(ctx, s.diskPath, cache, func(item scannerItem) (sizeSummary, error) { 403 // Look for `xl.meta/xl.json' at the leaf. 404 if !strings.HasSuffix(item.Path, SlashSeparator+xlStorageFormatFile) && 405 !strings.HasSuffix(item.Path, SlashSeparator+xlStorageFormatFileV1) { 406 // if no xl.meta/xl.json found, skip the file. 407 return sizeSummary{}, errSkipFile 408 } 409 410 buf, err := xioutil.ReadFile(item.Path) 411 if err != nil { 412 if intDataUpdateTracker.debug { 413 console.Debugf(color.Green("scannerBucket:")+" object path missing: %v: %w\n", item.Path, err) 414 } 415 return sizeSummary{}, errSkipFile 416 } 417 418 // Remove filename which is the meta file. 419 item.transformMetaDir() 420 421 fivs, err := getFileInfoVersions(buf, item.bucket, item.objectPath()) 422 if err != nil { 423 if intDataUpdateTracker.debug { 424 console.Debugf(color.Green("scannerBucket:")+" reading xl.meta failed: %v: %w\n", item.Path, err) 425 } 426 return sizeSummary{}, errSkipFile 427 } 428 429 var totalSize int64 430 431 sizeS := sizeSummary{} 432 for _, version := range fivs.Versions { 433 oi := version.ToObjectInfo(item.bucket, item.objectPath()) 434 if objAPI != nil { 435 totalSize += item.applyActions(ctx, objAPI, actionMeta{ 436 oi: oi, 437 bitRotScan: healOpts.Bitrot, 438 }) 439 item.healReplication(ctx, objAPI, oi.Clone(), &sizeS) 440 } 441 } 442 sizeS.totalSize = totalSize 443 return sizeS, nil 444 }) 445 446 if err != nil { 447 return dataUsageInfo, err 448 } 449 450 dataUsageInfo.Info.LastUpdate = time.Now() 451 return dataUsageInfo, nil 452 } 453 454 // DiskInfo provides current information about disk space usage, 455 // total free inodes and underlying filesystem. 456 func (s *xlStorage) DiskInfo(context.Context) (info DiskInfo, err error) { 457 s.diskInfoCache.Once.Do(func() { 458 s.diskInfoCache.TTL = time.Second 459 s.diskInfoCache.Update = func() (interface{}, error) { 460 dcinfo := DiskInfo{ 461 RootDisk: s.rootDisk, 462 MountPath: s.diskPath, 463 Endpoint: s.endpoint.String(), 464 } 465 di, err := getDiskInfo(s.diskPath) 466 if err != nil { 467 return dcinfo, err 468 } 469 dcinfo.Total = di.Total 470 dcinfo.Free = di.Free 471 dcinfo.Used = di.Used 472 dcinfo.UsedInodes = di.Files - di.Ffree 473 dcinfo.FSType = di.FSType 474 475 diskID, err := s.GetDiskID() 476 if errors.Is(err, errUnformattedDisk) { 477 // if we found an unformatted disk then 478 // healing is automatically true. 479 dcinfo.Healing = true 480 } else { 481 // Check if the disk is being healed if GetDiskID 482 // returned any error other than fresh disk 483 dcinfo.Healing = s.Healing() != nil 484 } 485 486 dcinfo.ID = diskID 487 return dcinfo, err 488 } 489 }) 490 491 v, err := s.diskInfoCache.Get() 492 info = v.(DiskInfo) 493 return info, err 494 } 495 496 // getVolDir - will convert incoming volume names to 497 // corresponding valid volume names on the backend in a platform 498 // compatible way for all operating systems. If volume is not found 499 // an error is generated. 500 func (s *xlStorage) getVolDir(volume string) (string, error) { 501 if volume == "" || volume == "." || volume == ".." { 502 return "", errVolumeNotFound 503 } 504 volumeDir := pathJoin(s.diskPath, volume) 505 return volumeDir, nil 506 } 507 508 // GetDiskID - returns the cached disk uuid 509 func (s *xlStorage) GetDiskID() (string, error) { 510 s.RLock() 511 diskID := s.diskID 512 fileInfo := s.formatFileInfo 513 lastCheck := s.formatLastCheck 514 s.RUnlock() 515 516 // check if we have a valid disk ID that is less than 1 second old. 517 if fileInfo != nil && diskID != "" && time.Since(lastCheck) <= time.Second { 518 return diskID, nil 519 } 520 521 s.Lock() 522 // If somebody else updated the disk ID and changed the time, return what they got. 523 if !lastCheck.IsZero() && !s.formatLastCheck.Equal(lastCheck) && diskID != "" { 524 s.Unlock() 525 // Somebody else got the lock first. 526 return diskID, nil 527 } 528 s.Unlock() 529 530 formatFile := pathJoin(s.diskPath, minioMetaBucket, formatConfigFile) 531 fi, err := Lstat(formatFile) 532 if err != nil { 533 // If the disk is still not initialized. 534 if osIsNotExist(err) { 535 if err = Access(s.diskPath); err == nil { 536 // Disk is present but missing `format.json` 537 return "", errUnformattedDisk 538 } 539 if osIsNotExist(err) { 540 return "", errDiskNotFound 541 } else if osIsPermission(err) { 542 return "", errDiskAccessDenied 543 } 544 logger.LogIf(GlobalContext, err) // log unexpected errors 545 return "", errCorruptedFormat 546 } else if osIsPermission(err) { 547 return "", errDiskAccessDenied 548 } 549 logger.LogIf(GlobalContext, err) // log unexpected errors 550 return "", errCorruptedFormat 551 } 552 553 if xioutil.SameFile(fi, fileInfo) && diskID != "" { 554 s.Lock() 555 // If the file has not changed, just return the cached diskID information. 556 s.formatLastCheck = time.Now() 557 s.Unlock() 558 return diskID, nil 559 } 560 561 b, err := xioutil.ReadFile(formatFile) 562 if err != nil { 563 // If the disk is still not initialized. 564 if osIsNotExist(err) { 565 if err = Access(s.diskPath); err == nil { 566 // Disk is present but missing `format.json` 567 return "", errUnformattedDisk 568 } 569 if osIsNotExist(err) { 570 return "", errDiskNotFound 571 } else if osIsPermission(err) { 572 return "", errDiskAccessDenied 573 } 574 logger.LogIf(GlobalContext, err) // log unexpected errors 575 return "", errCorruptedFormat 576 } else if osIsPermission(err) { 577 return "", errDiskAccessDenied 578 } 579 logger.LogIf(GlobalContext, err) // log unexpected errors 580 return "", errCorruptedFormat 581 } 582 583 format := &formatErasureV3{} 584 var json = jsoniter.ConfigCompatibleWithStandardLibrary 585 if err = json.Unmarshal(b, &format); err != nil { 586 logger.LogIf(GlobalContext, err) // log unexpected errors 587 return "", errCorruptedFormat 588 } 589 590 s.Lock() 591 defer s.Unlock() 592 s.diskID = format.Erasure.This 593 s.formatLegacy = format.Erasure.DistributionAlgo == formatErasureVersionV2DistributionAlgoV1 594 s.formatFileInfo = fi 595 s.formatLastCheck = time.Now() 596 return s.diskID, nil 597 } 598 599 // Make a volume entry. 600 func (s *xlStorage) SetDiskID(id string) { 601 // NO-OP for xlStorage as it is handled either by xlStorageDiskIDCheck{} for local disks or 602 // storage rest server for remote disks. 603 } 604 605 func (s *xlStorage) MakeVolBulk(ctx context.Context, volumes ...string) error { 606 for _, volume := range volumes { 607 if err := s.MakeVol(ctx, volume); err != nil { 608 if errors.Is(err, errDiskAccessDenied) { 609 return errDiskAccessDenied 610 } 611 } 612 } 613 return nil 614 } 615 616 // Make a volume entry. 617 func (s *xlStorage) MakeVol(ctx context.Context, volume string) error { 618 if !isValidVolname(volume) { 619 return errInvalidArgument 620 } 621 622 volumeDir, err := s.getVolDir(volume) 623 if err != nil { 624 return err 625 } 626 627 if err = Access(volumeDir); err != nil { 628 // Volume does not exist we proceed to create. 629 if osIsNotExist(err) { 630 // Make a volume entry, with mode 0777 mkdir honors system umask. 631 err = reliableMkdirAll(volumeDir, 0777) 632 } 633 if osIsPermission(err) { 634 return errDiskAccessDenied 635 } else if isSysErrIO(err) { 636 return errFaultyDisk 637 } 638 return err 639 } 640 641 // Stat succeeds we return errVolumeExists. 642 return errVolumeExists 643 } 644 645 // ListVols - list volumes. 646 func (s *xlStorage) ListVols(context.Context) (volsInfo []VolInfo, err error) { 647 return listVols(s.diskPath) 648 } 649 650 // List all the volumes from diskPath. 651 func listVols(dirPath string) ([]VolInfo, error) { 652 if err := checkPathLength(dirPath); err != nil { 653 return nil, err 654 } 655 entries, err := readDir(dirPath) 656 if err != nil { 657 return nil, errDiskNotFound 658 } 659 volsInfo := make([]VolInfo, 0, len(entries)) 660 for _, entry := range entries { 661 if !HasSuffix(entry, SlashSeparator) || !isValidVolname(pathutil.Clean(entry)) { 662 // Skip if entry is neither a directory not a valid volume name. 663 continue 664 } 665 volsInfo = append(volsInfo, VolInfo{ 666 Name: pathutil.Clean(entry), 667 }) 668 } 669 return volsInfo, nil 670 } 671 672 // StatVol - get volume info. 673 func (s *xlStorage) StatVol(ctx context.Context, volume string) (vol VolInfo, err error) { 674 // Verify if volume is valid and it exists. 675 volumeDir, err := s.getVolDir(volume) 676 if err != nil { 677 return VolInfo{}, err 678 } 679 680 // Stat a volume entry. 681 var st os.FileInfo 682 st, err = Lstat(volumeDir) 683 if err != nil { 684 switch { 685 case osIsNotExist(err): 686 return VolInfo{}, errVolumeNotFound 687 case osIsPermission(err): 688 return VolInfo{}, errDiskAccessDenied 689 case isSysErrIO(err): 690 return VolInfo{}, errFaultyDisk 691 default: 692 return VolInfo{}, err 693 } 694 } 695 // As os.Lstat() doesn't carry other than ModTime(), use ModTime() 696 // as CreatedTime. 697 createdTime := st.ModTime() 698 return VolInfo{ 699 Name: volume, 700 Created: createdTime, 701 }, nil 702 } 703 704 // DeleteVol - delete a volume. 705 func (s *xlStorage) DeleteVol(ctx context.Context, volume string, forceDelete bool) (err error) { 706 // Verify if volume is valid and it exists. 707 volumeDir, err := s.getVolDir(volume) 708 if err != nil { 709 return err 710 } 711 712 if forceDelete { 713 err = RemoveAll(volumeDir) 714 } else { 715 err = Remove(volumeDir) 716 } 717 718 if err != nil { 719 switch { 720 case osIsNotExist(err): 721 return errVolumeNotFound 722 case isSysErrNotEmpty(err): 723 return errVolumeNotEmpty 724 case osIsPermission(err): 725 return errDiskAccessDenied 726 case isSysErrIO(err): 727 return errFaultyDisk 728 default: 729 return err 730 } 731 } 732 return nil 733 } 734 735 func (s *xlStorage) isLeaf(volume string, leafPath string) bool { 736 volumeDir, err := s.getVolDir(volume) 737 if err != nil { 738 return false 739 } 740 741 if err = Access(pathJoin(volumeDir, leafPath, xlStorageFormatFile)); err == nil { 742 return true 743 } 744 if osIsNotExist(err) { 745 // We need a fallback code where directory might contain 746 // legacy `xl.json`, in such situation we just rename 747 // and proceed if rename is successful we know that it 748 // is the leaf since `xl.json` was present. 749 return s.renameLegacyMetadata(volumeDir, leafPath) == nil 750 } 751 return false 752 } 753 754 // ListDir - return all the entries at the given directory path. 755 // If an entry is a directory it will be returned with a trailing SlashSeparator. 756 func (s *xlStorage) ListDir(ctx context.Context, volume, dirPath string, count int) (entries []string, err error) { 757 // Verify if volume is valid and it exists. 758 volumeDir, err := s.getVolDir(volume) 759 if err != nil { 760 return nil, err 761 } 762 763 dirPathAbs := pathJoin(volumeDir, dirPath) 764 if count > 0 { 765 entries, err = readDirN(dirPathAbs, count) 766 } else { 767 entries, err = readDir(dirPathAbs) 768 } 769 if err != nil { 770 if err == errFileNotFound { 771 if ierr := Access(volumeDir); ierr != nil { 772 if osIsNotExist(ierr) { 773 return nil, errVolumeNotFound 774 } else if isSysErrIO(ierr) { 775 return nil, errFaultyDisk 776 } 777 } 778 } 779 return nil, err 780 } 781 782 return entries, nil 783 } 784 785 // DeleteVersions deletes slice of versions, it can be same object 786 // or multiple objects. 787 func (s *xlStorage) DeleteVersions(ctx context.Context, volume string, versions []FileInfo) []error { 788 errs := make([]error, len(versions)) 789 790 for i, version := range versions { 791 if err := s.DeleteVersion(ctx, volume, version.Name, version, false); err != nil { 792 errs[i] = err 793 } 794 } 795 796 return errs 797 } 798 799 // DeleteVersion - deletes FileInfo metadata for path at `xl.meta`. forceDelMarker 800 // will force creating a new `xl.meta` to create a new delete marker 801 func (s *xlStorage) DeleteVersion(ctx context.Context, volume, path string, fi FileInfo, forceDelMarker bool) error { 802 if HasSuffix(path, SlashSeparator) { 803 return s.Delete(ctx, volume, path, false) 804 } 805 806 buf, err := s.ReadAll(ctx, volume, pathJoin(path, xlStorageFormatFile)) 807 if err != nil { 808 if err != errFileNotFound { 809 return err 810 } 811 if fi.Deleted && forceDelMarker { 812 // Create a new xl.meta with a delete marker in it 813 return s.WriteMetadata(ctx, volume, path, fi) 814 } 815 if fi.VersionID != "" { 816 return errFileVersionNotFound 817 } 818 return errFileNotFound 819 } 820 821 if len(buf) == 0 { 822 if fi.VersionID != "" { 823 return errFileVersionNotFound 824 } 825 return errFileNotFound 826 } 827 828 volumeDir, err := s.getVolDir(volume) 829 if err != nil { 830 return err 831 } 832 833 if !isXL2V1Format(buf) { 834 // Delete the meta file, if there are no more versions the 835 // top level parent is automatically removed. 836 return s.deleteFile(volumeDir, pathJoin(volumeDir, path), true) 837 } 838 839 var xlMeta xlMetaV2 840 if err = xlMeta.Load(buf); err != nil { 841 return err 842 } 843 844 dataDir, lastVersion, err := xlMeta.DeleteVersion(fi) 845 if err != nil { 846 return err 847 } 848 849 // transitioned objects maintains metadata on the source cluster. When transition 850 // status is set, update the metadata to disk. 851 if !lastVersion || fi.TransitionStatus != "" { 852 // when data-dir is specified. Transition leverages existing DeleteObject 853 // api call to mark object as deleted. When object is pending transition, 854 // just update the metadata and avoid deleting data dir. 855 if dataDir != "" && fi.TransitionStatus != lifecycle.TransitionPending { 856 versionID := fi.VersionID 857 if versionID == "" { 858 versionID = nullVersionID 859 } 860 xlMeta.data.remove(versionID) 861 // PR #11758 used DataDir, preserve it 862 // for users who might have used master 863 // branch 864 xlMeta.data.remove(dataDir) 865 866 filePath := pathJoin(volumeDir, path, dataDir) 867 if err = checkPathLength(filePath); err != nil { 868 return err 869 } 870 871 tmpuuid := mustGetUUID() 872 if err = renameAll(filePath, pathutil.Join(s.diskPath, minioMetaTmpDeletedBucket, tmpuuid)); err != nil { 873 if err != errFileNotFound { 874 return err 875 } 876 } 877 } 878 879 buf, err = xlMeta.AppendTo(nil) 880 if err != nil { 881 return err 882 } 883 884 return s.WriteAll(ctx, volume, pathJoin(path, xlStorageFormatFile), buf) 885 } 886 887 // Move everything to trash. 888 filePath := retainSlash(pathJoin(volumeDir, path)) 889 if err = checkPathLength(filePath); err != nil { 890 return err 891 } 892 err = renameAll(filePath, pathutil.Join(s.diskPath, minioMetaTmpDeletedBucket, mustGetUUID())) 893 894 // Delete parents if needed. 895 filePath = retainSlash(pathutil.Dir(pathJoin(volumeDir, path))) 896 if filePath == retainSlash(volumeDir) { 897 return err 898 } 899 s.deleteFile(volumeDir, filePath, false) 900 return err 901 } 902 903 // Updates only metadata for a given version. 904 func (s *xlStorage) UpdateMetadata(ctx context.Context, volume, path string, fi FileInfo) error { 905 if len(fi.Metadata) == 0 { 906 return errInvalidArgument 907 } 908 909 buf, err := s.ReadAll(ctx, volume, pathJoin(path, xlStorageFormatFile)) 910 if err != nil { 911 if err == errFileNotFound { 912 if fi.VersionID != "" { 913 return errFileVersionNotFound 914 } 915 } 916 return err 917 } 918 919 if !isXL2V1Format(buf) { 920 return errFileVersionNotFound 921 } 922 923 var xlMeta xlMetaV2 924 if err = xlMeta.Load(buf); err != nil { 925 logger.LogIf(ctx, err) 926 return err 927 } 928 929 if err = xlMeta.UpdateObjectVersion(fi); err != nil { 930 return err 931 } 932 933 buf, err = xlMeta.AppendTo(nil) 934 if err != nil { 935 return err 936 } 937 938 return s.WriteAll(ctx, volume, pathJoin(path, xlStorageFormatFile), buf) 939 } 940 941 // WriteMetadata - writes FileInfo metadata for path at `xl.meta` 942 func (s *xlStorage) WriteMetadata(ctx context.Context, volume, path string, fi FileInfo) error { 943 buf, err := s.ReadAll(ctx, volume, pathJoin(path, xlStorageFormatFile)) 944 if err != nil && err != errFileNotFound { 945 return err 946 } 947 948 var xlMeta xlMetaV2 949 if !isXL2V1Format(buf) { 950 xlMeta, err = newXLMetaV2(fi) 951 if err != nil { 952 logger.LogIf(ctx, err) 953 return err 954 } 955 956 buf, err = xlMeta.AppendTo(nil) 957 if err != nil { 958 logger.LogIf(ctx, err) 959 return err 960 } 961 } else { 962 if err = xlMeta.Load(buf); err != nil { 963 logger.LogIf(ctx, err) 964 return err 965 } 966 967 if err = xlMeta.AddVersion(fi); err != nil { 968 logger.LogIf(ctx, err) 969 return err 970 } 971 972 buf, err = xlMeta.AppendTo(nil) 973 if err != nil { 974 logger.LogIf(ctx, err) 975 return err 976 } 977 } 978 979 return s.WriteAll(ctx, volume, pathJoin(path, xlStorageFormatFile), buf) 980 } 981 982 func (s *xlStorage) renameLegacyMetadata(volumeDir, path string) (err error) { 983 s.RLock() 984 legacy := s.formatLegacy 985 s.RUnlock() 986 if !legacy { 987 // if its not a legacy backend then this function is 988 // a no-op always returns errFileNotFound 989 return errFileNotFound 990 } 991 992 // Validate file path length, before reading. 993 filePath := pathJoin(volumeDir, path) 994 if err = checkPathLength(filePath); err != nil { 995 return err 996 } 997 998 srcFilePath := pathJoin(filePath, xlStorageFormatFileV1) 999 dstFilePath := pathJoin(filePath, xlStorageFormatFile) 1000 1001 // Renaming xl.json to xl.meta should be fully synced to disk. 1002 defer func() { 1003 if err == nil { 1004 if s.globalSync { 1005 // Sync to disk only upon success. 1006 globalSync() 1007 } 1008 } 1009 }() 1010 1011 if err = Rename(srcFilePath, dstFilePath); err != nil { 1012 switch { 1013 case isSysErrNotDir(err): 1014 return errFileNotFound 1015 case isSysErrPathNotFound(err): 1016 return errFileNotFound 1017 case isSysErrCrossDevice(err): 1018 return fmt.Errorf("%w (%s)->(%s)", errCrossDeviceLink, srcFilePath, dstFilePath) 1019 case osIsNotExist(err): 1020 return errFileNotFound 1021 case osIsExist(err): 1022 // This is returned only when destination is a directory and we 1023 // are attempting a rename from file to directory. 1024 return errIsNotRegular 1025 default: 1026 return err 1027 } 1028 } 1029 return nil 1030 } 1031 1032 // ReadVersion - reads metadata and returns FileInfo at path `xl.meta` 1033 // for all objects less than `32KiB` this call returns data as well 1034 // along with metadata. 1035 func (s *xlStorage) ReadVersion(ctx context.Context, volume, path, versionID string, readData bool) (fi FileInfo, err error) { 1036 volumeDir, err := s.getVolDir(volume) 1037 if err != nil { 1038 return fi, err 1039 } 1040 1041 buf, err := s.ReadAll(ctx, volume, pathJoin(path, xlStorageFormatFile)) 1042 if err != nil { 1043 if err == errFileNotFound { 1044 if err = s.renameLegacyMetadata(volumeDir, path); err != nil { 1045 if err == errFileNotFound { 1046 if versionID != "" { 1047 return fi, errFileVersionNotFound 1048 } 1049 return fi, errFileNotFound 1050 } 1051 return fi, err 1052 } 1053 buf, err = s.ReadAll(ctx, volume, pathJoin(path, xlStorageFormatFile)) 1054 if err != nil { 1055 if err == errFileNotFound { 1056 if versionID != "" { 1057 return fi, errFileVersionNotFound 1058 } 1059 return fi, errFileNotFound 1060 } 1061 return fi, err 1062 } 1063 } else { 1064 return fi, err 1065 } 1066 } 1067 1068 if len(buf) == 0 { 1069 if versionID != "" { 1070 return fi, errFileVersionNotFound 1071 } 1072 return fi, errFileNotFound 1073 } 1074 1075 fi, err = getFileInfo(buf, volume, path, versionID, readData) 1076 if err != nil { 1077 return fi, err 1078 } 1079 1080 if readData { 1081 if len(fi.Data) > 0 || fi.Size == 0 { 1082 return fi, nil 1083 } 1084 1085 // Reading data for small objects when 1086 // - object has not yet transitioned 1087 // - object size lesser than 128KiB 1088 // - object has maximum of 1 parts 1089 if fi.TransitionStatus == "" && fi.DataDir != "" && fi.Size <= smallFileThreshold && len(fi.Parts) == 1 { 1090 // Enable O_DIRECT optionally only if drive supports it. 1091 requireDirectIO := globalStorageClass.GetDMA() == storageclass.DMAReadWrite 1092 partPath := fmt.Sprintf("part.%d", fi.Parts[0].Number) 1093 fi.Data, err = s.readAllData(volumeDir, pathJoin(volumeDir, path, fi.DataDir, partPath), requireDirectIO) 1094 if err != nil { 1095 return FileInfo{}, err 1096 } 1097 } 1098 } 1099 1100 return fi, nil 1101 } 1102 1103 func (s *xlStorage) readAllData(volumeDir string, filePath string, requireDirectIO bool) (buf []byte, err error) { 1104 var r io.ReadCloser 1105 if requireDirectIO { 1106 var f *os.File 1107 f, err = disk.OpenFileDirectIO(filePath, readMode, 0666) 1108 r = &odirectReader{f, nil, nil, true, true, s, nil} 1109 } else { 1110 r, err = OpenFile(filePath, readMode, 0) 1111 } 1112 if err != nil { 1113 if osIsNotExist(err) { 1114 // Check if the object doesn't exist because its bucket 1115 // is missing in order to return the correct error. 1116 if err = Access(volumeDir); err != nil && osIsNotExist(err) { 1117 return nil, errVolumeNotFound 1118 } 1119 return nil, errFileNotFound 1120 } else if osIsPermission(err) { 1121 return nil, errFileAccessDenied 1122 } else if isSysErrNotDir(err) || isSysErrIsDir(err) { 1123 return nil, errFileNotFound 1124 } else if isSysErrHandleInvalid(err) { 1125 // This case is special and needs to be handled for windows. 1126 return nil, errFileNotFound 1127 } else if isSysErrIO(err) { 1128 return nil, errFaultyDisk 1129 } else if isSysErrTooManyFiles(err) { 1130 return nil, errTooManyOpenFiles 1131 } else if isSysErrInvalidArg(err) { 1132 st, _ := Lstat(filePath) 1133 if st != nil && st.IsDir() { 1134 // Linux returns InvalidArg for directory O_DIRECT 1135 // we need to keep this fallback code to return correct 1136 // errors upwards. 1137 return nil, errFileNotFound 1138 } 1139 return nil, errUnsupportedDisk 1140 } 1141 return nil, err 1142 } 1143 1144 defer r.Close() 1145 buf, err = ioutil.ReadAll(r) 1146 if err != nil { 1147 err = osErrToFileErr(err) 1148 } 1149 1150 return buf, err 1151 } 1152 1153 // ReadAll reads from r until an error or EOF and returns the data it read. 1154 // A successful call returns err == nil, not err == EOF. Because ReadAll is 1155 // defined to read from src until EOF, it does not treat an EOF from Read 1156 // as an error to be reported. 1157 // This API is meant to be used on files which have small memory footprint, do 1158 // not use this on large files as it would cause server to crash. 1159 func (s *xlStorage) ReadAll(ctx context.Context, volume string, path string) (buf []byte, err error) { 1160 volumeDir, err := s.getVolDir(volume) 1161 if err != nil { 1162 return nil, err 1163 } 1164 1165 // Validate file path length, before reading. 1166 filePath := pathJoin(volumeDir, path) 1167 if err = checkPathLength(filePath); err != nil { 1168 return nil, err 1169 } 1170 1171 requireDirectIO := globalStorageClass.GetDMA() == storageclass.DMAReadWrite 1172 return s.readAllData(volumeDir, filePath, requireDirectIO) 1173 } 1174 1175 // ReadFile reads exactly len(buf) bytes into buf. It returns the 1176 // number of bytes copied. The error is EOF only if no bytes were 1177 // read. On return, n == len(buf) if and only if err == nil. n == 0 1178 // for io.EOF. 1179 // 1180 // If an EOF happens after reading some but not all the bytes, 1181 // ReadFile returns ErrUnexpectedEOF. 1182 // 1183 // If the BitrotVerifier is not nil or not verified ReadFile 1184 // tries to verify whether the disk has bitrot. 1185 // 1186 // Additionally ReadFile also starts reading from an offset. ReadFile 1187 // semantics are same as io.ReadFull. 1188 func (s *xlStorage) ReadFile(ctx context.Context, volume string, path string, offset int64, buffer []byte, verifier *BitrotVerifier) (int64, error) { 1189 if offset < 0 { 1190 return 0, errInvalidArgument 1191 } 1192 1193 volumeDir, err := s.getVolDir(volume) 1194 if err != nil { 1195 return 0, err 1196 } 1197 1198 var n int 1199 1200 // Stat a volume entry. 1201 if err = Access(volumeDir); err != nil { 1202 if osIsNotExist(err) { 1203 return 0, errVolumeNotFound 1204 } else if isSysErrIO(err) { 1205 return 0, errFaultyDisk 1206 } else if osIsPermission(err) { 1207 return 0, errFileAccessDenied 1208 } 1209 return 0, err 1210 } 1211 1212 // Validate effective path length before reading. 1213 filePath := pathJoin(volumeDir, path) 1214 if err = checkPathLength(filePath); err != nil { 1215 return 0, err 1216 } 1217 1218 // Open the file for reading. 1219 file, err := Open(filePath) 1220 if err != nil { 1221 switch { 1222 case osIsNotExist(err): 1223 return 0, errFileNotFound 1224 case osIsPermission(err): 1225 return 0, errFileAccessDenied 1226 case isSysErrNotDir(err): 1227 return 0, errFileAccessDenied 1228 case isSysErrIO(err): 1229 return 0, errFaultyDisk 1230 case isSysErrTooManyFiles(err): 1231 return 0, errTooManyOpenFiles 1232 default: 1233 return 0, err 1234 } 1235 } 1236 1237 // Close the file descriptor. 1238 defer file.Close() 1239 1240 st, err := file.Stat() 1241 if err != nil { 1242 return 0, err 1243 } 1244 1245 // Verify it is a regular file, otherwise subsequent Seek is 1246 // undefined. 1247 if !st.Mode().IsRegular() { 1248 return 0, errIsNotRegular 1249 } 1250 1251 if verifier == nil { 1252 n, err = file.ReadAt(buffer, offset) 1253 return int64(n), err 1254 } 1255 1256 h := verifier.algorithm.New() 1257 if _, err = io.Copy(h, io.LimitReader(file, offset)); err != nil { 1258 return 0, err 1259 } 1260 1261 if n, err = io.ReadFull(file, buffer); err != nil { 1262 return int64(n), err 1263 } 1264 1265 if _, err = h.Write(buffer); err != nil { 1266 return 0, err 1267 } 1268 1269 if _, err = io.Copy(h, file); err != nil { 1270 return 0, err 1271 } 1272 1273 if !bytes.Equal(h.Sum(nil), verifier.sum) { 1274 return 0, errFileCorrupt 1275 } 1276 1277 return int64(len(buffer)), nil 1278 } 1279 1280 func (s *xlStorage) openFile(filePath string, mode int) (f *os.File, err error) { 1281 // Create top level directories if they don't exist. 1282 // with mode 0777 mkdir honors system umask. 1283 if err = mkdirAll(pathutil.Dir(filePath), 0777); err != nil { 1284 return nil, osErrToFileErr(err) 1285 } 1286 1287 w, err := OpenFile(filePath, mode|writeMode, 0666) 1288 if err != nil { 1289 // File path cannot be verified since one of the parents is a file. 1290 switch { 1291 case isSysErrIsDir(err): 1292 return nil, errIsNotRegular 1293 case osIsPermission(err): 1294 return nil, errFileAccessDenied 1295 case isSysErrIO(err): 1296 return nil, errFaultyDisk 1297 case isSysErrTooManyFiles(err): 1298 return nil, errTooManyOpenFiles 1299 default: 1300 return nil, err 1301 } 1302 } 1303 1304 return w, nil 1305 } 1306 1307 // To support O_DIRECT reads for erasure backends. 1308 type odirectReader struct { 1309 f *os.File 1310 buf []byte 1311 bufp *[]byte 1312 freshRead bool 1313 smallFile bool 1314 s *xlStorage 1315 err error 1316 } 1317 1318 // Read - Implements Reader interface. 1319 func (o *odirectReader) Read(buf []byte) (n int, err error) { 1320 if o.err != nil && (len(o.buf) == 0 || o.freshRead) { 1321 return 0, o.err 1322 } 1323 if o.buf == nil { 1324 if o.smallFile { 1325 o.bufp = o.s.poolSmall.Get().(*[]byte) 1326 } else { 1327 o.bufp = o.s.poolLarge.Get().(*[]byte) 1328 } 1329 } 1330 if o.freshRead { 1331 o.buf = *o.bufp 1332 n, err = o.f.Read(o.buf) 1333 if err != nil && err != io.EOF { 1334 if isSysErrInvalidArg(err) { 1335 if err = disk.DisableDirectIO(o.f); err != nil { 1336 o.err = err 1337 return n, err 1338 } 1339 n, err = o.f.Read(o.buf) 1340 } 1341 if err != nil && err != io.EOF { 1342 o.err = err 1343 return n, err 1344 } 1345 } 1346 if n == 0 { 1347 // err is likely io.EOF 1348 o.err = err 1349 return n, err 1350 } 1351 o.err = err 1352 o.buf = o.buf[:n] 1353 o.freshRead = false 1354 } 1355 if len(buf) >= len(o.buf) { 1356 n = copy(buf, o.buf) 1357 o.freshRead = true 1358 return n, o.err 1359 } 1360 n = copy(buf, o.buf) 1361 o.buf = o.buf[n:] 1362 // There is more left in buffer, do not return any EOF yet. 1363 return n, nil 1364 } 1365 1366 // Close - Release the buffer and close the file. 1367 func (o *odirectReader) Close() error { 1368 if o.smallFile { 1369 o.s.poolSmall.Put(o.bufp) 1370 } else { 1371 o.s.poolLarge.Put(o.bufp) 1372 } 1373 return o.f.Close() 1374 } 1375 1376 // ReadFileStream - Returns the read stream of the file. 1377 func (s *xlStorage) ReadFileStream(ctx context.Context, volume, path string, offset, length int64) (io.ReadCloser, error) { 1378 if offset < 0 { 1379 return nil, errInvalidArgument 1380 } 1381 1382 volumeDir, err := s.getVolDir(volume) 1383 if err != nil { 1384 return nil, err 1385 } 1386 1387 // Validate effective path length before reading. 1388 filePath := pathJoin(volumeDir, path) 1389 if err = checkPathLength(filePath); err != nil { 1390 return nil, err 1391 } 1392 1393 var file *os.File 1394 // O_DIRECT only supported if offset is zero 1395 if offset == 0 && globalStorageClass.GetDMA() == storageclass.DMAReadWrite { 1396 file, err = disk.OpenFileDirectIO(filePath, readMode, 0666) 1397 } else { 1398 // Open the file for reading. 1399 file, err = OpenFile(filePath, readMode, 0666) 1400 } 1401 if err != nil { 1402 switch { 1403 case osIsNotExist(err): 1404 if err = Access(volumeDir); err != nil && osIsNotExist(err) { 1405 return nil, errVolumeNotFound 1406 } 1407 return nil, errFileNotFound 1408 case osIsPermission(err): 1409 return nil, errFileAccessDenied 1410 case isSysErrNotDir(err): 1411 return nil, errFileAccessDenied 1412 case isSysErrIO(err): 1413 return nil, errFaultyDisk 1414 case isSysErrTooManyFiles(err): 1415 return nil, errTooManyOpenFiles 1416 case isSysErrInvalidArg(err): 1417 return nil, errUnsupportedDisk 1418 default: 1419 return nil, err 1420 } 1421 } 1422 1423 st, err := file.Stat() 1424 if err != nil { 1425 file.Close() 1426 return nil, err 1427 } 1428 1429 // Verify it is a regular file, otherwise subsequent Seek is 1430 // undefined. 1431 if !st.Mode().IsRegular() { 1432 file.Close() 1433 return nil, errIsNotRegular 1434 } 1435 1436 if offset == 0 && globalStorageClass.GetDMA() == storageclass.DMAReadWrite { 1437 or := &odirectReader{file, nil, nil, true, false, s, nil} 1438 if length <= smallFileThreshold { 1439 or = &odirectReader{file, nil, nil, true, true, s, nil} 1440 } 1441 r := struct { 1442 io.Reader 1443 io.Closer 1444 }{Reader: io.LimitReader(or, length), Closer: closeWrapper(func() error { 1445 return or.Close() 1446 })} 1447 return r, nil 1448 } 1449 1450 r := struct { 1451 io.Reader 1452 io.Closer 1453 }{Reader: io.LimitReader(file, length), Closer: closeWrapper(func() error { 1454 return file.Close() 1455 })} 1456 1457 if offset > 0 { 1458 if _, err = file.Seek(offset, io.SeekStart); err != nil { 1459 r.Close() 1460 return nil, err 1461 } 1462 } 1463 1464 // Add readahead to big reads 1465 if length >= readAheadSize { 1466 rc, err := readahead.NewReadCloserSize(r, readAheadBuffers, readAheadBufSize) 1467 if err != nil { 1468 r.Close() 1469 return nil, err 1470 } 1471 return rc, nil 1472 } 1473 1474 // Just add a small 64k buffer. 1475 r.Reader = bufio.NewReaderSize(r.Reader, 64<<10) 1476 return r, nil 1477 } 1478 1479 // closeWrapper converts a function to an io.Closer 1480 type closeWrapper func() error 1481 1482 // Close calls the wrapped function. 1483 func (c closeWrapper) Close() error { 1484 return c() 1485 } 1486 1487 // CreateFile - creates the file. 1488 func (s *xlStorage) CreateFile(ctx context.Context, volume, path string, fileSize int64, r io.Reader) (err error) { 1489 if fileSize < -1 { 1490 return errInvalidArgument 1491 } 1492 1493 volumeDir, err := s.getVolDir(volume) 1494 if err != nil { 1495 return err 1496 } 1497 1498 filePath := pathJoin(volumeDir, path) 1499 if err = checkPathLength(filePath); err != nil { 1500 return err 1501 } 1502 1503 parentFilePath := pathutil.Dir(filePath) 1504 defer func() { 1505 if err != nil { 1506 if volume == minioMetaTmpBucket { 1507 // only cleanup parent path if the 1508 // parent volume name is minioMetaTmpBucket 1509 removeAll(parentFilePath) 1510 } 1511 } 1512 }() 1513 1514 if fileSize >= 0 && fileSize <= smallFileThreshold { 1515 // For streams smaller than 128KiB we simply write them as O_DSYNC (fdatasync) 1516 // and not O_DIRECT to avoid the complexities of aligned I/O. 1517 w, err := s.openFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC) 1518 if err != nil { 1519 return err 1520 } 1521 defer w.Close() 1522 1523 written, err := io.Copy(w, r) 1524 if err != nil { 1525 return osErrToFileErr(err) 1526 } 1527 1528 if written < fileSize { 1529 return errLessData 1530 } else if written > fileSize { 1531 return errMoreData 1532 } 1533 1534 return nil 1535 } 1536 1537 // Create top level directories if they don't exist. 1538 // with mode 0777 mkdir honors system umask. 1539 if err = mkdirAll(parentFilePath, 0777); err != nil { 1540 return osErrToFileErr(err) 1541 } 1542 1543 w, err := OpenFileDirectIO(filePath, os.O_CREATE|os.O_WRONLY|os.O_EXCL, 0666) 1544 if err != nil { 1545 return osErrToFileErr(err) 1546 } 1547 1548 defer func() { 1549 disk.Fdatasync(w) // Only interested in flushing the size_t not mtime/atime 1550 w.Close() 1551 }() 1552 1553 bufp := s.poolLarge.Get().(*[]byte) 1554 defer s.poolLarge.Put(bufp) 1555 1556 written, err := xioutil.CopyAligned(w, r, *bufp, fileSize) 1557 if err != nil { 1558 return err 1559 } 1560 1561 if written < fileSize && fileSize >= 0 { 1562 return errLessData 1563 } else if written > fileSize && fileSize >= 0 { 1564 return errMoreData 1565 } 1566 1567 return nil 1568 } 1569 1570 func (s *xlStorage) WriteAll(ctx context.Context, volume string, path string, b []byte) (err error) { 1571 volumeDir, err := s.getVolDir(volume) 1572 if err != nil { 1573 return err 1574 } 1575 1576 filePath := pathJoin(volumeDir, path) 1577 if err = checkPathLength(filePath); err != nil { 1578 return err 1579 } 1580 1581 w, err := s.openFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC) 1582 if err != nil { 1583 return err 1584 } 1585 defer w.Close() 1586 1587 n, err := w.Write(b) 1588 if err != nil { 1589 return err 1590 } 1591 1592 if n != len(b) { 1593 return io.ErrShortWrite 1594 } 1595 1596 return nil 1597 } 1598 1599 // AppendFile - append a byte array at path, if file doesn't exist at 1600 // path this call explicitly creates it. 1601 func (s *xlStorage) AppendFile(ctx context.Context, volume string, path string, buf []byte) (err error) { 1602 volumeDir, err := s.getVolDir(volume) 1603 if err != nil { 1604 return err 1605 } 1606 1607 // Stat a volume entry. 1608 if err = Access(volumeDir); err != nil { 1609 if osIsNotExist(err) { 1610 return errVolumeNotFound 1611 } else if osIsPermission(err) { 1612 return errVolumeAccessDenied 1613 } else if isSysErrIO(err) { 1614 return errFaultyDisk 1615 } 1616 return err 1617 } 1618 1619 filePath := pathJoin(volumeDir, path) 1620 if err = checkPathLength(filePath); err != nil { 1621 return err 1622 } 1623 1624 var w *os.File 1625 // Create file if not found. Not doing O_DIRECT here to avoid the code that does buffer aligned writes. 1626 // AppendFile() is only used by healing code to heal objects written in old format. 1627 w, err = s.openFile(filePath, os.O_CREATE|os.O_APPEND|os.O_WRONLY) 1628 if err != nil { 1629 return err 1630 } 1631 defer w.Close() 1632 1633 n, err := w.Write(buf) 1634 if err != nil { 1635 return err 1636 } 1637 1638 if n != len(buf) { 1639 return io.ErrShortWrite 1640 } 1641 1642 return nil 1643 } 1644 1645 // CheckParts check if path has necessary parts available. 1646 func (s *xlStorage) CheckParts(ctx context.Context, volume string, path string, fi FileInfo) error { 1647 volumeDir, err := s.getVolDir(volume) 1648 if err != nil { 1649 return err 1650 } 1651 1652 // Stat a volume entry. 1653 if err = Access(volumeDir); err != nil { 1654 if osIsNotExist(err) { 1655 return errVolumeNotFound 1656 } 1657 return err 1658 } 1659 1660 for _, part := range fi.Parts { 1661 partPath := pathJoin(path, fi.DataDir, fmt.Sprintf("part.%d", part.Number)) 1662 filePath := pathJoin(volumeDir, partPath) 1663 if err = checkPathLength(filePath); err != nil { 1664 return err 1665 } 1666 st, err := Lstat(filePath) 1667 if err != nil { 1668 return osErrToFileErr(err) 1669 } 1670 if st.Mode().IsDir() { 1671 return errFileNotFound 1672 } 1673 // Check if shard is truncated. 1674 if st.Size() < fi.Erasure.ShardFileSize(part.Size) { 1675 return errFileCorrupt 1676 } 1677 } 1678 1679 return nil 1680 } 1681 1682 // CheckFile check if path has necessary metadata. 1683 // This function does the following check, suppose 1684 // you are creating a metadata file at "a/b/c/d/xl.meta", 1685 // makes sure that there is no `xl.meta` at 1686 // - "a/b/c/" 1687 // - "a/b/" 1688 // - "a/" 1689 func (s *xlStorage) CheckFile(ctx context.Context, volume string, path string) error { 1690 volumeDir, err := s.getVolDir(volume) 1691 if err != nil { 1692 return err 1693 } 1694 s.RLock() 1695 formatLegacy := s.formatLegacy 1696 s.RUnlock() 1697 1698 var checkFile func(p string) error 1699 checkFile = func(p string) error { 1700 if p == "." || p == SlashSeparator { 1701 return errPathNotFound 1702 } 1703 1704 filePath := pathJoin(volumeDir, p, xlStorageFormatFile) 1705 if err := checkPathLength(filePath); err != nil { 1706 return err 1707 } 1708 st, _ := Lstat(filePath) 1709 if st == nil { 1710 1711 if !formatLegacy { 1712 return errPathNotFound 1713 } 1714 1715 filePathOld := pathJoin(volumeDir, p, xlStorageFormatFileV1) 1716 if err := checkPathLength(filePathOld); err != nil { 1717 return err 1718 } 1719 1720 st, _ = Lstat(filePathOld) 1721 if st == nil { 1722 return errPathNotFound 1723 } 1724 } 1725 1726 if st != nil { 1727 if !st.Mode().IsRegular() { 1728 // not a regular file return error. 1729 return errFileNotFound 1730 } 1731 // Success fully found 1732 return nil 1733 } 1734 1735 return checkFile(pathutil.Dir(p)) 1736 } 1737 1738 return checkFile(path) 1739 } 1740 1741 // deleteFile deletes a file or a directory if its empty unless recursive 1742 // is set to true. If the target is successfully deleted, it will recursively 1743 // move up the tree, deleting empty parent directories until it finds one 1744 // with files in it. Returns nil for a non-empty directory even when 1745 // recursive is set to false. 1746 func (s *xlStorage) deleteFile(basePath, deletePath string, recursive bool) error { 1747 if basePath == "" || deletePath == "" { 1748 return nil 1749 } 1750 isObjectDir := HasSuffix(deletePath, SlashSeparator) 1751 basePath = pathutil.Clean(basePath) 1752 deletePath = pathutil.Clean(deletePath) 1753 if !strings.HasPrefix(deletePath, basePath) || deletePath == basePath { 1754 return nil 1755 } 1756 1757 var err error 1758 if recursive { 1759 err = renameAll(deletePath, pathutil.Join(s.diskPath, minioMetaTmpDeletedBucket, mustGetUUID())) 1760 } else { 1761 err = Remove(deletePath) 1762 } 1763 if err != nil { 1764 switch { 1765 case isSysErrNotEmpty(err): 1766 // if object is a directory, but if its not empty 1767 // return FileNotFound to indicate its an empty prefix. 1768 if isObjectDir { 1769 return errFileNotFound 1770 } 1771 // Ignore errors if the directory is not empty. The server relies on 1772 // this functionality, and sometimes uses recursion that should not 1773 // error on parent directories. 1774 return nil 1775 case osIsNotExist(err): 1776 return errFileNotFound 1777 case osIsPermission(err): 1778 return errFileAccessDenied 1779 case isSysErrIO(err): 1780 return errFaultyDisk 1781 default: 1782 return err 1783 } 1784 } 1785 1786 deletePath = pathutil.Dir(deletePath) 1787 1788 // Delete parent directory obviously not recursively. Errors for 1789 // parent directories shouldn't trickle down. 1790 s.deleteFile(basePath, deletePath, false) 1791 1792 return nil 1793 } 1794 1795 // DeleteFile - delete a file at path. 1796 func (s *xlStorage) Delete(ctx context.Context, volume string, path string, recursive bool) (err error) { 1797 volumeDir, err := s.getVolDir(volume) 1798 if err != nil { 1799 return err 1800 } 1801 1802 // Stat a volume entry. 1803 if err = Access(volumeDir); err != nil { 1804 if osIsNotExist(err) { 1805 return errVolumeNotFound 1806 } else if osIsPermission(err) { 1807 return errVolumeAccessDenied 1808 } else if isSysErrIO(err) { 1809 return errFaultyDisk 1810 } 1811 return err 1812 } 1813 1814 // Following code is needed so that we retain SlashSeparator suffix if any in 1815 // path argument. 1816 filePath := pathJoin(volumeDir, path) 1817 if err = checkPathLength(filePath); err != nil { 1818 return err 1819 } 1820 1821 // Delete file and delete parent directory as well if it's empty. 1822 return s.deleteFile(volumeDir, filePath, recursive) 1823 } 1824 1825 // RenameData - rename source path to destination path atomically, metadata and data directory. 1826 func (s *xlStorage) RenameData(ctx context.Context, srcVolume, srcPath string, fi FileInfo, dstVolume, dstPath string) (err error) { 1827 defer func() { 1828 if err == nil { 1829 if s.globalSync { 1830 globalSync() 1831 } 1832 } 1833 }() 1834 1835 srcVolumeDir, err := s.getVolDir(srcVolume) 1836 if err != nil { 1837 return err 1838 } 1839 1840 dstVolumeDir, err := s.getVolDir(dstVolume) 1841 if err != nil { 1842 return err 1843 } 1844 1845 // Stat a volume entry. 1846 if err = Access(srcVolumeDir); err != nil { 1847 if osIsNotExist(err) { 1848 return errVolumeNotFound 1849 } else if isSysErrIO(err) { 1850 return errFaultyDisk 1851 } 1852 return err 1853 } 1854 1855 if err = Access(dstVolumeDir); err != nil { 1856 if osIsNotExist(err) { 1857 return errVolumeNotFound 1858 } else if isSysErrIO(err) { 1859 return errFaultyDisk 1860 } 1861 return err 1862 } 1863 1864 srcFilePath := pathutil.Join(srcVolumeDir, pathJoin(srcPath, xlStorageFormatFile)) 1865 dstFilePath := pathutil.Join(dstVolumeDir, pathJoin(dstPath, xlStorageFormatFile)) 1866 1867 var srcDataPath string 1868 var dstDataPath string 1869 dataDir := retainSlash(fi.DataDir) 1870 if dataDir != "" { 1871 srcDataPath = retainSlash(pathJoin(srcVolumeDir, srcPath, dataDir)) 1872 // make sure to always use path.Join here, do not use pathJoin as 1873 // it would additionally add `/` at the end and it comes in the 1874 // way of renameAll(), parentDir creation. 1875 dstDataPath = pathutil.Join(dstVolumeDir, dstPath, dataDir) 1876 } 1877 1878 if err = checkPathLength(srcFilePath); err != nil { 1879 return err 1880 } 1881 1882 if err = checkPathLength(dstFilePath); err != nil { 1883 return err 1884 } 1885 1886 dstBuf, err := xioutil.ReadFile(dstFilePath) 1887 if err != nil { 1888 if !osIsNotExist(err) { 1889 return osErrToFileErr(err) 1890 } 1891 // errFileNotFound comes here. 1892 err = s.renameLegacyMetadata(dstVolumeDir, dstPath) 1893 if err != nil && err != errFileNotFound { 1894 return err 1895 } 1896 if err == nil { 1897 dstBuf, err = xioutil.ReadFile(dstFilePath) 1898 if err != nil && !osIsNotExist(err) { 1899 return osErrToFileErr(err) 1900 } 1901 } 1902 if err == errFileNotFound { 1903 // Verification to ensure that we 1904 // don't have objects already created 1905 // at this location, verify that resultant 1906 // directories don't have any unexpected 1907 // directories that we do not understand 1908 // or expect. If its already there we should 1909 // make sure to reject further renames 1910 // for such objects. 1911 // 1912 // This elaborate check is necessary to avoid 1913 // scenarios such as these. 1914 // 1915 // bucket1/name1/obj1/xl.meta 1916 // bucket1/name1/xl.meta --> this should never 1917 // be allowed. 1918 { 1919 entries, err := readDirN(pathutil.Dir(dstFilePath), 1) 1920 if err != nil && err != errFileNotFound { 1921 return err 1922 } 1923 if len(entries) > 0 { 1924 entry := pathutil.Clean(entries[0]) 1925 if entry != legacyDataDir { 1926 _, uerr := uuid.Parse(entry) 1927 if uerr != nil { 1928 return errFileParentIsFile 1929 } 1930 } 1931 } 1932 } 1933 } 1934 } 1935 1936 var xlMeta xlMetaV2 1937 var legacyPreserved bool 1938 if len(dstBuf) > 0 { 1939 if isXL2V1Format(dstBuf) { 1940 if err = xlMeta.Load(dstBuf); err != nil { 1941 logger.LogIf(s.ctx, err) 1942 return err 1943 } 1944 } else { 1945 // This code-path is to preserve the legacy data. 1946 xlMetaLegacy := &xlMetaV1Object{} 1947 var json = jsoniter.ConfigCompatibleWithStandardLibrary 1948 if err := json.Unmarshal(dstBuf, xlMetaLegacy); err != nil { 1949 logger.LogIf(s.ctx, err) 1950 return errFileCorrupt 1951 } 1952 if err = xlMeta.AddLegacy(xlMetaLegacy); err != nil { 1953 logger.LogIf(s.ctx, err) 1954 return errFileCorrupt 1955 } 1956 legacyPreserved = true 1957 } 1958 } else { 1959 s.RLock() 1960 formatLegacy := s.formatLegacy 1961 s.RUnlock() 1962 // It is possible that some drives may not have `xl.meta` file 1963 // in such scenarios verify if atleast `part.1` files exist 1964 // to verify for legacy version. 1965 if formatLegacy { 1966 // We only need this code if we are moving 1967 // from `xl.json` to `xl.meta`, we can avoid 1968 // one extra readdir operation here for all 1969 // new deployments. 1970 currentDataPath := pathJoin(dstVolumeDir, dstPath) 1971 entries, err := readDirN(currentDataPath, 1) 1972 if err != nil && err != errFileNotFound { 1973 return osErrToFileErr(err) 1974 } 1975 for _, entry := range entries { 1976 if entry == xlStorageFormatFile || strings.HasSuffix(entry, slashSeparator) { 1977 continue 1978 } 1979 if strings.HasPrefix(entry, "part.") { 1980 legacyPreserved = true 1981 break 1982 } 1983 } 1984 } 1985 } 1986 1987 if legacyPreserved { 1988 // Preserve all the legacy data, could be slow, but at max there can be 10,000 parts. 1989 currentDataPath := pathJoin(dstVolumeDir, dstPath) 1990 entries, err := readDir(currentDataPath) 1991 if err != nil { 1992 return osErrToFileErr(err) 1993 } 1994 1995 legacyDataPath := pathJoin(dstVolumeDir, dstPath, legacyDataDir) 1996 // legacy data dir means its old content, honor system umask. 1997 if err = reliableMkdirAll(legacyDataPath, 0777); err != nil { 1998 return osErrToFileErr(err) 1999 } 2000 2001 for _, entry := range entries { 2002 // Skip xl.meta renames further, also ignore any directories such as `legacyDataDir` 2003 if entry == xlStorageFormatFile || strings.HasSuffix(entry, slashSeparator) { 2004 continue 2005 } 2006 2007 if err = Rename(pathJoin(currentDataPath, entry), pathJoin(legacyDataPath, entry)); err != nil { 2008 return osErrToFileErr(err) 2009 } 2010 } 2011 } 2012 2013 var oldDstDataPath string 2014 if fi.VersionID == "" { 2015 // return the latest "null" versionId info 2016 ofi, err := xlMeta.ToFileInfo(dstVolume, dstPath, nullVersionID) 2017 if err == nil && !ofi.Deleted { 2018 if xlMeta.SharedDataDirCountStr(nullVersionID, ofi.DataDir) == 0 { 2019 // Purge the destination path as we are not preserving anything 2020 // versioned object was not requested. 2021 oldDstDataPath = pathJoin(dstVolumeDir, dstPath, ofi.DataDir) 2022 } 2023 } 2024 } 2025 2026 if err = xlMeta.AddVersion(fi); err != nil { 2027 return err 2028 } 2029 2030 dstBuf, err = xlMeta.AppendTo(nil) 2031 if err != nil { 2032 logger.LogIf(ctx, err) 2033 return errFileCorrupt 2034 } 2035 2036 if srcDataPath != "" { 2037 if err = s.WriteAll(ctx, srcVolume, pathJoin(srcPath, xlStorageFormatFile), dstBuf); err != nil { 2038 return err 2039 } 2040 2041 if oldDstDataPath != "" { 2042 renameAll(oldDstDataPath, pathutil.Join(s.diskPath, minioMetaTmpDeletedBucket, mustGetUUID())) 2043 } 2044 2045 // renameAll only for objects that have xl.meta not saved inline. 2046 if len(fi.Data) == 0 && fi.Size > 0 { 2047 renameAll(dstDataPath, pathutil.Join(s.diskPath, minioMetaTmpDeletedBucket, mustGetUUID())) 2048 if err = renameAll(srcDataPath, dstDataPath); err != nil { 2049 logger.LogIf(ctx, err) 2050 return osErrToFileErr(err) 2051 } 2052 } 2053 2054 // Commit meta-file 2055 if err = renameAll(srcFilePath, dstFilePath); err != nil { 2056 logger.LogIf(ctx, err) 2057 return osErrToFileErr(err) 2058 } 2059 } else { 2060 // Write meta-file directly, no data 2061 if err = s.WriteAll(ctx, dstVolume, pathJoin(dstPath, xlStorageFormatFile), dstBuf); err != nil { 2062 logger.LogIf(ctx, err) 2063 return err 2064 } 2065 } 2066 2067 // Remove parent dir of the source file if empty 2068 parentDir := pathutil.Dir(srcFilePath) 2069 s.deleteFile(srcVolumeDir, parentDir, false) 2070 return nil 2071 } 2072 2073 // RenameFile - rename source path to destination path atomically. 2074 func (s *xlStorage) RenameFile(ctx context.Context, srcVolume, srcPath, dstVolume, dstPath string) (err error) { 2075 srcVolumeDir, err := s.getVolDir(srcVolume) 2076 if err != nil { 2077 return err 2078 } 2079 dstVolumeDir, err := s.getVolDir(dstVolume) 2080 if err != nil { 2081 return err 2082 } 2083 // Stat a volume entry. 2084 if err = Access(srcVolumeDir); err != nil { 2085 if osIsNotExist(err) { 2086 return errVolumeNotFound 2087 } else if isSysErrIO(err) { 2088 return errFaultyDisk 2089 } 2090 return err 2091 } 2092 2093 if err = Access(dstVolumeDir); err != nil { 2094 if osIsNotExist(err) { 2095 return errVolumeNotFound 2096 } else if isSysErrIO(err) { 2097 return errFaultyDisk 2098 } 2099 return err 2100 } 2101 2102 srcIsDir := HasSuffix(srcPath, SlashSeparator) 2103 dstIsDir := HasSuffix(dstPath, SlashSeparator) 2104 // Either src and dst have to be directories or files, else return error. 2105 if !(srcIsDir && dstIsDir || !srcIsDir && !dstIsDir) { 2106 return errFileAccessDenied 2107 } 2108 srcFilePath := pathutil.Join(srcVolumeDir, srcPath) 2109 if err = checkPathLength(srcFilePath); err != nil { 2110 return err 2111 } 2112 dstFilePath := pathutil.Join(dstVolumeDir, dstPath) 2113 if err = checkPathLength(dstFilePath); err != nil { 2114 return err 2115 } 2116 if srcIsDir { 2117 // If source is a directory, we expect the destination to be non-existent but we 2118 // we still need to allow overwriting an empty directory since it represents 2119 // an object empty directory. 2120 dirInfo, err := Lstat(dstFilePath) 2121 if isSysErrIO(err) { 2122 return errFaultyDisk 2123 } 2124 if err != nil { 2125 if !osIsNotExist(err) { 2126 return err 2127 } 2128 } else { 2129 if !dirInfo.IsDir() { 2130 return errFileAccessDenied 2131 } 2132 if err = Remove(dstFilePath); err != nil { 2133 if isSysErrNotEmpty(err) { 2134 return errFileAccessDenied 2135 } 2136 return err 2137 } 2138 } 2139 } 2140 2141 if err = renameAll(srcFilePath, dstFilePath); err != nil { 2142 return osErrToFileErr(err) 2143 } 2144 2145 // Remove parent dir of the source file if empty 2146 parentDir := pathutil.Dir(srcFilePath) 2147 s.deleteFile(srcVolumeDir, parentDir, false) 2148 2149 return nil 2150 } 2151 2152 func (s *xlStorage) bitrotVerify(partPath string, partSize int64, algo BitrotAlgorithm, sum []byte, shardSize int64) error { 2153 // Open the file for reading. 2154 file, err := Open(partPath) 2155 if err != nil { 2156 return osErrToFileErr(err) 2157 } 2158 2159 // Close the file descriptor. 2160 defer file.Close() 2161 fi, err := file.Stat() 2162 if err != nil { 2163 // Unable to stat on the file, return an expected error 2164 // for healing code to fix this file. 2165 return err 2166 } 2167 return bitrotVerify(file, fi.Size(), partSize, algo, sum, shardSize) 2168 } 2169 2170 func (s *xlStorage) VerifyFile(ctx context.Context, volume, path string, fi FileInfo) (err error) { 2171 volumeDir, err := s.getVolDir(volume) 2172 if err != nil { 2173 return err 2174 } 2175 2176 // Stat a volume entry. 2177 if err = Access(volumeDir); err != nil { 2178 if osIsNotExist(err) { 2179 return errVolumeNotFound 2180 } else if isSysErrIO(err) { 2181 return errFaultyDisk 2182 } else if osIsPermission(err) { 2183 return errVolumeAccessDenied 2184 } 2185 return err 2186 } 2187 2188 erasure := fi.Erasure 2189 for _, part := range fi.Parts { 2190 checksumInfo := erasure.GetChecksumInfo(part.Number) 2191 partPath := pathJoin(volumeDir, path, fi.DataDir, fmt.Sprintf("part.%d", part.Number)) 2192 if err := s.bitrotVerify(partPath, 2193 erasure.ShardFileSize(part.Size), 2194 checksumInfo.Algorithm, 2195 checksumInfo.Hash, erasure.ShardSize()); err != nil { 2196 if !IsErr(err, []error{ 2197 errFileNotFound, 2198 errVolumeNotFound, 2199 errFileCorrupt, 2200 }...) { 2201 logger.GetReqInfo(s.ctx).AppendTags("disk", s.String()) 2202 logger.LogIf(s.ctx, err) 2203 } 2204 return err 2205 } 2206 } 2207 2208 return nil 2209 }