github.com/xhghs/rclone@v1.51.1-0.20200430155106-e186a28cced8/vfs/cache.go (about)

     1  // This deals with caching of files locally
     2  
     3  package vfs
     4  
     5  import (
     6  	"context"
     7  	"fmt"
     8  	"os"
     9  	"path/filepath"
    10  	"runtime"
    11  	"sort"
    12  	"strings"
    13  	"sync"
    14  	"time"
    15  
    16  	"github.com/djherbis/times"
    17  	"github.com/pkg/errors"
    18  	"github.com/rclone/rclone/fs"
    19  	fscache "github.com/rclone/rclone/fs/cache"
    20  	"github.com/rclone/rclone/fs/config"
    21  )
    22  
    23  // CacheMode controls the functionality of the cache
    24  type CacheMode byte
    25  
    26  // CacheMode options
    27  const (
    28  	CacheModeOff     CacheMode = iota // cache nothing - return errors for writes which can't be satisfied
    29  	CacheModeMinimal                  // cache only the minimum, eg read/write opens
    30  	CacheModeWrites                   // cache all files opened with write intent
    31  	CacheModeFull                     // cache all files opened in any mode
    32  )
    33  
    34  var cacheModeToString = []string{
    35  	CacheModeOff:     "off",
    36  	CacheModeMinimal: "minimal",
    37  	CacheModeWrites:  "writes",
    38  	CacheModeFull:    "full",
    39  }
    40  
    41  // String turns a CacheMode into a string
    42  func (l CacheMode) String() string {
    43  	if l >= CacheMode(len(cacheModeToString)) {
    44  		return fmt.Sprintf("CacheMode(%d)", l)
    45  	}
    46  	return cacheModeToString[l]
    47  }
    48  
    49  // Set a CacheMode
    50  func (l *CacheMode) Set(s string) error {
    51  	for n, name := range cacheModeToString {
    52  		if s != "" && name == s {
    53  			*l = CacheMode(n)
    54  			return nil
    55  		}
    56  	}
    57  	return errors.Errorf("Unknown cache mode level %q", s)
    58  }
    59  
    60  // Type of the value
    61  func (l *CacheMode) Type() string {
    62  	return "CacheMode"
    63  }
    64  
    65  // cache opened files
    66  type cache struct {
    67  	f      fs.Fs                 // fs for the cache directory
    68  	opt    *Options              // vfs Options
    69  	root   string                // root of the cache directory
    70  	itemMu sync.Mutex            // protects the following variables
    71  	item   map[string]*cacheItem // files/directories in the cache
    72  	used   int64                 // total size of files in the cache
    73  }
    74  
    75  // cacheItem is stored in the item map
    76  type cacheItem struct {
    77  	opens  int       // number of times file is open
    78  	atime  time.Time // last time file was accessed
    79  	isFile bool      // if this is a file or a directory
    80  	size   int64     // size of the cached item
    81  }
    82  
    83  // newCacheItem returns an item for the cache
    84  func newCacheItem(isFile bool) *cacheItem {
    85  	return &cacheItem{atime: time.Now(), isFile: isFile}
    86  }
    87  
    88  // newCache creates a new cache heirachy for f
    89  //
    90  // This starts background goroutines which can be cancelled with the
    91  // context passed in.
    92  func newCache(ctx context.Context, f fs.Fs, opt *Options) (*cache, error) {
    93  	fRoot := filepath.FromSlash(f.Root())
    94  	if runtime.GOOS == "windows" {
    95  		if strings.HasPrefix(fRoot, `\\?`) {
    96  			fRoot = fRoot[3:]
    97  		}
    98  		fRoot = strings.Replace(fRoot, ":", "", -1)
    99  	}
   100  	root := filepath.Join(config.CacheDir, "vfs", f.Name(), fRoot)
   101  	fs.Debugf(nil, "vfs cache root is %q", root)
   102  
   103  	f, err := fscache.Get(root)
   104  	if err != nil {
   105  		return nil, errors.Wrap(err, "failed to create cache remote")
   106  	}
   107  
   108  	c := &cache{
   109  		f:    f,
   110  		opt:  opt,
   111  		root: root,
   112  		item: make(map[string]*cacheItem),
   113  	}
   114  
   115  	go c.cleaner(ctx)
   116  
   117  	return c, nil
   118  }
   119  
   120  // findParent returns the parent directory of name, or "" for the root
   121  func findParent(name string) string {
   122  	parent := filepath.Dir(name)
   123  	if parent == "." || parent == "/" {
   124  		parent = ""
   125  	}
   126  	return parent
   127  }
   128  
   129  // clean returns the cleaned version of name for use in the index map
   130  func clean(name string) string {
   131  	name = strings.Trim(name, "/")
   132  	name = filepath.Clean(name)
   133  	if name == "." || name == "/" {
   134  		name = ""
   135  	}
   136  	return name
   137  }
   138  
   139  // toOSPath turns a remote relative name into an OS path in the cache
   140  func (c *cache) toOSPath(name string) string {
   141  	return filepath.Join(c.root, filepath.FromSlash(name))
   142  }
   143  
   144  // mkdir makes the directory for name in the cache and returns an os
   145  // path for the file
   146  func (c *cache) mkdir(name string) (string, error) {
   147  	parent := findParent(name)
   148  	leaf := filepath.Base(name)
   149  	parentPath := c.toOSPath(parent)
   150  	err := os.MkdirAll(parentPath, 0700)
   151  	if err != nil {
   152  		return "", errors.Wrap(err, "make cache directory failed")
   153  	}
   154  	c.cacheDir(parent)
   155  	return filepath.Join(parentPath, leaf), nil
   156  }
   157  
   158  // _get gets name from the cache or creates a new one
   159  //
   160  // It returns the item and found as to whether this item was found in
   161  // the cache (or just created).
   162  //
   163  // name should be a remote path not an osPath
   164  //
   165  // must be called with itemMu held
   166  func (c *cache) _get(isFile bool, name string) (item *cacheItem, found bool) {
   167  	item = c.item[name]
   168  	found = item != nil
   169  	if !found {
   170  		item = newCacheItem(isFile)
   171  		c.item[name] = item
   172  	}
   173  	return item, found
   174  }
   175  
   176  // opens returns the number of opens that are on the file
   177  //
   178  // name should be a remote path not an osPath
   179  func (c *cache) opens(name string) int {
   180  	name = clean(name)
   181  	c.itemMu.Lock()
   182  	defer c.itemMu.Unlock()
   183  	item := c.item[name]
   184  	if item == nil {
   185  		return 0
   186  	}
   187  	return item.opens
   188  }
   189  
   190  // get gets name from the cache or creates a new one
   191  //
   192  // name should be a remote path not an osPath
   193  func (c *cache) get(name string) *cacheItem {
   194  	name = clean(name)
   195  	c.itemMu.Lock()
   196  	item, _ := c._get(true, name)
   197  	c.itemMu.Unlock()
   198  	return item
   199  }
   200  
   201  // updateStat sets the atime of the name to that passed in if it is
   202  // newer than the existing or there isn't an existing time.
   203  //
   204  // it also sets the size
   205  //
   206  // name should be a remote path not an osPath
   207  func (c *cache) updateStat(name string, when time.Time, size int64) {
   208  	name = clean(name)
   209  	c.itemMu.Lock()
   210  	item, found := c._get(true, name)
   211  	if !found || when.Sub(item.atime) > 0 {
   212  		fs.Debugf(name, "updateTime: setting atime to %v", when)
   213  		item.atime = when
   214  	}
   215  	item.size = size
   216  	c.itemMu.Unlock()
   217  }
   218  
   219  // _open marks name as open, must be called with the lock held
   220  //
   221  // name should be a remote path not an osPath
   222  func (c *cache) _open(isFile bool, name string) {
   223  	for {
   224  		item, _ := c._get(isFile, name)
   225  		item.opens++
   226  		item.atime = time.Now()
   227  		if name == "" {
   228  			break
   229  		}
   230  		isFile = false
   231  		name = findParent(name)
   232  	}
   233  }
   234  
   235  // open marks name as open
   236  //
   237  // name should be a remote path not an osPath
   238  func (c *cache) open(name string) {
   239  	name = clean(name)
   240  	c.itemMu.Lock()
   241  	c._open(true, name)
   242  	c.itemMu.Unlock()
   243  }
   244  
   245  // cacheDir marks a directory and its parents as being in the cache
   246  //
   247  // name should be a remote path not an osPath
   248  func (c *cache) cacheDir(name string) {
   249  	name = clean(name)
   250  	c.itemMu.Lock()
   251  	defer c.itemMu.Unlock()
   252  	for {
   253  		item := c.item[name]
   254  		if item != nil {
   255  			break
   256  		}
   257  		c.item[name] = newCacheItem(false)
   258  		if name == "" {
   259  			break
   260  		}
   261  		name = findParent(name)
   262  	}
   263  }
   264  
   265  // exists checks to see if the file exists in the cache or not
   266  func (c *cache) exists(name string) bool {
   267  	osPath := c.toOSPath(name)
   268  	fi, err := os.Stat(osPath)
   269  	if err != nil {
   270  		return false
   271  	}
   272  	// checks for non-regular files (e.g. directories, symlinks, devices, etc.)
   273  	if !fi.Mode().IsRegular() {
   274  		return false
   275  	}
   276  	return true
   277  }
   278  
   279  // renames the file in cache
   280  func (c *cache) rename(name string, newName string) (err error) {
   281  	osOldPath := c.toOSPath(name)
   282  	osNewPath := c.toOSPath(newName)
   283  	sfi, err := os.Stat(osOldPath)
   284  	if err != nil {
   285  		return errors.Wrapf(err, "Failed to stat source: %s", osOldPath)
   286  	}
   287  	if !sfi.Mode().IsRegular() {
   288  		// cannot copy non-regular files (e.g., directories, symlinks, devices, etc.)
   289  		return errors.Errorf("Non-regular source file: %s (%q)", sfi.Name(), sfi.Mode().String())
   290  	}
   291  	dfi, err := os.Stat(osNewPath)
   292  	if err != nil {
   293  		if !os.IsNotExist(err) {
   294  			return errors.Wrapf(err, "Failed to stat destination: %s", osNewPath)
   295  		}
   296  		parent := findParent(osNewPath)
   297  		err = os.MkdirAll(parent, 0700)
   298  		if err != nil {
   299  			return errors.Wrapf(err, "Failed to create parent dir: %s", parent)
   300  		}
   301  	} else {
   302  		if !(dfi.Mode().IsRegular()) {
   303  			return errors.Errorf("Non-regular destination file: %s (%q)", dfi.Name(), dfi.Mode().String())
   304  		}
   305  		if os.SameFile(sfi, dfi) {
   306  			return nil
   307  		}
   308  	}
   309  	if err = os.Rename(osOldPath, osNewPath); err != nil {
   310  		return errors.Wrapf(err, "Failed to rename in cache: %s to %s", osOldPath, osNewPath)
   311  	}
   312  	// Rename the cache item
   313  	c.itemMu.Lock()
   314  	if oldItem, ok := c.item[name]; ok {
   315  		c.item[newName] = oldItem
   316  		delete(c.item, name)
   317  	}
   318  	c.itemMu.Unlock()
   319  	fs.Infof(name, "Renamed in cache")
   320  	return nil
   321  }
   322  
   323  // _close marks name as closed - must be called with the lock held
   324  func (c *cache) _close(isFile bool, name string) {
   325  	for {
   326  		item, _ := c._get(isFile, name)
   327  		item.opens--
   328  		item.atime = time.Now()
   329  		if item.opens < 0 {
   330  			fs.Errorf(name, "cache: double close")
   331  		}
   332  		osPath := c.toOSPath(name)
   333  		fi, err := os.Stat(osPath)
   334  		// Update the size on close
   335  		if err == nil && !fi.IsDir() {
   336  			item.size = fi.Size()
   337  		}
   338  		if name == "" {
   339  			break
   340  		}
   341  		isFile = false
   342  		name = findParent(name)
   343  	}
   344  }
   345  
   346  // close marks name as closed
   347  //
   348  // name should be a remote path not an osPath
   349  func (c *cache) close(name string) {
   350  	name = clean(name)
   351  	c.itemMu.Lock()
   352  	c._close(true, name)
   353  	c.itemMu.Unlock()
   354  }
   355  
   356  // remove should be called if name is deleted
   357  func (c *cache) remove(name string) {
   358  	osPath := c.toOSPath(name)
   359  	err := os.Remove(osPath)
   360  	if err != nil && !os.IsNotExist(err) {
   361  		fs.Errorf(name, "Failed to remove from cache: %v", err)
   362  	} else {
   363  		fs.Infof(name, "Removed from cache")
   364  	}
   365  }
   366  
   367  // removeDir should be called if dir is deleted and returns true if
   368  // the directory is gone.
   369  func (c *cache) removeDir(dir string) bool {
   370  	osPath := c.toOSPath(dir)
   371  	err := os.Remove(osPath)
   372  	if err == nil || os.IsNotExist(err) {
   373  		if err == nil {
   374  			fs.Debugf(dir, "Removed empty directory")
   375  		}
   376  		return true
   377  	}
   378  	if !os.IsExist(err) {
   379  		fs.Errorf(dir, "Failed to remove cached dir: %v", err)
   380  	}
   381  	return false
   382  }
   383  
   384  // setModTime should be called to set the modification time of the cache file
   385  func (c *cache) setModTime(name string, modTime time.Time) {
   386  	osPath := c.toOSPath(name)
   387  	err := os.Chtimes(osPath, modTime, modTime)
   388  	if err != nil {
   389  		fs.Errorf(name, "Failed to set modification time of cached file: %v", err)
   390  	}
   391  }
   392  
   393  // cleanUp empties the cache of everything
   394  func (c *cache) cleanUp() error {
   395  	return os.RemoveAll(c.root)
   396  }
   397  
   398  // walk walks the cache calling the function
   399  func (c *cache) walk(fn func(osPath string, fi os.FileInfo, name string) error) error {
   400  	return filepath.Walk(c.root, func(osPath string, fi os.FileInfo, err error) error {
   401  		if err != nil {
   402  			return err
   403  		}
   404  		// Find path relative to the cache root
   405  		name, err := filepath.Rel(c.root, osPath)
   406  		if err != nil {
   407  			return errors.Wrap(err, "filepath.Rel failed in walk")
   408  		}
   409  		if name == "." {
   410  			name = ""
   411  		}
   412  		// And convert into slashes
   413  		name = filepath.ToSlash(name)
   414  
   415  		return fn(osPath, fi, name)
   416  	})
   417  }
   418  
   419  // updateStats walks the cache updating any atimes and sizes it finds
   420  //
   421  // it also updates used
   422  func (c *cache) updateStats() error {
   423  	var newUsed int64
   424  	err := c.walk(func(osPath string, fi os.FileInfo, name string) error {
   425  		if !fi.IsDir() {
   426  			// Update the atime with that of the file
   427  			atime := times.Get(fi).AccessTime()
   428  			c.updateStat(name, atime, fi.Size())
   429  			newUsed += fi.Size()
   430  		} else {
   431  			c.cacheDir(name)
   432  		}
   433  		return nil
   434  	})
   435  	c.itemMu.Lock()
   436  	c.used = newUsed
   437  	c.itemMu.Unlock()
   438  	return err
   439  }
   440  
   441  // purgeOld gets rid of any files that are over age
   442  func (c *cache) purgeOld(maxAge time.Duration) {
   443  	c._purgeOld(maxAge, c.remove)
   444  }
   445  
   446  func (c *cache) _purgeOld(maxAge time.Duration, remove func(name string)) {
   447  	c.itemMu.Lock()
   448  	defer c.itemMu.Unlock()
   449  	cutoff := time.Now().Add(-maxAge)
   450  	for name, item := range c.item {
   451  		if item.isFile && item.opens == 0 {
   452  			// If not locked and access time too long ago - delete the file
   453  			dt := item.atime.Sub(cutoff)
   454  			// fs.Debugf(name, "atime=%v cutoff=%v, dt=%v", item.atime, cutoff, dt)
   455  			if dt < 0 {
   456  				remove(name)
   457  				// Remove the entry
   458  				delete(c.item, name)
   459  			}
   460  		}
   461  	}
   462  }
   463  
   464  // Purge any empty directories
   465  func (c *cache) purgeEmptyDirs() {
   466  	c._purgeEmptyDirs(c.removeDir)
   467  }
   468  
   469  func (c *cache) _purgeEmptyDirs(removeDir func(name string) bool) {
   470  	c.itemMu.Lock()
   471  	defer c.itemMu.Unlock()
   472  	var dirs []string
   473  	for name, item := range c.item {
   474  		if !item.isFile && item.opens == 0 {
   475  			dirs = append(dirs, name)
   476  		}
   477  	}
   478  	// remove empty directories in reverse alphabetical order
   479  	sort.Strings(dirs)
   480  	for i := len(dirs) - 1; i >= 0; i-- {
   481  		dir := dirs[i]
   482  		// Remove the entry
   483  		if removeDir(dir) {
   484  			delete(c.item, dir)
   485  		}
   486  	}
   487  }
   488  
   489  // This is a cacheItem with a name for sorting
   490  type cacheNamedItem struct {
   491  	name string
   492  	item *cacheItem
   493  }
   494  type cacheNamedItems []cacheNamedItem
   495  
   496  func (v cacheNamedItems) Len() int           { return len(v) }
   497  func (v cacheNamedItems) Swap(i, j int)      { v[i], v[j] = v[j], v[i] }
   498  func (v cacheNamedItems) Less(i, j int) bool { return v[i].item.atime.Before(v[j].item.atime) }
   499  
   500  // Remove any files that are over quota starting from the
   501  // oldest first
   502  func (c *cache) purgeOverQuota(quota int64) {
   503  	c._purgeOverQuota(quota, c.remove)
   504  }
   505  
   506  func (c *cache) _purgeOverQuota(quota int64, remove func(name string)) {
   507  	c.itemMu.Lock()
   508  	defer c.itemMu.Unlock()
   509  
   510  	if quota <= 0 || c.used < quota {
   511  		return
   512  	}
   513  
   514  	var items cacheNamedItems
   515  
   516  	// Make a slice of unused files
   517  	for name, item := range c.item {
   518  		if item.isFile && item.opens == 0 {
   519  			items = append(items, cacheNamedItem{
   520  				name: name,
   521  				item: item,
   522  			})
   523  		}
   524  	}
   525  	sort.Sort(items)
   526  
   527  	// Remove items until the quota is OK
   528  	for _, item := range items {
   529  		if c.used < quota {
   530  			break
   531  		}
   532  		remove(item.name)
   533  		// Remove the entry
   534  		delete(c.item, item.name)
   535  		c.used -= item.item.size
   536  	}
   537  }
   538  
   539  // clean empties the cache of stuff if it can
   540  func (c *cache) clean() {
   541  	// Cache may be empty so end
   542  	_, err := os.Stat(c.root)
   543  	if os.IsNotExist(err) {
   544  		return
   545  	}
   546  
   547  	c.itemMu.Lock()
   548  	oldItems, oldUsed := len(c.item), fs.SizeSuffix(c.used)
   549  	c.itemMu.Unlock()
   550  
   551  	// first walk the FS to update the atimes and sizes
   552  	err = c.updateStats()
   553  	if err != nil {
   554  		fs.Errorf(nil, "Error traversing cache %q: %v", c.root, err)
   555  	}
   556  
   557  	// Remove any files that are over age
   558  	c.purgeOld(c.opt.CacheMaxAge)
   559  
   560  	// Now remove any files that are over quota starting from the
   561  	// oldest first
   562  	c.purgeOverQuota(int64(c.opt.CacheMaxSize))
   563  
   564  	// Remove any empty directories
   565  	c.purgeEmptyDirs()
   566  
   567  	// Stats
   568  	c.itemMu.Lock()
   569  	newItems, newUsed := len(c.item), fs.SizeSuffix(c.used)
   570  	c.itemMu.Unlock()
   571  
   572  	fs.Infof(nil, "Cleaned the cache: objects %d (was %d), total size %v (was %v)", newItems, oldItems, newUsed, oldUsed)
   573  }
   574  
   575  // cleaner calls clean at regular intervals
   576  //
   577  // doesn't return until context is cancelled
   578  func (c *cache) cleaner(ctx context.Context) {
   579  	if c.opt.CachePollInterval <= 0 {
   580  		fs.Debugf(nil, "Cache cleaning thread disabled because poll interval <= 0")
   581  		return
   582  	}
   583  	// Start cleaning the cache immediately
   584  	c.clean()
   585  	// Then every interval specified
   586  	timer := time.NewTicker(c.opt.CachePollInterval)
   587  	defer timer.Stop()
   588  	for {
   589  		select {
   590  		case <-timer.C:
   591  			c.clean()
   592  		case <-ctx.Done():
   593  			fs.Debugf(nil, "cache cleaner exiting")
   594  			return
   595  		}
   596  	}
   597  }