aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBernd Schubert <bschubert@ddn.com>2025-05-18 00:24:07 +0200
committerBernd Schubert <bernd@bsbernd.com>2025-05-20 18:14:44 +0200
commitbaadab0492a495fda98216b351976d2e5d6d0866 (patch)
tree61017d2976439c6b7453b402aad5ecd3816b72ef
parenteadd6a5454373c7c404463c81be652275da5e07b (diff)
downloadlibfuse-baadab0492a495fda98216b351976d2e5d6d0866.tar.gz
conn->want conversion: Fix fuse_apply_conn_info_opts()
fuse_apply_conn_info_opts() was applying to 'want_ext', which would cause conflicts with 'want' if the application applied its own flags to 'conn->want'. Solution is: - to move fuse_{set,unset,get}_feature_flag and convert_to_conn_want_ext() to fuse_lowlevel.c and to define them as part of the public API, although convert_to_conn_want_ext() should not be used - it is currently needed to be a public function due as it needs to be defined for the tests. Related to https://github.com/libfuse/libfuse/issues/1171 and https://github.com/libfuse/libfuse/pull/1172. Closes: https://github.com/libfuse/libfuse/issues/1171 Signed-off-by: Bernd Schubert <bschubert@ddn.com>
-rw-r--r--include/fuse_common.h50
-rw-r--r--lib/fuse.c9
-rw-r--r--lib/fuse_i.h38
-rw-r--r--lib/fuse_lowlevel.c78
-rw-r--r--lib/fuse_versionscript6
-rw-r--r--lib/helper.c4
-rw-r--r--lib/util.c8
-rw-r--r--lib/util.h3
-rw-r--r--test/test_want_conversion.c97
9 files changed, 187 insertions, 106 deletions
diff --git a/include/fuse_common.h b/include/fuse_common.h
index 0b0ea4c..054c618 100644
--- a/include/fuse_common.h
+++ b/include/fuse_common.h
@@ -1107,28 +1107,40 @@ void fuse_loop_cfg_convert(struct fuse_loop_config *config,
struct fuse_loop_config_v1 *v1_conf);
#endif
+/**
+ * Set a feature flag in the want_ext field of fuse_conn_info.
+ *
+ * @param conn connection information
+ * @param flag feature flag to be set
+ * @return true if the flag was set, false if the flag is not supported
+ */
+bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag);
-static inline bool fuse_set_feature_flag(struct fuse_conn_info *conn,
- uint64_t flag)
-{
- if (conn->capable_ext & flag) {
- conn->want_ext |= flag;
- return true;
- }
- return false;
-}
+/**
+ * Unset a feature flag in the want_ext field of fuse_conn_info.
+ *
+ * @param conn connection information
+ * @param flag feature flag to be unset
+ */
+void fuse_unset_feature_flag(struct fuse_conn_info *conn, uint64_t flag);
+
+/**
+ * Get the value of a feature flag in the want_ext field of fuse_conn_info.
+ *
+ * @param conn connection information
+ * @param flag feature flag to be checked
+ * @return true if the flag is set, false otherwise
+ */
+bool fuse_get_feature_flag(struct fuse_conn_info *conn, uint64_t flag);
+
+/*
+ * DO NOT USE: Not part of public API, for internal test use only.
+ * The function signature or any use of it is not guaranteeed to
+ * remain stable. And neither are results of what this function does.
+ */
+int fuse_convert_to_conn_want_ext(struct fuse_conn_info *conn);
-static inline void fuse_unset_feature_flag(struct fuse_conn_info *conn,
- uint64_t flag)
-{
- conn->want_ext &= ~flag;
-}
-static inline bool fuse_get_feature_flag(struct fuse_conn_info *conn,
- uint64_t flag)
-{
- return conn->capable_ext & flag ? true : false;
-}
/* ----------------------------------------------------------- *
* Compatibility stuff *
diff --git a/lib/fuse.c b/lib/fuse.c
index ccf4aeb..b116661 100644
--- a/lib/fuse.c
+++ b/lib/fuse.c
@@ -2560,15 +2560,8 @@ void fuse_fs_init(struct fuse_fs *fs, struct fuse_conn_info *conn,
fuse_unset_feature_flag(conn, FUSE_CAP_POSIX_LOCKS);
if (!fs->op.flock)
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);
-
- conn->want = want_default;
+ if (fs->op.init)
fs->user_data = fs->op.init(conn, cfg);
-
- convert_to_conn_want_ext(conn, want_ext_default, want_default);
- }
}
static int fuse_init_intr_signal(int signum, int *installed);
diff --git a/lib/fuse_i.h b/lib/fuse_i.h
index 14d99ad..80ec803 100644
--- a/lib/fuse_i.h
+++ b/lib/fuse_i.h
@@ -106,6 +106,13 @@ struct fuse_session {
/* io_uring */
struct fuse_session_uring uring;
+
+ /*
+ * conn->want and conn_want_ext options set by libfuse , needed
+ * to correctly convert want to want_ext
+ */
+ uint32_t conn_want;
+ uint64_t conn_want_ext;
};
struct fuse_chan {
@@ -250,36 +257,5 @@ 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)
- return -EINVAL;
-
- /* high bits from want_ext, low bits from want */
- conn->want_ext = fuse_higher_32_bits(conn->want_ext) |
- conn->want;
- }
-
- /* ensure there won't be a second conversion */
- conn->want = fuse_lower_32_bits(conn->want_ext);
-
- return 0;
-}
#endif /* LIB_FUSE_I_H_*/
diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c
index 9fb793b..c2f3ccb 100644
--- a/lib/fuse_lowlevel.c
+++ b/lib/fuse_lowlevel.c
@@ -2435,6 +2435,77 @@ static bool want_flags_valid(uint64_t capable, uint64_t want)
return true;
}
+/**
+ * Get the wanted capability flags, converting from old format if necessary
+ */
+int fuse_convert_to_conn_want_ext(struct fuse_conn_info *conn)
+{
+ struct fuse_session *se = container_of(conn, struct fuse_session, conn);
+
+ /*
+ * 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 != se->conn_want &&
+ fuse_lower_32_bits(conn->want_ext) != conn->want) {
+ if (conn->want_ext != se->conn_want_ext) {
+ fuse_log(FUSE_LOG_ERR,
+ "%s: Both conn->want_ext and conn->want are set.\n"
+ "want=%x, want_ext=%lx, se->want=%lx se->want_ext=%lx\n",
+ __func__, conn->want, conn->want_ext,
+ se->conn_want, se->conn_want_ext);
+ return -EINVAL;
+ }
+
+ /* high bits from want_ext, low bits from want */
+ conn->want_ext = fuse_higher_32_bits(conn->want_ext) |
+ conn->want;
+ }
+
+ /* ensure there won't be a second conversion */
+ conn->want = fuse_lower_32_bits(conn->want_ext);
+
+ return 0;
+}
+
+bool fuse_set_feature_flag(struct fuse_conn_info *conn,
+ uint64_t flag)
+{
+ struct fuse_session *se = container_of(conn, struct fuse_session, conn);
+
+ if (conn->capable_ext & flag) {
+ conn->want_ext |= flag;
+ se->conn_want_ext |= flag;
+ conn->want |= flag;
+ se->conn_want |= flag;
+ return true;
+ }
+ return false;
+}
+
+void fuse_unset_feature_flag(struct fuse_conn_info *conn,
+ uint64_t flag)
+{
+ struct fuse_session *se = container_of(conn, struct fuse_session, conn);
+
+ conn->want_ext &= ~flag;
+ se->conn_want_ext &= ~flag;
+ conn->want &= ~flag;
+ se->conn_want &= ~flag;
+}
+
+bool fuse_get_feature_flag(struct fuse_conn_info *conn,
+ uint64_t flag)
+{
+ return conn->capable_ext & flag ? true : false;
+}
+
+
/* Prevent bogus data races (bogus since "init" is called before
* multi-threading becomes relevant */
static __attribute__((no_sanitize("thread"))) void
@@ -2603,12 +2674,8 @@ _do_init(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in,
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);
-
// Apply the first 32 bits of capable_ext to capable
se->conn.capable = fuse_lower_32_bits(se->conn.capable_ext);
- se->conn.want = want_default;
se->op.init(se->userdata, &se->conn);
@@ -2617,8 +2684,7 @@ _do_init(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in,
* se->conn.want_ext
* Userspace might still use conn.want - we need to convert it
*/
- convert_to_conn_want_ext(&se->conn, want_ext_default,
- want_default);
+ fuse_convert_to_conn_want_ext(&se->conn);
}
if (!want_flags_valid(se->conn.capable_ext, se->conn.want_ext)) {
diff --git a/lib/fuse_versionscript b/lib/fuse_versionscript
index 22c59e1..ab57d7c 100644
--- a/lib/fuse_versionscript
+++ b/lib/fuse_versionscript
@@ -205,6 +205,12 @@ FUSE_3.17 {
FUSE_3.18 {
global:
fuse_req_is_uring;
+ fuse_set_feature_flag;
+ fuse_unset_feature_flag;
+ fuse_get_feature_flag;
+
+ # Not part of public API, for internal test use only
+ fuse_convert_to_conn_want_ext;
} FUSE_3.17;
# Local Variables:
diff --git a/lib/helper.c b/lib/helper.c
index 5811c53..ced70a2 100644
--- a/lib/helper.c
+++ b/lib/helper.c
@@ -430,13 +430,13 @@ void fuse_apply_conn_info_opts(struct fuse_conn_info_opts *opts,
#define LL_ENABLE(cond, cap) \
do { \
if (cond) \
- conn->want_ext |= (cap); \
+ fuse_set_feature_flag(conn, cap); \
} while (0)
#define LL_DISABLE(cond, cap) \
do { \
if (cond) \
- conn->want_ext &= ~(cap); \
+ fuse_unset_feature_flag(conn, cap); \
} while (0)
LL_ENABLE(opts->splice_read, FUSE_CAP_SPLICE_READ);
diff --git a/lib/util.c b/lib/util.c
index 00f1e1a..7583d47 100644
--- a/lib/util.c
+++ b/lib/util.c
@@ -10,7 +10,14 @@
#include <stdlib.h>
#include <errno.h>
+#ifndef FUSE_USE_VERSION
+#define FUSE_USE_VERSION (FUSE_MAKE_VERSION(3, 18))
+#endif
+
#include "util.h"
+#include "fuse_log.h"
+#include "fuse_lowlevel.h"
+#include <stdio.h>
int libfuse_strtol(const char *str, long *res)
{
@@ -44,3 +51,4 @@ void fuse_set_thread_name(unsigned long tid, const char *name)
(void)name;
#endif
}
+
diff --git a/lib/util.h b/lib/util.h
index 96b59d3..d91a5e9 100644
--- a/lib/util.h
+++ b/lib/util.h
@@ -2,12 +2,15 @@
#define FUSE_UTIL_H_
#include <stdint.h>
+#include <stdbool.h>
#define ROUND_UP(val, round_to) (((val) + (round_to - 1)) & ~(round_to - 1))
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
+struct fuse_conn_info;
+
int libfuse_strtol(const char *str, long *res);
void fuse_set_thread_name(unsigned long tid, const char *name);
diff --git a/test/test_want_conversion.c b/test/test_want_conversion.c
index bee23cc..db731ed 100644
--- a/test/test_want_conversion.c
+++ b/test/test_want_conversion.c
@@ -1,16 +1,22 @@
-#include "util.h"
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 17)
+#include "util.h"
#include "fuse_i.h"
+#include "fuse_lowlevel.h"
#include <stdio.h>
#include <assert.h>
#include <inttypes.h>
#include <stdbool.h>
+#include <err.h>
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);
+ struct fuse_session *se = container_of(conn, struct fuse_session, conn);
+
+ printf("%s: want=0x%" PRIx32 " want_ext=0x%" PRIx64
+ " want_default=0x%" PRIx32 " want_ext_default=0x%" PRIx64 "\n",
+ prefix, conn->want, conn->want_ext, se->conn_want,
+ se->conn_want_ext);
}
static void application_init_old_style(struct fuse_conn_info *conn)
@@ -18,33 +24,31 @@ static void application_init_old_style(struct fuse_conn_info *conn)
/* Simulate application init the old style */
conn->want |= FUSE_CAP_ASYNC_READ;
conn->want &= ~FUSE_CAP_SPLICE_READ;
+
+ /*
+ * Also use new style API, as that might happen through
+ * fuse_apply_conn_info_opts()
+ */
+ fuse_set_feature_flag(conn, FUSE_CAP_IOCTL_DIR);
}
static void application_init_new_style(struct fuse_conn_info *conn)
{
/* Simulate application init the new style */
fuse_set_feature_flag(conn, FUSE_CAP_ASYNC_READ);
+ fuse_set_feature_flag(conn, FUSE_CAP_IOCTL_DIR);
fuse_unset_feature_flag(conn, FUSE_CAP_SPLICE_READ);
}
static void test_fuse_fs_init(struct fuse_conn_info *conn, bool new_style)
{
- 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;
-
if (new_style)
application_init_new_style(conn);
else
application_init_old_style(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, bool new_style)
@@ -53,49 +57,71 @@ static void test_do_init(struct fuse_conn_info *conn, bool new_style)
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 |
- FUSE_CAP_ASYNC_READ;
+ FUSE_CAP_ASYNC_READ | FUSE_CAP_IOCTL_DIR;
conn->capable = fuse_lower_32_bits(conn->capable_ext);
- conn->want_ext = conn->capable_ext;
+
+ fuse_set_feature_flag(conn, FUSE_CAP_SPLICE_READ |
+ FUSE_CAP_SPLICE_WRITE |
+ FUSE_CAP_SPLICE_MOVE);
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, new_style);
+ print_conn_info("After init", conn);
- rc = convert_to_conn_want_ext(conn, want_ext_default, want_default);
+ rc = fuse_convert_to_conn_want_ext(conn);
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);
+ assert(conn->want_ext & FUSE_CAP_IOCTL_DIR);
+
/* 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));
+ FUSE_CAP_EXPORT_SUPPORT | FUSE_CAP_ASYNC_READ |
+ FUSE_CAP_IOCTL_DIR));
print_conn_info("After init", conn);
}
static void test_want_conversion_basic(void)
{
- struct fuse_conn_info conn = { 0 };
+ const struct fuse_lowlevel_ops ops = { 0 };
+ struct fuse_args args = FUSE_ARGS_INIT(0, NULL);
+ struct fuse_session *se;
+ struct fuse_conn_info *conn;
+
+ /* Add the program name to arg[0] */
+ if (fuse_opt_add_arg(&args, "test_signals")) {
+ fprintf(stderr, "Failed to add argument\n");
+ errx(1, "Failed to add argument");
+ }
+
+
+ se = fuse_session_new(&args, &ops, sizeof(ops), NULL);
+ assert(se);
+ conn = &se->conn;
+ printf("\nTesting basic want conversion, old style:\n");
+ test_do_init(conn, false);
+ fuse_session_destroy(se);
+
+ se = fuse_session_new(&args, &ops, sizeof(ops), NULL);
+ assert(se);
+ conn = &se->conn;
+ printf("\nTesting basic want conversion, new style:\n");
+ test_do_init(conn, true);
+ print_conn_info("After init", conn);
+ fuse_session_destroy(se);
+
+ fuse_opt_free_args(&args);
- printf("\nTesting basic want conversion:\n");
- test_do_init(&conn, false);
- test_do_init(&conn, true);
- print_conn_info("After init", &conn);
}
static void test_want_conversion_conflict(void)
@@ -115,16 +141,11 @@ static void test_want_conversion_conflict(void)
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);
+ rc = fuse_convert_to_conn_want_ext(&conn);
assert(rc == -EINVAL);
print_conn_info("Test conflict after", &conn);
@@ -143,11 +164,7 @@ static void test_want_conversion_high_bits(void)
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);
+ rc = fuse_convert_to_conn_want_ext(&conn);
assert(rc == 0);
assert(conn.want_ext == ((1ULL << 33) | FUSE_CAP_ASYNC_READ));
print_conn_info("Test high bits after", &conn);