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  }