gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/renter/skylink.go (about)

     1  package renter
     2  
     3  import (
     4  	"context"
     5  	"sync"
     6  	"time"
     7  
     8  	"gitlab.com/NebulousLabs/errors"
     9  	"gitlab.com/SkynetLabs/skyd/build"
    10  	"gitlab.com/SkynetLabs/skyd/skymodules"
    11  	"gitlab.com/SkynetLabs/skyd/skymodules/renter/filesystem"
    12  	"go.sia.tech/siad/crypto"
    13  )
    14  
    15  // skylinkManager manages skylink requests
    16  type skylinkManager struct {
    17  	// pruneTimeThreshold is the time threshold for pruning unpin requests.
    18  	pruneTimeThreshold time.Time
    19  
    20  	// unpinRequests are requests to unpin a skylink. It is a map of
    21  	// Skylink.String() to a time.Time by when the skylink should be fully
    22  	// unpinned. It is a map of strings instead of skylinks because the FileNode
    23  	// contain a slice of strings and not a slice of Skylinks.
    24  	unpinRequests map[string]time.Time
    25  	mu            sync.Mutex
    26  }
    27  
    28  // newSkylinkManager returns a newly initialized skylinkManager
    29  func newSkylinkManager() *skylinkManager {
    30  	return &skylinkManager{
    31  		unpinRequests: make(map[string]time.Time),
    32  	}
    33  }
    34  
    35  // callIsUnpinned returns whether or not a FileNode has be requested to be
    36  // unpinned.
    37  func (sm *skylinkManager) callIsUnpinned(fn *filesystem.FileNode) bool {
    38  	sm.mu.Lock()
    39  	defer sm.mu.Unlock()
    40  
    41  	// Check all the skylinks associated with the FileNode. If any have a pending
    42  	// unpin request then we return true.
    43  	for _, skylink := range fn.Metadata().Skylinks {
    44  		// Check if skylink has a pending unpin request.
    45  		if _, ok := sm.unpinRequests[skylink]; ok {
    46  			return true
    47  		}
    48  	}
    49  	return false
    50  }
    51  
    52  // callPruneUnpinRequests will prune the skylinkManager's old unpinRequests
    53  func (sm *skylinkManager) callPruneUnpinRequests() {
    54  	sm.mu.Lock()
    55  	defer sm.mu.Unlock()
    56  
    57  	// Iterate over the unpinRequests and check for requests that are in the
    58  	// past when compared to the pruneTime
    59  	for sl, t := range sm.unpinRequests {
    60  		// If the unpin request's time is in the past we can remove it from the
    61  		// list.
    62  		if t.Before(sm.pruneTimeThreshold) {
    63  			delete(sm.unpinRequests, sl)
    64  		}
    65  	}
    66  }
    67  
    68  // callUpdatePruneTimeThreshold updates the skylinkManager's pruneTimeThreshold
    69  func (sm *skylinkManager) callUpdatePruneTimeThreshold(t time.Time) {
    70  	sm.mu.Lock()
    71  	defer sm.mu.Unlock()
    72  	sm.pruneTimeThreshold = t
    73  }
    74  
    75  // managedAddUnpinRequest adds an unpin request to the skylinkManager
    76  func (sm *skylinkManager) managedAddUnpinRequest(skylink skymodules.Skylink) {
    77  	sm.mu.Lock()
    78  	defer sm.mu.Unlock()
    79  
    80  	// Grab the string
    81  	skylinkStr := skylink.String()
    82  	// Check if the request was already submitted. If so, return as to not reset
    83  	// the time.
    84  	_, ok := sm.unpinRequests[skylinkStr]
    85  	if ok {
    86  		// unpin request already submitted
    87  		return
    88  	}
    89  
    90  	// Add the unpin request and set the time in the future by twice the target
    91  	// health check interval.
    92  	sm.unpinRequests[skylinkStr] = time.Now().Add(TargetHealthCheckFrequency * 2)
    93  }
    94  
    95  // UnpinSkylink unpins a skylink from the renter by removing the underlying
    96  // siafile.
    97  //
    98  // NOTE: Since the SiaPath is not stored in the SkyfileLayout or the
    99  // SkyfileMetadata, we have to iterate over the entire filesystem to try and
   100  // find the filenode(s) that contain the skylink. We handle this via the bubble
   101  // code and the Renter's skylinkManager.
   102  func (r *Renter) UnpinSkylink(skylink skymodules.Skylink) error {
   103  	err := r.tg.Add()
   104  	if err != nil {
   105  		return err
   106  	}
   107  	defer r.tg.Done()
   108  
   109  	// Check if link is v2.
   110  	if skylink.IsSkylinkV2() {
   111  		return errors.New("can't unpin version 2 skylink")
   112  	}
   113  
   114  	// Check if skylink is blocked. If it is we can return early since the bubble
   115  	// code will handle deletion of blocked files.
   116  	err = r.managedHandleIsBlockedCheck(r.tg.StopCtx(), skylink, skymodules.SiaPath{})
   117  	if err != nil {
   118  		return err
   119  	}
   120  
   121  	// Check for dependency injection
   122  	if r.staticDeps.Disrupt("SkipUnpinRequest") {
   123  		return nil
   124  	}
   125  
   126  	// Add the unpin request
   127  	r.staticSkylinkManager.managedAddUnpinRequest(skylink)
   128  	return nil
   129  }
   130  
   131  // managedBlocklistHash returns the hash to be used in the blocklist
   132  func (r *Renter) managedBlocklistHash(ctx context.Context, sl skymodules.Skylink, ignoreCutoff bool) (crypto.Hash, error) {
   133  	// We want to return the hash of the merkleroot of the V1 skylink. This
   134  	// means for V2 skylinks we need to resolve it first.
   135  	switch {
   136  	case sl.IsSkylinkV1():
   137  		return crypto.HashObject(sl.MerkleRoot()), nil
   138  	case sl.IsSkylinkV2():
   139  		// NOTE: We don't want to check the blocklist while the V2 link
   140  		// is being resolved. This is so a link that is already on the
   141  		// block list could be added again in a large user generated
   142  		// list.
   143  		slv1, _, err := r.managedTryResolveSkylinkV2(ctx, sl, false, false)
   144  		if err != nil {
   145  			return crypto.Hash{}, errors.AddContext(err, "unable to resolve V2 skylink")
   146  		}
   147  		return crypto.HashObject(slv1.MerkleRoot()), nil
   148  	default:
   149  		build.Critical(ErrInvalidSkylinkVersion)
   150  	}
   151  	return crypto.Hash{}, ErrInvalidSkylinkVersion
   152  }
   153  
   154  // managedHandleIsBlockedCheck handles checking if a skylink is blocked. It will
   155  // also handle the deletion of a blocked file if necessary. This method can be
   156  // used for both V1 and V2 skylinks.
   157  func (r *Renter) managedHandleIsBlockedCheck(ctx context.Context, sl skymodules.Skylink, siaPath skymodules.SiaPath) error {
   158  	// Convert skylink to blocklist hash
   159  	hash, err := r.managedBlocklistHash(ctx, sl, false)
   160  	if err != nil {
   161  		return errors.AddContext(err, "unable to get blocklist hash")
   162  	}
   163  	// Check the Skynet Blocklist
   164  	shouldDelete, isBlocked := r.staticSkynetBlocklist.IsHashBlocked(hash)
   165  	if !isBlocked {
   166  		return nil
   167  	}
   168  	// Check if the skyfile should be deleted
   169  	if shouldDelete && !siaPath.IsEmpty() {
   170  		// Skylink is blocked and the data should be deleted, try and
   171  		// delete file
   172  		deleteErr := r.DeleteFile(siaPath)
   173  		// Don't bother logging an error if the file doesn't exist
   174  		if deleteErr != nil && !errors.Contains(deleteErr, filesystem.ErrNotExist) {
   175  			r.staticLog.Println("WARN: unable to delete blocked skyfile at", siaPath.String())
   176  		}
   177  	}
   178  	// Return that the Skylink is blocked
   179  	return ErrSkylinkBlocked
   180  }
   181  
   182  // managedParseBlocklistHashes parses the input hash string slice and returns
   183  // the appropriate hash to be added to the blocklist. A slice of invalid inputs
   184  // is returned for every input that could not get properly parsed.
   185  func (r *Renter) managedParseBlocklistHashes(ctx context.Context, hashStrs []string, isHash, ignoreCutoff bool) ([]crypto.Hash, []skymodules.SkynetBlocklistInvalidInput) {
   186  	var invalids []skymodules.SkynetBlocklistInvalidInput
   187  	var hashes []crypto.Hash
   188  	for _, paramStr := range hashStrs {
   189  		var hash crypto.Hash
   190  		// Convert Hash
   191  		if isHash {
   192  			err := hash.LoadString(paramStr)
   193  			if err != nil {
   194  				invalids = append(invalids, skymodules.SkynetBlocklistInvalidInput{
   195  					Input: paramStr,
   196  					Error: err.Error(),
   197  				})
   198  				continue
   199  			}
   200  		} else {
   201  			// Convert Skylink
   202  			var skylink skymodules.Skylink
   203  			err := skylink.LoadString(paramStr)
   204  			if err != nil {
   205  				invalids = append(invalids, skymodules.SkynetBlocklistInvalidInput{
   206  					Input: paramStr,
   207  					Error: err.Error(),
   208  				})
   209  				continue
   210  			}
   211  			hash, err = r.managedBlocklistHash(ctx, skylink, ignoreCutoff)
   212  			if err != nil {
   213  				invalids = append(invalids, skymodules.SkynetBlocklistInvalidInput{
   214  					Input: paramStr,
   215  					Error: err.Error(),
   216  				})
   217  				continue
   218  			}
   219  		}
   220  		hashes = append(hashes, hash)
   221  	}
   222  	return hashes, invalids
   223  }