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
17 changes: 14 additions & 3 deletions cache.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@

#include "cache.h"
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
Expand Down Expand Up @@ -386,7 +385,18 @@ static int cache_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
struct node *node;
struct cache_dirent **cdent;

assert(offset == 0);
/*
* sshfs uses mode-1 readdir: the offset parameter is ignored and the
* complete listing is always handed to the filler with offset 0;
* libfuse caches the entries and does the offset-based slicing
* itself. Being called with a non-zero offset is valid (e.g. FSKit
* on macOS resuming an enumeration from a saved cookie with a fresh
* directory handle) and must still produce the full listing --
* returning nothing here would be interpreted as an empty directory
* and make entries silently vanish.
* See: https://github.com/libfuse/sshfs/issues/338
*/
(void) offset;

pthread_mutex_lock(&cache.lock);
node = cache_lookup(path);
Expand Down Expand Up @@ -421,7 +431,8 @@ static int cache_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
ch.dir = g_ptr_array_new();
g_ptr_array_set_free_func(ch.dir, free_cache_dirent);
ch.wrctr = cache_get_write_ctr();
err = cache.next_oper->readdir(path, &ch, cache_dirfill, offset, fi, flags);
/* Always request a fresh, complete enumeration to (re)fill the cache */
err = cache.next_oper->readdir(path, &ch, cache_dirfill, 0, fi, flags);
g_ptr_array_add(ch.dir, NULL);
dir = ch.dir;
if (!err) {
Expand Down
140 changes: 121 additions & 19 deletions sshfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -228,9 +228,29 @@ struct buffer {
size_t size;
};

struct dir_entry {
char *name;
struct stat stbuf;
};

struct dir_handle {
struct buffer buf;
struct conn *conn;
/*
* Snapshot of the *current* enumeration. sshfs uses old-style readdir
* over a single-pass SFTP directory handle. Each new enumeration
* (offset == 0, e.g. a fresh listing or a rewinddir) re-reads the
* directory from the server so newly created files and updated
* attributes show up; a continuation (offset != 0) is served from this
* snapshot so the listing never comes back empty when the handle has
* already been exhausted. See:
* https://github.com/libfuse/sshfs/issues/338
*/
GPtrArray *entries; /* array of struct dir_entry * */
char *path; /* directory path, for reopening the handle */
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 */
};

struct list_head {
Expand Down Expand Up @@ -958,8 +978,7 @@ static int buf_get_statvfs(struct buffer *buf, struct statvfs *stbuf)
return 0;
}

static int buf_get_entries(struct buffer *buf, void *dbuf,
fuse_fill_dir_t filler)
static int buf_get_entries(struct buffer *buf, GPtrArray *entries)
{
uint32_t count;
unsigned i;
Expand All @@ -978,11 +997,16 @@ static int buf_get_entries(struct buffer *buf, void *dbuf,
free(longname);
err = buf_get_attrs(buf, &stbuf, NULL);
if (!err) {
struct dir_entry *entry;
if (sshfs.follow_symlinks &&
S_ISLNK(stbuf.st_mode)) {
stbuf.st_mode = 0;
}
filler(dbuf, name, &stbuf, 0, 0);
entry = g_new(struct dir_entry, 1);
entry->name = name;
entry->stbuf = stbuf;
g_ptr_array_add(entries, entry);
name = NULL; /* ownership passed to entry */
}
}
free(name);
Expand Down Expand Up @@ -2309,7 +2333,7 @@ static int sshfs_req_pending(struct request *req)
}

static int sftp_readdir_async(struct conn *conn, struct buffer *handle,
void *buf, off_t offset, fuse_fill_dir_t filler)
GPtrArray *entries)
{
int err = 0;
int outstanding = 0;
Expand All @@ -2318,7 +2342,6 @@ static int sftp_readdir_async(struct conn *conn, struct buffer *handle,

int done = 0;

assert(offset == 0);
while (!done || outstanding) {
struct request *req;
struct buffer name;
Expand Down Expand Up @@ -2369,7 +2392,7 @@ static int sftp_readdir_async(struct conn *conn, struct buffer *handle,
done = 1;
}
if (!done) {
err = buf_get_entries(&name, buf, filler);
err = buf_get_entries(&name, entries);
buf_free(&name);

/* increase number of outstanding requests */
Expand All @@ -2387,15 +2410,14 @@ static int sftp_readdir_async(struct conn *conn, struct buffer *handle,
}

static int sftp_readdir_sync(struct conn *conn, struct buffer *handle,
void *buf, off_t offset, fuse_fill_dir_t filler)
GPtrArray *entries)
{
int err;
assert(offset == 0);
do {
struct buffer name;
err = sftp_request(conn, SSH_FXP_READDIR, handle, SSH_FXP_NAME, &name);
if (!err) {
err = buf_get_entries(&name, buf, filler);
err = buf_get_entries(&name, entries);
buf_free(&name);
}
} while (!err);
Expand Down Expand Up @@ -2427,31 +2449,104 @@ static int sshfs_opendir(const char *path, struct fuse_file_info *fi)
handle->conn = conn;
handle->conn->dir_count++;
pthread_mutex_unlock(&sshfs.lock);
handle->has_handle = 1;
handle->path = g_strdup(path);
fi->fh = (unsigned long) handle;
} else
g_free(handle);
buf_free(&buf);
return err;
}

static void free_dir_entry(gpointer data)
{
struct dir_entry *entry = data;
free(entry->name);
g_free(entry);
}

/*
* Reopen the SFTP directory handle. SFTP has no rewind, so re-reading a
* directory from the start means closing the exhausted handle and opening a
* fresh one.
*/
static int sshfs_reopen_dir(struct dir_handle *handle)
{
struct buffer buf;
int err;

if (handle->has_handle) {
sftp_request(handle->conn, SSH_FXP_CLOSE, &handle->buf, 0, NULL);
/* free + reset to a clean state so sftp_request can refill it */
buf_clear(&handle->buf);
handle->has_handle = 0;
}
buf_init(&buf, 0);
buf_add_path(&buf, handle->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;
}
return err;
}

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;
int err;
unsigned i;
struct dir_handle *handle;

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

if (sshfs.sync_readdir)
err = sftp_readdir_sync(handle->conn, &handle->buf, dbuf,
offset, filler);
else
err = sftp_readdir_async(handle->conn, &handle->buf, dbuf,
offset, filler);
/*
* 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.
* 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->read_err) {
if (handle->entries)
g_ptr_array_free(handle->entries, TRUE);
handle->entries =
g_ptr_array_new_with_free_func(free_dir_entry);
if (sshfs.sync_readdir)
handle->read_err = sftp_readdir_sync(
handle->conn, &handle->buf,
handle->entries);
else
handle->read_err = sftp_readdir_async(
handle->conn, &handle->buf,
handle->entries);
handle->buf_exhausted = 1;
}
}

return err;
if (handle->read_err)
return handle->read_err;

/*
* Old-style readdir: hand FUSE the complete listing with offset 0 and
* let it do the offset-based slicing itself.
*/
for (i = 0; i < handle->entries->len; i++) {
struct dir_entry *entry = g_ptr_array_index(handle->entries, i);
filler(dbuf, entry->name, &entry->stbuf, 0, 0);
}

return 0;
}

static int sshfs_releasedir(const char *path, struct fuse_file_info *fi)
Expand All @@ -2461,11 +2556,18 @@ static int sshfs_releasedir(const char *path, struct fuse_file_info *fi)
struct dir_handle *handle;

handle = (struct dir_handle*) fi->fh;
err = sftp_request(handle->conn, SSH_FXP_CLOSE, &handle->buf, 0, NULL);
err = 0;
if (handle->has_handle) {
err = sftp_request(handle->conn, SSH_FXP_CLOSE, &handle->buf,
0, NULL);
buf_free(&handle->buf);
}
pthread_mutex_lock(&sshfs.lock);
handle->conn->dir_count--;
pthread_mutex_unlock(&sshfs.lock);
buf_free(&handle->buf);
if (handle->entries)
g_ptr_array_free(handle->entries, TRUE);
g_free(handle->path);
g_free(handle);
return err;
}
Expand Down