Added writeback cache to passthrough_ll
authorNikolaus Rath <Nikolaus@rath.org>
Fri, 4 Aug 2017 20:38:03 +0000 (22:38 +0200)
committerNikolaus Rath <Nikolaus@rath.org>
Sun, 6 Aug 2017 08:13:43 +0000 (10:13 +0200)
This fixes issue #191 (where the test was done by simply adding
FUSE_CAP_WRITEBACK_CACHE without adjusting the flags in the
open() call).

Fixes: #191.
ChangeLog.rst
example/passthrough_ll.c
test/test_examples.py

index 5fc0d0c3d18a19dce31074838d4b23f5ba5e9b84..2640aa9e1fe5642444a7c9dd91796a2e03f4c4e5 100644 (file)
@@ -18,6 +18,8 @@ Unreleased Changes
 * Incorporated several patches from the FreeBSD port. libfuse should
   now compile under FreeBSD without the need for patches.
 
+* The passthrough_ll example now supports writeback caching.
+
 libfuse 3.1.0 (2017-07-08)
 ==========================
 
index 36ca1202f0d370e4ddb28b74ed9ed57bcb378248..65ab0f33d05ee1e5a141bb8ce8f78732b2b8f87c 100644 (file)
  * until the file is not opened anymore would make the example much
  * more complicated.
  *
+ * When writeback caching is enabled (-o writeback mount option), it
+ * is only possible to write to files for which the mounting user has
+ * read permissions. This is because the writeback cache requires the
+ * kernel to be able to issue read requests for all files (which the
+ * passthrough filesystem cannot satisfy if it can't read the file in
+ * the underlying filesystem).
+ *
  * Compile with:
  *
  *     gcc -Wall passthrough_ll.c `pkg-config fuse3 --cflags --libs` -o passthrough_ll
