Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions cache.c
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,10 @@ static int cache_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
struct node *node;
struct cache_dirent **cdent;

/* Removed or replaced through this mount, so it had to be empty */
if (path == NULL)
return 0;

/*
* sshfs uses mode-1 readdir: the offset parameter is ignored and the
* complete listing is always handed to the filler with offset 0;
Expand Down
30 changes: 17 additions & 13 deletions sshfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ struct dir_handle {
* https://github.com/libfuse/sshfs/issues/338
*/
GPtrArray *entries; /* array of struct dir_entry * */
char *path; /* directory path, for reopening the handle */
char *path; /* path the current SFTP handle was opened with */
int read_err; /* error from the last read, if any */
int has_handle; /* buf holds an open SFTP dir handle to close */
int buf_exhausted; /* the SFTP handle has been read to EOF */
Expand Down Expand Up @@ -1989,12 +1989,8 @@ static void *sshfs_init(struct fuse_conn_info *conn,
if (conn->capable & FUSE_CAP_ASYNC_READ)
sshfs.sync_read = 1;

// These workarounds require the "path" argument.
cfg->nullpath_ok = !(sshfs.truncate_workaround || sshfs.fstat_workaround);

// When using multiple connections, release() needs to know the path
if (sshfs.max_conns > 1)
cfg->nullpath_ok = 0;
// readdir reopens directories at their current path
cfg->nullpath_ok = 0;

// Lookup of . and .. is supported
conn->capable |= FUSE_CAP_EXPORT_SUPPORT;
Expand Down Expand Up @@ -2470,7 +2466,7 @@ static void free_dir_entry(gpointer data)
* directory from the start means closing the exhausted handle and opening a
* fresh one.
*/
static int sshfs_reopen_dir(struct dir_handle *handle)
static int sshfs_reopen_dir(struct dir_handle *handle, const char *path)
{
struct buffer buf;
int err;
Expand All @@ -2482,14 +2478,16 @@ static int sshfs_reopen_dir(struct dir_handle *handle)
handle->has_handle = 0;
}
buf_init(&buf, 0);
buf_add_path(&buf, handle->path);
buf_add_path(&buf, path);
err = sftp_request(handle->conn, SSH_FXP_OPENDIR, &buf,
SSH_FXP_HANDLE, &handle->buf);
buf_free(&buf);
if (!err) {
buf_finish(&handle->buf);
handle->has_handle = 1;
handle->buf_exhausted = 0;
g_free(handle->path);
handle->path = g_strdup(path);
}
return err;
}
Expand All @@ -2498,25 +2496,31 @@ static int sshfs_readdir(const char *path, void *dbuf, fuse_fill_dir_t filler,
off_t offset, struct fuse_file_info *fi,
enum fuse_readdir_flags flags)
{
(void) path; (void) flags;
(void) flags;
unsigned i;
struct dir_handle *handle;

handle = (struct dir_handle*) fi->fh;

/* Removed or replaced through this mount, so it had to be empty */
if (path == NULL)
return 0;

/*
* offset == 0 starts a fresh enumeration (a new listing or a
* rewinddir), so re-read the directory from the server to pick up newly
* created files and updated attributes. The SFTP handle is single-pass:
* once read to EOF, reusing it would read nothing and the directory
* would appear empty, so we reopen a fresh handle first. offset != 0 is
* a continuation of the current enumeration and is served from the
* snapshot taken at offset 0.
* snapshot taken at offset 0. OpenSSH resolves entries against the
* OPENDIR path, so also reopen a handle whose directory was renamed.
* See: https://github.com/libfuse/sshfs/issues/338
*/
if (offset == 0 || handle->entries == NULL) {
if (handle->buf_exhausted)
handle->read_err = sshfs_reopen_dir(handle);
if (handle->buf_exhausted || !handle->has_handle ||
strcmp(path, handle->path) != 0)
handle->read_err = sshfs_reopen_dir(handle, path);
if (!handle->read_err) {
if (handle->entries)
g_ptr_array_free(handle->entries, TRUE);
Expand Down
30 changes: 30 additions & 0 deletions test/test_sshfs.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ def test_sshfs(
tst_readdir_repeated(mnt_dir)
tst_rename_sibling(mnt_dir)
tst_rename_open_release(mnt_dir)
tst_rewinddir_after_rename(src_dir, mnt_dir)
except Exception as exc:
cleanup(mount_process, mnt_dir)
raise exc
Expand Down Expand Up @@ -532,6 +533,35 @@ def tst_rename_open_release(mnt_dir):
os.unlink(dst)


def tst_rewinddir_after_rename(src_dir, mnt_dir):
# Open directory streams must follow a rename, not the old path
name = name_generator()
old, new = pjoin(mnt_dir, name), pjoin(mnt_dir, name_generator())
names = {name_generator() for _ in range(5)}
os.mkdir(pjoin(src_dir, name))
for n in names:
open(pjoin(src_dir, name, n), "wb").close()

rewound = os.open(old, os.O_RDONLY | os.O_DIRECTORY)
unread = os.open(old, os.O_RDONLY | os.O_DIRECTORY)
try:
assert set(os.listdir(rewound)) == names
os.rename(old, new)
os.mkdir(old)
open(pjoin(old, "marker"), "wb").close()
open(pjoin(new, "fresh"), "wb").close()
names.add("fresh")
# Rewind before reading unread, which would refill the dir cache
os.lseek(rewound, 0, os.SEEK_SET)
assert set(os.listdir(rewound)) == names
assert set(os.listdir(unread)) == names
finally:
os.close(rewound)
os.close(unread)
shutil.rmtree(old)
shutil.rmtree(new)


def tst_link(mnt_dir, cache_timeout):
name1 = pjoin(mnt_dir, name_generator())
name2 = pjoin(mnt_dir, name_generator())
Expand Down
Loading