gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/renter/siafile/siafileset.go (about)

     1  package siafile
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"math"
     7  	"os"
     8  	"path/filepath"
     9  	"runtime"
    10  	"strings"
    11  	"sync"
    12  	"time"
    13  
    14  	"github.com/karrick/godirwalk"
    15  	"gitlab.com/NebulousLabs/errors"
    16  	"gitlab.com/NebulousLabs/fastrand"
    17  	"gitlab.com/SiaPrime/writeaheadlog"
    18  
    19  	"gitlab.com/SiaPrime/SiaPrime/build"
    20  	"gitlab.com/SiaPrime/SiaPrime/crypto"
    21  	"gitlab.com/SiaPrime/SiaPrime/modules"
    22  	"gitlab.com/SiaPrime/SiaPrime/modules/renter/siadir"
    23  )
    24  
    25  // The SiaFileSet structure helps track the number of threads using a siafile
    26  // and will enable features like caching and lazy loading to improve start up
    27  // times and reduce memory usage. SiaFile methods such as New, Delete, Rename,
    28  // etc should be called through the SiaFileSet to maintain atomic transactions.
    29  type (
    30  	// SiaFileSet is a helper struct responsible for managing the renter's
    31  	// siafiles in memory
    32  	SiaFileSet struct {
    33  		staticSiaFileDir string
    34  		siaFileMap       map[SiafileUID]*siaFileSetEntry
    35  		siapathToUID     map[modules.SiaPath]SiafileUID
    36  
    37  		// utilities
    38  		mu  sync.Mutex
    39  		wal *writeaheadlog.WAL
    40  	}
    41  
    42  	// siaFileSetEntry contains information about the threads accessing the
    43  	// SiaFile and references to the SiaFile and the SiaFileSet
    44  	siaFileSetEntry struct {
    45  		*SiaFile
    46  		staticSiaFileSet *SiaFileSet
    47  
    48  		threadMap   map[uint64]threadInfo
    49  		threadMapMu sync.Mutex
    50  	}
    51  
    52  	// SiaFileSetEntry is the exported struct that is returned to the thread
    53  	// accessing the SiaFile and the Entry
    54  	SiaFileSetEntry struct {
    55  		*siaFileSetEntry
    56  		threadUID uint64
    57  	}
    58  
    59  	// threadInfo contains useful information about the thread accessing the
    60  	// SiaFileSetEntry
    61  	threadInfo struct {
    62  		callingFiles []string
    63  		callingLines []int
    64  		lockTime     time.Time
    65  	}
    66  )
    67  
    68  // NewSiaFileSet initializes and returns a SiaFileSet
    69  func NewSiaFileSet(filesDir string, wal *writeaheadlog.WAL) *SiaFileSet {
    70  	return &SiaFileSet{
    71  		staticSiaFileDir: filesDir,
    72  		siaFileMap:       make(map[SiafileUID]*siaFileSetEntry),
    73  		siapathToUID:     make(map[modules.SiaPath]SiafileUID),
    74  		wal:              wal,
    75  	}
    76  }
    77  
    78  // newThreadInfo created a threadInfo entry for the threadMap
    79  func newThreadInfo() threadInfo {
    80  	tt := threadInfo{
    81  		callingFiles: make([]string, threadDepth+1),
    82  		callingLines: make([]int, threadDepth+1),
    83  		lockTime:     time.Now(),
    84  	}
    85  	for i := 0; i <= threadDepth; i++ {
    86  		_, tt.callingFiles[i], tt.callingLines[i], _ = runtime.Caller(2 + i)
    87  	}
    88  	return tt
    89  }
    90  
    91  // randomThreadUID returns a random uint64 to be used as the thread UID in the
    92  // threadMap of the SiaFileSetEntry
    93  func randomThreadUID() uint64 {
    94  	return fastrand.Uint64n(math.MaxUint64)
    95  }
    96  
    97  // CopyEntry returns a copy of the SiaFileSetEntry
    98  func (entry *SiaFileSetEntry) CopyEntry() (*SiaFileSetEntry, error) {
    99  	// Grab siafile set lock
   100  	entry.staticSiaFileSet.mu.Lock()
   101  	defer entry.staticSiaFileSet.mu.Unlock()
   102  
   103  	// Check if entry is deleted, we will not make a copy of a deleted file.
   104  	if entry.Deleted() {
   105  		return nil, errors.New("can't make copy of deleted siafile entry")
   106  	}
   107  
   108  	// Sanity Check that the entry is currently in the SiaFileSet
   109  	_, exists := entry.staticSiaFileSet.siaFileMap[entry.UID()]
   110  	if !exists {
   111  		// If the file is deleted, it should be marked as delted. If the file is
   112  		// renamed, it should still have the same UID. The entry is coming from
   113  		// an existing entry, so the entry should still be in memory because not
   114  		// all of the entries have been closed. There shouldn't be a case where
   115  		// the entry does not exist in the siafile set for this code if it's not
   116  		// deleted.
   117  		build.Critical("provided entry does not exist in the SiaFileSet, but has not been marked as deleted")
   118  		return nil, errors.New("siafile entry not found in siafileset")
   119  	}
   120  
   121  	// Create the copy of the entry. Both the original entry and the copied
   122  	// entry will need to be closed.
   123  	entry.threadMapMu.Lock()
   124  	defer entry.threadMapMu.Unlock()
   125  	threadUID := randomThreadUID()
   126  	entry.threadMap[threadUID] = newThreadInfo()
   127  	entryCopy := &SiaFileSetEntry{
   128  		siaFileSetEntry: entry.siaFileSetEntry,
   129  		threadUID:       threadUID,
   130  	}
   131  	return entryCopy, nil
   132  }
   133  
   134  // Close will close the set entry, removing the entry from memory if there are
   135  // no other entries using the siafile.
   136  //
   137  // Note that 'Close' grabs a lock on the SiaFileSet, do not call this function
   138  // while holding a lock on the SiafileSet - standard concurrency conventions
   139  // though dictate that you should not be calling exported / capitalized
   140  // functions while holding a lock anyway, but this function is particularly
   141  // sensitive to that.
   142  func (entry *SiaFileSetEntry) Close() error {
   143  	entry.staticSiaFileSet.mu.Lock()
   144  	entry.staticSiaFileSet.closeEntry(entry)
   145  	entry.staticSiaFileSet.mu.Unlock()
   146  	return nil
   147  }
   148  
   149  // FileSet returns the SiaFileSet of the entry.
   150  func (entry *SiaFileSetEntry) FileSet() *SiaFileSet {
   151  	return entry.staticSiaFileSet
   152  }
   153  
   154  // SiaPath returns the siapath of a siafile.
   155  func (sfs *SiaFileSet) SiaPath(entry *SiaFileSetEntry) modules.SiaPath {
   156  	sfs.mu.Lock()
   157  	defer sfs.mu.Unlock()
   158  	return sfs.siaPath(entry.siaFileSetEntry)
   159  }
   160  
   161  // closeEntry will close an entry in the SiaFileSet, removing the siafile from
   162  // the cache if no other entries are open for that siafile.
   163  //
   164  // Note that this function needs to be called while holding a lock on the
   165  // SiaFileSet, per standard concurrency conventions. This function also goes and
   166  // grabs a lock on the entry that it is being passed, which means that the lock
   167  // cannot be held while calling 'closeEntry'.
   168  //
   169  // The memory model we have has the SiaFileSet as the superior object, so per
   170  // convention methods on the SiaFileSet should not be getting held while entry
   171  // locks are being held, but this function is particularly dependent on that
   172  // convention.
   173  func (sfs *SiaFileSet) closeEntry(entry *SiaFileSetEntry) {
   174  	// Lock the thread map mu and remove the threadUID from the entry.
   175  	entry.threadMapMu.Lock()
   176  	defer entry.threadMapMu.Unlock()
   177  
   178  	if _, exists := entry.threadMap[entry.threadUID]; !exists {
   179  		build.Critical("threaduid doesn't exist in threadMap: ", entry.SiaFilePath(), len(entry.threadMap))
   180  	}
   181  	delete(entry.threadMap, entry.threadUID)
   182  
   183  	// The entry that exists in the siafile set may not be the same as the entry
   184  	// that is being closed, this can happen if there was a rename or a delete
   185  	// and then a new/different file was uploaded with the same siapath.
   186  	//
   187  	// If they are not the same entry, there is nothing more to do.
   188  	currentEntry := sfs.siaFileMap[entry.UID()]
   189  	if currentEntry != entry.siaFileSetEntry {
   190  		return
   191  	}
   192  
   193  	// If there are no more threads that have the current entry open, delete this
   194  	// entry from the set cache and save the file to make sure all changes are
   195  	// persisted.
   196  	if len(currentEntry.threadMap) == 0 {
   197  		delete(sfs.siaFileMap, entry.UID())
   198  		delete(sfs.siapathToUID, sfs.siaPath(entry.siaFileSetEntry))
   199  		// If the entry had a partialSiaFile, close that as well.
   200  		if entry.partialsSiaFile != nil {
   201  			sfs.closeEntry(entry.partialsSiaFile)
   202  		}
   203  	}
   204  }
   205  
   206  // createAndApplyTransaction is a helper method that creates a writeaheadlog
   207  // transaction and applies it.
   208  func (sfs *SiaFileSet) createAndApplyTransaction(updates ...writeaheadlog.Update) error {
   209  	if len(updates) == 0 {
   210  		return nil
   211  	}
   212  	// Create the writeaheadlog transaction.
   213  	txn, err := sfs.wal.NewTransaction(updates)
   214  	if err != nil {
   215  		return errors.AddContext(err, "failed to create wal txn")
   216  	}
   217  	// No extra setup is required. Signal that it is done.
   218  	if err := <-txn.SignalSetupComplete(); err != nil {
   219  		return errors.AddContext(err, "failed to signal setup completion")
   220  	}
   221  	// Apply the updates.
   222  	if err := applyUpdates(modules.ProdDependencies, updates...); err != nil {
   223  		return errors.AddContext(err, "failed to apply updates")
   224  	}
   225  	// Updates are applied. Let the writeaheadlog know.
   226  	if err := txn.SignalUpdatesApplied(); err != nil {
   227  		return errors.AddContext(err, "failed to signal that updates are applied")
   228  	}
   229  	return nil
   230  }
   231  
   232  // delete deletes the SiaFileSetEntry's SiaFile
   233  func (sfs *SiaFileSet) deleteFile(siaPath modules.SiaPath) error {
   234  	// Fetch the corresponding siafile and call delete.
   235  	//
   236  	// NOTE: since we are just accessing the entry directly from the map while
   237  	// holding the SiaFileSet lock we are not creating a new threadUID and
   238  	// therefore do not need to call close on this entry
   239  	entry, _, exists := sfs.siaPathToEntryAndUID(siaPath)
   240  	if !exists {
   241  		// Check if the file exists on disk
   242  		siaFilePath := siaPath.SiaFileSysPath(sfs.staticSiaFileDir)
   243  		_, err := os.Stat(siaFilePath)
   244  		if os.IsNotExist(err) {
   245  			return ErrUnknownPath
   246  		}
   247  		// If the entry does not exist then we want to just remove the entry from disk
   248  		// without loading it from disk to avoid errors due to corrupt siafiles
   249  		update := createDeleteUpdate(siaFilePath)
   250  		return sfs.createAndApplyTransaction(update)
   251  	}
   252  
   253  	// Delete SiaFile
   254  	err := entry.Delete()
   255  	if err != nil {
   256  		return err
   257  	}
   258  	// Remove the siafile from the set maps so that other threads can't find
   259  	// it.
   260  	delete(sfs.siaFileMap, entry.UID())
   261  	delete(sfs.siapathToUID, sfs.siaPath(entry))
   262  	return nil
   263  }
   264  
   265  // siaPath is a convenience wrapper around FromSysPath. Since the files are
   266  // loaded from disk, the siapaths should always be correct. It's argument is
   267  // also an entry instead of the path and dir.
   268  func (sfs *SiaFileSet) siaPath(entry *siaFileSetEntry) (sp modules.SiaPath) {
   269  	if err := sp.FromSysPath(entry.SiaFilePath(), sfs.staticSiaFileDir); err != nil {
   270  		build.Critical("Siapath of entry is corrupted. This shouldn't happen within the SiaFileSet", err)
   271  	}
   272  	return
   273  }
   274  
   275  // siaPathToEntryAndUID translates a siaPath to a siaFileSetEntry and
   276  // SiafileUID while also sanity checking siapathToUID and siaFileMap for
   277  // consistency.
   278  func (sfs *SiaFileSet) siaPathToEntryAndUID(siaPath modules.SiaPath) (*siaFileSetEntry, SiafileUID, bool) {
   279  	uid, exists := sfs.siapathToUID[siaPath]
   280  	if !exists {
   281  		return nil, "", false
   282  	}
   283  	entry, exists2 := sfs.siaFileMap[uid]
   284  	if !exists2 {
   285  		build.Critical("siapathToUID and siaFileMap are inconsistent")
   286  		delete(sfs.siapathToUID, siaPath)
   287  		return nil, "", false
   288  	}
   289  	return entry, uid, exists
   290  }
   291  
   292  // exists checks to see if a file with the provided siaPath already exists in
   293  // the renter
   294  func (sfs *SiaFileSet) exists(siaPath modules.SiaPath) bool {
   295  	// Check for file in Memory
   296  	_, _, exists := sfs.siaPathToEntryAndUID(siaPath)
   297  	if exists {
   298  		return true
   299  	}
   300  	// Check for file on disk
   301  	_, err := os.Stat(siaPath.SiaFileSysPath(sfs.staticSiaFileDir))
   302  	return !os.IsNotExist(err)
   303  }
   304  
   305  // readLockFileInfo returns information on a siafile. As a performance
   306  // optimization, the fileInfo takes the maps returned by
   307  // renter.managedContractUtilityMaps for many files at once.
   308  func (sfs *SiaFileSet) readLockCachedFileInfo(siaPath modules.SiaPath, offline map[string]bool, goodForRenew map[string]bool, contracts map[string]modules.RenterContract) (modules.FileInfo, error) {
   309  	// Get the file's metadata and its contracts
   310  	md, err := sfs.readLockMetadata(siaPath)
   311  	if err != nil {
   312  		return modules.FileInfo{}, err
   313  	}
   314  
   315  	// Build the FileInfo
   316  	var onDisk bool
   317  	localPath := md.LocalPath
   318  	if localPath != "" {
   319  		_, err = os.Stat(localPath)
   320  		onDisk = err == nil
   321  	}
   322  	maxHealth := math.Max(md.CachedHealth, md.CachedStuckHealth)
   323  	fileInfo := modules.FileInfo{
   324  		AccessTime:       md.AccessTime,
   325  		Available:        md.CachedUserRedundancy >= 1,
   326  		ChangeTime:       md.ChangeTime,
   327  		CipherType:       md.StaticMasterKeyType.String(),
   328  		CreateTime:       md.CreateTime,
   329  		Expiration:       md.CachedExpiration,
   330  		Filesize:         uint64(md.FileSize),
   331  		Health:           md.CachedHealth,
   332  		LocalPath:        localPath,
   333  		MaxHealth:        maxHealth,
   334  		MaxHealthPercent: siadir.HealthPercentage(maxHealth),
   335  		ModTime:          md.ModTime,
   336  		NumStuckChunks:   md.NumStuckChunks,
   337  		OnDisk:           onDisk,
   338  		Recoverable:      onDisk || md.CachedUserRedundancy >= 1,
   339  		Redundancy:       md.CachedUserRedundancy,
   340  		Renewing:         true,
   341  		SiaPath:          siaPath,
   342  		Stuck:            md.NumStuckChunks > 0,
   343  		StuckHealth:      md.CachedStuckHealth,
   344  		UploadedBytes:    md.CachedUploadedBytes,
   345  		UploadProgress:   md.CachedUploadProgress,
   346  	}
   347  	return fileInfo, nil
   348  }
   349  
   350  // newSiaFileSetEntry initializes and returns a siaFileSetEntry
   351  func (sfs *SiaFileSet) newSiaFileSetEntry(sf *SiaFile) (*siaFileSetEntry, error) {
   352  	threads := make(map[uint64]threadInfo)
   353  	entry := &siaFileSetEntry{
   354  		SiaFile:          sf,
   355  		staticSiaFileSet: sfs,
   356  		threadMap:        threads,
   357  	}
   358  	// Sanity check that the UID is in fact unique.
   359  	if _, exists := sfs.siaFileMap[entry.UID()]; exists {
   360  		err := fmt.Errorf("siafile '%v' with uid '%v' was already loaded", sfs.siaPath(entry), entry.UID())
   361  		build.Critical(err)
   362  		return nil, err
   363  	}
   364  	// Sanity check that there isn't a naming conflict
   365  	if _, exists := sfs.siapathToUID[sfs.siaPath(entry)]; exists {
   366  		err := errors.New("siapath already in map")
   367  		build.Critical(err)
   368  		return nil, err
   369  	}
   370  	// Add entry to siaFileMap and siapathToUID map.
   371  	sfs.siaFileMap[entry.UID()] = entry
   372  	sfs.siapathToUID[sfs.siaPath(entry)] = entry.UID()
   373  	return entry, nil
   374  }
   375  
   376  // open will return the siaFileSetEntry in memory or load it from disk
   377  func (sfs *SiaFileSet) open(siaPath modules.SiaPath) (*SiaFileSetEntry, error) {
   378  	if strings.HasPrefix(siaPath.String(), ".") {
   379  		err := errors.New("never open a hidden siafile with 'open'")
   380  		build.Critical(err)
   381  		return nil, err
   382  	}
   383  	var entry *siaFileSetEntry
   384  	var exists bool
   385  	entry, _, exists = sfs.siaPathToEntryAndUID(siaPath)
   386  	if !exists {
   387  		// Try and Load File from disk
   388  		sf, err := LoadSiaFile(siaPath.SiaFileSysPath(sfs.staticSiaFileDir), sfs.wal)
   389  		if os.IsNotExist(err) {
   390  			return nil, ErrUnknownPath
   391  		}
   392  		if err != nil {
   393  			return nil, err
   394  		}
   395  		// Check for duplicate uid.
   396  		if conflictingEntry, exists := sfs.siaFileMap[sf.UID()]; exists {
   397  			err := fmt.Errorf("%v and %v share the same UID '%v'", sfs.siaPath(conflictingEntry), siaPath, sf.UID())
   398  			build.Critical(err)
   399  			return nil, err
   400  		}
   401  		// Create the entry for the SiaFile and assign the partials file.
   402  		entry, err = sfs.newSiaFileSetEntry(sf)
   403  		if err != nil {
   404  			return nil, err
   405  		}
   406  	}
   407  	if entry.Deleted() {
   408  		return nil, ErrUnknownPath
   409  	}
   410  	// Load the corresponding partialsSiaFile.
   411  	partialsSiaFile, err := sfs.openPartialsSiaFile(entry.ErasureCode(), true)
   412  	if err != nil {
   413  		return nil, err
   414  	}
   415  	entry.SetPartialsSiaFile(partialsSiaFile)
   416  
   417  	threadUID := randomThreadUID()
   418  	entry.threadMapMu.Lock()
   419  	defer entry.threadMapMu.Unlock()
   420  	entry.threadMap[threadUID] = newThreadInfo()
   421  	return &SiaFileSetEntry{
   422  		siaFileSetEntry: entry,
   423  		threadUID:       threadUID,
   424  	}, nil
   425  }
   426  
   427  // AddExistingPartialsSiaFile adds an existing partial SiaFile to the set and
   428  // stores it on disk. If the file already exists this will produce an error
   429  // since we can't just add a suffix to it.
   430  func (sfs *SiaFileSet) AddExistingPartialsSiaFile(sf *SiaFile, chunks []chunk) (map[uint64]uint64, error) {
   431  	sfs.mu.Lock()
   432  	defer sfs.mu.Unlock()
   433  	return sfs.addExistingPartialsSiaFile(sf, chunks)
   434  }
   435  
   436  // readLockMetadata returns the metadata of the SiaFile at siaPath. NOTE: The
   437  // 'readLock' prefix in this case is used to indicate that it's safe to call
   438  // this method with other 'readLock' methods without locking since is doesn't
   439  // write to any fields. This guarantee can be made by locking sfs.mu and then
   440  // spawning multiple threads which call 'readLock' methods in parallel.
   441  func (sfs *SiaFileSet) readLockMetadata(siaPath modules.SiaPath) (Metadata, error) {
   442  	var entry *siaFileSetEntry
   443  	entry, _, exists := sfs.siaPathToEntryAndUID(siaPath)
   444  	if exists {
   445  		// Get metadata from entry.
   446  		return entry.Metadata(), nil
   447  	}
   448  	// Try and Load Metadata from disk
   449  	md, err := LoadSiaFileMetadata(siaPath.SiaFileSysPath(sfs.staticSiaFileDir))
   450  	if os.IsNotExist(err) {
   451  		return Metadata{}, ErrUnknownPath
   452  	}
   453  	if err != nil {
   454  		return Metadata{}, err
   455  	}
   456  	return md, nil
   457  }
   458  
   459  // AddExistingSiaFile adds an existing SiaFile to the set and stores it on disk.
   460  // If the exact same file already exists, this is a no-op. If a file already
   461  // exists with a different UID, the UID will be updated and a unique path will
   462  // be chosen. If no file exists, the UID will be updated but the path remains
   463  // the same.
   464  func (sfs *SiaFileSet) AddExistingSiaFile(sf *SiaFile, chunks []chunk) error {
   465  	sfs.mu.Lock()
   466  	defer sfs.mu.Unlock()
   467  	return sfs.addExistingSiaFile(sf, chunks, 0)
   468  }
   469  
   470  // addExistingPartialsSiaFile adds an existing partial SiaFile to the set and
   471  // stores it on disk. If the file already exists this will produce an error
   472  // since we can't just add a suffix to it.
   473  func (sfs *SiaFileSet) addExistingPartialsSiaFile(sf *SiaFile, chunks []chunk) (map[uint64]uint64, error) {
   474  	// Check if a file with that path exists already.
   475  	var siaPath modules.SiaPath
   476  	err := siaPath.LoadSysPath(sfs.staticSiaFileDir, sf.SiaFilePath())
   477  	if err != nil {
   478  		return nil, err
   479  	}
   480  	oldFile, err := sfs.openPartialsSiaFile(sf.ErasureCode(), false)
   481  	if err == nil {
   482  		defer sfs.closeEntry(oldFile)
   483  		return oldFile.Merge(sf)
   484  	} else if os.IsNotExist(err) {
   485  		return nil, sf.SaveWithChunks(chunks)
   486  	}
   487  	return nil, err
   488  }
   489  
   490  // addExistingSiaFile adds an existing SiaFile to the set and stores it on disk.
   491  // If the exact same file already exists, this is a no-op. If a file already
   492  // exists with a different UID, the UID will be updated and a unique path will
   493  // be chosen. If no file exists, the UID will be updated but the path remains
   494  // the same.
   495  func (sfs *SiaFileSet) addExistingSiaFile(sf *SiaFile, chunks []chunk, suffix uint) error {
   496  	// Check if a file with that path exists already.
   497  	var siaPath modules.SiaPath
   498  	err := siaPath.LoadSysPath(sfs.staticSiaFileDir, sf.SiaFilePath())
   499  	if err != nil {
   500  		return err
   501  	}
   502  	var oldFile *SiaFileSetEntry
   503  	if suffix == 0 {
   504  		oldFile, err = sfs.open(siaPath)
   505  	} else {
   506  		oldFile, err = sfs.open(siaPath.AddSuffix(suffix))
   507  	}
   508  	exists := err == nil
   509  	if exists {
   510  		defer sfs.closeEntry(oldFile)
   511  	}
   512  	if err != nil && err != ErrUnknownPath {
   513  		return err
   514  	}
   515  	// If it doesn't exist, update the UID, the path if necessary and save the
   516  	// file.
   517  	if !exists {
   518  		sf.UpdateUniqueID()
   519  		if suffix > 0 {
   520  			siaFilePath := siaPath.AddSuffix(suffix).SiaFileSysPath(sfs.staticSiaFileDir)
   521  			sf.SetSiaFilePath(siaFilePath)
   522  		}
   523  		// Set the correct partials SiaFile since the file we are adding wasn't part
   524  		// of a SiaFileSet before and therefore doesn't have one yet.
   525  		ec := sf.ErasureCode()
   526  		psf, err := sfs.openPartialsSiaFile(ec, true)
   527  		if err != nil {
   528  			return err
   529  		}
   530  		sf.SetPartialsSiaFile(psf)
   531  		err = sf.SaveWithChunks(chunks)
   532  		if err != nil {
   533  			sfs.closeEntry(psf)
   534  		}
   535  		return err
   536  	}
   537  	// If it exists and the UID matches too, skip the file.
   538  	if sf.UID() == oldFile.UID() {
   539  		return nil
   540  	}
   541  	// If it exists and the UIDs don't match, increment the suffix and try again.
   542  	return sfs.addExistingSiaFile(sf, chunks, suffix+1)
   543  }
   544  
   545  // Delete deletes the SiaFileSetEntry's SiaFile
   546  func (sfs *SiaFileSet) Delete(siaPath modules.SiaPath) error {
   547  	sfs.mu.Lock()
   548  	defer sfs.mu.Unlock()
   549  	return sfs.deleteFile(siaPath)
   550  }
   551  
   552  // Exists checks to see if a file with the provided siaPath already exists in
   553  // the renter
   554  func (sfs *SiaFileSet) Exists(siaPath modules.SiaPath) bool {
   555  	sfs.mu.Lock()
   556  	defer sfs.mu.Unlock()
   557  	return sfs.exists(siaPath)
   558  }
   559  
   560  // FileInfo returns information on a siafile. As a performance optimization, the
   561  // fileInfo takes the maps returned by renter.managedContractUtilityMaps for
   562  // many files at once.
   563  func (sfs *SiaFileSet) FileInfo(siaPath modules.SiaPath, offline map[string]bool, goodForRenew map[string]bool, contracts map[string]modules.RenterContract) (modules.FileInfo, error) {
   564  	entry, err := sfs.Open(siaPath)
   565  	if err != nil {
   566  		return modules.FileInfo{}, err
   567  	}
   568  	defer entry.Close()
   569  
   570  	// Build the FileInfo
   571  	var onDisk bool
   572  	localPath := entry.LocalPath()
   573  	if localPath != "" {
   574  		_, err = os.Stat(localPath)
   575  		onDisk = err == nil
   576  	}
   577  	_, _, health, stuckHealth, numStuckChunks := entry.Health(offline, goodForRenew)
   578  	_, redundancy, err := entry.Redundancy(offline, goodForRenew)
   579  	if err != nil {
   580  		return modules.FileInfo{}, errors.AddContext(err, "failed to get file redundancy")
   581  	}
   582  	uploadProgress, uploadedBytes, err := entry.UploadProgressAndBytes()
   583  	if err != nil {
   584  		return modules.FileInfo{}, errors.AddContext(err, "failed to get upload progress and bytes")
   585  	}
   586  	maxHealth := math.Max(health, stuckHealth)
   587  	fileInfo := modules.FileInfo{
   588  		AccessTime:       entry.AccessTime(),
   589  		Available:        redundancy >= 1,
   590  		ChangeTime:       entry.ChangeTime(),
   591  		CipherType:       entry.MasterKey().Type().String(),
   592  		CreateTime:       entry.CreateTime(),
   593  		Expiration:       entry.Expiration(contracts),
   594  		Filesize:         entry.Size(),
   595  		Health:           health,
   596  		LocalPath:        localPath,
   597  		MaxHealth:        maxHealth,
   598  		MaxHealthPercent: siadir.HealthPercentage(maxHealth),
   599  		ModTime:          entry.ModTime(),
   600  		NumStuckChunks:   numStuckChunks,
   601  		OnDisk:           onDisk,
   602  		Recoverable:      onDisk || redundancy >= 1,
   603  		Redundancy:       redundancy,
   604  		Renewing:         true,
   605  		SiaPath:          siaPath,
   606  		Stuck:            numStuckChunks > 0,
   607  		StuckHealth:      stuckHealth,
   608  		UploadedBytes:    uploadedBytes,
   609  		UploadProgress:   uploadProgress,
   610  	}
   611  	return fileInfo, nil
   612  }
   613  
   614  // CachedFileInfo returns a modules.FileInfo for a given file like FileInfo but
   615  // instead of computing redundancy, health etc. it uses cached values.
   616  func (sfs *SiaFileSet) CachedFileInfo(siaPath modules.SiaPath, offline map[string]bool, goodForRenew map[string]bool, contracts map[string]modules.RenterContract) (modules.FileInfo, error) {
   617  	sfs.mu.Lock()
   618  	defer sfs.mu.Unlock()
   619  	return sfs.readLockCachedFileInfo(siaPath, offline, goodForRenew, contracts)
   620  }
   621  
   622  // FileList returns all of the files that the renter has in the folder specified
   623  // by siaPath. If cached is true, this method will used cached values for
   624  // health, redundancy etc.
   625  func (sfs *SiaFileSet) FileList(siaPath modules.SiaPath, recursive, cached bool, offlineMap map[string]bool, goodForRenewMap map[string]bool, contractsMap map[string]modules.RenterContract) ([]modules.FileInfo, error) {
   626  	// Guarantee that no other thread is writing to sfs. This is only necessary
   627  	// when 'cached' is true since it allows us to call 'readLockCachedFileInfo'.
   628  	// Otherwise we need to hold the lock for every call to 'fileInfo' anyway.
   629  	if cached {
   630  		sfs.mu.Lock()
   631  		defer sfs.mu.Unlock()
   632  	}
   633  	// Declare a worker method to spawn workers which keep loading files from disk
   634  	// until the loadChan is closed.
   635  	fileList := []modules.FileInfo{}
   636  	var fileListMu sync.Mutex
   637  	loadChan := make(chan string)
   638  	worker := func() {
   639  		for path := range loadChan {
   640  			// Load the Siafile.
   641  			var siaPath modules.SiaPath
   642  			if err := siaPath.LoadSysPath(sfs.staticSiaFileDir, path); err != nil {
   643  				continue
   644  			}
   645  			var file modules.FileInfo
   646  			var err error
   647  			if cached {
   648  				file, err = sfs.readLockCachedFileInfo(siaPath, offlineMap, goodForRenewMap, contractsMap)
   649  			} else {
   650  				// It is ok to call an Exported method here because we only
   651  				// acquire the siaFileSet lock if we are requesting the cached
   652  				// values
   653  				file, err = sfs.FileInfo(siaPath, offlineMap, goodForRenewMap, contractsMap)
   654  			}
   655  			if os.IsNotExist(err) || err == ErrUnknownPath {
   656  				continue
   657  			}
   658  			if err != nil {
   659  				continue
   660  			}
   661  			fileListMu.Lock()
   662  			fileList = append(fileList, file)
   663  			fileListMu.Unlock()
   664  		}
   665  	}
   666  	// spin up some threads
   667  	var wg sync.WaitGroup
   668  	for i := 0; i < fileListRoutines; i++ {
   669  		wg.Add(1)
   670  		go func() {
   671  			worker()
   672  			wg.Done()
   673  		}()
   674  	}
   675  	// Walk over the whole tree if recursive is specified.
   676  	folder := siaPath.SiaDirSysPath(sfs.staticSiaFileDir)
   677  	if recursive {
   678  		err := godirwalk.Walk(folder, &godirwalk.Options{
   679  			Unsorted: true,
   680  			Callback: func(path string, info *godirwalk.Dirent) error {
   681  				// Skip folders and non-sia files.
   682  				if info.IsDir() || filepath.Ext(path) != modules.SiaFileExtension {
   683  					return nil
   684  				}
   685  				loadChan <- path
   686  				return nil
   687  			},
   688  		})
   689  		if err != nil {
   690  			return nil, err
   691  		}
   692  	} else {
   693  		fis, err := ioutil.ReadDir(folder)
   694  		if err != nil {
   695  			return nil, err
   696  		}
   697  		for _, info := range fis {
   698  			if info.IsDir() || filepath.Ext(info.Name()) != modules.SiaFileExtension {
   699  				continue
   700  			}
   701  			loadChan <- filepath.Join(folder, info.Name())
   702  		}
   703  	}
   704  	close(loadChan)
   705  	wg.Wait()
   706  	return fileList, nil
   707  }
   708  
   709  // newSiaFile create a new SiaFile, adds it to the SiaFileSet, adds the thread
   710  // to the threadMap, and returns the SiaFileSetEntry. Since this method returns
   711  // the SiaFileSetEntry, wherever NewSiaFile is called there should be a Close
   712  // called on the SiaFileSetEntry to avoid the file being stuck in memory due the
   713  // thread never being removed from the threadMap
   714  func (sfs *SiaFileSet) newSiaFile(up modules.FileUploadParams, masterKey crypto.CipherKey, fileSize uint64, fileMode os.FileMode) (*SiaFileSetEntry, error) {
   715  	// Check is SiaFile already exists
   716  	exists := sfs.exists(up.SiaPath)
   717  	if exists && !up.Force {
   718  		return nil, ErrPathOverload
   719  	}
   720  	// Open the corresponding partials file.
   721  	partialsSiaFile, err := sfs.openPartialsSiaFile(up.ErasureCode, true)
   722  	if err != nil {
   723  		return nil, err
   724  	}
   725  	// Make sure there are no leading slashes
   726  	siaFilePath := up.SiaPath.SiaFileSysPath(sfs.staticSiaFileDir)
   727  	sf, err := New(siaFilePath, up.Source, sfs.wal, up.ErasureCode, masterKey, fileSize, fileMode, partialsSiaFile, true)
   728  	if err != nil {
   729  		return nil, err
   730  	}
   731  	entry, err := sfs.newSiaFileSetEntry(sf)
   732  	if err != nil {
   733  		return nil, err
   734  	}
   735  	threadUID := randomThreadUID()
   736  	entry.threadMap[threadUID] = newThreadInfo()
   737  	return &SiaFileSetEntry{
   738  		siaFileSetEntry: entry,
   739  		threadUID:       threadUID,
   740  	}, nil
   741  }
   742  
   743  // NewSiaFile create a new SiaFile, adds it to the SiaFileSet, adds the thread
   744  // to the threadMap, and returns the SiaFileSetEntry. Since this method returns
   745  // the SiaFileSetEntry, wherever NewSiaFile is called there should be a Close
   746  // called on the SiaFileSetEntry to avoid the file being stuck in memory due the
   747  // thread never being removed from the threadMap
   748  func (sfs *SiaFileSet) NewSiaFile(up modules.FileUploadParams, masterKey crypto.CipherKey, fileSize uint64, fileMode os.FileMode) (*SiaFileSetEntry, error) {
   749  	sfs.mu.Lock()
   750  	defer sfs.mu.Unlock()
   751  	return sfs.newSiaFile(up, masterKey, fileSize, fileMode)
   752  }
   753  
   754  // Open returns the siafile from the SiaFileSet for the corresponding key and
   755  // adds the thread to the entry's threadMap. If the siafile is not in memory it
   756  // will load it from disk
   757  func (sfs *SiaFileSet) Open(siaPath modules.SiaPath) (*SiaFileSetEntry, error) {
   758  	sfs.mu.Lock()
   759  	defer sfs.mu.Unlock()
   760  	return sfs.open(siaPath)
   761  }
   762  
   763  // LoadPartialSiaFile loads a SiaFile containing combined chunks.
   764  func (sfs *SiaFileSet) LoadPartialSiaFile(siaPath modules.SiaPath) (*SiaFileSetEntry, error) {
   765  	sfs.mu.Lock()
   766  	defer sfs.mu.Unlock()
   767  	return sfs.loadPartialsSiaFile(siaPath)
   768  }
   769  
   770  // Metadata returns the metadata of a SiaFile.
   771  func (sfs *SiaFileSet) Metadata(siaPath modules.SiaPath) (Metadata, error) {
   772  	sfs.mu.Lock()
   773  	defer sfs.mu.Unlock()
   774  	sf, err := sfs.open(siaPath)
   775  	if err != nil {
   776  		return Metadata{}, err
   777  	}
   778  	return sf.Metadata(), sf.Close()
   779  }
   780  
   781  // Rename will move a siafile from one path to a new path. Existing entries that
   782  // are already open at the old path will continue to be valid.
   783  func (sfs *SiaFileSet) Rename(siaPath, newSiaPath modules.SiaPath) error {
   784  	sfs.mu.Lock()
   785  	defer sfs.mu.Unlock()
   786  
   787  	// Check for a conflict in the destination path.
   788  	exists := sfs.exists(newSiaPath)
   789  	if exists {
   790  		return ErrPathOverload
   791  	}
   792  	// Grab the existing entry.
   793  	entry, err := sfs.open(siaPath)
   794  	if err != nil {
   795  		return err
   796  	}
   797  	// This whole function is wrapped in a set lock, which means per convention
   798  	// we cannot call an exported function (Close) on the entry. We will have to
   799  	// call closeEntry on the set instead.
   800  	defer sfs.closeEntry(entry)
   801  
   802  	// Update SiaFileSet map to hold the entry in the new siapath.
   803  	sfs.siapathToUID[newSiaPath] = entry.UID()
   804  	delete(sfs.siapathToUID, siaPath)
   805  
   806  	// Update the siafile to have a new name.
   807  	return entry.Rename(newSiaPath.SiaFileSysPath(sfs.staticSiaFileDir))
   808  }
   809  
   810  // DeleteDir deletes a siadir and all the siadirs and siafiles within it
   811  // recursively.
   812  func (sfs *SiaFileSet) DeleteDir(siaPath modules.SiaPath, deleteDir siadir.DeleteDirFunc) error {
   813  	// Prevent new files from being opened.
   814  	sfs.mu.Lock()
   815  	defer sfs.mu.Unlock()
   816  	// Lock all files within the dir.
   817  	var lockedFiles []*siaFileSetEntry
   818  	defer func() {
   819  		for _, entry := range lockedFiles {
   820  			entry.mu.Unlock()
   821  		}
   822  	}()
   823  	for sp := range sfs.siapathToUID {
   824  		entry, _, exists := sfs.siaPathToEntryAndUID(sp)
   825  		if !exists {
   826  			continue
   827  		}
   828  		if strings.HasPrefix(sp.String(), siaPath.String()) {
   829  			entry.mu.Lock()
   830  			lockedFiles = append(lockedFiles, entry)
   831  		}
   832  	}
   833  	// Delete the dir using the provided delete function.
   834  	if err := deleteDir(siaPath); err != nil {
   835  		return errors.AddContext(err, "failed to delete dir")
   836  	}
   837  	// Delete was successful. Delete the siafiles in memory before they are being
   838  	// unlocked again.
   839  	for _, entry := range lockedFiles {
   840  		// Get siaPath.
   841  		var sp modules.SiaPath
   842  		if err := sp.LoadSysPath(sfs.staticSiaFileDir, entry.siaFilePath); err != nil {
   843  			build.Critical("Getting the siaPath shouldn't fail")
   844  			continue
   845  		}
   846  		// Mark the file as deleted. It will be closed automatically by the last
   847  		// thread calling 'Close' on it.
   848  		entry.deleted = true
   849  	}
   850  	return nil
   851  }
   852  
   853  // RenameDir renames a siadir and all the siadirs and siafiles within it
   854  // recursively.
   855  func (sfs *SiaFileSet) RenameDir(oldPath, newPath modules.SiaPath, rename siadir.RenameDirFunc) error {
   856  	if oldPath.Equals(modules.RootSiaPath()) {
   857  		return errors.New("can't rename root dir")
   858  	}
   859  	if oldPath.Equals(newPath) {
   860  		return nil // nothing to do
   861  	}
   862  	if strings.HasPrefix(newPath.String(), oldPath.String()) {
   863  		return errors.New("can't rename folder into itself")
   864  	}
   865  	// Prevent new files from being opened.
   866  	sfs.mu.Lock()
   867  	defer sfs.mu.Unlock()
   868  	var lockedFiles []*siaFileSetEntry
   869  	defer func() {
   870  		for _, entry := range lockedFiles {
   871  			entry.mu.Unlock()
   872  		}
   873  	}()
   874  	// Lock all files within the old dir.
   875  	for siaPath := range sfs.siapathToUID {
   876  		entry, _, exists := sfs.siaPathToEntryAndUID(siaPath)
   877  		if !exists {
   878  			continue
   879  		}
   880  		if strings.HasPrefix(siaPath.String(), oldPath.String()) {
   881  			entry.mu.Lock()
   882  			lockedFiles = append(lockedFiles, entry)
   883  		}
   884  	}
   885  	// Rename the dir using the provided rename function.
   886  	if err := rename(oldPath, newPath); err != nil {
   887  		return errors.AddContext(err, "failed to rename dir")
   888  	}
   889  	// Rename was successful. Rename the siafiles in memory before they are being
   890  	// unlocked again.
   891  	for _, entry := range lockedFiles {
   892  		// Get old SiaPath.
   893  		var oldSiaPath modules.SiaPath
   894  		if err := oldSiaPath.LoadSysPath(sfs.staticSiaFileDir, entry.siaFilePath); err != nil {
   895  			build.Critical("Getting the siaPath shouldn't fail")
   896  			continue
   897  		}
   898  		// Rebase path to new folder.
   899  		sp, err := oldSiaPath.Rebase(oldPath, newPath)
   900  		if err != nil {
   901  			build.Critical("Rebasing siapaths shouldn't fail")
   902  			continue
   903  		}
   904  		// Update the siafilepath of the entry and the siafileToUIDMap.
   905  		delete(sfs.siapathToUID, oldSiaPath)
   906  		sfs.siapathToUID[sp] = entry.staticMetadata.UniqueID
   907  		entry.siaFilePath = sp.SiaFileSysPath(sfs.staticSiaFileDir)
   908  	}
   909  	return nil
   910  }
   911  
   912  // OpenPartialsSiaFile opens a partials SiaFile and creates it if it doesn't
   913  // exist yet.
   914  func (sfs *SiaFileSet) OpenPartialsSiaFile(ec modules.ErasureCoder) (*SiaFileSetEntry, error) {
   915  	sfs.mu.Lock()
   916  	defer sfs.mu.Unlock()
   917  	return sfs.openPartialsSiaFile(ec, true)
   918  }
   919  
   920  // openPartialsSiaFile opens a SiaFile which will be used to upload chunks
   921  // consisting of partial chunks. If the SiaFile doesn't exist, it will be
   922  // created.
   923  func (sfs *SiaFileSet) openPartialsSiaFile(ec modules.ErasureCoder, create bool) (*SiaFileSetEntry, error) {
   924  	siaPath := modules.CombinedSiaFilePath(ec)
   925  	var entry *siaFileSetEntry
   926  	var exists bool
   927  	entry, _, exists = sfs.siaPathToEntryAndUID(siaPath)
   928  	if !exists && !create {
   929  		return nil, os.ErrNotExist
   930  	} else if !exists {
   931  		// Try and Load File from disk.
   932  		sf, err := LoadSiaFile(siaPath.SiaPartialsFileSysPath(sfs.staticSiaFileDir), sfs.wal)
   933  		if os.IsNotExist(err) {
   934  			// File doesn't exist. Create a new one.
   935  			siaFilePath := siaPath.SiaPartialsFileSysPath(sfs.staticSiaFileDir)
   936  			sf, err = New(siaFilePath, "", sfs.wal, ec, crypto.GenerateSiaKey(crypto.TypeDefaultRenter), 0, 0600, nil, true)
   937  			if err != nil {
   938  				return nil, err
   939  			}
   940  		}
   941  		if err != nil {
   942  			return nil, err
   943  		}
   944  		// Create the entry for the SiaFile and assign the partials file.
   945  		entry, err = sfs.newSiaFileSetEntry(sf)
   946  		if err != nil {
   947  			return nil, err
   948  		}
   949  	}
   950  	if entry.Deleted() {
   951  		return nil, ErrUnknownPath
   952  	}
   953  	threadUID := randomThreadUID()
   954  	entry.threadMapMu.Lock()
   955  	defer entry.threadMapMu.Unlock()
   956  	entry.threadMap[threadUID] = newThreadInfo()
   957  	return &SiaFileSetEntry{
   958  		siaFileSetEntry: entry,
   959  		threadUID:       threadUID,
   960  	}, nil
   961  }
   962  
   963  // loadPartialsSiaFile loads a SiaFile which will be used to upload chunks
   964  // consisting of partial chunks.
   965  func (sfs *SiaFileSet) loadPartialsSiaFile(siaPath modules.SiaPath) (*SiaFileSetEntry, error) {
   966  	var entry *siaFileSetEntry
   967  	var exists bool
   968  	entry, _, exists = sfs.siaPathToEntryAndUID(siaPath)
   969  	if !exists {
   970  		// Try and Load File from disk.
   971  		sf, err := LoadSiaFile(siaPath.SiaPartialsFileSysPath(sfs.staticSiaFileDir), sfs.wal)
   972  		if os.IsNotExist(err) {
   973  			return nil, ErrUnknownPath
   974  		}
   975  		if err != nil {
   976  			return nil, err
   977  		}
   978  		// Create the entry for the SiaFile and assign the partials file.
   979  		entry, err = sfs.newSiaFileSetEntry(sf)
   980  		if err != nil {
   981  			return nil, err
   982  		}
   983  	}
   984  	if entry.Deleted() {
   985  		return nil, ErrUnknownPath
   986  	}
   987  	threadUID := randomThreadUID()
   988  	entry.threadMapMu.Lock()
   989  	defer entry.threadMapMu.Unlock()
   990  	entry.threadMap[threadUID] = newThreadInfo()
   991  	return &SiaFileSetEntry{
   992  		siaFileSetEntry: entry,
   993  		threadUID:       threadUID,
   994  	}, nil
   995  }