aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Nikolaus Rath <Nikolaus@rath.org>2017-08-04 22:38:03 +0200
committerGravatar Nikolaus Rath <Nikolaus@rath.org>2017-08-06 10:13:43 +0200
commitb3109e71faf2713402f70d226617352815f6c72e (patch)
tree46d878cf848256eb420ccf66af20fa9faa269e2b
parent53c07425fa378eedb9b38c05025da5832d7a3a2e (diff)
Added writeback cache to passthrough_ll
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.
-rw-r--r--ChangeLog.rst2
-rw-r--r--example/passthrough_ll.c48
-rwxr-xr-xtest/test_examples.py80
3 files changed, 107 insertions, 23 deletions
diff --git a/ChangeLog.rst b/ChangeLog.rst
index 5fc0d0c..2640aa9 100644
--- a/ChangeLog.rst
+++ b/ChangeLog.rst
@@ -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)
==========================
diff --git a/example/passthrough_ll.c b/example/passthrough_ll.c
index 36ca120..65ab0f3 100644
--- a/example/passthrough_ll.c
+++ b/example/passthrough_ll.c
@@ -19,6 +19,13 @@
* 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;
diff --git a/test/test_examples.py b/test/test_examples.py
index 61ebffd..f9badad 100755
--- a/test/test_examples.py
+++ b/test/test_examples.py
@@ -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