gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/renter/filesystem/dirnode.go (about) 1 package filesystem 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "math" 7 "os" 8 "path/filepath" 9 "strings" 10 "sync" 11 "time" 12 13 "gitlab.com/NebulousLabs/errors" 14 "gitlab.com/SkynetLabs/skyd/skymodules" 15 "gitlab.com/SkynetLabs/skyd/skymodules/renter/filesystem/siadir" 16 "gitlab.com/SkynetLabs/skyd/skymodules/renter/filesystem/siafile" 17 "go.sia.tech/siad/crypto" 18 "go.sia.tech/siad/modules" 19 ) 20 21 type ( 22 // DirNode is a node which references a SiaDir. 23 DirNode struct { 24 node 25 26 directories map[string]*DirNode 27 files map[string]*FileNode 28 29 // lazySiaDir is the SiaDir of the DirNode. 'lazy' means that it will 30 // only be loaded on demand and destroyed as soon as the length of 31 // 'threads' reaches 0. 32 lazySiaDir **siadir.SiaDir 33 } 34 ) 35 36 // Close calls close on the DirNode and also removes the dNode from its parent 37 // if it's no longer being used and if it doesn't have any children which are 38 // currently in use. This happens iteratively for all parent as long as 39 // removing a child resulted in them not having any children left. 40 func (n *DirNode) Close() error { 41 // If a parent exists, we need to lock it while closing a child. 42 parent := n.node.managedLockWithParent() 43 44 // call private close method. 45 n.closeDirNode() 46 47 // Remove node from parent if there are no more children after this close. 48 removeDir := len(n.threads) == 0 && len(n.directories) == 0 && len(n.files) == 0 49 if parent != nil && removeDir { 50 parent.removeDir(n) 51 } 52 53 // Unlock child and parent. 54 n.mu.Unlock() 55 if parent != nil { 56 parent.mu.Unlock() 57 // Check if the parent needs to be removed from its parent too. 58 parent.managedTryRemoveFromParentsIteratively() 59 } 60 61 return nil 62 } 63 64 // Delete is a wrapper for SiaDir.Delete. 65 func (n *DirNode) Delete() error { 66 n.mu.Lock() 67 defer n.mu.Unlock() 68 sd, err := n.siaDir() 69 if err != nil { 70 return err 71 } 72 return sd.Delete() 73 } 74 75 // Deleted is a wrapper for SiaDir.Deleted. 76 func (n *DirNode) Deleted() (bool, error) { 77 n.mu.Lock() 78 sd, err := n.siaDir() 79 n.mu.Unlock() 80 if err != nil { 81 return false, err 82 } 83 return sd.Deleted(), nil 84 } 85 86 // Dir will return a child dir of this directory if it exists. 87 func (n *DirNode) Dir(name string) (*DirNode, error) { 88 n.mu.Lock() 89 node, err := n.openDir(name) 90 n.mu.Unlock() 91 return node, errors.AddContext(err, "unable to open child directory") 92 } 93 94 // DirReader is a wrapper for SiaDir.DirReader. 95 func (n *DirNode) DirReader() (*siadir.DirReader, error) { 96 n.mu.Lock() 97 defer n.mu.Unlock() 98 sd, err := n.siaDir() 99 if err != nil { 100 return nil, err 101 } 102 return sd.DirReader() 103 } 104 105 // File will return a child file of this directory if it exists. 106 func (n *DirNode) File(name string) (*FileNode, error) { 107 n.mu.Lock() 108 node, err := n.openFile(name) 109 n.mu.Unlock() 110 return node, errors.AddContext(err, "unable to open child file") 111 } 112 113 // Metadata is a wrapper for SiaDir.Metadata. 114 func (n *DirNode) Metadata() (siadir.Metadata, error) { 115 n.mu.Lock() 116 defer n.mu.Unlock() 117 sd, err := n.siaDir() 118 if os.IsNotExist(err) { 119 return siadir.Metadata{}, ErrNotExist 120 } 121 if err != nil { 122 return siadir.Metadata{}, err 123 } 124 return sd.Metadata(), nil 125 } 126 127 // Path is a wrapper for SiaDir.Path. 128 func (n *DirNode) Path() (string, error) { 129 n.mu.Lock() 130 defer n.mu.Unlock() 131 sd, err := n.siaDir() 132 if err != nil { 133 return "", err 134 } 135 return sd.Path(), nil 136 } 137 138 // UpdateLastHealthCheckTime is a wrapper for SiaDir.UpdateLastHealthCheckTime. 139 func (n *DirNode) UpdateLastHealthCheckTime(aggregateLastHealthCheckTime, lastHealthCheckTime time.Time) error { 140 n.mu.Lock() 141 defer n.mu.Unlock() 142 sd, err := n.siaDir() 143 if err != nil { 144 return err 145 } 146 return sd.UpdateLastHealthCheckTime(aggregateLastHealthCheckTime, lastHealthCheckTime) 147 } 148 149 // UpdateMetadata is a wrapper for SiaDir.UpdateMetadata. 150 func (n *DirNode) UpdateMetadata(md siadir.Metadata) error { 151 n.mu.Lock() 152 defer n.mu.Unlock() 153 sd, err := n.siaDir() 154 if err != nil { 155 return err 156 } 157 return sd.UpdateMetadata(md) 158 } 159 160 // managedList returns the files and dirs within the SiaDir specified by siaPath. 161 // offlineMap, goodForRenewMap and contractMap don't need to be provided if 162 // 'cached' is set to 'true'. 163 func (n *DirNode) managedList(fsRoot string, recursive, cached bool, offlineMap map[string]bool, goodForRenewMap map[string]bool, contractsMap map[string]skymodules.RenterContract, flf skymodules.FileListFunc, dlf skymodules.DirListFunc) error { 164 // Prepare a pool of workers. 165 numThreads := 40 166 dirLoadChan := make(chan *DirNode, numThreads) 167 fileLoadChan := make(chan func() (*FileNode, error), numThreads) 168 dirWorker := func() { 169 for sd := range dirLoadChan { 170 var di skymodules.DirectoryInfo 171 var err error 172 if sd.managedAbsPath() == fsRoot { 173 di, err = sd.managedInfo(skymodules.RootSiaPath()) 174 } else { 175 di, err = sd.managedInfo(nodeSiaPath(fsRoot, &sd.node)) 176 } 177 sd.Close() 178 if errors.Contains(err, ErrNotExist) { 179 continue 180 } 181 if err != nil { 182 n.staticLog.Debugf("Failed to get DirectoryInfo of '%v': %v", sd.managedAbsPath(), err) 183 continue 184 } 185 dlf(di) 186 } 187 } 188 fileWorker := func() { 189 for load := range fileLoadChan { 190 sf, err := load() 191 if err != nil { 192 n.staticLog.Debugf("Failed to load file: %v", err) 193 continue 194 } 195 var fi skymodules.FileInfo 196 if cached { 197 fi, err = sf.staticCachedInfo(nodeSiaPath(fsRoot, &sf.node)) 198 } else { 199 fi, err = sf.managedFileInfo(nodeSiaPath(fsRoot, &sf.node), offlineMap, goodForRenewMap, contractsMap) 200 } 201 if errors.Contains(err, ErrNotExist) { 202 continue 203 } 204 if err != nil { 205 n.staticLog.Debugf("Failed to get FileInfo of '%v': %v", sf.managedAbsPath(), err) 206 continue 207 } 208 flf(fi) 209 } 210 } 211 // Spin the workers up. 212 var wg sync.WaitGroup 213 for i := 0; i < numThreads/2; i++ { 214 wg.Add(1) 215 go func() { 216 dirWorker() 217 wg.Done() 218 }() 219 wg.Add(1) 220 go func() { 221 fileWorker() 222 wg.Done() 223 }() 224 } 225 err := n.managedRecursiveList(recursive, cached, fileLoadChan, dirLoadChan) 226 // Signal the workers that we are done adding work and wait for them to 227 // finish any pending work. 228 close(dirLoadChan) 229 close(fileLoadChan) 230 wg.Wait() 231 return err 232 } 233 234 // managedRecursiveList returns the files and dirs within the SiaDir. 235 func (n *DirNode) managedRecursiveList(recursive, cached bool, fileLoadChan chan func() (*FileNode, error), dirLoadChan chan *DirNode) error { 236 // Get DirectoryInfo of dir itself. 237 dirLoadChan <- n.managedCopy() 238 // Read dir. 239 fis, err := ioutil.ReadDir(n.managedAbsPath()) 240 if err != nil { 241 return err 242 } 243 // Separate dirs and files. 244 var dirNames, fileNames []string 245 for _, info := range fis { 246 // Skip non-siafiles and non-dirs. 247 if !info.IsDir() && filepath.Ext(info.Name()) != skymodules.SiaFileExtension { 248 continue 249 } 250 if info.IsDir() { 251 dirNames = append(dirNames, info.Name()) 252 continue 253 } 254 fileNames = append(fileNames, strings.TrimSuffix(info.Name(), skymodules.SiaFileExtension)) 255 } 256 // Handle dirs first. 257 for _, dirName := range dirNames { 258 // Open the dir. 259 n.mu.Lock() 260 dir, err := n.openDir(dirName) 261 n.mu.Unlock() 262 if errors.Contains(err, ErrNotExist) { 263 continue 264 } 265 if err != nil { 266 return err 267 } 268 if recursive { 269 // Call managedList on the child if 'recursive' was specified. 270 err = dir.managedRecursiveList(recursive, cached, fileLoadChan, dirLoadChan) 271 } else { 272 // If not recursive, hand a copy to the worker. It will handle closing it. 273 dirLoadChan <- dir.managedCopy() 274 } 275 if err != nil { 276 return err 277 } 278 err = dir.Close() 279 if err != nil { 280 return err 281 } 282 continue 283 } 284 // Check if there are any files to handle. 285 if len(fileNames) == 0 { 286 return nil 287 } 288 // Handle files by sending a method to load them to the workers. Wee add all 289 // of them to the waitgroup to be able to tell when to release the mutex. 290 n.mu.Lock() 291 defer n.mu.Unlock() 292 var wg sync.WaitGroup 293 wg.Add(len(fileNames)) 294 for i := range fileNames { 295 fileName := fileNames[i] 296 f := func() (*FileNode, error) { 297 file, err := n.readonlyOpenFile(fileName) 298 wg.Done() 299 if err != nil { 300 return nil, err 301 } 302 return file, nil // no need to close it since it was created using readonlyOpenFile. 303 } 304 fileLoadChan <- f 305 } 306 // Wait for the workers to finish all calls to `openFile` before releasing the 307 // lock. 308 wg.Wait() 309 return nil 310 } 311 312 // close calls the common close method. 313 func (n *DirNode) closeDirNode() { 314 n.node.closeNode() 315 // If no more threads use the directory we delete the SiaDir to invalidate 316 // the cache. 317 if len(n.threads) == 0 { 318 *n.lazySiaDir = nil 319 } 320 } 321 322 // managedNewSiaFileFromReader will read a siafile and its chunks from the given 323 // reader and add it to the directory. This will always load the file from the 324 // given reader. 325 func (n *DirNode) managedNewSiaFileFromExisting(sf *siafile.SiaFile, chunks siafile.Chunks) error { 326 n.mu.Lock() 327 defer n.mu.Unlock() 328 // Get the initial path of the siafile. 329 path := sf.SiaFilePath() 330 // Check if the path is taken. 331 currentPath, exists := n.uniquePrefix(path, sf.UID()) 332 if exists { 333 return nil // file already exists 334 } 335 // Either the file doesn't exist yet or we found a filename that doesn't 336 // exist. Update the UID for safety and set the correct siafilepath. 337 sf.UpdateUniqueID() 338 sf.SetSiaFilePath(currentPath) 339 // Save the file to disk. 340 if err := sf.SaveWithChunks(chunks); err != nil { 341 return err 342 } 343 // Add the node to the dir. 344 fileName := strings.TrimSuffix(filepath.Base(currentPath), skymodules.SiaFileExtension) 345 fn := &FileNode{ 346 node: newNode(n, currentPath, fileName, 0, n.staticWal, n.staticLog), 347 SiaFile: sf, 348 } 349 n.files[fileName] = fn 350 return nil 351 } 352 353 // managedNewSiaFileFromLegacyData adds an existing SiaFile to the filesystem 354 // using the provided siafile.FileData object. 355 func (n *DirNode) managedNewSiaFileFromLegacyData(fileName string, fd siafile.FileData) (*FileNode, error) { 356 n.mu.Lock() 357 defer n.mu.Unlock() 358 // Check if the path is taken. 359 path := filepath.Join(n.absPath(), fileName+skymodules.SiaFileExtension) 360 if _, err := os.Stat(filepath.Join(path)); !os.IsNotExist(err) { 361 return nil, ErrExists 362 } 363 // Check if the file or folder exists already. 364 key := strings.TrimSuffix(fileName, skymodules.SiaFileExtension) 365 if exists := n.childExists(key); exists { 366 return nil, ErrExists 367 } 368 // Otherwise create the file. 369 sf, err := siafile.NewFromLegacyData(fd, path, n.staticWal) 370 if err != nil { 371 return nil, err 372 } 373 // Add it to the node. 374 fn := &FileNode{ 375 node: newNode(n, path, key, 0, n.staticWal, n.staticLog), 376 SiaFile: sf, 377 } 378 n.files[key] = fn 379 return fn.managedCopy(), nil 380 } 381 382 // uniquePrefix returns a new path for the siafile with the given path 383 // and uid by adding a suffix to the current path and incrementing it as long as 384 // the resulting path is already taken. 385 func (n *DirNode) uniquePrefix(path string, uid siafile.SiafileUID) (string, bool) { 386 suffix := 0 387 currentPath := path 388 for { 389 fileName := strings.TrimSuffix(filepath.Base(currentPath), skymodules.SiaFileExtension) 390 oldFile, err := n.openFile(fileName) 391 exists := err == nil 392 if exists && oldFile.UID() == uid { 393 oldFile.managedClose() 394 return "", true // skip file since it already exists 395 } else if exists { 396 // Exists: update currentPath and fileName 397 suffix++ 398 currentPath = strings.TrimSuffix(path, skymodules.SiaFileExtension) 399 currentPath = fmt.Sprintf("%v_%v%v", currentPath, suffix, skymodules.SiaFileExtension) 400 fileName = filepath.Base(currentPath) 401 oldFile.managedClose() 402 continue 403 } 404 break 405 } 406 return currentPath, false 407 } 408 409 // siaDir is a wrapper for the lazySiaDir field. 410 func (n *DirNode) siaDir() (*siadir.SiaDir, error) { 411 if *n.lazySiaDir != nil { 412 return *n.lazySiaDir, nil 413 } 414 sd, err := siadir.LoadSiaDir(n.absPath(), modules.ProdDependencies) 415 if os.IsNotExist(err) { 416 return nil, ErrNotExist 417 } 418 if err != nil { 419 return nil, err 420 } 421 *n.lazySiaDir = sd 422 return sd, nil 423 } 424 425 // managedTryRemoveFromParentsIteratively will remove the DirNode from its 426 // parent if it doesn't have any more files or directories as children. It will 427 // do so iteratively until it reaches an acestor with children. 428 func (n *DirNode) managedTryRemoveFromParentsIteratively() { 429 n.mu.Lock() 430 child := n 431 parent := n.parent 432 n.mu.Unlock() 433 434 // Iteratively try to remove from parents as long as children got 435 // removed. 436 removeDir := true 437 for removeDir && parent != nil { 438 parent.mu.Lock() 439 child.mu.Lock() 440 removeDir = len(child.threads)+len(child.directories)+len(child.files) == 0 441 if removeDir { 442 parent.removeDir(child) 443 } 444 child.mu.Unlock() 445 child, parent = parent, parent.parent 446 child.mu.Unlock() // parent became child 447 } 448 } 449 450 // managedDelete recursively deletes a dNode from disk. 451 func (n *DirNode) managedDelete() error { 452 // If there is a parent lock it. 453 parent := n.managedLockWithParent() 454 if parent != nil { 455 defer parent.mu.Unlock() 456 } 457 defer n.mu.Unlock() 458 // Get contents of dir. 459 dirsToLock := n.childDirs() 460 var filesToDelete []*FileNode 461 var lockedNodes []*node 462 for _, file := range n.childFiles() { 463 file.node.mu.Lock() 464 file.SiaFile.Lock() 465 lockedNodes = append(lockedNodes, &file.node) 466 filesToDelete = append(filesToDelete, file) 467 } 468 // Unlock all locked nodes regardless of errors. 469 defer func() { 470 for _, file := range filesToDelete { 471 file.Unlock() 472 } 473 for _, node := range lockedNodes { 474 node.mu.Unlock() 475 } 476 }() 477 // Lock dir and all open children. Remember in which order we acquired the 478 // locks. 479 for len(dirsToLock) > 0 { 480 // Get next dir. 481 d := dirsToLock[0] 482 dirsToLock = dirsToLock[1:] 483 // Lock the dir. 484 d.mu.Lock() 485 lockedNodes = append(lockedNodes, &d.node) 486 // Remember the open files. 487 for _, file := range d.files { 488 file.node.mu.Lock() 489 file.SiaFile.Lock() 490 lockedNodes = append(lockedNodes, &file.node) 491 filesToDelete = append(filesToDelete, file) 492 } 493 // Add the open dirs to dirsToLock. 494 dirsToLock = append(dirsToLock, d.childDirs()...) 495 } 496 // Delete the dir. 497 dir, err := n.siaDir() 498 if err != nil { 499 return err 500 } 501 err = dir.Delete() 502 if err != nil { 503 return err 504 } 505 // Remove the dir from the parent if it exists. 506 if n.parent != nil { 507 n.parent.removeDir(n) 508 } 509 // Delete all the open files in memory. 510 for _, file := range filesToDelete { 511 file.UnmanagedSetDeleted(true) 512 } 513 return nil 514 } 515 516 // managedDeleteFile deletes the file with the given name from the directory. 517 func (n *DirNode) managedDeleteFile(fileName string) error { 518 n.mu.Lock() 519 defer n.mu.Unlock() 520 521 // Check if the file is open in memory. If it is delete it. 522 sf, exists := n.files[fileName] 523 if exists { 524 err := sf.managedDelete() 525 if err != nil { 526 return err 527 } 528 n.removeFile(sf) 529 return nil 530 } 531 532 // Check whether the file is actually a directory. 533 _, exists = n.directories[fileName] 534 if exists { 535 return ErrDeleteFileIsDir 536 } 537 538 // Check if the on-disk version is a file. This check is needed because 539 // os.Remove will delete an empty directory without returning any error, if 540 // the user has a directory name 'dir.sia' it could cause an edge case. 541 // 542 // There is a test for this edge case in the integration test called 543 // 'TestUploadAfterDelete'. 544 sysPath := filepath.Join(n.absPath(), fileName+skymodules.SiaFileExtension) 545 info, err := os.Stat(sysPath) 546 if os.IsNotExist(err) { 547 return errors.Extend(err, ErrNotExist) 548 } else if err != nil { 549 return errors.AddContext(err, "unable to find file") 550 } 551 if info.IsDir() { 552 return ErrDeleteFileIsDir 553 } 554 555 // Otherwise simply delete the file. 556 err = os.Remove(sysPath) 557 return errors.AddContext(err, "unable to delete file") 558 } 559 560 // managedInfo builds and returns the DirectoryInfo of a SiaDir. 561 func (n *DirNode) managedInfo(siaPath skymodules.SiaPath) (skymodules.DirectoryInfo, error) { 562 // Grab the siadir metadata 563 metadata, err := n.Metadata() 564 if err != nil { 565 return skymodules.DirectoryInfo{}, err 566 } 567 aggregateMaxHealth := math.Max(metadata.AggregateHealth, metadata.AggregateStuckHealth) 568 maxHealth := math.Max(metadata.Health, metadata.StuckHealth) 569 return skymodules.DirectoryInfo{ 570 // Aggregate Fields 571 AggregateHealth: metadata.AggregateHealth, 572 AggregateLastHealthCheckTime: metadata.AggregateLastHealthCheckTime, 573 AggregateMaxHealth: aggregateMaxHealth, 574 AggregateMaxHealthPercentage: skymodules.HealthPercentage(aggregateMaxHealth), 575 AggregateMinRedundancy: metadata.AggregateMinRedundancy, 576 AggregateMostRecentModTime: metadata.AggregateModTime, 577 AggregateNumFiles: metadata.AggregateNumFiles, 578 AggregateNumLostFiles: metadata.AggregateNumLostFiles, 579 AggregateNumStuckChunks: metadata.AggregateNumStuckChunks, 580 AggregateNumSubDirs: metadata.AggregateNumSubDirs, 581 AggregateNumUnfinishedFiles: metadata.AggregateNumUnfinishedFiles, 582 AggregateRepairSize: metadata.AggregateRepairSize, 583 AggregateSize: metadata.AggregateSize, 584 AggregateStuckHealth: metadata.AggregateStuckHealth, 585 AggregateStuckSize: metadata.AggregateStuckSize, 586 587 // Skynet Fields 588 AggregateSkynetFiles: metadata.AggregateSkynetFiles, 589 AggregateSkynetSize: metadata.AggregateSkynetSize, 590 591 // SiaDir Fields 592 Health: metadata.Health, 593 LastHealthCheckTime: metadata.LastHealthCheckTime, 594 MaxHealth: maxHealth, 595 MaxHealthPercentage: skymodules.HealthPercentage(maxHealth), 596 MinRedundancy: metadata.MinRedundancy, 597 DirMode: metadata.Mode, 598 MostRecentModTime: metadata.ModTime, 599 NumFiles: metadata.NumFiles, 600 NumLostFiles: metadata.NumLostFiles, 601 NumStuckChunks: metadata.NumStuckChunks, 602 NumSubDirs: metadata.NumSubDirs, 603 NumUnfinishedFiles: metadata.NumUnfinishedFiles, 604 RepairSize: metadata.RepairSize, 605 DirSize: metadata.Size, 606 StuckHealth: metadata.StuckHealth, 607 StuckSize: metadata.StuckSize, 608 SiaPath: siaPath, 609 UID: n.staticUID, 610 611 // Skynet Fields 612 SkynetFiles: metadata.SkynetFiles, 613 SkynetSize: metadata.SkynetSize, 614 }, nil 615 } 616 617 // childDirs is a convenience method to return the directories field of a DNode 618 // as a slice. 619 func (n *DirNode) childDirs() []*DirNode { 620 dirs := make([]*DirNode, 0, len(n.directories)) 621 for _, dir := range n.directories { 622 dirs = append(dirs, dir) 623 } 624 return dirs 625 } 626 627 // managedExists returns 'true' if a file or folder with the given name already 628 // exists within the dir. 629 func (n *DirNode) childExists(name string) bool { 630 // Check the ones in memory first. 631 if _, exists := n.files[name]; exists { 632 return true 633 } 634 if _, exists := n.directories[name]; exists { 635 return true 636 } 637 // Check that no dir or file exists on disk. 638 _, errFile := os.Stat(filepath.Join(n.absPath(), name)) 639 _, errDir := os.Stat(filepath.Join(n.absPath(), name+skymodules.SiaFileExtension)) 640 return !os.IsNotExist(errFile) || !os.IsNotExist(errDir) 641 } 642 643 // childFiles is a convenience method to return the files field of a DNode as a 644 // slice. 645 func (n *DirNode) childFiles() []*FileNode { 646 files := make([]*FileNode, 0, len(n.files)) 647 for _, file := range n.files { 648 files = append(files, file) 649 } 650 return files 651 } 652 653 // managedNewSiaFile creates a new SiaFile in the directory. 654 func (n *DirNode) managedNewSiaFile(fileName string, source string, ec skymodules.ErasureCoder, mk crypto.CipherKey, fileSize uint64, fileMode os.FileMode) error { 655 n.mu.Lock() 656 defer n.mu.Unlock() 657 // Make sure we don't have a file or folder with that name already. 658 if exists := n.childExists(fileName); exists { 659 return ErrExists 660 } 661 _, err := siafile.New(filepath.Join(n.absPath(), fileName+skymodules.SiaFileExtension), source, n.staticWal, ec, mk, fileSize, fileMode) 662 return errors.AddContext(err, "NewSiaFile: failed to create file") 663 } 664 665 // managedNewSiaDir creates the SiaDir with the given dirName as its child. We 666 // try to create the SiaDir if it exists in memory but not on disk, as it may 667 // have just been deleted. We also do not return an error if the SiaDir exists 668 // in memory and on disk already, which may be due to a race. 669 func (n *DirNode) managedNewSiaDir(dirName string, rootPath string, mode os.FileMode) error { 670 n.mu.Lock() 671 defer n.mu.Unlock() 672 // Check if a file already exists with that name. 673 if _, exists := n.files[dirName]; exists { 674 return ErrExists 675 } 676 // Check that no dir or file exists on disk. 677 _, err := os.Stat(filepath.Join(n.absPath(), dirName+skymodules.SiaFileExtension)) 678 if !os.IsNotExist(err) { 679 return ErrExists 680 } 681 _, err = siadir.New(filepath.Join(n.absPath(), dirName), rootPath, mode) 682 if errors.Contains(err, os.ErrExist) { 683 return nil 684 } 685 return err 686 } 687 688 // managedOpenFile opens a SiaFile and adds it and all of its parents to the 689 // filesystem tree. 690 func (n *DirNode) managedOpenFile(fileName string) (*FileNode, error) { 691 n.mu.Lock() 692 defer n.mu.Unlock() 693 return n.openFile(fileName) 694 } 695 696 // openFile is like readonlyOpenFile but adds the file to the parent. 697 func (n *DirNode) openFile(fileName string) (*FileNode, error) { 698 fn, err := n.readonlyOpenFile(fileName) 699 if err != nil { 700 return nil, err 701 } 702 n.files[fileName] = fn 703 return fn.managedCopy(), nil 704 } 705 706 // readonlyOpenFile opens a SiaFile but doesn't add it to the parent. That's 707 // useful for opening nodes which are short-lived and don't need to be closed. 708 func (n *DirNode) readonlyOpenFile(fileName string) (*FileNode, error) { 709 fn, exists := n.files[fileName] 710 if exists && fn.Deleted() { 711 return nil, ErrNotExist // don't return a deleted file 712 } 713 if exists { 714 return fn, nil 715 } 716 // Load file from disk. 717 filePath := filepath.Join(n.absPath(), fileName+skymodules.SiaFileExtension) 718 sf, err := siafile.LoadSiaFile(filePath, n.staticWal) 719 if errors.Contains(err, siafile.ErrUnknownPath) || os.IsNotExist(err) { 720 return nil, ErrNotExist 721 } 722 if err != nil { 723 return nil, errors.AddContext(err, fmt.Sprintf("failed to load SiaFile '%v' from disk", filePath)) 724 } 725 fn = &FileNode{ 726 node: newNode(n, filePath, fileName, 0, n.staticWal, n.staticLog), 727 SiaFile: sf, 728 } 729 // Clone the node, give it a new UID and return it. 730 return fn, nil 731 } 732 733 // openDir opens the dir with the specified name within the current dir. 734 func (n *DirNode) openDir(dirName string) (*DirNode, error) { 735 // Check if dir was already loaded. Then just copy it. 736 dir, exists := n.directories[dirName] 737 if exists { 738 return dir.managedCopy(), nil 739 } 740 // Load the dir. 741 dirPath := filepath.Join(n.absPath(), dirName) 742 _, err := os.Stat(dirPath) 743 if os.IsNotExist(err) { 744 return nil, ErrNotExist 745 } 746 if err != nil { 747 return nil, err 748 } 749 // Make sure the metadata exists too. 750 dirMDPath := filepath.Join(dirPath, skymodules.SiaDirExtension) 751 _, err = os.Stat(dirMDPath) 752 if os.IsNotExist(err) { 753 return nil, ErrNotExist 754 } 755 if err != nil { 756 return nil, err 757 } 758 // Add the dir to the opened dirs. 759 dir = &DirNode{ 760 node: newNode(n, dirPath, dirName, 0, n.staticWal, n.staticLog), 761 directories: make(map[string]*DirNode), 762 files: make(map[string]*FileNode), 763 lazySiaDir: new(*siadir.SiaDir), 764 } 765 n.directories[*dir.name] = dir 766 return dir.managedCopy(), nil 767 } 768 769 // copyDirNode copies the node, adds a new thread to the threads map and returns 770 // the new instance. 771 func (n *DirNode) copyDirNode() *DirNode { 772 // Copy the dNode and change the uid to a unique one. 773 newNode := *n 774 newNode.threadUID = newThreadUID() 775 newNode.threads[newNode.threadUID] = struct{}{} 776 return &newNode 777 } 778 779 // managedCopy copies the node, adds a new thread to the threads map and returns the 780 // new instance. 781 func (n *DirNode) managedCopy() *DirNode { 782 // Copy the dNode and change the uid to a unique one. 783 n.mu.Lock() 784 defer n.mu.Unlock() 785 return n.copyDirNode() 786 } 787 788 // managedOpenDir opens a SiaDir. 789 func (n *DirNode) managedOpenDir(path string) (_ *DirNode, err error) { 790 // Get the name of the next sub directory. 791 pathList := strings.Split(path, string(filepath.Separator)) 792 n.mu.Lock() 793 subNode, err := n.openDir(pathList[0]) 794 n.mu.Unlock() 795 if err != nil { 796 return nil, err 797 } 798 // If path is empty we are done. 799 pathList = pathList[1:] 800 if len(pathList) == 0 { 801 return subNode, nil 802 } 803 // Otherwise open the next dir. 804 defer func() { 805 err = errors.Compose(err, subNode.Close()) 806 }() 807 return subNode.managedOpenDir(filepath.Join(pathList...)) 808 } 809 810 // managedRemoveDir removes a dir from a dNode. 811 // NOTE: child.mu needs to be locked 812 func (n *DirNode) removeDir(child *DirNode) { 813 // Remove the child node. 814 currentChild, exists := n.directories[*child.name] 815 if !exists || child.staticUID != currentChild.staticUID { 816 return // nothing to do 817 } 818 delete(n.directories, *child.name) 819 } 820 821 // removeFile removes a child from a dNode. 822 // NOTE: child.mu needs to be locked 823 func (n *DirNode) removeFile(child *FileNode) { 824 // Remove the child node. 825 currentChild, exists := n.files[*child.name] 826 if !exists || child.SiaFile != currentChild.SiaFile { 827 return // Nothing to do 828 } 829 delete(n.files, *child.name) 830 } 831 832 // managedRename renames the fNode's underlying file. 833 func (n *DirNode) managedRename(newName string, oldParent, newParent *DirNode) error { 834 // Iteratively remove oldParent after Rename is done. 835 defer oldParent.managedTryRemoveFromParentsIteratively() 836 837 // Lock the parents. If they are the same, only lock one. 838 oldParent.mu.Lock() 839 defer oldParent.mu.Unlock() 840 if oldParent.staticUID != newParent.staticUID { 841 newParent.mu.Lock() 842 defer newParent.mu.Unlock() 843 } 844 // Check that newParent doesn't have a dir or file with that name already. 845 if exists := newParent.childExists(newName); exists { 846 return ErrExists 847 } 848 n.mu.Lock() 849 defer n.mu.Unlock() 850 dirsToLock := n.childDirs() 851 var dirsToRename []*DirNode 852 var filesToRename []*FileNode 853 var lockedNodes []*node 854 for _, file := range n.childFiles() { 855 file.node.mu.Lock() 856 file.SiaFile.Lock() 857 lockedNodes = append(lockedNodes, &file.node) 858 filesToRename = append(filesToRename, file) 859 } 860 // Unlock all locked nodes regardless of errors. 861 defer func() { 862 for _, file := range filesToRename { 863 file.Unlock() 864 } 865 for _, node := range lockedNodes { 866 node.mu.Unlock() 867 } 868 }() 869 // Lock dir and all open children. Remember in which order we acquired the 870 // locks. 871 for len(dirsToLock) > 0 { 872 // Get next dir. 873 d := dirsToLock[0] 874 dirsToLock = dirsToLock[1:] 875 // Lock the dir. 876 d.mu.Lock() 877 lockedNodes = append(lockedNodes, &d.node) 878 dirsToRename = append(dirsToRename, d) 879 // Lock the open files. 880 for _, file := range d.files { 881 file.node.mu.Lock() 882 file.SiaFile.Lock() 883 lockedNodes = append(lockedNodes, &file.node) 884 filesToRename = append(filesToRename, file) 885 } 886 // Add the open dirs to dirsToLock. 887 dirsToLock = append(dirsToLock, d.childDirs()...) 888 } 889 newBase := filepath.Join(newParent.absPath(), newName) 890 // Rename the dir. 891 dir, err := n.siaDir() 892 if err != nil { 893 return err 894 } 895 err = dir.Rename(newBase) 896 if os.IsExist(err) { 897 return ErrExists 898 } 899 if err != nil { 900 return err 901 } 902 // Remove dir from old parent and add it to new parent. 903 oldParent.removeDir(n) 904 // Update parent and name. 905 n.parent = newParent 906 *n.name = newName 907 *n.path = newBase 908 // Add dir to new parent. 909 n.parent.directories[*n.name] = n 910 // Rename all locked nodes in memory. 911 for _, node := range lockedNodes { 912 *node.path = filepath.Join(*node.parent.path, *node.name) 913 } 914 // Rename all files in memory. 915 for _, file := range filesToRename { 916 *file.path = *file.path + skymodules.SiaFileExtension 917 file.UnmanagedSetSiaFilePath(*file.path) 918 } 919 // Rename all dirs in memory. 920 for _, dir := range dirsToRename { 921 if *dir.lazySiaDir == nil { 922 continue // dir isn't loaded 923 } 924 err = (*dir.lazySiaDir).SetPath(*dir.path) 925 if err != nil { 926 return errors.AddContext(err, fmt.Sprintf("unable to set path for %v", *dir.path)) 927 } 928 } 929 return err 930 }