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 }