gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/renter/filesystem/siafile/snapshot.go (about)

     1  package siafile
     2  
     3  import (
     4  	"bytes"
     5  	"io"
     6  	"io/ioutil"
     7  	"math"
     8  	"os"
     9  
    10  	"gitlab.com/NebulousLabs/errors"
    11  
    12  	"gitlab.com/SkynetLabs/skyd/skymodules"
    13  	"go.sia.tech/siad/crypto"
    14  )
    15  
    16  type (
    17  	// Snapshot is a snapshot of a SiaFile. A snapshot is a deep-copy and
    18  	// can be accessed without locking at the cost of being a frozen readonly
    19  	// representation of a siafile which only exists in memory.
    20  	Snapshot struct {
    21  		staticChunks      []Chunk
    22  		staticFileSize    int64
    23  		staticPieceSize   uint64
    24  		staticErasureCode skymodules.ErasureCoder
    25  		staticMasterKey   crypto.CipherKey
    26  		staticMode        os.FileMode
    27  		staticPubKeyTable []HostPublicKey
    28  		staticSiaPath     skymodules.SiaPath
    29  		staticLocalPath   string
    30  		staticUID         SiafileUID
    31  	}
    32  )
    33  
    34  // SnapshotReader is a helper type that allows reading a raw SiaFile from disk
    35  // while keeping the file in memory locked.
    36  type SnapshotReader struct {
    37  	f  *os.File
    38  	sf *SiaFile
    39  }
    40  
    41  // Close closes the underlying file.
    42  func (sfr *SnapshotReader) Close() error {
    43  	defer sfr.sf.mu.RUnlock()
    44  	return sfr.f.Close()
    45  }
    46  
    47  // Read calls Read on the underlying file.
    48  func (sfr *SnapshotReader) Read(b []byte) (int, error) {
    49  	return sfr.f.Read(b)
    50  }
    51  
    52  // Stat returns the FileInfo of the underlying file.
    53  func (sfr *SnapshotReader) Stat() (os.FileInfo, error) {
    54  	return sfr.f.Stat()
    55  }
    56  
    57  // SnapshotReader creates a io.ReadCloser that can be used to read the raw
    58  // Siafile from disk. Note that the underlying siafile holds a readlock until
    59  // the SnapshotReader is closed, which means that no operations can be called to
    60  // the underlying siafile which may cause it to grab a lock, because that will
    61  // cause a deadlock.
    62  //
    63  // Operations which require grabbing a readlock on the underlying siafile are
    64  // also not okay, because if some other thread has attempted to grab a writelock
    65  // on the siafile, the readlock will block and then the Close() statement may
    66  // never be reached for the SnapshotReader.
    67  //
    68  // TODO: Things upstream would be a lot easier if we could drop the requirement
    69  // to hold a lock for the duration of the life of the snapshot reader.
    70  func (sf *SiaFile) SnapshotReader() (*SnapshotReader, error) {
    71  	// Lock the file.
    72  	sf.mu.RLock()
    73  	if sf.deleted {
    74  		sf.mu.RUnlock()
    75  		return nil, errors.AddContext(ErrDeleted, "can't copy deleted SiaFile")
    76  	}
    77  	// Open file.
    78  	f, err := os.Open(sf.siaFilePath)
    79  	if err != nil {
    80  		sf.mu.RUnlock()
    81  		return nil, err
    82  	}
    83  	return &SnapshotReader{
    84  		sf: sf,
    85  		f:  f,
    86  	}, nil
    87  }
    88  
    89  // ChunkIndexByOffset will return the chunkIndex that contains the provided
    90  // offset of a file and also the relative offset within the chunk. If the
    91  // offset is out of bounds, chunkIndex will be equal to NumChunk().
    92  func (s *Snapshot) ChunkIndexByOffset(offset uint64) (chunkIndex uint64, off uint64) {
    93  	return skymodules.ChunkIndexByOffset(offset, s.ChunkSize())
    94  }
    95  
    96  // ChunkSize returns the size of a single chunk of the file.
    97  func (s *Snapshot) ChunkSize() uint64 {
    98  	return s.staticPieceSize * uint64(s.staticErasureCode.MinPieces())
    99  }
   100  
   101  // ErasureCode returns the erasure coder used by the file.
   102  func (s *Snapshot) ErasureCode() skymodules.ErasureCoder {
   103  	return s.staticErasureCode
   104  }
   105  
   106  // LocalPath returns the localPath used to repair the file.
   107  func (s *Snapshot) LocalPath() string {
   108  	return s.staticLocalPath
   109  }
   110  
   111  // MasterKey returns the masterkey used to encrypt the file.
   112  func (s *Snapshot) MasterKey() crypto.CipherKey {
   113  	return s.staticMasterKey
   114  }
   115  
   116  // Mode returns the FileMode of the file.
   117  func (s *Snapshot) Mode() os.FileMode {
   118  	return s.staticMode
   119  }
   120  
   121  // NumChunks returns the number of chunks the file consists of. This will
   122  // return the number of chunks the file consists of even if the file is not
   123  // fully uploaded yet.
   124  func (s *Snapshot) NumChunks() uint64 {
   125  	return uint64(len(s.staticChunks))
   126  }
   127  
   128  // Pieces returns all the pieces for a chunk in a slice of slices that contains
   129  // all the pieces for a certain index.
   130  func (s *Snapshot) Pieces(chunkIndex uint64) [][]Piece {
   131  	// Return the pieces. Since the snapshot is meant to be used read-only, we
   132  	// don't have to return a deep-copy here.
   133  	return s.staticChunks[chunkIndex].Pieces
   134  }
   135  
   136  // PieceSize returns the size of a single piece of the file.
   137  func (s *Snapshot) PieceSize() uint64 {
   138  	return s.staticPieceSize
   139  }
   140  
   141  // SiaPath returns the SiaPath of the file.
   142  func (s *Snapshot) SiaPath() skymodules.SiaPath {
   143  	return s.staticSiaPath
   144  }
   145  
   146  // Size returns the size of the file.
   147  func (s *Snapshot) Size() uint64 {
   148  	return uint64(s.staticFileSize)
   149  }
   150  
   151  // UID returns the UID of the file.
   152  func (s *Snapshot) UID() SiafileUID {
   153  	return s.staticUID
   154  }
   155  
   156  // readlockChunks reads all chunks from the siafile within the range [min;max].
   157  func (sf *SiaFile) readlockChunks(min, max int) ([]chunk, error) {
   158  	// Copy chunks.
   159  	chunks := make([]chunk, 0, sf.numChunks)
   160  	for chunkIndex := 0; chunkIndex < sf.numChunks; chunkIndex++ {
   161  		if chunkIndex < min || chunkIndex > max {
   162  			chunks = append(chunks, chunk{Index: chunkIndex})
   163  			continue
   164  		}
   165  		// Read chunk.
   166  		c, err := sf.chunk(chunkIndex)
   167  		if err != nil {
   168  			return nil, err
   169  		}
   170  		// Handle complete partial chunk.
   171  		chunks = append(chunks, c)
   172  	}
   173  	return chunks, nil
   174  }
   175  
   176  // readlockSnapshot creates a snapshot of the SiaFile.
   177  func (sf *SiaFile) readlockSnapshot(sp skymodules.SiaPath, chunks []chunk) (*Snapshot, error) {
   178  	mk := sf.staticMasterKey()
   179  
   180  	// Copy PubKeyTable.
   181  	pkt := make([]HostPublicKey, len(sf.pubKeyTable))
   182  	copy(pkt, sf.pubKeyTable)
   183  
   184  	// Figure out how much memory we need to allocate for the piece sets and
   185  	// pieces.
   186  	var numPieceSets, numPieces int
   187  	for _, chunk := range chunks {
   188  		numPieceSets += len(chunk.Pieces)
   189  		for pieceIndex := range chunk.Pieces {
   190  			numPieces += len(chunk.Pieces[pieceIndex])
   191  		}
   192  	}
   193  	// Allocate all the piece sets and pieces at once.
   194  	allPieceSets := make([][]Piece, numPieceSets)
   195  	allPieces := make([]Piece, numPieces)
   196  
   197  	// Copy chunks.
   198  	exportedChunks := make([]Chunk, 0, len(chunks))
   199  	for _, chunk := range chunks {
   200  		// Handle full chunk
   201  		pieces := allPieceSets[:len(chunk.Pieces)]
   202  		allPieceSets = allPieceSets[len(chunk.Pieces):]
   203  		for pieceIndex := range pieces {
   204  			pieces[pieceIndex] = allPieces[:len(chunk.Pieces[pieceIndex])]
   205  			allPieces = allPieces[len(chunk.Pieces[pieceIndex]):]
   206  			for i, piece := range chunk.Pieces[pieceIndex] {
   207  				pieces[pieceIndex][i] = Piece{
   208  					HostPubKey: sf.hostKey(piece.HostTableOffset).PublicKey,
   209  					MerkleRoot: piece.MerkleRoot,
   210  				}
   211  			}
   212  		}
   213  		exportedChunks = append(exportedChunks, Chunk{
   214  			Pieces: pieces,
   215  		})
   216  	}
   217  	// Get non-static metadata fields under lock.
   218  	fileSize := sf.staticMetadata.FileSize
   219  	mode := sf.staticMetadata.Mode
   220  	uid := sf.staticMetadata.UniqueID
   221  	localPath := sf.staticMetadata.LocalPath
   222  
   223  	return &Snapshot{
   224  		staticChunks:      exportedChunks,
   225  		staticFileSize:    fileSize,
   226  		staticPieceSize:   sf.staticMetadata.StaticPieceSize,
   227  		staticErasureCode: sf.staticMetadata.staticErasureCode,
   228  		staticMasterKey:   mk,
   229  		staticMode:        mode,
   230  		staticPubKeyTable: pkt,
   231  		staticSiaPath:     sp,
   232  		staticLocalPath:   localPath,
   233  		staticUID:         uid,
   234  	}, nil
   235  }
   236  
   237  // Snapshot creates a snapshot of the SiaFile.
   238  func (sf *SiaFile) Snapshot(sp skymodules.SiaPath) (*Snapshot, error) {
   239  	sf.mu.RLock()
   240  	defer sf.mu.RUnlock()
   241  
   242  	chunks, err := sf.readlockChunks(0, math.MaxInt32)
   243  	if err != nil {
   244  		return nil, err
   245  	}
   246  	return sf.readlockSnapshot(sp, chunks)
   247  }
   248  
   249  // SnapshotRange creates a snapshot of the Siafile over a specific range.
   250  func (sf *SiaFile) SnapshotRange(sp skymodules.SiaPath, offset, length uint64) (*Snapshot, error) {
   251  	sf.mu.RLock()
   252  	defer sf.mu.RUnlock()
   253  
   254  	minChunk := int(offset / sf.staticChunkSize())
   255  	maxChunk := int((offset + length) / sf.staticChunkSize())
   256  	maxChunkOffset := (offset + length) % sf.staticChunkSize()
   257  	if maxChunk > 0 && maxChunkOffset == 0 {
   258  		maxChunk--
   259  	}
   260  
   261  	chunks, err := sf.readlockChunks(minChunk, maxChunk)
   262  	if err != nil {
   263  		return nil, err
   264  	}
   265  	return sf.readlockSnapshot(sp, chunks)
   266  }
   267  
   268  // SnapshotFromReader reads a siafile from the specified reader and creates a
   269  // snapshot from it.
   270  func SnapshotFromReader(sp skymodules.SiaPath, r io.Reader) (*Snapshot, error) {
   271  	d, err := ioutil.ReadAll(r)
   272  	if err != nil {
   273  		return nil, err
   274  	}
   275  	sf, chunks, err := LoadSiaFileFromReaderWithChunks(bytes.NewReader(d), "", nil)
   276  	if err != nil {
   277  		return nil, err
   278  	}
   279  	return sf.readlockSnapshot(sp, chunks.chunks)
   280  }