diff options
author | Nikolaus Rath <Nikolaus@rath.org> | 2022-03-14 09:48:43 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-03-14 09:48:43 +0000 |
commit | 2b7a6f065b6e30723d6cc8668cff198dbb62b914 (patch) | |
tree | 6420fbb2ddc1c8f04f6c4a05633401d3ceca68e1 | |
parent | 3c2ba7aa2500618b7b11255ef3f699d6615ad5a2 (diff) | |
parent | 66b04453b7a5d7aefa0a55e9101afe0347215128 (diff) | |
download | fuse-2b7a6f065b6e30723d6cc8668cff198dbb62b914.tar.gz |
Merge pull request #635 from amir73il/fopen_noflush
Add support for FOPEN_NOFLUSH flag
-rw-r--r-- | ChangeLog.rst | 5 | ||||
-rw-r--r-- | example/passthrough_hp.cc | 1 | ||||
-rw-r--r-- | include/fuse.h | 8 | ||||
-rw-r--r-- | include/fuse_common.h | 6 | ||||
-rw-r--r-- | include/fuse_kernel.h | 2 | ||||
-rw-r--r-- | lib/fuse.c | 6 | ||||
-rw-r--r-- | lib/fuse_lowlevel.c | 2 | ||||
-rw-r--r-- | test/test_ctests.py | 5 | ||||
-rw-r--r-- | test/test_write_cache.c | 61 |
9 files changed, 94 insertions, 2 deletions
diff --git a/ChangeLog.rst b/ChangeLog.rst index 2dd8954..3c3be1e 100644 --- a/ChangeLog.rst +++ b/ChangeLog.rst @@ -1,3 +1,8 @@ +Unreleased Changes +================== + +* Add support for flag FOPEN_NOFLUSH for avoiding flush on close. + * Fixed returning an error condition to ioctl(2) libfuse 3.10.5 (2021-09-06) diff --git a/example/passthrough_hp.cc b/example/passthrough_hp.cc index 872fc73..e15f893 100644 --- a/example/passthrough_hp.cc +++ b/example/passthrough_hp.cc @@ -856,6 +856,7 @@ static void sfs_open(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi) { lock_guard<mutex> g {inode.m}; inode.nopen++; fi->keep_cache = (fs.timeout != 0); + fi->noflush = (fs.timeout == 0 && (fi->flags & O_ACCMODE) == O_RDONLY); fi->fh = fd; fuse_reply_open(req, fi); } diff --git a/include/fuse.h b/include/fuse.h index a273b15..9148688 100644 --- a/include/fuse.h +++ b/include/fuse.h @@ -248,6 +248,14 @@ struct fuse_config { int auto_cache; /** + * By default, fuse waits for all pending writes to complete + * and calls the FLUSH operation on close(2) of every fuse fd. + * With this option, wait and FLUSH are not done for read-only + * fuse fd, similar to the behavior of NFS/SMB clients. + */ + int no_rofd_flush; + + /** * The timeout in seconds for which file attributes are cached * for the purpose of checking if auto_cache should flush the * file data on open. diff --git a/include/fuse_common.h b/include/fuse_common.h index ea4bdb0..d7481be 100644 --- a/include/fuse_common.h +++ b/include/fuse_common.h @@ -83,8 +83,12 @@ struct fuse_file_info { nothing when set by open()). */ unsigned int cache_readdir : 1; + /** Can be filled in by open, to indicate that flush is not needed + on close. */ + unsigned int noflush : 1; + /** Padding. Reserved for future use*/ - unsigned int padding : 25; + unsigned int padding : 24; unsigned int padding2 : 32; /** File handle id. May be filled in by filesystem in create, diff --git a/include/fuse_kernel.h b/include/fuse_kernel.h index 018a00a..48f2000 100644 --- a/include/fuse_kernel.h +++ b/include/fuse_kernel.h @@ -238,12 +238,14 @@ struct fuse_file_lock { * FOPEN_NONSEEKABLE: the file is not seekable * FOPEN_CACHE_DIR: allow caching this directory * FOPEN_STREAM: the file is stream-like (no file position at all) + * FOPEN_NOFLUSH: don't flush data cache on close (unless FUSE_WRITEBACK_CACHE) */ #define FOPEN_DIRECT_IO (1 << 0) #define FOPEN_KEEP_CACHE (1 << 1) #define FOPEN_NONSEEKABLE (1 << 2) #define FOPEN_CACHE_DIR (1 << 3) #define FOPEN_STREAM (1 << 4) +#define FOPEN_NOFLUSH (1 << 5) /** * INIT request/reply flags @@ -3272,6 +3272,10 @@ static void fuse_lib_open(fuse_req_t req, fuse_ino_t ino, if (f->conf.auto_cache) open_auto_cache(f, ino, path, fi); + + if (f->conf.no_rofd_flush && + (fi->flags & O_ACCMODE) == O_RDONLY) + fi->noflush = 1; } fuse_finish_interrupt(f, req, &d); } @@ -4655,6 +4659,7 @@ static const struct fuse_opt fuse_lib_opts[] = { FUSE_LIB_OPT("kernel_cache", kernel_cache, 1), FUSE_LIB_OPT("auto_cache", auto_cache, 1), FUSE_LIB_OPT("noauto_cache", auto_cache, 0), + FUSE_LIB_OPT("no_rofd_flush", no_rofd_flush, 1), FUSE_LIB_OPT("umask=", set_mode, 1), FUSE_LIB_OPT("umask=%o", umask, 0), FUSE_LIB_OPT("uid=", set_uid, 1), @@ -4707,6 +4712,7 @@ void fuse_lib_help(struct fuse_args *args) printf( " -o kernel_cache cache files in kernel\n" " -o [no]auto_cache enable caching based on modification times (off)\n" +" -o no_rofd_flush disable flushing of read-only fd on close (off)\n" " -o umask=M set file permissions (octal)\n" " -o uid=N set file owner\n" " -o gid=N set file group\n" diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c index d227688..b5638fc 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -395,6 +395,8 @@ static void fill_open(struct fuse_open_out *arg, arg->open_flags |= FOPEN_CACHE_DIR; if (f->nonseekable) arg->open_flags |= FOPEN_NONSEEKABLE; + if (f->noflush) + arg->open_flags |= FOPEN_NOFLUSH; } int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e) diff --git a/test/test_ctests.py b/test/test_ctests.py index 4290f20..63728c3 100644 --- a/test/test_ctests.py +++ b/test/test_ctests.py @@ -33,6 +33,11 @@ def test_write_cache(tmpdir, writeback, output_checker): mnt_dir ] if writeback: cmdline.append('-owriteback_cache') + elif LooseVersion(platform.release()) >= '5.16': + # Test that close(rofd) does not block waiting for pending writes. + # This test requires kernel commit a390ccb316be ("fuse: add FOPEN_NOFLUSH") + # so opt-in for this test from kernel 5.16. + cmdline.append('--delay_ms=200') subprocess.check_call(cmdline, stdout=output_checker.fd, stderr=output_checker.fd) diff --git a/test/test_write_cache.c b/test/test_write_cache.c index 88344dc..03ccc98 100644 --- a/test/test_write_cache.c +++ b/test/test_write_cache.c @@ -34,9 +34,11 @@ struct options { int writeback; int data_size; + int delay_ms; } options = { .writeback = 0, .data_size = 4096, + .delay_ms = 0, }; #define OPTION(t, p) \ @@ -44,10 +46,15 @@ struct options { static const struct fuse_opt option_spec[] = { OPTION("writeback_cache", writeback), OPTION("--data-size=%d", data_size), + OPTION("--delay_ms=%d", delay_ms), FUSE_OPT_END }; static int got_write; +pthread_cond_t cond = PTHREAD_COND_INITIALIZER; +pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; +static int write_start, write_done; + static void tfs_init (void *userdata, struct fuse_conn_info *conn) { (void) userdata; @@ -117,6 +124,9 @@ static void tfs_open(fuse_req_t req, fuse_ino_t ino, fuse_reply_err(req, EISDIR); else { assert(ino == FILE_INO); + /* Test close(rofd) does not block waiting for pending writes */ + fi->noflush = !options.writeback && options.delay_ms && + (fi->flags & O_ACCMODE) == O_RDONLY; fuse_reply_open(req, fi); } } @@ -136,6 +146,22 @@ static void tfs_write(fuse_req_t req, fuse_ino_t ino, const char *buf, expected, size); else got_write = 1; + + /* Simulate waiting for pending writes */ + if (options.delay_ms) { + pthread_mutex_lock(&lock); + write_start = 1; + pthread_cond_signal(&cond); + pthread_mutex_unlock(&lock); + + usleep(options.delay_ms * 1000); + + pthread_mutex_lock(&lock); + write_done = 1; + pthread_cond_signal(&cond); + pthread_mutex_unlock(&lock); + } + fuse_reply_write(req, size); } @@ -147,6 +173,25 @@ static struct fuse_lowlevel_ops tfs_oper = { .write = tfs_write, }; +static void* close_rofd(void *data) { + int rofd = (int)(long) data; + + /* Wait for first write to start */ + pthread_mutex_lock(&lock); + while (!write_start && !write_done) + pthread_cond_wait(&cond, &lock); + pthread_mutex_unlock(&lock); + + close(rofd); + printf("rofd closed. write_start: %d write_done: %d\n", write_start, write_done); + + /* First write should not have been completed */ + if (write_done) + fprintf(stderr, "ERROR: close(rofd) blocked on write!\n"); + + return NULL; +} + static void* run_fs(void *data) { struct fuse_session *se = (struct fuse_session*) data; assert(fuse_session_loop(se) == 0); @@ -157,7 +202,8 @@ static void test_fs(char *mountpoint) { char fname[PATH_MAX]; char *buf; size_t dsize = options.data_size; - int fd; + int fd, rofd; + pthread_t rofd_thread; buf = malloc(dsize); assert(buf != NULL); @@ -173,10 +219,23 @@ static void test_fs(char *mountpoint) { assert(0); } + if (options.delay_ms) { + /* Verify that close(rofd) does not block waiting for pending writes */ + rofd = open(fname, O_RDONLY); + assert(pthread_create(&rofd_thread, NULL, close_rofd, (void *)(long)rofd) == 0); + /* Give close_rofd time to start */ + usleep(options.delay_ms * 1000); + } + assert(write(fd, buf, dsize) == dsize); assert(write(fd, buf, dsize) == dsize); free(buf); close(fd); + + if (options.delay_ms) { + printf("rwfd closed. write_start: %d write_done: %d\n", write_start, write_done); + assert(pthread_join(rofd_thread, NULL) == 0); + } } int main(int argc, char *argv[]) { |