Skip to content

Commit e7a0c1a

Browse files
authored
Merge pull request #379 from Yike-Ye/fix/readdir-full-snapshot
fix: handle readdir offset != 0 by snapshotting the directory
2 parents 8bf7f92 + c14a6e7 commit e7a0c1a

2 files changed

Lines changed: 135 additions & 22 deletions

File tree

cache.c

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88

99
#include "cache.h"
1010
#include <stdio.h>
11-
#include <assert.h>
1211
#include <stdlib.h>
1312
#include <string.h>
1413
#include <errno.h>
@@ -386,7 +385,18 @@ static int cache_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
386385
struct node *node;
387386
struct cache_dirent **cdent;
388387

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

391401
pthread_mutex_lock(&cache.lock);
392402
node = cache_lookup(path);
@@ -421,7 +431,8 @@ static int cache_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
421431
ch.dir = g_ptr_array_new();
422432
g_ptr_array_set_free_func(ch.dir, free_cache_dirent);
423433
ch.wrctr = cache_get_write_ctr();
424-
err = cache.next_oper->readdir(path, &ch, cache_dirfill, offset, fi, flags);
434+
/* Always request a fresh, complete enumeration to (re)fill the cache */
435+
err = cache.next_oper->readdir(path, &ch, cache_dirfill, 0, fi, flags);
425436
g_ptr_array_add(ch.dir, NULL);
426437
dir = ch.dir;
427438
if (!err) {

sshfs.c

Lines changed: 121 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -228,9 +228,29 @@ struct buffer {
228228
size_t size;
229229
};
230230

231+
struct dir_entry {
232+
char *name;
233+
struct stat stbuf;
234+
};
235+
231236
struct dir_handle {
232237
struct buffer buf;
233238
struct conn *conn;
239+
/*
240+
* Snapshot of the *current* enumeration. sshfs uses old-style readdir
241+
* over a single-pass SFTP directory handle. Each new enumeration
242+
* (offset == 0, e.g. a fresh listing or a rewinddir) re-reads the
243+
* directory from the server so newly created files and updated
244+
* attributes show up; a continuation (offset != 0) is served from this
245+
* snapshot so the listing never comes back empty when the handle has
246+
* already been exhausted. See:
247+
* https://github.com/libfuse/sshfs/issues/338
248+
*/
249+
GPtrArray *entries; /* array of struct dir_entry * */
250+
char *path; /* directory path, for reopening the handle */
251+
int read_err; /* error from the last read, if any */
252+
int has_handle; /* buf holds an open SFTP dir handle to close */
253+
int buf_exhausted; /* the SFTP handle has been read to EOF */
234254
};
235255

236256
struct list_head {
@@ -958,8 +978,7 @@ static int buf_get_statvfs(struct buffer *buf, struct statvfs *stbuf)
958978
return 0;
959979
}
960980

961-
static int buf_get_entries(struct buffer *buf, void *dbuf,
962-
fuse_fill_dir_t filler)
981+
static int buf_get_entries(struct buffer *buf, GPtrArray *entries)
963982
{
964983
uint32_t count;
965984
unsigned i;
@@ -978,11 +997,16 @@ static int buf_get_entries(struct buffer *buf, void *dbuf,
978997
free(longname);
979998
err = buf_get_attrs(buf, &stbuf, NULL);
980999
if (!err) {
1000+
struct dir_entry *entry;
9811001
if (sshfs.follow_symlinks &&
9821002
S_ISLNK(stbuf.st_mode)) {
9831003
stbuf.st_mode = 0;
9841004
}
985-
filler(dbuf, name, &stbuf, 0, 0);
1005+
entry = g_new(struct dir_entry, 1);
1006+
entry->name = name;
1007+
entry->stbuf = stbuf;
1008+
g_ptr_array_add(entries, entry);
1009+
name = NULL; /* ownership passed to entry */
9861010
}
9871011
}
9881012
free(name);
@@ -2309,7 +2333,7 @@ static int sshfs_req_pending(struct request *req)
23092333
}
23102334

23112335
static int sftp_readdir_async(struct conn *conn, struct buffer *handle,
2312-
void *buf, off_t offset, fuse_fill_dir_t filler)
2336+
GPtrArray *entries)
23132337
{
23142338
int err = 0;
23152339
int outstanding = 0;
@@ -2318,7 +2342,6 @@ static int sftp_readdir_async(struct conn *conn, struct buffer *handle,
23182342

23192343
int done = 0;
23202344

2321-
assert(offset == 0);
23222345
while (!done || outstanding) {
23232346
struct request *req;
23242347
struct buffer name;
@@ -2369,7 +2392,7 @@ static int sftp_readdir_async(struct conn *conn, struct buffer *handle,
23692392
done = 1;
23702393
}
23712394
if (!done) {
2372-
err = buf_get_entries(&name, buf, filler);
2395+
err = buf_get_entries(&name, entries);
23732396
buf_free(&name);
23742397

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

23892412
static int sftp_readdir_sync(struct conn *conn, struct buffer *handle,
2390-
void *buf, off_t offset, fuse_fill_dir_t filler)
2413+
GPtrArray *entries)
23912414
{
23922415
int err;
2393-
assert(offset == 0);
23942416
do {
23952417
struct buffer name;
23962418
err = sftp_request(conn, SSH_FXP_READDIR, handle, SSH_FXP_NAME, &name);
23972419
if (!err) {
2398-
err = buf_get_entries(&name, buf, filler);
2420+
err = buf_get_entries(&name, entries);
23992421
buf_free(&name);
24002422
}
24012423
} while (!err);
@@ -2427,31 +2449,104 @@ static int sshfs_opendir(const char *path, struct fuse_file_info *fi)
24272449
handle->conn = conn;
24282450
handle->conn->dir_count++;
24292451
pthread_mutex_unlock(&sshfs.lock);
2452+
handle->has_handle = 1;
2453+
handle->path = g_strdup(path);
24302454
fi->fh = (unsigned long) handle;
24312455
} else
24322456
g_free(handle);
24332457
buf_free(&buf);
24342458
return err;
24352459
}
24362460

2461+
static void free_dir_entry(gpointer data)
2462+
{
2463+
struct dir_entry *entry = data;
2464+
free(entry->name);
2465+
g_free(entry);
2466+
}
2467+
2468+
/*
2469+
* Reopen the SFTP directory handle. SFTP has no rewind, so re-reading a
2470+
* directory from the start means closing the exhausted handle and opening a
2471+
* fresh one.
2472+
*/
2473+
static int sshfs_reopen_dir(struct dir_handle *handle)
2474+
{
2475+
struct buffer buf;
2476+
int err;
2477+
2478+
if (handle->has_handle) {
2479+
sftp_request(handle->conn, SSH_FXP_CLOSE, &handle->buf, 0, NULL);
2480+
/* free + reset to a clean state so sftp_request can refill it */
2481+
buf_clear(&handle->buf);
2482+
handle->has_handle = 0;
2483+
}
2484+
buf_init(&buf, 0);
2485+
buf_add_path(&buf, handle->path);
2486+
err = sftp_request(handle->conn, SSH_FXP_OPENDIR, &buf,
2487+
SSH_FXP_HANDLE, &handle->buf);
2488+
buf_free(&buf);
2489+
if (!err) {
2490+
buf_finish(&handle->buf);
2491+
handle->has_handle = 1;
2492+
handle->buf_exhausted = 0;
2493+
}
2494+
return err;
2495+
}
2496+
24372497
static int sshfs_readdir(const char *path, void *dbuf, fuse_fill_dir_t filler,
24382498
off_t offset, struct fuse_file_info *fi,
24392499
enum fuse_readdir_flags flags)
24402500
{
24412501
(void) path; (void) flags;
2442-
int err;
2502+
unsigned i;
24432503
struct dir_handle *handle;
24442504

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

2447-
if (sshfs.sync_readdir)
2448-
err = sftp_readdir_sync(handle->conn, &handle->buf, dbuf,
2449-
offset, filler);
2450-
else
2451-
err = sftp_readdir_async(handle->conn, &handle->buf, dbuf,
2452-
offset, filler);
2507+
/*
2508+
* offset == 0 starts a fresh enumeration (a new listing or a
2509+
* rewinddir), so re-read the directory from the server to pick up newly
2510+
* created files and updated attributes. The SFTP handle is single-pass:
2511+
* once read to EOF, reusing it would read nothing and the directory
2512+
* would appear empty, so we reopen a fresh handle first. offset != 0 is
2513+
* a continuation of the current enumeration and is served from the
2514+
* snapshot taken at offset 0.
2515+
* See: https://github.com/libfuse/sshfs/issues/338
2516+
*/
2517+
if (offset == 0 || handle->entries == NULL) {
2518+
if (handle->buf_exhausted)
2519+
handle->read_err = sshfs_reopen_dir(handle);
2520+
if (!handle->read_err) {
2521+
if (handle->entries)
2522+
g_ptr_array_free(handle->entries, TRUE);
2523+
handle->entries =
2524+
g_ptr_array_new_with_free_func(free_dir_entry);
2525+
if (sshfs.sync_readdir)
2526+
handle->read_err = sftp_readdir_sync(
2527+
handle->conn, &handle->buf,
2528+
handle->entries);
2529+
else
2530+
handle->read_err = sftp_readdir_async(
2531+
handle->conn, &handle->buf,
2532+
handle->entries);
2533+
handle->buf_exhausted = 1;
2534+
}
2535+
}
24532536

2454-
return err;
2537+
if (handle->read_err)
2538+
return handle->read_err;
2539+
2540+
/*
2541+
* Old-style readdir: hand FUSE the complete listing with offset 0 and
2542+
* let it do the offset-based slicing itself.
2543+
*/
2544+
for (i = 0; i < handle->entries->len; i++) {
2545+
struct dir_entry *entry = g_ptr_array_index(handle->entries, i);
2546+
filler(dbuf, entry->name, &entry->stbuf, 0, 0);
2547+
}
2548+
2549+
return 0;
24552550
}
24562551

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

24632558
handle = (struct dir_handle*) fi->fh;
2464-
err = sftp_request(handle->conn, SSH_FXP_CLOSE, &handle->buf, 0, NULL);
2559+
err = 0;
2560+
if (handle->has_handle) {
2561+
err = sftp_request(handle->conn, SSH_FXP_CLOSE, &handle->buf,
2562+
0, NULL);
2563+
buf_free(&handle->buf);
2564+
}
24652565
pthread_mutex_lock(&sshfs.lock);
24662566
handle->conn->dir_count--;
24672567
pthread_mutex_unlock(&sshfs.lock);
2468-
buf_free(&handle->buf);
2568+
if (handle->entries)
2569+
g_ptr_array_free(handle->entries, TRUE);
2570+
g_free(handle->path);
24692571
g_free(handle);
24702572
return err;
24712573
}

0 commit comments

Comments
 (0)