aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBernd Schubert <bschubert@ddn.com>2025-04-16 00:24:42 +0200
committerBernd Schubert <bernd@bsbernd.com>2025-04-16 13:48:42 +0200
commit3863da58b1f7904675ca050434d8219bc410f34a (patch)
treea10b16ccda64e72fd4eeff0a69066c8436a9374f
parentd66ca89e86a72fa5ad48d88ef5570062a79397be (diff)
downloadlibfuse-3863da58b1f7904675ca050434d8219bc410f34a.tar.gz
conn: prevent duplicate flag conversion in high-level interface
The high-level interface triggers flag conversion twice: once in the high-level init and once in the low-level init. This caused false "both 'want' and 'want_ext' are set" errors when using fuse_set_feature_flag() or fuse_unset_feature_flag(). The existing check for duplicate conversion only worked when 32-bit flags were set directly. When using the preferred flag manipulation functions, conn->want and the lower 32 bits of conn->want_ext would differ, triggering the error. Fix this by synchronizing conn->want with the lower 32 bits of conn->want_ext after conversion, ensuring consistent state for subsequent calls. Closes: https://github.com/libfuse/libfuse/issues/1171 Signed-off-by: Bernd Schubert <bschubert@ddn.com>
-rw-r--r--example/hello.c4
-rw-r--r--lib/fuse_i.h3
-rw-r--r--test/hello.c180
-rw-r--r--test/meson.build2
-rwxr-xr-xtest/test_examples.py11
-rw-r--r--test/test_want_conversion.c30
6 files changed, 215 insertions, 15 deletions
diff --git a/example/hello.c b/example/hello.c
index 90919f4..d9f01b9 100644
--- a/example/hello.c
+++ b/example/hello.c
@@ -59,8 +59,8 @@ static void *hello_init(struct fuse_conn_info *conn,
cfg->kernel_cache = 1;
/* Test setting flags the old way */
- conn->want = FUSE_CAP_ASYNC_READ;
- conn->want &= ~FUSE_CAP_ASYNC_READ;
+ fuse_set_feature_flag(conn, FUSE_CAP_ASYNC_READ);
+ fuse_unset_feature_flag(conn, FUSE_CAP_ASYNC_READ);
return NULL;
}
diff --git a/lib/fuse_i.h b/lib/fuse_i.h
index 23fcaa6..48b8294 100644
--- a/lib/fuse_i.h
+++ b/lib/fuse_i.h
@@ -256,5 +256,8 @@ static inline int convert_to_conn_want_ext(struct fuse_conn_info *conn,
conn->want;
}
+ /* ensure there won't be a second conversion */
+ conn->want = fuse_lower_32_bits(conn->want_ext);
+
return 0;
}
diff --git a/test/hello.c b/test/hello.c
new file mode 100644
index 0000000..a07df0e
--- /dev/null
+++ b/test/hello.c
@@ -0,0 +1,180 @@
+/*
+ * FUSE: Filesystem in Userspace
+ * Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+ *
+ * This program can be distributed under the terms of the GNU GPLv2.
+ * See the file COPYING.
+ */
+
+/** @file
+ *
+ * minimal example filesystem using high-level API
+ *
+ * Compile with:
+ *
+ * gcc -Wall hello.c `pkg-config fuse3 --cflags --libs` -o hello
+ *
+ * ## Source code ##
+ * \include hello.c
+ */
+
+#define FUSE_USE_VERSION 31
+
+#include <fuse.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stddef.h>
+#include <assert.h>
+
+/*
+ * Command line options
+ *
+ * We can't set default values for the char* fields here because
+ * fuse_opt_parse would attempt to free() them when the user specifies
+ * different values on the command line.
+ */
+static struct options {
+ const char *filename;
+ const char *contents;
+ int show_help;
+} options;
+
+#define OPTION(t, p) { t, offsetof(struct options, p), 1 }
+static const struct fuse_opt option_spec[] = {
+ OPTION("--name=%s", filename), OPTION("--contents=%s", contents),
+ OPTION("-h", show_help), OPTION("--help", show_help), FUSE_OPT_END
+};
+
+static void *hello_init(struct fuse_conn_info *conn, struct fuse_config *cfg)
+{
+ (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;
+}
+
+static int hello_getattr(const char *path, struct stat *stbuf,
+ struct fuse_file_info *fi)
+{
+ (void)fi;
+ int res = 0;
+
+ memset(stbuf, 0, sizeof(struct stat));
+ if (strcmp(path, "/") == 0) {
+ stbuf->st_mode = S_IFDIR | 0755;
+ stbuf->st_nlink = 2;
+ } else if (strcmp(path + 1, options.filename) == 0) {
+ stbuf->st_mode = S_IFREG | 0444;
+ stbuf->st_nlink = 1;
+ stbuf->st_size = strlen(options.contents);
+ } else
+ res = -ENOENT;
+
+ return res;
+}
+
+static int hello_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+ off_t offset, struct fuse_file_info *fi,
+ enum fuse_readdir_flags flags)
+{
+ (void)offset;
+ (void)fi;
+ (void)flags;
+
+ if (strcmp(path, "/") != 0)
+ return -ENOENT;
+
+ filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+ filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+ filler(buf, options.filename, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
+ return 0;
+}
+
+static int hello_open(const char *path, struct fuse_file_info *fi)
+{
+ if (strcmp(path + 1, options.filename) != 0)
+ return -ENOENT;
+
+ if ((fi->flags & O_ACCMODE) != O_RDONLY)
+ return -EACCES;
+
+ return 0;
+}
+
+static int hello_read(const char *path, char *buf, size_t size, off_t offset,
+ struct fuse_file_info *fi)
+{
+ size_t len;
+ (void)fi;
+ if (strcmp(path + 1, options.filename) != 0)
+ return -ENOENT;
+
+ len = strlen(options.contents);
+ if (offset < len) {
+ if (offset + size > len)
+ size = len - offset;
+ memcpy(buf, options.contents + offset, size);
+ } else
+ size = 0;
+
+ return size;
+}
+
+static const struct fuse_operations hello_oper = {
+ .init = hello_init,
+ .getattr = hello_getattr,
+ .readdir = hello_readdir,
+ .open = hello_open,
+ .read = hello_read,
+};
+
+static void show_help(const char *progname)
+{
+ printf("usage: %s [options] <mountpoint>\n\n", progname);
+ printf("File-system specific options:\n"
+ " --name=<s> Name of the \"hello\" file\n"
+ " (default: \"hello\")\n"
+ " --contents=<s> Contents \"hello\" file\n"
+ " (default \"Hello, World!\\n\")\n"
+ "\n");
+}
+
+int main(int argc, char *argv[])
+{
+ int ret;
+ struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
+ /* Set defaults -- we have to use strdup so that
+ * fuse_opt_parse can free the defaults if other
+ * values are specified
+ */
+ options.filename = strdup("hello");
+ options.contents = strdup("Hello World!\n");
+
+ /* Parse options */
+ if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+ return 1;
+
+ /* When --help is specified, first print our own file-system
+ * specific help text, then signal fuse_main to show
+ * additional help (by adding `--help` to the options again)
+ * without usage: line (by setting argv[0] to the empty
+ * string)
+ */
+ if (options.show_help) {
+ show_help(argv[0]);
+ assert(fuse_opt_add_arg(&args, "--help") == 0);
+ args.argv[0][0] = '\0';
+ }
+
+ ret = fuse_main(args.argc, args.argv, &hello_oper, NULL);
+ fuse_opt_free_args(&args);
+ return ret;
+}
diff --git a/test/meson.build b/test/meson.build
index 5997030..56568d8 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -1,6 +1,6 @@
# Compile helper programs
td = []
-foreach prog: [ 'test_write_cache', 'test_setattr' ]
+foreach prog: [ 'test_write_cache', 'test_setattr', 'hello' ]
td += executable(prog, prog + '.c',
include_directories: include_dirs,
link_with: [ libfuse ],
diff --git a/test/test_examples.py b/test/test_examples.py
index 54a2f88..9c8b77e 100755
--- a/test/test_examples.py
+++ b/test/test_examples.py
@@ -44,8 +44,13 @@ if sys.platform == 'linux':
options.append('clone_fd')
def invoke_directly(mnt_dir, name, options):
- cmdline = base_cmdline + [ pjoin(basename, 'example', name),
- '-f', mnt_dir, '-o', ','.join(options) ]
+ # Handle test/hello specially since it's not in example/
+ if name.startswith('test/'):
+ path = pjoin(basename, name)
+ else:
+ path = pjoin(basename, 'example', name)
+
+ cmdline = base_cmdline + [ path, '-f', mnt_dir, '-o', ','.join(options) ]
if name == 'hello_ll':
# supports single-threading only
cmdline.append('-s')
@@ -88,7 +93,7 @@ def readdir_inode(dir):
@pytest.mark.parametrize("cmdline_builder", (invoke_directly, invoke_mount_fuse,
invoke_mount_fuse_drop_privileges))
@pytest.mark.parametrize("options", powerset(options))
-@pytest.mark.parametrize("name", ('hello', 'hello_ll'))
+@pytest.mark.parametrize("name", ('hello', 'hello_ll', 'test/hello'))
def test_hello(tmpdir, name, options, cmdline_builder, output_checker):
logger = logging.getLogger(__name__)
mnt_dir = str(tmpdir)
diff --git a/test/test_want_conversion.c b/test/test_want_conversion.c
index 935b58d..bee23cc 100644
--- a/test/test_want_conversion.c
+++ b/test/test_want_conversion.c
@@ -1,11 +1,11 @@
#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>
+#include <stdbool.h>
static void print_conn_info(const char *prefix, struct fuse_conn_info *conn)
{
@@ -13,14 +13,21 @@ static void print_conn_info(const char *prefix, struct fuse_conn_info *conn)
conn->want, conn->want_ext);
}
-static void application_init(struct fuse_conn_info *conn)
+static void application_init_old_style(struct fuse_conn_info *conn)
{
- /* Simulate application init */
+ /* Simulate application init the old style */
conn->want |= FUSE_CAP_ASYNC_READ;
conn->want &= ~FUSE_CAP_SPLICE_READ;
}
-static void test_fuse_fs_init(struct fuse_conn_info *conn)
+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_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);
@@ -31,18 +38,22 @@ static void test_fuse_fs_init(struct fuse_conn_info *conn)
conn->want = want_default;
- application_init(conn);
+ 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)
+static void test_do_init(struct fuse_conn_info *conn, bool new_style)
{
/* 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;
+ FUSE_CAP_FLOCK_LOCKS | FUSE_CAP_EXPORT_SUPPORT |
+ FUSE_CAP_ASYNC_READ;
conn->capable = fuse_lower_32_bits(conn->capable_ext);
conn->want_ext = conn->capable_ext;
@@ -55,7 +66,7 @@ static void test_do_init(struct fuse_conn_info *conn)
conn->want = want_default;
conn->capable = fuse_lower_32_bits(conn->capable_ext);
- test_fuse_fs_init(conn);
+ test_fuse_fs_init(conn, new_style);
rc = convert_to_conn_want_ext(conn, want_ext_default, want_default);
assert(rc == 0);
@@ -82,7 +93,8 @@ static void test_want_conversion_basic(void)
struct fuse_conn_info conn = { 0 };
printf("\nTesting basic want conversion:\n");
- test_do_init(&conn);
+ test_do_init(&conn, false);
+ test_do_init(&conn, true);
print_conn_info("After init", &conn);
}