1
mirror of https://github.com/rclone/rclone synced 2024-12-27 19:43:48 +01:00
rclone/vfs/dir.go
nielash a56cd52025 vfs: fix renaming a directory
Before this change, renaming a directory d failed to rename its key in
d.parent.items, which caused trouble later when doing Dir.Stat on a
subdirectory. This change fixes the issue.
2024-06-11 12:09:19 +01:00

1192 lines
31 KiB
Go

package vfs
import (
"context"
"fmt"
"os"
"path"
"sort"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/rclone/rclone/fs"
"github.com/rclone/rclone/fs/dirtree"
"github.com/rclone/rclone/fs/list"
"github.com/rclone/rclone/fs/log"
"github.com/rclone/rclone/fs/operations"
"github.com/rclone/rclone/fs/walk"
"github.com/rclone/rclone/vfs/vfscommon"
"golang.org/x/text/unicode/norm"
)
// Dir represents a directory entry
type Dir struct {
vfs *VFS // read only
inode uint64 // read only: inode number
f fs.Fs // read only
cleanupTimer *time.Timer // read only: timer to call cacheCleanup
mu sync.RWMutex // protects the following
parent *Dir // parent, nil for root
path string
entry fs.Directory
read time.Time // time directory entry last read
items map[string]Node // directory entries - can be empty but not nil
virtual map[string]vState // virtual directory entries - may be nil
sys atomic.Value // user defined info to be attached here
modTimeMu sync.Mutex // protects the following
modTime time.Time
_hasVirtual atomic.Bool // shows if the directory has virtual entries
}
//go:generate stringer -type=vState
// vState describes the state of the virtual directory entries
type vState byte
const (
vOK vState = iota // Not virtual
vAddFile // added file
vAddDir // added directory
vDel // removed file or directory
)
func newDir(vfs *VFS, f fs.Fs, parent *Dir, fsDir fs.Directory) *Dir {
d := &Dir{
vfs: vfs,
f: f,
parent: parent,
entry: fsDir,
path: fsDir.Remote(),
modTime: fsDir.ModTime(context.TODO()),
inode: newInode(),
items: make(map[string]Node),
}
d.cleanupTimer = time.AfterFunc(vfs.Opt.DirCacheTime*2, d.cacheCleanup)
d.setHasVirtual(false)
return d
}
func (d *Dir) cacheCleanup() {
defer func() {
// We should never panic here
_ = recover()
}()
when := time.Now()
d.mu.Lock()
_, stale := d._age(when)
d.mu.Unlock()
if stale {
d.ForgetAll()
}
}
// String converts it to printable
func (d *Dir) String() string {
if d == nil {
return "<nil *Dir>"
}
d.mu.RLock()
defer d.mu.RUnlock()
return d.path + "/"
}
// Dumps the directory tree to the string builder with the given indent
//
//lint:ignore U1000 false positive when running staticcheck,
//nolint:unused // Don't include unused when running golangci-lint
func (d *Dir) dumpIndent(out *strings.Builder, indent string) {
if d == nil {
fmt.Fprintf(out, "%s<nil *Dir>\n", indent)
return
}
d.mu.RLock()
defer d.mu.RUnlock()
fmt.Fprintf(out, "%sPath: %s\n", indent, d.path)
fmt.Fprintf(out, "%sEntry: %v\n", indent, d.entry)
fmt.Fprintf(out, "%sRead: %v\n", indent, d.read)
fmt.Fprintf(out, "%s- items %d\n", indent, len(d.items))
// Sort?
for leaf, node := range d.items {
switch x := node.(type) {
case *Dir:
fmt.Fprintf(out, "%s %s/ - %v\n", indent, leaf, x)
// check the parent is correct
if x.parent != d {
fmt.Fprintf(out, "%s PARENT POINTER WRONG\n", indent)
}
x.dumpIndent(out, indent+"\t")
case *File:
fmt.Fprintf(out, "%s %s - %v\n", indent, leaf, x)
default:
panic("bad dir entry")
}
}
fmt.Fprintf(out, "%s- virtual %d\n", indent, len(d.virtual))
for leaf, state := range d.virtual {
fmt.Fprintf(out, "%s %s - %v\n", indent, leaf, state)
}
}
// Dumps a nicely formatted directory tree to a string
//
//lint:ignore U1000 false positive when running staticcheck,
//nolint:unused // Don't include unused when running golangci-lint
func (d *Dir) dump() string {
var out strings.Builder
d.dumpIndent(&out, "")
return out.String()
}
// IsFile returns false for Dir - satisfies Node interface
func (d *Dir) IsFile() bool {
return false
}
// IsDir returns true for Dir - satisfies Node interface
func (d *Dir) IsDir() bool {
return true
}
// Mode bits of the directory - satisfies Node interface
func (d *Dir) Mode() (mode os.FileMode) {
return d.vfs.Opt.DirPerms
}
// Name (base) of the directory - satisfies Node interface
func (d *Dir) Name() (name string) {
d.mu.RLock()
name = path.Base(d.path)
d.mu.RUnlock()
if name == "." {
name = "/"
}
return name
}
// Path of the directory - satisfies Node interface
func (d *Dir) Path() (name string) {
d.mu.RLock()
defer d.mu.RUnlock()
return d.path
}
// Sys returns underlying data source (can be nil) - satisfies Node interface
func (d *Dir) Sys() interface{} {
return d.sys.Load()
}
// SetSys sets the underlying data source (can be nil) - satisfies Node interface
func (d *Dir) SetSys(x interface{}) {
d.sys.Store(x)
}
// Inode returns the inode number - satisfies Node interface
func (d *Dir) Inode() uint64 {
return d.inode
}
// Node returns the Node associated with this - satisfies Noder interface
func (d *Dir) Node() Node {
return d
}
// hasVirtual returns whether the directory has virtual entries
func (d *Dir) hasVirtual() bool {
return d._hasVirtual.Load()
}
// setHasVirtual sets the hasVirtual flag for the directory
func (d *Dir) setHasVirtual(hasVirtual bool) {
d._hasVirtual.Store(hasVirtual)
}
// ForgetAll forgets directory entries for this directory and any children.
//
// It does not invalidate or clear the cache of the parent directory.
//
// It returns true if the directory or any of its children had virtual entries
// so could not be forgotten. Children which didn't have virtual entries and
// children with virtual entries will be forgotten even if true is returned.
func (d *Dir) ForgetAll() (hasVirtual bool) {
d.mu.RLock()
fs.Debugf(d.path, "forgetting directory cache")
for _, node := range d.items {
if dir, ok := node.(*Dir); ok {
if dir.ForgetAll() {
d.setHasVirtual(true)
}
}
}
d.mu.RUnlock()
d.mu.Lock()
defer d.mu.Unlock()
// Purge any unnecessary virtual entries
d._purgeVirtual()
d.read = time.Time{}
// Check if this dir has virtual entries
if len(d.virtual) != 0 {
d.setHasVirtual(true)
}
// Don't clear directory entries if there are virtual entries in this
// directory or any children
if !d.hasVirtual() {
d.items = make(map[string]Node)
d.cleanupTimer.Stop()
}
return d.hasVirtual()
}
// forgetDirPath clears the cache for itself and all subdirectories if
// they match the given path. The path is specified relative from the
// directory it is called from.
//
// It does not invalidate or clear the cache of the parent directory.
func (d *Dir) forgetDirPath(relativePath string) {
dir := d.cachedDir(relativePath)
if dir == nil {
return
}
dir.ForgetAll()
}
// invalidateDir invalidates the directory cache for absPath relative to the root
func (d *Dir) invalidateDir(absPath string) {
node := d.vfs.root.cachedNode(absPath)
if dir, ok := node.(*Dir); ok {
dir.mu.Lock()
if !dir.read.IsZero() {
fs.Debugf(dir.path, "invalidating directory cache")
dir.read = time.Time{}
}
dir.mu.Unlock()
}
}
// changeNotify invalidates the directory cache for the relativePath
// passed in.
//
// if entryType is a directory it invalidates the parent of the directory too.
func (d *Dir) changeNotify(relativePath string, entryType fs.EntryType) {
defer log.Trace(d.path, "relativePath=%q, type=%v", relativePath, entryType)("")
d.mu.RLock()
absPath := path.Join(d.path, relativePath)
d.mu.RUnlock()
d.invalidateDir(vfscommon.FindParent(absPath))
if entryType == fs.EntryDirectory {
d.invalidateDir(absPath)
}
}
// ForgetPath clears the cache for itself and all subdirectories if
// they match the given path. The path is specified relative from the
// directory it is called from. The cache of the parent directory is
// marked as stale, but not cleared otherwise.
// It is not possible to traverse the directory tree upwards, i.e.
// you cannot clear the cache for the Dir's ancestors or siblings.
func (d *Dir) ForgetPath(relativePath string, entryType fs.EntryType) {
defer log.Trace(d.path, "relativePath=%q, type=%v", relativePath, entryType)("")
d.mu.RLock()
absPath := path.Join(d.path, relativePath)
d.mu.RUnlock()
if absPath != "" {
d.invalidateDir(vfscommon.FindParent(absPath))
}
if entryType == fs.EntryDirectory {
d.forgetDirPath(relativePath)
}
}
// walk runs a function on all cached directories. It will be called
// on a directory's children first.
//
// The mutex will be held for the directory when fun is called
func (d *Dir) walk(fun func(*Dir)) {
d.mu.Lock()
defer d.mu.Unlock()
for _, node := range d.items {
if dir, ok := node.(*Dir); ok {
dir.walk(fun)
}
}
fun(d)
}
// countActiveWriters returns the number of writers active in this
// directory and any subdirectories.
func (d *Dir) countActiveWriters() (writers int) {
d.walk(func(d *Dir) {
// NB d.mu is held by walk() here
fs.Debugf(d.path, "Looking for writers")
for leaf, item := range d.items {
fs.Debugf(leaf, "reading active writers")
if file, ok := item.(*File); ok {
n := file.activeWriters()
if n != 0 {
fs.Debugf(file, "active writers %d", n)
}
writers += n
}
}
})
return writers
}
// age returns the duration since the last time the directory contents
// was read and the content is considered stale. age will be 0 and
// stale true if the last read time is empty.
// age must be called with d.mu held.
func (d *Dir) _age(when time.Time) (age time.Duration, stale bool) {
if d.read.IsZero() {
return age, true
}
age = when.Sub(d.read)
stale = age > d.vfs.Opt.DirCacheTime
return
}
// renameTree renames the directories under this directory
//
// path should be the desired path
func (d *Dir) renameTree(dirPath string) {
d.mu.Lock()
defer d.mu.Unlock()
// Make sure the path is correct for each node
if d.path != dirPath {
fs.Debugf(d.path, "Renaming to %q", dirPath)
delete(d.parent.items, name(d.path))
d.path = dirPath
d.parent.items[name(d.path)] = d
d.entry = fs.NewDirCopy(context.TODO(), d.entry).SetRemote(dirPath)
}
// Do the same to any child directories and files
for leaf, node := range d.items {
switch x := node.(type) {
case *Dir:
x.renameTree(path.Join(dirPath, leaf))
case *File:
x.renameDir(dirPath)
default:
panic("bad dir entry")
}
}
}
// rename should be called after the directory is renamed
//
// Reset the directory to new state, discarding all the objects and
// reading everything again
func (d *Dir) rename(newParent *Dir, fsDir fs.Directory) {
d.ForgetAll()
d.modTimeMu.Lock()
d.modTime = fsDir.ModTime(context.TODO())
d.modTimeMu.Unlock()
d.mu.Lock()
oldPath := d.path
d.parent = newParent
d.entry = fsDir
d.path = fsDir.Remote()
newPath := d.path
delete(d.parent.items, name(oldPath))
d.parent.items[name(d.path)] = d
d.read = time.Time{}
d.mu.Unlock()
// Rename any remaining items in the tree that we couldn't forget
d.renameTree(d.path)
// Rename in the cache
if d.vfs.cache != nil && d.vfs.cache.DirExists(oldPath) {
if err := d.vfs.cache.DirRename(oldPath, newPath); err != nil {
fs.Infof(d, "Dir.Rename failed in Cache: %v", err)
}
}
}
// convert path to name
func name(p string) string {
p = path.Base(p)
if p == "." {
p = "/"
}
return p
}
// addObject adds a new object or directory to the directory
//
// The name passed in is marked as virtual as it hasn't been read from a remote
// directory listing.
//
// note that we add new objects rather than updating old ones
func (d *Dir) addObject(node Node) {
d.mu.Lock()
leaf := node.Name()
d.items[leaf] = node
if d.virtual == nil {
d.virtual = make(map[string]vState)
}
vAdd := vAddFile
if node.IsDir() {
vAdd = vAddDir
}
d.virtual[leaf] = vAdd
d.setHasVirtual(true)
fs.Debugf(d.path, "Added virtual directory entry %v: %q", vAdd, leaf)
d.mu.Unlock()
}
// AddVirtual adds a virtual object of name and size to the directory
//
// This will be replaced with a real object when it is read back from the
// remote.
//
// This is used to add directory entries while things are uploading
func (d *Dir) AddVirtual(leaf string, size int64, isDir bool) {
var node Node
d.mu.RLock()
dPath := d.path
_, found := d.items[leaf]
d.mu.RUnlock()
if found {
// Don't overwrite existing objects
return
}
if isDir {
remote := path.Join(dPath, leaf)
entry := fs.NewDir(remote, time.Now())
node = newDir(d.vfs, d.f, d, entry)
} else {
f := newFile(d, dPath, nil, leaf)
f.setSize(size)
node = f
}
d.addObject(node)
}
// delObject removes an object from the directory
//
// The name passed in is marked as virtual as the delete it hasn't been read
// from a remote directory listing.
func (d *Dir) delObject(leaf string) {
d.mu.Lock()
delete(d.items, leaf)
if d.virtual == nil {
d.virtual = make(map[string]vState)
}
d.virtual[leaf] = vDel
d.setHasVirtual(true)
fs.Debugf(d.path, "Added virtual directory entry %v: %q", vDel, leaf)
d.mu.Unlock()
}
// DelVirtual removes an object from the directory listing
//
// It marks it as removed until it has confirmed the object is missing when the
// directory entries are re-read in.
//
// This is used to remove directory entries after things have been deleted or
// renamed but before we've had confirmation from the backend.
func (d *Dir) DelVirtual(leaf string) {
d.delObject(leaf)
}
// read the directory and sets d.items - must be called with the lock held
func (d *Dir) _readDir() error {
when := time.Now()
if age, stale := d._age(when); stale {
if age != 0 {
fs.Debugf(d.path, "Re-reading directory (%v old)", age)
}
} else {
return nil
}
entries, err := list.DirSorted(context.TODO(), d.f, false, d.path)
if err == fs.ErrorDirNotFound {
// We treat directory not found as empty because we
// create directories on the fly
} else if err != nil {
return err
}
if d.vfs.Opt.BlockNormDupes { // do this only if requested, as it will have a performance hit
ci := fs.GetConfig(context.TODO())
// sort entries such that NFD comes before NFC of same name
sort.Slice(entries, func(i, j int) bool {
if entries[i] != entries[j] && fs.DirEntryType(entries[i]) == fs.DirEntryType(entries[j]) && norm.NFC.String(entries[i].Remote()) == norm.NFC.String(entries[j].Remote()) {
if norm.NFD.IsNormalString(entries[i].Remote()) && !norm.NFD.IsNormalString(entries[j].Remote()) {
return true
}
}
return entries.Less(i, j)
})
// detect dupes, remove them from the list and log an error
normalizedNames := make(map[string]struct{}, entries.Len())
filteredEntries := make(fs.DirEntries, 0)
for _, e := range entries {
normName := fmt.Sprintf("%s-%T", operations.ToNormal(e.Remote(), !ci.NoUnicodeNormalization, (ci.IgnoreCaseSync || d.vfs.Opt.CaseInsensitive)), e) // include type to track objects and dirs separately
_, found := normalizedNames[normName]
if found {
fs.Errorf(e.Remote(), "duplicate normalized names detected - skipping")
continue
}
normalizedNames[normName] = struct{}{}
filteredEntries = append(filteredEntries, e)
}
entries = filteredEntries
}
err = d._readDirFromEntries(entries, nil, time.Time{})
if err != nil {
return err
}
d.read = when
d.cleanupTimer.Reset(d.vfs.Opt.DirCacheTime * 2)
return nil
}
// update d.items for each dir in the DirTree below this one and
// set the last read time - must be called with the lock held
func (d *Dir) _readDirFromDirTree(dirTree dirtree.DirTree, when time.Time) error {
return d._readDirFromEntries(dirTree[d.path], dirTree, when)
}
// Remove the virtual directory entry leaf
func (d *Dir) _deleteVirtual(name string) {
virtualState, ok := d.virtual[name]
if !ok {
return
}
delete(d.virtual, name)
if len(d.virtual) == 0 {
d.virtual = nil
d.setHasVirtual(false)
}
fs.Debugf(d.path, "Removed virtual directory entry %v: %q", virtualState, name)
}
// Purge virtual entries assuming the directory has just been re-read
//
// Remove all the entries except:
//
// 1) vDirAdd on remotes which can't have empty directories. These will remain
// virtual as long as the directory is empty. When the directory becomes real
// (ie files are added) the virtual directory will be removed. This means that
// directories will disappear when the last file is deleted which is probably
// OK.
//
// 2) vFileAdd that are being written or uploaded
func (d *Dir) _purgeVirtual() {
canHaveEmptyDirectories := d.f.Features().CanHaveEmptyDirectories
for name, virtualState := range d.virtual {
switch virtualState {
case vAddDir:
if canHaveEmptyDirectories {
// if remote can have empty directories then a
// new dir will be read in the listing
d._deleteVirtual(name)
//} else {
// leave the empty directory marker
}
case vAddFile:
// Delete all virtual file adds that have finished uploading
node, ok := d.items[name]
if !ok {
// if the object has disappeared somehow then remove the virtual
d._deleteVirtual(name)
continue
}
f, ok := node.(*File)
if !ok {
// if the object isn't a file then remove the virtual as it is wrong
d._deleteVirtual(name)
continue
}
if f.writingInProgress() {
// if writing in progress then leave virtual
continue
}
if d.vfs.Opt.CacheMode >= vfscommon.CacheModeMinimal && d.vfs.cache.InUse(f.Path()) {
// if object in use or dirty then leave virtual
continue
}
d._deleteVirtual(name)
default:
d._deleteVirtual(name)
}
}
}
// Manage the virtuals in a listing
//
// This keeps a record of the names listed in this directory so far
type manageVirtuals map[string]struct{}
// Create a new manageVirtuals and purge the d.virtuals of any entries which can
// be removed.
//
// must be called with the Dir lock held
func (d *Dir) _newManageVirtuals() manageVirtuals {
tv := make(manageVirtuals)
d._purgeVirtual()
return tv
}
// This should be called for every entry added to the directory
//
// It returns true if this entry should be skipped.
//
// must be called with the Dir lock held
func (mv manageVirtuals) add(d *Dir, name string) bool {
// Keep a record of all names listed
mv[name] = struct{}{}
// Remove virtuals if possible
switch d.virtual[name] {
case vAddFile, vAddDir:
// item was added to the dir but since it is found in a
// listing is no longer virtual
d._deleteVirtual(name)
case vDel:
// item is deleted from the dir so skip it
return true
case vOK:
}
return false
}
// This should be called after the directory entry is read to update d.items
// with virtual entries
//
// must be called with the Dir lock held
func (mv manageVirtuals) end(d *Dir) {
// delete unused d.items
for name := range d.items {
if _, ok := mv[name]; !ok {
// name was previously in the directory but wasn't found
// in the current listing
switch d.virtual[name] {
case vAddFile, vAddDir:
// virtually added so leave virtual item
default:
// otherwise delete it
delete(d.items, name)
}
}
}
// delete unused d.virtual~s
for name, virtualState := range d.virtual {
if _, ok := mv[name]; !ok {
// name exists as a virtual but isn't in the current
// listing so if it is a virtual delete we can remove it
// as it is no longer needed.
if virtualState == vDel {
d._deleteVirtual(name)
}
}
}
}
// update d.items and if dirTree is not nil update each dir in the DirTree below this one and
// set the last read time - must be called with the lock held
func (d *Dir) _readDirFromEntries(entries fs.DirEntries, dirTree dirtree.DirTree, when time.Time) error {
var err error
mv := d._newManageVirtuals()
for _, entry := range entries {
name := path.Base(entry.Remote())
if name == "." || name == ".." {
continue
}
node := d.items[name]
if mv.add(d, name) {
continue
}
switch item := entry.(type) {
case fs.Object:
obj := item
// Reuse old file value if it exists
if file, ok := node.(*File); node != nil && ok {
file.setObjectNoUpdate(obj)
} else {
node = newFile(d, d.path, obj, name)
}
case fs.Directory:
// Reuse old dir value if it exists
if node == nil || !node.IsDir() {
node = newDir(d.vfs, d.f, d, item)
}
dir := node.(*Dir)
dir.mu.Lock()
dir.modTime = item.ModTime(context.TODO())
if dirTree != nil {
err = dir._readDirFromDirTree(dirTree, when)
if err != nil {
dir.read = time.Time{}
} else {
dir.read = when
dir.cleanupTimer.Reset(d.vfs.Opt.DirCacheTime * 2)
}
}
dir.mu.Unlock()
if err != nil {
return err
}
default:
err = fmt.Errorf("unknown type %T", item)
fs.Errorf(d, "readDir error: %v", err)
return err
}
d.items[name] = node
}
mv.end(d)
return nil
}
// readDirTree forces a refresh of the complete directory tree
func (d *Dir) readDirTree() error {
d.mu.RLock()
f, path := d.f, d.path
d.mu.RUnlock()
when := time.Now()
fs.Debugf(path, "Reading directory tree")
dt, err := walk.NewDirTree(context.TODO(), f, path, false, -1)
if err != nil {
return err
}
d.mu.Lock()
defer d.mu.Unlock()
d.read = time.Time{}
err = d._readDirFromDirTree(dt, when)
if err != nil {
return err
}
fs.Debugf(d.path, "Reading directory tree done in %s", time.Since(when))
d.read = when
d.cleanupTimer.Reset(d.vfs.Opt.DirCacheTime * 2)
return nil
}
// readDir forces a refresh of the directory
func (d *Dir) readDir() error {
d.mu.Lock()
defer d.mu.Unlock()
d.read = time.Time{}
return d._readDir()
}
// stat a single item in the directory
//
// returns ENOENT if not found.
// returns a custom error if directory on a case-insensitive file system
// contains files with names that differ only by case.
func (d *Dir) stat(leaf string) (Node, error) {
d.mu.Lock()
defer d.mu.Unlock()
err := d._readDir()
if err != nil {
return nil, err
}
item, ok := d.items[leaf]
ci := fs.GetConfig(context.TODO())
normUnicode := !ci.NoUnicodeNormalization
normCase := ci.IgnoreCaseSync || d.vfs.Opt.CaseInsensitive
if !ok && (normUnicode || normCase) {
leafNormalized := operations.ToNormal(leaf, normUnicode, normCase) // this handles both case and unicode normalization
for name, node := range d.items {
if operations.ToNormal(name, normUnicode, normCase) == leafNormalized {
if ok {
// duplicate normalized match is an error
return nil, fmt.Errorf("duplicate filename %q detected with case/unicode normalization settings", leaf)
}
// found a normalized match
ok = true
item = node
}
}
}
if !ok {
return nil, ENOENT
}
return item, nil
}
// Check to see if a directory is empty
func (d *Dir) isEmpty() (bool, error) {
d.mu.Lock()
defer d.mu.Unlock()
err := d._readDir()
if err != nil {
return false, err
}
return len(d.items) == 0, nil
}
// ModTime returns the modification time of the directory
func (d *Dir) ModTime() time.Time {
d.modTimeMu.Lock()
defer d.modTimeMu.Unlock()
// fs.Debugf(d.path, "Dir.ModTime %v", d.modTime)
return d.modTime
}
// Size of the directory
func (d *Dir) Size() int64 {
return 0
}
// SetModTime sets the modTime for this dir
func (d *Dir) SetModTime(modTime time.Time) error {
if d.vfs.Opt.ReadOnly {
return EROFS
}
d.modTimeMu.Lock()
d.modTime = modTime
d.modTimeMu.Unlock()
return nil
}
func (d *Dir) cachedDir(relativePath string) (dir *Dir) {
dir, _ = d.cachedNode(relativePath).(*Dir)
return
}
func (d *Dir) cachedNode(relativePath string) Node {
segments := strings.Split(strings.Trim(relativePath, "/"), "/")
var node Node = d
for _, s := range segments {
if s == "" {
continue
}
if dir, ok := node.(*Dir); ok {
dir.mu.Lock()
node = dir.items[s]
dir.mu.Unlock()
if node != nil {
continue
}
}
return nil
}
return node
}
// Stat looks up a specific entry in the receiver.
//
// Stat should return a Node corresponding to the entry. If the
// name does not exist in the directory, Stat should return ENOENT.
//
// Stat need not to handle the names "." and "..".
func (d *Dir) Stat(name string) (node Node, err error) {
// fs.Debugf(path, "Dir.Stat")
node, err = d.stat(name)
if err != nil {
if err != ENOENT {
fs.Errorf(d, "Dir.Stat error: %v", err)
}
return nil, err
}
// fs.Debugf(path, "Dir.Stat OK")
return node, nil
}
// ReadDirAll reads the contents of the directory sorted
func (d *Dir) ReadDirAll() (items Nodes, err error) {
// fs.Debugf(d.path, "Dir.ReadDirAll")
d.mu.Lock()
err = d._readDir()
if err != nil {
fs.Debugf(d.path, "Dir.ReadDirAll error: %v", err)
d.mu.Unlock()
return nil, err
}
for _, item := range d.items {
items = append(items, item)
}
d.mu.Unlock()
sort.Sort(items)
// fs.Debugf(d.path, "Dir.ReadDirAll OK with %d entries", len(items))
return items, nil
}
// accessModeMask masks off the read modes from the flags
const accessModeMask = (os.O_RDONLY | os.O_WRONLY | os.O_RDWR)
// Open the directory according to the flags provided
func (d *Dir) Open(flags int) (fd Handle, err error) {
rdwrMode := flags & accessModeMask
if rdwrMode != os.O_RDONLY {
fs.Errorf(d, "Can only open directories read only")
return nil, EPERM
}
return newDirHandle(d), nil
}
// Create makes a new file node
func (d *Dir) Create(name string, flags int) (*File, error) {
// fs.Debugf(path, "Dir.Create")
// Return existing node if one exists
node, err := d.stat(name)
switch err {
case ENOENT:
// not found, carry on
case nil:
// found so check what it is
if node.IsFile() {
return node.(*File), err
}
return nil, EEXIST // EISDIR would be better but we don't have that
default:
// a different error - report
fs.Errorf(d, "Dir.Create stat failed: %v", err)
return nil, err
}
// node doesn't exist so create it
if d.vfs.Opt.ReadOnly {
return nil, EROFS
}
if err = d.SetModTime(time.Now()); err != nil {
fs.Errorf(d, "Dir.Create failed to set modtime on parent dir: %v", err)
return nil, err
}
// This gets added to the directory when the file is opened for write
return newFile(d, d.Path(), nil, name), nil
}
// Mkdir creates a new directory
func (d *Dir) Mkdir(name string) (*Dir, error) {
if d.vfs.Opt.ReadOnly {
return nil, EROFS
}
path := path.Join(d.path, name)
node, err := d.stat(name)
switch err {
case ENOENT:
// not found, carry on
case nil:
// found so check what it is
if node.IsDir() {
return node.(*Dir), err
}
return nil, EEXIST
default:
// a different error - report
fs.Errorf(d, "Dir.Mkdir failed to read directory: %v", err)
return nil, err
}
// fs.Debugf(path, "Dir.Mkdir")
err = d.f.Mkdir(context.TODO(), path)
if err != nil {
fs.Errorf(d, "Dir.Mkdir failed to create directory: %v", err)
return nil, err
}
fsDir := fs.NewDir(path, time.Now())
dir := newDir(d.vfs, d.f, d, fsDir)
d.addObject(dir)
if err = d.SetModTime(time.Now()); err != nil {
fs.Errorf(d, "Dir.Mkdir failed to set modtime on parent dir: %v", err)
return nil, err
}
// fs.Debugf(path, "Dir.Mkdir OK")
return dir, nil
}
// Remove the directory
func (d *Dir) Remove() error {
if d.vfs.Opt.ReadOnly {
return EROFS
}
// Check directory is empty first
empty, err := d.isEmpty()
if err != nil {
fs.Errorf(d, "Dir.Remove dir error: %v", err)
return err
}
if !empty {
fs.Errorf(d, "Dir.Remove not empty")
return ENOTEMPTY
}
// remove directory
err = d.f.Rmdir(context.TODO(), d.path)
if err != nil {
fs.Errorf(d, "Dir.Remove failed to remove directory: %v", err)
return err
}
// Remove the item from the parent directory listing
if d.parent != nil {
d.parent.delObject(d.Name())
}
return nil
}
// RemoveAll removes the directory and any contents recursively
func (d *Dir) RemoveAll() error {
if d.vfs.Opt.ReadOnly {
return EROFS
}
// Remove contents of the directory
nodes, err := d.ReadDirAll()
if err != nil {
fs.Errorf(d, "Dir.RemoveAll failed to read directory: %v", err)
return err
}
for _, node := range nodes {
err = node.RemoveAll()
if err != nil {
fs.Errorf(node.Path(), "Dir.RemoveAll failed to remove: %v", err)
return err
}
}
return d.Remove()
}
// DirEntry returns the underlying fs.DirEntry
func (d *Dir) DirEntry() (entry fs.DirEntry) {
return d.entry
}
// RemoveName removes the entry with the given name from the receiver,
// which must be a directory. The entry to be removed may correspond
// to a file (unlink) or to a directory (rmdir).
func (d *Dir) RemoveName(name string) error {
if d.vfs.Opt.ReadOnly {
return EROFS
}
// fs.Debugf(path, "Dir.Remove")
node, err := d.stat(name)
if err != nil {
fs.Errorf(d, "Dir.Remove error: %v", err)
return err
}
if err = d.SetModTime(time.Now()); err != nil {
fs.Errorf(d, "Dir.Remove failed to set modtime on parent dir: %v", err)
return err
}
return node.Remove()
}
// Rename the file
func (d *Dir) Rename(oldName, newName string, destDir *Dir) error {
// fs.Debugf(d, "BEFORE\n%s", d.dump())
if d.vfs.Opt.ReadOnly {
return EROFS
}
oldPath := path.Join(d.path, oldName)
newPath := path.Join(destDir.path, newName)
// fs.Debugf(oldPath, "Dir.Rename to %q", newPath)
oldNode, err := d.stat(oldName)
if err != nil {
fs.Errorf(oldPath, "Dir.Rename error: %v", err)
return err
}
switch x := oldNode.DirEntry().(type) {
case nil:
if oldFile, ok := oldNode.(*File); ok {
if err = oldFile.rename(context.TODO(), destDir, newName); err != nil {
fs.Errorf(oldPath, "Dir.Rename error: %v", err)
return err
}
} else {
fs.Errorf(oldPath, "Dir.Rename can't rename open file that is not a vfs.File")
return EPERM
}
case fs.Object:
if oldFile, ok := oldNode.(*File); ok {
if err = oldFile.rename(context.TODO(), destDir, newName); err != nil {
fs.Errorf(oldPath, "Dir.Rename error: %v", err)
return err
}
} else {
err := fmt.Errorf("Fs %q can't rename file that is not a vfs.File", d.f)
fs.Errorf(oldPath, "Dir.Rename error: %v", err)
return err
}
case fs.Directory:
features := d.f.Features()
if features.DirMove == nil && features.Move == nil && features.Copy == nil {
err := fmt.Errorf("Fs %q can't rename directories (no DirMove, Move or Copy)", d.f)
fs.Errorf(oldPath, "Dir.Rename error: %v", err)
return err
}
srcRemote := x.Remote()
dstRemote := newPath
err = operations.DirMove(context.TODO(), d.f, srcRemote, dstRemote)
if err != nil {
fs.Errorf(oldPath, "Dir.Rename error: %v", err)
return err
}
newDir := fs.NewDirCopy(context.TODO(), x).SetRemote(newPath)
// Update the node with the new details
if oldNode != nil {
if oldDir, ok := oldNode.(*Dir); ok {
fs.Debugf(x, "Updating dir with %v %p", newDir, oldDir)
oldDir.rename(destDir, newDir)
}
}
default:
err = fmt.Errorf("unknown type %T", oldNode)
fs.Errorf(d.path, "Dir.Rename error: %v", err)
return err
}
// Show moved - delete from old dir and add to new
d.delObject(oldName)
destDir.addObject(oldNode)
if err = d.SetModTime(time.Now()); err != nil {
fs.Errorf(d, "Dir.Rename failed to set modtime on parent dir: %v", err)
return err
}
// fs.Debugf(newPath, "Dir.Rename renamed from %q", oldPath)
// fs.Debugf(d, "AFTER\n%s", d.dump())
return nil
}
// Sync the directory
//
// Note that we don't do anything except return OK
func (d *Dir) Sync() error {
return nil
}
// VFS returns the instance of the VFS
func (d *Dir) VFS() *VFS {
// No locking required
return d.vfs
}
// Fs returns the Fs that the Dir is on
func (d *Dir) Fs() fs.Fs {
// No locking required
return d.f
}
// Truncate changes the size of the named file.
func (d *Dir) Truncate(size int64) error {
return ENOSYS
}