@@ -72,9 +79,18 @@ struct lo_inode {
 
 struct lo_data {
        int debug;
+       int writeback;
        struct lo_inode root;
 };
 
+static const struct fuse_opt lo_opts[] = {
+       { "writeback",
+         offsetof(struct lo_data, writeback), 1 },
+       { "no_writeback",
+         offsetof(struct lo_data, writeback), 0 },
+       FUSE_OPT_END
+};
+
 static struct lo_data *lo_data(fuse_req_t req)
 {
        return (struct lo_data *) fuse_req_userdata(req);
@@ -101,8 +117,15 @@ static bool lo_debug(fuse_req_t req)
 static void lo_init(void *userdata,
                    struct fuse_conn_info *conn)
 {
-       (void) userdata;
+       struct lo_data *lo = (struct lo_data*) userdata;
        conn->want |= FUSE_CAP_EXPORT_SUPPORT;
+
+       if (lo->writeback &&
+           conn->capable & FUSE_CAP_WRITEBACK_CACHE) {
+               if (lo->debug)
+                       fprintf(stderr, "lo_init: activating writeback\n");
+               conn->want |= FUSE_CAP_WRITEBACK_CACHE;
+       }
 }
 
 static void lo_getattr(fuse_req_t req, fuse_ino_t ino,
@@ -427,6 +450,23 @@ static void lo_open(fuse_req_t req, fuse_ino_t ino,
                fprintf(stderr, "lo_open(ino=%" PRIu64 ", flags=%d)\n",
                        ino, fi->flags);
 
+       /* With writeback cache, kernel may send read requests even
+          when userspace opened write-only */
+       if (lo_data(req)->writeback &&
+           (fi->flags & O_ACCMODE) == O_WRONLY) {
+               fi->flags &= ~O_ACCMODE;
+               fi->flags |= O_RDWR;
+       }
+
+       /* With writeback cache, O_APPEND is handled by the kernel.
+          This breaks atomicity (since the file may change in the
+          underlying filesystem, so that the kernel's idea of the
+          end of the file isn't accurate anymore). In this example,
+          we just accept that. A more rigorous filesystem may want
+          to return an error here */
+       if (lo_data(req)->writeback && (fi->flags & O_APPEND))
+               fi->flags &= ~O_APPEND;
+
        sprintf(buf, "/proc/self/fd/%i", lo_fd(req, ino));
        fd = open(buf, fi->flags & ~O_NOFOLLOW);
        if (fd == -1)
@@ -505,7 +545,8 @@ int main(int argc, char *argv[])
        struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
        struct fuse_session *se;
        struct fuse_cmdline_opts opts;
-       struct lo_data lo = { .debug = 0 };
+       struct lo_data lo = { .debug = 0,
+                             .writeback = 0 };
        int ret = -1;
 
        lo.root.next = lo.root.prev = &lo.root;
@@ -526,6 +567,9 @@ int main(int argc, char *argv[])
                goto err_out1;
        }
 
+       if (fuse_opt_parse(&args, &lo, lo_opts, NULL)== -1)
+               return 1;
+       
        lo.debug = opts.debug;
        lo.root.fd = open("/", O_PATH);
        lo.root.nlookup = 2;
index 61ebffd9665e93db484d412eadf8b31cce49846b..f9badad629c0942dbc7df1e7972df19ba966cb8b 100755 (executable)
@@ -60,8 +60,48 @@ def test_hello(tmpdir, name, options):
     else:
         umount(mount_process, mnt_dir)
 
-@pytest.mark.parametrize("name", ('passthrough', 'passthrough_fh',
-                                  'passthrough_ll'))
+@pytest.mark.parametrize("writeback", (False, True))
+@pytest.mark.parametrize("debug", (False, True))
+def test_passthrough_ll(tmpdir, writeback, debug, capfd):
+    
+    # Avoid false positives from libfuse debug messages
+    if debug:
+        capfd.register_output(r'^   unique: [0-9]+, error: -[0-9]+ .+$',
+                              count=0)
+
+    mnt_dir = str(tmpdir.mkdir('mnt'))
+    src_dir = str(tmpdir.mkdir('src'))
+
+    cmdline = base_cmdline + \
+              [ pjoin(basename, 'example', 'passthrough_ll'),
+                '-f', mnt_dir ]
+    if debug:
+        cmdline.append('-d')
+
+    if writeback:
+        cmdline.append('-o')
+        cmdline.append('writeback')
+        
+    mount_process = subprocess.Popen(cmdline)
+    try:
+        wait_for_mount(mount_process, mnt_dir)
+        work_dir = mnt_dir + src_dir
+
+        tst_statvfs(work_dir)
+        tst_readdir(src_dir, work_dir)
+        tst_open_read(src_dir, work_dir)
+        tst_open_write(src_dir, work_dir)
+        tst_create(work_dir)
+        tst_passthrough(src_dir, work_dir)
+        tst_append(src_dir, work_dir)
+        tst_seek(src_dir, work_dir)
+    except:
+        cleanup(mnt_dir)
+        raise
+    else:
+        umount(mount_process, mnt_dir)
+
+@pytest.mark.parametrize("name", ('passthrough', 'passthrough_fh'))
 @pytest.mark.parametrize("debug", (False, True))
 def test_passthrough(tmpdir, name, debug, capfd):
     
@@ -70,7 +110,6 @@ def test_passthrough(tmpdir, name, debug, capfd):
         capfd.register_output(r'^   unique: [0-9]+, error: -[0-9]+ .+$',
                               count=0)
 
-    is_ll = (name == 'passthrough_ll')
     mnt_dir = str(tmpdir.mkdir('mnt'))
     src_dir = str(tmpdir.mkdir('src'))
 
@@ -92,24 +131,23 @@ def test_passthrough(tmpdir, name, debug, capfd):
         tst_passthrough(src_dir, work_dir)
         tst_append(src_dir, work_dir)
         tst_seek(src_dir, work_dir)
-        if not is_ll:
-            tst_mkdir(work_dir)
-            tst_rmdir(src_dir, work_dir)
-            tst_unlink(src_dir, work_dir)
-            tst_symlink(work_dir)
-            if os.getuid() == 0:
-                tst_chown(work_dir)
-
-            # Underlying fs may not have full nanosecond resolution
-            tst_utimens(work_dir, ns_tol=1000)
-
-            tst_link(work_dir)
-            tst_truncate_path(work_dir)
-            tst_truncate_fd(work_dir)
-            tst_open_unlink(work_dir)
-
-            subprocess.check_call([ os.path.join(basename, 'test', 'test_syscalls'),
-                                    work_dir, ':' + src_dir ])
+        tst_mkdir(work_dir)
+        tst_rmdir(src_dir, work_dir)
+        tst_unlink(src_dir, work_dir)
+        tst_symlink(work_dir)
+        if os.getuid() == 0:
+            tst_chown(work_dir)
+
+        # Underlying fs may not have full nanosecond resolution
+        tst_utimens(work_dir, ns_tol=1000)
+
+        tst_link(work_dir)
+        tst_truncate_path(work_dir)
+        tst_truncate_fd(work_dir)
+        tst_open_unlink(work_dir)
+        
+        subprocess.check_call([ os.path.join(basename, 'test', 'test_syscalls'),
+                                work_dir, ':' + src_dir ])
     except:
         cleanup(mnt_dir)
         raise