* [PATCH 01/10] test: register pytest run as a meson test
2026-05-08 16:39 [PATCH 00/10] libfuse: new mount API and SYNC_INIT fixes and tests Bernd Schubert via B4 Relay
@ 2026-05-08 16:39 ` Bernd Schubert via B4 Relay
2026-05-08 23:53 ` Darrick J. Wong
2026-05-08 16:39 ` [PATCH 02/10] Add tests to verify that mountinfo matches requested options Bernd Schubert via B4 Relay
` (8 subsequent siblings)
9 siblings, 1 reply; 25+ messages in thread
From: Bernd Schubert via B4 Relay @ 2026-05-08 16:39 UTC (permalink / raw)
To: fuse-devel; +Cc: Darrick J. Wong, Bernd Schubert
From: Bernd Schubert <bernd@bsbernd.com>
Wire the pytest suite into meson as a single test() so it can be run
via 'meson test -C build' (or 'meson test -C build pytest -v') instead
of having to invoke pytest directly. The same definition works for
both unprivileged and root runs -- invoke 'meson test' as your user,
or 'sudo -E meson test' to exercise the tests that need root (these
detect the uid at runtime via os.geteuid()).
Assisted by Claude/Opus 4.7
Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
---
test/ci-build.sh | 9 ++++++---
test/meson.build | 23 +++++++++++++++++++----
2 files changed, 25 insertions(+), 7 deletions(-)
diff --git a/test/ci-build.sh b/test/ci-build.sh
index 799f13b2..727cc8c9 100755
--- a/test/ci-build.sh
+++ b/test/ci-build.sh
@@ -2,7 +2,7 @@
set -e
-TEST_CMD="pytest -vv --tb=short --maxfail=1 --log-level=INFO --log-cli-level=INFO test/"
+TEST_CMD="meson test -C . --print-errorlogs"
SAN="-Db_sanitize=address,undefined"
# not default
@@ -122,8 +122,11 @@ sanitized_build()
sudo chown root:root util/fusermount3
sudo chmod 4755 util/fusermount3
- # Test as root and regular user
- sudo env PATH=$PATH ${TEST_CMD}
+ # Test as root and regular user. Give the root run a distinct
+ # meson log basename so its meson-logs/testlog.* files don't end
+ # up owned by root and block the subsequent user run from writing
+ # them.
+ sudo env PATH=$PATH ${TEST_CMD} --logbase=testlog-root
# Cleanup temporary files (since they are now owned by root)
sudo rm -rf test/.pytest_cache/ test/__pycache__
diff --git a/test/meson.build b/test/meson.build
index 84333f96..87668d61 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -41,13 +41,28 @@ td += custom_target('test_scripts', input: test_scripts,
command: ['cp', '-fPp',
'@INPUT@', meson.current_build_dir() ])
-# Provide something helpful when running 'ninja test'
-
if meson.is_subproject()
test('libfuse is a subproject, skipping tests', executable('wrong_command',
'wrong_command.c', install: false,
c_args: [ '-DMESON_IS_SUBPROJECT' ]))
else
- test('wrong_command', executable('wrong_command', 'wrong_command.c',
- install: false))
+ # Run the pytest suite via 'meson test'. The same definition works
+ # both as a regular user and under sudo: invoke 'meson test -C build'
+ # for the unprivileged subset, or 'sudo -E meson test -C build' to
+ # also exercise the tests that require root (they pick the uid up at
+ # runtime via os.geteuid()).
+ pytest = find_program('pytest', required: false)
+ if pytest.found()
+ test('pytest', pytest,
+ args: ['-vv', '--tb=short', '--maxfail=1',
+ '--log-level=INFO', '--log-cli-level=INFO',
+ meson.current_build_dir()],
+ depends: td,
+ workdir: meson.project_build_root(),
+ timeout: 600,
+ is_parallel: false)
+ else
+ test('pytest not found', executable('wrong_command',
+ 'wrong_command.c', install: false))
+ endif
endif
--
2.53.0
^ permalink raw reply related [flat|nested] 25+ messages in thread* Re: [PATCH 01/10] test: register pytest run as a meson test
2026-05-08 16:39 ` [PATCH 01/10] test: register pytest run as a meson test Bernd Schubert via B4 Relay
@ 2026-05-08 23:53 ` Darrick J. Wong
0 siblings, 0 replies; 25+ messages in thread
From: Darrick J. Wong @ 2026-05-08 23:53 UTC (permalink / raw)
To: bernd; +Cc: fuse-devel
On Fri, May 08, 2026 at 06:39:04PM +0200, Bernd Schubert via B4 Relay wrote:
> From: Bernd Schubert <bernd@bsbernd.com>
>
> Wire the pytest suite into meson as a single test() so it can be run
> via 'meson test -C build' (or 'meson test -C build pytest -v') instead
> of having to invoke pytest directly. The same definition works for
> both unprivileged and root runs -- invoke 'meson test' as your user,
> or 'sudo -E meson test' to exercise the tests that need root (these
> detect the uid at runtime via os.geteuid()).
Hey, that would be a nice simplification!
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
--D
> Assisted by Claude/Opus 4.7
> Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
> ---
> test/ci-build.sh | 9 ++++++---
> test/meson.build | 23 +++++++++++++++++++----
> 2 files changed, 25 insertions(+), 7 deletions(-)
>
> diff --git a/test/ci-build.sh b/test/ci-build.sh
> index 799f13b2..727cc8c9 100755
> --- a/test/ci-build.sh
> +++ b/test/ci-build.sh
> @@ -2,7 +2,7 @@
>
> set -e
>
> -TEST_CMD="pytest -vv --tb=short --maxfail=1 --log-level=INFO --log-cli-level=INFO test/"
> +TEST_CMD="meson test -C . --print-errorlogs"
> SAN="-Db_sanitize=address,undefined"
>
> # not default
> @@ -122,8 +122,11 @@ sanitized_build()
> sudo chown root:root util/fusermount3
> sudo chmod 4755 util/fusermount3
>
> - # Test as root and regular user
> - sudo env PATH=$PATH ${TEST_CMD}
> + # Test as root and regular user. Give the root run a distinct
> + # meson log basename so its meson-logs/testlog.* files don't end
> + # up owned by root and block the subsequent user run from writing
> + # them.
> + sudo env PATH=$PATH ${TEST_CMD} --logbase=testlog-root
> # Cleanup temporary files (since they are now owned by root)
> sudo rm -rf test/.pytest_cache/ test/__pycache__
>
> diff --git a/test/meson.build b/test/meson.build
> index 84333f96..87668d61 100644
> --- a/test/meson.build
> +++ b/test/meson.build
> @@ -41,13 +41,28 @@ td += custom_target('test_scripts', input: test_scripts,
> command: ['cp', '-fPp',
> '@INPUT@', meson.current_build_dir() ])
>
> -# Provide something helpful when running 'ninja test'
> -
> if meson.is_subproject()
> test('libfuse is a subproject, skipping tests', executable('wrong_command',
> 'wrong_command.c', install: false,
> c_args: [ '-DMESON_IS_SUBPROJECT' ]))
> else
> - test('wrong_command', executable('wrong_command', 'wrong_command.c',
> - install: false))
> + # Run the pytest suite via 'meson test'. The same definition works
> + # both as a regular user and under sudo: invoke 'meson test -C build'
> + # for the unprivileged subset, or 'sudo -E meson test -C build' to
> + # also exercise the tests that require root (they pick the uid up at
> + # runtime via os.geteuid()).
> + pytest = find_program('pytest', required: false)
> + if pytest.found()
> + test('pytest', pytest,
> + args: ['-vv', '--tb=short', '--maxfail=1',
> + '--log-level=INFO', '--log-cli-level=INFO',
> + meson.current_build_dir()],
> + depends: td,
> + workdir: meson.project_build_root(),
> + timeout: 600,
> + is_parallel: false)
> + else
> + test('pytest not found', executable('wrong_command',
> + 'wrong_command.c', install: false))
> + endif
> endif
>
> --
> 2.53.0
>
>
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH 02/10] Add tests to verify that mountinfo matches requested options
2026-05-08 16:39 [PATCH 00/10] libfuse: new mount API and SYNC_INIT fixes and tests Bernd Schubert via B4 Relay
2026-05-08 16:39 ` [PATCH 01/10] test: register pytest run as a meson test Bernd Schubert via B4 Relay
@ 2026-05-08 16:39 ` Bernd Schubert via B4 Relay
2026-05-08 23:59 ` Darrick J. Wong
2026-05-08 16:39 ` [PATCH 03/10] test: assert ro/rw, nosuid/suid, nodev/dev round-trip via fsmount Bernd Schubert via B4 Relay
` (7 subsequent siblings)
9 siblings, 1 reply; 25+ messages in thread
From: Bernd Schubert via B4 Relay @ 2026-05-08 16:39 UTC (permalink / raw)
To: fuse-devel; +Cc: Darrick J. Wong, Bernd Schubert
From: Bernd Schubert <bernd@bsbernd.com>
This is especially for the new mount API, but does not hurt
either for the traditional API.
Assisted by Claude Opus 4.7
Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
---
test/meson.build | 3 +-
test/test_mount_state.py | 116 +++++++++++++++++++++++++++++++++++++++++++++++
test/util.py | 45 ++++++++++++++++++
3 files changed, 163 insertions(+), 1 deletion(-)
diff --git a/test/meson.build b/test/meson.build
index 87668d61..55018f92 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -35,7 +35,8 @@ td += executable('test_loop_config', 'test_loop_config.c',
install: false)
test_scripts = [ 'conftest.py', 'pytest.ini', 'test_examples.py',
- 'util.py', 'test_ctests.py', 'test_custom_io.py' ]
+ 'util.py', 'test_ctests.py', 'test_custom_io.py',
+ 'test_mount_state.py' ]
td += custom_target('test_scripts', input: test_scripts,
output: test_scripts, build_by_default: true,
command: ['cp', '-fPp',
diff --git a/test/test_mount_state.py b/test/test_mount_state.py
new file mode 100644
index 00000000..eacb7326
--- /dev/null
+++ b/test/test_mount_state.py
@@ -0,0 +1,116 @@
+#!/usr/bin/env python3
+'''
+Tests that observable mount state (as exposed by /proc/self/mountinfo)
+matches the options requested at mount time.
+
+Existing tests check filesystem behavior (read/write/xattr/...) but
+never inspect the post-mount metadata recorded by the kernel. That
+metadata is populated differently by the legacy mount(2) path and the
+new fsopen/fsconfig/fsmount path, so an option dropped on one path can
+go undetected — the subtype regression in 5e9e16d6 is one example.
+These tests assert what /proc/self/mountinfo reports for each mount,
+so a parity bug between the two paths fails loudly.
+'''
+
+if __name__ == '__main__':
+ import pytest
+ import sys
+ sys.exit(pytest.main([__file__] + sys.argv[1:]))
+
+import os
+import subprocess
+import pytest
+from contextlib import contextmanager
+from os.path import join as pjoin
+from util import (wait_for_mount, umount, cleanup, base_cmdline, basename,
+ fuse_test_marker, parse_mountinfo)
+
+pytestmark = fuse_test_marker()
+
+
+@contextmanager
+def hello_mount(tmpdir, output_checker, name, options=()):
+ mnt_dir = str(tmpdir)
+ cmdline = base_cmdline + [pjoin(basename, 'example', name),
+ '-f', mnt_dir]
+ if name == 'hello_ll':
+ cmdline.append('-s')
+ if options:
+ cmdline += ['-o', ','.join(options)]
+ mp = subprocess.Popen(cmdline, stdout=output_checker.fd,
+ stderr=output_checker.fd)
+ try:
+ wait_for_mount(mp, mnt_dir)
+ yield mnt_dir
+ except:
+ cleanup(mp, mnt_dir)
+ raise
+ else:
+ umount(mp, mnt_dir)
+
+
+@pytest.mark.parametrize('name', ('hello', 'hello_ll'))
+def test_mountinfo_baseline(tmpdir, output_checker, name):
+ # libfuse's add_default_subtype() (lib/helper.c) defaults the
+ # subtype to basename(argv[0]) when the caller didn't pass
+ # -o fsname=/-o subtype=, so the bare-mount fstype is
+ # 'fuse.<example-name>', not 'fuse'. The override case is what
+ # test_mountinfo_subtype below verifies; here we just assert the
+ # fuse-ness and the standard kernel-side identity options.
+ with hello_mount(tmpdir, output_checker, name) as mnt:
+ info = parse_mountinfo(mnt)
+ assert info is not None, 'mountpoint not found in /proc/self/mountinfo'
+ assert info['fstype'] in ('fuse', 'fuse.' + name), \
+ 'unexpected fstype %r (expected fuse or fuse.%s)' % \
+ (info['fstype'], name)
+ assert any(o.startswith('user_id=') for o in info['super_options'])
+ assert any(o.startswith('group_id=') for o in info['super_options'])
+
+
+@pytest.mark.parametrize('name', ('hello', 'hello_ll'))
+def test_mountinfo_subtype(tmpdir, output_checker, name):
+ # Regression guard for 5e9e16d6: the new mount API needs an
+ # explicit fsconfig(SET_STRING,"subtype",...). Without it the
+ # kernel records fstype=='fuse' (or whatever basename default
+ # leaks through) instead of the user-requested 'fuse.<subtype>'.
+ # An explicit -o subtype= must override the basename default.
+ with hello_mount(tmpdir, output_checker, name,
+ ('subtype=mysub',)) as mnt:
+ info = parse_mountinfo(mnt)
+ assert info is not None
+ assert info['fstype'] == 'fuse.mysub', \
+ 'explicit subtype not propagated: fstype=%r' % info['fstype']
+
+
+@pytest.mark.parametrize('name', ('hello', 'hello_ll'))
+def test_mountinfo_fsname(tmpdir, output_checker, name):
+ with hello_mount(tmpdir, output_checker, name,
+ ('fsname=myfsname',)) as mnt:
+ info = parse_mountinfo(mnt)
+ assert info is not None
+ assert info['source'] == 'myfsname', \
+ 'fsname not propagated: source=%r' % info['source']
+
+
+@pytest.mark.parametrize('name', ('hello', 'hello_ll'))
+def test_mountinfo_subtype_fsname(tmpdir, output_checker, name):
+ with hello_mount(tmpdir, output_checker, name,
+ ('subtype=mysub', 'fsname=myfsname')) as mnt:
+ info = parse_mountinfo(mnt)
+ assert info is not None
+ assert info['fstype'] == 'fuse.mysub'
+ # 'mysub#myfsname' is the ENODEV-fallback form when the kernel
+ # rejects fuse.<subtype>; accept either so the test isn't fragile.
+ assert info['source'] in ('myfsname', 'mysub#myfsname'), \
+ 'unexpected source: %r' % info['source']
+
+
+@pytest.mark.parametrize('name', ('hello', 'hello_ll'))
+def test_mountinfo_unprivileged_attrs(tmpdir, output_checker, name):
+ if os.getuid() == 0:
+ pytest.skip('only meaningful for unprivileged mounts via fusermount3')
+ with hello_mount(tmpdir, output_checker, name) as mnt:
+ info = parse_mountinfo(mnt)
+ assert info is not None
+ assert 'nosuid' in info['mount_options']
+ assert 'nodev' in info['mount_options']
diff --git a/test/util.py b/test/util.py
index 125fd50f..5afaf7d7 100644
--- a/test/util.py
+++ b/test/util.py
@@ -125,6 +125,51 @@ def umount(mount_process, mnt_dir):
pytest.fail('mount process did not terminate')
+def parse_mountinfo(mnt_dir):
+ '''Return the /proc/self/mountinfo entry for *mnt_dir*, or None.
+
+ Parses the line for the mountpoint and returns a dict with keys:
+ 'mountpoint' - the mount point path (str)
+ 'fstype' - filesystem type as the kernel sees it,
+ e.g. 'fuse' or 'fuse.<subtype>' (str)
+ 'source' - mount source field, e.g. 'hello' or
+ '<subtype>#<fsname>' fallback form (str)
+ 'mount_options' - per-mount options/attrs (set of str), e.g.
+ {'rw','nosuid','nodev','noatime','relatime'}
+ 'super_options' - superblock options from the filesystem (set of
+ str), e.g. {'rw','user_id=1000','group_id=1000',
+ 'default_permissions','allow_other','max_read=...'}
+
+ These fields are exactly what /proc/self/mountinfo exposes (see
+ `man 5 proc.5_mountinfo`); they capture the post-mount state that
+ differs between the legacy mount(2) path and the new fsopen/fsconfig/
+ fsmount path. Asserting on this dict catches bugs where one path
+ drops a parameter the other passes (e.g. `subtype` showing up in
+ `fstype`, `user=` ending up only in /run/mount/utab, ...).
+ '''
+ target = os.path.realpath(mnt_dir)
+ with open('/proc/self/mountinfo') as fh:
+ for line in fh:
+ parts = line.rstrip('\n').split(' ')
+ try:
+ sep = parts.index('-')
+ except ValueError:
+ continue
+ if len(parts) < sep + 4 or sep < 6:
+ continue
+ mountpoint = parts[4].replace('\\040', ' ')
+ if mountpoint != target:
+ continue
+ return {
+ 'mountpoint': mountpoint,
+ 'fstype': parts[sep + 1],
+ 'source': parts[sep + 2].replace('\\040', ' '),
+ 'mount_options': set(parts[5].split(',')),
+ 'super_options': set(parts[sep + 3].split(',')),
+ }
+ return None
+
+
def safe_sleep(secs):
'''Like time.sleep(), but sleep for at least *secs*
--
2.53.0
^ permalink raw reply related [flat|nested] 25+ messages in thread* Re: [PATCH 02/10] Add tests to verify that mountinfo matches requested options
2026-05-08 16:39 ` [PATCH 02/10] Add tests to verify that mountinfo matches requested options Bernd Schubert via B4 Relay
@ 2026-05-08 23:59 ` Darrick J. Wong
0 siblings, 0 replies; 25+ messages in thread
From: Darrick J. Wong @ 2026-05-08 23:59 UTC (permalink / raw)
To: bernd; +Cc: fuse-devel
On Fri, May 08, 2026 at 06:39:05PM +0200, Bernd Schubert via B4 Relay wrote:
> From: Bernd Schubert <bernd@bsbernd.com>
>
> This is especially for the new mount API, but does not hurt
> either for the traditional API.
>
> Assisted by Claude Opus 4.7
>
> Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
> ---
> test/meson.build | 3 +-
> test/test_mount_state.py | 116 +++++++++++++++++++++++++++++++++++++++++++++++
> test/util.py | 45 ++++++++++++++++++
> 3 files changed, 163 insertions(+), 1 deletion(-)
>
> diff --git a/test/meson.build b/test/meson.build
> index 87668d61..55018f92 100644
> --- a/test/meson.build
> +++ b/test/meson.build
> @@ -35,7 +35,8 @@ td += executable('test_loop_config', 'test_loop_config.c',
> install: false)
>
> test_scripts = [ 'conftest.py', 'pytest.ini', 'test_examples.py',
> - 'util.py', 'test_ctests.py', 'test_custom_io.py' ]
> + 'util.py', 'test_ctests.py', 'test_custom_io.py',
> + 'test_mount_state.py' ]
> td += custom_target('test_scripts', input: test_scripts,
> output: test_scripts, build_by_default: true,
> command: ['cp', '-fPp',
> diff --git a/test/test_mount_state.py b/test/test_mount_state.py
> new file mode 100644
> index 00000000..eacb7326
> --- /dev/null
> +++ b/test/test_mount_state.py
> @@ -0,0 +1,116 @@
> +#!/usr/bin/env python3
> +'''
> +Tests that observable mount state (as exposed by /proc/self/mountinfo)
> +matches the options requested at mount time.
> +
> +Existing tests check filesystem behavior (read/write/xattr/...) but
> +never inspect the post-mount metadata recorded by the kernel. That
> +metadata is populated differently by the legacy mount(2) path and the
> +new fsopen/fsconfig/fsmount path, so an option dropped on one path can
> +go undetected — the subtype regression in 5e9e16d6 is one example.
> +These tests assert what /proc/self/mountinfo reports for each mount,
> +so a parity bug between the two paths fails loudly.
That looks pretty useful to me! I wouldn't even have caught that
mount(8) turns "ro" into MOUNT_ATTR_RDONLY *and* fsconfig(..., "ro") if
you hadn't known that!
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
--D
> +'''
> +
> +if __name__ == '__main__':
> + import pytest
> + import sys
> + sys.exit(pytest.main([__file__] + sys.argv[1:]))
> +
> +import os
> +import subprocess
> +import pytest
> +from contextlib import contextmanager
> +from os.path import join as pjoin
> +from util import (wait_for_mount, umount, cleanup, base_cmdline, basename,
> + fuse_test_marker, parse_mountinfo)
> +
> +pytestmark = fuse_test_marker()
> +
> +
> +@contextmanager
> +def hello_mount(tmpdir, output_checker, name, options=()):
> + mnt_dir = str(tmpdir)
> + cmdline = base_cmdline + [pjoin(basename, 'example', name),
> + '-f', mnt_dir]
> + if name == 'hello_ll':
> + cmdline.append('-s')
> + if options:
> + cmdline += ['-o', ','.join(options)]
> + mp = subprocess.Popen(cmdline, stdout=output_checker.fd,
> + stderr=output_checker.fd)
> + try:
> + wait_for_mount(mp, mnt_dir)
> + yield mnt_dir
> + except:
> + cleanup(mp, mnt_dir)
> + raise
> + else:
> + umount(mp, mnt_dir)
> +
> +
> +@pytest.mark.parametrize('name', ('hello', 'hello_ll'))
> +def test_mountinfo_baseline(tmpdir, output_checker, name):
> + # libfuse's add_default_subtype() (lib/helper.c) defaults the
> + # subtype to basename(argv[0]) when the caller didn't pass
> + # -o fsname=/-o subtype=, so the bare-mount fstype is
> + # 'fuse.<example-name>', not 'fuse'. The override case is what
> + # test_mountinfo_subtype below verifies; here we just assert the
> + # fuse-ness and the standard kernel-side identity options.
> + with hello_mount(tmpdir, output_checker, name) as mnt:
> + info = parse_mountinfo(mnt)
> + assert info is not None, 'mountpoint not found in /proc/self/mountinfo'
> + assert info['fstype'] in ('fuse', 'fuse.' + name), \
> + 'unexpected fstype %r (expected fuse or fuse.%s)' % \
> + (info['fstype'], name)
> + assert any(o.startswith('user_id=') for o in info['super_options'])
> + assert any(o.startswith('group_id=') for o in info['super_options'])
> +
> +
> +@pytest.mark.parametrize('name', ('hello', 'hello_ll'))
> +def test_mountinfo_subtype(tmpdir, output_checker, name):
> + # Regression guard for 5e9e16d6: the new mount API needs an
> + # explicit fsconfig(SET_STRING,"subtype",...). Without it the
> + # kernel records fstype=='fuse' (or whatever basename default
> + # leaks through) instead of the user-requested 'fuse.<subtype>'.
> + # An explicit -o subtype= must override the basename default.
> + with hello_mount(tmpdir, output_checker, name,
> + ('subtype=mysub',)) as mnt:
> + info = parse_mountinfo(mnt)
> + assert info is not None
> + assert info['fstype'] == 'fuse.mysub', \
> + 'explicit subtype not propagated: fstype=%r' % info['fstype']
> +
> +
> +@pytest.mark.parametrize('name', ('hello', 'hello_ll'))
> +def test_mountinfo_fsname(tmpdir, output_checker, name):
> + with hello_mount(tmpdir, output_checker, name,
> + ('fsname=myfsname',)) as mnt:
> + info = parse_mountinfo(mnt)
> + assert info is not None
> + assert info['source'] == 'myfsname', \
> + 'fsname not propagated: source=%r' % info['source']
> +
> +
> +@pytest.mark.parametrize('name', ('hello', 'hello_ll'))
> +def test_mountinfo_subtype_fsname(tmpdir, output_checker, name):
> + with hello_mount(tmpdir, output_checker, name,
> + ('subtype=mysub', 'fsname=myfsname')) as mnt:
> + info = parse_mountinfo(mnt)
> + assert info is not None
> + assert info['fstype'] == 'fuse.mysub'
> + # 'mysub#myfsname' is the ENODEV-fallback form when the kernel
> + # rejects fuse.<subtype>; accept either so the test isn't fragile.
> + assert info['source'] in ('myfsname', 'mysub#myfsname'), \
> + 'unexpected source: %r' % info['source']
> +
> +
> +@pytest.mark.parametrize('name', ('hello', 'hello_ll'))
> +def test_mountinfo_unprivileged_attrs(tmpdir, output_checker, name):
> + if os.getuid() == 0:
> + pytest.skip('only meaningful for unprivileged mounts via fusermount3')
> + with hello_mount(tmpdir, output_checker, name) as mnt:
> + info = parse_mountinfo(mnt)
> + assert info is not None
> + assert 'nosuid' in info['mount_options']
> + assert 'nodev' in info['mount_options']
> diff --git a/test/util.py b/test/util.py
> index 125fd50f..5afaf7d7 100644
> --- a/test/util.py
> +++ b/test/util.py
> @@ -125,6 +125,51 @@ def umount(mount_process, mnt_dir):
> pytest.fail('mount process did not terminate')
>
>
> +def parse_mountinfo(mnt_dir):
> + '''Return the /proc/self/mountinfo entry for *mnt_dir*, or None.
> +
> + Parses the line for the mountpoint and returns a dict with keys:
> + 'mountpoint' - the mount point path (str)
> + 'fstype' - filesystem type as the kernel sees it,
> + e.g. 'fuse' or 'fuse.<subtype>' (str)
> + 'source' - mount source field, e.g. 'hello' or
> + '<subtype>#<fsname>' fallback form (str)
> + 'mount_options' - per-mount options/attrs (set of str), e.g.
> + {'rw','nosuid','nodev','noatime','relatime'}
> + 'super_options' - superblock options from the filesystem (set of
> + str), e.g. {'rw','user_id=1000','group_id=1000',
> + 'default_permissions','allow_other','max_read=...'}
> +
> + These fields are exactly what /proc/self/mountinfo exposes (see
> + `man 5 proc.5_mountinfo`); they capture the post-mount state that
> + differs between the legacy mount(2) path and the new fsopen/fsconfig/
> + fsmount path. Asserting on this dict catches bugs where one path
> + drops a parameter the other passes (e.g. `subtype` showing up in
> + `fstype`, `user=` ending up only in /run/mount/utab, ...).
> + '''
> + target = os.path.realpath(mnt_dir)
> + with open('/proc/self/mountinfo') as fh:
> + for line in fh:
> + parts = line.rstrip('\n').split(' ')
> + try:
> + sep = parts.index('-')
> + except ValueError:
> + continue
> + if len(parts) < sep + 4 or sep < 6:
> + continue
> + mountpoint = parts[4].replace('\\040', ' ')
> + if mountpoint != target:
> + continue
> + return {
> + 'mountpoint': mountpoint,
> + 'fstype': parts[sep + 1],
> + 'source': parts[sep + 2].replace('\\040', ' '),
> + 'mount_options': set(parts[5].split(',')),
> + 'super_options': set(parts[sep + 3].split(',')),
> + }
> + return None
> +
> +
> def safe_sleep(secs):
> '''Like time.sleep(), but sleep for at least *secs*
>
>
> --
> 2.53.0
>
>
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH 03/10] test: assert ro/rw, nosuid/suid, nodev/dev round-trip via fsmount
2026-05-08 16:39 [PATCH 00/10] libfuse: new mount API and SYNC_INIT fixes and tests Bernd Schubert via B4 Relay
2026-05-08 16:39 ` [PATCH 01/10] test: register pytest run as a meson test Bernd Schubert via B4 Relay
2026-05-08 16:39 ` [PATCH 02/10] Add tests to verify that mountinfo matches requested options Bernd Schubert via B4 Relay
@ 2026-05-08 16:39 ` Bernd Schubert via B4 Relay
2026-05-09 0:04 ` Darrick J. Wong
2026-05-08 16:39 ` [PATCH 04/10] New mount API: read-only option is for fsmount() and fsconfig() Bernd Schubert via B4 Relay
` (6 subsequent siblings)
9 siblings, 1 reply; 25+ messages in thread
From: Bernd Schubert via B4 Relay @ 2026-05-08 16:39 UTC (permalink / raw)
To: fuse-devel; +Cc: Darrick J. Wong, Bernd Schubert
From: Bernd Schubert <bernd@bsbernd.com>
Assisted by ClaudeOpus 4.7
Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
---
test/test_mount_state.py | 70 ++++++++++++++++++++++++++++++++++++++++++------
1 file changed, 62 insertions(+), 8 deletions(-)
diff --git a/test/test_mount_state.py b/test/test_mount_state.py
index eacb7326..a7949ec3 100644
--- a/test/test_mount_state.py
+++ b/test/test_mount_state.py
@@ -105,12 +105,66 @@ def test_mountinfo_subtype_fsname(tmpdir, output_checker, name):
'unexpected source: %r' % info['source']
-@pytest.mark.parametrize('name', ('hello', 'hello_ll'))
-def test_mountinfo_unprivileged_attrs(tmpdir, output_checker, name):
- if os.getuid() == 0:
- pytest.skip('only meaningful for unprivileged mounts via fusermount3')
- with hello_mount(tmpdir, output_checker, name) as mnt:
- info = parse_mountinfo(mnt)
+# (label, options, must-be-in mount_options, must-NOT-be-in mount_options)
+#
+# Library defaults are MS_NOSUID|MS_NODEV (lib/mount.c:771,
+# util/fusermount.c:988), so a no-options mount is expected to land
+# with both attrs set. The negation forms (suid/dev) clear the default
+# flags via lib/mount.c:set_mount_flag(), which on the new mount API
+# path means MOUNT_ATTR_NOSUID/MOUNT_ATTR_NODEV are not set in the
+# fsmount() call. Asserting their absence catches a routing bug where
+# the negation wasn't honored.
+ATTR_CASES = [
+ ('default', (), ('rw', 'nosuid', 'nodev'), ('ro',)),
+ ('ro', ('ro',), ('ro',), ('rw',)),
+ ('nosuid', ('nosuid',), ('nosuid',), ()),
+ ('nodev', ('nodev',), ('nodev',), ()),
+]
+
+# suid/dev are root-only: the kernel rejects MS_NOSUID/MS_NODEV being
+# cleared for unprivileged mounts, and fusermount3 hard-codes them on
+# anyway (util/fusermount.c:988).
+ATTR_CASES_ROOT = [
+ ('suid', ('suid',), (), ('nosuid',)),
+ ('dev', ('dev',), (), ('nodev',)),
+]
+
+
+def _check_attrs(info, must_have, must_not_have):
assert info is not None
- assert 'nosuid' in info['mount_options']
- assert 'nodev' in info['mount_options']
+ for opt in must_have:
+ assert opt in info['mount_options'], \
+ '%r missing from mount_options=%r' % (opt, info['mount_options'])
+ for opt in must_not_have:
+ assert opt not in info['mount_options'], \
+ 'unexpected %r in mount_options=%r' % (opt, info['mount_options'])
+
+
+@pytest.mark.parametrize('name', ('hello', 'hello_ll'))
+@pytest.mark.parametrize('label,opts,must_have,must_not_have', ATTR_CASES,
+ ids=[c[0] for c in ATTR_CASES])
+def test_mountinfo_attrs(tmpdir, output_checker, name,
+ label, opts, must_have, must_not_have):
+ with hello_mount(tmpdir, output_checker, name, opts) as mnt:
+ info = parse_mountinfo(mnt)
+ _check_attrs(info, must_have, must_not_have)
+ # ro/rw also surface in super_options; if we asked for ro the
+ # superblock must agree. Catches a path that sets MOUNT_ATTR_RDONLY
+ # but forgets the FSCONFIG-side MS_RDONLY (or vice versa).
+ if 'ro' in must_have:
+ assert 'ro' in info['super_options'], \
+ 'ro on mount but rw on superblock: super_options=%r' % \
+ info['super_options']
+
+
+@pytest.mark.parametrize('name', ('hello', 'hello_ll'))
+@pytest.mark.parametrize('label,opts,must_have,must_not_have',
+ ATTR_CASES_ROOT,
+ ids=[c[0] for c in ATTR_CASES_ROOT])
+def test_mountinfo_attrs_root(tmpdir, output_checker, name,
+ label, opts, must_have, must_not_have):
+ if os.getuid() != 0:
+ pytest.skip('clearing nosuid/nodev requires root')
+ with hello_mount(tmpdir, output_checker, name, opts) as mnt:
+ info = parse_mountinfo(mnt)
+ _check_attrs(info, must_have, must_not_have)
--
2.53.0
^ permalink raw reply related [flat|nested] 25+ messages in thread* Re: [PATCH 03/10] test: assert ro/rw, nosuid/suid, nodev/dev round-trip via fsmount
2026-05-08 16:39 ` [PATCH 03/10] test: assert ro/rw, nosuid/suid, nodev/dev round-trip via fsmount Bernd Schubert via B4 Relay
@ 2026-05-09 0:04 ` Darrick J. Wong
0 siblings, 0 replies; 25+ messages in thread
From: Darrick J. Wong @ 2026-05-09 0:04 UTC (permalink / raw)
To: bernd; +Cc: fuse-devel
On Fri, May 08, 2026 at 06:39:06PM +0200, Bernd Schubert via B4 Relay wrote:
> From: Bernd Schubert <bernd@bsbernd.com>
>
> Assisted by ClaudeOpus 4.7
> Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
> ---
> test/test_mount_state.py | 70 ++++++++++++++++++++++++++++++++++++++++++------
> 1 file changed, 62 insertions(+), 8 deletions(-)
>
> diff --git a/test/test_mount_state.py b/test/test_mount_state.py
> index eacb7326..a7949ec3 100644
> --- a/test/test_mount_state.py
> +++ b/test/test_mount_state.py
> @@ -105,12 +105,66 @@ def test_mountinfo_subtype_fsname(tmpdir, output_checker, name):
> 'unexpected source: %r' % info['source']
>
>
> -@pytest.mark.parametrize('name', ('hello', 'hello_ll'))
> -def test_mountinfo_unprivileged_attrs(tmpdir, output_checker, name):
> - if os.getuid() == 0:
> - pytest.skip('only meaningful for unprivileged mounts via fusermount3')
> - with hello_mount(tmpdir, output_checker, name) as mnt:
> - info = parse_mountinfo(mnt)
> +# (label, options, must-be-in mount_options, must-NOT-be-in mount_options)
> +#
> +# Library defaults are MS_NOSUID|MS_NODEV (lib/mount.c:771,
> +# util/fusermount.c:988), so a no-options mount is expected to land
> +# with both attrs set. The negation forms (suid/dev) clear the default
> +# flags via lib/mount.c:set_mount_flag(), which on the new mount API
> +# path means MOUNT_ATTR_NOSUID/MOUNT_ATTR_NODEV are not set in the
> +# fsmount() call. Asserting their absence catches a routing bug where
> +# the negation wasn't honored.
> +ATTR_CASES = [
> + ('default', (), ('rw', 'nosuid', 'nodev'), ('ro',)),
> + ('ro', ('ro',), ('ro',), ('rw',)),
> + ('nosuid', ('nosuid',), ('nosuid',), ()),
> + ('nodev', ('nodev',), ('nodev',), ()),
> +]
> +
> +# suid/dev are root-only: the kernel rejects MS_NOSUID/MS_NODEV being
> +# cleared for unprivileged mounts, and fusermount3 hard-codes them on
> +# anyway (util/fusermount.c:988).
> +ATTR_CASES_ROOT = [
> + ('suid', ('suid',), (), ('nosuid',)),
> + ('dev', ('dev',), (), ('nodev',)),
> +]
> +
> +
> +def _check_attrs(info, must_have, must_not_have):
> assert info is not None
> - assert 'nosuid' in info['mount_options']
> - assert 'nodev' in info['mount_options']
> + for opt in must_have:
> + assert opt in info['mount_options'], \
> + '%r missing from mount_options=%r' % (opt, info['mount_options'])
> + for opt in must_not_have:
> + assert opt not in info['mount_options'], \
> + 'unexpected %r in mount_options=%r' % (opt, info['mount_options'])
> +
> +
> +@pytest.mark.parametrize('name', ('hello', 'hello_ll'))
> +@pytest.mark.parametrize('label,opts,must_have,must_not_have', ATTR_CASES,
> + ids=[c[0] for c in ATTR_CASES])
> +def test_mountinfo_attrs(tmpdir, output_checker, name,
> + label, opts, must_have, must_not_have):
> + with hello_mount(tmpdir, output_checker, name, opts) as mnt:
> + info = parse_mountinfo(mnt)
> + _check_attrs(info, must_have, must_not_have)
> + # ro/rw also surface in super_options; if we asked for ro the
> + # superblock must agree. Catches a path that sets MOUNT_ATTR_RDONLY
> + # but forgets the FSCONFIG-side MS_RDONLY (or vice versa).
Ehhh we're probably going to have to fix this again to handle the new
"ro=vfs"/"ro=fs" behaviors that util-linux 2.41 added.
/me is sad that this wasn't all that well publicized on fsdevel:
https://lore.kernel.org/linux-fsdevel/?q=%22ro%3Dvfs%22
The rest of the new tests look ok to me.
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
--D
> + if 'ro' in must_have:
> + assert 'ro' in info['super_options'], \
> + 'ro on mount but rw on superblock: super_options=%r' % \
> + info['super_options']
> +
> +
> +@pytest.mark.parametrize('name', ('hello', 'hello_ll'))
> +@pytest.mark.parametrize('label,opts,must_have,must_not_have',
> + ATTR_CASES_ROOT,
> + ids=[c[0] for c in ATTR_CASES_ROOT])
> +def test_mountinfo_attrs_root(tmpdir, output_checker, name,
> + label, opts, must_have, must_not_have):
> + if os.getuid() != 0:
> + pytest.skip('clearing nosuid/nodev requires root')
> + with hello_mount(tmpdir, output_checker, name, opts) as mnt:
> + info = parse_mountinfo(mnt)
> + _check_attrs(info, must_have, must_not_have)
>
> --
> 2.53.0
>
>
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH 04/10] New mount API: read-only option is for fsmount() and fsconfig()
2026-05-08 16:39 [PATCH 00/10] libfuse: new mount API and SYNC_INIT fixes and tests Bernd Schubert via B4 Relay
` (2 preceding siblings ...)
2026-05-08 16:39 ` [PATCH 03/10] test: assert ro/rw, nosuid/suid, nodev/dev round-trip via fsmount Bernd Schubert via B4 Relay
@ 2026-05-08 16:39 ` Bernd Schubert via B4 Relay
2026-05-09 0:17 ` Darrick J. Wong
2026-05-08 16:39 ` [PATCH 05/10] libfuse: don't use SYNC_INIT unless asked for Bernd Schubert via B4 Relay
` (5 subsequent siblings)
9 siblings, 1 reply; 25+ messages in thread
From: Bernd Schubert via B4 Relay @ 2026-05-08 16:39 UTC (permalink / raw)
To: fuse-devel; +Cc: Darrick J. Wong, Bernd Schubert
From: "Darrick J. Wong" <djwong@kernel.org>
The "ro" mount option needs to be handled by
fsmount(..., MOUNT_ATTR_RDONLY), for the superblock
and fsconfig(SET_FLAG, "ro") for the vfsmount.
Update by Bernd: Instead of moving it to MOUNT_ATTR_RDONLY,
handle it by both.
Fixes: 0d7e72541564 ("Unify mount flag structures and remove redundant is_mount_attr field")
Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
---
lib/mount_fsmount.c | 21 ++++++++++++++++-----
lib/mount_util.c | 44 +++++++++++++++++++++++++-------------------
lib/mount_util.h | 3 ++-
3 files changed, 43 insertions(+), 25 deletions(-)
diff --git a/lib/mount_fsmount.c b/lib/mount_fsmount.c
index 5e13b34c..edb9e043 100644
--- a/lib/mount_fsmount.c
+++ b/lib/mount_fsmount.c
@@ -39,6 +39,7 @@
* Convert MS_* mount flags to MOUNT_ATTR_* mount attributes.
* These flags are passed to fsmount(), not fsconfig().
* Mount attributes control mount-point level behavior.
+ * To called after set_ms_flags() which consumes the fsconfig flags.
*
* @attrs MOUNT_ATTR flags, built from MS_ flags
* @return remaining flags
@@ -108,8 +109,12 @@ static void log_fsconfig_kmsg(int fd)
/*
* Apply VFS superblock (fsconfig) flags to the filesystem context.
- * Only handles flags that are filesystem parameters (ro, sync, dirsync).
- * Mount attributes (nosuid, nodev, etc.) are handled separately via fsmount().
+ * Handles the fsconfig leg of every entry whose is_fsconfig is set
+ * (ro, rw, sync, async, dirsync). Mount attributes (nosuid, nodev, etc.)
+ * are handled separately via fsmount().
+ *
+ * Entries that have *both* legs (ro/rw) leave the MS_ bit in *ms_flags
+ * so that ms_flags_to_mount_attrs() can also pick them up.
*
* @ms_flags flags to set, outvalue are the remaining flags
* @return 0 on success, negative error code on failure
@@ -120,8 +125,7 @@ static int set_ms_flags(int fsfd, unsigned long *ms_flags)
int i;
for (i = 0; mount_flags[i].opt != NULL && flags != 0; i++) {
- /* Only process fsconfig flags (mount_attr == 0) with on==1 */
- if (mount_flags[i].mount_attr || !mount_flags[i].on)
+ if (!mount_flags[i].is_fsconfig || !mount_flags[i].on)
continue;
if (!(flags & mount_flags[i].flag))
@@ -137,7 +141,14 @@ static int set_ms_flags(int fsfd, unsigned long *ms_flags)
return -save_errno;
}
- flags &= ~mount_flags[i].flag;
+
+ /*
+ * Only consume the bit if no fsmount mount-attr leg is
+ * also pending for this option. Otherwise leave it for
+ * ms_flags_to_mount_attrs() to apply via fsmount().
+ */
+ if (!mount_flags[i].mount_attr)
+ flags &= ~mount_flags[i].flag;
}
*ms_flags = flags;
diff --git a/lib/mount_util.c b/lib/mount_util.c
index 1a0aec9b..f1e58d98 100644
--- a/lib/mount_util.c
+++ b/lib/mount_util.c
@@ -108,26 +108,32 @@
#define MOUNT_ATTR_NOSYMFOLLOW 0
#endif
+/*
+ * is_fsconfig and mount_attr are independent: ro/rw need both legs
+ * (SB_RDONLY on the superblock via fsconfig SET_FLAG, plus
+ * MOUNT_ATTR_RDONLY on the vfsmount via fsmount). Everything else is
+ * exclusively one or the other.
+ */
const struct mount_flags mount_flags[] = {
-/* opt flag on safe mount_attr */
-{"rw", MS_RDONLY, 0, 1, 0}, /* fsconfig */
-{"ro", MS_RDONLY, 1, 1, 0}, /* fsconfig */
-{"suid", MS_NOSUID, 0, 0, MOUNT_ATTR_NOSUID}, /* fsmount */
-{"nosuid", MS_NOSUID, 1, 1, MOUNT_ATTR_NOSUID}, /* fsmount */
-{"dev", MS_NODEV, 0, 1, MOUNT_ATTR_NODEV}, /* fsmount */
-{"nodev", MS_NODEV, 1, 1, MOUNT_ATTR_NODEV}, /* fsmount */
-{"exec", MS_NOEXEC, 0, 1, MOUNT_ATTR_NOEXEC}, /* fsmount */
-{"noexec", MS_NOEXEC, 1, 1, MOUNT_ATTR_NOEXEC}, /* fsmount */
-{"async", MS_SYNCHRONOUS, 0, 1, 0}, /* fsconfig */
-{"sync", MS_SYNCHRONOUS, 1, 1, 0}, /* fsconfig */
-{"noatime", MS_NOATIME, 1, 1, MOUNT_ATTR_NOATIME}, /* fsmount */
-{"nodiratime", MS_NODIRATIME, 1, 1, MOUNT_ATTR_NODIRATIME}, /* fsmount */
-{"norelatime", MS_RELATIME, 0, 1, MOUNT_ATTR_RELATIME}, /* fsmount */
-{"nostrictatime", MS_STRICTATIME, 0, 1, MOUNT_ATTR_STRICTATIME},/* fsmount */
-{"symfollow", MS_NOSYMFOLLOW, 0, 1, MOUNT_ATTR_NOSYMFOLLOW},/* fsmount */
-{"nosymfollow", MS_NOSYMFOLLOW, 1, 1, MOUNT_ATTR_NOSYMFOLLOW},/* fsmount */
-{"dirsync", MS_DIRSYNC, 1, 1, 0}, /* fsconfig */
-{NULL, 0, 0, 0, 0}
+/* opt flag on safe fsconfig mount_attr */
+{"rw", MS_RDONLY, 0, 1, 1, MOUNT_ATTR_RDONLY},
+{"ro", MS_RDONLY, 1, 1, 1, MOUNT_ATTR_RDONLY},
+{"suid", MS_NOSUID, 0, 0, 0, MOUNT_ATTR_NOSUID},
+{"nosuid", MS_NOSUID, 1, 1, 0, MOUNT_ATTR_NOSUID},
+{"dev", MS_NODEV, 0, 1, 0, MOUNT_ATTR_NODEV},
+{"nodev", MS_NODEV, 1, 1, 0, MOUNT_ATTR_NODEV},
+{"exec", MS_NOEXEC, 0, 1, 0, MOUNT_ATTR_NOEXEC},
+{"noexec", MS_NOEXEC, 1, 1, 0, MOUNT_ATTR_NOEXEC},
+{"async", MS_SYNCHRONOUS, 0, 1, 1, 0},
+{"sync", MS_SYNCHRONOUS, 1, 1, 1, 0},
+{"noatime", MS_NOATIME, 1, 1, 0, MOUNT_ATTR_NOATIME},
+{"nodiratime", MS_NODIRATIME, 1, 1, 0, MOUNT_ATTR_NODIRATIME},
+{"norelatime", MS_RELATIME, 0, 1, 0, MOUNT_ATTR_RELATIME},
+{"nostrictatime", MS_STRICTATIME, 0, 1, 0, MOUNT_ATTR_STRICTATIME},
+{"symfollow", MS_NOSYMFOLLOW, 0, 1, 0, MOUNT_ATTR_NOSYMFOLLOW},
+{"nosymfollow", MS_NOSYMFOLLOW, 1, 1, 0, MOUNT_ATTR_NOSYMFOLLOW},
+{"dirsync", MS_DIRSYNC, 1, 1, 1, 0},
+{NULL, 0, 0, 0, 0, 0}
};
#ifdef IGNORE_MTAB
diff --git a/lib/mount_util.h b/lib/mount_util.h
index 7ddd6b01..bf088996 100644
--- a/lib/mount_util.h
+++ b/lib/mount_util.h
@@ -18,7 +18,8 @@ struct mount_flags {
unsigned long flag;
int on;
int safe; /* used by fusermount */
- unsigned long mount_attr; /* MOUNT_ATTR_* value for fsmount (0 = fsconfig flag) */
+ int is_fsconfig; /* apply via fsconfig SET_FLAG (superblock-level) */
+ unsigned long mount_attr; /* MOUNT_ATTR_* for fsmount (0 = none) */
};
extern const struct mount_flags mount_flags[];
--
2.53.0
^ permalink raw reply related [flat|nested] 25+ messages in thread* Re: [PATCH 04/10] New mount API: read-only option is for fsmount() and fsconfig()
2026-05-08 16:39 ` [PATCH 04/10] New mount API: read-only option is for fsmount() and fsconfig() Bernd Schubert via B4 Relay
@ 2026-05-09 0:17 ` Darrick J. Wong
0 siblings, 0 replies; 25+ messages in thread
From: Darrick J. Wong @ 2026-05-09 0:17 UTC (permalink / raw)
To: bernd; +Cc: fuse-devel
On Fri, May 08, 2026 at 06:39:07PM +0200, Bernd Schubert via B4 Relay wrote:
> From: "Darrick J. Wong" <djwong@kernel.org>
>
> The "ro" mount option needs to be handled by
>
> fsmount(..., MOUNT_ATTR_RDONLY), for the superblock
> and fsconfig(SET_FLAG, "ro") for the vfsmount.
Yikes. Now there's a subtlety!
> Update by Bernd: Instead of moving it to MOUNT_ATTR_RDONLY,
> handle it by both.
>
> Fixes: 0d7e72541564 ("Unify mount flag structures and remove redundant is_mount_attr field")
> Signed-off-by: Darrick J. Wong <djwong@kernel.org>
> Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
> ---
> lib/mount_fsmount.c | 21 ++++++++++++++++-----
> lib/mount_util.c | 44 +++++++++++++++++++++++++-------------------
> lib/mount_util.h | 3 ++-
> 3 files changed, 43 insertions(+), 25 deletions(-)
>
> diff --git a/lib/mount_fsmount.c b/lib/mount_fsmount.c
> index 5e13b34c..edb9e043 100644
> --- a/lib/mount_fsmount.c
> +++ b/lib/mount_fsmount.c
> @@ -39,6 +39,7 @@
> * Convert MS_* mount flags to MOUNT_ATTR_* mount attributes.
> * These flags are passed to fsmount(), not fsconfig().
> * Mount attributes control mount-point level behavior.
> + * To called after set_ms_flags() which consumes the fsconfig flags.
> *
> * @attrs MOUNT_ATTR flags, built from MS_ flags
> * @return remaining flags
> @@ -108,8 +109,12 @@ static void log_fsconfig_kmsg(int fd)
>
> /*
> * Apply VFS superblock (fsconfig) flags to the filesystem context.
> - * Only handles flags that are filesystem parameters (ro, sync, dirsync).
> - * Mount attributes (nosuid, nodev, etc.) are handled separately via fsmount().
> + * Handles the fsconfig leg of every entry whose is_fsconfig is set
> + * (ro, rw, sync, async, dirsync). Mount attributes (nosuid, nodev, etc.)
> + * are handled separately via fsmount().
> + *
> + * Entries that have *both* legs (ro/rw) leave the MS_ bit in *ms_flags
> + * so that ms_flags_to_mount_attrs() can also pick them up.
> *
> * @ms_flags flags to set, outvalue are the remaining flags
> * @return 0 on success, negative error code on failure
> @@ -120,8 +125,7 @@ static int set_ms_flags(int fsfd, unsigned long *ms_flags)
> int i;
>
> for (i = 0; mount_flags[i].opt != NULL && flags != 0; i++) {
> - /* Only process fsconfig flags (mount_attr == 0) with on==1 */
> - if (mount_flags[i].mount_attr || !mount_flags[i].on)
> + if (!mount_flags[i].is_fsconfig || !mount_flags[i].on)
> continue;
>
> if (!(flags & mount_flags[i].flag))
> @@ -137,7 +141,14 @@ static int set_ms_flags(int fsfd, unsigned long *ms_flags)
>
> return -save_errno;
> }
> - flags &= ~mount_flags[i].flag;
> +
> + /*
> + * Only consume the bit if no fsmount mount-attr leg is
> + * also pending for this option. Otherwise leave it for
> + * ms_flags_to_mount_attrs() to apply via fsmount().
> + */
> + if (!mount_flags[i].mount_attr)
> + flags &= ~mount_flags[i].flag;
> }
>
> *ms_flags = flags;
> diff --git a/lib/mount_util.c b/lib/mount_util.c
> index 1a0aec9b..f1e58d98 100644
> --- a/lib/mount_util.c
> +++ b/lib/mount_util.c
> @@ -108,26 +108,32 @@
> #define MOUNT_ATTR_NOSYMFOLLOW 0
> #endif
>
> +/*
> + * is_fsconfig and mount_attr are independent: ro/rw need both legs
> + * (SB_RDONLY on the superblock via fsconfig SET_FLAG, plus
> + * MOUNT_ATTR_RDONLY on the vfsmount via fsmount). Everything else is
> + * exclusively one or the other.
> + */
> const struct mount_flags mount_flags[] = {
> -/* opt flag on safe mount_attr */
> -{"rw", MS_RDONLY, 0, 1, 0}, /* fsconfig */
> -{"ro", MS_RDONLY, 1, 1, 0}, /* fsconfig */
> -{"suid", MS_NOSUID, 0, 0, MOUNT_ATTR_NOSUID}, /* fsmount */
> -{"nosuid", MS_NOSUID, 1, 1, MOUNT_ATTR_NOSUID}, /* fsmount */
> -{"dev", MS_NODEV, 0, 1, MOUNT_ATTR_NODEV}, /* fsmount */
> -{"nodev", MS_NODEV, 1, 1, MOUNT_ATTR_NODEV}, /* fsmount */
> -{"exec", MS_NOEXEC, 0, 1, MOUNT_ATTR_NOEXEC}, /* fsmount */
> -{"noexec", MS_NOEXEC, 1, 1, MOUNT_ATTR_NOEXEC}, /* fsmount */
> -{"async", MS_SYNCHRONOUS, 0, 1, 0}, /* fsconfig */
> -{"sync", MS_SYNCHRONOUS, 1, 1, 0}, /* fsconfig */
> -{"noatime", MS_NOATIME, 1, 1, MOUNT_ATTR_NOATIME}, /* fsmount */
> -{"nodiratime", MS_NODIRATIME, 1, 1, MOUNT_ATTR_NODIRATIME}, /* fsmount */
> -{"norelatime", MS_RELATIME, 0, 1, MOUNT_ATTR_RELATIME}, /* fsmount */
> -{"nostrictatime", MS_STRICTATIME, 0, 1, MOUNT_ATTR_STRICTATIME},/* fsmount */
> -{"symfollow", MS_NOSYMFOLLOW, 0, 1, MOUNT_ATTR_NOSYMFOLLOW},/* fsmount */
> -{"nosymfollow", MS_NOSYMFOLLOW, 1, 1, MOUNT_ATTR_NOSYMFOLLOW},/* fsmount */
> -{"dirsync", MS_DIRSYNC, 1, 1, 0}, /* fsconfig */
> -{NULL, 0, 0, 0, 0}
> +/* opt flag on safe fsconfig mount_attr */
> +{"rw", MS_RDONLY, 0, 1, 1, MOUNT_ATTR_RDONLY},
I almost wonder if this should get reflowed as:
const struct mount_flags mount_flags[] = {
{
.opt = "rw",
.flag = MS_RDONLY,
.on = 0,
.safe = 1,
.fsconfig = 1,
.mount_attr = MOUNT_ATTR_RDONLY,
},
...
};
But that can be left for a future patch. This patch, as presented,
looks correct to me.
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
Also: Apparently util-linux trims "ro=vfs" and "ro=fs" down to just "ro"
when it invokes a mount helper:
# strace -s99 -f -e execve mount -o ro=vfs -t fuse.doesnotexist whatever /mnt
execve("/usr/bin/mount", ["mount", "-o", "ro=vfs", "-t", "fuse.doesnotexist", "whatever", "/mnt"], 0x7ffdfd517200 /* 28 vars */) = 0
strace: Process 20601 attached
[pid 20601] execve("/sbin/mount.fuse", ["/sbin/mount.fuse", "whatever", "/mnt", "-o", "ro", "-t", "fuse.doesnotexist"], 0x7ffdb1764278 /* 24 vars */) = 0
[pid 20601] execve("/bin/sh", ["/bin/sh", "-c", "'doesnotexist' 'whatever' '/mnt' '-o' 'ro,dev,suid'"], 0x55becc2a22e0 /* 25 vars */) = 0
So I guess we're off the hook for handling that weirdness.
--D
> +{"ro", MS_RDONLY, 1, 1, 1, MOUNT_ATTR_RDONLY},
> +{"suid", MS_NOSUID, 0, 0, 0, MOUNT_ATTR_NOSUID},
> +{"nosuid", MS_NOSUID, 1, 1, 0, MOUNT_ATTR_NOSUID},
> +{"dev", MS_NODEV, 0, 1, 0, MOUNT_ATTR_NODEV},
> +{"nodev", MS_NODEV, 1, 1, 0, MOUNT_ATTR_NODEV},
> +{"exec", MS_NOEXEC, 0, 1, 0, MOUNT_ATTR_NOEXEC},
> +{"noexec", MS_NOEXEC, 1, 1, 0, MOUNT_ATTR_NOEXEC},
> +{"async", MS_SYNCHRONOUS, 0, 1, 1, 0},
> +{"sync", MS_SYNCHRONOUS, 1, 1, 1, 0},
> +{"noatime", MS_NOATIME, 1, 1, 0, MOUNT_ATTR_NOATIME},
> +{"nodiratime", MS_NODIRATIME, 1, 1, 0, MOUNT_ATTR_NODIRATIME},
> +{"norelatime", MS_RELATIME, 0, 1, 0, MOUNT_ATTR_RELATIME},
> +{"nostrictatime", MS_STRICTATIME, 0, 1, 0, MOUNT_ATTR_STRICTATIME},
> +{"symfollow", MS_NOSYMFOLLOW, 0, 1, 0, MOUNT_ATTR_NOSYMFOLLOW},
> +{"nosymfollow", MS_NOSYMFOLLOW, 1, 1, 0, MOUNT_ATTR_NOSYMFOLLOW},
> +{"dirsync", MS_DIRSYNC, 1, 1, 1, 0},
> +{NULL, 0, 0, 0, 0, 0}
> };
>
> #ifdef IGNORE_MTAB
> diff --git a/lib/mount_util.h b/lib/mount_util.h
> index 7ddd6b01..bf088996 100644
> --- a/lib/mount_util.h
> +++ b/lib/mount_util.h
> @@ -18,7 +18,8 @@ struct mount_flags {
> unsigned long flag;
> int on;
> int safe; /* used by fusermount */
> - unsigned long mount_attr; /* MOUNT_ATTR_* value for fsmount (0 = fsconfig flag) */
> + int is_fsconfig; /* apply via fsconfig SET_FLAG (superblock-level) */
> + unsigned long mount_attr; /* MOUNT_ATTR_* for fsmount (0 = none) */
> };
> extern const struct mount_flags mount_flags[];
>
>
> --
> 2.53.0
>
>
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH 05/10] libfuse: don't use SYNC_INIT unless asked for
2026-05-08 16:39 [PATCH 00/10] libfuse: new mount API and SYNC_INIT fixes and tests Bernd Schubert via B4 Relay
` (3 preceding siblings ...)
2026-05-08 16:39 ` [PATCH 04/10] New mount API: read-only option is for fsmount() and fsconfig() Bernd Schubert via B4 Relay
@ 2026-05-08 16:39 ` Bernd Schubert via B4 Relay
2026-05-08 16:39 ` [PATCH 06/10] example: silence add_languages warning by setting 'native: false' Bernd Schubert via B4 Relay
` (4 subsequent siblings)
9 siblings, 0 replies; 25+ messages in thread
From: Bernd Schubert via B4 Relay @ 2026-05-08 16:39 UTC (permalink / raw)
To: fuse-devel; +Cc: Darrick J. Wong, Bernd Schubert
From: "Darrick J. Wong" <djwong@kernel.org>
fuse2fs calls fuse_main, then starts threads from ->init. It doesn't
call fuse_daemonize_early_start because I haven't ported it to use any
of the new APIs. I don't have io_uring enabled for fuse on my dev box.
fuse_main calls fuse_session_mount calls fuse_session_mount_new_api
calls session_start_sync_init. At the start of the function,
se->want_sync_init, se->uring.enable, and daemonize.active are all
false. The first branch is not taken, so we call FUSE_DEV_IOC_SYNC_INIT
and enable sync_init even though the user didn't ask for that and didn't
prepare for it either.
FUSE_DEV_IOC_SYNC_INIT succeeds, so we send the synchronous FUSE_INIT
from mount, which calls fuse2fs' init() method. That starts the
background threads and returns. Upon return to the kernel, the mount()
now succeeds, and the next thing that fuse_main does is call
fuse_daemonize(). Since we didn't call fuse_daemonize_early_start, the
daemonize forks the process and the threads die with the parent.
If we didn't ask for SYNC_INIT, don't enable it. This is needed to
maintain compatibility with older fuse servers that only support
asynchronous FUSE_INIT.
--
Fixes: 3e1101057aea ("fuse mount: Support synchronous FUSE_INIT (privileged daemon)")
Signed-off-by: Darrick J. Wong <djwong@kernel.org>
v1.2: simplify the logic even more
v1.1: improve commit message, refine logging logic
---
lib/fuse_lowlevel.c | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c
index 0e16845d..6e078e82 100644
--- a/lib/fuse_lowlevel.c
+++ b/lib/fuse_lowlevel.c
@@ -4475,8 +4475,11 @@ static int session_start_sync_init(struct fuse_session *se, int fd)
{
int err, res;
- if (!se->want_sync_init &&
- (se->uring.enable && !fuse_daemonize_is_used())) {
+ /*
+ * Older fuse servers do not set want_sync_init or start the new
+ * daemonize code, so they get async init.
+ */
+ if (!fuse_daemonize_is_used() || !se->want_sync_init) {
if (se->debug)
fuse_log(FUSE_LOG_DEBUG,
"fuse: sync init not enabled\n");
--
2.53.0
^ permalink raw reply related [flat|nested] 25+ messages in thread* [PATCH 06/10] example: silence add_languages warning by setting 'native: false'
2026-05-08 16:39 [PATCH 00/10] libfuse: new mount API and SYNC_INIT fixes and tests Bernd Schubert via B4 Relay
` (4 preceding siblings ...)
2026-05-08 16:39 ` [PATCH 05/10] libfuse: don't use SYNC_INIT unless asked for Bernd Schubert via B4 Relay
@ 2026-05-08 16:39 ` Bernd Schubert via B4 Relay
2026-05-09 0:23 ` Darrick J. Wong
2026-05-08 16:39 ` [PATCH 07/10] mount: clarify kernel_opts vs mnt_opts vs flags in fuse_kern_fsmount Bernd Schubert via B4 Relay
` (3 subsequent siblings)
9 siblings, 1 reply; 25+ messages in thread
From: Bernd Schubert via B4 Relay @ 2026-05-08 16:39 UTC (permalink / raw)
To: fuse-devel; +Cc: Darrick J. Wong, Bernd Schubert
From: Bernd Schubert <bernd@bsbernd.com>
The C++ examples (passthrough_hp, memfs_ll) link against libfuse,
i.e. there is no cross compilation from example/ point of view.
Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
---
example/meson.build | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/example/meson.build b/example/meson.build
index 76cf2d96..31e69187 100644
--- a/example/meson.build
+++ b/example/meson.build
@@ -32,7 +32,7 @@ foreach ex : threaded_examples
install: false)
endforeach
-if platform != 'dragonfly' and add_languages('cpp', required : false)
+if platform != 'dragonfly' and add_languages('cpp', native : false, required : false)
executable('passthrough_hp', 'passthrough_hp.cc',
dependencies: [ thread_dep, libfuse_dep ],
install: false)
--
2.53.0
^ permalink raw reply related [flat|nested] 25+ messages in thread* Re: [PATCH 06/10] example: silence add_languages warning by setting 'native: false'
2026-05-08 16:39 ` [PATCH 06/10] example: silence add_languages warning by setting 'native: false' Bernd Schubert via B4 Relay
@ 2026-05-09 0:23 ` Darrick J. Wong
0 siblings, 0 replies; 25+ messages in thread
From: Darrick J. Wong @ 2026-05-09 0:23 UTC (permalink / raw)
To: bernd; +Cc: fuse-devel
On Fri, May 08, 2026 at 06:39:09PM +0200, Bernd Schubert via B4 Relay wrote:
> From: Bernd Schubert <bernd@bsbernd.com>
>
> The C++ examples (passthrough_hp, memfs_ll) link against libfuse,
> i.e. there is no cross compilation from example/ point of view.
But doesn't that mean that if libfuse is cross-compiled, then the
examples need to be as well so that they can link against the built
libfuse?
Weirdly it seems ok even with this applied so <shrug> I'll trust you
know meson better than I do :)
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
--D
> Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
> ---
> example/meson.build | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/example/meson.build b/example/meson.build
> index 76cf2d96..31e69187 100644
> --- a/example/meson.build
> +++ b/example/meson.build
> @@ -32,7 +32,7 @@ foreach ex : threaded_examples
> install: false)
> endforeach
>
> -if platform != 'dragonfly' and add_languages('cpp', required : false)
> +if platform != 'dragonfly' and add_languages('cpp', native : false, required : false)
> executable('passthrough_hp', 'passthrough_hp.cc',
> dependencies: [ thread_dep, libfuse_dep ],
> install: false)
>
> --
> 2.53.0
>
>
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH 07/10] mount: clarify kernel_opts vs mnt_opts vs flags in fuse_kern_fsmount
2026-05-08 16:39 [PATCH 00/10] libfuse: new mount API and SYNC_INIT fixes and tests Bernd Schubert via B4 Relay
` (5 preceding siblings ...)
2026-05-08 16:39 ` [PATCH 06/10] example: silence add_languages warning by setting 'native: false' Bernd Schubert via B4 Relay
@ 2026-05-08 16:39 ` Bernd Schubert via B4 Relay
2026-05-09 0:27 ` Darrick J. Wong
2026-05-08 16:39 ` [PATCH 08/10] fuse_daemonize_early_start: Disallow daemonization when already active Bernd Schubert via B4 Relay
` (2 subsequent siblings)
9 siblings, 1 reply; 25+ messages in thread
From: Bernd Schubert via B4 Relay @ 2026-05-08 16:39 UTC (permalink / raw)
To: fuse-devel; +Cc: Darrick J. Wong, Bernd Schubert
From: Bernd Schubert <bernd@bsbernd.com>
Just code/API updates, as these mount options can be rather confusing.
Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
---
lib/mount_i_linux.h | 13 ++++++++++---
1 file changed, 10 insertions(+), 3 deletions(-)
diff --git a/lib/mount_i_linux.h b/lib/mount_i_linux.h
index ab83e30b..112c365e 100644
--- a/lib/mount_i_linux.h
+++ b/lib/mount_i_linux.h
@@ -36,13 +36,20 @@ int fuse_kern_mount_get_base_mnt_opts(const struct mount_opts *mo, char **mnt_op
/**
* Mount using the new Linux mount API (fsopen/fsconfig/fsmount/move_mount)
* @mnt: mountpoint
- * @flags: mount flags (MS_NOSUID, MS_NODEV, etc.)
+ * @flags: MS_* bits (ro/rw, nosuid, nodev, sync, dirsync, ...) -
+ * translated by set_ms_flags() into fsconfig(SET_FLAG) and/or
+ * fsmount(MOUNT_ATTR_*); never carried via the strings below.
* @blkdev: 1 for fuseblk, 0 for fuse
* @fsname: filesystem name (or NULL)
* @subtype: filesystem subtype (or NULL)
* @source_dev: device name for building source string
- * @kernel_opts: kernel mount options string
- * @mnt_opts: additional mount options to pass to the kernel
+ * @kernel_opts: FUSE-kernel options parsed from -o args
+ * (e.g. "allow_other,max_read=65536,default_permissions")
+ * @mnt_opts: extras the caller layers on
+ * (e.g. "fd=7,rootmode=40000,user_id=1000,group_id=1000")
+ *
+ * @kernel_opts and @mnt_opts are both applied via fsconfig() in order;
+ * overlap is tolerated.
*
* Returns: 0 on success, -1 on failure with errno set
*/
--
2.53.0
^ permalink raw reply related [flat|nested] 25+ messages in thread* Re: [PATCH 07/10] mount: clarify kernel_opts vs mnt_opts vs flags in fuse_kern_fsmount
2026-05-08 16:39 ` [PATCH 07/10] mount: clarify kernel_opts vs mnt_opts vs flags in fuse_kern_fsmount Bernd Schubert via B4 Relay
@ 2026-05-09 0:27 ` Darrick J. Wong
2026-05-10 17:21 ` Bernd Schubert
0 siblings, 1 reply; 25+ messages in thread
From: Darrick J. Wong @ 2026-05-09 0:27 UTC (permalink / raw)
To: bernd; +Cc: fuse-devel
On Fri, May 08, 2026 at 06:39:10PM +0200, Bernd Schubert via B4 Relay wrote:
> From: Bernd Schubert <bernd@bsbernd.com>
>
> Just code/API updates, as these mount options can be rather confusing.
>
> Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
> ---
> lib/mount_i_linux.h | 13 ++++++++++---
> 1 file changed, 10 insertions(+), 3 deletions(-)
>
> diff --git a/lib/mount_i_linux.h b/lib/mount_i_linux.h
> index ab83e30b..112c365e 100644
> --- a/lib/mount_i_linux.h
> +++ b/lib/mount_i_linux.h
> @@ -36,13 +36,20 @@ int fuse_kern_mount_get_base_mnt_opts(const struct mount_opts *mo, char **mnt_op
> /**
> * Mount using the new Linux mount API (fsopen/fsconfig/fsmount/move_mount)
> * @mnt: mountpoint
> - * @flags: mount flags (MS_NOSUID, MS_NODEV, etc.)
> + * @flags: MS_* bits (ro/rw, nosuid, nodev, sync, dirsync, ...) -
> + * translated by set_ms_flags() into fsconfig(SET_FLAG) and/or
> + * fsmount(MOUNT_ATTR_*); never carried via the strings below.
I wonder if this parameter ought to be renamed ms_flags?
> * @blkdev: 1 for fuseblk, 0 for fuse
> * @fsname: filesystem name (or NULL)
> * @subtype: filesystem subtype (or NULL)
> * @source_dev: device name for building source string
> - * @kernel_opts: kernel mount options string
> - * @mnt_opts: additional mount options to pass to the kernel
> + * @kernel_opts: FUSE-kernel options parsed from -o args
> + * (e.g. "allow_other,max_read=65536,default_permissions")
> + * @mnt_opts: extras the caller layers on
> + * (e.g. "fd=7,rootmode=40000,user_id=1000,group_id=1000")
Oh, the "caller" here is libfuse. Could that be clarified a bit?
@mnt_opts: internal mount options added by libfuse
(e.g. "fd=7,rootmode=40000,user_id=1000,group_id=1000")
--D
> + *
> + * @kernel_opts and @mnt_opts are both applied via fsconfig() in order;
> + * overlap is tolerated.
> *
> * Returns: 0 on success, -1 on failure with errno set
> */
>
> --
> 2.53.0
>
>
^ permalink raw reply [flat|nested] 25+ messages in thread* Re: [PATCH 07/10] mount: clarify kernel_opts vs mnt_opts vs flags in fuse_kern_fsmount
2026-05-09 0:27 ` Darrick J. Wong
@ 2026-05-10 17:21 ` Bernd Schubert
0 siblings, 0 replies; 25+ messages in thread
From: Bernd Schubert @ 2026-05-10 17:21 UTC (permalink / raw)
To: Darrick J. Wong; +Cc: fuse-devel
On 5/9/26 02:27, Darrick J. Wong wrote:
> On Fri, May 08, 2026 at 06:39:10PM +0200, Bernd Schubert via B4 Relay wrote:
>> From: Bernd Schubert <bernd@bsbernd.com>
>>
>> Just code/API updates, as these mount options can be rather confusing.
>>
>> Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
>> ---
>> lib/mount_i_linux.h | 13 ++++++++++---
>> 1 file changed, 10 insertions(+), 3 deletions(-)
>>
>> diff --git a/lib/mount_i_linux.h b/lib/mount_i_linux.h
>> index ab83e30b..112c365e 100644
>> --- a/lib/mount_i_linux.h
>> +++ b/lib/mount_i_linux.h
>> @@ -36,13 +36,20 @@ int fuse_kern_mount_get_base_mnt_opts(const struct mount_opts *mo, char **mnt_op
>> /**
>> * Mount using the new Linux mount API (fsopen/fsconfig/fsmount/move_mount)
>> * @mnt: mountpoint
>> - * @flags: mount flags (MS_NOSUID, MS_NODEV, etc.)
>> + * @flags: MS_* bits (ro/rw, nosuid, nodev, sync, dirsync, ...) -
>> + * translated by set_ms_flags() into fsconfig(SET_FLAG) and/or
>> + * fsmount(MOUNT_ATTR_*); never carried via the strings below.
>
> I wonder if this parameter ought to be renamed ms_flags?
>
>> * @blkdev: 1 for fuseblk, 0 for fuse
>> * @fsname: filesystem name (or NULL)
>> * @subtype: filesystem subtype (or NULL)
>> * @source_dev: device name for building source string
>> - * @kernel_opts: kernel mount options string
>> - * @mnt_opts: additional mount options to pass to the kernel
>> + * @kernel_opts: FUSE-kernel options parsed from -o args
>> + * (e.g. "allow_other,max_read=65536,default_permissions")
>> + * @mnt_opts: extras the caller layers on
>> + * (e.g. "fd=7,rootmode=40000,user_id=1000,group_id=1000")
>
> Oh, the "caller" here is libfuse. Could that be clarified a bit?
>
> @mnt_opts: internal mount options added by libfuse
> (e.g. "fd=7,rootmode=40000,user_id=1000,group_id=1000")
>
I dropped this patch for now, the comment is actually not right. I'm
going to add some refactoring, maybe mnt_opts can be entirely dropped,
not sure yet.
Thanks,
Bernd
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH 08/10] fuse_daemonize_early_start: Disallow daemonization when already active.
2026-05-08 16:39 [PATCH 00/10] libfuse: new mount API and SYNC_INIT fixes and tests Bernd Schubert via B4 Relay
` (6 preceding siblings ...)
2026-05-08 16:39 ` [PATCH 07/10] mount: clarify kernel_opts vs mnt_opts vs flags in fuse_kern_fsmount Bernd Schubert via B4 Relay
@ 2026-05-08 16:39 ` Bernd Schubert via B4 Relay
2026-05-09 0:30 ` Darrick J. Wong
2026-05-08 16:39 ` [PATCH 09/10] fuse mount: Do not set sync_init when sync_init was not used Bernd Schubert via B4 Relay
2026-05-08 16:39 ` [PATCH 10/10] highlevel: Switch fuse_main_real_versioned() to fuse_daemonize_early() Bernd Schubert via B4 Relay
9 siblings, 1 reply; 25+ messages in thread
From: Bernd Schubert via B4 Relay @ 2026-05-08 16:39 UTC (permalink / raw)
To: fuse-devel; +Cc: Darrick J. Wong, Bernd Schubert
From: Bernd Schubert <bernd@bsbernd.com>
A coding error could run fuse_daemonize_early_start() two times
and so far the code was not handling/failing that.
Also do not set "active" when it actually does not do daemonization.
Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
---
lib/fuse_daemonize.c | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/lib/fuse_daemonize.c b/lib/fuse_daemonize.c
index c9a91748..8afe4b0d 100644
--- a/lib/fuse_daemonize.c
+++ b/lib/fuse_daemonize.c
@@ -211,18 +211,22 @@ int fuse_daemonize_early_start(unsigned int flags)
struct fuse_daemonize *dm = &daemonize;
int err = 0;
+ if (dm->active)
+ return -EALREADY;
+
dm->flags = flags;
dm->signal_pipe_wr = -1;
dm->death_pipe_rd = -1;
dm->stop_pipe_rd = -1;
dm->stop_pipe_wr = -1;
- dm->active = true;
if (!(flags & FUSE_DAEMONIZE_NO_CHDIR))
(void)chdir("/");
- if (!(flags & FUSE_DAEMONIZE_NO_BACKGROUND))
+ if (!(flags & FUSE_DAEMONIZE_NO_BACKGROUND)) {
+ dm->active = true;
err = do_daemonize(dm);
+ }
return err;
}
--
2.53.0
^ permalink raw reply related [flat|nested] 25+ messages in thread* Re: [PATCH 08/10] fuse_daemonize_early_start: Disallow daemonization when already active.
2026-05-08 16:39 ` [PATCH 08/10] fuse_daemonize_early_start: Disallow daemonization when already active Bernd Schubert via B4 Relay
@ 2026-05-09 0:30 ` Darrick J. Wong
2026-05-10 16:53 ` Bernd Schubert
0 siblings, 1 reply; 25+ messages in thread
From: Darrick J. Wong @ 2026-05-09 0:30 UTC (permalink / raw)
To: bernd; +Cc: fuse-devel
On Fri, May 08, 2026 at 06:39:11PM +0200, Bernd Schubert via B4 Relay wrote:
> From: Bernd Schubert <bernd@bsbernd.com>
>
> A coding error could run fuse_daemonize_early_start() two times
> and so far the code was not handling/failing that.
>
> Also do not set "active" when it actually does not do daemonization.
>
> Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
> ---
> lib/fuse_daemonize.c | 8 ++++++--
> 1 file changed, 6 insertions(+), 2 deletions(-)
>
> diff --git a/lib/fuse_daemonize.c b/lib/fuse_daemonize.c
> index c9a91748..8afe4b0d 100644
> --- a/lib/fuse_daemonize.c
> +++ b/lib/fuse_daemonize.c
> @@ -211,18 +211,22 @@ int fuse_daemonize_early_start(unsigned int flags)
> struct fuse_daemonize *dm = &daemonize;
> int err = 0;
>
> + if (dm->active)
> + return -EALREADY;
> +
> dm->flags = flags;
> dm->signal_pipe_wr = -1;
> dm->death_pipe_rd = -1;
> dm->stop_pipe_rd = -1;
> dm->stop_pipe_wr = -1;
> - dm->active = true;
>
> if (!(flags & FUSE_DAEMONIZE_NO_CHDIR))
> (void)chdir("/");
>
> - if (!(flags & FUSE_DAEMONIZE_NO_BACKGROUND))
> + if (!(flags & FUSE_DAEMONIZE_NO_BACKGROUND)) {
> + dm->active = true;
I suppose there's no harm in letting multiple
fuse_daemonize_early_start(FUSE_DAEMONIZE_NO_BACKGROUND) calls occur,
but wouldn't it be simpler to say that fuse_daemonize_early_start should
only be called once by fuse servers that want sync_init and don't use
fuse_main(); or at most once by fuse servers that want sync_init and do
use fuse_main()?
vs. having a weird library call where with *some* parameters you can
call it more than once.
--D
> err = do_daemonize(dm);
> + }
>
> return err;
> }
>
> --
> 2.53.0
>
>
^ permalink raw reply [flat|nested] 25+ messages in thread* Re: [PATCH 08/10] fuse_daemonize_early_start: Disallow daemonization when already active.
2026-05-09 0:30 ` Darrick J. Wong
@ 2026-05-10 16:53 ` Bernd Schubert
2026-05-10 17:01 ` Bernd Schubert
0 siblings, 1 reply; 25+ messages in thread
From: Bernd Schubert @ 2026-05-10 16:53 UTC (permalink / raw)
To: Darrick J. Wong; +Cc: fuse-devel
On 5/9/26 02:30, Darrick J. Wong wrote:
> On Fri, May 08, 2026 at 06:39:11PM +0200, Bernd Schubert via B4 Relay wrote:
>> From: Bernd Schubert <bernd@bsbernd.com>
>>
>> A coding error could run fuse_daemonize_early_start() two times
>> and so far the code was not handling/failing that.
>>
>> Also do not set "active" when it actually does not do daemonization.
>>
>> Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
>> ---
>> lib/fuse_daemonize.c | 8 ++++++--
>> 1 file changed, 6 insertions(+), 2 deletions(-)
>>
>> diff --git a/lib/fuse_daemonize.c b/lib/fuse_daemonize.c
>> index c9a91748..8afe4b0d 100644
>> --- a/lib/fuse_daemonize.c
>> +++ b/lib/fuse_daemonize.c
>> @@ -211,18 +211,22 @@ int fuse_daemonize_early_start(unsigned int flags)
>> struct fuse_daemonize *dm = &daemonize;
>> int err = 0;
>>
>> + if (dm->active)
>> + return -EALREADY;
>> +
>> dm->flags = flags;
>> dm->signal_pipe_wr = -1;
>> dm->death_pipe_rd = -1;
>> dm->stop_pipe_rd = -1;
>> dm->stop_pipe_wr = -1;
>> - dm->active = true;
>>
>> if (!(flags & FUSE_DAEMONIZE_NO_CHDIR))
>> (void)chdir("/");
>>
>> - if (!(flags & FUSE_DAEMONIZE_NO_BACKGROUND))
>> + if (!(flags & FUSE_DAEMONIZE_NO_BACKGROUND)) {
>> + dm->active = true;
>
> I suppose there's no harm in letting multiple
> fuse_daemonize_early_start(FUSE_DAEMONIZE_NO_BACKGROUND) calls occur,
> but wouldn't it be simpler to say that fuse_daemonize_early_start should
> only be called once by fuse servers that want sync_init and don't use
> fuse_main(); or at most once by fuse servers that want sync_init and do
> use fuse_main()?
fuse_daemonize_early_ is needed for sync_init, right, but for sure not
exclusively. It is equally needed for daemons that want to start threads
before doing anything related to fuse. These daemons probably already
have their own daemonization code, but any new daemon can re-use the new
fuse_daemonize_early_ code, instead of creating yet another instance of
the same work.
>
> vs. having a weird library call where with *some* parameters you can
> call it more than once.
"dm->active = true" means "deamonization is currently in progress - it
is cleared by fuse_daemonize_early_signal() (_early_fail() /
_early_success). I.e. it shall not be set when there is no ongoing
daemonization. I updated the comments in struct fuse_daemonize.
Thanks,
Bernd
^ permalink raw reply [flat|nested] 25+ messages in thread* Re: [PATCH 08/10] fuse_daemonize_early_start: Disallow daemonization when already active.
2026-05-10 16:53 ` Bernd Schubert
@ 2026-05-10 17:01 ` Bernd Schubert
0 siblings, 0 replies; 25+ messages in thread
From: Bernd Schubert @ 2026-05-10 17:01 UTC (permalink / raw)
To: Darrick J. Wong; +Cc: fuse-devel
On 5/10/26 18:53, Bernd Schubert wrote:
>> vs. having a weird library call where with *some* parameters you can
>> call it more than once.
Regarding calling fuse_daemonize_early_start() multiple times, how is
fuse_daemonize_early_start() supposed to behave then? Second time is a
no-op or a nested daemonization? The latter would even work after this
-EALREADY change, but only after void _early_success or _early_fail()
was called.
Well, let me change the 2nd nested call to return of 0 / no-op.
Thanks,
Bernd
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH 09/10] fuse mount: Do not set sync_init when sync_init was not used
2026-05-08 16:39 [PATCH 00/10] libfuse: new mount API and SYNC_INIT fixes and tests Bernd Schubert via B4 Relay
` (7 preceding siblings ...)
2026-05-08 16:39 ` [PATCH 08/10] fuse_daemonize_early_start: Disallow daemonization when already active Bernd Schubert via B4 Relay
@ 2026-05-08 16:39 ` Bernd Schubert via B4 Relay
2026-05-09 0:35 ` Darrick J. Wong
2026-05-08 16:39 ` [PATCH 10/10] highlevel: Switch fuse_main_real_versioned() to fuse_daemonize_early() Bernd Schubert via B4 Relay
9 siblings, 1 reply; 25+ messages in thread
From: Bernd Schubert via B4 Relay @ 2026-05-08 16:39 UTC (permalink / raw)
To: fuse-devel; +Cc: Darrick J. Wong, Bernd Schubert
From: Bernd Schubert <bernd@bsbernd.com>
synchronous init is just a hint and we cannot fail on it, but for
daemonization we need to do know when it was actually used.
Bug was that se->sync_init() was set unconditionally on mount success.
Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
---
lib/fuse_lowlevel.c | 22 +++++++++++++++-------
1 file changed, 15 insertions(+), 7 deletions(-)
diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c
index 6e078e82..60b936c3 100644
--- a/lib/fuse_lowlevel.c
+++ b/lib/fuse_lowlevel.c
@@ -4471,10 +4471,13 @@ static void *session_sync_init_worker(void *data)
}
/* Enable synchronous FUSE_INIT and start worker thread */
-static int session_start_sync_init(struct fuse_session *se, int fd)
+static int session_start_sync_init(struct fuse_session *se, int fd,
+ bool *is_sync_init)
{
int err, res;
+ *is_sync_init = false;
+
/*
* Older fuse servers do not set want_sync_init or start the new
* daemonize code, so they get async init.
@@ -4536,6 +4539,8 @@ static int session_start_sync_init(struct fuse_session *se, int fd)
return -EIO;
}
+ *is_sync_init = true;
+
return 0;
}
@@ -4594,7 +4599,8 @@ static int session_wait_sync_init_completion(struct fuse_session *se)
static int new_api_fusermount(struct fuse_session *se,
const char *mountpoint,
const char *mnt_opts,
- int *sock_fd, pid_t *fusermount_pid)
+ int *sock_fd, pid_t *fusermount_pid,
+ bool *is_sync_init)
{
int fd, err;
@@ -4617,7 +4623,7 @@ static int new_api_fusermount(struct fuse_session *se,
/* Start worker thread with correct fd from fusermount3 */
se->fd = fd;
- err = session_start_sync_init(se, fd);
+ err = session_start_sync_init(se, fd, is_sync_init);
if (err) {
fuse_log(FUSE_LOG_ERR,
"fuse: failed to start sync init worker\n");
@@ -4649,6 +4655,7 @@ static int fuse_session_mount_new_api(struct fuse_session *se,
char *mnt_opts = NULL;
char *mnt_opts_with_fd = NULL;
char fd_opt[32];
+ bool is_sync_init = false;
res = fuse_kern_mount_get_base_mnt_opts(se->mo, &mnt_opts);
err = -EIO;
@@ -4665,7 +4672,7 @@ static int fuse_session_mount_new_api(struct fuse_session *se,
}
se->fd = fd;
- err = session_start_sync_init(se, fd);
+ err = session_start_sync_init(se, fd, &is_sync_init);
if (err)
goto err;
@@ -4684,7 +4691,8 @@ static int fuse_session_mount_new_api(struct fuse_session *se,
close(fd);
se->fd = -1;
fd = new_api_fusermount(se, mountpoint, mnt_opts,
- &sock_fd, &fusermount_pid);
+ &sock_fd, &fusermount_pid,
+ &is_sync_init);
if (fd < 0) {
err = fd;
goto err_with_sock;
@@ -4711,11 +4719,12 @@ err:
se->fd = -1;
se->error = err;
}
+ se->is_sync_init = is_sync_init;
+
/* Wait for synchronous FUSE_INIT to complete */
if (session_wait_sync_init_completion(se) < 0)
fuse_log(FUSE_LOG_ERR, "fuse: sync init completion failed\n");
- se->is_sync_init = true;
free(mnt_opts);
free(mnt_opts_with_fd);
return fd;
@@ -4791,7 +4800,6 @@ int fuse_session_mount(struct fuse_session *se, const char *_mountpoint)
out:
se->fd = fd;
se->mountpoint = mountpoint;
-
fuse_daemonize_early_set_mounted();
return 0;
--
2.53.0
^ permalink raw reply related [flat|nested] 25+ messages in thread* Re: [PATCH 09/10] fuse mount: Do not set sync_init when sync_init was not used
2026-05-08 16:39 ` [PATCH 09/10] fuse mount: Do not set sync_init when sync_init was not used Bernd Schubert via B4 Relay
@ 2026-05-09 0:35 ` Darrick J. Wong
2026-05-10 17:04 ` Bernd Schubert
0 siblings, 1 reply; 25+ messages in thread
From: Darrick J. Wong @ 2026-05-09 0:35 UTC (permalink / raw)
To: bernd; +Cc: fuse-devel
On Fri, May 08, 2026 at 06:39:12PM +0200, Bernd Schubert via B4 Relay wrote:
> From: Bernd Schubert <bernd@bsbernd.com>
>
> synchronous init is just a hint and we cannot fail on it, but for
> daemonization we need to do know when it was actually used.
> Bug was that se->sync_init() was set unconditionally on mount success.
>
> Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
> ---
> lib/fuse_lowlevel.c | 22 +++++++++++++++-------
> 1 file changed, 15 insertions(+), 7 deletions(-)
>
> diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c
> index 6e078e82..60b936c3 100644
> --- a/lib/fuse_lowlevel.c
> +++ b/lib/fuse_lowlevel.c
> @@ -4471,10 +4471,13 @@ static void *session_sync_init_worker(void *data)
> }
>
> /* Enable synchronous FUSE_INIT and start worker thread */
> -static int session_start_sync_init(struct fuse_session *se, int fd)
> +static int session_start_sync_init(struct fuse_session *se, int fd,
> + bool *is_sync_init)
> {
> int err, res;
>
> + *is_sync_init = false;
> +
> /*
> * Older fuse servers do not set want_sync_init or start the new
> * daemonize code, so they get async init.
> @@ -4536,6 +4539,8 @@ static int session_start_sync_init(struct fuse_session *se, int fd)
> return -EIO;
> }
>
> + *is_sync_init = true;
Hrm. So we only end up with return value == 0 and *is_sync_init==true
if SYNC_INIT was enabled via ioctl and all the pthread stuff is ready to
go. The other two outcomes are:
1. return == 0 && *is_sync_init==false if the kernel didn't let us use
synchronous init
2. return != 0 if something broke and we just want to abort
If that's all correct then I've understood this patch well enough to say
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
--D
> +
> return 0;
> }
>
> @@ -4594,7 +4599,8 @@ static int session_wait_sync_init_completion(struct fuse_session *se)
> static int new_api_fusermount(struct fuse_session *se,
> const char *mountpoint,
> const char *mnt_opts,
> - int *sock_fd, pid_t *fusermount_pid)
> + int *sock_fd, pid_t *fusermount_pid,
> + bool *is_sync_init)
> {
> int fd, err;
>
> @@ -4617,7 +4623,7 @@ static int new_api_fusermount(struct fuse_session *se,
>
> /* Start worker thread with correct fd from fusermount3 */
> se->fd = fd;
> - err = session_start_sync_init(se, fd);
> + err = session_start_sync_init(se, fd, is_sync_init);
> if (err) {
> fuse_log(FUSE_LOG_ERR,
> "fuse: failed to start sync init worker\n");
> @@ -4649,6 +4655,7 @@ static int fuse_session_mount_new_api(struct fuse_session *se,
> char *mnt_opts = NULL;
> char *mnt_opts_with_fd = NULL;
> char fd_opt[32];
> + bool is_sync_init = false;
>
> res = fuse_kern_mount_get_base_mnt_opts(se->mo, &mnt_opts);
> err = -EIO;
> @@ -4665,7 +4672,7 @@ static int fuse_session_mount_new_api(struct fuse_session *se,
> }
>
> se->fd = fd;
> - err = session_start_sync_init(se, fd);
> + err = session_start_sync_init(se, fd, &is_sync_init);
> if (err)
> goto err;
>
> @@ -4684,7 +4691,8 @@ static int fuse_session_mount_new_api(struct fuse_session *se,
> close(fd);
> se->fd = -1;
> fd = new_api_fusermount(se, mountpoint, mnt_opts,
> - &sock_fd, &fusermount_pid);
> + &sock_fd, &fusermount_pid,
> + &is_sync_init);
> if (fd < 0) {
> err = fd;
> goto err_with_sock;
> @@ -4711,11 +4719,12 @@ err:
> se->fd = -1;
> se->error = err;
> }
> + se->is_sync_init = is_sync_init;
> +
> /* Wait for synchronous FUSE_INIT to complete */
> if (session_wait_sync_init_completion(se) < 0)
> fuse_log(FUSE_LOG_ERR, "fuse: sync init completion failed\n");
>
> - se->is_sync_init = true;
> free(mnt_opts);
> free(mnt_opts_with_fd);
> return fd;
> @@ -4791,7 +4800,6 @@ int fuse_session_mount(struct fuse_session *se, const char *_mountpoint)
> out:
> se->fd = fd;
> se->mountpoint = mountpoint;
> -
> fuse_daemonize_early_set_mounted();
>
> return 0;
>
> --
> 2.53.0
>
>
^ permalink raw reply [flat|nested] 25+ messages in thread* Re: [PATCH 09/10] fuse mount: Do not set sync_init when sync_init was not used
2026-05-09 0:35 ` Darrick J. Wong
@ 2026-05-10 17:04 ` Bernd Schubert
0 siblings, 0 replies; 25+ messages in thread
From: Bernd Schubert @ 2026-05-10 17:04 UTC (permalink / raw)
To: Darrick J. Wong; +Cc: fuse-devel
On 5/9/26 02:35, Darrick J. Wong wrote:
> On Fri, May 08, 2026 at 06:39:12PM +0200, Bernd Schubert via B4 Relay wrote:
>> From: Bernd Schubert <bernd@bsbernd.com>
>>
>> synchronous init is just a hint and we cannot fail on it, but for
>> daemonization we need to do know when it was actually used.
>> Bug was that se->sync_init() was set unconditionally on mount success.
>>
>> Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
>> ---
>> lib/fuse_lowlevel.c | 22 +++++++++++++++-------
>> 1 file changed, 15 insertions(+), 7 deletions(-)
>>
>> diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c
>> index 6e078e82..60b936c3 100644
>> --- a/lib/fuse_lowlevel.c
>> +++ b/lib/fuse_lowlevel.c
>> @@ -4471,10 +4471,13 @@ static void *session_sync_init_worker(void *data)
>> }
>>
>> /* Enable synchronous FUSE_INIT and start worker thread */
>> -static int session_start_sync_init(struct fuse_session *se, int fd)
>> +static int session_start_sync_init(struct fuse_session *se, int fd,
>> + bool *is_sync_init)
>> {
>> int err, res;
>>
>> + *is_sync_init = false;
>> +
>> /*
>> * Older fuse servers do not set want_sync_init or start the new
>> * daemonize code, so they get async init.
>> @@ -4536,6 +4539,8 @@ static int session_start_sync_init(struct fuse_session *se, int fd)
>> return -EIO;
>> }
>>
>> + *is_sync_init = true;
>
> Hrm. So we only end up with return value == 0 and *is_sync_init==true
> if SYNC_INIT was enabled via ioctl and all the pthread stuff is ready to
> go. The other two outcomes are:
>
> 1. return == 0 && *is_sync_init==false if the kernel didn't let us use
> synchronous init
>
> 2. return != 0 if something broke and we just want to abort
>
> If that's all correct then I've understood this patch well enough to say
Exactly! I'm going to amend the commit message.
> Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
>
> --D
>
Thanks,
Bernd
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH 10/10] highlevel: Switch fuse_main_real_versioned() to fuse_daemonize_early()
2026-05-08 16:39 [PATCH 00/10] libfuse: new mount API and SYNC_INIT fixes and tests Bernd Schubert via B4 Relay
` (8 preceding siblings ...)
2026-05-08 16:39 ` [PATCH 09/10] fuse mount: Do not set sync_init when sync_init was not used Bernd Schubert via B4 Relay
@ 2026-05-08 16:39 ` Bernd Schubert via B4 Relay
2026-05-09 0:38 ` Darrick J. Wong
9 siblings, 1 reply; 25+ messages in thread
From: Bernd Schubert via B4 Relay @ 2026-05-08 16:39 UTC (permalink / raw)
To: fuse-devel; +Cc: Darrick J. Wong, Bernd Schubert, Miklos Szeredi
From: Bernd Schubert <bernd@bsbernd.com>
For high level we can switch directly in that function to the new daemonize
API. Additionally we need to check if the daemon has already acticated
daemonization to avoid warning messages.
Suggested-by: Miklos Szeredi <miklos@szeredi.hu>
Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
---
lib/helper.c | 30 +++++++++++++++++++-----------
1 file changed, 19 insertions(+), 11 deletions(-)
diff --git a/lib/helper.c b/lib/helper.c
index 4d7c2b7f..15de3fbf 100644
--- a/lib/helper.c
+++ b/lib/helper.c
@@ -260,6 +260,7 @@ int fuse_main_real_versioned(int argc, char *argv[],
struct fuse_cmdline_opts opts;
int res;
struct fuse_loop_config *loop_config = NULL;
+ bool self_daemonize = false;
if (fuse_parse_cmdline(&args, &opts) != 0)
return 1;
@@ -289,6 +290,21 @@ int fuse_main_real_versioned(int argc, char *argv[],
goto out1;
}
+ /* The application might have already started daemonization itself */
+ if (!fuse_daemonize_early_is_active()) {
+ int daemonize_early_flags = 0;
+
+ if (opts.foreground)
+ daemonize_early_flags |= FUSE_DAEMONIZE_NO_BACKGROUND;
+
+ self_daemonize = true;
+ res = fuse_daemonize_early_start(daemonize_early_flags);
+ if (res != 0) {
+ fuse_log(FUSE_LOG_ERR, "fuse: daemonize_early_start failed\n");
+ goto out1;
+ }
+ }
+
struct fuse *_fuse_new_31(struct fuse_args *args,
const struct fuse_operations *op, size_t op_size,
struct libfuse_version *version,
@@ -305,22 +321,14 @@ int fuse_main_real_versioned(int argc, char *argv[],
goto out2;
}
- /*
- * fuse_daemonize() already checks and fails then, but we need to
- * handle it gracefully here, as this is done libfuse internal
- * and caller didn't ask to daemonize with old API.
- */
- if (!fuse_daemonize_early_is_active()) {
- if (fuse_daemonize(opts.foreground) != 0) {
- res = 5;
- goto out3;
- }
- }
if (fuse_set_signal_handlers(se) != 0) {
res = 6;
goto out3;
}
+ if (self_daemonize)
+ fuse_daemonize_early_success();
+
if (opts.singlethread)
res = fuse_loop(fuse);
else {
--
2.53.0
^ permalink raw reply related [flat|nested] 25+ messages in thread* Re: [PATCH 10/10] highlevel: Switch fuse_main_real_versioned() to fuse_daemonize_early()
2026-05-08 16:39 ` [PATCH 10/10] highlevel: Switch fuse_main_real_versioned() to fuse_daemonize_early() Bernd Schubert via B4 Relay
@ 2026-05-09 0:38 ` Darrick J. Wong
2026-05-10 17:19 ` Bernd Schubert
0 siblings, 1 reply; 25+ messages in thread
From: Darrick J. Wong @ 2026-05-09 0:38 UTC (permalink / raw)
To: bernd; +Cc: fuse-devel, Miklos Szeredi
On Fri, May 08, 2026 at 06:39:13PM +0200, Bernd Schubert via B4 Relay wrote:
> From: Bernd Schubert <bernd@bsbernd.com>
>
> For high level we can switch directly in that function to the new daemonize
> API. Additionally we need to check if the daemon has already acticated
> daemonization to avoid warning messages.
>
> Suggested-by: Miklos Szeredi <miklos@szeredi.hu>
> Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
> ---
> lib/helper.c | 30 +++++++++++++++++++-----------
> 1 file changed, 19 insertions(+), 11 deletions(-)
>
> diff --git a/lib/helper.c b/lib/helper.c
> index 4d7c2b7f..15de3fbf 100644
> --- a/lib/helper.c
> +++ b/lib/helper.c
> @@ -260,6 +260,7 @@ int fuse_main_real_versioned(int argc, char *argv[],
> struct fuse_cmdline_opts opts;
> int res;
> struct fuse_loop_config *loop_config = NULL;
> + bool self_daemonize = false;
>
> if (fuse_parse_cmdline(&args, &opts) != 0)
> return 1;
> @@ -289,6 +290,21 @@ int fuse_main_real_versioned(int argc, char *argv[],
> goto out1;
> }
>
> + /* The application might have already started daemonization itself */
> + if (!fuse_daemonize_early_is_active()) {
> + int daemonize_early_flags = 0;
> +
> + if (opts.foreground)
> + daemonize_early_flags |= FUSE_DAEMONIZE_NO_BACKGROUND;
> +
> + self_daemonize = true;
Technically speaking you don't have to set self_daemonize until after
the _early_start succeeds, right?
That doesn't bother me all that much, so
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
--D
> + res = fuse_daemonize_early_start(daemonize_early_flags);
> + if (res != 0) {
> + fuse_log(FUSE_LOG_ERR, "fuse: daemonize_early_start failed\n");
> + goto out1;
> + }
> + }
> +
> struct fuse *_fuse_new_31(struct fuse_args *args,
> const struct fuse_operations *op, size_t op_size,
> struct libfuse_version *version,
> @@ -305,22 +321,14 @@ int fuse_main_real_versioned(int argc, char *argv[],
> goto out2;
> }
>
> - /*
> - * fuse_daemonize() already checks and fails then, but we need to
> - * handle it gracefully here, as this is done libfuse internal
> - * and caller didn't ask to daemonize with old API.
> - */
> - if (!fuse_daemonize_early_is_active()) {
> - if (fuse_daemonize(opts.foreground) != 0) {
> - res = 5;
> - goto out3;
> - }
> - }
> if (fuse_set_signal_handlers(se) != 0) {
> res = 6;
> goto out3;
> }
>
> + if (self_daemonize)
> + fuse_daemonize_early_success();
> +
> if (opts.singlethread)
> res = fuse_loop(fuse);
> else {
>
> --
> 2.53.0
>
>
^ permalink raw reply [flat|nested] 25+ messages in thread* Re: [PATCH 10/10] highlevel: Switch fuse_main_real_versioned() to fuse_daemonize_early()
2026-05-09 0:38 ` Darrick J. Wong
@ 2026-05-10 17:19 ` Bernd Schubert
0 siblings, 0 replies; 25+ messages in thread
From: Bernd Schubert @ 2026-05-10 17:19 UTC (permalink / raw)
To: Darrick J. Wong; +Cc: fuse-devel, Miklos Szeredi
On 5/9/26 02:38, Darrick J. Wong wrote:
> On Fri, May 08, 2026 at 06:39:13PM +0200, Bernd Schubert via B4 Relay wrote:
>> From: Bernd Schubert <bernd@bsbernd.com>
>>
>> For high level we can switch directly in that function to the new daemonize
>> API. Additionally we need to check if the daemon has already acticated
>> daemonization to avoid warning messages.
>>
>> Suggested-by: Miklos Szeredi <miklos@szeredi.hu>
>> Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
>> ---
>> lib/helper.c | 30 +++++++++++++++++++-----------
>> 1 file changed, 19 insertions(+), 11 deletions(-)
>>
>> diff --git a/lib/helper.c b/lib/helper.c
>> index 4d7c2b7f..15de3fbf 100644
>> --- a/lib/helper.c
>> +++ b/lib/helper.c
>> @@ -260,6 +260,7 @@ int fuse_main_real_versioned(int argc, char *argv[],
>> struct fuse_cmdline_opts opts;
>> int res;
>> struct fuse_loop_config *loop_config = NULL;
>> + bool self_daemonize = false;
>>
>> if (fuse_parse_cmdline(&args, &opts) != 0)
>> return 1;
>> @@ -289,6 +290,21 @@ int fuse_main_real_versioned(int argc, char *argv[],
>> goto out1;
>> }
>>
>> + /* The application might have already started daemonization itself */
>> + if (!fuse_daemonize_early_is_active()) {
>> + int daemonize_early_flags = 0;
>> +
>> + if (opts.foreground)
>> + daemonize_early_flags |= FUSE_DAEMONIZE_NO_BACKGROUND;
>> +
>> + self_daemonize = true;
>
> Technically speaking you don't have to set self_daemonize until after
> the _early_start succeeds, right?
Yeah, I moved it below the "if (res != 0) {" condition now, probably
cleaner.
Thanks,
Bernd
^ permalink raw reply [flat|nested] 25+ messages in thread