gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/renter/siafile/metadata.go (about) 1 package siafile 2 3 import ( 4 "encoding/hex" 5 "os" 6 "path/filepath" 7 "time" 8 9 "gitlab.com/NebulousLabs/errors" 10 "gitlab.com/NebulousLabs/fastrand" 11 12 "gitlab.com/SiaPrime/SiaPrime/build" 13 "gitlab.com/SiaPrime/SiaPrime/crypto" 14 "gitlab.com/SiaPrime/SiaPrime/modules" 15 "gitlab.com/SiaPrime/SiaPrime/types" 16 "gitlab.com/SiaPrime/writeaheadlog" 17 ) 18 19 type ( 20 // PartialChunkInfo contains all the essential information about a partial 21 // chunk relevant to SiaFiles. A SiaFile with a partial chunk may contain 1 or 22 // 2 PartialChunkInfos since the partial chunk might be split between 2 23 // combined chunks. 24 PartialChunkInfo struct { 25 ID modules.CombinedChunkID `json:"id"` // ID of the combined chunk 26 Index uint64 `json:"index"` // Index of the combined chunk within partialsSiaFile 27 Offset uint64 `json:"offset"` // Offset of partial chunk within combined chunk 28 Length uint64 `json:"length"` // Length of partial chunk within combined chunk 29 Status uint8 `json:"status"` // Status of combined chunk 30 } 31 32 // SiafileUID is a unique identifier for siafile which is used to track 33 // siafiles even after renaming them. 34 SiafileUID string 35 36 // Metadata is the metadata of a SiaFile and is JSON encoded. 37 Metadata struct { 38 UniqueID SiafileUID `json:"uniqueid"` // unique identifier for file 39 40 StaticPagesPerChunk uint8 `json:"pagesperchunk"` // number of pages reserved for storing a chunk. 41 StaticVersion [16]byte `json:"version"` // version of the sia file format used 42 FileSize int64 `json:"filesize"` // total size of the file 43 StaticPieceSize uint64 `json:"piecesize"` // size of a single piece of the file 44 LocalPath string `json:"localpath"` // file to the local copy of the file used for repairing 45 46 // Fields for encryption 47 StaticMasterKey []byte `json:"masterkey"` // masterkey used to encrypt pieces 48 StaticMasterKeyType crypto.CipherType `json:"masterkeytype"` 49 StaticSharingKey []byte `json:"sharingkey"` // key used to encrypt shared pieces 50 StaticSharingKeyType crypto.CipherType `json:"sharingkeytype"` 51 52 // Fields for partial uploads 53 DisablePartialChunk bool `json:"disablepartialchunk"` // determines whether the file should be treated like legacy files 54 PartialChunks []PartialChunkInfo `json:"partialchunks"` // information about the partial chunk. 55 HasPartialChunk bool `json:"haspartialchunk"` // indicates whether this file is supposed to have a partial chunk or not 56 57 // The following fields are the usual unix timestamps of files. 58 ModTime time.Time `json:"modtime"` // time of last content modification 59 ChangeTime time.Time `json:"changetime"` // time of last metadata modification 60 AccessTime time.Time `json:"accesstime"` // time of last access 61 CreateTime time.Time `json:"createtime"` // time of file creation 62 63 // Cached fields. These fields are cached fields and are only meant to be used 64 // to create FileInfos for file related API endpoints. There is no guarantee 65 // that these fields are up-to-date. Neither in memory nor on disk. Updates to 66 // these fields aren't persisted immediately. Instead they will only be 67 // persisted whenever another method persists the metadata or when the SiaFile 68 // is closed. 69 // 70 // CachedRedundancy is the redundancy of the file on the network and is 71 // updated within the 'Redundancy' method which is periodically called by the 72 // repair code. 73 // 74 // CachedUserRedundancy is the redundancy of the file on the network as 75 // visible to the user and is updated within the 'Redundancy' method which is 76 // periodically called by the repair code. 77 // 78 // CachedHealth is the health of the file on the network and is also 79 // periodically updated by the health check loop whenever 'Health' is called. 80 // 81 // CachedStuckHealth is the health of the stuck chunks of the file. It is 82 // updated by the health check loop. CachedExpiration is the lowest height at 83 // which any of the file's contracts will expire. Also updated periodically by 84 // the health check loop whenever 'Health' is called. 85 // 86 // CachedUploadedBytes is the number of bytes of the file that have been 87 // uploaded to the network so far. Is updated every time a piece is added to 88 // the siafile. 89 // 90 // CachedUploadProgress is the upload progress of the file and is updated 91 // every time a piece is added to the siafile. 92 // 93 CachedRedundancy float64 `json:"cachedredundancy"` 94 CachedUserRedundancy float64 `json:"cacheduserredundancy"` 95 CachedHealth float64 `json:"cachedhealth"` 96 CachedStuckHealth float64 `json:"cachedstuckhealth"` 97 CachedExpiration types.BlockHeight `json:"cachedexpiration"` 98 CachedUploadedBytes uint64 `json:"cacheduploadedbytes"` 99 CachedUploadProgress float64 `json:"cacheduploadprogress"` 100 101 // Repair loop fields 102 // 103 // Health is the worst health of the file's unstuck chunks and 104 // represents the percent of redundancy missing 105 // 106 // LastHealthCheckTime is the timestamp of the last time the SiaFile's 107 // health was checked by Health() 108 // 109 // NumStuckChunks is the number of all the SiaFile's chunks that have 110 // been marked as stuck by the repair loop. This doesn't include a potential 111 // partial chunk at the end of the file though. Use 'numStuckChunks()' for 112 // that instead. 113 // 114 // Redundancy is the cached value of the last time the file's redundancy 115 // was checked 116 // 117 // StuckHealth is the worst health of any of the file's stuck chunks 118 // 119 Health float64 `json:"health"` 120 LastHealthCheckTime time.Time `json:"lasthealthchecktime"` 121 NumStuckChunks uint64 `json:"numstuckchunks"` 122 Redundancy float64 `json:"redundancy"` 123 StuckHealth float64 `json:"stuckhealth"` 124 125 // File ownership/permission fields. 126 Mode os.FileMode `json:"mode"` // unix filemode of the sia file - uint32 127 UserID int `json:"userid"` // id of the user who owns the file 128 GroupID int `json:"groupid"` // id of the group that owns the file 129 130 // The following fields are the offsets for data that is written to disk 131 // after the pubKeyTable. We reserve a generous amount of space for the 132 // table and extra fields, but we need to remember those offsets in case we 133 // need to resize later on. 134 // 135 // chunkOffset is the offset of the first chunk, forced to be a factor of 136 // 4096, default 4kib 137 // 138 // pubKeyTableOffset is the offset of the publicKeyTable within the 139 // file. 140 // 141 ChunkOffset int64 `json:"chunkoffset"` 142 PubKeyTableOffset int64 `json:"pubkeytableoffset"` 143 144 // erasure code settings. 145 // 146 // StaticErasureCodeType specifies the algorithm used for erasure coding 147 // chunks. Available types are: 148 // 0 - Invalid / Missing Code 149 // 1 - Reed Solomon Code 150 // 151 // erasureCodeParams specifies possible parameters for a certain 152 // StaticErasureCodeType. Currently params will be parsed as follows: 153 // Reed Solomon Code - 4 bytes dataPieces / 4 bytes parityPieces 154 // 155 StaticErasureCodeType [4]byte `json:"erasurecodetype"` 156 StaticErasureCodeParams [8]byte `json:"erasurecodeparams"` 157 staticErasureCode modules.ErasureCoder // not persisted, exists for convenience 158 } 159 160 // BubbledMetadata is the metadata of a siafile that gets bubbled 161 BubbledMetadata struct { 162 Health float64 163 LastHealthCheckTime time.Time 164 ModTime time.Time 165 NumStuckChunks uint64 166 Redundancy float64 167 Size uint64 168 StuckHealth float64 169 } 170 171 // CachedHealthMetadata is a healper struct that contains the siafile health 172 // metadata fields that are cached 173 CachedHealthMetadata struct { 174 Health float64 175 Redundancy float64 176 StuckHealth float64 177 } 178 ) 179 180 // AccessTime returns the AccessTime timestamp of the file. 181 func (sf *SiaFile) AccessTime() time.Time { 182 sf.mu.RLock() 183 defer sf.mu.RUnlock() 184 return sf.staticMetadata.AccessTime 185 } 186 187 // ChangeTime returns the ChangeTime timestamp of the file. 188 func (sf *SiaFile) ChangeTime() time.Time { 189 sf.mu.RLock() 190 defer sf.mu.RUnlock() 191 return sf.staticMetadata.ChangeTime 192 } 193 194 // PartialChunks returns the partial chunk infos of the siafile. 195 func (sf *SiaFile) PartialChunks() []PartialChunkInfo { 196 sf.mu.RLock() 197 defer sf.mu.RUnlock() 198 return sf.staticMetadata.PartialChunks 199 } 200 201 // CreateTime returns the CreateTime timestamp of the file. 202 func (sf *SiaFile) CreateTime() time.Time { 203 sf.mu.RLock() 204 defer sf.mu.RUnlock() 205 return sf.staticMetadata.CreateTime 206 } 207 208 // ChunkSize returns the size of a single chunk of the file. 209 func (sf *SiaFile) ChunkSize() uint64 { 210 return sf.staticChunkSize() 211 } 212 213 // HasPartialChunk returns whether this file is supposed to have a partial chunk 214 // or not. 215 func (sf *SiaFile) HasPartialChunk() bool { 216 sf.mu.RLock() 217 defer sf.mu.RUnlock() 218 return sf.staticMetadata.HasPartialChunk 219 } 220 221 // LastHealthCheckTime returns the LastHealthCheckTime timestamp of the file 222 func (sf *SiaFile) LastHealthCheckTime() time.Time { 223 sf.mu.RLock() 224 defer sf.mu.RUnlock() 225 return sf.staticMetadata.LastHealthCheckTime 226 } 227 228 // LocalPath returns the path of the local data of the file. 229 func (sf *SiaFile) LocalPath() string { 230 sf.mu.RLock() 231 defer sf.mu.RUnlock() 232 return sf.staticMetadata.LocalPath 233 } 234 235 // MasterKey returns the masterkey used to encrypt the file. 236 func (sf *SiaFile) MasterKey() crypto.CipherKey { 237 sk, err := crypto.NewSiaKey(sf.staticMetadata.StaticMasterKeyType, sf.staticMetadata.StaticMasterKey) 238 if err != nil { 239 // This should never happen since the constructor of the SiaFile takes 240 // a CipherKey as an argument which guarantees that it is already a 241 // valid key. 242 panic(errors.AddContext(err, "failed to create masterkey of siafile")) 243 } 244 return sk 245 } 246 247 // Metadata returns the SiaFile's metadata, resolving any fields related to 248 // partial chunks. 249 func (sf *SiaFile) Metadata() Metadata { 250 sf.mu.RLock() 251 defer sf.mu.RUnlock() 252 md := sf.staticMetadata 253 md.NumStuckChunks = sf.numStuckChunks() 254 return md 255 } 256 257 // Mode returns the FileMode of the SiaFile. 258 func (sf *SiaFile) Mode() os.FileMode { 259 sf.mu.RLock() 260 defer sf.mu.RUnlock() 261 return sf.staticMetadata.Mode 262 } 263 264 // ModTime returns the ModTime timestamp of the file. 265 func (sf *SiaFile) ModTime() time.Time { 266 sf.mu.RLock() 267 defer sf.mu.RUnlock() 268 return sf.staticMetadata.ModTime 269 } 270 271 // NumStuckChunks returns the Number of Stuck Chunks recorded in the file's 272 // metadata 273 func (sf *SiaFile) NumStuckChunks() uint64 { 274 sf.mu.RLock() 275 defer sf.mu.RUnlock() 276 return sf.numStuckChunks() 277 } 278 279 // PieceSize returns the size of a single piece of the file. 280 func (sf *SiaFile) PieceSize() uint64 { 281 return sf.staticMetadata.StaticPieceSize 282 } 283 284 // Rename changes the name of the file to a new one. To guarantee that renaming 285 // the file is atomic across all operating systems, we create a wal transaction 286 // that moves over all the chunks one-by-one and deletes the src file. 287 func (sf *SiaFile) Rename(newSiaFilePath string) error { 288 sf.mu.Lock() 289 defer sf.mu.Unlock() 290 if sf.deleted { 291 return errors.New("can't rename deleted siafile") 292 } 293 // Create path to renamed location. 294 dir, _ := filepath.Split(newSiaFilePath) 295 err := os.MkdirAll(dir, 0700) 296 if err != nil { 297 return err 298 } 299 // Create the delete update before changing the path to the new one. 300 updates := []writeaheadlog.Update{sf.createDeleteUpdate()} 301 // Load all the chunks. 302 chunks := make([]chunk, 0, sf.numChunks) 303 err = sf.iterateChunksReadonly(func(chunk chunk) error { 304 if _, ok := sf.isIncludedPartialChunk(uint64(chunk.Index)); ok { 305 return nil // Ignore partial chunk 306 } 307 chunks = append(chunks, chunk) 308 return nil 309 }) 310 if err != nil { 311 return err 312 } 313 // Rename file in memory. 314 sf.siaFilePath = newSiaFilePath 315 // Update the ChangeTime because the metadata changed. 316 sf.staticMetadata.ChangeTime = time.Now() 317 // Write the header to the new location. 318 headerUpdate, err := sf.saveHeaderUpdates() 319 if err != nil { 320 return err 321 } 322 updates = append(updates, headerUpdate...) 323 // Write the chunks to the new location. 324 for _, chunk := range chunks { 325 updates = append(updates, sf.saveChunkUpdate(chunk)) 326 } 327 // Apply updates. 328 return createAndApplyTransaction(sf.wal, updates...) 329 } 330 331 // SetMode sets the filemode of the sia file. 332 func (sf *SiaFile) SetMode(mode os.FileMode) error { 333 sf.mu.Lock() 334 defer sf.mu.Unlock() 335 sf.staticMetadata.Mode = mode 336 sf.staticMetadata.ChangeTime = time.Now() 337 338 // Save changes to metadata to disk. 339 updates, err := sf.saveMetadataUpdates() 340 if err != nil { 341 return err 342 } 343 return sf.createAndApplyTransaction(updates...) 344 } 345 346 // SetLastHealthCheckTime sets the LastHealthCheckTime in memory to the current 347 // time but does not update and write to disk. 348 // 349 // NOTE: This call should be used in conjunction with a method that saves the 350 // SiaFile metadata 351 func (sf *SiaFile) SetLastHealthCheckTime() { 352 sf.mu.Lock() 353 defer sf.mu.Unlock() 354 sf.staticMetadata.LastHealthCheckTime = time.Now() 355 } 356 357 // SetLocalPath changes the local path of the file which is used to repair 358 // the file from disk. 359 func (sf *SiaFile) SetLocalPath(path string) error { 360 sf.mu.Lock() 361 defer sf.mu.Unlock() 362 sf.staticMetadata.LocalPath = path 363 364 // Save changes to metadata to disk. 365 updates, err := sf.saveMetadataUpdates() 366 if err != nil { 367 return err 368 } 369 return sf.createAndApplyTransaction(updates...) 370 } 371 372 // Size returns the file's size. 373 func (sf *SiaFile) Size() uint64 { 374 sf.mu.RLock() 375 defer sf.mu.RUnlock() 376 return uint64(sf.staticMetadata.FileSize) 377 } 378 379 // UpdateUniqueID creates a new random uid for the SiaFile. 380 func (sf *SiaFile) UpdateUniqueID() { 381 sf.staticMetadata.UniqueID = uniqueID() 382 } 383 384 // UpdateAccessTime updates the AccessTime timestamp to the current time. 385 func (sf *SiaFile) UpdateAccessTime() error { 386 sf.mu.Lock() 387 defer sf.mu.Unlock() 388 sf.staticMetadata.AccessTime = time.Now() 389 390 // Save changes to metadata to disk. 391 updates, err := sf.saveMetadataUpdates() 392 if err != nil { 393 return err 394 } 395 return sf.createAndApplyTransaction(updates...) 396 } 397 398 // numStuckChunks returns the number of stuck chunks recorded in the file's 399 // metadata. 400 func (sf *SiaFile) numStuckChunks() uint64 { 401 numStuckChunks := sf.staticMetadata.NumStuckChunks 402 for _, cc := range sf.staticMetadata.PartialChunks { 403 stuck, err := sf.partialsSiaFile.StuckChunkByIndex(cc.Index) 404 if err != nil { 405 build.Critical("failed to get 'stuck' status of partial chunk") 406 } 407 if stuck { 408 numStuckChunks++ 409 } 410 } 411 return numStuckChunks 412 } 413 414 // staticChunkSize returns the size of a single chunk of the file. 415 func (sf *SiaFile) staticChunkSize() uint64 { 416 return sf.staticMetadata.StaticPieceSize * uint64(sf.staticMetadata.staticErasureCode.MinPieces()) 417 } 418 419 // uniqueID creates a random unique SiafileUID. 420 func uniqueID() SiafileUID { 421 return SiafileUID(hex.EncodeToString(fastrand.Bytes(20))) 422 }