From 4ac662d144c0956e5e13cbcfd433b969928f56ce Mon Sep 17 00:00:00 2001 From: Nick Craig-Wood Date: Sun, 5 Jul 2020 12:09:49 +0100 Subject: [PATCH] cmount: fix macOS losing directory contents #4393 Before this change when reading directories we would use the directory handle and the Readdir(-1) call on the directory handle. This worked fine for the first read, but if the directory was read again on the same handle Readdir(-1) returns nothing (as per its design). It turns out that macOS leaves the directory handle open and just re-reads the data from it, so this problem causes directories to start out full then subsequently appear empty. macOS/OSXFUSE is passing an offset of 0 to the Readdir call telling rclone to seek in the directory, but we've told FUSE that we can't seek by always returning ofst=0 in the fill function. This fix works around the problem by reading the directory from the path each time, ignoring the actual handle. This should be no less efficient. We will return an ESPIPE if offset is ever non 0. There are possible corner cases reading deleted directories which this ignores. --- cmd/cmount/fs.go | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/cmd/cmount/fs.go b/cmd/cmount/fs.go index 883bbf069..d3f992514 100644 --- a/cmd/cmount/fs.go +++ b/cmd/cmount/fs.go @@ -217,12 +217,18 @@ func (fsys *FS) Readdir(dirPath string, itemsRead := -1 defer log.Trace(dirPath, "ofst=%d, fh=0x%X", ofst, fh)("items=%d, errc=%d", &itemsRead, &errc) - node, errc := fsys.getHandle(fh) + dir, errc := fsys.lookupDir(dirPath) if errc != 0 { return errc } - items, err := node.Readdir(-1) + // We can't seek in directories and FUSE should know that so + // return an error if ofst is ever set. + if ofst > 0 { + return -fuse.ESPIPE + } + + nodes, err := dir.ReadDirAll() if err != nil { return translateError(err) } @@ -242,22 +248,19 @@ func (fsys *FS) Readdir(dirPath string, // directory is read in a single readdir operation. fill(".", nil, 0) fill("..", nil, 0) - for _, item := range items { - node, ok := item.(vfs.Node) - if ok { - name := node.Name() - if len(name) > mountlib.MaxLeafSize { - fs.Errorf(dirPath, "Name too long (%d bytes) for FUSE, skipping: %s", len(name), name) - continue - } - // We have called host.SetCapReaddirPlus() so supply the stat information - // It is very cheap at this point so supply it regardless of OS capabilities - var stat fuse.Stat_t - _ = fsys.stat(node, &stat) // not capable of returning an error - fill(name, &stat, 0) + for _, node := range nodes { + name := node.Name() + if len(name) > mountlib.MaxLeafSize { + fs.Errorf(dirPath, "Name too long (%d bytes) for FUSE, skipping: %s", len(name), name) + continue } + // We have called host.SetCapReaddirPlus() so supply the stat information + // It is very cheap at this point so supply it regardless of OS capabilities + var stat fuse.Stat_t + _ = fsys.stat(node, &stat) // not capable of returning an error + fill(name, &stat, 0) } - itemsRead = len(items) + itemsRead = len(nodes) return 0 }