Fix readdir() bug when a non-zero offset is specified in filler
authorRostislav Skudnov <rostislav@tuxera.com>
Wed, 25 Jul 2018 10:36:38 +0000 (10:36 +0000)
committerNikolaus Rath <Nikolaus@rath.org>
Wed, 25 Jul 2018 11:28:59 +0000 (12:28 +0100)
The bug occurs when a filesystem client reads a directory until the end,
seeks using seekdir() to some valid non-zero position and calls
readdir(). A valid 'struct dirent *' is expected, but NULL is returned
instead. Pseudocode demonstrating the bug:

DIR *dp = opendir("some_dir");
struct dirent *de = readdir(dp);

/* Get offset of the second entry */
long offset = telldir(dp);

/* Read directory until the end */
while (de)
de = readdir(de);

seekdir(dp, offset);
de = readdir(dp);
/* de must contain the second entry, but NULL is returned instead */

The reason of the bug is that when the end of directory is reached, the
kernel calls FUSE_READDIR op with an offset at the end of directory, so
the filesystem's .readdir callback never calls the filler function, and
we end up with dh->filled set to 1. After seekdir(), FUSE_READDIR is
called again with a new offset, but this time the filesystem's .readdir
callback is never called, and an empty reply is returned.

Fix by setting dh->filled to 1 only when zero offsets are given to
filler function.

This commit is backported from the following commit in 'master' branch:

commit 5f125c5e6be24c8d216a4d3c623dc73d742c8c86
Author: Rostislav <rostislav@users.noreply.github.com>
Date:   Sat Jul 21 12:57:09 2018 +0300

    Fix readdir() bug when a non-zero offset is specified in filler (#269)

ChangeLog
lib/fuse.c
test/test.c

index 8f6536fb42174e0bae35a71e7f8ec1ed28c30101..7faf386adc67540c2dfb9a25a3ee72b412535223 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,10 @@
+Unreleased Changes
+==================
+* Added a test of `seekdir` to test_syscalls.
+* Fixed `readdir` bug when non-zero offsets are given to filler and the
+  filesystem client, after reading a whole directory, re-reads it from a
+  non-zero offset e. g. by calling `seekdir` followed by `readdir`.
+
 FUSE 2.9.8 (2018-07-24)
 =======================
 
index c3ffb6c9e63d462c8929075429c2adf945d3f318..d1d873a370185bf73bf54fd657e9f8f602f69c0a 100644 (file)
@@ -3479,10 +3479,14 @@ static int fill_dir(void *dh_, const char *name, const struct stat *statp,
        }
 
        if (off) {
+               if (dh->filled) {
+                       dh->error = -EIO;
+                       return 1;
+               }
+
                if (extend_contents(dh, dh->needlen) == -1)
                        return 1;
 
-               dh->filled = 0;
                newlen = dh->len +
                        fuse_add_direntry(dh->req, dh->contents + dh->len,
                                          dh->needlen - dh->len, name,
@@ -3490,6 +3494,8 @@ static int fill_dir(void *dh_, const char *name, const struct stat *statp,
                if (newlen > dh->needlen)
                        return 1;
        } else {
+               dh->filled = 1;
+
                newlen = dh->len +
                        fuse_add_direntry(dh->req, NULL, 0, name, NULL, 0);
                if (extend_contents(dh, newlen) == -1)
@@ -3519,7 +3525,7 @@ static int readdir_fill(struct fuse *f, fuse_req_t req, fuse_ino_t ino,
                dh->len = 0;
                dh->error = 0;
                dh->needlen = size;
-               dh->filled = 1;
+               dh->filled = 0;
                dh->req = req;
                fuse_prepare_interrupt(f, req, &d);
                err = fuse_fs_readdir(f->fs, path, dh, fill_dir, off, fi);
index 2d3ec72296f10b294a5f39e516cd5592c2c3c888..9dc0eedc735f36f86d6704a37958ba5e2df4c156 100644 (file)
@@ -21,6 +21,7 @@ static char testname[256];
 static char testdata[] = "abcdefghijklmnopqrstuvwxyz";
 static char testdata2[] = "1234567890-=qwertyuiop[]\asdfghjkl;'zxcvbnm,./";
 static const char *testdir_files[] = { "f1", "f2", NULL};
+static long seekdir_offsets[4];
 static char zerodata[4096];
 static int testdatalen = sizeof(testdata) - 1;
 static int testdata2len = sizeof(testdata2) - 1;
@@ -80,6 +81,8 @@ static void __start_test(const char *fmt, ...)
 #define PERROR(msg) test_perror(__FUNCTION__, msg)
 #define ERROR(msg, args...) test_error(__FUNCTION__, msg, ##args)
 
+#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
+
 static int check_size(const char *path, int len)
 {
        struct stat stbuf;
@@ -644,6 +647,63 @@ static int test_ftruncate(int len, int mode)
        return 0;
 }
 
+static int test_seekdir(void)
+{
+       int i;
+       int res;
+       DIR *dp;
+       struct dirent *de;
+
+       start_test("seekdir");
+       res = create_dir(testdir, testdir_files);
+       if (res == -1)
+               return res;
+
+       dp = opendir(testdir);
+       if (dp == NULL) {
+               PERROR("opendir");
+               return -1;
+       }
+
+       /* Remember dir offsets */
+       for (i = 0; i < ARRAY_SIZE(seekdir_offsets); i++) {
+               seekdir_offsets[i] = telldir(dp);
+               errno = 0;
+               de = readdir(dp);
+               if (de == NULL) {
+                       if (errno) {
+                               PERROR("readdir");
+                               goto fail;
+                       }
+                       break;
+               }
+       }
+
+       /* Walk until the end of directory */
+       while (de)
+               de = readdir(dp);
+
+       /* Start from the last valid dir offset and seek backwards */
+       for (i--; i >= 0; i--) {
+               seekdir(dp, seekdir_offsets[i]);
+               de = readdir(dp);
+               if (de == NULL) {
+                       ERROR("Unexpected end of directory after seekdir()");
+                       goto fail;
+               }
+       }
+
+       closedir(dp);
+       res = cleanup_dir(testdir, testdir_files, 0);
+       if (!res)
+               success();
+       return res;
+fail:
+       closedir(dp);
+       cleanup_dir(testdir, testdir_files, 1);
+       return -1;
+}
+
 static int test_utime(void)
 {
        struct utimbuf utm;
@@ -1641,6 +1701,7 @@ int main(int argc, char *argv[])
        err += test_rename_file();
        err += test_rename_dir();
        err += test_rename_dir_loop();
+       err += test_seekdir();
        err += test_utime();
        err += test_truncate(0);
        err += test_truncate(testdatalen / 2);