From b3109e71faf2713402f70d226617352815f6c72e Mon Sep 17 00:00:00 2001 From: Nikolaus Rath Date: Fri, 4 Aug 2017 22:38:03 +0200 Subject: [PATCH] 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. --- ChangeLog.rst | 2 + example/passthrough_ll.c | 48 +++++++++++++++++++++++- test/test_examples.py | 80 +++++++++++++++++++++++++++++----------- 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 -- 2.30.2