diff options
-rw-r--r-- | .travis.yml | 2 | ||||
-rw-r--r-- | ChangeLog.rst | 15 | ||||
-rw-r--r-- | Makefile.am | 15 | ||||
-rw-r--r-- | example/hello_ll.c | 2 | ||||
-rw-r--r-- | example/notify_inval_entry.c | 2 | ||||
-rw-r--r-- | example/notify_inval_inode.c | 2 | ||||
-rw-r--r-- | example/notify_store_retrieve.c | 2 | ||||
-rw-r--r-- | example/passthrough_ll.c | 2 | ||||
-rw-r--r-- | include/fuse.h | 4 | ||||
-rw-r--r-- | include/fuse_lowlevel.h | 21 | ||||
-rw-r--r-- | lib/cuse_lowlevel.c | 2 | ||||
-rw-r--r-- | lib/fuse.c | 4 | ||||
-rw-r--r-- | lib/fuse_i.h | 20 | ||||
-rw-r--r-- | lib/fuse_loop_mt.c | 8 | ||||
-rw-r--r-- | lib/fuse_lowlevel.c | 73 | ||||
-rw-r--r-- | lib/helper.c | 4 | ||||
-rwxr-xr-x | test/test_examples.py | 9 | ||||
-rw-r--r-- | test/util.py | 23 |
18 files changed, 133 insertions, 77 deletions
diff --git a/.travis.yml b/.travis.yml index 6d2f313..b7bed8f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,4 +30,4 @@ script: - ./configure - CFLAGS="-fsanitize=address,undefined -g -O1 -Wall -Werror" make - doxygen doc/Doxyfile - - python -m pytest test/ + - make test diff --git a/ChangeLog.rst b/ChangeLog.rst index 4f355ce..a498273 100644 --- a/ChangeLog.rst +++ b/ChangeLog.rst @@ -1,6 +1,14 @@ Unreleased Changes ================== +* The `fuse_session_new` function no longer accepts the ``-o + clone_fd`` option. Instead, this has become a parameter of the + `fuse_session_loop_mt` and ``fuse_loop_mt` functions. + +* For low-level file systems that implement the `write_buf` handler, + the `splice_read` option is now enabled by default. As usual, this + can be changed in the file system's `init` handler. + * `fuse_session_new` now treats low-level options more consistently: First, options are used to modify FUSE defaults. Second, the file system may inspect and/or adjust the settings in its `init` @@ -132,6 +140,13 @@ FUSE 3.0.0pre0 (2016-10-03) descriptor for each processing thread, which might improve performance. +* Added *writeback_cache* option. With kernel 3.14 and newer this + enables write-back caching which can significantly improve + performance. + +* Added *async_dio* option. With kernel 3.13 and newer, this allows + direct I/O to be done asynchronously. + * The (high- and low-level) `rename` handlers now takes a *flags* parameter (with values corresponding to the *renameat2* system call introduced in Linux 3.15). diff --git a/Makefile.am b/Makefile.am index 54aee13..25d88d0 100644 --- a/Makefile.am +++ b/Makefile.am @@ -15,6 +15,19 @@ pkgconfig_DATA = fuse3.pc $(pkgconfig_DATA): config.status +.PHONY: setuid_fusermount +setuid_fusermount: + @echo "Attempting to use sudo to make util/fusermount setuid root" + @echo "If this fails, set permissions manually and re-run make test" + test $$(ls -n util/fusermount | awk 'NR==1 {print $$3}') -eq 0 || \ + sudo chown root util/fusermount + test -u util/fusermount || \ + sudo chmod u+s util/fusermount + +# If we are not root, util/fusermount needs to be setuid root +# for tests to work. + +test_deps = $(shell [ "$${UID}" -eq 0 ] || echo setuid_fusermount) .PHONY: test -test: all +test: all $(test_deps) python3 -m pytest test/ diff --git a/example/hello_ll.c b/example/hello_ll.c index b830cb2..e0ce610 100644 --- a/example/hello_ll.c +++ b/example/hello_ll.c @@ -224,7 +224,7 @@ int main(int argc, char *argv[]) if (opts.singlethread) ret = fuse_session_loop(se); else - ret = fuse_session_loop_mt(se); + ret = fuse_session_loop_mt(se, opts.clone_fd); fuse_session_unmount(se); err_out3: diff --git a/example/notify_inval_entry.c b/example/notify_inval_entry.c index 898eff1..1c2a6c9 100644 --- a/example/notify_inval_entry.c +++ b/example/notify_inval_entry.c @@ -327,7 +327,7 @@ int main(int argc, char *argv[]) { if (opts.singlethread) ret = fuse_session_loop(se); else - ret = fuse_session_loop_mt(se); + ret = fuse_session_loop_mt(se, opts.clone_fd); fuse_session_unmount(se); err_out3: diff --git a/example/notify_inval_inode.c b/example/notify_inval_inode.c index 1b813a0..c9ab4d8 100644 --- a/example/notify_inval_inode.c +++ b/example/notify_inval_inode.c @@ -350,7 +350,7 @@ int main(int argc, char *argv[]) { if (opts.singlethread) ret = fuse_session_loop(se); else - ret = fuse_session_loop_mt(se); + ret = fuse_session_loop_mt(se, opts.clone_fd); fuse_session_unmount(se); err_out3: diff --git a/example/notify_store_retrieve.c b/example/notify_store_retrieve.c index 76f3291..5b5fa63 100644 --- a/example/notify_store_retrieve.c +++ b/example/notify_store_retrieve.c @@ -393,7 +393,7 @@ int main(int argc, char *argv[]) { if (opts.singlethread) ret = fuse_session_loop(se); else - ret = fuse_session_loop_mt(se); + ret = fuse_session_loop_mt(se, opts.clone_fd); assert(retrieve_status != 1); fuse_session_unmount(se); diff --git a/example/passthrough_ll.c b/example/passthrough_ll.c index 66f92cf..df9d7d3 100644 --- a/example/passthrough_ll.c +++ b/example/passthrough_ll.c @@ -504,7 +504,7 @@ int main(int argc, char *argv[]) if (opts.singlethread) ret = fuse_session_loop(se); else - ret = fuse_session_loop_mt(se); + ret = fuse_session_loop_mt(se, opts.clone_fd); fuse_session_unmount(se); err_out3: diff --git a/include/fuse.h b/include/fuse.h index 8943835..5b9082b 100644 --- a/include/fuse.h +++ b/include/fuse.h @@ -748,11 +748,13 @@ void fuse_exit(struct fuse *f); * in the callback function of fuse_operations is also thread-safe. * * @param f the FUSE handle + * @param clone_fd whether to use separate device fds for each thread + * (may increase performance) * @return 0 if no error occurred, -1 otherwise * * See also: fuse_loop() */ -int fuse_loop_mt(struct fuse *f); +int fuse_loop_mt(struct fuse *f, int clone_fd); /** * Get the current context diff --git a/include/fuse_lowlevel.h b/include/fuse_lowlevel.h index bad1d45..0b7ee2b 100644 --- a/include/fuse_lowlevel.h +++ b/include/fuse_lowlevel.h @@ -420,12 +420,18 @@ struct fuse_lowlevel_ops { /** * Open a file * - * Open flags (with the exception of O_CREAT, O_EXCL, O_NOCTTY and - * O_TRUNC) are available in fi->flags. + * Open flags are available in fi->flags. Creation (O_CREAT, + * O_EXCL, O_NOCTTY) and by default also truncation (O_TRUNC) + * flags will be filtered out. If an application specifies + * O_TRUNC, fuse first calls truncate() and then open(). * - * Filesystem may store an arbitrary file handle (pointer, index, - * etc) in fi->fh, and use this in other all other file operations - * (read, write, flush, release, fsync). + * If filesystem is able to handle O_TRUNC directly, the + * init() handler should set the `FUSE_CAP_ATOMIC_O_TRUNC` bit + * in ``conn->want``. + * + * Filesystem may store an arbitrary file handle (pointer, + * index, etc) in fi->fh, and use this in other all other file + * operations (read, write, flush, release, fsync). * * Filesystem may also implement stateless file I/O and not store * anything in fi->fh. @@ -1651,6 +1657,7 @@ struct fuse_cmdline_opts { char *mountpoint; int show_version; int show_help; + int clone_fd; }; /** @@ -1721,9 +1728,11 @@ int fuse_session_loop(struct fuse_session *se); * Enter a multi-threaded event loop * * @param se the session + * @param clone_fd whether to use separate device fds for each thread + * (may increase performance) * @return 0 on success, -1 on error */ -int fuse_session_loop_mt(struct fuse_session *se); +int fuse_session_loop_mt(struct fuse_session *se, int clone_fd); /** * Flag a session as terminated. diff --git a/lib/cuse_lowlevel.c b/lib/cuse_lowlevel.c index c9aa47d..49fc7d4 100644 --- a/lib/cuse_lowlevel.c +++ b/lib/cuse_lowlevel.c @@ -350,7 +350,7 @@ int cuse_lowlevel_main(int argc, char *argv[], const struct cuse_info *ci, return 1; if (multithreaded) - res = fuse_session_loop_mt(se); + res = fuse_session_loop_mt(se, 0); else res = fuse_session_loop(se); @@ -4378,7 +4378,7 @@ int fuse_loop(struct fuse *f) return fuse_session_loop(f->se); } -int fuse_loop_mt(struct fuse *f) +int fuse_loop_mt(struct fuse *f, int clone_fd) { if (f == NULL) return -1; @@ -4387,7 +4387,7 @@ int fuse_loop_mt(struct fuse *f) if (res) return -1; - res = fuse_session_loop_mt(fuse_get_session(f)); + res = fuse_session_loop_mt(fuse_get_session(f), clone_fd); fuse_stop_cleanup_thread(f); return res; } diff --git a/lib/fuse_i.h b/lib/fuse_i.h index c8aa279..5ed23c7 100644 --- a/lib/fuse_i.h +++ b/lib/fuse_i.h @@ -41,14 +41,7 @@ struct fuse_notify_req { struct fuse_notify_req *prev; }; -struct fuse_session { - char *mountpoint; - volatile int exited; - int fd; - struct mount_opts *mo; - - int debug; - int allow_root; +struct session_opts { int atomic_o_trunc; int no_remote_posix_lock; int no_remote_flock; @@ -66,9 +59,18 @@ struct fuse_session { int no_async_dio; int writeback_cache; int no_writeback_cache; - int clone_fd; int async_read; int sync_read; +}; + +struct fuse_session { + char *mountpoint; + volatile int exited; + int fd; + struct mount_opts *mo; + struct session_opts opts; + int debug; + int allow_root; struct fuse_lowlevel_ops op; int got_init; struct cuse_data *cuse_data; diff --git a/lib/fuse_loop_mt.c b/lib/fuse_loop_mt.c index e6e2263..54fb56d 100644 --- a/lib/fuse_loop_mt.c +++ b/lib/fuse_loop_mt.c @@ -47,6 +47,7 @@ struct fuse_mt { sem_t finish; int exit; int error; + int clone_fd; }; static struct fuse_chan *fuse_chan_new(int fd) @@ -265,13 +266,13 @@ static int fuse_loop_start_thread(struct fuse_mt *mt) w->mt = mt; w->ch = NULL; - if (mt->se->clone_fd) { + if (mt->clone_fd) { w->ch = fuse_clone_chan(mt); if(!w->ch) { /* Don't attempt this again */ fprintf(stderr, "fuse: trying to continue " "without -o clone_fd.\n"); - mt->se->clone_fd = 0; + mt->clone_fd = 0; } } @@ -299,7 +300,7 @@ static void fuse_join_worker(struct fuse_mt *mt, struct fuse_worker *w) free(w); } -int fuse_session_loop_mt(struct fuse_session *se) +int fuse_session_loop_mt(struct fuse_session *se, int clone_fd) { int err; struct fuse_mt mt; @@ -307,6 +308,7 @@ int fuse_session_loop_mt(struct fuse_session *se) memset(&mt, 0, sizeof(struct fuse_mt)); mt.se = se; + mt.clone_fd = clone_fd; mt.error = 0; mt.numworker = 0; mt.numavail = 0; diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c index abde850..a96f3a5 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -1809,8 +1809,8 @@ static void do_fallocate(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) fuse_reply_err(req, ENOSYS); } -static void apply_want_options(struct fuse_session *opts, - struct fuse_conn_info *conn) +static void apply_want_options(struct session_opts *opts, + struct fuse_conn_info *conn) { #define LL_ENABLE(cond,cap) \ if (cond) conn->want |= (cap) @@ -1931,6 +1931,7 @@ static void do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) if ((cond) && (f->conn.capable & (cap))) \ f->conn.want |= (cap) LL_SET_DEFAULT(1, FUSE_CAP_ASYNC_READ); + LL_SET_DEFAULT(f->op.write_buf, FUSE_CAP_SPLICE_READ); LL_SET_DEFAULT(f->op.getlk && f->op.setlk, FUSE_CAP_POSIX_LOCKS); LL_SET_DEFAULT(f->op.flock, FUSE_CAP_FLOCK_LOCKS); @@ -1951,7 +1952,7 @@ static void do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) /* Apply command-line options (so that init() handler has an idea about user preferences */ - apply_want_options(f, &f->conn); + apply_want_options(&f->opts, &f->conn); /* Allow file-system to overwrite defaults */ if (f->op.init) @@ -1959,7 +1960,7 @@ static void do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) /* Now explicitly overwrite file-system's decision with command-line options */ - apply_want_options(f, &f->conn); + apply_want_options(&f->opts, &f->conn); /* Always enable big writes, this is superseded by the max_write option */ @@ -2573,32 +2574,31 @@ static const struct fuse_opt fuse_ll_opts[] = { LL_OPTION("max_readahead=%u", conn.max_readahead, 0), LL_OPTION("max_background=%u", conn.max_background, 0), LL_OPTION("congestion_threshold=%u", conn.congestion_threshold, 0), - LL_OPTION("sync_read", sync_read, 1), - LL_OPTION("async_read", async_read, 1), - LL_OPTION("atomic_o_trunc", atomic_o_trunc, 1), - LL_OPTION("no_remote_lock", no_remote_posix_lock, 1), - LL_OPTION("no_remote_lock", no_remote_flock, 1), - LL_OPTION("no_remote_flock", no_remote_flock, 1), - LL_OPTION("no_remote_posix_lock", no_remote_posix_lock, 1), - LL_OPTION("splice_write", splice_write, 1), - LL_OPTION("no_splice_write", no_splice_write, 1), - LL_OPTION("splice_move", splice_move, 1), - LL_OPTION("no_splice_move", no_splice_move, 1), - LL_OPTION("splice_read", splice_read, 1), - LL_OPTION("no_splice_read", no_splice_read, 1), - LL_OPTION("auto_inval_data", auto_inval_data, 1), - LL_OPTION("no_auto_inval_data", no_auto_inval_data, 1), - LL_OPTION("readdirplus=no", no_readdirplus, 1), - LL_OPTION("readdirplus=yes", no_readdirplus, 0), - LL_OPTION("readdirplus=yes", no_readdirplus_auto, 1), - LL_OPTION("readdirplus=auto", no_readdirplus, 0), - LL_OPTION("readdirplus=auto", no_readdirplus_auto, 0), - LL_OPTION("async_dio", async_dio, 1), - LL_OPTION("no_async_dio", no_async_dio, 1), - LL_OPTION("writeback_cache", writeback_cache, 1), - LL_OPTION("no_writeback_cache", no_writeback_cache, 1), + LL_OPTION("sync_read", opts.sync_read, 1), + LL_OPTION("async_read", opts.async_read, 1), + LL_OPTION("atomic_o_trunc", opts.atomic_o_trunc, 1), + LL_OPTION("no_remote_lock", opts.no_remote_posix_lock, 1), + LL_OPTION("no_remote_lock", opts.no_remote_flock, 1), + LL_OPTION("no_remote_flock", opts.no_remote_flock, 1), + LL_OPTION("no_remote_posix_lock", opts.no_remote_posix_lock, 1), + LL_OPTION("splice_write", opts.splice_write, 1), + LL_OPTION("no_splice_write", opts.no_splice_write, 1), + LL_OPTION("splice_move", opts.splice_move, 1), + LL_OPTION("no_splice_move", opts.no_splice_move, 1), + LL_OPTION("splice_read", opts.splice_read, 1), + LL_OPTION("no_splice_read", opts.no_splice_read, 1), + LL_OPTION("auto_inval_data", opts.auto_inval_data, 1), + LL_OPTION("no_auto_inval_data", opts.no_auto_inval_data, 1), + LL_OPTION("readdirplus=no", opts.no_readdirplus, 1), + LL_OPTION("readdirplus=yes", opts.no_readdirplus, 0), + LL_OPTION("readdirplus=yes", opts.no_readdirplus_auto, 1), + LL_OPTION("readdirplus=auto", opts.no_readdirplus, 0), + LL_OPTION("readdirplus=auto", opts.no_readdirplus_auto, 0), + LL_OPTION("async_dio", opts.async_dio, 1), + LL_OPTION("no_async_dio", opts.no_async_dio, 1), + LL_OPTION("writeback_cache", opts.writeback_cache, 1), + LL_OPTION("no_writeback_cache", opts.no_writeback_cache, 1), LL_OPTION("time_gran=%u", conn.time_gran, 0), - LL_OPTION("clone_fd", clone_fd, 1), FUSE_OPT_END }; @@ -2629,17 +2629,7 @@ void fuse_lowlevel_help(void) " -o readdirplus=S control readdirplus use (yes|no|auto)\n" " -o [no_]async_dio asynchronous direct I/O\n" " -o [no_]writeback_cache asynchronous, buffered writes\n" -" -o time_gran=N time granularity in nsec\n" -" -o clone_fd clone fuse device file descriptors\n\n"); -} - -static int fuse_ll_opt_proc(void *data, const char *arg, int key, - struct fuse_args *outargs) -{ - (void) data; (void) outargs; (void) key; (void) arg; - - /* Passthrough unknown options */ - return 1; +" -o time_gran=N time granularity in nsec\n\n"); } void fuse_session_destroy(struct fuse_session *se) @@ -2842,13 +2832,12 @@ struct fuse_session *fuse_session_new(struct fuse_args *args, } se->conn.max_write = UINT_MAX; se->conn.max_readahead = UINT_MAX; - se->atomic_o_trunc = 0; /* Parse options */ mo = parse_mount_opts(args); if (mo == NULL) goto out2; - if(fuse_opt_parse(args, se, fuse_ll_opts, fuse_ll_opt_proc) == -1) + if(fuse_opt_parse(args, se, fuse_ll_opts, NULL) == -1) goto out3; if (args->argc != 1) { int i; diff --git a/lib/helper.c b/lib/helper.c index cc5cefc..ea06d81 100644 --- a/lib/helper.c +++ b/lib/helper.c @@ -45,6 +45,7 @@ static const struct fuse_opt fuse_helper_opts[] = { FUSE_HELPER_OPT("subtype=", nodefault_subtype), FUSE_OPT_KEY("fsname=", FUSE_OPT_KEY_KEEP), FUSE_OPT_KEY("subtype=", FUSE_OPT_KEY_KEEP), + FUSE_HELPER_OPT("clone_fd", clone_fd), FUSE_OPT_END }; @@ -56,6 +57,7 @@ void fuse_cmdline_help(void) " -d -o debug enable debug output (implies -f)\n" " -f foreground operation\n" " -s disable multi-threaded operation\n" + " -o clone_fd use separate fuse device fd for each thread\n" "\n"); } @@ -246,7 +248,7 @@ int fuse_main_real(int argc, char *argv[], const struct fuse_operations *op, if (opts.singlethread) res = fuse_loop(fuse); else - res = fuse_loop_mt(fuse); + res = fuse_loop_mt(fuse, opts.clone_fd); if (res) res = 1; diff --git a/test/test_examples.py b/test/test_examples.py index e0f9be4..a8064c3 100755 --- a/test/test_examples.py +++ b/test/test_examples.py @@ -13,12 +13,13 @@ import stat import shutil import filecmp import errno +import platform +from distutils.version import LooseVersion from tempfile import NamedTemporaryFile from util import (wait_for_mount, umount, cleanup, base_cmdline, - safe_sleep) + safe_sleep, basename) from os.path import join as pjoin -basename = pjoin(os.path.dirname(__file__), '..') TEST_FILE = __file__ with open(TEST_FILE, 'rb') as fh: @@ -206,8 +207,10 @@ def test_notify_inval_entry(tmpdir, notify): else: umount(mount_process, mnt_dir) -@pytest.mark.parametrize("writeback", (True, False)) +@pytest.mark.parametrize("writeback", (False, True)) def test_write_cache(tmpdir, writeback): + if writeback and LooseVersion(platform.release()) < '3.14': + pytest.skip('Requires kernel 3.14 or newer') # This test hangs under Valgrind when running close(fd) # test_write_cache.c:test_fs(). Most likely this is because of an internal # deadlock in valgrind, it probably assumes that until close() returns, diff --git a/test/util.py b/test/util.py index 2160f70..baba20b 100644 --- a/test/util.py +++ b/test/util.py @@ -3,6 +3,9 @@ import subprocess import pytest import os import time +from os.path import join as pjoin + +basename = pjoin(os.path.dirname(__file__), '..') def wait_for_mount(mount_process, mnt_dir, test_fn=os.path.ismount): @@ -17,12 +20,24 @@ def wait_for_mount(mount_process, mnt_dir, pytest.fail("mountpoint failed to come up") def cleanup(mnt_dir): - subprocess.call(['fusermount', '-z', '-u', mnt_dir], + # Don't bother trying Valgrind if things already went wrong + + subprocess.call([pjoin(basename, 'util', 'fusermount'), + '-z', '-u', mnt_dir], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT) def umount(mount_process, mnt_dir): - subprocess.check_call(['fusermount', '-z', '-u', mnt_dir]) + # fusermount will be setuid root, so we can only trace it with + # valgrind if we're root + if os.getuid() == 0: + cmdline = base_cmdline + else: + cmdline = [] + + cmdline = cmdline + [ pjoin(basename, 'util', 'fusermount'), + '-z', '-u', mnt_dir ] + subprocess.check_call(cmdline) assert not os.path.ismount(mnt_dir) # Give mount process a little while to terminate. Popen.wait(timeout) @@ -68,3 +83,7 @@ if has_program('valgrind') and has_program('libtool'): 'valgrind', '-q', '--' ] else: base_cmdline = [] + + +# Try to use local fusermount +os.environ['PATH'] = '%s:%s' % (pjoin(basename, 'util'), os.environ['PATH']) |