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  }