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

     1  // Package vfs provides a virtual filing system layer over rclone's
     2  // native objects.
     3  //
     4  // It attempts to behave in a similar way to Go's filing system
     5  // manipulation code in the os package.  The same named function
     6  // should behave in an identical fashion.  The objects also obey Go's
     7  // standard interfaces.
     8  //
     9  // Note that paths don't start or end with /, so the root directory
    10  // may be referred to as "".  However Stat strips slashes so you can
    11  // use paths with slashes in.
    12  //
    13  // It also includes directory caching
    14  //
    15  // The vfs package returns Error values to signal precisely which
    16  // error conditions have ocurred.  It may also return general errors
    17  // it receives.  It tries to use os Error values (eg os.ErrExist)
    18  // where possible.
    19  package vfs
    20  
    21  import (
    22  	"context"
    23  	"fmt"
    24  	"os"
    25  	"path"
    26  	"runtime"
    27  	"strings"
    28  	"sync"
    29  	"sync/atomic"
    30  	"time"
    31  
    32  	"github.com/rclone/rclone/fs"
    33  	"github.com/rclone/rclone/fs/log"
    34  )
    35  
    36  // DefaultOpt is the default values uses for Opt
    37  var DefaultOpt = Options{
    38  	NoModTime:         false,
    39  	NoChecksum:        false,
    40  	NoSeek:            false,
    41  	DirCacheTime:      5 * 60 * time.Second,
    42  	PollInterval:      time.Minute,
    43  	ReadOnly:          false,
    44  	Umask:             0,
    45  	UID:               ^uint32(0), // these values instruct WinFSP-FUSE to use the current user
    46  	GID:               ^uint32(0), // overriden for non windows in mount_unix.go
    47  	DirPerms:          os.FileMode(0777),
    48  	FilePerms:         os.FileMode(0666),
    49  	CacheMode:         CacheModeOff,
    50  	CacheMaxAge:       3600 * time.Second,
    51  	CachePollInterval: 60 * time.Second,
    52  	ChunkSize:         128 * fs.MebiByte,
    53  	ChunkSizeLimit:    -1,
    54  	CacheMaxSize:      -1,
    55  	CaseInsensitive:   runtime.GOOS == "windows" || runtime.GOOS == "darwin", // default to true on Windows and Mac, false otherwise
    56  }
    57  
    58  // Node represents either a directory (*Dir) or a file (*File)
    59  type Node interface {
    60  	os.FileInfo
    61  	IsFile() bool
    62  	Inode() uint64
    63  	SetModTime(modTime time.Time) error
    64  	Sync() error
    65  	Remove() error
    66  	RemoveAll() error
    67  	DirEntry() fs.DirEntry
    68  	VFS() *VFS
    69  	Open(flags int) (Handle, error)
    70  	Truncate(size int64) error
    71  	Path() string
    72  }
    73  
    74  // Check interfaces
    75  var (
    76  	_ Node = (*File)(nil)
    77  	_ Node = (*Dir)(nil)
    78  )
    79  
    80  // Nodes is a slice of Node
    81  type Nodes []Node
    82  
    83  // Sort functions
    84  func (ns Nodes) Len() int           { return len(ns) }
    85  func (ns Nodes) Swap(i, j int)      { ns[i], ns[j] = ns[j], ns[i] }
    86  func (ns Nodes) Less(i, j int) bool { return ns[i].Path() < ns[j].Path() }
    87  
    88  // Noder represents something which can return a node
    89  type Noder interface {
    90  	fmt.Stringer
    91  	Node() Node
    92  }
    93  
    94  // Check interfaces
    95  var (
    96  	_ Noder = (*File)(nil)
    97  	_ Noder = (*Dir)(nil)
    98  	_ Noder = (*ReadFileHandle)(nil)
    99  	_ Noder = (*WriteFileHandle)(nil)
   100  	_ Noder = (*RWFileHandle)(nil)
   101  	_ Noder = (*DirHandle)(nil)
   102  )
   103  
   104  // OsFiler is the methods on *os.File
   105  type OsFiler interface {
   106  	Chdir() error
   107  	Chmod(mode os.FileMode) error
   108  	Chown(uid, gid int) error
   109  	Close() error
   110  	Fd() uintptr
   111  	Name() string
   112  	Read(b []byte) (n int, err error)
   113  	ReadAt(b []byte, off int64) (n int, err error)
   114  	Readdir(n int) ([]os.FileInfo, error)
   115  	Readdirnames(n int) (names []string, err error)
   116  	Seek(offset int64, whence int) (ret int64, err error)
   117  	Stat() (os.FileInfo, error)
   118  	Sync() error
   119  	Truncate(size int64) error
   120  	Write(b []byte) (n int, err error)
   121  	WriteAt(b []byte, off int64) (n int, err error)
   122  	WriteString(s string) (n int, err error)
   123  }
   124  
   125  // Handle is the interface statisified by open files or directories.
   126  // It is the methods on *os.File, plus a few more useful for FUSE
   127  // filingsystems.  Not all of them are supported.
   128  type Handle interface {
   129  	OsFiler
   130  	// Additional methods useful for FUSE filesystems
   131  	Flush() error
   132  	Release() error
   133  	Node() Node
   134  	//	Size() int64
   135  }
   136  
   137  // baseHandle implements all the missing methods
   138  type baseHandle struct{}
   139  
   140  func (h baseHandle) Chdir() error                                         { return ENOSYS }
   141  func (h baseHandle) Chmod(mode os.FileMode) error                         { return ENOSYS }
   142  func (h baseHandle) Chown(uid, gid int) error                             { return ENOSYS }
   143  func (h baseHandle) Close() error                                         { return ENOSYS }
   144  func (h baseHandle) Fd() uintptr                                          { return 0 }
   145  func (h baseHandle) Name() string                                         { return "" }
   146  func (h baseHandle) Read(b []byte) (n int, err error)                     { return 0, ENOSYS }
   147  func (h baseHandle) ReadAt(b []byte, off int64) (n int, err error)        { return 0, ENOSYS }
   148  func (h baseHandle) Readdir(n int) ([]os.FileInfo, error)                 { return nil, ENOSYS }
   149  func (h baseHandle) Readdirnames(n int) (names []string, err error)       { return nil, ENOSYS }
   150  func (h baseHandle) Seek(offset int64, whence int) (ret int64, err error) { return 0, ENOSYS }
   151  func (h baseHandle) Stat() (os.FileInfo, error)                           { return nil, ENOSYS }
   152  func (h baseHandle) Sync() error                                          { return nil }
   153  func (h baseHandle) Truncate(size int64) error                            { return ENOSYS }
   154  func (h baseHandle) Write(b []byte) (n int, err error)                    { return 0, ENOSYS }
   155  func (h baseHandle) WriteAt(b []byte, off int64) (n int, err error)       { return 0, ENOSYS }
   156  func (h baseHandle) WriteString(s string) (n int, err error)              { return 0, ENOSYS }
   157  func (h baseHandle) Flush() (err error)                                   { return ENOSYS }
   158  func (h baseHandle) Release() (err error)                                 { return ENOSYS }
   159  func (h baseHandle) Node() Node                                           { return nil }
   160  
   161  //func (h baseHandle) Size() int64                                          { return 0 }
   162  
   163  // Check interfaces
   164  var (
   165  	_ OsFiler = (*os.File)(nil)
   166  	_ Handle  = (*baseHandle)(nil)
   167  	_ Handle  = (*ReadFileHandle)(nil)
   168  	_ Handle  = (*WriteFileHandle)(nil)
   169  	_ Handle  = (*DirHandle)(nil)
   170  )
   171  
   172  // VFS represents the top level filing system
   173  type VFS struct {
   174  	f         fs.Fs
   175  	root      *Dir
   176  	Opt       Options
   177  	cache     *cache
   178  	cancel    context.CancelFunc
   179  	usageMu   sync.Mutex
   180  	usageTime time.Time
   181  	usage     *fs.Usage
   182  	pollChan  chan time.Duration
   183  }
   184  
   185  // Options is options for creating the vfs
   186  type Options struct {
   187  	NoSeek            bool          // don't allow seeking if set
   188  	NoChecksum        bool          // don't check checksums if set
   189  	ReadOnly          bool          // if set VFS is read only
   190  	NoModTime         bool          // don't read mod times for files
   191  	DirCacheTime      time.Duration // how long to consider directory listing cache valid
   192  	PollInterval      time.Duration
   193  	Umask             int
   194  	UID               uint32
   195  	GID               uint32
   196  	DirPerms          os.FileMode
   197  	FilePerms         os.FileMode
   198  	ChunkSize         fs.SizeSuffix // if > 0 read files in chunks
   199  	ChunkSizeLimit    fs.SizeSuffix // if > ChunkSize double the chunk size after each chunk until reached
   200  	CacheMode         CacheMode
   201  	CacheMaxAge       time.Duration
   202  	CacheMaxSize      fs.SizeSuffix
   203  	CachePollInterval time.Duration
   204  	CaseInsensitive   bool
   205  }
   206  
   207  // New creates a new VFS and root directory.  If opt is nil, then
   208  // DefaultOpt will be used
   209  func New(f fs.Fs, opt *Options) *VFS {
   210  	fsDir := fs.NewDir("", time.Now())
   211  	vfs := &VFS{
   212  		f: f,
   213  	}
   214  
   215  	// Make a copy of the options
   216  	if opt != nil {
   217  		vfs.Opt = *opt
   218  	} else {
   219  		vfs.Opt = DefaultOpt
   220  	}
   221  
   222  	// Mask the permissions with the umask
   223  	vfs.Opt.DirPerms &= ^os.FileMode(vfs.Opt.Umask)
   224  	vfs.Opt.FilePerms &= ^os.FileMode(vfs.Opt.Umask)
   225  
   226  	// Make sure directories are returned as directories
   227  	vfs.Opt.DirPerms |= os.ModeDir
   228  
   229  	// Create root directory
   230  	vfs.root = newDir(vfs, f, nil, fsDir)
   231  
   232  	// Start polling function
   233  	if do := vfs.f.Features().ChangeNotify; do != nil {
   234  		vfs.pollChan = make(chan time.Duration)
   235  		do(context.TODO(), vfs.root.changeNotify, vfs.pollChan)
   236  		vfs.pollChan <- vfs.Opt.PollInterval
   237  	} else {
   238  		fs.Infof(f, "poll-interval is not supported by this remote")
   239  	}
   240  
   241  	vfs.SetCacheMode(vfs.Opt.CacheMode)
   242  
   243  	// add the remote control
   244  	vfs.addRC()
   245  	return vfs
   246  }
   247  
   248  // Fs returns the Fs passed into the New call
   249  func (vfs *VFS) Fs() fs.Fs {
   250  	return vfs.f
   251  }
   252  
   253  // SetCacheMode change the cache mode
   254  func (vfs *VFS) SetCacheMode(cacheMode CacheMode) {
   255  	vfs.Shutdown()
   256  	vfs.cache = nil
   257  	if cacheMode > CacheModeOff {
   258  		ctx, cancel := context.WithCancel(context.Background())
   259  		cache, err := newCache(ctx, vfs.f, &vfs.Opt) // FIXME pass on context or get from Opt?
   260  		if err != nil {
   261  			fs.Errorf(nil, "Failed to create vfs cache - disabling: %v", err)
   262  			vfs.Opt.CacheMode = CacheModeOff
   263  			cancel()
   264  			return
   265  		}
   266  		vfs.Opt.CacheMode = cacheMode
   267  		vfs.cancel = cancel
   268  		vfs.cache = cache
   269  	}
   270  }
   271  
   272  // Shutdown stops any background go-routines
   273  func (vfs *VFS) Shutdown() {
   274  	if vfs.cancel != nil {
   275  		vfs.cancel()
   276  		vfs.cancel = nil
   277  	}
   278  }
   279  
   280  // CleanUp deletes the contents of the on disk cache
   281  func (vfs *VFS) CleanUp() error {
   282  	if vfs.Opt.CacheMode == CacheModeOff {
   283  		return nil
   284  	}
   285  	return vfs.cache.cleanUp()
   286  }
   287  
   288  // FlushDirCache empties the directory cache
   289  func (vfs *VFS) FlushDirCache() {
   290  	vfs.root.ForgetAll()
   291  }
   292  
   293  // WaitForWriters sleeps until all writers have finished or
   294  // time.Duration has elapsed
   295  func (vfs *VFS) WaitForWriters(timeout time.Duration) {
   296  	defer log.Trace(nil, "timeout=%v", timeout)("")
   297  	const tickTime = 1 * time.Second
   298  	deadline := time.NewTimer(timeout)
   299  	defer deadline.Stop()
   300  	tick := time.NewTimer(tickTime)
   301  	defer tick.Stop()
   302  	tick.Stop()
   303  	for {
   304  		writers := 0
   305  		vfs.root.walk(func(d *Dir) {
   306  			fs.Debugf(d.path, "Looking for writers")
   307  			// NB d.mu is held by walk() here
   308  			for leaf, item := range d.items {
   309  				fs.Debugf(leaf, "reading active writers")
   310  				if file, ok := item.(*File); ok {
   311  					n := file.activeWriters()
   312  					if n != 0 {
   313  						fs.Debugf(file, "active writers %d", n)
   314  					}
   315  					writers += n
   316  				}
   317  			}
   318  		})
   319  		if writers == 0 {
   320  			return
   321  		}
   322  		fs.Debugf(nil, "Still %d writers active, waiting %v", writers, tickTime)
   323  		tick.Reset(tickTime)
   324  		select {
   325  		case <-tick.C:
   326  			break
   327  		case <-deadline.C:
   328  			fs.Errorf(nil, "Exiting even though %d writers are active after %v", writers, timeout)
   329  			return
   330  		}
   331  	}
   332  }
   333  
   334  // Root returns the root node
   335  func (vfs *VFS) Root() (*Dir, error) {
   336  	// fs.Debugf(vfs.f, "Root()")
   337  	return vfs.root, nil
   338  }
   339  
   340  var inodeCount uint64
   341  
   342  // newInode creates a new unique inode number
   343  func newInode() (inode uint64) {
   344  	return atomic.AddUint64(&inodeCount, 1)
   345  }
   346  
   347  // Stat finds the Node by path starting from the root
   348  //
   349  // It is the equivalent of os.Stat - Node contains the os.FileInfo
   350  // interface.
   351  func (vfs *VFS) Stat(path string) (node Node, err error) {
   352  	path = strings.Trim(path, "/")
   353  	node = vfs.root
   354  	for path != "" {
   355  		i := strings.IndexRune(path, '/')
   356  		var name string
   357  		if i < 0 {
   358  			name, path = path, ""
   359  		} else {
   360  			name, path = path[:i], path[i+1:]
   361  		}
   362  		if name == "" {
   363  			continue
   364  		}
   365  		dir, ok := node.(*Dir)
   366  		if !ok {
   367  			// We need to look in a directory, but found a file
   368  			return nil, ENOENT
   369  		}
   370  		node, err = dir.Stat(name)
   371  		if err != nil {
   372  			return nil, err
   373  		}
   374  	}
   375  	return
   376  }
   377  
   378  // StatParent finds the parent directory and the leaf name of a path
   379  func (vfs *VFS) StatParent(name string) (dir *Dir, leaf string, err error) {
   380  	name = strings.Trim(name, "/")
   381  	parent, leaf := path.Split(name)
   382  	node, err := vfs.Stat(parent)
   383  	if err != nil {
   384  		return nil, "", err
   385  	}
   386  	if node.IsFile() {
   387  		return nil, "", os.ErrExist
   388  	}
   389  	dir = node.(*Dir)
   390  	return dir, leaf, nil
   391  }
   392  
   393  // decodeOpenFlags returns a string representing the open flags
   394  func decodeOpenFlags(flags int) string {
   395  	var out []string
   396  	rdwrMode := flags & accessModeMask
   397  	switch rdwrMode {
   398  	case os.O_RDONLY:
   399  		out = append(out, "O_RDONLY")
   400  	case os.O_WRONLY:
   401  		out = append(out, "O_WRONLY")
   402  	case os.O_RDWR:
   403  		out = append(out, "O_RDWR")
   404  	default:
   405  		out = append(out, fmt.Sprintf("0x%X", rdwrMode))
   406  	}
   407  	if flags&os.O_APPEND != 0 {
   408  		out = append(out, "O_APPEND")
   409  	}
   410  	if flags&os.O_CREATE != 0 {
   411  		out = append(out, "O_CREATE")
   412  	}
   413  	if flags&os.O_EXCL != 0 {
   414  		out = append(out, "O_EXCL")
   415  	}
   416  	if flags&os.O_SYNC != 0 {
   417  		out = append(out, "O_SYNC")
   418  	}
   419  	if flags&os.O_TRUNC != 0 {
   420  		out = append(out, "O_TRUNC")
   421  	}
   422  	flags &^= accessModeMask | os.O_APPEND | os.O_CREATE | os.O_EXCL | os.O_SYNC | os.O_TRUNC
   423  	if flags != 0 {
   424  		out = append(out, fmt.Sprintf("0x%X", flags))
   425  	}
   426  	return strings.Join(out, "|")
   427  }
   428  
   429  // OpenFile a file according to the flags and perm provided
   430  func (vfs *VFS) OpenFile(name string, flags int, perm os.FileMode) (fd Handle, err error) {
   431  	defer log.Trace(name, "flags=%s, perm=%v", decodeOpenFlags(flags), perm)("fd=%v, err=%v", &fd, &err)
   432  
   433  	// http://pubs.opengroup.org/onlinepubs/7908799/xsh/open.html
   434  	// The result of using O_TRUNC with O_RDONLY is undefined.
   435  	// Linux seems to truncate the file, but we prefer to return EINVAL
   436  	if flags&accessModeMask == os.O_RDONLY && flags&os.O_TRUNC != 0 {
   437  		return nil, EINVAL
   438  	}
   439  
   440  	node, err := vfs.Stat(name)
   441  	if err != nil {
   442  		if err != ENOENT || flags&os.O_CREATE == 0 {
   443  			return nil, err
   444  		}
   445  		// If not found and O_CREATE then create the file
   446  		dir, leaf, err := vfs.StatParent(name)
   447  		if err != nil {
   448  			return nil, err
   449  		}
   450  		node, err = dir.Create(leaf, flags)
   451  		if err != nil {
   452  			return nil, err
   453  		}
   454  	}
   455  	return node.Open(flags)
   456  }
   457  
   458  // Rename oldName to newName
   459  func (vfs *VFS) Rename(oldName, newName string) error {
   460  	// find the parent directories
   461  	oldDir, oldLeaf, err := vfs.StatParent(oldName)
   462  	if err != nil {
   463  		return err
   464  	}
   465  	newDir, newLeaf, err := vfs.StatParent(newName)
   466  	if err != nil {
   467  		return err
   468  	}
   469  	err = oldDir.Rename(oldLeaf, newLeaf, newDir)
   470  	if err != nil {
   471  		return err
   472  	}
   473  	return nil
   474  }
   475  
   476  // Statfs returns into about the filing system if known
   477  //
   478  // The values will be -1 if they aren't known
   479  //
   480  // This information is cached for the DirCacheTime interval
   481  func (vfs *VFS) Statfs() (total, used, free int64) {
   482  	// defer log.Trace("/", "")("total=%d, used=%d, free=%d", &total, &used, &free)
   483  	vfs.usageMu.Lock()
   484  	defer vfs.usageMu.Unlock()
   485  	total, used, free = -1, -1, -1
   486  	doAbout := vfs.f.Features().About
   487  	if doAbout == nil {
   488  		return
   489  	}
   490  	if vfs.usageTime.IsZero() || time.Since(vfs.usageTime) >= vfs.Opt.DirCacheTime {
   491  		var err error
   492  		vfs.usage, err = doAbout(context.TODO())
   493  		vfs.usageTime = time.Now()
   494  		if err != nil {
   495  			fs.Errorf(vfs.f, "Statfs failed: %v", err)
   496  			return
   497  		}
   498  	}
   499  	if u := vfs.usage; u != nil {
   500  		if u.Total != nil {
   501  			total = *u.Total
   502  		}
   503  		if u.Free != nil {
   504  			free = *u.Free
   505  		}
   506  		if u.Used != nil {
   507  			used = *u.Used
   508  		}
   509  	}
   510  	return
   511  }