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 }