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  }