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 }