aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/checkpatch.yml2
-rw-r--r--example/hello.c5
-rw-r--r--example/hello_ll.c4
-rw-r--r--lib/fuse.c33
-rw-r--r--lib/fuse_i.h34
-rw-r--r--lib/fuse_lowlevel.c30
-rw-r--r--lib/helper.c4
-rw-r--r--lib/util.h27
-rw-r--r--test/meson.build3
-rw-r--r--test/test_want_conversion.c152
-rw-r--r--test/test_write_cache.c2
11 files changed, 265 insertions, 31 deletions
diff --git a/.github/workflows/checkpatch.yml b/.github/workflows/checkpatch.yml
index 45133eb..8eb6acf 100644
--- a/.github/workflows/checkpatch.yml
+++ b/.github/workflows/checkpatch.yml
@@ -30,7 +30,7 @@ jobs:
git rev-list --reverse $base_commit..HEAD | while read commit; do
subject=$(git log -1 --format=%s $commit)
echo "Checking commit: $commit - $subject"
- if ! ./checkpatch.pl --max-line-length=100 --no-tree --ignore MAINTAINERS,SPDX_LICENSE_TAG,COMMIT_MESSAGE,FILE_PATH_CHANGES,EMAIL_SUBJECT,AVOID_EXTERNS,GIT_COMMIT_ID,ENOSYS_SYSCALL,ENOSYS,FROM_SIGN_OFF_MISMATCH,QUOTED_COMMIT_ID -g $commit; then
+ if ! ./checkpatch.pl --max-line-length=100 --no-tree --ignore MAINTAINERS,SPDX_LICENSE_TAG,COMMIT_MESSAGE,FILE_PATH_CHANGES,EMAIL_SUBJECT,AVOID_EXTERNS,GIT_COMMIT_ID,ENOSYS_SYSCALL,ENOSYS,FROM_SIGN_OFF_MISMATCH,QUOTED_COMMIT_ID,PREFER_ATTRIBUTE_ALWAYS_UNUSED,PREFER_DEFINED_ATTRIBUTE_MACRO -g $commit; then
echo "checkpatch.pl found issues in commit $commit - $subject"
exit 1
fi
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 <pthread.h>
#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 <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
@@ -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 bf8410f..23fcaa6 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 <stdint.h>
#include <stdbool.h>
+#include <errno.h>
#define MIN(a, b) \
({ \
@@ -224,3 +227,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 1e2491b..17bc816 100644
--- a/lib/fuse_lowlevel.c
+++ b/lib/fuse_lowlevel.c
@@ -9,7 +9,6 @@
See the file COPYING.LIB
*/
-#include <stdbool.h>
#define _GNU_SOURCE
#include "fuse_config.h"
@@ -20,6 +19,8 @@
#include "mount_util.h"
#include "util.h"
+#include <stdint.h>
+#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
@@ -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 1fe3fde..0b0dad2 100644
--- a/lib/helper.c
+++ b/lib/helper.c
@@ -426,13 +426,13 @@ void fuse_apply_conn_info_opts(struct fuse_conn_info_opts *opts,
#define LL_ENABLE(cond, cap) \
do { \
if (cond) \
- conn->want |= (cap); \
+ conn->want_ext |= (cap); \
} while (0)
#define LL_DISABLE(cond, cap) \
do { \
if (cond) \
- conn->want &= ~(cap); \
+ conn->want_ext &= ~(cap); \
} while (0)
LL_ENABLE(opts->splice_read, FUSE_CAP_SPLICE_READ);
diff --git a/lib/util.h b/lib/util.h
index a5c5463..508fafb 100644
--- a/lib/util.h
+++ b/lib/util.h
@@ -1,6 +1,33 @@
+#ifndef FUSE_UTIL_H_
+#define FUSE_UTIL_H_
+
+#include <stdint.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)
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 <string.h>
+#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 17)
+
+#include "fuse_i.h"
+#include <stdio.h>
+#include <assert.h>
+#include <inttypes.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);
+}
+
+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);
}
}