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

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