From f52f71df3373185c4d18414aad57c4e8995a4393 Mon Sep 17 00:00:00 2001 From: Bernd Schubert Date: Sat, 22 Mar 2025 23:57:55 +0100 Subject: fuse: Fix want flag conversion 32-bit conn->want flags been left to be ABI compatible to 3.10, even though the so version was changed. The more recent way is to use fuse_set_feature_flag(), which will use conn->want_ext. Given that we now have two flags (want and want_ext), we need to convert and that brought several issues - If the application sets conn->want, that needs to be set into the lower 32 bit of conn->want_ext. As the application might actually unset values, it really has to be a copy and not just 'or' - fixed now. - convert_to_conn_want_ext() actually needs to check for _modified_ conn->want and conn->want_ext - convert_to_conn_want_ext() must consider being called from high and lowlevel interfact, with different want_ext_default and want_default values. It is only a failure, if the application changed both, conn->want and conn->want_ext. This function was failing in issue #1171, because high level fuse_fs_init() was changing values and then lowlevel do_init() was incorrectly failing on that. This also adds a new test (test_want_conversion) and sets values into example/{hello.c,hello_ll.c} Also some more internal users of conn->want are converted to fuse_{set,unset}_feature_flag(). Closes: https://github.com/libfuse/libfuse/issues/1171 Signed-off-by: Bernd Schubert (cherry picked from commit f68970cd235a7e14026ca0f6240428bbebe8223b) --- example/hello.c | 5 ++ example/hello_ll.c | 4 ++ lib/fuse.c | 33 ++++++++-- lib/fuse_i.h | 34 ++++++++++ lib/fuse_lowlevel.c | 30 ++------- lib/helper.c | 4 +- lib/util.h | 27 ++++++++ test/meson.build | 3 + test/test_want_conversion.c | 152 ++++++++++++++++++++++++++++++++++++++++++++ test/test_write_cache.c | 2 +- 10 files changed, 264 insertions(+), 30 deletions(-) create mode 100644 test/test_want_conversion.c diff --git a/example/hello.c b/example/hello.c index 6df8173..90919f4 100644 --- a/example/hello.c +++ b/example/hello.c @@ -57,6 +57,11 @@ static void *hello_init(struct fuse_conn_info *conn, { (void) conn; cfg->kernel_cache = 1; + + /* Test setting flags the old way */ + conn->want = FUSE_CAP_ASYNC_READ; + conn->want &= ~FUSE_CAP_ASYNC_READ; + return NULL; } diff --git a/example/hello_ll.c b/example/hello_ll.c index 0fcb7fe..12927cc 100644 --- a/example/hello_ll.c +++ b/example/hello_ll.c @@ -59,6 +59,10 @@ static void hello_ll_init(void *userdata, struct fuse_conn_info *conn) /* Disable the receiving and processing of FUSE_INTERRUPT requests */ conn->no_interrupt = 1; + + /* Test setting flags the old way */ + conn->want = FUSE_CAP_ASYNC_READ; + conn->want &= ~FUSE_CAP_ASYNC_READ; } static void hello_ll_getattr(fuse_req_t req, fuse_ino_t ino, diff --git a/lib/fuse.c b/lib/fuse.c index 9335429..136f0c2 100644 --- a/lib/fuse.c +++ b/lib/fuse.c @@ -10,6 +10,8 @@ */ #define _GNU_SOURCE +#include "fuse.h" +#include #include "fuse_config.h" #include "fuse_i.h" @@ -17,7 +19,9 @@ #include "fuse_opt.h" #include "fuse_misc.h" #include "fuse_kernel.h" +#include "util.h" +#include #include #include #include @@ -2606,13 +2610,34 @@ void fuse_fs_init(struct fuse_fs *fs, struct fuse_conn_info *conn, { fuse_get_context()->private_data = fs->user_data; if (!fs->op.write_buf) - conn->want &= ~FUSE_CAP_SPLICE_READ; + fuse_unset_feature_flag(conn, FUSE_CAP_SPLICE_READ); if (!fs->op.lock) - conn->want &= ~FUSE_CAP_POSIX_LOCKS; + fuse_unset_feature_flag(conn, FUSE_CAP_POSIX_LOCKS); if (!fs->op.flock) - conn->want &= ~FUSE_CAP_FLOCK_LOCKS; - if (fs->op.init) + fuse_unset_feature_flag(conn, FUSE_CAP_FLOCK_LOCKS); + if (fs->op.init) { + uint64_t want_ext_default = conn->want_ext; + uint32_t want_default = fuse_lower_32_bits(conn->want_ext); + int rc; + + conn->want = want_default; fs->user_data = fs->op.init(conn, cfg); + + rc = convert_to_conn_want_ext(conn, want_ext_default, + want_default); + + if (rc != 0) { + /* + * This is a grave developer error, but + * we cannot return an error here, as the function + * signature does not allow it. + */ + fuse_log( + FUSE_LOG_ERR, + "fuse: Aborting due to invalid conn want flags.\n"); + _exit(EXIT_FAILURE); + } + } } static int fuse_init_intr_signal(int signum, int *installed); diff --git a/lib/fuse_i.h b/lib/fuse_i.h index ea04c34..6fbfc2d 100644 --- a/lib/fuse_i.h +++ b/lib/fuse_i.h @@ -8,8 +8,11 @@ #include "fuse.h" #include "fuse_lowlevel.h" +#include "util.h" +#include #include +#include #define MIN(a, b) \ ({ \ @@ -222,3 +225,34 @@ int fuse_loop_cfg_verify(struct fuse_loop_config *config); /* room needed in buffer to accommodate header */ #define FUSE_BUFFER_HEADER_SIZE 0x1000 +/** + * Get the wanted capability flags, converting from old format if necessary + */ +static inline int convert_to_conn_want_ext(struct fuse_conn_info *conn, + uint64_t want_ext_default, + uint32_t want_default) +{ + /* + * Convert want to want_ext if necessary. + * For the high level interface this function might be called + * twice, once from the high level interface and once from the + * low level interface. Both, with different want_ext_default and + * want_default values. In order to suppress a failure for the + * second call, we check if the lower 32 bits of want_ext are + * already set to the value of want. + */ + if (conn->want != want_default && + fuse_lower_32_bits(conn->want_ext) != conn->want) { + if (conn->want_ext != want_ext_default) { + fuse_log(FUSE_LOG_ERR, + "fuse: both 'want' and 'want_ext' are set\n"); + return -EINVAL; + } + + /* high bits from want_ext, low bits from want */ + conn->want_ext = fuse_higher_32_bits(conn->want_ext) | + conn->want; + } + + return 0; +} diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c index d650944..c22b4a2 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -9,7 +9,6 @@ See the file COPYING.LIB */ -#include #define _GNU_SOURCE #include "fuse_config.h" @@ -20,6 +19,8 @@ #include "mount_util.h" #include "util.h" +#include +#include #include #include #include @@ -1997,25 +1998,6 @@ static bool want_flags_valid(uint64_t capable, uint64_t want) return true; } -/** - * Get the wanted capability flags, converting from old format if necessary - */ -static inline int convert_to_conn_want_ext(struct fuse_conn_info *conn, - uint64_t want_ext_default) -{ - /* Convert want to want_ext if necessary */ - if (conn->want != 0) { - if (conn->want_ext != want_ext_default) { - fuse_log(FUSE_LOG_ERR, - "fuse: both 'want' and 'want_ext' are set\n"); - return -EINVAL; - } - conn->want_ext |= conn->want; - } - - return 0; -} - /* Prevent bogus data races (bogus since "init" is called before * multi-threading becomes relevant */ static __attribute__((no_sanitize("thread"))) @@ -2177,11 +2159,12 @@ void do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) se->got_init = 1; if (se->op.init) { uint64_t want_ext_default = se->conn.want_ext; + uint32_t want_default = fuse_lower_32_bits(se->conn.want_ext); int rc; // Apply the first 32 bits of capable_ext to capable - se->conn.capable = - (uint32_t)(se->conn.capable_ext & 0xFFFFFFFF); + se->conn.capable = fuse_lower_32_bits(se->conn.capable_ext); + se->conn.want = want_default; se->op.init(se->userdata, &se->conn); @@ -2190,7 +2173,8 @@ void do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) * se->conn.want_ext * Userspace might still use conn.want - we need to convert it */ - rc = convert_to_conn_want_ext(&se->conn, want_ext_default); + rc = convert_to_conn_want_ext(&se->conn, want_ext_default, + want_default); if (rc != 0) { fuse_reply_err(req, EPROTO); se->error = -EPROTO; diff --git a/lib/helper.c b/lib/helper.c index a7b2fe0..59dd488 100644 --- a/lib/helper.c +++ b/lib/helper.c @@ -424,9 +424,9 @@ void fuse_apply_conn_info_opts(struct fuse_conn_info_opts *opts, conn->max_readahead = opts->max_readahead; #define LL_ENABLE(cond,cap) \ - if (cond) conn->want |= (cap) + if (cond) conn->want_ext |= (cap) #define LL_DISABLE(cond,cap) \ - if (cond) conn->want &= ~(cap) + if (cond) conn->want_ext &= ~(cap) LL_ENABLE(opts->splice_read, FUSE_CAP_SPLICE_READ); LL_DISABLE(opts->no_splice_read, FUSE_CAP_SPLICE_READ); diff --git a/lib/util.h b/lib/util.h index 74ce748..0c4c258 100644 --- a/lib/util.h +++ b/lib/util.h @@ -1,3 +1,30 @@ +#ifndef FUSE_UTIL_H_ +#define FUSE_UTIL_H_ + +#include + #define ROUND_UP(val, round_to) (((val) + (round_to - 1)) & ~(round_to - 1)) int libfuse_strtol(const char *str, long *res); + +/** + * Return the low bits of a number + */ +static inline uint32_t fuse_lower_32_bits(uint64_t nr) +{ + return (uint32_t)(nr & 0xffffffff); +} + +/** + * Return the high bits of a number + */ +static inline uint64_t fuse_higher_32_bits(uint64_t nr) +{ + return nr & ~0xffffffffULL; +} + +#ifndef FUSE_VAR_UNUSED +#define FUSE_VAR_UNUSED(var) (__attribute__((unused)) var) +#endif + +#endif diff --git a/test/meson.build b/test/meson.build index 3d74b9a..5997030 100644 --- a/test/meson.build +++ b/test/meson.build @@ -16,6 +16,9 @@ td += executable('readdir_inode', 'readdir_inode.c', td += executable('release_unlink_race', 'release_unlink_race.c', dependencies: [ libfuse_dep ], install: false) +td += executable('test_want_conversion', 'test_want_conversion.c', + dependencies: [ libfuse_dep ], + install: false) test_scripts = [ 'conftest.py', 'pytest.ini', 'test_examples.py', 'util.py', 'test_ctests.py', 'test_custom_io.py' ] diff --git a/test/test_want_conversion.c b/test/test_want_conversion.c new file mode 100644 index 0000000..935b58d --- /dev/null +++ b/test/test_want_conversion.c @@ -0,0 +1,152 @@ +#include "util.h" +#include +#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 17) + +#include "fuse_i.h" +#include +#include +#include + +static void print_conn_info(const char *prefix, struct fuse_conn_info *conn) +{ + printf("%s: want=0x%" PRIx32 " want_ext=0x%" PRIx64 "\n", prefix, + conn->want, conn->want_ext); +} + +static void application_init(struct fuse_conn_info *conn) +{ + /* Simulate application init */ + conn->want |= FUSE_CAP_ASYNC_READ; + conn->want &= ~FUSE_CAP_SPLICE_READ; +} + +static void test_fuse_fs_init(struct fuse_conn_info *conn) +{ + uint64_t want_ext_default = conn->want_ext; + uint32_t want_default = fuse_lower_32_bits(conn->want_ext); + int rc; + + /* High-level init */ + fuse_set_feature_flag(conn, FUSE_CAP_EXPORT_SUPPORT); + + conn->want = want_default; + + application_init(conn); + + rc = convert_to_conn_want_ext(conn, want_ext_default, want_default); + assert(rc == 0); +} + +static void test_do_init(struct fuse_conn_info *conn) +{ + /* Initial setup */ + conn->capable_ext = FUSE_CAP_SPLICE_READ | FUSE_CAP_SPLICE_WRITE | + FUSE_CAP_SPLICE_MOVE | FUSE_CAP_POSIX_LOCKS | + FUSE_CAP_FLOCK_LOCKS | FUSE_CAP_EXPORT_SUPPORT; + conn->capable = fuse_lower_32_bits(conn->capable_ext); + conn->want_ext = conn->capable_ext; + + print_conn_info("Initial state", conn); + + uint64_t want_ext_default = conn->want_ext; + uint32_t want_default = fuse_lower_32_bits(conn->want_ext); + int rc; + + conn->want = want_default; + conn->capable = fuse_lower_32_bits(conn->capable_ext); + + test_fuse_fs_init(conn); + + rc = convert_to_conn_want_ext(conn, want_ext_default, want_default); + assert(rc == 0); + + /* Verify all expected flags are set */ + assert(!(conn->want_ext & FUSE_CAP_SPLICE_READ)); + assert(conn->want_ext & FUSE_CAP_SPLICE_WRITE); + assert(conn->want_ext & FUSE_CAP_SPLICE_MOVE); + assert(conn->want_ext & FUSE_CAP_POSIX_LOCKS); + assert(conn->want_ext & FUSE_CAP_FLOCK_LOCKS); + assert(conn->want_ext & FUSE_CAP_EXPORT_SUPPORT); + assert(conn->want_ext & FUSE_CAP_ASYNC_READ); + /* Verify no other flags are set */ + assert(conn->want_ext == + (FUSE_CAP_SPLICE_WRITE | FUSE_CAP_SPLICE_MOVE | + FUSE_CAP_POSIX_LOCKS | FUSE_CAP_FLOCK_LOCKS | + FUSE_CAP_EXPORT_SUPPORT | FUSE_CAP_ASYNC_READ)); + + print_conn_info("After init", conn); +} + +static void test_want_conversion_basic(void) +{ + struct fuse_conn_info conn = { 0 }; + + printf("\nTesting basic want conversion:\n"); + test_do_init(&conn); + print_conn_info("After init", &conn); +} + +static void test_want_conversion_conflict(void) +{ + struct fuse_conn_info conn = { 0 }; + int rc; + + printf("\nTesting want conversion conflict:\n"); + + /* Test conflicting values */ + /* Initialize like fuse_lowlevel.c does */ + conn.capable_ext = FUSE_CAP_SPLICE_READ | FUSE_CAP_SPLICE_WRITE | + FUSE_CAP_SPLICE_MOVE | FUSE_CAP_POSIX_LOCKS | + FUSE_CAP_FLOCK_LOCKS; + conn.capable = fuse_lower_32_bits(conn.capable_ext); + conn.want_ext = conn.capable_ext; + conn.want = fuse_lower_32_bits(conn.want_ext); + print_conn_info("Test conflict initial", &conn); + + /* Initialize default values like in basic test */ + uint64_t want_ext_default_ll = conn.want_ext; + uint32_t want_default_ll = fuse_lower_32_bits(want_ext_default_ll); + + /* Simulate application init modifying capabilities */ + conn.want_ext |= FUSE_CAP_ATOMIC_O_TRUNC; /* Add new capability */ + conn.want &= ~FUSE_CAP_SPLICE_READ; /* Remove a capability */ + + rc = convert_to_conn_want_ext(&conn, want_ext_default_ll, + want_default_ll); + assert(rc == -EINVAL); + print_conn_info("Test conflict after", &conn); + + printf("Want conversion conflict test passed\n"); +} + +static void test_want_conversion_high_bits(void) +{ + struct fuse_conn_info conn = { 0 }; + int rc; + + printf("\nTesting want conversion high bits preservation:\n"); + + /* Test high bits preservation */ + conn.want_ext = (1ULL << 33) | FUSE_CAP_ASYNC_READ; + conn.want = fuse_lower_32_bits(conn.want_ext); + print_conn_info("Test high bits initial", &conn); + + uint64_t want_ext_default_ll = conn.want_ext; + uint32_t want_default_ll = fuse_lower_32_bits(want_ext_default_ll); + + rc = convert_to_conn_want_ext(&conn, want_ext_default_ll, + want_default_ll); + assert(rc == 0); + assert(conn.want_ext == ((1ULL << 33) | FUSE_CAP_ASYNC_READ)); + print_conn_info("Test high bits after", &conn); + + printf("Want conversion high bits test passed\n"); +} + +int main(void) +{ + test_want_conversion_basic(); + test_want_conversion_conflict(); + test_want_conversion_high_bits(); + return 0; +} diff --git a/test/test_write_cache.c b/test/test_write_cache.c index 0c8fa7e..9f21f02 100644 --- a/test/test_write_cache.c +++ b/test/test_write_cache.c @@ -69,7 +69,7 @@ static void tfs_init (void *userdata, struct fuse_conn_info *conn) if(options.writeback) { assert(fuse_get_feature_flag(conn, FUSE_CAP_WRITEBACK_CACHE)); - conn->want |= FUSE_CAP_WRITEBACK_CACHE; + fuse_set_feature_flag(conn, FUSE_CAP_WRITEBACK_CACHE); } } -- cgit v1.2.3