gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/renter/siafile/persist.go (about) 1 package siafile 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io" 7 "io/ioutil" 8 "os" 9 "path/filepath" 10 11 "gitlab.com/NebulousLabs/errors" 12 13 "gitlab.com/SiaPrime/SiaPrime/build" 14 "gitlab.com/SiaPrime/SiaPrime/encoding" 15 "gitlab.com/SiaPrime/SiaPrime/modules" 16 "gitlab.com/SiaPrime/writeaheadlog" 17 ) 18 19 var ( 20 // errUnknownSiaFileUpdate is returned when applyUpdates finds an update 21 // that is unknown 22 errUnknownSiaFileUpdate = errors.New("unknown siafile update") 23 ) 24 25 // ApplyUpdates is a wrapper for applyUpdates that uses the production 26 // dependencies. 27 func ApplyUpdates(updates ...writeaheadlog.Update) error { 28 return applyUpdates(modules.ProdDependencies, updates...) 29 } 30 31 // LoadSiaFile is a wrapper for loadSiaFile that uses the production 32 // dependencies. 33 func LoadSiaFile(path string, wal *writeaheadlog.WAL) (*SiaFile, error) { 34 return loadSiaFile(path, wal, modules.ProdDependencies) 35 } 36 37 // LoadSiaFileFromReader allows loading a SiaFile from a different location that 38 // directly from disk as long as the source satisfies the SiaFileSource 39 // interface. 40 func LoadSiaFileFromReader(r io.ReadSeeker, path string, wal *writeaheadlog.WAL) (*SiaFile, error) { 41 return loadSiaFileFromReader(r, path, wal, modules.ProdDependencies) 42 } 43 44 // LoadSiaFileFromReaderWithChunks does not only read the header of the Siafile 45 // from disk but also the chunks which it returns separately. This is useful if 46 // the file is read from a buffer in-memory and the chunks can't be read from 47 // disk later. 48 func LoadSiaFileFromReaderWithChunks(r io.ReadSeeker, path string, wal *writeaheadlog.WAL) (*SiaFile, []chunk, error) { 49 sf, err := LoadSiaFileFromReader(r, path, wal) 50 if err != nil { 51 return nil, nil, err 52 } 53 // Load chunks from reader. 54 var chunks []chunk 55 chunkBytes := make([]byte, int(sf.staticMetadata.StaticPagesPerChunk)*pageSize) 56 for chunkIndex := 0; chunkIndex < sf.numChunks; chunkIndex++ { 57 if _, err := r.Read(chunkBytes); err != nil && err != io.EOF { 58 return nil, nil, errors.AddContext(err, fmt.Sprintf("failed to read chunk %v", chunkIndex)) 59 } 60 chunk, err := unmarshalChunk(uint32(sf.staticMetadata.staticErasureCode.NumPieces()), chunkBytes) 61 if err != nil { 62 return nil, nil, errors.AddContext(err, fmt.Sprintf("failed to unmarshal chunk %v", chunkIndex)) 63 } 64 chunk.Index = int(chunkIndex) 65 chunks = append(chunks, chunk) 66 } 67 return sf, chunks, nil 68 } 69 70 // LoadSiaFileMetadata is a wrapper for loadSiaFileMetadata that uses the 71 // production dependencies. 72 func LoadSiaFileMetadata(path string) (Metadata, error) { 73 return loadSiaFileMetadata(path, modules.ProdDependencies) 74 } 75 76 // SetPartialChunks informs the SiaFile about a partial chunk that has been 77 // saved by the partial chunk set. As such it should be exclusively called by 78 // the partial chunk set. It updates the metadata of the SiaFile and also adds a 79 // new chunk to the partial SiaFile if necessary. At the end it applies the 80 // updates of the partial chunk set, the SiaFile and the partial SiaFile 81 // atomically. 82 func (sf *SiaFile) SetPartialChunks(combinedChunks []modules.PartialChunk, updates []writeaheadlog.Update) error { 83 // SavePartialChunk can only be called when there is no partial chunk yet. 84 if !sf.staticMetadata.HasPartialChunk || len(sf.staticMetadata.PartialChunks) > 0 { 85 return fmt.Errorf("can't call SetPartialChunk unless file has a partial chunk and doesn't have combined chunks assigned to it yet: %v %v", 86 sf.staticMetadata.HasPartialChunk, len(sf.staticMetadata.PartialChunks)) 87 } 88 // Check the number of combinedChunks for sanity. 89 if len(combinedChunks) != 1 && len(combinedChunks) != 2 { 90 return fmt.Errorf("should have 1 or 2 combined chunks but got %v", len(combinedChunks)) 91 } 92 // Make sure the length is what we would expect. 93 var totalLength int64 94 for _, cc := range combinedChunks { 95 totalLength += int64(cc.Length) 96 } 97 expectedLength := sf.staticMetadata.FileSize % int64(sf.staticChunkSize()) 98 if totalLength != expectedLength { 99 return fmt.Errorf("expect partial chunk length to be %v but was %v", expectedLength, totalLength) 100 } 101 // Lock both the SiaFile and partials SiaFile. We need to atomically update 102 // both of them. 103 sf.mu.Lock() 104 defer sf.mu.Unlock() 105 // Check if siafile has been deleted. 106 if sf.deleted { 107 return errors.New("can't set combined chunk of deleted siafile") 108 } 109 sf.partialsSiaFile.mu.Lock() 110 defer sf.partialsSiaFile.mu.Unlock() 111 // For each combined chunk that is not yet tracked within the partials sia 112 // file, add a chunk to the partials sia file. 113 pcs := make([]PartialChunkInfo, 0, len(combinedChunks)) 114 for _, c := range combinedChunks { 115 pc := PartialChunkInfo{ 116 ID: c.ChunkID, 117 Length: c.Length, 118 Offset: c.Offset, 119 Status: CombinedChunkStatusInComplete, 120 } 121 if c.InPartialsFile { 122 pc.Index = uint64(sf.partialsSiaFile.numChunks - 1) 123 } else { 124 pc.Index = uint64(sf.partialsSiaFile.numChunks) 125 u, err := sf.partialsSiaFile.addCombinedChunk() 126 if err != nil { 127 return err 128 } 129 updates = append(updates, u...) 130 } 131 pcs = append(pcs, pc) 132 } 133 // Update the combined chunk metadata on disk. 134 u, err := sf.saveMetadataUpdates() 135 if err != nil { 136 return err 137 } 138 updates = append(updates, u...) 139 err = createAndApplyTransaction(sf.wal, updates...) 140 if err != nil { 141 return err 142 } 143 sf.numChunks = sf.numChunks - 1 + len(combinedChunks) 144 sf.staticMetadata.PartialChunks = pcs 145 return nil 146 } 147 148 // SetPartialsSiaFile sets the partialsSiaFile field of the SiaFile. This is 149 // usually done for non-partials SiaFiles after loading them from disk. 150 func (sf *SiaFile) SetPartialsSiaFile(partialsSiaFile *SiaFileSetEntry) { 151 sf.mu.Lock() 152 defer sf.mu.Unlock() 153 sf.partialsSiaFile = partialsSiaFile 154 } 155 156 // SetSiaFilePath sets the path of the siafile on disk. 157 func (sf *SiaFile) SetSiaFilePath(path string) { 158 sf.mu.Lock() 159 defer sf.mu.Unlock() 160 sf.siaFilePath = path 161 } 162 163 // applyUpdates applies a number of writeaheadlog updates to the corresponding 164 // SiaFile. This method can apply updates from different SiaFiles and should 165 // only be run before the SiaFiles are loaded from disk right after the startup 166 // of siad. Otherwise we might run into concurrency issues. 167 func applyUpdates(deps modules.Dependencies, updates ...writeaheadlog.Update) error { 168 for _, u := range updates { 169 err := func() error { 170 switch u.Name { 171 case updateDeleteName: 172 return readAndApplyDeleteUpdate(deps, u) 173 case updateInsertName: 174 return readAndApplyInsertUpdate(deps, u) 175 case updateDeletePartialName: 176 return readAndApplyDeleteUpdate(deps, u) 177 case writeaheadlog.NameDeleteUpdate: 178 return writeaheadlog.ApplyDeleteUpdate(u) 179 case writeaheadlog.NameTruncateUpdate: 180 return writeaheadlog.ApplyTruncateUpdate(u) 181 case writeaheadlog.NameWriteAtUpdate: 182 return writeaheadlog.ApplyWriteAtUpdate(u) 183 default: 184 return errUnknownSiaFileUpdate 185 } 186 }() 187 if err != nil { 188 return errors.AddContext(err, "failed to apply update") 189 } 190 } 191 return nil 192 } 193 194 // createDeleteUpdate is a helper method that creates a writeaheadlog for 195 // deleting a file. 196 func createDeleteUpdate(path string) writeaheadlog.Update { 197 return writeaheadlog.Update{ 198 Name: updateDeleteName, 199 Instructions: []byte(path), 200 } 201 } 202 203 // createDeletePartialUpdate is a helper method that creates a writeaheadlog for 204 // deleting a .partial file. 205 func createDeletePartialUpdate(path string) writeaheadlog.Update { 206 return writeaheadlog.Update{ 207 Name: updateDeletePartialName, 208 Instructions: []byte(path), 209 } 210 } 211 212 // loadSiaFile loads a SiaFile from disk. 213 func loadSiaFile(path string, wal *writeaheadlog.WAL, deps modules.Dependencies) (*SiaFile, error) { 214 // Open the file. 215 f, err := deps.Open(path) 216 if err != nil { 217 return nil, err 218 } 219 defer f.Close() 220 return loadSiaFileFromReader(f, path, wal, deps) 221 } 222 223 // loadSiaFileFromReader allows loading a SiaFile from a different location that 224 // directly from disk as long as the source satisfies the SiaFileSource 225 // interface. 226 func loadSiaFileFromReader(r io.ReadSeeker, path string, wal *writeaheadlog.WAL, deps modules.Dependencies) (*SiaFile, error) { 227 // Create the SiaFile 228 sf := &SiaFile{ 229 deps: deps, 230 siaFilePath: path, 231 wal: wal, 232 } 233 // Load the metadata. 234 decoder := json.NewDecoder(r) 235 err := decoder.Decode(&sf.staticMetadata) 236 if err != nil { 237 return nil, errors.AddContext(err, "failed to decode metadata") 238 } 239 // COMPATv137 legacy files might not have a unique id. 240 if sf.staticMetadata.UniqueID == "" { 241 sf.staticMetadata.UniqueID = uniqueID() 242 } 243 // Create the erasure coder. 244 sf.staticMetadata.staticErasureCode, err = unmarshalErasureCoder(sf.staticMetadata.StaticErasureCodeType, sf.staticMetadata.StaticErasureCodeParams) 245 if err != nil { 246 return nil, err 247 } 248 // COMPATv140 legacy 0-byte files might not have correct cached fields since we 249 // never update them once they are created. 250 if sf.staticMetadata.FileSize == 0 { 251 ec := sf.staticMetadata.staticErasureCode 252 sf.staticMetadata.CachedHealth = 0 253 sf.staticMetadata.CachedStuckHealth = 0 254 sf.staticMetadata.CachedRedundancy = float64(ec.NumPieces()) / float64(ec.MinPieces()) 255 sf.staticMetadata.CachedUserRedundancy = sf.staticMetadata.CachedRedundancy 256 sf.staticMetadata.CachedUploadProgress = 100 257 } 258 // Load the pubKeyTable. 259 pubKeyTableLen := sf.staticMetadata.ChunkOffset - sf.staticMetadata.PubKeyTableOffset 260 if pubKeyTableLen < 0 { 261 return nil, fmt.Errorf("pubKeyTableLen is %v, can't load file", pubKeyTableLen) 262 } 263 rawPubKeyTable := make([]byte, pubKeyTableLen) 264 if _, err := r.Seek(sf.staticMetadata.PubKeyTableOffset, io.SeekStart); err != nil { 265 return nil, errors.AddContext(err, "failed to seek to pubKeyTable") 266 } 267 if _, err := r.Read(rawPubKeyTable); err == io.EOF { 268 // Empty table. 269 sf.pubKeyTable = []HostPublicKey{} 270 } else if err != nil { 271 // Unexpected error. 272 return nil, errors.AddContext(err, "failed to read pubKeyTable from disk") 273 } else { 274 // Unmarshal table. 275 sf.pubKeyTable, err = unmarshalPubKeyTable(rawPubKeyTable) 276 if err != nil { 277 return nil, errors.AddContext(err, "failed to unmarshal pubKeyTable") 278 } 279 } 280 // Seek to the start of the chunks. 281 off, err := r.Seek(sf.staticMetadata.ChunkOffset, io.SeekStart) 282 if err != nil { 283 return nil, err 284 } 285 // Sanity check that the offset is page aligned. 286 if off%pageSize != 0 { 287 return nil, errors.New("chunkOff is not page aligned") 288 } 289 // Set numChunks field. 290 numChunks := sf.staticMetadata.FileSize / int64(sf.staticChunkSize()) 291 if sf.staticMetadata.FileSize%int64(sf.staticChunkSize()) != 0 || numChunks == 0 { 292 numChunks++ 293 } 294 sf.numChunks = int(numChunks) 295 if len(sf.staticMetadata.PartialChunks) > 0 { 296 sf.numChunks = sf.numChunks - 1 + len(sf.staticMetadata.PartialChunks) 297 } 298 return sf, nil 299 } 300 301 // loadSiaFileMetadata loads only the metadata of a SiaFile from disk. 302 func loadSiaFileMetadata(path string, deps modules.Dependencies) (md Metadata, err error) { 303 // Open the file. 304 f, err := deps.Open(path) 305 if err != nil { 306 return Metadata{}, err 307 } 308 defer f.Close() 309 // Load the metadata. 310 decoder := json.NewDecoder(f) 311 if err = decoder.Decode(&md); err != nil { 312 return 313 } 314 // Create the erasure coder. 315 md.staticErasureCode, err = unmarshalErasureCoder(md.StaticErasureCodeType, md.StaticErasureCodeParams) 316 if err != nil { 317 return 318 } 319 return 320 } 321 322 // readAndApplyDeleteUpdate reads the delete update and applies it. This helper 323 // assumes that the file is not open 324 func readAndApplyDeleteUpdate(deps modules.Dependencies, update writeaheadlog.Update) error { 325 err := deps.RemoveFile(readDeleteUpdate(update)) 326 if os.IsNotExist(err) { 327 return nil 328 } 329 return err 330 } 331 332 // readAndApplyInsertupdate reads the insert update and applies it. This helper 333 // assumes that the file is not open and so should only be called on start up 334 // before any siafiles are loaded from disk 335 func readAndApplyInsertUpdate(deps modules.Dependencies, update writeaheadlog.Update) error { 336 // Decode update. 337 path, index, data, err := readInsertUpdate(update) 338 if err != nil { 339 return err 340 } 341 342 // Open the file. 343 f, err := deps.OpenFile(path, os.O_RDWR|os.O_CREATE, 0600) 344 if err != nil { 345 return err 346 } 347 defer f.Close() 348 349 // Write data. 350 if n, err := f.WriteAt(data, index); err != nil { 351 return err 352 } else if n < len(data) { 353 return fmt.Errorf("update was only applied partially - %v / %v", n, len(data)) 354 } 355 // Sync file. 356 return f.Sync() 357 } 358 359 // readDeleteUpdate unmarshals the update's instructions and returns the 360 // encoded path. 361 func readDeleteUpdate(update writeaheadlog.Update) string { 362 return string(update.Instructions) 363 } 364 365 // readInsertUpdate unmarshals the update's instructions and returns the path, index 366 // and data encoded in the instructions. 367 func readInsertUpdate(update writeaheadlog.Update) (path string, index int64, data []byte, err error) { 368 if !IsSiaFileUpdate(update) { 369 err = errors.New("readUpdate can't read non-SiaFile update") 370 build.Critical(err) 371 return 372 } 373 err = encoding.UnmarshalAll(update.Instructions, &path, &index, &data) 374 return 375 } 376 377 // allocateHeaderPage allocates a new page for the metadata and publicKeyTable. 378 // It returns an update that moves the chunkData back by one pageSize if 379 // applied and also updates the ChunkOffset of the metadata. 380 func (sf *SiaFile) allocateHeaderPage() (writeaheadlog.Update, error) { 381 // Sanity check the chunk offset. 382 if sf.staticMetadata.ChunkOffset%pageSize != 0 { 383 build.Critical("the chunk offset is not page aligned") 384 } 385 // Open the file. 386 f, err := sf.deps.OpenFile(sf.siaFilePath, os.O_RDWR|os.O_CREATE, 0600) 387 if err != nil { 388 return writeaheadlog.Update{}, errors.AddContext(err, "failed to open siafile") 389 } 390 defer f.Close() 391 // Seek the chunk offset. 392 _, err = f.Seek(sf.staticMetadata.ChunkOffset, io.SeekStart) 393 if err != nil { 394 return writeaheadlog.Update{}, err 395 } 396 // Read all the chunk data. 397 chunkData, err := ioutil.ReadAll(f) 398 if err != nil { 399 return writeaheadlog.Update{}, err 400 } 401 // Move the offset back by a pageSize. 402 sf.staticMetadata.ChunkOffset += pageSize 403 404 // Create and return update. 405 return sf.createInsertUpdate(sf.staticMetadata.ChunkOffset, chunkData), nil 406 } 407 408 // applyUpdates applies updates to the SiaFile. Only updates that belong to the 409 // SiaFile on which applyUpdates is called can be applied. Everything else will 410 // be considered a developer error and cause the update to not be applied to 411 // avoid corruption. applyUpdates also syncs the SiaFile for convenience since 412 // it already has an open file handle. 413 func (sf *SiaFile) applyUpdates(updates ...writeaheadlog.Update) (err error) { 414 // Sanity check that file hasn't been deleted. 415 if sf.deleted { 416 return errors.New("can't call applyUpdates on deleted file") 417 } 418 419 // If the set of updates contains a delete, all updates prior to that delete 420 // are irrelevant, so perform the last delete and then process the remaining 421 // updates. This also prevents a bug on Windows where we attempt to delete 422 // the file while holding a open file handle. 423 for i := len(updates) - 1; i >= 0; i-- { 424 u := updates[i] 425 switch u.Name { 426 case updateDeleteName: 427 if err := readAndApplyDeleteUpdate(sf.deps, u); err != nil { 428 return err 429 } 430 updates = updates[i+1:] 431 break 432 default: 433 continue 434 } 435 } 436 if len(updates) == 0 { 437 return nil 438 } 439 440 // Create the path if it doesn't exist yet. 441 if err = os.MkdirAll(filepath.Dir(sf.siaFilePath), 0700); err != nil { 442 return err 443 } 444 // Create and/or open the file. 445 f, err := sf.deps.OpenFile(sf.siaFilePath, os.O_RDWR|os.O_CREATE, 0600) 446 if err != nil { 447 return err 448 } 449 defer func() { 450 if err == nil { 451 // If no error occurred we sync and close the file. 452 err = errors.Compose(f.Sync(), f.Close()) 453 } else { 454 // Otherwise we still need to close the file. 455 err = errors.Compose(err, f.Close()) 456 } 457 }() 458 459 // Apply updates. 460 for _, u := range updates { 461 err := func() error { 462 switch u.Name { 463 case updateDeleteName: 464 // Sanity check: all of the updates should be insert updates. 465 build.Critical("Unexpected non-insert update", u.Name) 466 return nil 467 case updateInsertName: 468 return sf.readAndApplyInsertUpdate(f, u) 469 case updateDeletePartialName: 470 return readAndApplyDeleteUpdate(sf.deps, u) 471 default: 472 return errUnknownSiaFileUpdate 473 } 474 }() 475 if err != nil { 476 return errors.AddContext(err, "failed to apply update") 477 } 478 } 479 return nil 480 } 481 482 // chunk reads the chunk with index chunkIndex from disk. 483 func (sf *SiaFile) chunk(chunkIndex int) (chunk, error) { 484 // Handle partial chunk. 485 if cci, ok := sf.isIncludedPartialChunk(uint64(chunkIndex)); ok { 486 c, err := sf.partialsSiaFile.Chunk(cci.Index) 487 c.Index = chunkIndex // convert index within partials file to requested index 488 return c, err 489 } else if sf.isIncompletePartialChunk(uint64(chunkIndex)) { 490 return chunk{Index: chunkIndex}, nil 491 } 492 // Handle full chunk. 493 chunkOffset := sf.chunkOffset(chunkIndex) 494 chunkBytes := make([]byte, int(sf.staticMetadata.StaticPagesPerChunk)*pageSize) 495 f, err := sf.deps.Open(sf.siaFilePath) 496 if err != nil { 497 return chunk{}, errors.AddContext(err, "failed to open file to read chunk") 498 } 499 defer f.Close() 500 if _, err := f.ReadAt(chunkBytes, chunkOffset); err != nil && err != io.EOF { 501 return chunk{}, errors.AddContext(err, "failed to read chunk from disk") 502 } 503 c, err := unmarshalChunk(uint32(sf.staticMetadata.staticErasureCode.NumPieces()), chunkBytes) 504 if err != nil { 505 return chunk{}, errors.AddContext(err, "failed to unmarshal chunk") 506 } 507 c.Index = chunkIndex // Set non-persisted field 508 return c, nil 509 } 510 511 // iterateChunks iterates over all the chunks on disk and create wal updates for 512 // each chunk that was modified. 513 func (sf *SiaFile) iterateChunks(iterFunc func(chunk *chunk) (bool, error)) ([]writeaheadlog.Update, error) { 514 var updates []writeaheadlog.Update 515 err := sf.iterateChunksReadonly(func(chunk chunk) error { 516 modified, err := iterFunc(&chunk) 517 if err != nil { 518 return err 519 } 520 cci, ok := sf.isIncludedPartialChunk(uint64(chunk.Index)) 521 if !ok && sf.isIncompletePartialChunk(uint64(chunk.Index)) { 522 // Can't persist incomplete partial chunk. Make sure iterFunc doesn't try to. 523 return errors.New("can't persist incomplete partial chunk") 524 } 525 if modified && ok { 526 chunk.Index = int(cci.Index) 527 updates = append(updates, sf.partialsSiaFile.saveChunkUpdate(chunk)) 528 } else if modified { 529 updates = append(updates, sf.saveChunkUpdate(chunk)) 530 } 531 return nil 532 }) 533 return updates, err 534 } 535 536 // iterateChunksReadonly iterates over all the chunks on disk and calls iterFunc 537 // on each one without modifying them. 538 func (sf *SiaFile) iterateChunksReadonly(iterFunc func(chunk chunk) error) error { 539 // Open the file. 540 f, err := os.Open(sf.siaFilePath) 541 if err != nil { 542 return errors.AddContext(err, "failed to open file") 543 } 544 defer f.Close() 545 // Seek to the first chunk. 546 _, err = f.Seek(sf.staticMetadata.ChunkOffset, io.SeekStart) 547 if err != nil { 548 return errors.AddContext(err, "failed to seek to ChunkOffset") 549 } 550 // Read the chunks one-by-one. 551 chunkBytes := make([]byte, int(sf.staticMetadata.StaticPagesPerChunk)*pageSize) 552 for chunkIndex := 0; chunkIndex < sf.numChunks; chunkIndex++ { 553 var c chunk 554 var err error 555 if cci, ok := sf.isIncludedPartialChunk(uint64(chunkIndex)); ok { 556 c, err = sf.partialsSiaFile.Chunk(cci.Index) 557 } else if sf.isIncompletePartialChunk(uint64(chunkIndex)) { 558 c = chunk{Pieces: make([][]piece, sf.staticMetadata.staticErasureCode.NumPieces())} 559 } else { 560 if _, err := f.Read(chunkBytes); err != nil && err != io.EOF { 561 return errors.AddContext(err, fmt.Sprintf("failed to read chunk %v", chunkIndex)) 562 } 563 c, err = unmarshalChunk(uint32(sf.staticMetadata.staticErasureCode.NumPieces()), chunkBytes) 564 if err != nil { 565 return errors.AddContext(err, fmt.Sprintf("failed to unmarshal chunk %v", chunkIndex)) 566 } 567 } 568 c.Index = int(chunkIndex) 569 if err := iterFunc(c); err != nil { 570 return errors.AddContext(err, fmt.Sprintf("failed to iterate over chunk %v", chunkIndex)) 571 } 572 } 573 return nil 574 } 575 576 // chunkOffset returns the offset of a marshaled chunk withint the file. 577 func (sf *SiaFile) chunkOffset(chunkIndex int) int64 { 578 if chunkIndex < 0 { 579 panic("chunk index can't be negative") 580 } 581 return sf.staticMetadata.ChunkOffset + int64(chunkIndex)*int64(sf.staticMetadata.StaticPagesPerChunk)*pageSize 582 } 583 584 // createAndApplyTransaction is a helper method that creates a writeaheadlog 585 // transaction and applies it. 586 func (sf *SiaFile) createAndApplyTransaction(updates ...writeaheadlog.Update) error { 587 // Sanity check that file hasn't been deleted. 588 if sf.deleted { 589 return errors.New("can't call createAndApplyTransaction on deleted file") 590 } 591 if len(updates) == 0 { 592 return nil 593 } 594 // Create the writeaheadlog transaction. 595 txn, err := sf.wal.NewTransaction(updates) 596 if err != nil { 597 return errors.AddContext(err, "failed to create wal txn") 598 } 599 // No extra setup is required. Signal that it is done. 600 if err := <-txn.SignalSetupComplete(); err != nil { 601 return errors.AddContext(err, "failed to signal setup completion") 602 } 603 // Apply the updates. 604 if err := sf.applyUpdates(updates...); err != nil { 605 return errors.AddContext(err, "failed to apply updates") 606 } 607 // Updates are applied. Let the writeaheadlog know. 608 if err := txn.SignalUpdatesApplied(); err != nil { 609 return errors.AddContext(err, "failed to signal that updates are applied") 610 } 611 return nil 612 } 613 614 // createAndApplyTransaction is a generic version of the 615 // createAndApplyTransaction method of the SiaFile. This will result in 2 fsyncs 616 // independent of the number of updates. 617 func createAndApplyTransaction(wal *writeaheadlog.WAL, updates ...writeaheadlog.Update) error { 618 if len(updates) == 0 { 619 return nil 620 } 621 // Create the writeaheadlog transaction. 622 txn, err := wal.NewTransaction(updates) 623 if err != nil { 624 return errors.AddContext(err, "failed to create wal txn") 625 } 626 // No extra setup is required. Signal that it is done. 627 if err := <-txn.SignalSetupComplete(); err != nil { 628 return errors.AddContext(err, "failed to signal setup completion") 629 } 630 // Apply the updates. 631 if err := ApplyUpdates(updates...); err != nil { 632 return errors.AddContext(err, "failed to apply updates") 633 } 634 // Updates are applied. Let the writeaheadlog know. 635 if err := txn.SignalUpdatesApplied(); err != nil { 636 return errors.AddContext(err, "failed to signal that updates are applied") 637 } 638 return nil 639 } 640 641 // createDeleteUpdate is a helper method that creates a writeaheadlog for 642 // deleting a file. 643 func (sf *SiaFile) createDeleteUpdate() writeaheadlog.Update { 644 return createDeleteUpdate(sf.siaFilePath) 645 } 646 647 // createInsertUpdate is a helper method which creates a writeaheadlog update for 648 // writing the specified data to the provided index. It is usually not called 649 // directly but wrapped into another helper that creates an update for a 650 // specific part of the SiaFile. e.g. the metadata 651 func createInsertUpdate(path string, index int64, data []byte) writeaheadlog.Update { 652 if index < 0 { 653 index = 0 654 data = []byte{} 655 build.Critical("index passed to createUpdate should never be negative") 656 } 657 // Create update 658 return writeaheadlog.Update{ 659 Name: updateInsertName, 660 Instructions: encoding.MarshalAll(path, index, data), 661 } 662 } 663 664 // createInsertUpdate is a helper method which creates a writeaheadlog update for 665 // writing the specified data to the provided index. It is usually not called 666 // directly but wrapped into another helper that creates an update for a 667 // specific part of the SiaFile. e.g. the metadata 668 func (sf *SiaFile) createInsertUpdate(index int64, data []byte) writeaheadlog.Update { 669 return createInsertUpdate(sf.siaFilePath, index, data) 670 } 671 672 // readAndApplyInsertUpdate reads the insert update for a SiaFile and then 673 // applies it 674 func (sf *SiaFile) readAndApplyInsertUpdate(f modules.File, update writeaheadlog.Update) error { 675 // Decode update. 676 path, index, data, err := readInsertUpdate(update) 677 if err != nil { 678 return err 679 } 680 681 // Sanity check path. Update should belong to SiaFile. 682 if sf.siaFilePath != path { 683 build.Critical(fmt.Sprintf("can't apply update for file %s to SiaFile %s", path, sf.siaFilePath)) 684 return nil 685 } 686 687 // Write data. 688 if n, err := f.WriteAt(data, index); err != nil { 689 return err 690 } else if n < len(data) { 691 return fmt.Errorf("update was only applied partially - %v / %v", n, len(data)) 692 } 693 return nil 694 } 695 696 // saveFile saves the SiaFile's header and the provided chunks atomically. 697 func (sf *SiaFile) saveFile(chunks []chunk) error { 698 // Sanity check that file hasn't been deleted. 699 if sf.deleted { 700 return errors.New("can't call saveFile on deleted file") 701 } 702 headerUpdates, err := sf.saveHeaderUpdates() 703 if err != nil { 704 return errors.AddContext(err, "failed to to create save header updates") 705 } 706 var chunksUpdates []writeaheadlog.Update 707 for _, chunk := range chunks { 708 chunksUpdates = append(chunksUpdates, sf.saveChunkUpdate(chunk)) 709 } 710 err = sf.createAndApplyTransaction(append(headerUpdates, chunksUpdates...)...) 711 return errors.AddContext(err, "failed to apply saveFile updates") 712 } 713 714 // saveChunkUpdate creates a writeaheadlog update that saves a single marshaled chunk 715 // to disk when applied. 716 func (sf *SiaFile) saveChunkUpdate(chunk chunk) writeaheadlog.Update { 717 offset := sf.chunkOffset(chunk.Index) 718 chunkBytes := marshalChunk(chunk) 719 return sf.createInsertUpdate(offset, chunkBytes) 720 } 721 722 // saveChunksUpdates creates writeaheadlog updates which save the marshaled chunks of 723 // the SiaFile to disk when applied. 724 func (sf *SiaFile) saveChunksUpdates() ([]writeaheadlog.Update, error) { 725 return sf.iterateChunks(func(chunk *chunk) (bool, error) { 726 return true, nil 727 }) 728 } 729 730 // saveHeaderUpdates creates writeaheadlog updates to saves the metadata and 731 // pubKeyTable of the SiaFile to disk using the writeaheadlog. If the metadata 732 // and overlap due to growing too large and would therefore corrupt if they 733 // were written to disk, a new page is allocated. 734 func (sf *SiaFile) saveHeaderUpdates() ([]writeaheadlog.Update, error) { 735 // Create a list of updates which need to be applied to save the metadata. 736 var updates []writeaheadlog.Update 737 738 // Marshal the pubKeyTable. 739 pubKeyTable, err := marshalPubKeyTable(sf.pubKeyTable) 740 if err != nil { 741 return nil, errors.AddContext(err, "failed to marshal pubkey table") 742 } 743 744 // Update the pubKeyTableOffset. This is not necessarily the final offset 745 // but we need to marshal the metadata with this new offset to see if the 746 // metadata and the pubKeyTable overlap. 747 sf.staticMetadata.PubKeyTableOffset = sf.staticMetadata.ChunkOffset - int64(len(pubKeyTable)) 748 749 // Marshal the metadata. 750 metadata, err := marshalMetadata(sf.staticMetadata) 751 if err != nil { 752 return nil, errors.AddContext(err, "failed to marshal metadata") 753 } 754 755 // If the metadata and the pubKeyTable overlap, we need to allocate a new 756 // page for them. Afterwards we need to marshal the metadata again since 757 // ChunkOffset and PubKeyTableOffset change when allocating a new page. 758 for int64(len(metadata))+int64(len(pubKeyTable)) > sf.staticMetadata.ChunkOffset { 759 // Create update to move chunkData back by a page. 760 chunkUpdate, err := sf.allocateHeaderPage() 761 if err != nil { 762 return nil, errors.AddContext(err, "failed to allocate new header page") 763 } 764 updates = append(updates, chunkUpdate) 765 // Update the PubKeyTableOffset. 766 sf.staticMetadata.PubKeyTableOffset = sf.staticMetadata.ChunkOffset - int64(len(pubKeyTable)) 767 // Marshal the metadata again. 768 metadata, err = marshalMetadata(sf.staticMetadata) 769 if err != nil { 770 return nil, errors.AddContext(err, "failed to marshal metadata again") 771 } 772 } 773 774 // Create updates for the metadata and pubKeyTable. 775 updates = append(updates, sf.createInsertUpdate(0, metadata)) 776 updates = append(updates, sf.createInsertUpdate(sf.staticMetadata.PubKeyTableOffset, pubKeyTable)) 777 return updates, nil 778 } 779 780 // saveMetadataUpdates saves the metadata of the SiaFile but not the 781 // publicKeyTable. Most of the time updates are only made to the metadata and 782 // not to the publicKeyTable and the metadata fits within a single disk sector 783 // on the harddrive. This means that using saveMetadataUpdate instead of 784 // saveHeader is potentially faster for SiaFiles with a header that can not be 785 // marshaled within a single page. 786 func (sf *SiaFile) saveMetadataUpdates() ([]writeaheadlog.Update, error) { 787 // Marshal the pubKeyTable. 788 pubKeyTable, err := marshalPubKeyTable(sf.pubKeyTable) 789 if err != nil { 790 return nil, err 791 } 792 // Sanity check the length of the pubKeyTable to find out if the length of 793 // the table changed. We should never just save the metadata if the table 794 // changed as well as it might lead to corruptions. 795 if sf.staticMetadata.PubKeyTableOffset+int64(len(pubKeyTable)) != sf.staticMetadata.ChunkOffset { 796 build.Critical("never call saveMetadata if the pubKeyTable changed, call saveHeader instead") 797 return sf.saveHeaderUpdates() 798 } 799 // Marshal the metadata. 800 metadata, err := marshalMetadata(sf.staticMetadata) 801 if err != nil { 802 return nil, err 803 } 804 // If the header doesn't fit in the space between the beginning of the file 805 // and the pubKeyTable, we need to call saveHeader since the pubKeyTable 806 // needs to be moved as well and saveHeader is already handling that 807 // edgecase. 808 if int64(len(metadata)) > sf.staticMetadata.PubKeyTableOffset { 809 return sf.saveHeaderUpdates() 810 } 811 // Otherwise we can create and return the updates. 812 return []writeaheadlog.Update{sf.createInsertUpdate(0, metadata)}, nil 813 }