summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNikolaus Rath <Nikolaus@rath.org>2022-03-14 09:48:43 +0000
committerGitHub <noreply@github.com>2022-03-14 09:48:43 +0000
commit2b7a6f065b6e30723d6cc8668cff198dbb62b914 (patch)
tree6420fbb2ddc1c8f04f6c4a05633401d3ceca68e1
parent3c2ba7aa2500618b7b11255ef3f699d6615ad5a2 (diff)
parent66b04453b7a5d7aefa0a55e9101afe0347215128 (diff)
downloadfuse-2b7a6f065b6e30723d6cc8668cff198dbb62b914.tar.gz
Merge pull request #635 from amir73il/fopen_noflush
Add support for FOPEN_NOFLUSH flag
-rw-r--r--ChangeLog.rst5
-rw-r--r--example/passthrough_hp.cc1
-rw-r--r--include/fuse.h8
-rw-r--r--include/fuse_common.h6
-rw-r--r--include/fuse_kernel.h2
-rw-r--r--lib/fuse.c6
-rw-r--r--lib/fuse_lowlevel.c2
-rw-r--r--test/test_ctests.py5
-rw-r--r--test/test_write_cache.c61
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
diff --git a/lib/fuse.c b/lib/fuse.c
index 91a4b70..507c80b 100644
--- a/lib/fuse.c
+++ b/lib/fuse.c
@@ -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[]) {