From f68970cd235a7e14026ca0f6240428bbebe8223b 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 --- test/meson.build | 3 + test/test_want_conversion.c | 152 ++++++++++++++++++++++++++++++++++++++++++++ test/test_write_cache.c | 2 +- 3 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 test/test_want_conversion.c (limited to 'test') 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 2e9c671..2b5b4f3 100644 --- a/test/test_write_cache.c +++ b/test/test_write_cache.c @@ -66,7 +66,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