github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/vfs/vfscache/cache.go (about)

     1  // Package vfscache deals with caching of files locally for the VFS layer
     2  package vfscache
     3  
     4  import (
     5  	"context"
     6  	"errors"
     7  	"fmt"
     8  	"os"
     9  	"path"
    10  	"path/filepath"
    11  	"runtime"
    12  	"sort"
    13  	"strings"
    14  	"sync"
    15  	"time"
    16  
    17  	"github.com/rclone/rclone/fs"
    18  	fscache "github.com/rclone/rclone/fs/cache"
    19  	"github.com/rclone/rclone/fs/config"
    20  	"github.com/rclone/rclone/fs/fserrors"
    21  	"github.com/rclone/rclone/fs/hash"
    22  	"github.com/rclone/rclone/fs/operations"
    23  	"github.com/rclone/rclone/fs/rc"
    24  	"github.com/rclone/rclone/lib/diskusage"
    25  	"github.com/rclone/rclone/lib/encoder"
    26  	"github.com/rclone/rclone/lib/file"
    27  	"github.com/rclone/rclone/lib/systemd"
    28  	"github.com/rclone/rclone/vfs/vfscache/writeback"
    29  	"github.com/rclone/rclone/vfs/vfscommon"
    30  )
    31  
    32  // NB as Cache and Item are tightly linked it is necessary to have a
    33  // total lock ordering between them. So Cache.mu must always be
    34  // taken before Item.mu to avoid deadlocks.
    35  //
    36  // Cache may call into Item but care is needed if Item calls Cache
    37  
    38  // FIXME need to purge cache nodes which don't have backing files and aren't dirty
    39  // these may get created by the VFS layer or may be orphans from reload()
    40  
    41  // Cache opened files
    42  type Cache struct {
    43  	// read only - no locking needed to read these
    44  	fremote    fs.Fs                // fs for the remote we are caching
    45  	fcache     fs.Fs                // fs for the cache directory
    46  	fcacheMeta fs.Fs                // fs for the cache metadata directory
    47  	opt        *vfscommon.Options   // vfs Options
    48  	root       string               // root of the cache directory
    49  	metaRoot   string               // root of the cache metadata directory
    50  	hashType   hash.Type            // hash to use locally and remotely
    51  	hashOption *fs.HashesOption     // corresponding OpenOption
    52  	writeback  *writeback.WriteBack // holds Items for writeback
    53  	avFn       AddVirtualFn         // if set, can be called to add dir entries
    54  
    55  	mu            sync.Mutex       // protects the following variables
    56  	cond          sync.Cond        // cond lock for synchronous cache cleaning
    57  	item          map[string]*Item // files/directories in the cache
    58  	errItems      map[string]error // items in error state
    59  	used          int64            // total size of files in the cache
    60  	outOfSpace    bool             // out of space
    61  	cleanerKicked bool             // some thread kicked the cleaner upon out of space
    62  	kickerMu      sync.Mutex       // mutex for cleanerKicked
    63  	kick          chan struct{}    // channel for kicking clear to start
    64  
    65  }
    66  
    67  // AddVirtualFn if registered by the WithAddVirtual method, can be
    68  // called to register the object or directory at remote as a virtual
    69  // entry in directory listings.
    70  //
    71  // This is used when reloading the Cache and uploading items need to
    72  // go into the directory tree.
    73  type AddVirtualFn func(remote string, size int64, isDir bool) error
    74  
    75  // New creates a new cache hierarchy for fremote
    76  //
    77  // This starts background goroutines which can be cancelled with the
    78  // context passed in.
    79  func New(ctx context.Context, fremote fs.Fs, opt *vfscommon.Options, avFn AddVirtualFn) (*Cache, error) {
    80  	// Get cache root path.
    81  	// We need it in two variants: OS path as an absolute path with UNC prefix,
    82  	// OS-specific path separators, and encoded with OS-specific encoder. Standard path
    83  	// without UNC prefix, with slash path separators, and standard (internal) encoding.
    84  	// Care must be taken when creating OS paths so that the ':' separator following a
    85  	// drive letter is not encoded (e.g. into unicode fullwidth colon).
    86  	var err error
    87  	parentOSPath := config.GetCacheDir() // Assuming string contains a local absolute path in OS encoding
    88  	fs.Debugf(nil, "vfs cache: root is %q", parentOSPath)
    89  	parentPath := fromOSPath(parentOSPath)
    90  
    91  	// Get a relative cache path representing the remote.
    92  	relativeDirPath := fremote.Root() // This is a remote path in standard encoding
    93  	if runtime.GOOS == "windows" {
    94  		if strings.HasPrefix(relativeDirPath, `//?/`) {
    95  			relativeDirPath = relativeDirPath[2:] // Trim off the "//" for the result to be a valid when appending to another path
    96  		}
    97  	}
    98  	relativeDirPath = fremote.Name() + "/" + relativeDirPath
    99  	relativeDirOSPath := toOSPath(relativeDirPath)
   100  
   101  	// Create cache root dirs
   102  	var dataOSPath, metaOSPath string
   103  	if dataOSPath, metaOSPath, err = createRootDirs(parentOSPath, relativeDirOSPath); err != nil {
   104  		return nil, err
   105  	}
   106  	fs.Debugf(nil, "vfs cache: data root is %q", dataOSPath)
   107  	fs.Debugf(nil, "vfs cache: metadata root is %q", metaOSPath)
   108  
   109  	// Get (create) cache backends
   110  	var fdata, fmeta fs.Fs
   111  	if fdata, fmeta, err = getBackends(ctx, parentPath, relativeDirPath); err != nil {
   112  		return nil, err
   113  	}
   114  	hashType, hashOption := operations.CommonHash(ctx, fdata, fremote)
   115  
   116  	// Create the cache object
   117  	c := &Cache{
   118  		fremote:    fremote,
   119  		fcache:     fdata,
   120  		fcacheMeta: fmeta,
   121  		opt:        opt,
   122  		root:       dataOSPath,
   123  		metaRoot:   metaOSPath,
   124  		item:       make(map[string]*Item),
   125  		errItems:   make(map[string]error),
   126  		hashType:   hashType,
   127  		hashOption: hashOption,
   128  		writeback:  writeback.New(ctx, opt),
   129  		avFn:       avFn,
   130  	}
   131  
   132  	// load in the cache and metadata off disk
   133  	err = c.reload(ctx)
   134  	if err != nil {
   135  		return nil, fmt.Errorf("failed to load cache: %w", err)
   136  	}
   137  
   138  	// Remove any empty directories
   139  	c.purgeEmptyDirs("", true)
   140  
   141  	// Create a channel for cleaner to be kicked upon out of space con
   142  	c.kick = make(chan struct{}, 1)
   143  	c.cond = sync.Cond{L: &c.mu}
   144  
   145  	go c.cleaner(ctx)
   146  
   147  	return c, nil
   148  }
   149  
   150  // Stats returns info about the Cache
   151  func (c *Cache) Stats() (out rc.Params) {
   152  	out = make(rc.Params)
   153  	// read only - no locking needed to read these
   154  	out["path"] = c.root
   155  	out["pathMeta"] = c.metaRoot
   156  	out["hashType"] = c.hashType
   157  
   158  	uploadsInProgress, uploadsQueued := c.writeback.Stats()
   159  	out["uploadsInProgress"] = uploadsInProgress
   160  	out["uploadsQueued"] = uploadsQueued
   161  
   162  	c.mu.Lock()
   163  	defer c.mu.Unlock()
   164  
   165  	out["files"] = len(c.item)
   166  	out["erroredFiles"] = len(c.errItems)
   167  	out["bytesUsed"] = c.used
   168  	out["outOfSpace"] = c.outOfSpace
   169  
   170  	return out
   171  }
   172  
   173  // createDir creates a directory path, along with any necessary parents
   174  func createDir(dir string) error {
   175  	return file.MkdirAll(dir, 0700)
   176  }
   177  
   178  // createRootDir creates a single cache root directory
   179  func createRootDir(parentOSPath string, name string, relativeDirOSPath string) (path string, err error) {
   180  	path = file.UNCPath(filepath.Join(parentOSPath, name, relativeDirOSPath))
   181  	err = createDir(path)
   182  	return
   183  }
   184  
   185  // createRootDirs creates all cache root directories
   186  func createRootDirs(parentOSPath string, relativeDirOSPath string) (dataOSPath string, metaOSPath string, err error) {
   187  	if dataOSPath, err = createRootDir(parentOSPath, "vfs", relativeDirOSPath); err != nil {
   188  		err = fmt.Errorf("failed to create data cache directory: %w", err)
   189  	} else if metaOSPath, err = createRootDir(parentOSPath, "vfsMeta", relativeDirOSPath); err != nil {
   190  		err = fmt.Errorf("failed to create metadata cache directory: %w", err)
   191  	}
   192  	return
   193  }
   194  
   195  // createItemDir creates the directory for named item in all cache roots
   196  //
   197  // Returns an os path for the data cache file.
   198  func (c *Cache) createItemDir(name string) (string, error) {
   199  	parent := vfscommon.FindParent(name)
   200  	parentPath := c.toOSPath(parent)
   201  	err := createDir(parentPath)
   202  	if err != nil {
   203  		return "", fmt.Errorf("failed to create data cache item directory: %w", err)
   204  	}
   205  	parentPathMeta := c.toOSPathMeta(parent)
   206  	err = createDir(parentPathMeta)
   207  	if err != nil {
   208  		return "", fmt.Errorf("failed to create metadata cache item directory: %w", err)
   209  	}
   210  	return c.toOSPath(name), nil
   211  }
   212  
   213  // getBackend gets a backend for a cache root dir
   214  func getBackend(ctx context.Context, parentPath string, name string, relativeDirPath string) (fs.Fs, error) {
   215  	path := fmt.Sprintf("%s/%s/%s", parentPath, name, relativeDirPath)
   216  	return fscache.Get(ctx, path)
   217  }
   218  
   219  // getBackends gets backends for all cache root dirs
   220  func getBackends(ctx context.Context, parentPath string, relativeDirPath string) (fdata fs.Fs, fmeta fs.Fs, err error) {
   221  	if fdata, err = getBackend(ctx, parentPath, "vfs", relativeDirPath); err != nil {
   222  		err = fmt.Errorf("failed to get data cache backend: %w", err)
   223  	} else if fmeta, err = getBackend(ctx, parentPath, "vfsMeta", relativeDirPath); err != nil {
   224  		err = fmt.Errorf("failed to get metadata cache backend: %w", err)
   225  	}
   226  	return
   227  }
   228  
   229  // clean returns the cleaned version of name for use in the index map
   230  //
   231  // name should be a remote path not an osPath
   232  func clean(name string) string {
   233  	name = strings.Trim(name, "/")
   234  	name = path.Clean(name)
   235  	if name == "." || name == "/" {
   236  		name = ""
   237  	}
   238  	return name
   239  }
   240  
   241  // fromOSPath turns a OS path into a standard/remote path
   242  func fromOSPath(osPath string) string {
   243  	return encoder.OS.ToStandardPath(filepath.ToSlash(osPath))
   244  }
   245  
   246  // toOSPath turns a standard/remote path into an OS path
   247  func toOSPath(standardPath string) string {
   248  	return filepath.FromSlash(encoder.OS.FromStandardPath(standardPath))
   249  }
   250  
   251  // toOSPath turns a remote relative name into an OS path in the cache
   252  func (c *Cache) toOSPath(name string) string {
   253  	return filepath.Join(c.root, toOSPath(name))
   254  }
   255  
   256  // toOSPathMeta turns a remote relative name into an OS path in the
   257  // cache for the metadata
   258  func (c *Cache) toOSPathMeta(name string) string {
   259  	return filepath.Join(c.metaRoot, toOSPath(name))
   260  }
   261  
   262  // _get gets name from the cache or creates a new one
   263  //
   264  // It returns the item and found as to whether this item was found in
   265  // the cache (or just created).
   266  //
   267  // name should be a remote path not an osPath
   268  //
   269  // must be called with mu held
   270  func (c *Cache) _get(name string) (item *Item, found bool) {
   271  	item = c.item[name]
   272  	found = item != nil
   273  	if !found {
   274  		item = newItem(c, name)
   275  		c.item[name] = item
   276  	}
   277  	return item, found
   278  }
   279  
   280  // put puts item under name in the cache
   281  //
   282  // It returns an old item if there was one or nil if not.
   283  //
   284  // name should be a remote path not an osPath
   285  func (c *Cache) put(name string, item *Item) (oldItem *Item) {
   286  	name = clean(name)
   287  	c.mu.Lock()
   288  	oldItem = c.item[name]
   289  	if oldItem != item {
   290  		c.item[name] = item
   291  	} else {
   292  		oldItem = nil
   293  	}
   294  	c.mu.Unlock()
   295  	return oldItem
   296  }
   297  
   298  // InUse returns whether the name is in use in the cache
   299  //
   300  // name should be a remote path not an osPath
   301  func (c *Cache) InUse(name string) bool {
   302  	name = clean(name)
   303  	c.mu.Lock()
   304  	item := c.item[name]
   305  	c.mu.Unlock()
   306  	if item == nil {
   307  		return false
   308  	}
   309  	return item.inUse()
   310  }
   311  
   312  // DirtyItem returns the Item if it exists in the cache **and** is
   313  // dirty otherwise it returns nil.
   314  //
   315  // name should be a remote path not an osPath
   316  func (c *Cache) DirtyItem(name string) (item *Item) {
   317  	name = clean(name)
   318  	c.mu.Lock()
   319  	defer c.mu.Unlock()
   320  	item = c.item[name]
   321  	if item != nil && !item.IsDirty() {
   322  		item = nil
   323  	}
   324  	return item
   325  }
   326  
   327  // get gets a file name from the cache or creates a new one
   328  //
   329  // It returns the item and found as to whether this item was found in
   330  // the cache (or just created).
   331  //
   332  // name should be a remote path not an osPath
   333  func (c *Cache) get(name string) (item *Item, found bool) {
   334  	name = clean(name)
   335  	c.mu.Lock()
   336  	item, found = c._get(name)
   337  	c.mu.Unlock()
   338  	return item, found
   339  }
   340  
   341  // Item gets a cache item for name
   342  //
   343  // To use it item.Open will need to be called.
   344  //
   345  // name should be a remote path not an osPath
   346  func (c *Cache) Item(name string) (item *Item) {
   347  	item, _ = c.get(name)
   348  	return item
   349  }
   350  
   351  // Exists checks to see if the file exists in the cache or not.
   352  //
   353  // This is done by bringing the item into the cache which will
   354  // validate the backing file and metadata and then asking if the Item
   355  // exists or not.
   356  func (c *Cache) Exists(name string) bool {
   357  	item, _ := c.get(name)
   358  	return item.Exists()
   359  }
   360  
   361  // rename with os.Rename and more checking
   362  func rename(osOldPath, osNewPath string) error {
   363  	sfi, err := os.Stat(osOldPath)
   364  	if err != nil {
   365  		// Just do nothing if the source does not exist
   366  		if os.IsNotExist(err) {
   367  			return nil
   368  		}
   369  		return fmt.Errorf("failed to stat source: %s: %w", osOldPath, err)
   370  	}
   371  	if !sfi.Mode().IsRegular() {
   372  		// cannot copy non-regular files (e.g., directories, symlinks, devices, etc.)
   373  		return fmt.Errorf("non-regular source file: %s (%q)", sfi.Name(), sfi.Mode().String())
   374  	}
   375  	dfi, err := os.Stat(osNewPath)
   376  	if err != nil {
   377  		if !os.IsNotExist(err) {
   378  			return fmt.Errorf("failed to stat destination: %s: %w", osNewPath, err)
   379  		}
   380  		parent := vfscommon.OSFindParent(osNewPath)
   381  		err = createDir(parent)
   382  		if err != nil {
   383  			return fmt.Errorf("failed to create parent dir: %s: %w", parent, err)
   384  		}
   385  	} else {
   386  		if !(dfi.Mode().IsRegular()) {
   387  			return fmt.Errorf("non-regular destination file: %s (%q)", dfi.Name(), dfi.Mode().String())
   388  		}
   389  		if os.SameFile(sfi, dfi) {
   390  			return nil
   391  		}
   392  	}
   393  	if err = os.Rename(osOldPath, osNewPath); err != nil {
   394  		return fmt.Errorf("failed to rename in cache: %s to %s: %w", osOldPath, osNewPath, err)
   395  	}
   396  	return nil
   397  }
   398  
   399  // Rename the item in cache
   400  func (c *Cache) Rename(name string, newName string, newObj fs.Object) (err error) {
   401  	item, _ := c.get(name)
   402  	err = item.rename(name, newName, newObj)
   403  	if err != nil {
   404  		return err
   405  	}
   406  
   407  	// Move the item in the cache
   408  	c.mu.Lock()
   409  	if item, ok := c.item[name]; ok {
   410  		c.item[newName] = item
   411  		delete(c.item, name)
   412  	}
   413  	c.mu.Unlock()
   414  
   415  	fs.Infof(name, "vfs cache: renamed in cache to %q", newName)
   416  	return nil
   417  }
   418  
   419  // DirExists checks to see if the directory exists in the cache or not.
   420  func (c *Cache) DirExists(name string) bool {
   421  	path := c.toOSPath(name)
   422  	_, err := os.Stat(path)
   423  	return err == nil
   424  }
   425  
   426  // DirRename the dir in cache
   427  func (c *Cache) DirRename(oldDirName string, newDirName string) (err error) {
   428  	// Make sure names are / suffixed for reading keys out of c.item
   429  	if !strings.HasSuffix(oldDirName, "/") {
   430  		oldDirName += "/"
   431  	}
   432  	if !strings.HasSuffix(newDirName, "/") {
   433  		newDirName += "/"
   434  	}
   435  
   436  	// Find all items to rename
   437  	var renames []string
   438  	c.mu.Lock()
   439  	for itemName := range c.item {
   440  		if strings.HasPrefix(itemName, oldDirName) {
   441  			renames = append(renames, itemName)
   442  		}
   443  	}
   444  	c.mu.Unlock()
   445  
   446  	// Rename the items
   447  	for _, itemName := range renames {
   448  		newPath := newDirName + itemName[len(oldDirName):]
   449  		renameErr := c.Rename(itemName, newPath, nil)
   450  		if renameErr != nil {
   451  			err = renameErr
   452  		}
   453  	}
   454  
   455  	// Old path should be empty now so remove it
   456  	c.purgeEmptyDirs(oldDirName[:len(oldDirName)-1], false)
   457  
   458  	fs.Infof(oldDirName, "vfs cache: renamed dir in cache to %q", newDirName)
   459  	return err
   460  }
   461  
   462  // Remove should be called if name is deleted
   463  //
   464  // This returns true if the file was in the transfer queue so may not
   465  // have completely uploaded yet.
   466  func (c *Cache) Remove(name string) (wasWriting bool) {
   467  	name = clean(name)
   468  	c.mu.Lock()
   469  	item := c.item[name]
   470  	if item != nil {
   471  		delete(c.item, name)
   472  	}
   473  	c.mu.Unlock()
   474  	if item == nil {
   475  		return false
   476  	}
   477  	return item.remove("file deleted")
   478  }
   479  
   480  // SetModTime should be called to set the modification time of the cache file
   481  func (c *Cache) SetModTime(name string, modTime time.Time) {
   482  	item, _ := c.get(name)
   483  	item.setModTime(modTime)
   484  }
   485  
   486  // CleanUp empties the cache of everything
   487  func (c *Cache) CleanUp() error {
   488  	err1 := os.RemoveAll(c.root)
   489  	err2 := os.RemoveAll(c.metaRoot)
   490  	if err1 != nil {
   491  		return err1
   492  	}
   493  	return err2
   494  }
   495  
   496  // walk walks the cache calling the function
   497  func (c *Cache) walk(dir string, fn func(osPath string, fi os.FileInfo, name string) error) error {
   498  	return filepath.Walk(dir, func(osPath string, fi os.FileInfo, err error) error {
   499  		if err != nil {
   500  			return err
   501  		}
   502  		// Find path relative to the cache root
   503  		name, err := filepath.Rel(dir, osPath)
   504  		if err != nil {
   505  			return fmt.Errorf("filepath.Rel failed in walk: %w", err)
   506  		}
   507  		if name == "." {
   508  			name = ""
   509  		}
   510  		// And convert into slashes
   511  		name = filepath.ToSlash(name)
   512  
   513  		return fn(osPath, fi, name)
   514  	})
   515  }
   516  
   517  // reload walks the cache loading metadata files
   518  //
   519  // It iterates the files first then metadata trees. It doesn't expect
   520  // to find any new items iterating the metadata but it will clear up
   521  // orphan files.
   522  func (c *Cache) reload(ctx context.Context) error {
   523  	for _, dir := range []string{c.root, c.metaRoot} {
   524  		err := c.walk(dir, func(osPath string, fi os.FileInfo, name string) error {
   525  			if fi.IsDir() {
   526  				return nil
   527  			}
   528  			item, found := c.get(name)
   529  			if !found {
   530  				err := item.reload(ctx)
   531  				if err != nil {
   532  					fs.Errorf(name, "vfs cache: failed to reload item: %v", err)
   533  				}
   534  			}
   535  			return nil
   536  		})
   537  		if err != nil {
   538  			return fmt.Errorf("failed to walk cache %q: %w", dir, err)
   539  		}
   540  	}
   541  	return nil
   542  }
   543  
   544  // KickCleaner kicks cache cleaner upon out of space situation
   545  func (c *Cache) KickCleaner() {
   546  	/* Use a separate kicker mutex for the kick to go through without waiting for the
   547  	   cache mutex to avoid letting a thread kick again after the clearer just
   548  	   finished cleaning and unlock the cache mutex. */
   549  	fs.Debugf(nil, "vfs cache: at the beginning of KickCleaner")
   550  	c.kickerMu.Lock()
   551  	if !c.cleanerKicked {
   552  		c.cleanerKicked = true
   553  		fs.Debugf(nil, "vfs cache: in KickCleaner, ready to lock cache mutex")
   554  		c.mu.Lock()
   555  		c.outOfSpace = true
   556  		fs.Logf(nil, "vfs cache: in KickCleaner, ready to kick cleaner")
   557  		c.kick <- struct{}{}
   558  		c.mu.Unlock()
   559  	}
   560  	c.kickerMu.Unlock()
   561  
   562  	c.mu.Lock()
   563  	for c.outOfSpace {
   564  		fs.Debugf(nil, "vfs cache: in KickCleaner, looping on c.outOfSpace")
   565  		c.cond.Wait()
   566  	}
   567  	fs.Debugf(nil, "vfs cache: in KickCleaner, leaving c.outOfSpace loop")
   568  	c.mu.Unlock()
   569  }
   570  
   571  // removeNotInUse removes items not in use with a possible maxAge cutoff
   572  // called with cache mutex locked and up-to-date c.used (as we update it directly here)
   573  func (c *Cache) removeNotInUse(item *Item, maxAge time.Duration, emptyOnly bool) {
   574  	removed, spaceFreed := item.RemoveNotInUse(maxAge, emptyOnly)
   575  	// The item space might be freed even if we get an error after the cache file is removed
   576  	// The item will not be removed or reset the cache data is dirty (DataDirty)
   577  	c.used -= spaceFreed
   578  	if removed {
   579  		fs.Infof(nil, "vfs cache RemoveNotInUse (maxAge=%d, emptyOnly=%v): item %s was removed, freed %d bytes", maxAge, emptyOnly, item.GetName(), spaceFreed)
   580  		// Remove the entry
   581  		delete(c.item, item.name)
   582  	} else {
   583  		fs.Debugf(nil, "vfs cache RemoveNotInUse (maxAge=%d, emptyOnly=%v): item %s not removed, freed %d bytes", maxAge, emptyOnly, item.GetName(), spaceFreed)
   584  	}
   585  }
   586  
   587  // Retry failed resets during purgeClean()
   588  func (c *Cache) retryFailedResets() {
   589  	// Some items may have failed to reset because there was not enough space
   590  	// for saving the cache item's metadata.  Redo the Reset()'s here now that
   591  	// we may have some available space.
   592  	if len(c.errItems) != 0 {
   593  		fs.Debugf(nil, "vfs cache reset: before redoing reset errItems = %v", c.errItems)
   594  		for itemName := range c.errItems {
   595  			if retryItem, ok := c.item[itemName]; ok {
   596  				_, _, err := retryItem.Reset()
   597  				if err == nil || !fserrors.IsErrNoSpace(err) {
   598  					// TODO: not trying to handle non-ENOSPC errors yet
   599  					delete(c.errItems, itemName)
   600  				}
   601  			} else {
   602  				// The retry item was deleted because it was closed.
   603  				// No need to redo the failed reset now.
   604  				delete(c.errItems, itemName)
   605  			}
   606  		}
   607  		fs.Debugf(nil, "vfs cache reset: after redoing reset errItems = %v", c.errItems)
   608  	}
   609  }
   610  
   611  // Remove cache files that are not dirty until the quota is satisfied
   612  func (c *Cache) purgeClean() {
   613  	c.mu.Lock()
   614  	defer c.mu.Unlock()
   615  
   616  	if c.quotasOK() {
   617  		return
   618  	}
   619  
   620  	var items Items
   621  
   622  	// Make a slice of clean cache files
   623  	for _, item := range c.item {
   624  		if !item.IsDirty() {
   625  			items = append(items, item)
   626  		}
   627  	}
   628  
   629  	sort.Sort(items)
   630  
   631  	// Reset items until the quota is OK
   632  	for _, item := range items {
   633  		if c.quotasOK() {
   634  			break
   635  		}
   636  		resetResult, spaceFreed, err := item.Reset()
   637  		// The item space might be freed even if we get an error after the cache file is removed
   638  		// The item will not be removed or reset if the cache data is dirty (DataDirty)
   639  		c.used -= spaceFreed
   640  		fs.Infof(nil, "vfs cache purgeClean item.Reset %s: %s, freed %d bytes", item.GetName(), resetResult.String(), spaceFreed)
   641  		if resetResult == RemovedNotInUse {
   642  			delete(c.item, item.name)
   643  		}
   644  		if err != nil {
   645  			fs.Errorf(nil, "vfs cache purgeClean item.Reset %s reset failed, err = %v, freed %d bytes", item.GetName(), err, spaceFreed)
   646  			c.errItems[item.name] = err
   647  		}
   648  	}
   649  
   650  	// Reset outOfSpace without checking whether we have reduced cache space below the quota.
   651  	// This allows some files to reduce their pendingAccesses count to allow them to be reset
   652  	// in the next iteration of the purge cleaner loop.
   653  
   654  	c.outOfSpace = false
   655  	c.cond.Broadcast()
   656  }
   657  
   658  // purgeOld gets rid of any files that are over age
   659  func (c *Cache) purgeOld(maxAge time.Duration) {
   660  	c.mu.Lock()
   661  	defer c.mu.Unlock()
   662  	// cutoff := time.Now().Add(-maxAge)
   663  	for _, item := range c.item {
   664  		c.removeNotInUse(item, maxAge, false)
   665  	}
   666  	if c.quotasOK() {
   667  		c.outOfSpace = false
   668  		c.cond.Broadcast()
   669  	}
   670  }
   671  
   672  // Purge any empty directories
   673  func (c *Cache) purgeEmptyDirs(dir string, leaveRoot bool) {
   674  	ctx := context.Background()
   675  	err := operations.Rmdirs(ctx, c.fcache, dir, leaveRoot)
   676  	if err != nil {
   677  		fs.Errorf(c.fcache, "vfs cache: failed to remove empty directories from cache path %q: %v", dir, err)
   678  	}
   679  	err = operations.Rmdirs(ctx, c.fcacheMeta, dir, leaveRoot)
   680  	if err != nil {
   681  		fs.Errorf(c.fcache, "vfs cache: failed to remove empty directories from metadata cache path %q: %v", dir, err)
   682  	}
   683  }
   684  
   685  // updateUsed updates c.used so it is accurate
   686  func (c *Cache) updateUsed() (used int64) {
   687  	c.mu.Lock()
   688  	defer c.mu.Unlock()
   689  
   690  	newUsed := int64(0)
   691  	for _, item := range c.item {
   692  		newUsed += item.getDiskSize()
   693  	}
   694  	c.used = newUsed
   695  	return newUsed
   696  }
   697  
   698  // Check the available space for a disk is in limits.
   699  func (c *Cache) minFreeSpaceQuotaOK() bool {
   700  	if c.opt.CacheMinFreeSpace <= 0 {
   701  		return true
   702  	}
   703  	du, err := diskusage.New(config.GetCacheDir())
   704  	if err == diskusage.ErrUnsupported {
   705  		return true
   706  	}
   707  	if err != nil {
   708  		fs.Errorf(nil, "disk usage returned error: %v", err)
   709  		return true
   710  	}
   711  	return du.Available >= uint64(c.opt.CacheMinFreeSpace)
   712  }
   713  
   714  // Check the available quota for a disk is in limits.
   715  //
   716  // must be called with mu held.
   717  func (c *Cache) maxSizeQuotaOK() bool {
   718  	if c.opt.CacheMaxSize <= 0 {
   719  		return true
   720  	}
   721  	return c.used <= int64(c.opt.CacheMaxSize)
   722  }
   723  
   724  // Check the available quotas for a disk is in limits.
   725  //
   726  // must be called with mu held.
   727  func (c *Cache) quotasOK() bool {
   728  	return c.maxSizeQuotaOK() && c.minFreeSpaceQuotaOK()
   729  }
   730  
   731  // Return true if any quotas set
   732  func (c *Cache) haveQuotas() bool {
   733  	return c.opt.CacheMaxSize > 0 || c.opt.CacheMinFreeSpace > 0
   734  }
   735  
   736  // Remove clean cache files that are not open until the total space
   737  // is reduced below quota starting from the oldest first
   738  func (c *Cache) purgeOverQuota() {
   739  	c.updateUsed()
   740  
   741  	c.mu.Lock()
   742  	defer c.mu.Unlock()
   743  
   744  	if c.quotasOK() {
   745  		return
   746  	}
   747  
   748  	var items Items
   749  
   750  	// Make a slice of unused files
   751  	for _, item := range c.item {
   752  		if !item.inUse() {
   753  			items = append(items, item)
   754  		}
   755  	}
   756  
   757  	sort.Sort(items)
   758  
   759  	// Remove items until the quota is OK
   760  	for _, item := range items {
   761  		c.removeNotInUse(item, 0, c.quotasOK())
   762  	}
   763  	if c.quotasOK() {
   764  		c.outOfSpace = false
   765  		c.cond.Broadcast()
   766  	}
   767  }
   768  
   769  // clean empties the cache of stuff if it can
   770  func (c *Cache) clean(kicked bool) {
   771  	// Cache may be empty so end
   772  	_, err := os.Stat(c.root)
   773  	if os.IsNotExist(err) {
   774  		return
   775  	}
   776  	c.updateUsed()
   777  	c.mu.Lock()
   778  	oldItems, oldUsed := len(c.item), fs.SizeSuffix(c.used)
   779  	c.mu.Unlock()
   780  
   781  	// Remove any files that are over age
   782  	c.purgeOld(c.opt.CacheMaxAge)
   783  
   784  	// If have a maximum cache size...
   785  	if c.haveQuotas() {
   786  		// Remove files not in use until cache size is below quota starting from the oldest first
   787  		c.purgeOverQuota()
   788  
   789  		// Remove cache files that are not dirty if we are still above the max cache size
   790  		c.purgeClean()
   791  		c.retryFailedResets()
   792  	}
   793  
   794  	// Was kicked?
   795  	if kicked {
   796  		c.kickerMu.Lock() // Make sure this is called with cache mutex unlocked
   797  		// Re-enable io threads to kick me
   798  		c.cleanerKicked = false
   799  		c.kickerMu.Unlock()
   800  	}
   801  
   802  	// Stats
   803  	c.mu.Lock()
   804  	newItems, newUsed := len(c.item), fs.SizeSuffix(c.used)
   805  	totalInUse := 0
   806  	for _, item := range c.item {
   807  		if item.inUse() {
   808  			totalInUse++
   809  		}
   810  	}
   811  	c.mu.Unlock()
   812  	uploadsInProgress, uploadsQueued := c.writeback.Stats()
   813  
   814  	stats := fmt.Sprintf("objects %d (was %d) in use %d, to upload %d, uploading %d, total size %v (was %v)",
   815  		newItems, oldItems, totalInUse, uploadsQueued, uploadsInProgress, newUsed, oldUsed)
   816  	fs.Infof(nil, "vfs cache: cleaned: %s", stats)
   817  	if err = systemd.UpdateStatus(fmt.Sprintf("[%s] vfs cache: %s", time.Now().Format("15:04"), stats)); err != nil {
   818  		fs.Errorf(nil, "vfs cache: updating systemd status with current stats failed: %s", err)
   819  	}
   820  }
   821  
   822  // cleaner calls clean at regular intervals and upon being kicked for out-of-space condition
   823  //
   824  // doesn't return until context is cancelled
   825  func (c *Cache) cleaner(ctx context.Context) {
   826  	if c.opt.CachePollInterval <= 0 {
   827  		fs.Debugf(nil, "vfs cache: cleaning thread disabled because poll interval <= 0")
   828  		return
   829  	}
   830  	// Start cleaning the cache immediately
   831  	c.clean(false)
   832  	// Then every interval specified
   833  	timer := time.NewTicker(c.opt.CachePollInterval)
   834  	defer timer.Stop()
   835  	for {
   836  		select {
   837  		case <-c.kick: // a thread encountering ENOSPC kicked me
   838  			c.clean(true) // kicked is true
   839  		case <-timer.C:
   840  			c.clean(false) // timer driven cache poll, kicked is false
   841  		case <-ctx.Done():
   842  			fs.Debugf(nil, "vfs cache: cleaner exiting")
   843  			return
   844  		}
   845  	}
   846  }
   847  
   848  // TotalInUse returns the number of items in the cache which are InUse
   849  func (c *Cache) TotalInUse() (n int) {
   850  	c.mu.Lock()
   851  	defer c.mu.Unlock()
   852  	for _, item := range c.item {
   853  		if item.inUse() {
   854  			n++
   855  		}
   856  	}
   857  	return n
   858  }
   859  
   860  // Dump the cache into a string for debugging purposes
   861  func (c *Cache) Dump() string {
   862  	if c == nil {
   863  		return "Cache: <nil>\n"
   864  	}
   865  	c.mu.Lock()
   866  	defer c.mu.Unlock()
   867  	var out strings.Builder
   868  	out.WriteString("Cache{\n")
   869  	for name, item := range c.item {
   870  		fmt.Fprintf(&out, "\t%q: %+v,\n", name, item)
   871  	}
   872  	out.WriteString("}\n")
   873  	return out.String()
   874  }
   875  
   876  // AddVirtual adds a virtual directory entry by calling the addVirtual
   877  // callback if one has been registered.
   878  func (c *Cache) AddVirtual(remote string, size int64, isDir bool) error {
   879  	if c.avFn == nil {
   880  		return errors.New("no AddVirtual function registered")
   881  	}
   882  	return c.avFn(remote, size, isDir)
   883  }