* Re: [f2fs-dev] [GIT PULL] fscrypt updates for 6.7
From: patchwork-bot+f2fs @ 2026-05-01 4:52 UTC (permalink / raw)
To: Eric Biggers
Cc: torvalds, tytso, linux-kernel, linux-f2fs-devel, linux-fscrypt,
linux-fsdevel, jaegeuk, linux-ext4, linux-btrfs
In-Reply-To: <20231030040419.GA43439@sol.localdomain>
Hello:
This pull request was applied to jaegeuk/f2fs.git (dev)
by Linus Torvalds <torvalds@linux-foundation.org>:
On Sun, 29 Oct 2023 21:04:19 -0700 you wrote:
> The following changes since commit 6465e260f48790807eef06b583b38ca9789b6072:
>
> Linux 6.6-rc3 (2023-09-24 14:31:13 -0700)
>
> are available in the Git repository at:
>
> https://git.kernel.org/pub/scm/fs/fscrypt/linux.git tags/fscrypt-for-linus
>
> [...]
Here is the summary with links:
- [f2fs-dev,GIT,PULL] fscrypt updates for 6.7
https://git.kernel.org/jaegeuk/f2fs/c/9932f00bf40d
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply
* Re: [syzbot] [ext4?] INFO: task jbd2/sda1-NUM:NUM blocked in I/O wait for more than NUM seconds.
From: syzbot @ 2026-05-01 14:48 UTC (permalink / raw)
To: jack, linux-ext4, linux-kernel, syzkaller-bugs, tytso
In-Reply-To: <69f3f165.170a0220.5f1b.0010.GAE@google.com>
syzbot has found a reproducer for the following issue on:
HEAD commit: 26fd6bff2c05 Merge tag 'mtd/fixes-for-7.1-rc2' of git://gi..
git tree: upstream
console output: https://syzkaller.appspot.com/x/log.txt?x=1229bece580000
kernel config: https://syzkaller.appspot.com/x/.config?x=d0f0911eedbc130a
dashboard link: https://syzkaller.appspot.com/bug?extid=c7604c9fdd7580cca4e0
compiler: gcc (Debian 14.2.0-19) 14.2.0, GNU ld (GNU Binutils for Debian) 2.44
syz repro: https://syzkaller.appspot.com/x/repro.syz?x=17f37506580000
C reproducer: https://syzkaller.appspot.com/x/repro.c?x=171441ce580000
Downloadable assets:
disk image: https://storage.googleapis.com/syzbot-assets/7d72741f9879/disk-26fd6bff.raw.xz
vmlinux: https://storage.googleapis.com/syzbot-assets/b02c6a1eba87/vmlinux-26fd6bff.xz
kernel image: https://storage.googleapis.com/syzbot-assets/4f218b09b68f/bzImage-26fd6bff.xz
IMPORTANT: if you fix the issue, please add the following tag to the commit:
Reported-by: syzbot+c7604c9fdd7580cca4e0@syzkaller.appspotmail.com
INFO: task jbd2/sda1-8:4955 blocked in I/O wait for more than 143 seconds.
Not tainted syzkaller #0
"echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message.
task:jbd2/sda1-8 state:D stack:26088 pid:4955 tgid:4955 ppid:2 task_flags:0x240040 flags:0x00080000
Call Trace:
<TASK>
context_switch kernel/sched/core.c:5387 [inline]
__schedule+0x10e9/0x6820 kernel/sched/core.c:7188
__schedule_loop kernel/sched/core.c:7267 [inline]
schedule+0xdd/0x390 kernel/sched/core.c:7282
io_schedule+0x8a/0xf0 kernel/sched/core.c:8109
bit_wait_io+0xd/0xe0 kernel/sched/wait_bit.c:250
__wait_on_bit+0x65/0x180 kernel/sched/wait_bit.c:52
out_of_line_wait_on_bit+0xdc/0x110 kernel/sched/wait_bit.c:67
wait_on_bit_io include/linux/wait_bit.h:105 [inline]
__wait_on_buffer+0x64/0x70 fs/buffer.c:123
wait_on_buffer include/linux/buffer_head.h:420 [inline]
jbd2_journal_commit_transaction+0x388a/0x6870 fs/jbd2/commit.c:837
kjournald2+0x200/0x760 fs/jbd2/journal.c:201
kthread+0x370/0x450 kernel/kthread.c:436
ret_from_fork+0x72b/0xd50 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
</TASK>
INFO: task syz.0.189:6070 blocked in I/O wait for more than 143 seconds.
Not tainted syzkaller #0
"echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message.
task:syz.0.189 state:D stack:28240 pid:6070 tgid:6070 ppid:5783 task_flags:0x440040 flags:0x00080002
Call Trace:
<TASK>
context_switch kernel/sched/core.c:5387 [inline]
__schedule+0x10e9/0x6820 kernel/sched/core.c:7188
__schedule_loop kernel/sched/core.c:7267 [inline]
schedule+0xdd/0x390 kernel/sched/core.c:7282
io_schedule+0x8a/0xf0 kernel/sched/core.c:8109
bit_wait_io+0xd/0xe0 kernel/sched/wait_bit.c:250
__wait_on_bit+0x65/0x180 kernel/sched/wait_bit.c:52
out_of_line_wait_on_bit+0xdc/0x110 kernel/sched/wait_bit.c:67
wait_on_bit_io include/linux/wait_bit.h:105 [inline]
do_get_write_access+0x84f/0x1220 fs/jbd2/transaction.c:1113
jbd2_journal_get_write_access+0x1d6/0x280 fs/jbd2/transaction.c:1263
__ext4_journal_get_write_access+0x6a/0x340 fs/ext4/ext4_jbd2.c:241
ext4_reserve_inode_write+0x1b7/0x330 fs/ext4/inode.c:6375
__ext4_mark_inode_dirty+0x18f/0x890 fs/ext4/inode.c:6550
ext4_dirty_inode+0xd9/0x130 fs/ext4/inode.c:6587
__mark_inode_dirty+0x1f3/0x1720 fs/fs-writeback.c:2623
generic_update_time fs/inode.c:2192 [inline]
file_update_time_flags+0x46b/0x500 fs/inode.c:2422
ext4_page_mkwrite+0x324/0x1890 fs/ext4/inode.c:6753
do_page_mkwrite+0x17a/0x440 mm/memory.c:3668
do_shared_fault mm/memory.c:5969 [inline]
do_fault+0x3b5/0x1750 mm/memory.c:6031
do_pte_missing mm/memory.c:4550 [inline]
handle_pte_fault mm/memory.c:6411 [inline]
__handle_mm_fault+0x187d/0x2a00 mm/memory.c:6549
handle_mm_fault+0x36d/0xa20 mm/memory.c:6718
do_user_addr_fault+0x5a3/0x12f0 arch/x86/mm/fault.c:1334
handle_page_fault arch/x86/mm/fault.c:1474 [inline]
exc_page_fault+0x6f/0xd0 arch/x86/mm/fault.c:1527
asm_exc_page_fault+0x26/0x30 arch/x86/include/asm/idtentry.h:618
RIP: 0033:0x7f97a466a883
RSP: 002b:00007ffc3f813c60 EFLAGS: 00010246
RAX: 000000000003fde8 RBX: 0000000000000000 RCX: 0000000000000000
RDX: 0000001b2e124000 RSI: 0000000000040000 RDI: 00007f97a49db710
RBP: 0000000000000000 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000000 R12: 0000000000000000
R13: 0000000000000000 R14: 0000000000000000 R15: 00007ffc3f813ef0
</TASK>
Showing all locks held in the system:
1 lock held by ksoftirqd/1/23:
#0: ffff8880b853b3e0 (&rq->__lock){-.-.}-{2:2}, at: raw_spin_rq_lock_nested+0x2c/0x140 kernel/sched/core.c:652
1 lock held by khungtaskd/30:
#0: ffffffff8e7e52e0 (rcu_read_lock){....}-{1:3}, at: rcu_lock_acquire include/linux/rcupdate.h:300 [inline]
#0: ffffffff8e7e52e0 (rcu_read_lock){....}-{1:3}, at: rcu_read_lock include/linux/rcupdate.h:838 [inline]
#0: ffffffff8e7e52e0 (rcu_read_lock){....}-{1:3}, at: debug_show_all_locks+0x3d/0x184 kernel/locking/lockdep.c:6775
6 locks held by kworker/u8:3/47:
#0: ffff88801c6ca140 ((wq_completion)writeback){+.+.}-{0:0}, at: process_one_work+0x12d6/0x1980 kernel/workqueue.c:3277
#1: ffffc90000b77d08 ((work_completion)(&(&wb->dwork)->work)){+.+.}-{0:0}, at: process_one_work+0x973/0x1980 kernel/workqueue.c:3278
#2: ffff8880389060d8 (&type->s_umount_key#33){++++}-{4:4}, at: super_trylock_shared+0x1e/0xf0 fs/super.c:565
#3: ffff888038904c18 (&sbi->s_writepages_rwsem){++++}-{0:0}, at: do_writepages+0x278/0x600 mm/page-writeback.c:2575
#4: ffff888038902938 (jbd2_handle){++++}-{0:0}, at: start_this_handle+0xfaa/0x13a0 fs/jbd2/transaction.c:444
#5: ffff88807928f290 (&ei->i_data_sem){++++}-{4:4}, at: ext4_map_blocks+0x45a/0xd30 fs/ext4/inode.c:823
2 locks held by getty/5383:
#0: ffff88802dace0a0 (&tty->ldisc_sem){++++}-{0:0}, at: tty_ldisc_ref_wait+0x24/0x80 drivers/tty/tty_ldisc.c:243
#1: ffffc9000322b2e8 (&ldata->atomic_read_lock){+.+.}-{4:4}, at: n_tty_read+0x419/0x14f0 drivers/tty/n_tty.c:2211
3 locks held by syz.0.189/6070:
#0: ffff88807e006bc8 (vm_lock){++++}-{0:0}, at: lock_vma_under_rcu+0x11d/0x590 mm/mmap_lock.c:310
#1: ffff888038906508 (sb_pagefaults){.+.+}-{0:0}, at: do_page_mkwrite+0x17a/0x440 mm/memory.c:3668
#2: ffff888038902938 (jbd2_handle){++++}-{0:0}, at: start_this_handle+0xfaa/0x13a0 fs/jbd2/transaction.c:444
3 locks held by syz-executor/6077:
#0: ffff888038906410 (sb_writers#4){.+.+}-{0:0}, at: filename_create+0x10d/0x400 fs/namei.c:4943
#1: ffff8880792b1f98 (&type->i_mutex_dir_key#3/1){+.+.}-{4:4}, at: inode_lock_nested include/linux/fs.h:1074 [inline]
#1: ffff8880792b1f98 (&type->i_mutex_dir_key#3/1){+.+.}-{4:4}, at: __start_dirop fs/namei.c:2914 [inline]
#1: ffff8880792b1f98 (&type->i_mutex_dir_key#3/1){+.+.}-{4:4}, at: start_dirop fs/namei.c:2938 [inline]
#1: ffff8880792b1f98 (&type->i_mutex_dir_key#3/1){+.+.}-{4:4}, at: filename_create+0x1c0/0x400 fs/namei.c:4950
#2: ffff888038902938 (jbd2_handle){++++}-{0:0}, at: start_this_handle+0xfaa/0x13a0 fs/jbd2/transaction.c:444
=============================================
NMI backtrace for cpu 0
CPU: 0 UID: 0 PID: 30 Comm: khungtaskd Not tainted syzkaller #0 PREEMPT(full)
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 04/18/2026
Call Trace:
<TASK>
__dump_stack lib/dump_stack.c:94 [inline]
dump_stack_lvl+0x100/0x190 lib/dump_stack.c:120
nmi_cpu_backtrace.cold+0x12d/0x151 lib/nmi_backtrace.c:113
nmi_trigger_cpumask_backtrace+0x1d7/0x230 lib/nmi_backtrace.c:62
trigger_all_cpu_backtrace include/linux/nmi.h:162 [inline]
__sys_info lib/sys_info.c:157 [inline]
sys_info+0x141/0x190 lib/sys_info.c:165
check_hung_uninterruptible_tasks kernel/hung_task.c:353 [inline]
watchdog+0xcb1/0x1030 kernel/hung_task.c:561
kthread+0x370/0x450 kernel/kthread.c:436
ret_from_fork+0x72b/0xd50 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
</TASK>
Sending NMI from CPU 0 to CPUs 1:
NMI backtrace for cpu 1
CPU: 1 UID: 0 PID: 1057 Comm: kworker/u8:7 Not tainted syzkaller #0 PREEMPT(full)
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 04/18/2026
Workqueue: events_unbound toggle_allocation_gate
RIP: 0010:smp_call_function_many_cond+0xdd4/0x1700 kernel/smp.c:871
Code: 00 00 0f 84 1e 05 00 00 e8 c9 6a 0c 00 83 c5 01 bf 07 00 00 00 48 63 dd 48 89 de e8 96 65 0c 00 48 83 fb 07 0f 86 22 fb ff ff <44> 8b 64 24 58 44 8b 7c 24 5c e8 9d 6a 0c 00 8b 5c 24 4c bf 01 00
RSP: 0018:ffffc90003d37870 EFLAGS: 00000246
RAX: 0000000000000000 RBX: 0000000000000001 RCX: ffffffff81fb686f
RDX: 0000000000000000 RSI: 0000000000000000 RDI: ffff888029b83d80
RBP: 0000000000000000 R08: 0000000000000007 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000000 R12: ffffc90003d37958
R13: ffff8880b843c6c0 R14: ffff8880b843c601 R15: ffff8880b853c710
FS: 0000000000000000(0000) GS:ffff88812447d000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 000055a400fed660 CR3: 000000000e596000 CR4: 00000000003526f0
Call Trace:
<TASK>
on_each_cpu_cond_mask+0x40/0x90 kernel/smp.c:1057
on_each_cpu include/linux/smp.h:72 [inline]
smp_text_poke_sync_each_cpu arch/x86/kernel/alternative.c:2773 [inline]
smp_text_poke_batch_finish+0x976/0xc60 arch/x86/kernel/alternative.c:3045
arch_jump_label_transform_apply+0x1c/0x30 arch/x86/kernel/jump_label.c:146
jump_label_update+0x37a/0x550 kernel/jump_label.c:910
static_key_enable_cpuslocked+0x1bc/0x270 kernel/jump_label.c:210
static_key_enable+0x1a/0x20 kernel/jump_label.c:223
toggle_allocation_gate mm/kfence/core.c:906 [inline]
toggle_allocation_gate+0xfe/0x2d0 mm/kfence/core.c:898
process_one_work+0xa0e/0x1980 kernel/workqueue.c:3302
process_scheduled_works kernel/workqueue.c:3385 [inline]
worker_thread+0x5ef/0xe50 kernel/workqueue.c:3466
kthread+0x370/0x450 kernel/kthread.c:436
ret_from_fork+0x72b/0xd50 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
</TASK>
----------------
Code disassembly (best guess):
0: 00 00 add %al,(%rax)
2: 0f 84 1e 05 00 00 je 0x526
8: e8 c9 6a 0c 00 call 0xc6ad6
d: 83 c5 01 add $0x1,%ebp
10: bf 07 00 00 00 mov $0x7,%edi
15: 48 63 dd movslq %ebp,%rbx
18: 48 89 de mov %rbx,%rsi
1b: e8 96 65 0c 00 call 0xc65b6
20: 48 83 fb 07 cmp $0x7,%rbx
24: 0f 86 22 fb ff ff jbe 0xfffffb4c
* 2a: 44 8b 64 24 58 mov 0x58(%rsp),%r12d <-- trapping instruction
2f: 44 8b 7c 24 5c mov 0x5c(%rsp),%r15d
34: e8 9d 6a 0c 00 call 0xc6ad6
39: 8b 5c 24 4c mov 0x4c(%rsp),%ebx
3d: bf .byte 0xbf
3e: 01 00 add %eax,(%rax)
---
If you want syzbot to run the reproducer, reply with:
#syz test: git://repo/address.git branch-or-commit-hash
If you attach or paste a git patch, syzbot will apply it before testing.
^ permalink raw reply
* Re: [PATCH v3] generic/790: test post-EOF gap zeroing persistence
From: Zorro Lang @ 2026-05-01 16:46 UTC (permalink / raw)
To: Zhang Yi
Cc: fstests, linux-ext4, linux-fsdevel, bfoster, jack, yi.zhang,
yizhang089, yangerkun
In-Reply-To: <20260428085750.1072612-1-yi.zhang@huaweicloud.com>
On Tue, Apr 28, 2026 at 04:57:50PM +0800, Zhang Yi wrote:
> From: Zhang Yi <yi.zhang@huawei.com>
>
> Test that extending a file past a non-block-aligned EOF correctly
> zero-fills the gap [old_EOF, block_boundary), and that this zeroing
> persists through a filesystem shutdown+remount cycle.
>
> Stale data beyond EOF can persist on disk when append write data blocks
> are flushed before the on-disk file size update, or when concurrent
> append writeback and mmap writes persist non-zero data past EOF.
> Subsequent post-EOF operations (append write, fallocate, truncate up)
> must zero-fill and persist the gap to prevent exposing stale data.
>
> The test pollutes the file's last physical block (via FIEMAP + raw
> device write) with a sentinel pattern beyond i_size, then performs each
> extend operation and verifies the gap is zeroed both in memory and on
> disk.
>
> Signed-off-by: Zhang Yi <yi.zhang@huawei.com>
> ---
> v2->v3:
> - Add error check for the raw device pwrite, a failed pwrite would
> silently leave the test continuing with an unpolluted block,
> producing false-positive passes.
> - Add sync_range -a to wait until the extending I/O completes and to
> ensure file size update is persisted before shutdown, preventing
> unexpected file size errors.
> v1->v2:
> - Add _require_no_realtime to prevent testing on XFS realtime devices,
> where file data may reside on $SCRATCH_RTDEV.
> - Add _exclude_fs btrfs since FIEMAP returns logical addresses, not
> physical device offsets, writing to these offsets on $SCRATCH_DEV
> would corrupt the filesystem in multi-device setups. Besides, since
> btrfs doesn't support shutdown right now, we can support it later.
> - Add -v flag to od in _check_gap_zero() to prevent line folding of
> identical consecutive lines.
> - Add expected_new_sz parameter to _test_eof_zeroing(), verify file
> size was not rolled back after shutdown+remount cycle, and also drop
> the unnecessary file size check before the shutdown as well.
> - Clarify the comment regarding when stale data beyond EOF can persist.
>
> tests/generic/790 | 168 ++++++++++++++++++++++++++++++++++++++++++
> tests/generic/790.out | 4 +
> 2 files changed, 172 insertions(+)
> create mode 100755 tests/generic/790
> create mode 100644 tests/generic/790.out
>
> diff --git a/tests/generic/790 b/tests/generic/790
> new file mode 100755
> index 00000000..6daf3793
> --- /dev/null
> +++ b/tests/generic/790
> @@ -0,0 +1,168 @@
> +#! /bin/bash
> +# SPDX-License-Identifier: GPL-2.0
> +# Copyright (c) 2026 Huawei. All Rights Reserved.
> +#
> +# FS QA Test No. 790
> +#
> +# Test that extending a file past a non-block-aligned EOF correctly zero-fills
> +# the gap [old_EOF, block_boundary), and that this zeroing persists through a
> +# filesystem shutdown+remount cycle.
> +#
> +# Stale data beyond EOF can persist on disk when:
> +# 1) append write data blocks are flushed before the on-disk file size update,
> +# and the system crashes in this window.
> +# 2) concurrent append writeback and mmap writes persist non-zero data past EOF.
> +#
> +# Subsequent post-EOF operations (append write, fallocate, truncate up) must
> +# zero-fill and persist the gap to prevent exposing stale data.
> +#
> +# The test pollutes the file's last physical block (via FIEMAP + raw device
> +# write) with a sentinel pattern beyond i_size, then performs each extend
> +# operation and verifies the gap is zeroed both in memory and on disk.
> +#
> +. ./common/preamble
> +_begin_fstest auto quick rw shutdown
> +
> +. ./common/filter
> +
> +_require_scratch
> +_require_block_device $SCRATCH_DEV
> +_require_no_realtime
> +_require_scratch_shutdown
> +_require_metadata_journaling $SCRATCH_DEV
> +
> +# FIEMAP on Btrfs returns logical addresses within the filesystem's address
> +# space, not physical device offsets. Writing to these offsets on $SCRATCH_DEV
> +# would corrupt the filesystem in multi-device setups.
> +_exclude_fs btrfs
> +
> +_require_xfs_io_command "fiemap"
> +_require_xfs_io_command "falloc"
Add "fiemap" and "prealloc" groups, I'll do that when I merge it.
> +_require_xfs_io_command "pwrite"
> +_require_xfs_io_command "truncate"
> +_require_xfs_io_command "sync_range"
> +
> +# Check that gap region [offset, offset+nbytes) is entirely zero
> +_check_gap_zero()
> +{
> + local file="$1"
> + local offset="$2"
> + local nbytes="$3"
> + local label="$4"
> + local data
> + local stripped
> +
> + data=$(od -A n -t x1 -v -j $offset -N $nbytes "$file" 2>/dev/null)
> +
> + # Remove whitespace and check if any byte is non-zero
> + stripped=$(printf '%s' "$data" | tr -d ' \n\t')
> + if [ -n "$stripped" ] && ! echo "$stripped" | grep -qE "^0+$"; then
> + echo "FAIL: non-zero data in gap [$offset,$((offset + nbytes))) $label"
> + _hexdump -N $((offset + nbytes)) "$file"
> + return 1
> + fi
> + return 0
> +}
> +
> +# Get the physical block offset (in bytes) of the file's first block on device
> +_get_phys_offset()
> +{
> + local file="$1"
> + local fiemap_output
> + local phys_blk
> +
> + fiemap_output=$($XFS_IO_PROG -r -c "fiemap -v" "$file" 2>/dev/null)
> + phys_blk=$(echo "$fiemap_output" | _filter_xfs_io_fiemap | head -1 | awk '{print $3}')
> + if [ -z "$phys_blk" ]; then
> + echo ""
> + return
> + fi
> + # Convert 512-byte blocks to bytes
> + echo $((phys_blk * 512))
> +}
> +
> +_test_eof_zeroing()
> +{
> + local test_name="$1"
> + local extend_cmd="$2"
> + local expected_new_sz="$3"
> + local file=$SCRATCH_MNT/testfile_${test_name}
> +
> + echo "$test_name" | tee -a $seqres.full
> +
> + # Compute non-block-aligned EOF offset
> + local gap_bytes=16
> + local eof_offset=$((blksz - gap_bytes))
> +
> + # Step 1: Write one full block to ensure the filesystem allocates a
> + # physical block for the file instead of using inline data.
> + $XFS_IO_PROG -f -c "pwrite -S 0x5a 0 $blksz" -c fsync \
> + "$file" >> $seqres.full 2>&1
> +
> + # Step 2: Get physical block offset on device via FIEMAP
> + local phys_offset
> + phys_offset=$(_get_phys_offset "$file")
> + if [ -z "$phys_offset" ]; then
> + _fail "$test_name: failed to get physical block offset via fiemap"
> + fi
> +
> + # Step 3: Truncate file to non-block-aligned size and fsync.
> + # The on-disk region [eof_offset, blksz) may or may not be
> + # zeroed by the filesystem at this point.
> + $XFS_IO_PROG -c "truncate $eof_offset" -c fsync \
> + "$file" >> $seqres.full 2>&1
> +
> + # Step 4: Unmount and restore the physical block to all-0x5a on disk.
> + # This bypasses the kernel's pagecache EOF-zeroing to ensure
> + # the stale pattern is present on disk. Then remount.
> + _scratch_unmount
> + $XFS_IO_PROG -d -c "pwrite -S 0x5a $phys_offset $blksz" \
> + $SCRATCH_DEV >> $seqres.full 2>&1
> + if [ $? -ne 0 ]; then
> + _fail "$test_name: failed to inject stale data on disk"
> + fi
> + _scratch_mount >> $seqres.full 2>&1
> +
> + # Step 5: Execute the extend operation.
> + $XFS_IO_PROG -c "$extend_cmd" "$file" >> $seqres.full 2>&1
> +
> + # Step 6: Verify gap [eof_offset, blksz) is zeroed BEFORE shutdown
> + _check_gap_zero "$file" $eof_offset $gap_bytes "before shutdown" || return 1
> +
> + # Step 7: Sync the extended range and shutdown the filesystem with
> + # journal flush. This persists the file size extending, and
> + # the filesystem should persist the zeroed data in the gap
> + # range as well.
> + if [ "$extend_cmd" != "${extend_cmd#pwrite}" ]; then
> + $XFS_IO_PROG -c "sync_range -w $blksz $blksz" \
> + -c "sync_range -a $blksz $blksz" \
> + "$file" >> $seqres.full 2>&1
> + fi
> + _scratch_shutdown -f
Wow, the usage of sync_range in step#7 is impressive !
Reviewed-by: Zorro Lang <zlang@kernel.org>
> +
> + # Step 8: Remount and verify gap is still zeroed
> + _scratch_cycle_mount
> +
> + # Verify file size was not rolled back after shutdown+remount
> + local sz
> + sz=$(stat -c %s "$file")
> + if [ "$sz" -ne "$expected_new_sz" ]; then
> + _fail "$test_name: file size rolled back after shutdown+remount: $sz != $expected_new_sz"
> + fi
> +
> + _check_gap_zero "$file" $eof_offset $gap_bytes "after shutdown+remount" || return 1
> +}
> +
> +_scratch_mkfs >> $seqres.full 2>&1
> +_scratch_mount
> +
> +blksz=$(_get_block_size $SCRATCH_MNT)
> +
> +# Test three variants of EOF-extending operations
> +_test_eof_zeroing "append_write" "pwrite -S 0x42 $blksz $blksz" $((blksz * 2))
> +_test_eof_zeroing "truncate_up" "truncate $((blksz * 2))" $((blksz * 2))
> +_test_eof_zeroing "fallocate" "falloc $blksz $blksz" $((blksz * 2))
> +
> +# success, all done
> +status=0
> +exit
> diff --git a/tests/generic/790.out b/tests/generic/790.out
> new file mode 100644
> index 00000000..e5e2cc09
> --- /dev/null
> +++ b/tests/generic/790.out
> @@ -0,0 +1,4 @@
> +QA output created by 790
> +append_write
> +truncate_up
> +fallocate
> --
> 2.52.0
>
^ permalink raw reply
* [RFC] ext4: possible inconsistency in ext4_append() error path
From: Vineet Agarwal @ 2026-05-01 17:25 UTC (permalink / raw)
To: tytso
Cc: linux-ext4, adilger.kernel, libaokun, jack, ojaswin, ritesh.list,
yi.zhang, linux-kernel, Vineet Agarwal
Hi,
While looking into ext4 directory operations, I noticed a possible
inconsistency in the error handling of ext4_append().
In ext4_append(), the inode size is updated before all failure points
have been ruled out:
bh = ext4_bread(handle, inode, *block, EXT4_GET_BLOCKS_CREATE);
if (IS_ERR(bh))
return bh;
inode->i_size += inode->i_sb->s_blocksize;
EXT4_I(inode)->i_disksize = inode->i_size;
err = ext4_mark_inode_dirty(handle, inode);
if (err)
goto out;
err = ext4_journal_get_write_access(handle, inode->i_sb, bh,
EXT4_JTR_NONE);
if (err)
goto out;
If either ext4_mark_inode_dirty() or
ext4_journal_get_write_access() fails, the function returns an
error but does not restore the original inode size.
Callers of ext4_append() appear to treat it as an all-or-nothing
operation:
bh = ext4_append(handle, dir, &block);
if (IS_ERR(bh))
goto out;
However, in the failure case, inode->i_size may already have been
increased.
One possible consequence is that subsequent checks relying on i_size,
such as:
if (block >= inode->i_size >> inode->i_blkbits)
may allow a block index to pass bounds checks even though the append
operation did not complete successfully.
I understand that journaling may ensure on-disk consistency, but the
in-memory inode state may still temporarily reflect a change that did
not logically succeed.
Is this behavior intentional, or should ext4_append() avoid updating
i_size until after all failure points, or roll it back on error?
Thanks,
Vineet Agarwal
^ permalink raw reply
* Re: [PATCH 02/13] mount_service: add systemd socket service mounting helper
From: Darrick J. Wong @ 2026-05-01 17:35 UTC (permalink / raw)
To: bernd; +Cc: linux-fsdevel, fuse-devel, linux-ext4, miklos, neal, joannelkoong
In-Reply-To: <177758363568.1314717.5220084842430554136.stgit@frogsfrogsfrogs>
On Thu, Apr 30, 2026 at 02:15:32PM -0700, Darrick J. Wong wrote:
> From: Darrick J. Wong <djwong@kernel.org>
>
> Create a mount helper program that can start a fuse server that runs as
> a socket-based systemd service, and a new libfuse module to wrap all the
> details of communicating between the mount helper and the containerized
> fuse server.
>
> This enables untrusted ext4 mounts via systemd service containers, which
> avoids the problem of malicious filesystems compromising the integrity
> of the running kernel through memory corruption.
>
> In theory this could also be supported via inetd and clones, though the
> author hasn't found one that supports AF_UNIX sockets.
>
> Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
> ---
> include/fuse_service.h | 243 ++++
> include/fuse_service_priv.h | 160 ++
> lib/mount_common_i.h | 3
> util/mount_service.h | 40 +
> .github/workflows/install-ubuntu-dependencies.sh | 4
> doc/fuservicemount3.8 | 24
> doc/meson.build | 3
> include/meson.build | 4
> lib/fuse_service.c | 1233 +++++++++++++++++++
> lib/fuse_service_stub.c | 106 ++
> lib/fuse_versionscript | 17
> lib/helper.c | 51 +
> lib/meson.build | 17
> lib/mount.c | 12
> meson.build | 34 +
> meson_options.txt | 9
> util/fuservicemount.c | 18
> util/meson.build | 9
> util/mount_service.c | 1427 ++++++++++++++++++++++
> 19 files changed, 3412 insertions(+), 2 deletions(-)
> create mode 100644 include/fuse_service.h
> create mode 100644 include/fuse_service_priv.h
> create mode 100644 util/mount_service.h
> create mode 100644 doc/fuservicemount3.8
> create mode 100644 lib/fuse_service.c
> create mode 100644 lib/fuse_service_stub.c
> create mode 100644 util/fuservicemount.c
> create mode 100644 util/mount_service.c
>
>
> diff --git a/include/fuse_service.h b/include/fuse_service.h
> new file mode 100644
> index 00000000000000..7e4c204e7a70bf
> --- /dev/null
> +++ b/include/fuse_service.h
> @@ -0,0 +1,243 @@
> +/*
> + * FUSE: Filesystem in Userspace
> + * Copyright (C) 2025-2026 Oracle.
> + * Author: Darrick J. Wong <djwong@kernel.org>
> + *
> + * This program can be distributed under the terms of the GNU LGPLv2.
> + * See the file LGPL2.txt.
> + */
> +#ifndef FUSE_SERVICE_H_
> +#define FUSE_SERVICE_H_
> +
> +/** @file
> + *
> + * Low level API
> + *
> + * IMPORTANT: you should define FUSE_USE_VERSION before including this
> + * header. To use the newest API define it to 319 (recommended for any
> + * new application).
> + */
> +
> +#ifndef FUSE_USE_VERSION
> +#error FUSE_USE_VERSION not defined
> +#endif
> +
> +#include "fuse_common.h"
> +
> +#ifdef __cplusplus
> +extern "C" {
> +#endif
> +
> +#if FUSE_MAKE_VERSION(3, 19) <= FUSE_USE_VERSION
> +
> +struct fuse_service;
> +
> +/**
> + * Accept a socket created by mount.service for information exchange.
> + *
> + * @param sfp pointer to pointer to a service context. The pointer will always
> + * be initialized by this function; use fuse_service_accepted to
> + * find out if the fuse server is actually running as a service.
> + * @return 0 on success, or negative errno on failure
> + */
> +int fuse_service_accept(struct fuse_service **sfp);
> +
> +/**
> + * Has the fuse server accepted a service context?
> + *
> + * @param sf service context
> + * @return true if it has, false if not
> + */
> +static inline bool fuse_service_accepted(struct fuse_service *sf)
> +{
> + return sf != NULL;
> +}
> +
> +/**
> + * Will the mount service helper accept the allow_other option?
> + *
> + * @param sf service context
> + * @return true if it has, false if not
> + */
> +bool fuse_service_can_allow_other(struct fuse_service *sf);
> +
> +/**
> + * Release all resources associated with the service context.
> + *
> + * @param sfp service context
> + */
> +void fuse_service_release(struct fuse_service *sf);
> +
> +/**
> + * Destroy a service context and release all resources
> + *
> + * @param sfp pointer to pointer to a service context
> + */
> +void fuse_service_destroy(struct fuse_service **sfp);
> +
> +/**
> + * Append the command line arguments from the mount service helper to an
> + * existing fuse_args structure. The fuse_args should have been initialized
> + * with the argc and argv passed to main().
> + *
> + * @param sfp service context
> + * @param args arguments to modify (input+output)
> + * @return 0 on success, or negative errno on failure
> + */
> +int fuse_service_append_args(struct fuse_service *sf, struct fuse_args *args);
> +
> +/**
> + * Generate the effective fuse server command line from the args structure.
> + * The args structure should be the outcome from fuse_service_append_args.
> + * The resulting string is suitable for setproctitle and must be freed by the
> + * callre.
> + *
> + * @param argc argument count passed to main()
> + * @param argv argument vector passed to main()
> + * @param args fuse args structure
> + * @return effective command line string, or NULL
> + */
> +char *fuse_service_cmdline(int argc, char *argv[], struct fuse_args *args);
> +
> +struct fuse_cmdline_opts;
> +
> +/**
> + * Utility function to parse common options for simple file systems
> + * using the low-level API. A help text that describes the available
> + * options can be printed with `fuse_cmdline_help`. A single
> + * non-option argument is treated as the mountpoint. Multiple
> + * non-option arguments will result in an error.
> + *
> + * If neither -o subtype= or -o fsname= options are given, a new
> + * subtype option will be added and set to the basename of the program
> + * (the fsname will remain unset, and then defaults to "fuse").
> + *
> + * Known options will be removed from *args*, unknown options will
> + * remain. The mountpoint will not be checked here; that is the job of
> + * mount.service.
> + *
> + * @param args argument vector (input+output)
> + * @param opts output argument for parsed options
> + * @return 0 on success, -1 on failure
> + */
> +int fuse_service_parse_cmdline_opts(struct fuse_args *args,
> + struct fuse_cmdline_opts *opts);
> +
> +/**
> + * Don't complain if this file cannot be opened.
> + */
> +#define FUSE_SERVICE_REQUEST_FILE_QUIET (1U << 0)
> +
> +/**
> + * Ask the mount.service helper to open a file on behalf of the fuse server.
> + *
> + * @param sf service context
> + * @param path the path to file
> + * @param open_flags O_ flags
> + * @param create_mode mode with which to create the file
> + * @param request_flags set of FUSE_SERVICE_REQUEST_* flags
> + * @return 0 on success, or negative errno on failure
> + */
> +int fuse_service_request_file(struct fuse_service *sf, const char *path,
> + int open_flags, mode_t create_mode,
> + unsigned int request_flags);
> +
> +/**
> + * Ask the mount.service helper to open a block device on behalf of the fuse
> + * server.
> + *
> + * @param sf service context
> + * @param path the path to file
> + * @param open_flags O_ flags
> + * @param create_mode mode with which to create the file
> + * @param request_flags set of FUSE_SERVICE_REQUEST_* flags
> + * @param block_size set the block device block size to this value
> + * @return 0 on success, or negative errno on failure
> + */
> +int fuse_service_request_blockdev(struct fuse_service *sf, const char *path,
> + int open_flags, mode_t create_mode,
> + unsigned int request_flags,
> + unsigned int block_size);
> +
> +/**
> + * Receive a file previously requested.
> + *
> + * @param sf service context
> + * @param path to file
> + * @fdp pointer to file descriptor, which will be set a non-negative file
> + * descriptor value on success, or negative errno on failure
> + * @return 0 on success, or negative errno on socket communication failure
> + */
> +int fuse_service_receive_file(struct fuse_service *sf,
> + const char *path, int *fdp);
> +
> +/**
> + * Prevent the mount.service server from sending us any more open files.
> + *
> + * @param sf service context
> + * @return 0 on success, or negative errno on failure
> + */
> +int fuse_service_finish_file_requests(struct fuse_service *sf);
> +
> +/**
> + * Require that the filesystem mount point have the expected file format
> + * (S_IFDIR/S_IFREG). Can be overridden when calling
> + * fuse_service_session_mount.
> + *
> + * @param sf service context
> + * @param expected_fmt expected mode (S_IFDIR/S_IFREG) for mount point, or 0
> + * to skip checks
> + */
> +void fuse_service_expect_mount_format(struct fuse_service *sf,
> + mode_t expected_fmt);
> +
> +/**
> + * Bind a FUSE file system to the fuse session inside a fuse service process,
> + * then ask the mount.service helper to mount the filesystem for us. The fuse
> + * client will begin sending requests to the fuse server immediately after
> + * this. Do not call fuse_daemonize() when running as a fuse service.
> + *
> + * @param sf service context
> + * @param se fuse session
> + * @param expected_fmt expected mode (S_IFDIR/S_IFREG) for mount point, or 0
> + * to skip checks
> + * @param opts command line options
> + * @return 0 on success, or negative errno on failure
> + */
> +int fuse_service_session_mount(struct fuse_service *sf, struct fuse_session *se,
> + mode_t expected_fmt,
> + struct fuse_cmdline_opts *opts);
> +
> +/**
> + * Ask the mount helper to unmount th e filesystem.
> + *
> + * @param sf service context
> + * @return 0 on success, or negative errno on failure
> + */
> +int fuse_service_session_unmount(struct fuse_service *sf);
> +
> +/**
> + * Bid farewell to the mount.service helper. It is still necessary to call
> + * fuse_service_destroy after this.
> + *
> + * @param sf service context
> + * @param exitcode fuse server process exit status
> + * @return 0 on success, or negative errno on failure
> + */
> +int fuse_service_send_goodbye(struct fuse_service *sf, int exitcode);
> +
> +/**
> + * Exit routine for a fuse server running as a systemd service.
> + *
> + * @param ret 0 for success, nonzero for service failure.
> + * @return a value to be passed to exit() or returned from main
> + */
> +int fuse_service_exit(int ret);
> +
> +#endif /* FUSE_USE_VERSION >= FUSE_MAKE_VERSION(3, 19) */
> +
> +#ifdef __cplusplus
> +}
> +#endif
> +
> +#endif /* FUSE_SERVICE_H_ */
> diff --git a/include/fuse_service_priv.h b/include/fuse_service_priv.h
> new file mode 100644
> index 00000000000000..a3773d90c7db7e
> --- /dev/null
> +++ b/include/fuse_service_priv.h
> @@ -0,0 +1,160 @@
> +/*
> + * FUSE: Filesystem in Userspace
> + * Copyright (C) 2025-2026 Oracle.
> + * Author: Darrick J. Wong <djwong@kernel.org>
> + *
> + * This program can be distributed under the terms of the GNU LGPLv2.
> + * See the file LGPL2.txt.
> + */
> +#ifndef FUSE_SERVICE_PRIV_H_
> +#define FUSE_SERVICE_PRIV_H_
> +
> +/* All numeric fields are network order (big-endian) when going across the socket */
> +
> +struct fuse_service_memfd_arg {
> + uint32_t pos;
> + uint32_t len;
> +};
> +
> +struct fuse_service_memfd_argv {
> + uint32_t magic;
> + uint32_t argc;
> +};
> +
> +#define FUSE_SERVICE_MAX_CMD_SIZE (65536)
> +
> +#define FUSE_SERVICE_ARGS_MAGIC 0x41524753 /* ARGS */
> +
> +/* mount.service sends a hello to the server and it replies */
> +#define FUSE_SERVICE_HELLO_CMD 0x53414654 /* SAFT */
> +#define FUSE_SERVICE_HELLO_REPLY 0x4c415354 /* LAST */
> +
> +/* fuse servers send commands to mount.service */
> +#define FUSE_SERVICE_OPEN_CMD 0x4f50454e /* OPEN */
> +#define FUSE_SERVICE_OPEN_BDEV_CMD 0x42444556 /* BDEV */
> +#define FUSE_SERVICE_FSOPEN_CMD 0x54595045 /* TYPE */
> +#define FUSE_SERVICE_SOURCE_CMD 0x4e414d45 /* NAME */
> +#define FUSE_SERVICE_MNTOPTS_CMD 0x4f505453 /* OPTS */
> +#define FUSE_SERVICE_MNTPT_CMD 0x4d4e5450 /* MNTP */
> +#define FUSE_SERVICE_MOUNT_CMD 0x444f4954 /* DOIT */
> +#define FUSE_SERVICE_UNMOUNT_CMD 0x554d4e54 /* UMNT */
> +#define FUSE_SERVICE_BYE_CMD 0x42594545 /* BYEE */
> +
> +/* mount.service sends replies to the fuse server */
> +#define FUSE_SERVICE_OPEN_REPLY 0x46494c45 /* FILE */
> +#define FUSE_SERVICE_SIMPLE_REPLY 0x5245504c /* REPL */
> +
> +struct fuse_service_packet {
> + uint32_t magic; /* FUSE_SERVICE_*_{CMD,REPLY} */
> +};
> +
> +#define FUSE_SERVICE_PROTO (1)
> +#define FUSE_SERVICE_MIN_PROTO (1)
> +#define FUSE_SERVICE_MAX_PROTO (1)
> +
> +#define FUSE_SERVICE_FLAG_ALLOW_OTHER (1U << 0)
> +
> +#define FUSE_SERVICE_FLAGS (FUSE_SERVICE_FLAG_ALLOW_OTHER)
> +
> +struct fuse_service_hello {
> + struct fuse_service_packet p;
> + uint16_t min_version;
> + uint16_t max_version;
> + uint32_t flags;
> +};
> +
> +static inline bool check_null_endbyte(const void *p, size_t psz)
> +{
> + return *((const char *)p + psz - 1) == 0;
> +}
> +
> +struct fuse_service_hello_reply {
> + struct fuse_service_packet p;
> + uint16_t version;
> + uint16_t padding;
> +};
> +
> +struct fuse_service_simple_reply {
> + struct fuse_service_packet p;
> + uint32_t error; /* positive errno */
> +};
> +
> +struct fuse_service_requested_file {
> + struct fuse_service_packet p;
> + uint32_t error; /* positive errno */
> + char path[];
> +};
> +
> +static inline size_t sizeof_fuse_service_requested_file(size_t pathlen)
> +{
> + return sizeof(struct fuse_service_requested_file) + pathlen + 1;
> +}
> +
> +#define FUSE_SERVICE_FSOPEN_FUSEBLK (1U << 0)
> +#define FUSE_SERVICE_FSOPEN_FLAGS (FUSE_SERVICE_FSOPEN_FUSEBLK)
> +
> +struct fuse_service_fsopen_command {
> + struct fuse_service_packet p;
> + uint32_t fsopen_flags;
> +};
> +
> +#define FUSE_SERVICE_OPEN_QUIET (1U << 0)
> +#define FUSE_SERVICE_OPEN_FLAGS (FUSE_SERVICE_OPEN_QUIET)
> +
> +struct fuse_service_open_command {
> + struct fuse_service_packet p;
> + uint32_t open_flags;
> + uint32_t create_mode;
> + uint32_t request_flags;
> + uint32_t block_size;
> + char path[];
> +};
> +
> +static inline size_t sizeof_fuse_service_open_command(size_t pathlen)
> +{
> + return sizeof(struct fuse_service_open_command) + pathlen + 1;
> +}
> +
> +struct fuse_service_string_command {
> + struct fuse_service_packet p;
> + char value[];
> +};
> +
> +static inline size_t sizeof_fuse_service_string_command(size_t len)
> +{
> + return sizeof(struct fuse_service_string_command) + len + 1;
> +}
> +
> +struct fuse_service_mountpoint_command {
> + struct fuse_service_packet p;
> + uint16_t expected_fmt;
> + uint16_t padding;
> + char value[];
> +};
> +
> +static inline size_t sizeof_fuse_service_mountpoint_command(size_t len)
> +{
> + return sizeof(struct fuse_service_mountpoint_command) + len + 1;
> +}
> +
> +struct fuse_service_bye_command {
> + struct fuse_service_packet p;
> + uint32_t exitcode;
> +};
> +
> +struct fuse_service_mount_command {
> + struct fuse_service_packet p;
> + uint32_t ms_flags;
> +};
> +
> +struct fuse_service_unmount_command {
> + struct fuse_service_packet p;
> +};
> +
> +int fuse_parse_cmdline_service(struct fuse_args *args,
> + struct fuse_cmdline_opts *opts);
> +
> +#define FUSE_SERVICE_ARGV "argv"
> +#define FUSE_SERVICE_FUSEDEV "fusedev"
> +
> +#endif /* FUSE_SERVICE_PRIV_H_ */
> diff --git a/lib/mount_common_i.h b/lib/mount_common_i.h
> index 6bcb055ff1c23f..631dff3e6f8aaf 100644
> --- a/lib/mount_common_i.h
> +++ b/lib/mount_common_i.h
> @@ -14,5 +14,8 @@ struct mount_opts;
>
> char *fuse_mnt_build_source(const struct mount_opts *mo);
> char *fuse_mnt_build_type(const struct mount_opts *mo);
> +char *fuse_mnt_kernel_opts(const struct mount_opts *mo);
> +unsigned int fuse_mnt_flags(const struct mount_opts *mo);
> +
>
> #endif /* FUSE_MOUNT_COMMON_I_H_ */
> diff --git a/util/mount_service.h b/util/mount_service.h
> new file mode 100644
> index 00000000000000..a0b952a15dacf3
> --- /dev/null
> +++ b/util/mount_service.h
> @@ -0,0 +1,40 @@
> +/*
> + * FUSE: Filesystem in Userspace
> + * Copyright (C) 2025-2026 Oracle.
> + * Author: Darrick J. Wong <djwong@kernel.org>
> + *
> + * This program can be distributed under the terms of the GNU GPLv2.
> + * See the file GPL2.txt.
> + */
> +#ifndef MOUNT_SERVICE_H_
> +#define MOUNT_SERVICE_H_
> +
> +/**
> + * Magic value that means that we couldn't connect to the mount service,
> + * so the caller should try to fall back to traditional means.
> + */
> +#define MOUNT_SERVICE_FALLBACK_NEEDED (2)
> +
> +/**
> + * Connect to a fuse service socket and try to mount the filesystem as
> + * specified with the CLI arguments.
> + *
> + * @argc argument count
> + * @argv vector of argument strings
> + * @return EXIT_SUCCESS for success, EXIT_FAILURE if mount fails, or
> + * MOUNT_SERVICE_FALLBACK_NEEDED if no service is available.
> + */
> +int mount_service_main(int argc, char *argv[]);
> +
> +/**
> + * Return the fuse filesystem subtype from a full fuse filesystem type
> + * specification. IOWs, fuse.Y -> Y; fuseblk.Z -> Z; or A -> A. The returned
> + * pointer is within the caller's string. The subtype must not contain a path
> + * separator.
> + *
> + * @param fstype full fuse filesystem type
> + * @return fuse subtype
> + */
> +const char *mount_service_subtype(const char *fstype);
> +
> +#endif /* MOUNT_SERVICE_H_ */
> diff --git a/.github/workflows/install-ubuntu-dependencies.sh b/.github/workflows/install-ubuntu-dependencies.sh
> index 0eb7e610729b7c..9f6e69701438f3 100755
> --- a/.github/workflows/install-ubuntu-dependencies.sh
> +++ b/.github/workflows/install-ubuntu-dependencies.sh
> @@ -15,6 +15,8 @@ PACKAGES_CORE=(
> pkg-config
> python3
> python3-pip
> + libsystemd-dev
> + systemd-dev
> )
>
> PACKAGES_FULL=(
> @@ -31,6 +33,8 @@ PACKAGES_FULL=(
> libudev-dev:i386
> pkg-config:i386
> python3-pytest
> + libsystemd-dev
> + systemd-dev
> )
>
> PACKAGES_CODECHECKER=(
> diff --git a/doc/fuservicemount3.8 b/doc/fuservicemount3.8
> new file mode 100644
> index 00000000000000..e45d6a89c8b81a
> --- /dev/null
> +++ b/doc/fuservicemount3.8
> @@ -0,0 +1,24 @@
> +.TH fuservicemount3 "8"
> +.SH NAME
> +fuservicemount3 \- mount a FUSE filesystem that runs as a system socket service
> +.SH SYNOPSIS
> +.B fuservicemount3
> +.B source
> +.B mountpoint
> +.BI -t " fstype"
> +[
> +.I options
> +]
> +.SH DESCRIPTION
> +Mount a filesystem using a FUSE server that runs as a socket service.
> +These servers can be contained using the platform's service management
> +framework.
> +.SH "AUTHORS"
> +.LP
> +The author of the fuse socket service code is Darrick J. Wong <djwong@kernel.org>.
> +Debian GNU/Linux distribution.
> +.SH SEE ALSO
> +.BR fusermount3 (1)
> +.BR fusermount (1)
> +.BR mount (8)
> +.BR fuse (4)
> diff --git a/doc/meson.build b/doc/meson.build
> index db3e0b26f71975..c105cf3471fdf4 100644
> --- a/doc/meson.build
> +++ b/doc/meson.build
> @@ -2,3 +2,6 @@ if not platform.endswith('bsd') and platform != 'dragonfly'
> install_man('fusermount3.1', 'mount.fuse3.8')
> endif
>
> +if private_cfg.get('HAVE_SERVICEMOUNT', false)
> + install_man('fuservicemount3.8')
> +endif
> diff --git a/include/meson.build b/include/meson.build
> index bf671977a5a6a9..da51180f87eea2 100644
> --- a/include/meson.build
> +++ b/include/meson.build
> @@ -1,4 +1,8 @@
> libfuse_headers = [ 'fuse.h', 'fuse_common.h', 'fuse_lowlevel.h',
> 'fuse_opt.h', 'cuse_lowlevel.h', 'fuse_log.h' ]
>
> +if private_cfg.get('HAVE_SERVICEMOUNT', false)
> + libfuse_headers += [ 'fuse_service.h' ]
> +endif
> +
> install_headers(libfuse_headers, subdir: 'fuse3')
> diff --git a/lib/fuse_service.c b/lib/fuse_service.c
> new file mode 100644
> index 00000000000000..ef512c76120a0f
> --- /dev/null
> +++ b/lib/fuse_service.c
> @@ -0,0 +1,1233 @@
> +/*
> + * FUSE: Filesystem in Userspace
> + * Copyright (C) 2025-2026 Oracle.
> + * Author: Darrick J. Wong <djwong@kernel.org>
> + *
> + * Library functions to support fuse servers that can be run as "safe" systemd
> + * containers.
> + *
> + * This program can be distributed under the terms of the GNU LGPLv2.
> + * See the file LGPL2.txt
> + */
> +
> +#define _GNU_SOURCE
> +#include <stdint.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <stdio.h>
> +#include <errno.h>
> +#include <sys/socket.h>
> +#include <sys/un.h>
> +#include <unistd.h>
> +#include <sys/stat.h>
> +#include <fcntl.h>
> +#include <systemd/sd-daemon.h>
> +#include <arpa/inet.h>
> +#include <limits.h>
> +
> +#include "fuse_config.h"
> +#include "fuse_i.h"
> +#include "fuse_service_priv.h"
> +#include "fuse_service.h"
> +#include "mount_common_i.h"
> +
> +struct fuse_service {
> + /* expected file format of the mount point */
> + mode_t expected_fmt;
> +
> + /* socket fd */
> + int sockfd;
> +
> + /* /dev/fuse device */
> + int fusedevfd;
> +
> + /* memfd for cli arguments */
> + int argvfd;
> +
> + /* do we own fusedevfd? */
> + bool owns_fusedevfd;
> +
> + /* can we use allow_other? */
> + bool allow_other;
> +};
> +
> +static int __recv_fd(struct fuse_service *sf,
> + struct fuse_service_requested_file *buf,
> + ssize_t bufsize, int *fdp)
> +{
> + struct iovec iov = {
> + .iov_base = buf,
> + .iov_len = bufsize,
> + };
> + union {
> + struct cmsghdr cmsghdr;
> + char control[CMSG_SPACE(sizeof(int))];
> + } cmsgu = { };
> + struct msghdr msg = {
> + .msg_iov = &iov,
> + .msg_iovlen = 1,
> + .msg_control = cmsgu.control,
> +
> + /*
> + * Do not include padding at the end of the control buffer,
> + * because we don't want to receive fds that we weren't
> + * expecting.
> + */
> + .msg_controllen = CMSG_LEN(sizeof(int)),
> + };
> + struct cmsghdr *cmsg;
> + ssize_t size;
> +
> + /*
> + * A kernel LSM could decide to deny the fd transfer by writing a
> + * negative number (== invalid fd) into the cmsg buffer instead of
The kernel doesn't actually do this, so we only need to initialize the
buffer to all 1s to protect against the kernel not writing to the cmsg
buffer, which it shouldn't do without also setting MSG_CTRUNC or
returning an error, but who knows.
> + * installing the fd. Set the initial fd value to -1 to signal an
> + * invalid fd in case the kernel doesn't even set the cmsg buffer.
> + * It shouldn't do that, but we absolutely don't want a zero here.
> + */
> + memset(cmsgu.control, -1, sizeof(cmsgu.control));
> +
> + size = recvmsg(sf->sockfd, &msg, MSG_TRUNC | MSG_CMSG_CLOEXEC);
> + if (size < 0) {
> + int error = errno;
> +
> + fuse_log(FUSE_LOG_ERR, "fuse: service file reply: %s\n",
> + strerror(error));
> + return -error;
> + }
> + if (size > bufsize ||
> + size < offsetof(struct fuse_service_requested_file, path)) {
> + fuse_log(FUSE_LOG_ERR, "fuse: wrong service file reply size %zd, expected %zd\n",
> + size, bufsize);
> + return -EBADMSG;
> + }
> +
> + if (msg.msg_flags & MSG_CTRUNC) {
> + /* SMACK does this */
> + fuse_log(FUSE_LOG_ERR,
> +"fuse: service file reply control data truncated; did an LSM deny SCM_RIGHTS?\n");
> + return -EBADMSG;
> + }
> +
> + cmsg = CMSG_FIRSTHDR(&msg);
> + if (!cmsg) {
> + /* no control message means mount.service sent us an error */
> + return 0;
> + }
> + if (cmsg->cmsg_len != CMSG_LEN(sizeof(int))) {
> + fuse_log(FUSE_LOG_ERR,
> + "fuse: wrong service file reply control data size %zd, expected %zd\n",
> + cmsg->cmsg_len, CMSG_LEN(sizeof(int)));
> + return -EBADMSG;
> + }
> + if (cmsg->cmsg_level != SOL_SOCKET || cmsg->cmsg_type != SCM_RIGHTS) {
> + fuse_log(FUSE_LOG_ERR,
> +"fuse: wrong service file reply control data level %d type %d, expected %d and %d\n",
> + cmsg->cmsg_level, cmsg->cmsg_type, SOL_SOCKET,
> + SCM_RIGHTS);
> + return -EBADMSG;
> + }
> +
> + memcpy(fdp, (int *)CMSG_DATA(cmsg), sizeof(int));
> + return 0;
> +}
> +
> +static ssize_t __send_packet(struct fuse_service *sf, void *ptr, size_t len)
> +{
> + struct iovec iov = {
> + .iov_base = ptr,
> + .iov_len = len,
> + };
> + struct msghdr msg = {
> + .msg_iov = &iov,
> + .msg_iovlen = 1,
> + };
> +
> + return sendmsg(sf->sockfd, &msg, MSG_EOR | MSG_NOSIGNAL);
> +}
> +
> +static ssize_t __recv_packet(struct fuse_service *sf, void *ptr, size_t len)
> +{
> + struct iovec iov = {
> + .iov_base = ptr,
> + .iov_len = len,
> + };
> + struct msghdr msg = {
> + .msg_iov = &iov,
> + .msg_iovlen = 1,
> + };
> +
> + return recvmsg(sf->sockfd, &msg, MSG_TRUNC);
> +}
> +
> +int fuse_service_receive_file(struct fuse_service *sf, const char *path,
> + int *fdp)
> +{
> + struct fuse_service_requested_file *req;
> + const size_t req_sz = sizeof_fuse_service_requested_file(strlen(path));
> + int fd = -ENOENT;
> + int ret;
> +
> + *fdp = -ENOENT;
> +
> + req = calloc(1, req_sz + 1);
> + if (!req) {
> + int error = errno;
> +
> + fuse_log(FUSE_LOG_ERR, "fuse: alloc service file reply: %s\n",
> + strerror(error));
> + return -error;
> + }
> +
> + ret = __recv_fd(sf, req, req_sz, &fd);
> + if (ret)
> + goto out_req;
> +
> + if (fd < 0) {
> + /* The kernel might have given us an errno instead of an fd */
> + fuse_log(FUSE_LOG_ERR, "fuse: service fd transfer failed: %s\n",
> + strerror(-fd));
> + ret = fd;
> + goto out_req;
> + }
Codex points out that this nearly redundant with the "fd == -ENOENT"
check below and could be combined. Worse, it actually breaks the two
failure reporting cases below, so...
> +
> + if (ntohl(req->p.magic) != FUSE_SERVICE_OPEN_REPLY) {
> + fuse_log(FUSE_LOG_ERR, "fuse: service file reply contains wrong magic!\n");
> + ret = -EBADMSG;
> + goto out_close;
> + }
> + if (strcmp(req->path, path)) {
> + fuse_log(FUSE_LOG_ERR, "fuse: `%s': not the requested service file, got `%s'\n",
> + path, req->path);
> + ret = -EBADMSG;
> + goto out_close;
> + }
> +
> + if (req->error) {
> + *fdp = -ntohl(req->error);
> + goto out_close;
> + }
> +
> + if (fd == -ENOENT)
> + fuse_log(FUSE_LOG_ERR, "fuse: did not receive `%s' but no error?\n",
> + path);
...I'll compress both clauses into:
if (fd < 0) {
fuse_log(FUSE_LOG_ERR,
"fuse: did not receive valid fd for `%s' but no error?\n",
path);
goto out_req;
}
--D
> +
> + *fdp = fd;
> + goto out_req;
> +
> +out_close:
> + close(fd);
> +out_req:
> + free(req);
> + return ret;
> +}
> +
> +#define FUSE_SERVICE_REQUEST_FILE_FLAGS (FUSE_SERVICE_REQUEST_FILE_QUIET)
> +
> +static int fuse_service_request_path(struct fuse_service *sf, const char *path,
> + mode_t expected_fmt, int open_flags,
> + mode_t create_mode,
> + unsigned int request_flags,
> + unsigned int block_size)
> +{
> + struct fuse_service_open_command *cmd;
> + const size_t cmdsz = sizeof_fuse_service_open_command(strlen(path));
> + ssize_t size;
> + unsigned int rqflags = 0;
> + int ret;
> +
> + if (request_flags & ~FUSE_SERVICE_REQUEST_FILE_FLAGS) {
> + fuse_log(FUSE_LOG_ERR, "fuse: invalid fuse service file request flags 0x%x\n",
> + request_flags);
> + return -EINVAL;
> + }
> +
> + if (request_flags & FUSE_SERVICE_REQUEST_FILE_QUIET)
> + rqflags |= FUSE_SERVICE_OPEN_QUIET;
> +
> + cmd = calloc(1, cmdsz);
> + if (!cmd) {
> + int error = errno;
> +
> + fuse_log(FUSE_LOG_ERR, "fuse: alloc service file request: %s\n",
> + strerror(error));
> + return -error;
> + }
> + if (S_ISBLK(expected_fmt)) {
> + cmd->p.magic = htonl(FUSE_SERVICE_OPEN_BDEV_CMD);
> + cmd->block_size = htonl(block_size);
> + } else {
> + cmd->p.magic = htonl(FUSE_SERVICE_OPEN_CMD);
> + }
> + cmd->open_flags = htonl(open_flags);
> + cmd->create_mode = htonl(create_mode);
> + cmd->request_flags = htonl(rqflags);
> + strcpy(cmd->path, path);
> +
> + size = __send_packet(sf, cmd, cmdsz);
> + if (size < 0) {
> + int error = errno;
> +
> + fuse_log(FUSE_LOG_ERR, "fuse: request service file: %s\n",
> + strerror(error));
> + ret = -error;
> + goto out_free;
> + }
> +
> + ret = 0;
> +out_free:
> + free(cmd);
> + return ret;
> +}
> +
> +int fuse_service_request_file(struct fuse_service *sf, const char *path,
> + int open_flags, mode_t create_mode,
> + unsigned int request_flags)
> +{
> + return fuse_service_request_path(sf, path, S_IFREG, open_flags,
> + create_mode, request_flags, 0);
> +}
> +
> +int fuse_service_request_blockdev(struct fuse_service *sf, const char *path,
> + int open_flags, mode_t create_mode,
> + unsigned int request_flags,
> + unsigned int block_size)
> +{
> + return fuse_service_request_path(sf, path, S_IFBLK, open_flags,
> + create_mode, request_flags,
> + block_size);
> +}
> +
> +int fuse_service_send_goodbye(struct fuse_service *sf, int exitcode)
> +{
> + struct fuse_service_bye_command c = {
> + .p.magic = htonl(FUSE_SERVICE_BYE_CMD),
> + .exitcode = htonl(exitcode),
> + };
> + ssize_t size;
> +
> + /* already gone? */
> + if (sf->sockfd < 0)
> + return 0;
> +
> + size = __send_packet(sf, &c, sizeof(c));
> + if (size < 0) {
> + int error = errno;
> +
> + fuse_log(FUSE_LOG_ERR, "fuse: send service goodbye: %s\n",
> + strerror(error));
> + return -error;
> + }
> +
> + shutdown(sf->sockfd, SHUT_RDWR);
> + close(sf->sockfd);
> + sf->sockfd = -1;
> + return 0;
> +}
> +
> +static int count_listen_fds(void)
> +{
> + char *listen_fds;
> + char *listen_pid;
> + char *p;
> + long l;
> +
> + /*
> + * No environment variables means we're not running as a system socket
> + * service, so we'll back out without logging anything.
> + */
> + listen_fds = getenv("LISTEN_FDS");
> + listen_pid = getenv("LISTEN_PID");
> + if (!listen_fds || !listen_pid)
> + return 0;
> +
> + /*
> + * LISTEN_PID is the pid of the process to which systemd thinks it gave
> + * the socket fd. Hopefully that's us.
> + */
> + errno = 0;
> + l = strtol(listen_pid, &p, 10);
> + if (errno || *p != 0 || l != getpid())
> + return 0;
> +
> + /*
> + * LISTEN_FDS is the number of sockets that were opened in this
> + * process.
> + */
> + errno = 0;
> + l = strtol(listen_fds, &p, 10);
> + if (errno || *p != 0 || l > INT_MAX || l < 0)
> + return 0;
> +
> + return l;
> +}
> +
> +static int check_sendbuf_size(int sockfd)
> +{
> + const size_t min_size = sizeof_fuse_service_open_command(PATH_MAX);
> + int sendbuf_size = -1;
> + socklen_t optlen = sizeof(sendbuf_size);
> + int ret;
> +
> + /*
> + * If we can't query the maximum send buffer length, just keep going.
> + * Most likely we won't be sending huge open commands, and if we do,
> + * the sendmsg will fail there too.
> + */
> + ret = getsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &sendbuf_size, &optlen);
> + if (ret || sendbuf_size < 0)
> + return 0;
> +
> + if (sendbuf_size >= min_size)
> + return 0;
> +
> + fuse_log(FUSE_LOG_ERR, "max socket send buffer is %d, need at least %zu.\n",
> + sendbuf_size, min_size);
> + return -ENOBUFS;
> +}
> +
> +static int find_socket_fd(int nr_fds)
> +{
> + struct stat stbuf;
> + struct sockaddr_un urk;
> + socklen_t urklen = sizeof(urk);
> + int ret;
> +
> + if (nr_fds != 1) {
> + fuse_log(FUSE_LOG_ERR, "fuse: can only handle 1 service socket, got %d.\n",
> + nr_fds);
> + return -E2BIG;
> + }
> +
> + ret = fstat(SD_LISTEN_FDS_START, &stbuf);
> + if (ret) {
> + int error = errno;
> +
> + fuse_log(FUSE_LOG_ERR, "fuse: service socket: %s\n",
> + strerror(error));
> + return -error;
> + }
> +
> + if (!S_ISSOCK(stbuf.st_mode)) {
> + fuse_log(FUSE_LOG_ERR, "fuse: expected service fd %d to be a socket\n",
> + SD_LISTEN_FDS_START);
> + return -ENOTSOCK;
> + }
> +
> + ret = getsockname(SD_LISTEN_FDS_START, &urk, &urklen);
> + if (ret < 0) {
> + int error = errno;
> +
> + fuse_log(FUSE_LOG_ERR, "fuse: service socket family: %s\n",
> + strerror(error));
> + return -error;
> + }
> +
> + if (ret > 0 || urk.sun_family != AF_UNIX) {
> + /*
> + * If getsockname wanted to return more data than fits in a
> + * sockaddr_un, then it's obviously not an AF_UNIX socket.
> + *
> + * If it filled the buffer exactly but the family isn't AF_UNIX
> + * then we also return false.
> + */
> + fuse_log(FUSE_LOG_ERR, "fuse: service socket is not AF_UNIX\n");
> + return -EAFNOSUPPORT;
> + }
> +
> + ret = check_sendbuf_size(SD_LISTEN_FDS_START);
> + if (ret)
> + return ret;
> +
> + return SD_LISTEN_FDS_START;
> +}
> +
> +static int negotiate_hello(struct fuse_service *sf)
> +{
> + struct fuse_service_hello hello = { };
> + struct fuse_service_hello_reply reply = {
> + .p.magic = htonl(FUSE_SERVICE_HELLO_REPLY),
> + .version = htons(FUSE_SERVICE_PROTO),
> + };
> + uint32_t flags;
> + ssize_t size;
> +
> + size = __recv_packet(sf, &hello, sizeof(hello));
> + if (size < 0) {
> + int error = errno;
> +
> + fuse_log(FUSE_LOG_ERR, "fuse: receive service hello: %s\n",
> + strerror(error));
> + return -error;
> + }
> + if (size != sizeof(hello)) {
> + fuse_log(FUSE_LOG_ERR, "fuse: wrong service hello size %zd, expected %zd\n",
> + size, sizeof(hello));
> + return -EBADMSG;
> + }
> +
> + if (ntohl(hello.p.magic) != FUSE_SERVICE_HELLO_CMD) {
> + fuse_log(FUSE_LOG_ERR, "fuse: service server did not send hello command\n");
> + return -EBADMSG;
> + }
> +
> + if (ntohs(hello.min_version) < FUSE_SERVICE_MIN_PROTO) {
> + fuse_log(FUSE_LOG_ERR, "fuse: unsupported min service protocol version %u\n",
> + ntohs(hello.min_version));
> + return -EOPNOTSUPP;
> + }
> +
> + if (ntohs(hello.max_version) > FUSE_SERVICE_MAX_PROTO) {
> + fuse_log(FUSE_LOG_ERR, "fuse: unsupported max service protocol version %u\n",
> + ntohs(hello.min_version));
> + return -EOPNOTSUPP;
> + }
> +
> + flags = ntohl(hello.flags);
> + if (flags & ~FUSE_SERVICE_FLAGS) {
> + fprintf(stderr, "fuse: invalid hello flags: 0x%x\n",
> + flags & ~FUSE_SERVICE_FLAGS);
> + return -EINVAL;
> + }
> +
> + if (flags & FUSE_SERVICE_FLAG_ALLOW_OTHER)
> + sf->allow_other = true;
> +
> + size = __send_packet(sf, &reply, sizeof(reply));
> + if (size < 0) {
> + int error = errno;
> +
> + fuse_log(FUSE_LOG_ERR, "fuse: service hello reply: %s\n",
> + strerror(error));
> + return -error;
> + }
> +
> + return 0;
> +}
> +
> +int fuse_service_accept(struct fuse_service **sfp)
> +{
> + struct fuse_service *sf;
> + int nr_fds;
> + int sockfd;
> + int flags;
> + int ret = 0;
> +
> + *sfp = NULL;
> +
> + nr_fds = count_listen_fds();
> + if (nr_fds == 0)
> + return 0;
> +
> + /* Find the socket that connects us to mount.service */
> + sockfd = find_socket_fd(nr_fds);
> + if (sockfd < 0)
> + return sockfd;
> +
> + flags = fcntl(sockfd, F_GETFD);
> + if (flags < 0) {
> + int error = errno;
> +
> + fuse_log(FUSE_LOG_ERR, "fuse: service socket getfd: %s\n",
> + strerror(error));
> + return -error;
> + }
> +
> + if (!(flags & FD_CLOEXEC)) {
> + ret = fcntl(sockfd, F_SETFD, flags | FD_CLOEXEC);
> + if (ret) {
> + int error = errno;
> +
> + fuse_log(FUSE_LOG_ERR, "fuse: service socket set cloexec: %s\n",
> + strerror(error));
> + return -error;
> + }
> + }
> +
> + sf = calloc(1, sizeof(struct fuse_service));
> + if (!sf) {
> + int error = errno;
> +
> + fuse_log(FUSE_LOG_ERR, "fuse: service alloc: %s\n",
> + strerror(error));
> + return -error;
> + }
> + sf->sockfd = sockfd;
> +
> + ret = negotiate_hello(sf);
> + if (ret)
> + goto out_sf;
> +
> + /* Receive the two critical sockets */
> + ret = fuse_service_receive_file(sf, FUSE_SERVICE_ARGV, &sf->argvfd);
> + if (ret < 0)
> + goto out_sockfd;
> + if (sf->argvfd < 0) {
> + fuse_log(FUSE_LOG_ERR, "fuse: service mount options file: %s\n",
> + strerror(-sf->argvfd));
> + ret = sf->argvfd;
> + goto out_sockfd;
> + }
> +
> + ret = fuse_service_receive_file(sf, FUSE_SERVICE_FUSEDEV,
> + &sf->fusedevfd);
> + if (ret < 0)
> + goto out_argvfd;
> + if (sf->fusedevfd < 0) {
> + fuse_log(FUSE_LOG_ERR, "fuse: service fuse device: %s\n",
> + strerror(-sf->fusedevfd));
> + ret = sf->fusedevfd;
> + goto out_argvfd;
> + }
> +
> + sf->owns_fusedevfd = true;
> + *sfp = sf;
> + return 0;
> +
> +out_argvfd:
> + close(sf->argvfd);
> +out_sockfd:
> + shutdown(sf->sockfd, SHUT_RDWR);
> + close(sf->sockfd);
> +out_sf:
> + free(sf);
> + return ret;
> +}
> +
> +bool fuse_service_can_allow_other(struct fuse_service *sf)
> +{
> + return sf->allow_other;
> +}
> +
> +int fuse_service_append_args(struct fuse_service *sf,
> + struct fuse_args *existing_args)
> +{
> + struct fuse_service_memfd_argv memfd_args = { };
> + struct fuse_args new_args = {
> + .allocated = 1,
> + };
> + char *str = NULL;
> + off_t memfd_pos = 0;
> + ssize_t received;
> + unsigned int i;
> + int ret;
> +
> + /* Figure out how many arguments we're getting from the mount helper. */
> + received = pread(sf->argvfd, &memfd_args, sizeof(memfd_args), 0);
> + if (received < 0) {
> + int error = errno;
> +
> + fuse_log(FUSE_LOG_ERR, "fuse: service args file: %s\n",
> + strerror(error));
> + return -error;
> + }
> + if (received < sizeof(memfd_args)) {
> + fuse_log(FUSE_LOG_ERR, "fuse: service args file length unreadable\n");
> + return -EBADMSG;
> + }
> + if (ntohl(memfd_args.magic) != FUSE_SERVICE_ARGS_MAGIC) {
> + fuse_log(FUSE_LOG_ERR, "fuse: service args file corrupt\n");
> + return -EBADMSG;
> + }
> + memfd_args.magic = htonl(memfd_args.magic);
> + memfd_args.argc = htonl(memfd_args.argc);
> + memfd_pos += sizeof(memfd_args);
> +
> + /* Allocate a new array of argv string pointers */
> + new_args.argv = calloc(memfd_args.argc + existing_args->argc,
> + sizeof(char *));
> + if (!new_args.argv) {
> + int error = errno;
> +
> + fuse_log(FUSE_LOG_ERR, "fuse: service new args: %s\n",
> + strerror(error));
> + return -error;
> + }
> +
> + /*
> + * Copy the fuse server's CLI arguments. We'll leave new_args.argv[0]
> + * unset for now, because we'll set it in the next step with the fstype
> + * that the mount helper sent us.
> + */
> + new_args.argc++;
> + for (i = 1; i < existing_args->argc; i++) {
> + if (existing_args->allocated) {
> + new_args.argv[new_args.argc] = existing_args->argv[i];
> + existing_args->argv[i] = NULL;
> + } else {
> + char *dup = strdup(existing_args->argv[i]);
> +
> + if (!dup) {
> + int error = errno;
> +
> + fuse_log(FUSE_LOG_ERR,
> + "fuse: service duplicate existing args: %s\n",
> + strerror(error));
> + ret = -error;
> + goto out_new_args;
> + }
> +
> + new_args.argv[new_args.argc] = dup;
> + }
> +
> + new_args.argc++;
> + }
> +
> + /* Copy the rest of the arguments from the helper */
> + for (i = 0; i < memfd_args.argc; i++) {
> + struct fuse_service_memfd_arg memfd_arg = { };
> +
> + /* Read argv iovec */
> + received = pread(sf->argvfd, &memfd_arg, sizeof(memfd_arg),
> + memfd_pos);
> + if (received < 0) {
> + int error = errno;
> +
> + fuse_log(FUSE_LOG_ERR, "fuse: service args file iovec read: %s\n",
> + strerror(error));
> + ret = -error;
> + goto out_new_args;
> + }
> + if (received < sizeof(struct fuse_service_memfd_arg)) {
> + fuse_log(FUSE_LOG_ERR,
> + "fuse: service args file argv[%u] iovec short read %zd",
> + i, received);
> + ret = -EBADMSG;
> + goto out_new_args;
> + }
> + memfd_arg.pos = htonl(memfd_arg.pos);
> + memfd_arg.len = htonl(memfd_arg.len);
> + memfd_pos += sizeof(memfd_arg);
> +
> + /* read arg string from file */
> + str = calloc(1, memfd_arg.len + 1);
> + if (!str) {
> + int error = errno;
> +
> + fuse_log(FUSE_LOG_ERR, "fuse: service arg alloc: %s\n",
> + strerror(error));
> + ret = -error;
> + goto out_new_args;
> + }
> +
> + received = pread(sf->argvfd, str, memfd_arg.len, memfd_arg.pos);
> + if (received < 0) {
> + int error = errno;
> +
> + fuse_log(FUSE_LOG_ERR, "fuse: service args file read: %s\n",
> + strerror(error));
> + ret = -error;
> + goto out_str;
> + }
> + if (received < memfd_arg.len) {
> + fuse_log(FUSE_LOG_ERR, "fuse: service args file argv[%u] short read %zd",
> + i, received);
> + ret = -EBADMSG;
> + goto out_str;
> + }
> +
> + /* move string into the args structure */
> + if (i == 0) {
> + /* the first argument is the fs type */
> + new_args.argv[0] = str;
> + } else {
> + new_args.argv[new_args.argc] = str;
> + new_args.argc++;
> + }
> + str = NULL;
> + }
> +
> + /* drop existing args, move new args to existing args */
> + fuse_opt_free_args(existing_args);
> + memcpy(existing_args, &new_args, sizeof(*existing_args));
> +
> + close(sf->argvfd);
> + sf->argvfd = -1;
> +
> + return 0;
> +
> +out_str:
> + free(str);
> +out_new_args:
> + fuse_opt_free_args(&new_args);
> + return ret;
> +}
> +
> +#ifdef SO_PASSRIGHTS
> +int fuse_service_finish_file_requests(struct fuse_service *sf)
> +{
> + int zero = 0;
> + int ret;
> +
> + /*
> + * Don't let a malicious mount helper send us more fds. If the kernel
> + * doesn't know about this new(ish) option that's ok, we'll trust the
> + * servicemount helper.
> + */
> + ret = setsockopt(sf->sockfd, SOL_SOCKET, SO_PASSRIGHTS, &zero,
> + sizeof(zero));
> + if (ret && errno == ENOPROTOOPT)
> + ret = 0;
> + if (ret) {
> + int error = errno;
> +
> + fuse_log(FUSE_LOG_ERR, "fuse: disabling fd passing: %s\n",
> + strerror(error));
> + return -error;
> + }
> +
> + return 0;
> +}
> +#else
> +int fuse_service_finish_file_requests(struct fuse_service *sf)
> +{
> + (void)sf;
> + return 0;
> +}
> +#endif
> +
> +static int send_fsopen(struct fuse_service *sf, const char *fstype,
> + int *errorp)
> +{
> + struct fuse_service_simple_reply reply = { };
> + struct fuse_service_fsopen_command c = {
> + .p.magic = htonl(FUSE_SERVICE_FSOPEN_CMD),
> + };
> + ssize_t size;
> +
> + if (!strncmp(fstype, "fuseblk", 7))
> + c.fsopen_flags |= htonl(FUSE_SERVICE_FSOPEN_FUSEBLK);
> +
> + size = __send_packet(sf, &c, sizeof(c));
> + if (size < 0) {
> + int error = errno;
> +
> + fuse_log(FUSE_LOG_ERR, "fuse: send service fsopen command: %s\n",
> + strerror(error));
> + return -error;
> + }
> +
> + size = __recv_packet(sf, &reply, sizeof(reply));
> + if (size < 0) {
> + int error = errno;
> +
> + fuse_log(FUSE_LOG_ERR, "fuse: service fsopen reply: %s\n",
> + strerror(error));
> + return -error;
> + }
> + if (size != sizeof(reply)) {
> + fuse_log(FUSE_LOG_ERR, "fuse: wrong service fsopen reply size %zd, expected %zd\n",
> + size, sizeof(reply));
> + return -EBADMSG;
> + }
> +
> + if (ntohl(reply.p.magic) != FUSE_SERVICE_SIMPLE_REPLY) {
> + fuse_log(FUSE_LOG_ERR, "fuse: service fsopen reply contains wrong magic!\n");
> + return -EBADMSG;
> + }
> +
> + *errorp = ntohl(reply.error);
> + return 0;
> +}
> +
> +static int send_string(struct fuse_service *sf, uint32_t command,
> + const char *value, int *errorp)
> +{
> + struct fuse_service_simple_reply reply = { };
> + struct fuse_service_string_command *cmd;
> + const size_t cmdsz = sizeof_fuse_service_string_command(strlen(value));
> + ssize_t size;
> +
> + cmd = calloc(1, cmdsz);
> + if (!cmd) {
> + int error = errno;
> +
> + fuse_log(FUSE_LOG_ERR, "fuse: alloc service string send: %s\n",
> + strerror(error));
> + return -error;
> + }
> + cmd->p.magic = htonl(command);
> + strcpy(cmd->value, value);
> +
> + size = __send_packet(sf, cmd, cmdsz);
> + if (size < 0) {
> + int error = errno;
> +
> + fuse_log(FUSE_LOG_ERR, "fuse: send service string: %s\n",
> + strerror(error));
> + return -error;
> + }
> + free(cmd);
> +
> + size = __recv_packet(sf, &reply, sizeof(reply));
> + if (size < 0) {
> + int error = errno;
> +
> + fuse_log(FUSE_LOG_ERR, "fuse: service string reply: %s\n",
> + strerror(error));
> + return -error;
> + }
> + if (size != sizeof(reply)) {
> + fuse_log(FUSE_LOG_ERR, "fuse: wrong service string reply size %zd, expected %zd\n",
> + size, sizeof(reply));
> + return -EBADMSG;
> + }
> +
> + if (ntohl(reply.p.magic) != FUSE_SERVICE_SIMPLE_REPLY) {
> + fuse_log(FUSE_LOG_ERR, "fuse: service string reply contains wrong magic!\n");
> + return -EBADMSG;
> + }
> +
> + *errorp = ntohl(reply.error);
> + return 0;
> +}
> +
> +static int send_mountpoint(struct fuse_service *sf, mode_t expected_fmt,
> + const char *value, int *errorp)
> +{
> + struct fuse_service_simple_reply reply = { };
> + struct fuse_service_mountpoint_command *cmd;
> + const size_t cmdsz =
> + sizeof_fuse_service_mountpoint_command(strlen(value));
> + ssize_t size;
> +
> + cmd = calloc(1, cmdsz);
> + if (!cmd) {
> + int error = errno;
> +
> + fuse_log(FUSE_LOG_ERR, "fuse: alloc service mountpoint send: %s\n",
> + strerror(error));
> + return -error;
> + }
> + cmd->p.magic = htonl(FUSE_SERVICE_MNTPT_CMD);
> + cmd->expected_fmt = htons(expected_fmt);
> + strcpy(cmd->value, value);
> +
> + size = __send_packet(sf, cmd, cmdsz);
> + if (size < 0) {
> + int error = errno;
> +
> + fuse_log(FUSE_LOG_ERR, "fuse: send service mountpoint: %s\n",
> + strerror(error));
> + return -error;
> + }
> + free(cmd);
> +
> + size = __recv_packet(sf, &reply, sizeof(reply));
> + if (size < 0) {
> + int error = errno;
> +
> + fuse_log(FUSE_LOG_ERR, "fuse: service mountpoint reply: %s\n",
> + strerror(error));
> + return -error;
> + }
> + if (size != sizeof(reply)) {
> + fuse_log(FUSE_LOG_ERR,
> + "fuse: wrong service mountpoint reply size %zd, expected %zd\n",
> + size, sizeof(reply));
> + return -EBADMSG;
> + }
> +
> + if (ntohl(reply.p.magic) != FUSE_SERVICE_SIMPLE_REPLY) {
> + fuse_log(FUSE_LOG_ERR, "fuse: service mountpoint reply contains wrong magic!\n");
> + return -EBADMSG;
> + }
> +
> + *errorp = ntohl(reply.error);
> + return 0;
> +}
> +
> +static int send_mount(struct fuse_service *sf, unsigned int ms_flags,
> + int *errorp)
> +{
> + struct fuse_service_simple_reply reply = { };
> + struct fuse_service_mount_command c = {
> + .p.magic = htonl(FUSE_SERVICE_MOUNT_CMD),
> + .ms_flags = htonl(ms_flags),
> + };
> + ssize_t size;
> +
> + size = __send_packet(sf, &c, sizeof(c));
> + if (size < 0) {
> + int error = errno;
> +
> + fuse_log(FUSE_LOG_ERR, "fuse: send service mount command: %s\n",
> + strerror(error));
> + return -error;
> + }
> +
> + size = __recv_packet(sf, &reply, sizeof(reply));
> + if (size < 0) {
> + int error = errno;
> +
> + fuse_log(FUSE_LOG_ERR, "fuse: service mount reply: %s\n",
> + strerror(error));
> + return -error;
> + }
> + if (size != sizeof(reply)) {
> + fuse_log(FUSE_LOG_ERR, "fuse: wrong service mount reply size %zd, expected %zd\n",
> + size, sizeof(reply));
> + return -EBADMSG;
> + }
> +
> + if (ntohl(reply.p.magic) != FUSE_SERVICE_SIMPLE_REPLY) {
> + fuse_log(FUSE_LOG_ERR, "fuse: service mount reply contains wrong magic!\n");
> + return -EBADMSG;
> + }
> +
> + *errorp = ntohl(reply.error);
> + return 0;
> +}
> +
> +void fuse_service_expect_mount_format(struct fuse_service *sf,
> + mode_t expected_fmt)
> +{
> + sf->expected_fmt = expected_fmt;
> +}
> +
> +int fuse_service_session_mount(struct fuse_service *sf, struct fuse_session *se,
> + mode_t expected_fmt,
> + struct fuse_cmdline_opts *opts)
> +{
> + char *fstype = fuse_mnt_build_type(se->mo);
> + char *source = fuse_mnt_build_source(se->mo);
> + char *mntopts = fuse_mnt_kernel_opts(se->mo);
> + char path[32];
> + int ret;
> + int error = 0;
> +
> + if (!fstype || !source) {
> + fuse_log(FUSE_LOG_ERR, "fuse: cannot allocate service strings\n");
> + ret = -ENOMEM;
> + goto out_strings;
> + }
> +
> + if (!expected_fmt)
> + expected_fmt = sf->expected_fmt;
> +
> + /*
> + * The fuse session takes the fusedev fd if this succeeds. It is
> + * required to use the "/dev/fd/XX" format.
> + */
> + snprintf(path, sizeof(path), "/dev/fd/%d", sf->fusedevfd);
> + errno = 0;
> + ret = fuse_session_mount(se, path);
> + if (ret) {
> + /* Try to return richer errors than fuse_session_mount's -1 */
> + ret = errno ? -errno : -EINVAL;
> + goto out_strings;
> + }
> + sf->owns_fusedevfd = false;
> +
> + ret = send_fsopen(sf, fstype, &error);
> + if (ret)
> + goto out_strings;
> + if (error) {
> + fuse_log(FUSE_LOG_ERR, "fuse: service fsopen: %s\n",
> + strerror(error));
> + ret = -error;
> + goto out_strings;
> + }
> +
> + ret = send_string(sf, FUSE_SERVICE_SOURCE_CMD, source, &error);
> + if (ret)
> + goto out_strings;
> + if (error) {
> + fuse_log(FUSE_LOG_ERR, "fuse: service fs source: %s\n",
> + strerror(error));
> + ret = -error;
> + goto out_strings;
> + }
> +
> + ret = send_mountpoint(sf, expected_fmt, opts->mountpoint, &error);
> + if (ret)
> + goto out_strings;
> + if (error) {
> + fuse_log(FUSE_LOG_ERR, "fuse: service fs mountpoint: %s\n",
> + strerror(error));
> + ret = -error;
> + goto out_strings;
> + }
> +
> + if (mntopts) {
> + ret = send_string(sf, FUSE_SERVICE_MNTOPTS_CMD, mntopts,
> + &error);
> + if (ret)
> + goto out_strings;
> + if (error) {
> + fuse_log(FUSE_LOG_ERR, "fuse: service fs mount options: %s\n",
> + strerror(error));
> + ret = -error;
> + goto out_strings;
> + }
> + }
> +
> + ret = send_mount(sf, fuse_mnt_flags(se->mo), &error);
> + if (ret)
> + goto out_strings;
> + if (error) {
> + fuse_log(FUSE_LOG_ERR, "fuse: service mount: %s\n",
> + strerror(error));
> + ret = -error;
> + goto out_strings;
> + }
> +
> + /*
> + * foreground mode is needed so that systemd actually tracks the
> + * service correctly and doesn't try to kill it; and so that
> + * stdout/stderr don't get zapped. Change to the root directory so
> + * that the caller needn't call fuse_daemonize().
> + */
> + opts->foreground = 1;
> + (void)chdir("/");
> +
> +out_strings:
> + free(mntopts);
> + free(source);
> + free(fstype);
> + return ret;
> +}
> +
> +int fuse_service_session_unmount(struct fuse_service *sf)
> +{
> + struct fuse_service_simple_reply reply = { };
> + struct fuse_service_unmount_command c = {
> + .p.magic = htonl(FUSE_SERVICE_UNMOUNT_CMD),
> + };
> + ssize_t size;
> +
> + /* already gone? */
> + if (sf->sockfd < 0)
> + return 0;
> +
> + size = __send_packet(sf, &c, sizeof(c));
> + if (size < 0) {
> + int error = errno;
> +
> + fuse_log(FUSE_LOG_ERR, "fuse: send service unmount: %s\n",
> + strerror(error));
> + return -error;
> + }
> +
> + size = __recv_packet(sf, &reply, sizeof(reply));
> + if (size < 0) {
> + int error = errno;
> +
> + fuse_log(FUSE_LOG_ERR, "fuse: service unmount reply: %s\n",
> + strerror(error));
> + return -error;
> + }
> + if (size != sizeof(reply)) {
> + fuse_log(FUSE_LOG_ERR, "fuse: wrong service unmount reply size %zd, expected %zd\n",
> + size, sizeof(reply));
> + return -EBADMSG;
> + }
> +
> + if (ntohl(reply.p.magic) != FUSE_SERVICE_SIMPLE_REPLY) {
> + fuse_log(FUSE_LOG_ERR, "fuse: service unmount reply contains wrong magic!\n");
> + return -EBADMSG;
> + }
> +
> + if (reply.error) {
> + int error = ntohl(reply.error);
> +
> + fuse_log(FUSE_LOG_ERR, "fuse: service unmount: %s\n",
> + strerror(error));
> + return -error;
> + }
> +
> + return 0;
> +}
> +
> +void fuse_service_release(struct fuse_service *sf)
> +{
> + if (sf->owns_fusedevfd)
> + close(sf->fusedevfd);
> + sf->owns_fusedevfd = false;
> + sf->fusedevfd = -1;
> + close(sf->argvfd);
> + sf->argvfd = -1;
> + shutdown(sf->sockfd, SHUT_RDWR);
> + close(sf->sockfd);
> + sf->sockfd = -1;
> +}
> +
> +void fuse_service_destroy(struct fuse_service **sfp)
> +{
> + struct fuse_service *sf = *sfp;
> +
> + if (sf) {
> + fuse_service_release(*sfp);
> + free(sf);
> + }
> +
> + *sfp = NULL;
> +}
> +
> +char *fuse_service_cmdline(int argc, char *argv[], struct fuse_args *args)
> +{
> + char *p, *dst;
> + size_t len = 1;
> + ssize_t ret;
> + char *argv0;
> + unsigned int i;
> +
> + /* Try to preserve argv[0] */
> + if (argc > 0)
> + argv0 = argv[0];
> + else if (args->argc > 0)
> + argv0 = args->argv[0];
> + else
> + return NULL;
> +
> + /* Pick up the alleged fstype from args->argv[0] */
> + if (args->argc == 0)
> + return NULL;
> +
> + len += strlen(argv0) + 1;
> + len += 3; /* " -t" */
> + for (i = 0; i < args->argc; i++)
> + len += strlen(args->argv[i]) + 1;
> +
> + p = calloc(1, len);
> + if (!p)
> + return NULL;
> + dst = p;
> +
> + /* Format: argv0 -t alleged_fstype [all other options...] */
> + ret = sprintf(dst, "%s -t", argv0);
> + dst += ret;
> + for (i = 0; i < args->argc; i++) {
> + ret = sprintf(dst, " %s", args->argv[i]);
> + dst += ret;
> + }
> +
> + return p;
> +}
> +
> +int fuse_service_parse_cmdline_opts(struct fuse_args *args,
> + struct fuse_cmdline_opts *opts)
> +{
> + return fuse_parse_cmdline_service(args, opts);
> +}
> +
> +int fuse_service_exit(int ret)
> +{
> + /*
> + * We have to sleep 2 seconds here because journald uses the pid to
> + * connect our log messages to the systemd service. This is critical
> + * for capturing all the log messages if the service fails, because
> + * failure analysis tools use the service name to gather log messages
> + * for reporting.
> + */
> + sleep(2);
> +
> + /*
> + * If we're being run as a service, the return code must fit the LSB
> + * init script action error guidelines, which is to say that we
> + * compress all errors to 1 ("generic or unspecified error", LSB 5.0
> + * section 22.2) and hope the admin will scan the log for what actually
> + * happened.
> + */
> + return ret != 0 ? EXIT_FAILURE : EXIT_SUCCESS;
> +}
> diff --git a/lib/fuse_service_stub.c b/lib/fuse_service_stub.c
> new file mode 100644
> index 00000000000000..d34df3891a6e31
> --- /dev/null
> +++ b/lib/fuse_service_stub.c
> @@ -0,0 +1,106 @@
> +/*
> + * FUSE: Filesystem in Userspace
> + * Copyright (C) 2025-2026 Oracle.
> + * Author: Darrick J. Wong <djwong@kernel.org>
> + *
> + * Stub functions for platforms where we cannot have fuse servers run as "safe"
> + * systemd containers.
> + *
> + * This program can be distributed under the terms of the GNU LGPLv2.
> + * See the file LGPL2.txt
> + */
> +
> +/* we don't use any parameters at all */
> +#pragma GCC diagnostic ignored "-Wunused-parameter"
> +
> +#define _GNU_SOURCE
> +#include <errno.h>
> +
> +#include "fuse_config.h"
> +#include "fuse_i.h"
> +#include "fuse_service.h"
> +
> +int fuse_service_receive_file(struct fuse_service *sf, const char *path,
> + int *fdp)
> +{
> + return -EOPNOTSUPP;
> +}
> +
> +int fuse_service_request_file(struct fuse_service *sf, const char *path,
> + int open_flags, mode_t create_mode,
> + unsigned int request_flags)
> +{
> + return -EOPNOTSUPP;
> +}
> +
> +int fuse_service_request_blockdev(struct fuse_service *sf, const char *path,
> + int open_flags, mode_t create_mode,
> + unsigned int request_flags,
> + unsigned int block_size)
> +{
> + return -EOPNOTSUPP;
> +}
> +
> +int fuse_service_send_goodbye(struct fuse_service *sf, int error)
> +{
> + return -EOPNOTSUPP;
> +}
> +
> +int fuse_service_accept(struct fuse_service **sfp)
> +{
> + *sfp = NULL;
> + return 0;
> +}
> +
> +int fuse_service_append_args(struct fuse_service *sf,
> + struct fuse_args *existing_args)
> +{
> + return -EOPNOTSUPP;
> +}
> +
> +char *fuse_service_cmdline(int argc, char *argv[], struct fuse_args *args)
> +{
> + return NULL;
> +}
> +
> +int fuse_service_finish_file_requests(struct fuse_service *sf)
> +{
> + return -EOPNOTSUPP;
> +}
> +
> +void fuse_service_expect_mount_format(struct fuse_service *sf,
> + mode_t expected_fmt)
> +{
> +}
> +
> +int fuse_service_session_mount(struct fuse_service *sf, struct fuse_session *se,
> + mode_t expected_fmt,
> + struct fuse_cmdline_opts *opts)
> +{
> + return -EOPNOTSUPP;
> +}
> +
> +int fuse_service_session_unmount(struct fuse_service *sf)
> +{
> + return -EOPNOTSUPP;
> +}
> +
> +void fuse_service_release(struct fuse_service *sf)
> +{
> +}
> +
> +void fuse_service_destroy(struct fuse_service **sfp)
> +{
> + *sfp = NULL;
> +}
> +
> +int fuse_service_parse_cmdline_opts(struct fuse_args *args,
> + struct fuse_cmdline_opts *opts)
> +{
> + return -1;
> +}
> +
> +int fuse_service_exit(int ret)
> +{
> + return ret;
> +}
> diff --git a/lib/fuse_versionscript b/lib/fuse_versionscript
> index cce09610316f4b..f34dc959a1d1e1 100644
> --- a/lib/fuse_versionscript
> +++ b/lib/fuse_versionscript
> @@ -227,6 +227,23 @@ FUSE_3.19 {
> fuse_session_start_teardown_watchdog;
> fuse_session_stop_teardown_watchdog;
> fuse_lowlevel_notify_prune;
> +
> + fuse_service_accept;
> + fuse_service_append_args;
> + fuse_service_can_allow_other;
> + fuse_service_cmdline;
> + fuse_service_destroy;
> + fuse_service_exit;
> + fuse_service_expect_mount_format;
> + fuse_service_finish_file_requests;
> + fuse_service_parse_cmdline_opts;
> + fuse_service_receive_file;
> + fuse_service_release;
> + fuse_service_request_file;
> + fuse_service_request_blockdev;
> + fuse_service_send_goodbye;
> + fuse_service_session_mount;
> + fuse_service_session_unmount;
> } FUSE_3.18;
>
> # Local Variables:
> diff --git a/lib/helper.c b/lib/helper.c
> index 74906fdcbd76d9..819b9a6e4d243c 100644
> --- a/lib/helper.c
> +++ b/lib/helper.c
> @@ -26,6 +26,11 @@
> #include <errno.h>
> #include <sys/param.h>
>
> +#ifdef HAVE_SERVICEMOUNT
> +# include <linux/types.h>
> +# include "fuse_service_priv.h"
> +#endif
> +
> #define FUSE_HELPER_OPT(t, p) \
> { t, offsetof(struct fuse_cmdline_opts, p), 1 }
>
> @@ -228,6 +233,52 @@ int fuse_parse_cmdline_312(struct fuse_args *args,
> return 0;
> }
>
> +#ifdef HAVE_SERVICEMOUNT
> +static int fuse_helper_opt_proc_service(void *data, const char *arg, int key,
> + struct fuse_args *outargs)
> +{
> + (void) outargs;
> + struct fuse_cmdline_opts *opts = data;
> +
> + switch (key) {
> + case FUSE_OPT_KEY_NONOPT:
> + if (!opts->mountpoint)
> + return fuse_opt_add_opt(&opts->mountpoint, arg);
> +
> + fuse_log(FUSE_LOG_ERR, "fuse: invalid argument `%s'\n", arg);
> + return -1;
> + default:
> + /* Pass through unknown options */
> + return 1;
> + }
> +}
> +
> +int fuse_parse_cmdline_service(struct fuse_args *args,
> + struct fuse_cmdline_opts *opts)
> +{
> + memset(opts, 0, sizeof(struct fuse_cmdline_opts));
> +
> + opts->max_idle_threads = UINT_MAX; /* new default in fuse version 3.12 */
> + opts->max_threads = 10;
> +
> + if (fuse_opt_parse(args, opts, fuse_helper_opts,
> + fuse_helper_opt_proc_service) == -1)
> + return -1;
> +
> + /*
> + * *Linux*: if neither -o subtype nor -o fsname are specified,
> + * set subtype to program's basename.
> + * *FreeBSD*: if fsname is not specified, set to program's
> + * basename.
> + */
> + if (!opts->nodefault_subtype)
> + if (add_default_subtype(args->argv[0], args) == -1)
> + return -1;
> +
> + return 0;
> +}
> +#endif
> +
> /**
> * struct fuse_cmdline_opts got extended in libfuse-3.12
> */
> diff --git a/lib/meson.build b/lib/meson.build
> index fcd95741c9d374..d9a902f74b558f 100644
> --- a/lib/meson.build
> +++ b/lib/meson.build
> @@ -10,6 +10,12 @@ else
> libfuse_sources += [ 'mount_bsd.c' ]
> endif
>
> +if private_cfg.get('HAVE_SERVICEMOUNT', false)
> + libfuse_sources += [ 'fuse_service.c' ]
> +else
> + libfuse_sources += [ 'fuse_service_stub.c' ]
> +endif
> +
> deps = [ thread_dep ]
> if private_cfg.get('HAVE_ICONV')
> libfuse_sources += [ 'modules/iconv.c' ]
> @@ -49,18 +55,25 @@ libfuse = library('fuse3',
> dependencies: deps,
> install: true,
> link_depends: 'fuse_versionscript',
> - c_args: [ '-DFUSE_USE_VERSION=317',
> + c_args: [ '-DFUSE_USE_VERSION=319',
> '-DFUSERMOUNT_DIR="@0@"'.format(fusermount_path) ],
> link_args: ['-Wl,--version-script,' + meson.current_source_dir()
> + '/fuse_versionscript' ])
>
> +vars = []
> +if private_cfg.get('HAVE_SERVICEMOUNT', false)
> + service_socket_dir = private_cfg.get_unquoted('FUSE_SERVICE_SOCKET_DIR', '')
> + vars += ['service_socket_dir=' + service_socket_dir]
> + vars += ['service_socket_perms=' + service_socket_perms]
> +endif
> pkg = import('pkgconfig')
> pkg.generate(libraries: [ libfuse, '-lpthread' ],
> libraries_private: '-ldl',
> version: meson.project_version(),
> name: 'fuse3',
> description: 'Filesystem in Userspace',
> - subdirs: 'fuse3')
> + subdirs: 'fuse3',
> + variables: vars)
>
> libfuse_dep = declare_dependency(include_directories: include_dirs,
> link_with: libfuse, dependencies: deps)
> diff --git a/lib/mount.c b/lib/mount.c
> index 2397c3fb2aa26b..952d8899dcf218 100644
> --- a/lib/mount.c
> +++ b/lib/mount.c
> @@ -750,3 +750,15 @@ char *fuse_mnt_build_type(const struct mount_opts *mo)
>
> return type;
> }
> +
> +char *fuse_mnt_kernel_opts(const struct mount_opts *mo)
> +{
> + if (mo->kernel_opts)
> + return strdup(mo->kernel_opts);
> + return NULL;
> +}
> +
> +unsigned int fuse_mnt_flags(const struct mount_opts *mo)
> +{
> + return mo->flags;
> +}
> diff --git a/meson.build b/meson.build
> index 80c5f1dc0bd356..66425a0d4cc16f 100644
> --- a/meson.build
> +++ b/meson.build
> @@ -69,6 +69,16 @@ args_default = [ '-D_GNU_SOURCE' ]
> #
> private_cfg = configuration_data()
> private_cfg.set_quoted('PACKAGE_VERSION', meson.project_version())
> +service_socket_dir = get_option('service-socket-dir')
> +service_socket_perms = get_option('service-socket-perms')
> +if service_socket_dir == ''
> + service_socket_dir = '/run/filesystems'
> +endif
> +if service_socket_perms == ''
> + service_socket_perms = '0220'
> +endif
> +private_cfg.set_quoted('FUSE_SERVICE_SOCKET_DIR', service_socket_dir)
> +private_cfg.set('FUSE_SERVICE_SOCKET_PERMS', service_socket_perms)
>
> # Test for presence of some functions
> test_funcs = [ 'fork', 'fstatat', 'openat', 'readlinkat', 'pipe2',
> @@ -118,6 +128,13 @@ special_funcs = {
> return -1;
> }
> }
> + ''',
> + 'systemd_headers': '''
> + #include <systemd/sd-daemon.h>
> +
> + int main(int argc, char *argv[]) {
> + return SD_LISTEN_FDS_START;
> + }
> '''
> }
>
> @@ -180,6 +197,23 @@ if get_option('enable-io-uring') and liburing.found() and libnuma.found()
> endif
> endif
>
> +# Check for systemd support
> +systemd_system_unit_dir = get_option('systemd-system-unit-dir')
> +if systemd_system_unit_dir == ''
> + systemd = dependency('systemd', required: false)
> + if systemd.found()
> + systemd_system_unit_dir = systemd.get_variable(pkgconfig: 'systemd_system_unit_dir')
> + endif
> +endif
> +
> +if systemd_system_unit_dir == '' or private_cfg.get('HAVE_SYSTEMD_HEADERS', false) == false
> + warning('systemd service support will not be built')
> +else
> + private_cfg.set_quoted('SYSTEMD_SYSTEM_UNIT_DIR', systemd_system_unit_dir)
> + private_cfg.set('HAVE_SYSTEMD', true)
> + private_cfg.set('HAVE_SERVICEMOUNT', true)
> +endif
> +
> #
> # Compiler configuration
> #
> diff --git a/meson_options.txt b/meson_options.txt
> index c1f8fe69467184..193a74c96d0676 100644
> --- a/meson_options.txt
> +++ b/meson_options.txt
> @@ -27,3 +27,12 @@ option('enable-usdt', type : 'boolean', value : false,
>
> option('enable-io-uring', type: 'boolean', value: true,
> description: 'Enable fuse-over-io-uring support')
> +
> +option('service-socket-dir', type : 'string', value : '',
> + description: 'Where to install fuse server sockets (if empty, /run/filesystems)')
> +
> +option('service-socket-perms', type : 'string', value : '',
> + description: 'Default fuse server socket permissions (if empty, 0220)')
> +
> +option('systemd-system-unit-dir', type : 'string', value : '',
> + description: 'Where to install systemd unit files (if empty, query pkg-config(1))')
> diff --git a/util/fuservicemount.c b/util/fuservicemount.c
> new file mode 100644
> index 00000000000000..9c694a4290f94e
> --- /dev/null
> +++ b/util/fuservicemount.c
> @@ -0,0 +1,18 @@
> +/*
> + * FUSE: Filesystem in Userspace
> + * Copyright (C) 2025-2026 Oracle.
> + * Author: Darrick J. Wong <djwong@kernel.org>
> + *
> + * This program can be distributed under the terms of the GNU GPLv2.
> + * See the file GPL2.txt.
> + *
> + * This program wraps the mounting of FUSE filesystems that run in systemd
> + */
> +#define _GNU_SOURCE
> +#include "fuse_config.h"
> +#include "mount_service.h"
> +
> +int main(int argc, char *argv[])
> +{
> + return mount_service_main(argc, argv);
> +}
> diff --git a/util/meson.build b/util/meson.build
> index 0e4b1cce95377e..04ea5ac201340d 100644
> --- a/util/meson.build
> +++ b/util/meson.build
> @@ -6,6 +6,15 @@ executable('fusermount3', ['fusermount.c', '../lib/mount_util.c', '../lib/util.c
> install_dir: get_option('bindir'),
> c_args: '-DFUSE_CONF="@0@"'.format(fuseconf_path))
>
> +if private_cfg.get('HAVE_SERVICEMOUNT', false)
> + executable('fuservicemount3', ['mount_service.c', 'fuservicemount.c', '../lib/mount_util.c'],
> + include_directories: include_dirs,
> + link_with: [ libfuse ],
> + install: true,
> + install_dir: get_option('sbindir'),
> + c_args: '-DFUSE_USE_VERSION=319')
> +endif
> +
> executable('mount.fuse3', ['mount.fuse.c'],
> include_directories: include_dirs,
> link_with: [ libfuse ],
> diff --git a/util/mount_service.c b/util/mount_service.c
> new file mode 100644
> index 00000000000000..a43ff79c7bfb6f
> --- /dev/null
> +++ b/util/mount_service.c
> @@ -0,0 +1,1427 @@
> +/*
> + * FUSE: Filesystem in Userspace
> + * Copyright (C) 2025-2026 Oracle.
> + * Author: Darrick J. Wong <djwong@kernel.org>
> + *
> + * This program can be distributed under the terms of the GNU GPLv2.
> + * See the file GPL2.txt.
> + *
> + * This program does the mounting of FUSE filesystems that run in systemd
> + */
> +#define _GNU_SOURCE
> +#include "fuse_config.h"
> +#include <stdint.h>
> +#include <string.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <unistd.h>
> +#include <errno.h>
> +#include <fcntl.h>
> +#include <stdbool.h>
> +#include <limits.h>
> +#include <arpa/inet.h>
> +#include <sys/socket.h>
> +#include <sys/un.h>
> +#include <sys/mman.h>
> +#include <sys/mount.h>
> +#include <sys/stat.h>
> +#include <sys/ioctl.h>
> +#include <linux/fs.h>
> +
> +#include "mount_util.h"
> +#include "util.h"
> +#include "fuse_i.h"
> +#include "fuse_service_priv.h"
> +#include "mount_service.h"
> +
> +struct mount_service {
> + /* prefix for printing error messages */
> + const char *msgtag;
> +
> + /* fuse subtype based on -t cli argument */
> + char *subtype;
> +
> + /* source argument to mount() */
> + char *source;
> +
> + /* target argument (aka mountpoint) to mount() */
> + char *mountpoint;
> +
> + /* mountpoint that we pass to mount() */
> + char *real_mountpoint;
> +
> + /* resolved path to mountpoint that we use for mtab updates */
> + char *resv_mountpoint;
> +
> + /* mount options */
> + char *mntopts;
> +
> + /* socket fd */
> + int sockfd;
> +
> + /* /dev/fuse device */
> + int fusedevfd;
> +
> + /* memfd for cli arguments */
> + int argvfd;
> +
> + /* fd for mount point */
> + int mountfd;
> +
> + /* did we actually mount successfully? */
> + bool mounted;
> +
> + /* has the fsopen command already been submitted? */
> + bool fsopened;
> +
> + /* is this a fuseblk mount? */
> + bool fuseblk;
> +};
> +
> +static ssize_t __send_fd(struct mount_service *mo,
> + struct fuse_service_requested_file *req,
> + size_t req_sz, int fd)
> +{
> + union {
> + struct cmsghdr cmsghdr;
> + char control[CMSG_SPACE(sizeof(int))];
> + } cmsgu;
> + struct iovec iov = {
> + .iov_base = req,
> + .iov_len = req_sz,
> + };
> + struct msghdr msg = {
> + .msg_iov = &iov,
> + .msg_iovlen = 1,
> + .msg_control = cmsgu.control,
> + .msg_controllen = sizeof(cmsgu.control),
> + };
> + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
> +
> + if (!cmsg) {
> + errno = EINVAL;
> + return -1;
> + }
> +
> + memset(&cmsgu, 0, sizeof(cmsgu));
> + cmsg->cmsg_len = CMSG_LEN(sizeof(int));
> + cmsg->cmsg_level = SOL_SOCKET;
> + cmsg->cmsg_type = SCM_RIGHTS;
> +
> + *((int *)CMSG_DATA(cmsg)) = fd;
> +
> + return sendmsg(mo->sockfd, &msg, MSG_EOR | MSG_NOSIGNAL);
> +}
> +
> +static ssize_t __send_packet(struct mount_service *mo, void *ptr, size_t len)
> +{
> + struct iovec iov = {
> + .iov_base = ptr,
> + .iov_len = len,
> + };
> + struct msghdr msg = {
> + .msg_iov = &iov,
> + .msg_iovlen = 1,
> + };
> +
> + return sendmsg(mo->sockfd, &msg, MSG_EOR | MSG_NOSIGNAL);
> +}
> +
> +static ssize_t __recv_packet_size(struct mount_service *mo)
> +{
> + struct iovec iov = { };
> + struct msghdr msg = {
> + .msg_iov = &iov,
> + .msg_iovlen = 1,
> + };
> + return recvmsg(mo->sockfd, &msg, MSG_PEEK | MSG_TRUNC);
> +}
> +
> +static ssize_t __recv_packet(struct mount_service *mo, void *ptr, size_t len)
> +{
> + struct iovec iov = {
> + .iov_base = ptr,
> + .iov_len = len,
> + };
> + struct msghdr msg = {
> + .msg_iov = &iov,
> + .msg_iovlen = 1,
> + };
> +
> + return recvmsg(mo->sockfd, &msg, MSG_TRUNC);
> +}
> +
> +/*
> + * Filter out the subtype of the filesystem (e.g. fuse.Y[.Z] -> Y[.Z]). The
> + * fuse server determines if it's appropriate to set the "blockdev" mount
> + * option (aka fuseblk).
> + */
> +const char *mount_service_subtype(const char *fstype)
> +{
> + const char *subtype;
> +
> + if (!strncmp(fstype, "fuse.", 5))
> + subtype = fstype + 5;
> + else if (!strncmp(fstype, "fuseblk.", 8))
> + subtype = fstype + 8;
> + else
> + subtype = fstype;
> +
> + if (strchr(subtype, '/') != NULL) {
> + fprintf(stderr,
> + "%s: fs subtype cannot contain path separators\n",
> + fstype);
> + return NULL;
> + }
> +
> + return subtype;
> +}
> +
> +static int mount_service_init(struct mount_service *mo, int argc, char *argv[])
> +{
> + char *fstype = NULL;
> + const char *subtype;
> + int i;
> +
> + mo->sockfd = -1;
> + mo->argvfd = -1;
> + mo->fusedevfd = -1;
> + mo->mountfd = -1;
> +
> + for (i = 0; i < argc; i++) {
> + if (!strcmp(argv[i], "-t") && i + 1 < argc) {
> + fstype = argv[i + 1];
> + break;
> + }
> + }
> + if (!fstype) {
> + fprintf(stderr, "%s: cannot determine filesystem type.\n",
> + mo->msgtag);
> + return -1;
> + }
> +
> + subtype = mount_service_subtype(fstype);
> + if (!subtype)
> + return -1;
> +
> + mo->subtype = strdup(subtype);
> + if (!mo->subtype) {
> + int error = errno;
> +
> + fprintf(stderr, "%s: cannot alloc memory for fs subtype: %s\n",
> + mo->msgtag, strerror(error));
> + return -1;
> + }
> +
> + return 0;
> +}
> +
> +#ifdef SO_PASSRIGHTS
> +static int try_drop_passrights(struct mount_service *mo, int sockfd)
> +{
> + int zero = 0;
> + int ret;
> +
> + /*
> + * Don't let a malicious mount helper send us any fds. We don't trust
> + * the fuse server not to pollute our fd namespace, so we'll end now.
> + */
> + ret = setsockopt(sockfd, SOL_SOCKET, SO_PASSRIGHTS, &zero,
> + sizeof(zero));
> + if (ret) {
> + fprintf(stderr, "%s: disabling fd passing: %s\n",
> + mo->msgtag, strerror(errno));
> + return -1;
> + }
> +
> + return 0;
> +}
> +#else
> +# define try_drop_passrights(...) (0)
> +#endif
> +
> +static int check_sendbuf_size(struct mount_service *mo, int sockfd)
> +{
> + const size_t min_size = sizeof_fuse_service_open_command(PATH_MAX);
> + int sendbuf_size = -1;
> + socklen_t optlen = sizeof(sendbuf_size);
> + int ret;
> +
> + /*
> + * If we can't query the maximum send buffer length, just keep going.
> + * Most likely we won't be sending huge open commands, and if we do,
> + * the sendmsg will fail there too.
> + */
> + ret = getsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &sendbuf_size, &optlen);
> + if (ret || sendbuf_size < 0)
> + return 0;
> +
> + if (sendbuf_size >= min_size)
> + return 0;
> +
> + fprintf(stderr, "%s: max socket send buffer is %d, need at least %zu.\n",
> + mo->msgtag, sendbuf_size, min_size);
> + return MOUNT_SERVICE_FALLBACK_NEEDED;
> +}
> +
> +static int mount_service_connect(struct mount_service *mo)
> +{
> + struct sockaddr_un name = {
> + .sun_family = AF_UNIX,
> + };
> + int sockfd;
> + ssize_t written;
> + int ret;
> +
> + written = snprintf(name.sun_path, sizeof(name.sun_path),
> + FUSE_SERVICE_SOCKET_DIR "/%s", mo->subtype);
> + if (written >= sizeof(name.sun_path)) {
> + fprintf(stderr, "%s: filesystem type name `%s' is too long.\n",
> + mo->msgtag, mo->subtype);
> + return -1;
> + }
> +
> + sockfd = socket(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0);
> + if (sockfd < 0) {
> + int error = errno;
> +
> + fprintf(stderr, "%s: opening %s service socket: %s\n",
> + mo->msgtag, mo->subtype, strerror(error));
> + return -1;
> + }
> +
> + ret = check_sendbuf_size(mo, sockfd);
> + if (ret)
> + return ret;
> +
> + ret = connect(sockfd, (const struct sockaddr *)&name, sizeof(name));
> + if (ret && (errno == ENOENT || errno == ECONNREFUSED)) {
> + fprintf(stderr, "%s: no safe filesystem driver for %s available.\n",
> + mo->msgtag, mo->subtype);
> + close(sockfd);
> + return MOUNT_SERVICE_FALLBACK_NEEDED;
> + }
> + if (ret) {
> + int error = errno;
> +
> + fprintf(stderr, "%s: %s: %s\n",
> + mo->msgtag, name.sun_path, strerror(error));
> + goto out;
> + }
> +
> + ret = try_drop_passrights(mo, sockfd);
> + if (ret)
> + goto out;
> +
> + mo->sockfd = sockfd;
> + return 0;
> +out:
> + close(sockfd);
> + return -1;
> +}
> +
> +static int mount_service_send_hello(struct mount_service *mo)
> +{
> + struct fuse_service_hello hello = {
> + .p.magic = htonl(FUSE_SERVICE_HELLO_CMD),
> + .min_version = htons(FUSE_SERVICE_MIN_PROTO),
> + .max_version = htons(FUSE_SERVICE_MAX_PROTO),
> + };
> + struct fuse_service_hello_reply reply = { };
> + ssize_t size;
> +
> + if (getuid() == 0)
> + hello.flags |= htonl(FUSE_SERVICE_FLAG_ALLOW_OTHER);
> +
> + size = __send_packet(mo, &hello, sizeof(hello));
> + if (size < 0) {
> + fprintf(stderr, "%s: send hello: %s\n",
> + mo->msgtag, strerror(errno));
> + return -1;
> + }
> +
> + size = __recv_packet(mo, &reply, sizeof(reply));
> + if (size < 0) {
> + fprintf(stderr, "%s: hello reply: %s\n",
> + mo->msgtag, strerror(errno));
> + return -1;
> + }
> + if (size != sizeof(reply)) {
> + fprintf(stderr, "%s: wrong hello reply size %zd, expected %zu\n",
> + mo->msgtag, size, sizeof(reply));
> + return -1;
> + }
> +
> + if (ntohl(reply.p.magic) != FUSE_SERVICE_HELLO_REPLY) {
> + fprintf(stderr, "%s: %s service server did not reply to hello\n",
> + mo->msgtag, mo->subtype);
> + return -1;
> + }
> +
> + if (ntohs(reply.version) < FUSE_SERVICE_MIN_PROTO ||
> + ntohs(reply.version) > FUSE_SERVICE_MAX_PROTO) {
> + fprintf(stderr, "%s: unsupported protocol version %u\n",
> + mo->msgtag, ntohs(reply.version));
> + return -1;
> + }
> +
> + if (reply.padding) {
> + fprintf(stderr, "%s: nonzero value in padding field\n",
> + mo->msgtag);
> + return -1;
> + }
> +
> + return 0;
> +}
> +
> +static int mount_service_capture_arg(struct mount_service *mo,
> + struct fuse_service_memfd_argv *args,
> + const char *string, off_t *array_pos,
> + off_t *string_pos)
> +{
> + const size_t string_len = strlen(string) + 1;
> + struct fuse_service_memfd_arg arg = {
> + .pos = htonl(*string_pos),
> + .len = htonl(string_len),
> + };
> + ssize_t written;
> +
> + written = pwrite(mo->argvfd, string, string_len, *string_pos);
> + if (written < 0) {
> + fprintf(stderr, "%s: memfd argv write: %s\n",
> + mo->msgtag, strerror(errno));
> + return -1;
> + }
> + if (written < string_len) {
> + fprintf(stderr, "%s: memfd argv[%u] wrote %zd, expected %zu\n",
> + mo->msgtag, args->argc, written, string_len);
> + return -1;
> + }
> +
> + written = pwrite(mo->argvfd, &arg, sizeof(arg), *array_pos);
> + if (written < 0) {
> + fprintf(stderr, "%s: memfd arg write: %s\n",
> + mo->msgtag, strerror(errno));
> + return -1;
> + }
> + if (written < sizeof(arg)) {
> + fprintf(stderr, "%s: memfd arg[%u] wrote %zd, expected %zu\n",
> + mo->msgtag, args->argc, written, sizeof(arg));
> + return -1;
> + }
> +
> + args->argc++;
> + *string_pos += string_len;
> + *array_pos += sizeof(arg);
> +
> + return 0;
> +}
> +
> +static int mount_service_capture_args(struct mount_service *mo, int argc,
> + char *argv[])
> +{
> + struct fuse_service_memfd_argv args = {
> + .magic = htonl(FUSE_SERVICE_ARGS_MAGIC),
> + };
> + off_t array_pos = sizeof(struct fuse_service_memfd_argv);
> + off_t string_pos = array_pos +
> + (argc * sizeof(struct fuse_service_memfd_arg));
> + ssize_t written;
> + int i;
> + int ret;
> +
> + if (argc < 0) {
> + fprintf(stderr, "%s: argc cannot be negative\n",
> + mo->msgtag);
> + return -1;
> + }
> +
> + /*
> + * Create the memfd in which we'll stash arguments, and set the write
> + * pointer for the names.
> + */
> + mo->argvfd = memfd_create("fuse service argv", MFD_CLOEXEC);
> + if (mo->argvfd < 0) {
> + fprintf(stderr, "%s: argvfd create: %s\n",
> + mo->msgtag, strerror(errno));
> + return -1;
> + }
> +
> + /*
> + * Write the alleged subtype as if it were argv[0], then write the rest
> + * of the argv arguments.
> + */
> + ret = mount_service_capture_arg(mo, &args, mo->subtype, &array_pos,
> + &string_pos);
> + if (ret)
> + return ret;
> +
> + for (i = 1; i < argc; i++) {
> + /* skip the -t(ype) argument */
> + if (!strcmp(argv[i], "-t") && i + 1 < argc) {
> + i++;
> + continue;
> + }
> +
> + ret = mount_service_capture_arg(mo, &args, argv[i],
> + &array_pos, &string_pos);
> + if (ret)
> + return ret;
> + }
> +
> + /* Now write the header */
> + args.argc = htonl(args.argc);
> + written = pwrite(mo->argvfd, &args, sizeof(args), 0);
> + if (written < 0) {
> + fprintf(stderr, "%s: memfd argv write: %s\n",
> + mo->msgtag, strerror(errno));
> + return -1;
> + }
> + if (written < sizeof(args)) {
> + fprintf(stderr, "%s: memfd argv wrote %zd, expected %zu\n",
> + mo->msgtag, written, sizeof(args));
> + return -1;
> + }
> +
> + return 0;
> +}
> +
> +static int mount_service_send_file(struct mount_service *mo,
> + const char *path, int fd)
> +{
> + struct fuse_service_requested_file *req;
> + const size_t req_sz =
> + sizeof_fuse_service_requested_file(strlen(path));
> + ssize_t written;
> + int ret = 0;
> +
> + req = calloc(1, req_sz);
> + if (!req) {
> + fprintf(stderr, "%s: alloc send file reply: %s\n",
> + mo->msgtag, strerror(errno));
> + return -1;
> + }
> + req->p.magic = htonl(FUSE_SERVICE_OPEN_REPLY);
> + req->error = 0;
> + strcpy(req->path, path);
> +
> + written = __send_fd(mo, req, req_sz, fd);
> + if (written < 0) {
> + fprintf(stderr, "%s: send file reply: %s\n",
> + mo->msgtag, strerror(errno));
> + ret = -1;
> + goto out_req;
> + }
> + if (written < req_sz) {
> + fprintf(stderr, "%s: send file reply wrote %zd, expected %zu\n",
> + mo->msgtag, written, req_sz);
> + ret = -1;
> + goto out_req;
> + }
> +
> +out_req:
> + free(req);
> + return ret;
> +}
> +
> +static int mount_service_send_file_error(struct mount_service *mo, int error,
> + const char *path)
> +{
> + struct fuse_service_requested_file *req;
> + const size_t req_sz =
> + sizeof_fuse_service_requested_file(strlen(path));
> + ssize_t written;
> + int ret = 0;
> +
> + req = calloc(1, req_sz);
> + if (!req) {
> + fprintf(stderr, "%s: alloc send file error: %s\n",
> + mo->msgtag, strerror(errno));
> + return -1;
> + }
> + req->p.magic = htonl(FUSE_SERVICE_OPEN_REPLY);
> + req->error = htonl(error);
> + strcpy(req->path, path);
> +
> + written = __send_packet(mo, req, req_sz);
> + if (written < 0) {
> + fprintf(stderr, "%s: send file error: %s\n",
> + mo->msgtag, strerror(errno));
> + ret = -1;
> + goto out_req;
> + }
> + if (written < req_sz) {
> + fprintf(stderr, "%s: send file error wrote %zd, expected %zu\n",
> + mo->msgtag, written, req_sz);
> + ret = -1;
> + goto out_req;
> + }
> +
> +out_req:
> + free(req);
> + return ret;
> +}
> +
> +static int mount_service_send_required_files(struct mount_service *mo,
> + const char *fusedev)
> +{
> + int ret;
> +
> + mo->fusedevfd = open(fusedev, O_RDWR | O_CLOEXEC);
> + if (mo->fusedevfd < 0) {
> + int error = errno;
> +
> + fprintf(stderr, "%s: %s: %s\n",
> + mo->msgtag, fusedev, strerror(error));
> + return -1;
> + }
> +
> + ret = mount_service_send_file(mo, FUSE_SERVICE_ARGV, mo->argvfd);
> + if (ret)
> + goto out_fusedevfd;
> +
> + close(mo->argvfd);
> + mo->argvfd = -1;
> +
> + return mount_service_send_file(mo, FUSE_SERVICE_FUSEDEV,
> + mo->fusedevfd);
> +
> +out_fusedevfd:
> + close(mo->fusedevfd);
> + mo->fusedevfd = -1;
> + return ret;
> +}
> +
> +static int mount_service_receive_command(struct mount_service *mo,
> + struct fuse_service_packet **commandp,
> + size_t *commandsz)
> +{
> + struct fuse_service_packet *command;
> + ssize_t alleged_size, size;
> +
> + alleged_size = __recv_packet_size(mo);
> + if (alleged_size < 0) {
> + fprintf(stderr, "%s: peek service command: %s\n",
> + mo->msgtag, strerror(errno));
> + return -1;
> + }
> + if (alleged_size == 0) {
> + /* fuse server probably exited early */
> + fprintf(stderr, "%s: fuse server exited without saying goodbye!\n",
> + mo->msgtag);
> + return -1;
> + }
> + if (alleged_size < sizeof(struct fuse_service_packet)) {
> + fprintf(stderr, "%s: wrong command packet size %zd, expected at least %zu\n",
> + mo->msgtag, alleged_size,
> + sizeof(struct fuse_service_packet));
> + return -1;
> + }
> + if (alleged_size > FUSE_SERVICE_MAX_CMD_SIZE) {
> + fprintf(stderr, "%s: wrong command packet size %zd, expected less than %d\n",
> + mo->msgtag, alleged_size, FUSE_SERVICE_MAX_CMD_SIZE);
> + return -1;
> + }
> +
> + command = calloc(1, alleged_size + 1);
> + if (!command) {
> + fprintf(stderr, "%s: alloc service command: %s\n",
> + mo->msgtag, strerror(errno));
> + return -1;
> + }
> +
> + size = __recv_packet(mo, command, alleged_size);
> + if (size < 0) {
> + fprintf(stderr, "%s: receive service command: %s\n",
> + mo->msgtag, strerror(errno));
> + free(command);
> + return -1;
> + }
> + if (size != alleged_size) {
> + fprintf(stderr, "%s: wrong service command size %zd, expected %zd\n",
> + mo->msgtag, size, alleged_size);
> + free(command);
> + return -1;
> + }
> +
> + *commandp = command;
> + *commandsz = size;
> + return 0;
> +}
> +
> +static int mount_service_send_reply(struct mount_service *mo, int error)
> +{
> + struct fuse_service_simple_reply reply = {
> + .p.magic = htonl(FUSE_SERVICE_SIMPLE_REPLY),
> + .error = htonl(error),
> + };
> + ssize_t size;
> +
> + size = __send_packet(mo, &reply, sizeof(reply));
> + if (size < 0) {
> + fprintf(stderr, "%s: send service reply: %s\n",
> + mo->msgtag, strerror(errno));
> + return -1;
> + }
> +
> + return 0;
> +}
> +
> +static int prepare_bdev(struct mount_service *mo,
> + struct fuse_service_open_command *oc, int fd)
> +{
> + struct stat stbuf;
> + int ret;
> +
> + ret = fstat(fd, &stbuf);
> + if (ret) {
> + int error = errno;
> +
> + fprintf(stderr, "%s: %s: %s\n",
> + mo->msgtag, oc->path, strerror(error));
> + return -error;
> + }
> +
> + if (!S_ISBLK(stbuf.st_mode)) {
> + fprintf(stderr, "%s: %s: %s\n",
> + mo->msgtag, oc->path, strerror(ENOTBLK));
> + return -ENOTBLK;
> + }
> +
> + if (oc->block_size) {
> + int block_size = ntohl(oc->block_size);
> +
> + ret = ioctl(fd, BLKBSZSET, &block_size);
> + if (ret) {
> + int error = errno;
> +
> + fprintf(stderr, "%s: %s: %s\n",
> + mo->msgtag, oc->path, strerror(error));
> + return -error;
> + }
> + }
> +
> + return 0;
> +}
> +
> +static int mount_service_open_path(struct mount_service *mo,
> + mode_t expected_fmt,
> + struct fuse_service_packet *p, size_t psz)
> +{
> + struct fuse_service_open_command *oc =
> + container_of(p, struct fuse_service_open_command, p);
> + uint32_t request_flags;
> + int open_flags;
> + int ret;
> + int fd;
> +
> + if (psz < sizeof_fuse_service_open_command(1)) {
> + fprintf(stderr, "%s: open command too small\n",
> + mo->msgtag);
> + return mount_service_send_file_error(mo, EINVAL, "?");
> + }
> +
> + if (!check_null_endbyte(p, psz)) {
> + fprintf(stderr, "%s: open command must be null terminated\n",
> + mo->msgtag);
> + return mount_service_send_file_error(mo, EINVAL, "?");
> + }
> +
> + request_flags = ntohl(oc->request_flags);
> + if (request_flags & ~FUSE_SERVICE_OPEN_FLAGS) {
> + fprintf(stderr, "%s: open flags 0x%x not recognized\n",
> + mo->msgtag, request_flags & ~FUSE_SERVICE_OPEN_FLAGS);
> + return mount_service_send_file_error(mo, EINVAL, oc->path);
> + }
> +
> + open_flags = ntohl(oc->open_flags) | O_CLOEXEC;
> + fd = open(oc->path, open_flags, ntohl(oc->create_mode));
> + if (fd < 0) {
> + int error = errno;
> +
> + /*
> + * Don't print a busy device error report because the
> + * filesystem might decide to retry.
> + */
> + if (error != EBUSY && !(request_flags & FUSE_SERVICE_OPEN_QUIET))
> + fprintf(stderr, "%s: %s: %s\n",
> + mo->msgtag, oc->path, strerror(error));
> + return mount_service_send_file_error(mo, error, oc->path);
> + }
> +
> + if (S_ISBLK(expected_fmt)) {
> + ret = prepare_bdev(mo, oc, fd);
> + if (ret < 0) {
> + close(fd);
> + return mount_service_send_file_error(mo, -ret,
> + oc->path);
> + }
> + }
> +
> + ret = mount_service_send_file(mo, oc->path, fd);
> + close(fd);
> + return ret;
> +}
> +
> +static int mount_service_handle_open_cmd(struct mount_service *mo,
> + struct fuse_service_packet *p,
> + size_t psz)
> +{
> + return mount_service_open_path(mo, 0, p, psz);
> +}
> +
> +static int mount_service_handle_open_bdev_cmd(struct mount_service *mo,
> + struct fuse_service_packet *p,
> + size_t psz)
> +{
> + return mount_service_open_path(mo, S_IFBLK, p, psz);
> +}
> +
> +static inline const char *fsname(const struct mount_service *mo)
> +{
> + return mo->fuseblk ? "fuseblk" : "fuse";
> +}
> +
> +static int mount_service_handle_fsopen_cmd(struct mount_service *mo,
> + const struct fuse_service_packet *p,
> + size_t psz)
> +{
> + struct fuse_service_fsopen_command *oc =
> + container_of(p, struct fuse_service_fsopen_command, p);
> + uint32_t fsopen_flags;
> +
> + if (psz != sizeof(struct fuse_service_fsopen_command)) {
> + fprintf(stderr, "%s: fsopen command wrong size %zu, expected %zu\n",
> + mo->msgtag, psz, sizeof(*oc));
> + return mount_service_send_reply(mo, EINVAL);
> + }
> +
> + if (mo->fsopened) {
> + fprintf(stderr, "%s: fsopen command respecified\n",
> + mo->msgtag);
> + return mount_service_send_reply(mo, EINVAL);
> + }
> +
> + fsopen_flags = ntohl(oc->fsopen_flags);
> + if (fsopen_flags & ~FUSE_SERVICE_FSOPEN_FLAGS) {
> + fprintf(stderr, "%s: unknown fsopen flags, 0x%x\n",
> + mo->msgtag, fsopen_flags & ~FUSE_SERVICE_FSOPEN_FLAGS);
> + return mount_service_send_reply(mo, EINVAL);
> + }
> +
> + if (fsopen_flags & FUSE_SERVICE_FSOPEN_FUSEBLK) {
> + if (getuid() != 0) {
> + fprintf(stderr, "%s: fuseblk requires root privilege\n",
> + mo->msgtag);
> + return mount_service_send_reply(mo, EPERM);
> + }
> +
> + mo->fuseblk = true;
> + }
> + mo->fsopened = true;
> +
> + return mount_service_send_reply(mo, 0);
> +}
> +
> +static int mount_service_handle_source_cmd(struct mount_service *mo,
> + const struct fuse_service_packet *p,
> + size_t psz)
> +{
> + struct fuse_service_string_command *oc =
> + container_of(p, struct fuse_service_string_command, p);
> +
> + if (psz < sizeof_fuse_service_string_command(1)) {
> + fprintf(stderr, "%s: source command too small\n",
> + mo->msgtag);
> + return mount_service_send_reply(mo, EINVAL);
> + }
> +
> + if (!check_null_endbyte(p, psz)) {
> + fprintf(stderr, "%s: source command must be null terminated\n",
> + mo->msgtag);
> + return mount_service_send_reply(mo, EINVAL);
> + }
> +
> + if (mo->source) {
> + fprintf(stderr, "%s: source respecified!\n",
> + mo->msgtag);
> + return mount_service_send_reply(mo, EINVAL);
> + }
> +
> + mo->source = strdup(oc->value);
> + if (!mo->source) {
> + int error = errno;
> +
> + fprintf(stderr, "%s: alloc source string: %s\n",
> + mo->msgtag, strerror(error));
> + return mount_service_send_reply(mo, error);
> + }
> +
> + return mount_service_send_reply(mo, 0);
> +}
> +
> +static int mount_service_handle_mntopts_cmd(struct mount_service *mo,
> + const struct fuse_service_packet *p,
> + size_t psz)
> +{
> + struct fuse_service_string_command *oc =
> + container_of(p, struct fuse_service_string_command, p);
> +
> + if (psz < sizeof_fuse_service_string_command(1)) {
> + fprintf(stderr, "%s: mount options command too small\n",
> + mo->msgtag);
> + return mount_service_send_reply(mo, EINVAL);
> + }
> +
> + if (!check_null_endbyte(p, psz)) {
> + fprintf(stderr, "%s: mount options command must be null terminated\n",
> + mo->msgtag);
> + return mount_service_send_reply(mo, EINVAL);
> + }
> +
> + if (mo->mntopts) {
> + fprintf(stderr, "%s: mount options respecified!\n",
> + mo->msgtag);
> + return mount_service_send_reply(mo, EINVAL);
> + }
> +
> + mo->mntopts = strdup(oc->value);
> + if (!mo->mntopts) {
> + int error = errno;
> +
> + fprintf(stderr, "%s: alloc mount options string: %s\n",
> + mo->msgtag, strerror(error));
> + return mount_service_send_reply(mo, error);
> + }
> +
> + return mount_service_send_reply(mo, 0);
> +}
> +
> +static int attach_to_mountpoint(struct mount_service *mo, mode_t expected_fmt,
> + char *mntpt)
> +{
> + struct stat stbuf;
> + char *res_mntpt;
> + int mountfd = -1;
> + int error;
> + int ret;
> +
> + /*
> + * Open the alleged mountpoint, make sure it's a dir or a file.
> + */
> + mountfd = open(mntpt, O_RDONLY | O_CLOEXEC);
> + if (mountfd < 0) {
> + error = errno;
> + fprintf(stderr, "%s: %s: %s\n", mo->msgtag, mntpt,
> + strerror(error));
> + goto out_error;
> + }
> +
> + /*
> + * Make sure we can access the mountpoint and that it's either a
> + * directory or a regular file. Linux can handle mounting atop special
> + * files, but we don't care to do such crazy things.
> + */
> + ret = fstat(mountfd, &stbuf);
> + if (ret) {
> + error = errno;
> + fprintf(stderr, "%s: %s: %s\n", mo->msgtag, mntpt,
> + strerror(error));
> + goto out_mountfd;
> + }
> +
> + if (!S_ISDIR(stbuf.st_mode) && !S_ISREG(stbuf.st_mode)) {
> + error = EACCES;
> + fprintf(stderr, "%s: %s: Mount point must be directory or regular file.\n",
> + mo->msgtag, mntpt);
> + goto out_mountfd;
> + }
> +
> + /*
> + * Resolve the (possibly relative) mountpoint path before chdir'ing
> + * onto it.
> + */
> + res_mntpt = fuse_mnt_resolve_path(mo->msgtag, mntpt);
> + if (!res_mntpt) {
> + error = EACCES;
> + fprintf(stderr, "%s: %s: Could not resolve path to mount point.\n",
> + mo->msgtag, mntpt);
> + goto out_mountfd;
> + }
> +
> + /* Make sure the mountpoint type matches what the caller wanted */
> + switch (expected_fmt) {
> + case S_IFDIR:
> + if (!S_ISDIR(stbuf.st_mode)) {
> + error = ENOTDIR;
> + fprintf(stderr, "%s: %s: %s\n",
> + mo->msgtag, mntpt, strerror(error));
> + goto out_res_mntpt;
> + }
> + break;
> + case S_IFREG:
> + if (!S_ISREG(stbuf.st_mode)) {
> + error = EISDIR;
> + fprintf(stderr, "%s: %s: %s\n",
> + mo->msgtag, mntpt, strerror(error));
> + goto out_res_mntpt;
> + }
> + break;
> + }
> +
> + switch (stbuf.st_mode & S_IFMT) {
> + case S_IFREG:
> + /*
> + * This is a regular file, so we point mount() at the open file
> + * descriptor.
> + */
> + asprintf(&mo->real_mountpoint, "/dev/fd/%d", mountfd);
> + break;
> + case S_IFDIR:
> + /*
> + * Pin the mount so it can't go anywhere. This only works for
> + * directories, which is fortunately the common case.
> + */
> + ret = fchdir(mountfd);
> + if (ret) {
> + error = errno;
> + fprintf(stderr, "%s: %s: %s\n", mo->msgtag, mntpt,
> + strerror(error));
> + goto out_res_mntpt;
> + }
> +
> + /*
> + * Now that we're sitting on the mountpoint directory, we can
> + * pass "." to mount() and avoid races with directory tree
> + * mutations.
> + */
> + mo->real_mountpoint = strdup(".");
> + break;
> + default:
> + /* Should never get here */
> + error = EINVAL;
> + goto out_res_mntpt;
> + }
> + if (!mo->real_mountpoint) {
> + error = ENOMEM;
> + fprintf(stderr, "%s: %s: %s\n", mo->msgtag, mntpt,
> + strerror(error));
> + goto out_res_mntpt;
> + }
> +
> + mo->mountpoint = mntpt;
> + mo->mountfd = mountfd;
> + mo->resv_mountpoint = res_mntpt;
> +
> + return mount_service_send_reply(mo, 0);
> +
> +out_res_mntpt:
> + free(res_mntpt);
> +out_mountfd:
> + close(mountfd);
> +out_error:
> + free(mntpt);
> + return mount_service_send_reply(mo, error);
> +}
> +
> +static int mount_service_handle_mountpoint_cmd(struct mount_service *mo,
> + const struct fuse_service_packet *p,
> + size_t psz, int argc, char *argv[])
> +{
> + struct fuse_service_mountpoint_command *oc =
> + container_of(p, struct fuse_service_mountpoint_command, p);
> + char *mntpt;
> + mode_t expected_fmt;
> + bool foundit = false;
> + int i;
> +
> + if (psz < sizeof_fuse_service_mountpoint_command(1)) {
> + fprintf(stderr, "%s: mount point command too small\n",
> + mo->msgtag);
> + return mount_service_send_reply(mo, EINVAL);
> + }
> +
> + if (!check_null_endbyte(p, psz)) {
> + fprintf(stderr, "%s: mount point command must be null terminated\n",
> + mo->msgtag);
> + return mount_service_send_reply(mo, EINVAL);
> + }
> +
> + if (oc->padding) {
> + fprintf(stderr, "%s: nonzero value in padding field\n",
> + mo->msgtag);
> + return mount_service_send_reply(mo, EINVAL);
> + }
> +
> + if (mo->mountpoint) {
> + fprintf(stderr, "%s: mount point respecified!\n",
> + mo->msgtag);
> + return mount_service_send_reply(mo, EINVAL);
> + }
> +
> + /* Make sure the mountpoint file format matches what the caller wanted */
> + expected_fmt = ntohs(oc->expected_fmt);
> + switch (expected_fmt) {
> + case S_IFDIR:
> + case S_IFREG:
> + case 0:
> + break;
> + default:
> + fprintf(stderr, "%s: %s: weird expected format 0%o\n",
> + mo->msgtag, oc->value, expected_fmt);
> + return mount_service_send_reply(mo, EINVAL);
> + }
> +
> + /* Mountpoint must be mentioned in the caller's argument list */
> + for (i = 0; i < argc; i++) {
> + if (!strcmp(argv[i], oc->value)) {
> + foundit = true;
> + break;
> + }
> + }
> + if (!foundit) {
> + fprintf(stderr, "%s: mount point must be in command line arguments\n",
> + mo->msgtag);
> + return mount_service_send_reply(mo, EINVAL);
> + }
> +
> + mntpt = strdup(oc->value);
> + if (!mntpt) {
> + int error = errno;
> +
> + fprintf(stderr, "%s: alloc mount point string: %s\n",
> + mo->msgtag, strerror(error));
> + return mount_service_send_reply(mo, error);
> + }
> +
> + return attach_to_mountpoint(mo, expected_fmt, mntpt);
> +}
> +
> +static inline int format_libfuse_mntopts(char *buf, size_t bufsz,
> + const struct mount_service *mo,
> + const struct stat *stbuf)
> +{
> + if (mo->mntopts)
> + return snprintf(buf, bufsz,
> + "%s,fd=%i,rootmode=%o,user_id=%u,group_id=%u",
> + mo->mntopts, mo->fusedevfd,
> + stbuf->st_mode & S_IFMT,
> + getuid(), getgid());
> +
> + return snprintf(buf, bufsz,
> + "fd=%i,rootmode=%o,user_id=%u,group_id=%u",
> + mo->fusedevfd, stbuf->st_mode & S_IFMT,
> + getuid(), getgid());
> +}
> +
> +static int mount_service_regular_mount(struct mount_service *mo,
> + struct fuse_service_mount_command *oc,
> + struct stat *stbuf)
> +{
> + char *fstype = NULL;
> + char *realmopts;
> + int ret;
> +
> + /* Compute the amount of buffer space needed for the mount options */
> + ret = format_libfuse_mntopts(NULL, 0, mo, stbuf);
> + if (ret < 0) {
> + int error = errno;
> +
> + fprintf(stderr, "%s: mount option preformatting: %s\n",
> + mo->msgtag, strerror(error));
> + return mount_service_send_reply(mo, error);
> + }
> +
> + realmopts = calloc(1, ret + 1);
> + if (!realmopts) {
> + int error = errno;
> +
> + fprintf(stderr, "%s: alloc real mount options string: %s\n",
> + mo->msgtag, strerror(error));
> + return mount_service_send_reply(mo, error);
> + }
> +
> + ret = format_libfuse_mntopts(realmopts, ret + 1, mo, stbuf);
> + if (ret < 0) {
> + int error = errno;
> +
> + fprintf(stderr, "%s: mount options formatting: %s\n",
> + mo->msgtag, strerror(error));
> + ret = mount_service_send_reply(mo, error);
> + goto out_realmopts;
> + }
> +
> + asprintf(&fstype, "%s.%s", fsname(mo), mo->subtype);
> + if (!fstype) {
> + int error = errno;
> +
> + fprintf(stderr, "%s: mount fstype formatting: %s\n",
> + mo->msgtag, strerror(error));
> + ret = mount_service_send_reply(mo, error);
> + goto out_realmopts;
> + }
> +
> + ret = mount(mo->source, mo->real_mountpoint, fstype,
> + ntohl(oc->ms_flags), realmopts);
> + if (ret) {
> + int error = errno;
> +
> + fprintf(stderr, "%s: mount: %s\n",
> + mo->msgtag, strerror(error));
> + ret = mount_service_send_reply(mo, error);
> + goto out_fstype;
> + }
> +
> + mo->mounted = true;
> + ret = mount_service_send_reply(mo, 0);
> +out_fstype:
> + free(fstype);
> +out_realmopts:
> + free(realmopts);
> + return ret;
> +}
> +
> +static int mount_service_handle_mount_cmd(struct mount_service *mo,
> + struct fuse_service_packet *p,
> + size_t psz)
> +{
> + struct stat stbuf;
> + struct fuse_service_mount_command *oc =
> + container_of(p, struct fuse_service_mount_command, p);
> + int ret;
> +
> + if (psz != sizeof(struct fuse_service_mount_command)) {
> + fprintf(stderr, "%s: mount command wrong size %zu, expected %zu\n",
> + mo->msgtag, psz, sizeof(*oc));
> + return mount_service_send_reply(mo, EINVAL);
> + }
> +
> + if (!mo->source) {
> + fprintf(stderr, "%s: missing mount source parameter\n",
> + mo->msgtag);
> + return mount_service_send_reply(mo, EINVAL);
> + }
> +
> + if (!mo->mountpoint) {
> + fprintf(stderr, "%s: missing mount point parameter\n",
> + mo->msgtag);
> + return mount_service_send_reply(mo, EINVAL);
> + }
> +
> + /*
> + * Call fstat again because access modes might have changed since we
> + * validated the file type. This is still racy with mount since we
> + * don't lock the path target.
> + */
> + ret = fstat(mo->mountfd, &stbuf);
> + if (ret < 0) {
> + int error = errno;
> +
> + fprintf(stderr, "%s: %s: %s\n",
> + mo->msgtag, mo->mountpoint, strerror(error));
> + return mount_service_send_reply(mo, error);
> + }
> +
> + return mount_service_regular_mount(mo, oc, &stbuf);
> +}
> +
> +static int mount_service_handle_unmount_cmd(struct mount_service *mo,
> + struct fuse_service_packet *p,
> + size_t psz)
> +{
> + int ret;
> +
> + (void)p;
> +
> + if (psz != sizeof(struct fuse_service_unmount_command)) {
> + fprintf(stderr, "%s: unmount command wrong size %zu, expected %zu\n",
> + mo->msgtag, psz, sizeof(struct fuse_service_unmount_command));
> + return mount_service_send_reply(mo, EINVAL);
> + }
> +
> + if (!mo->mounted) {
> + fprintf(stderr, "%s: will not umount before successful mount!\n",
> + mo->msgtag);
> + return mount_service_send_reply(mo, EINVAL);
> + }
> +
> + ret = chdir("/");
> + if (ret) {
> + int error = errno;
> +
> + fprintf(stderr, "%s: fuse server failed chdir: %s\n",
> + mo->msgtag, strerror(error));
> + return mount_service_send_reply(mo, error);
> + }
> +
> + close(mo->mountfd);
> + mo->mountfd = -1;
> +
> + /*
> + * Try to unmount the resolved mountpoint, and hope that we're not the
> + * victim of a race.
> + */
> + ret = umount2(mo->resv_mountpoint, MNT_DETACH);
> + if (ret) {
> + int error = errno;
> +
> + fprintf(stderr, "%s: fuse server failed unmount: %s\n",
> + mo->msgtag, strerror(error));
> + return mount_service_send_reply(mo, error);
> + }
> +
> + mo->mounted = false;
> + return mount_service_send_reply(mo, 0);
> +}
> +
> +static int mount_service_handle_bye_cmd(struct mount_service *mo,
> + struct fuse_service_packet *p,
> + size_t psz)
> +{
> + struct fuse_service_bye_command *bc =
> + container_of(p, struct fuse_service_bye_command, p);
> + int ret;
> +
> + if (psz != sizeof(struct fuse_service_bye_command)) {
> + fprintf(stderr, "%s: bye command wrong size %zu, expected %zu\n",
> + mo->msgtag, psz, sizeof(*bc));
> + return mount_service_send_reply(mo, EINVAL);
> + }
> +
> + ret = ntohl(bc->exitcode);
> + if (ret)
> + fprintf(stderr, "%s: fuse server failed mount, check dmesg/logs for details.\n",
> + mo->msgtag);
> +
> + return ret;
> +}
> +
> +static void mount_service_destroy(struct mount_service *mo)
> +{
> + close(mo->mountfd);
> + close(mo->fusedevfd);
> + close(mo->argvfd);
> + shutdown(mo->sockfd, SHUT_RDWR);
> + close(mo->sockfd);
> +
> + free(mo->source);
> + free(mo->mountpoint);
> + free(mo->real_mountpoint);
> + free(mo->resv_mountpoint);
> + free(mo->mntopts);
> + free(mo->subtype);
> +
> + memset(mo, 0, sizeof(*mo));
> + mo->sockfd = -1;
> + mo->argvfd = -1;
> + mo->fusedevfd = -1;
> + mo->mountfd = -1;
> +}
> +
> +int mount_service_main(int argc, char *argv[])
> +{
> + const char *fusedev = fuse_mnt_get_devname();
> + struct mount_service mo = { };
> + bool running = true;
> + int ret;
> +
> + if (argc < 3 || !strcmp(argv[1], "--help")) {
> + printf("Usage: %s source mountpoint -t type [-o options]\n",
> + argv[0]);
> + return EXIT_FAILURE;
> + }
> +
> + if (argc > 0 && argv[0])
> + mo.msgtag = argv[0];
> + else
> + mo.msgtag = "mount.service";
> +
> + ret = mount_service_init(&mo, argc, argv);
> + if (ret)
> + return EXIT_FAILURE;
> +
> + ret = mount_service_connect(&mo);
> + if (ret == MOUNT_SERVICE_FALLBACK_NEEDED)
> + goto out;
> + if (ret) {
> + ret = EXIT_FAILURE;
> + goto out;
> + }
> +
> + ret = mount_service_send_hello(&mo);
> + if (ret) {
> + ret = EXIT_FAILURE;
> + goto out;
> + }
> +
> + ret = mount_service_capture_args(&mo, argc, argv);
> + if (ret) {
> + ret = EXIT_FAILURE;
> + goto out;
> + }
> +
> + ret = mount_service_send_required_files(&mo, fusedev);
> + if (ret) {
> + ret = EXIT_FAILURE;
> + goto out;
> + }
> +
> + while (running) {
> + struct fuse_service_packet *p = NULL;
> + size_t sz;
> +
> + ret = mount_service_receive_command(&mo, &p, &sz);
> + if (ret) {
> + ret = EXIT_FAILURE;
> + goto out;
> + }
> +
> + switch (ntohl(p->magic)) {
> + case FUSE_SERVICE_OPEN_CMD:
> + ret = mount_service_handle_open_cmd(&mo, p, sz);
> + break;
> + case FUSE_SERVICE_OPEN_BDEV_CMD:
> + ret = mount_service_handle_open_bdev_cmd(&mo, p, sz);
> + break;
> + case FUSE_SERVICE_FSOPEN_CMD:
> + ret = mount_service_handle_fsopen_cmd(&mo, p, sz);
> + break;
> + case FUSE_SERVICE_SOURCE_CMD:
> + ret = mount_service_handle_source_cmd(&mo, p, sz);
> + break;
> + case FUSE_SERVICE_MNTOPTS_CMD:
> + ret = mount_service_handle_mntopts_cmd(&mo, p, sz);
> + break;
> + case FUSE_SERVICE_MNTPT_CMD:
> + ret = mount_service_handle_mountpoint_cmd(&mo, p, sz,
> + argc, argv);
> + break;
> + case FUSE_SERVICE_MOUNT_CMD:
> + ret = mount_service_handle_mount_cmd(&mo, p, sz);
> + break;
> + case FUSE_SERVICE_UNMOUNT_CMD:
> + ret = mount_service_handle_unmount_cmd(&mo, p, sz);
> + break;
> + case FUSE_SERVICE_BYE_CMD:
> + ret = mount_service_handle_bye_cmd(&mo, p, sz);
> + free(p);
> + goto out;
> + default:
> + fprintf(stderr, "%s: unrecognized packet 0x%x\n",
> + mo.msgtag, ntohl(p->magic));
> + ret = EXIT_FAILURE;
> + break;
> + }
> + free(p);
> +
> + if (ret) {
> + ret = EXIT_FAILURE;
> + goto out;
> + }
> + }
> +
> + ret = EXIT_SUCCESS;
> +out:
> + mount_service_destroy(&mo);
> + return ret;
> +}
>
>
^ permalink raw reply
* [PATCH v13 00/15] Exposing case folding behavior
From: Chuck Lever @ 2026-05-02 14:20 UTC (permalink / raw)
To: Al Viro, Christian Brauner, Jan Kara
Cc: linux-fsdevel, linux-ext4, linux-xfs, linux-cifs, linux-nfs,
linux-api, linux-f2fs-devel, hirofumi, linkinjeon, sj1557.seo,
yuezhang.mo, almaz.alexandrovich, slava, glaubitz, frank.li,
tytso, adilger.kernel, cem, sfrench, pc, ronniesahlberg, sprasad,
trondmy, anna, jaegeuk, chao, hansg, senozhatsky, Chuck Lever,
Darrick J. Wong, Roland Mainz, Steve French
Following on from:
https://lore.kernel.org/linux-nfs/20251021-zypressen-bazillus-545a44af57fd@brauner/T/#m0ba197d75b7921d994cf284f3cef3a62abb11aaa
I'm attempting to implement enough support in the Linux VFS to
enable file services like NFSD and ksmbd (and user space
equivalents) to provide the actual status of case folding support
in local file systems. The default behavior for local file systems
not explicitly supported in this series is to reflect the usual
POSIX behaviors:
case-insensitive = false
case-nonpreserving = false
The case-insensitivity and case-nonpreserving booleans can be
consumed immediately by NFSD. These two attributes have been part of
the NFSv3 and NFSv4 protocols for decades, in order to support NFS
client implementations on non-POSIX systems.
Support for user space file servers is why this series exposes case
folding information via a user-space API. I don't know of any other
category of user-space application that requires access to case
folding info.
The Linux NFS community has a growing interest in supporting NFS
clients on Windows and MacOS platforms, where file name behavior does
not align with traditional POSIX semantics.
One example of a Windows-based NFS client is [1]. This client
implementation explicitly requires servers to report
FATTR4_WORD0_CASE_INSENSITIVE = TRUE for proper operation, a hard
requirement for Windows client interoperability because Windows
applications expect case-insensitive behavior. When an NFS client
knows the server is case-insensitive, it can avoid issuing multiple
LOOKUP/READDIR requests to search for case variants, and applications
like Win32 programs work correctly without manual workarounds or
code changes.
Even the Linux client can take advantage of this information. Trond
merged patches 4 years ago [2] that introduce support for case
insensitivity, in support of the Hammerspace NFS server. In
particular, when a client detects a case-insensitive NFS share,
negative dentry caching must be disabled (a lookup for "FILE.TXT"
failing shouldn't cache a negative entry when "file.txt" exists)
and directory change invalidation must clear all cached case-folded
file name variants.
Hammerspace servers and several other NFS server implementations
operate in multi-protocol environments, where a single file service
instance caters to both NFS and SMB clients. In those cases, things
work more smoothly for everyone when the NFS client can see and adapt
to the case folding behavior that SMB users rely on and expect. NFSD
needs to support the case-insensitivity and case-nonpreserving
booleans properly in order to participate as a first-class citizen
in such environments.
[1] https://github.com/kofemann/ms-nfs41-client
[2] https://patchwork.kernel.org/project/linux-nfs/cover/20211217203658.439352-1-trondmy@kernel.org/
---
Changes since v12:
- Address findings from sashiko (gemini-3.1):
- cifs: Restrict case-handling flags to directories per UAPI
- nfs: Clear case caps before PATHCONF so a failed reply
does not retain stale bits from the prior probe
- nfsd: Document the parent-resolution corner cases of
nfsd_get_case_info() (single-file exports, disconnected
dentries, hardlinks) in the v3 and v4 commit messages
Changes since v11:
- isofs: Wire .fileattr_get only on directory inodes, since
NFSD and ksmbd query casefolding on directories (Jan Kara)
- xfs, hfsplus: Drop the FS_CASEFOLD_FL fileattr_get mask;
admit the bit through fileattr_set's allowlist instead
- Address findings from sashiko(gemini-3) and gpt-5.5:
- cifs: Wire .fileattr_get on cifs_namespace_inode_operations
so DFS referral / automount directories report case handling
- fat, ntfs3: Fill FS_IMMUTABLE_FL in fileattr_get
- hfsplus: Hide FS_CASEFOLD_FL from the legacy flags view so
chattr round-trips do not hit the setflags whitelist
- nfs: Clear NFS_CAP_CASE_INSENSITIVE and
NFS_CAP_CASE_NONPRESERVING before re-OR'ing in the v3 and
v4 probe paths so re-probe / TSM does not retain stale caps
- nfsd: Switch nfsd_get_case_info() to errno return so
v3 PATHCONF and v4 GETATTR can apply version-appropriate
policy on failure
- nfsd: Use dget_parent() in v4 case-attr probe to keep
the parent dentry referenced across the query
- isofs: Report FS_XFLAG_CASENONPRESERVING for map=n/map=a
Changes since v10:
- cifs: Source case-handling flags from the server's cached
FS_ATTRIBUTE_INFORMATION reply instead of the nocase mount
option, with a nocase fallback when the reply is absent
- Address findings from sashiko(gemini-3) and gpt-5.5:
- nfs: Skip pathconf case bits on NFSv4 (set via FATTR4_CASE_*
instead)
- xfs: Hide FS_CASEFOLD_FL from the legacy flags view so
chattr round-trips do not hit the setflags whitelist
- ext4, f2fs: Drop redundant fileattr_get patches; the
FS_CASEFOLD_FL translation in fileattr_fill_flags() already
reports FS_XFLAG_CASEFOLD for casefolded directories
- nfsd: Report FATTR4_HOMOGENEOUS = FALSE when the exported
filesystem has a Unicode encoding, since per-directory
casefold makes the fs-scoped case attributes inhomogeneous
- nfsd: Document in nfsd_get_case_info() why -ENOIOCTLCMD and
-ENOTTY are swallowed while other errors propagate
- fat: Honor vfat 'check=strict' when reporting FS_XFLAG_CASEFOLD
- Set FS_CASEFOLD_FL so FS_IOC_GETFLAGS reflects case-insensitive
mount
- isofs: Register fileattr_get on regular file and symlink inodes,
not just directories
- nfsd: Query NFSv4 FATTR4_CASE_* from the parent directory for
non-directory objects, since casefold lives on the directory
Changes since v9:
- nfs: always probe PATHCONF for case caps. Default to case-
preserving when the server does not report case_preserving
- nfsd, ksmbd: tolerate -ENOTTY from vfs_fileattr_get() so
overlayfs exports on backing filesystems without fileattr_get
do not fail the RPC
- xfs: map FS_XFLAG_CASEFOLD inside xfs_ip2xflags() so BULKSTAT
and FS_IOC_FSGETXATTR report the flag consistently
- vboxsf: reject a short host reply to SHFL_INFO_VOLUME before
trusting volinfo.properties.case_sensitive
Changes since v8:
- Rebase on v7.0-rc1
Changes since v7:
- Split file_attr initialization changes into a separate patch
Changes since v6:
- Remove the memset from vfs_fileattr_get
Changes since v5:
- Finish the conversion to FS_XFLAGs
- NFSv4 GETATTR now clears the attr mask bit if nfsd_get_case_info()
fails
Changes since v4:
- Observe the MSDOS "nocase" mount option
- Define new FS_XFLAGs for the user API
Changes since v3:
- Change fa->case_preserving to fa_case_nonpreserving
- VFAT is case preserving
- Make new fields available to user space
Changes since v2:
- Remove unicode labels
- Replace vfs_get_case_info
- Add support for several more local file system implementations
- Add support for in-kernel SMB server
Changes since RFC:
- Use file_getattr instead of statx
- Postpone exposing Unicode version until later
- Support NTFS and ext4 in addition to FAT
- Support NFSv4 fattr4 in addition to NFSv3 PATHCONF
---
Chuck Lever (15):
fs: Move file_kattr initialization to callers
fs: Add case sensitivity flags to file_kattr
fat: Implement fileattr_get for case sensitivity
exfat: Implement fileattr_get for case sensitivity
ntfs3: Implement fileattr_get for case sensitivity
hfs: Implement fileattr_get for case sensitivity
hfsplus: Report case sensitivity in fileattr_get
xfs: Report case sensitivity in fileattr_get
cifs: Implement fileattr_get for case sensitivity
nfs: Implement fileattr_get for case sensitivity
vboxsf: Implement fileattr_get for case sensitivity
isofs: Implement fileattr_get for case sensitivity
nfsd: Report export case-folding via NFSv3 PATHCONF
nfsd: Implement NFSv4 FATTR4_CASE_INSENSITIVE and FATTR4_CASE_PRESERVING
ksmbd: Report filesystem case sensitivity via FS_ATTRIBUTE_INFORMATION
fs/exfat/exfat_fs.h | 2 ++
fs/exfat/file.c | 18 +++++++++--
fs/exfat/namei.c | 1 +
fs/fat/fat.h | 3 ++
fs/fat/file.c | 36 +++++++++++++++++++++
fs/fat/namei_msdos.c | 1 +
fs/fat/namei_vfat.c | 1 +
fs/file_attr.c | 16 +++++-----
fs/hfs/dir.c | 1 +
fs/hfs/hfs_fs.h | 2 ++
fs/hfs/inode.c | 14 ++++++++
fs/hfsplus/inode.c | 16 +++++++++-
fs/isofs/dir.c | 16 ++++++++++
fs/isofs/isofs.h | 3 ++
fs/nfs/client.c | 21 ++++++++----
fs/nfs/inode.c | 15 +++++++++
fs/nfs/internal.h | 3 ++
fs/nfs/namespace.c | 2 ++
fs/nfs/nfs3proc.c | 2 ++
fs/nfs/nfs3xdr.c | 7 ++--
fs/nfs/nfs4proc.c | 10 ++++--
fs/nfs/proc.c | 3 ++
fs/nfs/symlink.c | 3 ++
fs/nfsd/nfs3proc.c | 36 ++++++++++++++++-----
fs/nfsd/nfs4xdr.c | 52 ++++++++++++++++++++++++++++--
fs/nfsd/vfs.c | 72 ++++++++++++++++++++++++++++++++++++++++++
fs/nfsd/vfs.h | 3 ++
fs/nfsd/xdr3.h | 4 +--
fs/ntfs3/file.c | 29 +++++++++++++++++
fs/ntfs3/inode.c | 1 +
fs/ntfs3/namei.c | 2 ++
fs/ntfs3/ntfs_fs.h | 1 +
fs/smb/client/cifsfs.c | 53 +++++++++++++++++++++++++++++++
fs/smb/client/cifsfs.h | 3 ++
fs/smb/client/namespace.c | 1 +
fs/smb/server/smb2pdu.c | 30 ++++++++++++++----
fs/vboxsf/dir.c | 1 +
fs/vboxsf/file.c | 6 ++--
fs/vboxsf/super.c | 7 ++++
fs/vboxsf/utils.c | 30 ++++++++++++++++++
fs/vboxsf/vfsmod.h | 6 ++++
fs/xfs/libxfs/xfs_inode_util.c | 2 ++
fs/xfs/xfs_ioctl.c | 22 ++++++++++---
include/linux/fileattr.h | 3 +-
include/linux/nfs_fs_sb.h | 2 +-
include/linux/nfs_xdr.h | 2 ++
include/uapi/linux/fs.h | 7 ++++
47 files changed, 522 insertions(+), 49 deletions(-)
---
base-commit: 6596a02b207886e9e00bb0161c7fd59fea53c081
change-id: 20260422-case-sensitivity-5cbffc8f1558
Best regards,
--
Chuck Lever <chuck.lever@oracle.com>
^ permalink raw reply
* [PATCH v13 01/15] fs: Move file_kattr initialization to callers
From: Chuck Lever @ 2026-05-02 14:20 UTC (permalink / raw)
To: Al Viro, Christian Brauner, Jan Kara
Cc: linux-fsdevel, linux-ext4, linux-xfs, linux-cifs, linux-nfs,
linux-api, linux-f2fs-devel, hirofumi, linkinjeon, sj1557.seo,
yuezhang.mo, almaz.alexandrovich, slava, glaubitz, frank.li,
tytso, adilger.kernel, cem, sfrench, pc, ronniesahlberg, sprasad,
trondmy, anna, jaegeuk, chao, hansg, senozhatsky, Chuck Lever,
Darrick J. Wong, Roland Mainz
In-Reply-To: <20260502-case-sensitivity-v13-0-aa853140311f@oracle.com>
From: Chuck Lever <chuck.lever@oracle.com>
fileattr_fill_xflags() and fileattr_fill_flags() memset the
entire file_kattr struct before populating select fields, so
callers cannot pre-set fields in fa->fsx_xflags without having
their values clobbered. Darrick Wong noted that a function
named "fill_xflags" touching more than xflags forces callers
to know implementation details beyond its apparent scope.
Drop the memset from both fill functions and initialize at the
entry points instead: ioctl_setflags(), ioctl_fssetxattr(),
the file_setattr() syscall, and xfs_ioc_fsgetxattra() now
declare fa with an aggregate initializer. ioctl_getflags(),
ioctl_fsgetxattr(), and the file_getattr() syscall already
aggregate-initialize fa to pass flags_valid/fsx_valid hints
into vfs_fileattr_get().
Subsequent patches rely on this so that ->fileattr_get()
handlers can set case-sensitivity flags (FS_XFLAG_CASEFOLD,
FS_XFLAG_CASENONPRESERVING) in fa->fsx_xflags before the fill
functions run.
Suggested-by: Darrick J. Wong <djwong@kernel.org>
Reviewed-by: Jan Kara <jack@suse.cz>
Reviewed-by: Darrick J. Wong <djwong@kernel.org>
Reviewed-by: Roland Mainz <roland.mainz@nrubsig.org>
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
---
fs/file_attr.c | 12 ++++--------
fs/xfs/xfs_ioctl.c | 2 +-
2 files changed, 5 insertions(+), 9 deletions(-)
diff --git a/fs/file_attr.c b/fs/file_attr.c
index da983e105d70..f429da66a317 100644
--- a/fs/file_attr.c
+++ b/fs/file_attr.c
@@ -15,12 +15,10 @@
* @fa: fileattr pointer
* @xflags: FS_XFLAG_* flags
*
- * Set ->fsx_xflags, ->fsx_valid and ->flags (translated xflags). All
- * other fields are zeroed.
+ * Set ->fsx_xflags, ->fsx_valid and ->flags (translated xflags).
*/
void fileattr_fill_xflags(struct file_kattr *fa, u32 xflags)
{
- memset(fa, 0, sizeof(*fa));
fa->fsx_valid = true;
fa->fsx_xflags = xflags;
if (fa->fsx_xflags & FS_XFLAG_IMMUTABLE)
@@ -48,11 +46,9 @@ EXPORT_SYMBOL(fileattr_fill_xflags);
* @flags: FS_*_FL flags
*
* Set ->flags, ->flags_valid and ->fsx_xflags (translated flags).
- * All other fields are zeroed.
*/
void fileattr_fill_flags(struct file_kattr *fa, u32 flags)
{
- memset(fa, 0, sizeof(*fa));
fa->flags_valid = true;
fa->flags = flags;
if (fa->flags & FS_SYNC_FL)
@@ -325,7 +321,7 @@ int ioctl_setflags(struct file *file, unsigned int __user *argp)
{
struct mnt_idmap *idmap = file_mnt_idmap(file);
struct dentry *dentry = file->f_path.dentry;
- struct file_kattr fa;
+ struct file_kattr fa = {};
unsigned int flags;
int err;
@@ -357,7 +353,7 @@ int ioctl_fssetxattr(struct file *file, void __user *argp)
{
struct mnt_idmap *idmap = file_mnt_idmap(file);
struct dentry *dentry = file->f_path.dentry;
- struct file_kattr fa;
+ struct file_kattr fa = {};
int err;
err = copy_fsxattr_from_user(&fa, argp);
@@ -431,7 +427,7 @@ SYSCALL_DEFINE5(file_setattr, int, dfd, const char __user *, filename,
struct path filepath __free(path_put) = {};
unsigned int lookup_flags = 0;
struct file_attr fattr;
- struct file_kattr fa;
+ struct file_kattr fa = {};
int error;
BUILD_BUG_ON(sizeof(struct file_attr) < FILE_ATTR_SIZE_VER0);
diff --git a/fs/xfs/xfs_ioctl.c b/fs/xfs/xfs_ioctl.c
index 46e234863644..ed9b4846c05f 100644
--- a/fs/xfs/xfs_ioctl.c
+++ b/fs/xfs/xfs_ioctl.c
@@ -517,7 +517,7 @@ xfs_ioc_fsgetxattra(
xfs_inode_t *ip,
void __user *arg)
{
- struct file_kattr fa;
+ struct file_kattr fa = {};
xfs_ilock(ip, XFS_ILOCK_SHARED);
xfs_fill_fsxattr(ip, XFS_ATTR_FORK, &fa);
--
2.53.0
^ permalink raw reply related
* [PATCH v13 02/15] fs: Add case sensitivity flags to file_kattr
From: Chuck Lever @ 2026-05-02 14:20 UTC (permalink / raw)
To: Al Viro, Christian Brauner, Jan Kara
Cc: linux-fsdevel, linux-ext4, linux-xfs, linux-cifs, linux-nfs,
linux-api, linux-f2fs-devel, hirofumi, linkinjeon, sj1557.seo,
yuezhang.mo, almaz.alexandrovich, slava, glaubitz, frank.li,
tytso, adilger.kernel, cem, sfrench, pc, ronniesahlberg, sprasad,
trondmy, anna, jaegeuk, chao, hansg, senozhatsky, Chuck Lever,
Darrick J. Wong, Roland Mainz
In-Reply-To: <20260502-case-sensitivity-v13-0-aa853140311f@oracle.com>
From: Chuck Lever <chuck.lever@oracle.com>
Enable upper layers such as NFSD to retrieve case sensitivity
information from file systems by adding FS_XFLAG_CASEFOLD and
FS_XFLAG_CASENONPRESERVING flags.
Filesystems report case-insensitive or case-nonpreserving behavior
by setting these flags directly in fa->fsx_xflags. The default
(flags unset) indicates POSIX semantics: case-sensitive and
case-preserving. Both flags are added to FS_XFLAG_RDONLY_MASK so
FS_IOC_FSSETXATTR silently strips them, keeping the new xflags
strictly a reporting interface. Callers that want to toggle
casefolding continue to use FS_IOC_SETFLAGS with FS_CASEFOLD_FL,
the established UAPI on filesystems that support the operation
(ext4 and f2fs on empty directories).
Case sensitivity information is exported to userspace via the
fa_xflags field in the FS_IOC_FSGETXATTR ioctl and file_getattr()
system call.
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
Reviewed-by: Jan Kara <jack@suse.cz>
Reviewed-by: Roland Mainz <roland.mainz@nrubsig.org>
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
---
fs/file_attr.c | 4 ++++
include/linux/fileattr.h | 3 ++-
include/uapi/linux/fs.h | 7 +++++++
3 files changed, 13 insertions(+), 1 deletion(-)
diff --git a/fs/file_attr.c b/fs/file_attr.c
index f429da66a317..bfb00d256dd5 100644
--- a/fs/file_attr.c
+++ b/fs/file_attr.c
@@ -37,6 +37,8 @@ void fileattr_fill_xflags(struct file_kattr *fa, u32 xflags)
fa->flags |= FS_PROJINHERIT_FL;
if (fa->fsx_xflags & FS_XFLAG_VERITY)
fa->flags |= FS_VERITY_FL;
+ if (fa->fsx_xflags & FS_XFLAG_CASEFOLD)
+ fa->flags |= FS_CASEFOLD_FL;
}
EXPORT_SYMBOL(fileattr_fill_xflags);
@@ -67,6 +69,8 @@ void fileattr_fill_flags(struct file_kattr *fa, u32 flags)
fa->fsx_xflags |= FS_XFLAG_PROJINHERIT;
if (fa->flags & FS_VERITY_FL)
fa->fsx_xflags |= FS_XFLAG_VERITY;
+ if (fa->flags & FS_CASEFOLD_FL)
+ fa->fsx_xflags |= FS_XFLAG_CASEFOLD;
}
EXPORT_SYMBOL(fileattr_fill_flags);
diff --git a/include/linux/fileattr.h b/include/linux/fileattr.h
index 3780904a63a6..58044b598016 100644
--- a/include/linux/fileattr.h
+++ b/include/linux/fileattr.h
@@ -16,7 +16,8 @@
/* Read-only inode flags */
#define FS_XFLAG_RDONLY_MASK \
- (FS_XFLAG_PREALLOC | FS_XFLAG_HASATTR | FS_XFLAG_VERITY)
+ (FS_XFLAG_PREALLOC | FS_XFLAG_HASATTR | FS_XFLAG_VERITY | \
+ FS_XFLAG_CASEFOLD | FS_XFLAG_CASENONPRESERVING)
/* Flags to indicate valid value of fsx_ fields */
#define FS_XFLAG_VALUES_MASK \
diff --git a/include/uapi/linux/fs.h b/include/uapi/linux/fs.h
index 13f71202845e..2ea4c81df08f 100644
--- a/include/uapi/linux/fs.h
+++ b/include/uapi/linux/fs.h
@@ -254,6 +254,13 @@ struct file_attr {
#define FS_XFLAG_DAX 0x00008000 /* use DAX for IO */
#define FS_XFLAG_COWEXTSIZE 0x00010000 /* CoW extent size allocator hint */
#define FS_XFLAG_VERITY 0x00020000 /* fs-verity enabled */
+/*
+ * Case handling flags (read-only, cannot be set via ioctl).
+ * Default (neither set) indicates POSIX semantics: case-sensitive
+ * lookups and case-preserving storage.
+ */
+#define FS_XFLAG_CASEFOLD 0x00040000 /* case-insensitive lookups */
+#define FS_XFLAG_CASENONPRESERVING 0x00080000 /* case not preserved */
#define FS_XFLAG_HASATTR 0x80000000 /* no DIFLAG for this */
/* the read-only stuff doesn't really belong here, but any other place is
--
2.53.0
^ permalink raw reply related
* [PATCH v13 03/15] fat: Implement fileattr_get for case sensitivity
From: Chuck Lever @ 2026-05-02 14:20 UTC (permalink / raw)
To: Al Viro, Christian Brauner, Jan Kara
Cc: linux-fsdevel, linux-ext4, linux-xfs, linux-cifs, linux-nfs,
linux-api, linux-f2fs-devel, hirofumi, linkinjeon, sj1557.seo,
yuezhang.mo, almaz.alexandrovich, slava, glaubitz, frank.li,
tytso, adilger.kernel, cem, sfrench, pc, ronniesahlberg, sprasad,
trondmy, anna, jaegeuk, chao, hansg, senozhatsky, Chuck Lever,
Roland Mainz
In-Reply-To: <20260502-case-sensitivity-v13-0-aa853140311f@oracle.com>
From: Chuck Lever <chuck.lever@oracle.com>
Report FAT's case sensitivity behavior via the FS_XFLAG_CASEFOLD
and FS_XFLAG_CASENONPRESERVING flags. FAT filesystems are
case-insensitive by default.
MSDOS supports a 'nocase' mount option that enables case-sensitive
behavior; check this option when reporting case sensitivity.
VFAT long filename entries preserve case; without VFAT, only
uppercased 8.3 short names are stored. MSDOS with 'nocase' also
preserves case since the name-formatting code skips upcasing when
'nocase' is set. Check both options when reporting case preservation.
Reviewed-by: Jan Kara <jack@suse.cz>
Reviewed-by: Roland Mainz <roland.mainz@nrubsig.org>
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
---
fs/fat/fat.h | 3 +++
fs/fat/file.c | 36 ++++++++++++++++++++++++++++++++++++
fs/fat/namei_msdos.c | 1 +
fs/fat/namei_vfat.c | 1 +
4 files changed, 41 insertions(+)
diff --git a/fs/fat/fat.h b/fs/fat/fat.h
index 5a58f0bf8ce8..99ed9228a677 100644
--- a/fs/fat/fat.h
+++ b/fs/fat/fat.h
@@ -10,6 +10,8 @@
#include <linux/fs_context.h>
#include <linux/fs_parser.h>
+struct file_kattr;
+
/*
* vfat shortname flags
*/
@@ -408,6 +410,7 @@ extern void fat_truncate_blocks(struct inode *inode, loff_t offset);
extern int fat_getattr(struct mnt_idmap *idmap,
const struct path *path, struct kstat *stat,
u32 request_mask, unsigned int flags);
+int fat_fileattr_get(struct dentry *dentry, struct file_kattr *fa);
extern int fat_file_fsync(struct file *file, loff_t start, loff_t end,
int datasync);
diff --git a/fs/fat/file.c b/fs/fat/file.c
index becccdd2e501..37e7049b4c8c 100644
--- a/fs/fat/file.c
+++ b/fs/fat/file.c
@@ -17,6 +17,7 @@
#include <linux/fsnotify.h>
#include <linux/security.h>
#include <linux/falloc.h>
+#include <linux/fileattr.h>
#include "fat.h"
static long fat_fallocate(struct file *file, int mode,
@@ -398,6 +399,40 @@ void fat_truncate_blocks(struct inode *inode, loff_t offset)
fat_flush_inodes(inode->i_sb, inode, NULL);
}
+int fat_fileattr_get(struct dentry *dentry, struct file_kattr *fa)
+{
+ struct msdos_sb_info *sbi = MSDOS_SB(dentry->d_sb);
+ bool case_sensitive;
+
+ /*
+ * FAT filesystems are case-insensitive by default. VFAT
+ * becomes case-sensitive when mounted with 'check=strict',
+ * which installs vfat_dentry_ops. MSDOS has no such option;
+ * its 'nocase' mount option selects case-sensitive matching.
+ *
+ * VFAT long filename entries preserve case. Without VFAT, only
+ * uppercased 8.3 short names are stored. MSDOS with 'nocase'
+ * also preserves case.
+ */
+ if (sbi->options.isvfat)
+ case_sensitive = sbi->options.name_check == 's';
+ else
+ case_sensitive = sbi->options.nocase;
+
+ if (!case_sensitive) {
+ fa->fsx_xflags |= FS_XFLAG_CASEFOLD;
+ fa->flags |= FS_CASEFOLD_FL;
+ if (!sbi->options.isvfat)
+ fa->fsx_xflags |= FS_XFLAG_CASENONPRESERVING;
+ }
+ if (d_inode(dentry)->i_flags & S_IMMUTABLE) {
+ fa->fsx_xflags |= FS_XFLAG_IMMUTABLE;
+ fa->flags |= FS_IMMUTABLE_FL;
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(fat_fileattr_get);
+
int fat_getattr(struct mnt_idmap *idmap, const struct path *path,
struct kstat *stat, u32 request_mask, unsigned int flags)
{
@@ -575,5 +610,6 @@ EXPORT_SYMBOL_GPL(fat_setattr);
const struct inode_operations fat_file_inode_operations = {
.setattr = fat_setattr,
.getattr = fat_getattr,
+ .fileattr_get = fat_fileattr_get,
.update_time = fat_update_time,
};
diff --git a/fs/fat/namei_msdos.c b/fs/fat/namei_msdos.c
index 4cc65f330fb7..0fd2971ad4b1 100644
--- a/fs/fat/namei_msdos.c
+++ b/fs/fat/namei_msdos.c
@@ -644,6 +644,7 @@ static const struct inode_operations msdos_dir_inode_operations = {
.rename = msdos_rename,
.setattr = fat_setattr,
.getattr = fat_getattr,
+ .fileattr_get = fat_fileattr_get,
.update_time = fat_update_time,
};
diff --git a/fs/fat/namei_vfat.c b/fs/fat/namei_vfat.c
index 918b3756674c..e909447873e3 100644
--- a/fs/fat/namei_vfat.c
+++ b/fs/fat/namei_vfat.c
@@ -1185,6 +1185,7 @@ static const struct inode_operations vfat_dir_inode_operations = {
.rename = vfat_rename2,
.setattr = fat_setattr,
.getattr = fat_getattr,
+ .fileattr_get = fat_fileattr_get,
.update_time = fat_update_time,
};
--
2.53.0
^ permalink raw reply related
* [PATCH v13 04/15] exfat: Implement fileattr_get for case sensitivity
From: Chuck Lever @ 2026-05-02 14:20 UTC (permalink / raw)
To: Al Viro, Christian Brauner, Jan Kara
Cc: linux-fsdevel, linux-ext4, linux-xfs, linux-cifs, linux-nfs,
linux-api, linux-f2fs-devel, hirofumi, linkinjeon, sj1557.seo,
yuezhang.mo, almaz.alexandrovich, slava, glaubitz, frank.li,
tytso, adilger.kernel, cem, sfrench, pc, ronniesahlberg, sprasad,
trondmy, anna, jaegeuk, chao, hansg, senozhatsky, Chuck Lever,
Roland Mainz
In-Reply-To: <20260502-case-sensitivity-v13-0-aa853140311f@oracle.com>
From: Chuck Lever <chuck.lever@oracle.com>
Report exFAT's case sensitivity behavior via the FS_XFLAG_CASEFOLD
flag. exFAT compares names through the volume's upcase table; in
practice that table folds case, and case is preserved at rest.
Acked-by: Namjae Jeon <linkinjeon@kernel.org>
Reviewed-by: Roland Mainz <roland.mainz@nrubsig.org>
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
---
fs/exfat/exfat_fs.h | 2 ++
fs/exfat/file.c | 18 ++++++++++++++++--
fs/exfat/namei.c | 1 +
3 files changed, 19 insertions(+), 2 deletions(-)
diff --git a/fs/exfat/exfat_fs.h b/fs/exfat/exfat_fs.h
index 89ef5368277f..aff4dcd4e75a 100644
--- a/fs/exfat/exfat_fs.h
+++ b/fs/exfat/exfat_fs.h
@@ -496,6 +496,8 @@ int exfat_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
int exfat_getattr(struct mnt_idmap *idmap, const struct path *path,
struct kstat *stat, unsigned int request_mask,
unsigned int query_flags);
+struct file_kattr;
+int exfat_fileattr_get(struct dentry *dentry, struct file_kattr *fa);
int exfat_file_fsync(struct file *file, loff_t start, loff_t end, int datasync);
long exfat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg);
long exfat_compat_ioctl(struct file *filp, unsigned int cmd,
diff --git a/fs/exfat/file.c b/fs/exfat/file.c
index 354bdcfe4abc..91e5511945d1 100644
--- a/fs/exfat/file.c
+++ b/fs/exfat/file.c
@@ -14,6 +14,7 @@
#include <linux/writeback.h>
#include <linux/filelock.h>
#include <linux/falloc.h>
+#include <linux/fileattr.h>
#include "exfat_raw.h"
#include "exfat_fs.h"
@@ -323,6 +324,18 @@ int exfat_getattr(struct mnt_idmap *idmap, const struct path *path,
return 0;
}
+int exfat_fileattr_get(struct dentry *dentry, struct file_kattr *fa)
+{
+ /*
+ * exFAT compares filenames through an upcase table, so lookup
+ * is always case-insensitive. Long names are stored in UTF-16
+ * with case intact; CASENONPRESERVING stays clear.
+ */
+ fa->fsx_xflags |= FS_XFLAG_CASEFOLD;
+ fa->flags |= FS_CASEFOLD_FL;
+ return 0;
+}
+
int exfat_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
struct iattr *attr)
{
@@ -817,6 +830,7 @@ const struct file_operations exfat_file_operations = {
};
const struct inode_operations exfat_file_inode_operations = {
- .setattr = exfat_setattr,
- .getattr = exfat_getattr,
+ .setattr = exfat_setattr,
+ .getattr = exfat_getattr,
+ .fileattr_get = exfat_fileattr_get,
};
diff --git a/fs/exfat/namei.c b/fs/exfat/namei.c
index 2c5636634b4a..94002e43db08 100644
--- a/fs/exfat/namei.c
+++ b/fs/exfat/namei.c
@@ -1311,4 +1311,5 @@ const struct inode_operations exfat_dir_inode_operations = {
.rename = exfat_rename,
.setattr = exfat_setattr,
.getattr = exfat_getattr,
+ .fileattr_get = exfat_fileattr_get,
};
--
2.53.0
^ permalink raw reply related
* [PATCH v13 05/15] ntfs3: Implement fileattr_get for case sensitivity
From: Chuck Lever @ 2026-05-02 14:20 UTC (permalink / raw)
To: Al Viro, Christian Brauner, Jan Kara
Cc: linux-fsdevel, linux-ext4, linux-xfs, linux-cifs, linux-nfs,
linux-api, linux-f2fs-devel, hirofumi, linkinjeon, sj1557.seo,
yuezhang.mo, almaz.alexandrovich, slava, glaubitz, frank.li,
tytso, adilger.kernel, cem, sfrench, pc, ronniesahlberg, sprasad,
trondmy, anna, jaegeuk, chao, hansg, senozhatsky, Chuck Lever,
Roland Mainz
In-Reply-To: <20260502-case-sensitivity-v13-0-aa853140311f@oracle.com>
From: Chuck Lever <chuck.lever@oracle.com>
Report NTFS case sensitivity behavior via the FS_XFLAG_CASEFOLD
flag. NTFS always preserves case at rest.
Reviewed-by: Roland Mainz <roland.mainz@nrubsig.org>
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
---
fs/ntfs3/file.c | 29 +++++++++++++++++++++++++++++
fs/ntfs3/inode.c | 1 +
fs/ntfs3/namei.c | 2 ++
fs/ntfs3/ntfs_fs.h | 1 +
4 files changed, 33 insertions(+)
diff --git a/fs/ntfs3/file.c b/fs/ntfs3/file.c
index b041639ab406..ad9350d7fc3f 100644
--- a/fs/ntfs3/file.c
+++ b/fs/ntfs3/file.c
@@ -180,6 +180,34 @@ long ntfs_compat_ioctl(struct file *filp, u32 cmd, unsigned long arg)
}
#endif
+/*
+ * ntfs_fileattr_get - inode_operations::fileattr_get
+ */
+int ntfs_fileattr_get(struct dentry *dentry, struct file_kattr *fa)
+{
+ struct inode *inode = d_inode(dentry);
+ struct ntfs_sb_info *sbi = inode->i_sb->s_fs_info;
+
+ /* Avoid any operation if inode is bad. */
+ if (unlikely(is_bad_ni(ntfs_i(inode))))
+ return -EINVAL;
+
+ /*
+ * NTFS preserves case (the default). Case sensitivity depends on
+ * mount options: with "nocase", NTFS is case-insensitive;
+ * otherwise it is case-sensitive.
+ */
+ if (sbi->options->nocase) {
+ fa->fsx_xflags |= FS_XFLAG_CASEFOLD;
+ fa->flags |= FS_CASEFOLD_FL;
+ }
+ if (inode->i_flags & S_IMMUTABLE) {
+ fa->fsx_xflags |= FS_XFLAG_IMMUTABLE;
+ fa->flags |= FS_IMMUTABLE_FL;
+ }
+ return 0;
+}
+
/*
* ntfs_getattr - inode_operations::getattr
*/
@@ -1547,6 +1575,7 @@ const struct inode_operations ntfs_file_inode_operations = {
.get_acl = ntfs_get_acl,
.set_acl = ntfs_set_acl,
.fiemap = ntfs_fiemap,
+ .fileattr_get = ntfs_fileattr_get,
};
const struct file_operations ntfs_file_operations = {
diff --git a/fs/ntfs3/inode.c b/fs/ntfs3/inode.c
index 42af1abe17f8..a5ff04c2efd3 100644
--- a/fs/ntfs3/inode.c
+++ b/fs/ntfs3/inode.c
@@ -2095,6 +2095,7 @@ const struct inode_operations ntfs_link_inode_operations = {
.get_link = ntfs_get_link,
.setattr = ntfs_setattr,
.listxattr = ntfs_listxattr,
+ .fileattr_get = ntfs_fileattr_get,
};
const struct address_space_operations ntfs_aops = {
diff --git a/fs/ntfs3/namei.c b/fs/ntfs3/namei.c
index b2af8f695e60..eb241d7796ba 100644
--- a/fs/ntfs3/namei.c
+++ b/fs/ntfs3/namei.c
@@ -518,6 +518,7 @@ const struct inode_operations ntfs_dir_inode_operations = {
.getattr = ntfs_getattr,
.listxattr = ntfs_listxattr,
.fiemap = ntfs_fiemap,
+ .fileattr_get = ntfs_fileattr_get,
};
const struct inode_operations ntfs_special_inode_operations = {
@@ -526,6 +527,7 @@ const struct inode_operations ntfs_special_inode_operations = {
.listxattr = ntfs_listxattr,
.get_acl = ntfs_get_acl,
.set_acl = ntfs_set_acl,
+ .fileattr_get = ntfs_fileattr_get,
};
const struct dentry_operations ntfs_dentry_ops = {
diff --git a/fs/ntfs3/ntfs_fs.h b/fs/ntfs3/ntfs_fs.h
index bbf3b6a1dcbe..41db22d652c4 100644
--- a/fs/ntfs3/ntfs_fs.h
+++ b/fs/ntfs3/ntfs_fs.h
@@ -529,6 +529,7 @@ bool dir_is_empty(struct inode *dir);
extern const struct file_operations ntfs_dir_operations;
/* Globals from file.c */
+int ntfs_fileattr_get(struct dentry *dentry, struct file_kattr *fa);
int ntfs_getattr(struct mnt_idmap *idmap, const struct path *path,
struct kstat *stat, u32 request_mask, u32 flags);
int ntfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
--
2.53.0
^ permalink raw reply related
* [PATCH v13 06/15] hfs: Implement fileattr_get for case sensitivity
From: Chuck Lever @ 2026-05-02 14:20 UTC (permalink / raw)
To: Al Viro, Christian Brauner, Jan Kara
Cc: linux-fsdevel, linux-ext4, linux-xfs, linux-cifs, linux-nfs,
linux-api, linux-f2fs-devel, hirofumi, linkinjeon, sj1557.seo,
yuezhang.mo, almaz.alexandrovich, slava, glaubitz, frank.li,
tytso, adilger.kernel, cem, sfrench, pc, ronniesahlberg, sprasad,
trondmy, anna, jaegeuk, chao, hansg, senozhatsky, Chuck Lever,
Roland Mainz
In-Reply-To: <20260502-case-sensitivity-v13-0-aa853140311f@oracle.com>
From: Chuck Lever <chuck.lever@oracle.com>
Report HFS case sensitivity behavior via the FS_XFLAG_CASEFOLD
flag. HFS is always case-insensitive (using Mac OS Roman case
folding) and always preserves case at rest.
Reviewed-by: Viacheslav Dubeyko <slava@dubeyko.com>
Reviewed-by: Roland Mainz <roland.mainz@nrubsig.org>
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
---
fs/hfs/dir.c | 1 +
fs/hfs/hfs_fs.h | 2 ++
fs/hfs/inode.c | 14 ++++++++++++++
3 files changed, 17 insertions(+)
diff --git a/fs/hfs/dir.c b/fs/hfs/dir.c
index f5e7efe924e7..c4c6e1623f55 100644
--- a/fs/hfs/dir.c
+++ b/fs/hfs/dir.c
@@ -328,4 +328,5 @@ const struct inode_operations hfs_dir_inode_operations = {
.rmdir = hfs_remove,
.rename = hfs_rename,
.setattr = hfs_inode_setattr,
+ .fileattr_get = hfs_fileattr_get,
};
diff --git a/fs/hfs/hfs_fs.h b/fs/hfs/hfs_fs.h
index ac0e83f77a0f..1b23448c9a48 100644
--- a/fs/hfs/hfs_fs.h
+++ b/fs/hfs/hfs_fs.h
@@ -177,6 +177,8 @@ extern int hfs_get_block(struct inode *inode, sector_t block,
extern const struct address_space_operations hfs_aops;
extern const struct address_space_operations hfs_btree_aops;
+struct file_kattr;
+int hfs_fileattr_get(struct dentry *dentry, struct file_kattr *fa);
int hfs_write_begin(const struct kiocb *iocb, struct address_space *mapping,
loff_t pos, unsigned int len, struct folio **foliop,
void **fsdata);
diff --git a/fs/hfs/inode.c b/fs/hfs/inode.c
index 89b33a9d46d5..f41cc261684d 100644
--- a/fs/hfs/inode.c
+++ b/fs/hfs/inode.c
@@ -18,6 +18,7 @@
#include <linux/uio.h>
#include <linux/xattr.h>
#include <linux/blkdev.h>
+#include <linux/fileattr.h>
#include "hfs_fs.h"
#include "btree.h"
@@ -699,6 +700,18 @@ static int hfs_file_fsync(struct file *filp, loff_t start, loff_t end,
return ret;
}
+int hfs_fileattr_get(struct dentry *dentry, struct file_kattr *fa)
+{
+ /*
+ * HFS compares filenames using Mac OS Roman case folding, so
+ * lookup is always case-insensitive. Names are stored on disk
+ * with case intact; CASENONPRESERVING stays clear.
+ */
+ fa->fsx_xflags |= FS_XFLAG_CASEFOLD;
+ fa->flags |= FS_CASEFOLD_FL;
+ return 0;
+}
+
static const struct file_operations hfs_file_operations = {
.llseek = generic_file_llseek,
.read_iter = generic_file_read_iter,
@@ -715,4 +728,5 @@ static const struct inode_operations hfs_file_inode_operations = {
.lookup = hfs_file_lookup,
.setattr = hfs_inode_setattr,
.listxattr = generic_listxattr,
+ .fileattr_get = hfs_fileattr_get,
};
--
2.53.0
^ permalink raw reply related
* [PATCH v13 07/15] hfsplus: Report case sensitivity in fileattr_get
From: Chuck Lever @ 2026-05-02 14:20 UTC (permalink / raw)
To: Al Viro, Christian Brauner, Jan Kara
Cc: linux-fsdevel, linux-ext4, linux-xfs, linux-cifs, linux-nfs,
linux-api, linux-f2fs-devel, hirofumi, linkinjeon, sj1557.seo,
yuezhang.mo, almaz.alexandrovich, slava, glaubitz, frank.li,
tytso, adilger.kernel, cem, sfrench, pc, ronniesahlberg, sprasad,
trondmy, anna, jaegeuk, chao, hansg, senozhatsky, Chuck Lever,
Roland Mainz
In-Reply-To: <20260502-case-sensitivity-v13-0-aa853140311f@oracle.com>
From: Chuck Lever <chuck.lever@oracle.com>
Add case sensitivity reporting to the existing hfsplus_fileattr_get()
function via the FS_XFLAG_CASEFOLD flag. HFS+ always preserves case
at rest.
Case sensitivity depends on how the volume was formatted: HFSX
volumes may be either case-sensitive or case-insensitive, indicated
by the HFSPLUS_SB_CASEFOLD superblock flag.
FS_XFLAG_CASEFOLD is read-only: FS_XFLAG_RDONLY_MASK ensures
FS_IOC_FSSETXATTR strips it. The legacy FS_IOC_SETFLAGS path in
hfsplus_fileattr_set() also allows FS_CASEFOLD_FL through its
allowlist on case-insensitive volumes so that a chattr
read-modify-write cycle does not fail with EOPNOTSUPP.
Reviewed-by: Viacheslav Dubeyko <slava@dubeyko.com>
Reviewed-by: Roland Mainz <roland.mainz@nrubsig.org>
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
---
fs/hfsplus/inode.c | 16 +++++++++++++++-
1 file changed, 15 insertions(+), 1 deletion(-)
diff --git a/fs/hfsplus/inode.c b/fs/hfsplus/inode.c
index d05891ec492e..5565c14b4bf6 100644
--- a/fs/hfsplus/inode.c
+++ b/fs/hfsplus/inode.c
@@ -740,6 +740,7 @@ int hfsplus_fileattr_get(struct dentry *dentry, struct file_kattr *fa)
{
struct inode *inode = d_inode(dentry);
struct hfsplus_inode_info *hip = HFSPLUS_I(inode);
+ struct hfsplus_sb_info *sbi = HFSPLUS_SB(inode->i_sb);
unsigned int flags = 0;
if (inode->i_flags & S_IMMUTABLE)
@@ -748,6 +749,8 @@ int hfsplus_fileattr_get(struct dentry *dentry, struct file_kattr *fa)
flags |= FS_APPEND_FL;
if (hip->userflags & HFSPLUS_FLG_NODUMP)
flags |= FS_NODUMP_FL;
+ if (test_bit(HFSPLUS_SB_CASEFOLD, &sbi->flags))
+ flags |= FS_CASEFOLD_FL;
fileattr_fill_flags(fa, flags);
@@ -759,13 +762,24 @@ int hfsplus_fileattr_set(struct mnt_idmap *idmap,
{
struct inode *inode = d_inode(dentry);
struct hfsplus_inode_info *hip = HFSPLUS_I(inode);
+ struct hfsplus_sb_info *sbi = HFSPLUS_SB(inode->i_sb);
+ unsigned int allowed = FS_IMMUTABLE_FL | FS_APPEND_FL | FS_NODUMP_FL;
unsigned int new_fl = 0;
if (fileattr_has_fsx(fa))
return -EOPNOTSUPP;
+ /*
+ * FS_CASEFOLD_FL reflects HFSPLUS_SB_CASEFOLD, a mount-time
+ * property. Accept it as a no-op so chattr's RMW round-trip
+ * succeeds; reject any attempt to enable it on a volume that
+ * was not formatted case-insensitive.
+ */
+ if (test_bit(HFSPLUS_SB_CASEFOLD, &sbi->flags))
+ allowed |= FS_CASEFOLD_FL;
+
/* don't silently ignore unsupported ext2 flags */
- if (fa->flags & ~(FS_IMMUTABLE_FL|FS_APPEND_FL|FS_NODUMP_FL))
+ if (fa->flags & ~allowed)
return -EOPNOTSUPP;
if (fa->flags & FS_IMMUTABLE_FL)
--
2.53.0
^ permalink raw reply related
* [PATCH v13 08/15] xfs: Report case sensitivity in fileattr_get
From: Chuck Lever @ 2026-05-02 14:20 UTC (permalink / raw)
To: Al Viro, Christian Brauner, Jan Kara
Cc: linux-fsdevel, linux-ext4, linux-xfs, linux-cifs, linux-nfs,
linux-api, linux-f2fs-devel, hirofumi, linkinjeon, sj1557.seo,
yuezhang.mo, almaz.alexandrovich, slava, glaubitz, frank.li,
tytso, adilger.kernel, cem, sfrench, pc, ronniesahlberg, sprasad,
trondmy, anna, jaegeuk, chao, hansg, senozhatsky, Chuck Lever,
Roland Mainz
In-Reply-To: <20260502-case-sensitivity-v13-0-aa853140311f@oracle.com>
From: Chuck Lever <chuck.lever@oracle.com>
Upper layers such as NFSD need to query whether a filesystem
is case-sensitive. Add FS_XFLAG_CASEFOLD to xfs_ip2xflags()
when the filesystem is formatted with the ASCIICI feature
flag. This serves both FS_IOC_FSGETXATTR (via xfs_fill_fsxattr()
in xfs_fileattr_get()) and XFS_IOC_BULKSTAT (which populates
bs_xflags directly from xfs_ip2xflags()), so bulkstat consumers
and per-inode queries see a consistent view of the filesystem's
case-folding behavior.
FS_XFLAG_CASEFOLD is read-only: FS_XFLAG_RDONLY_MASK ensures
FS_IOC_FSSETXATTR strips it, and xfs_flags2diflags() has no
clause for CASEFOLD so the on-disk diflags are unaffected.
The legacy FS_IOC_SETFLAGS path in xfs_fileattr_set() also
allows FS_CASEFOLD_FL through its allowlist on ASCIICI
filesystems so that a chattr read-modify-write cycle does
not fail with EOPNOTSUPP.
XFS always preserves case. XFS is case-sensitive by default,
but supports ASCII case-insensitive lookups when formatted
with the ASCIICI feature flag.
Reviewed-by: Roland Mainz <roland.mainz@nrubsig.org>
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
---
fs/xfs/libxfs/xfs_inode_util.c | 2 ++
fs/xfs/xfs_ioctl.c | 20 +++++++++++++++++---
2 files changed, 19 insertions(+), 3 deletions(-)
diff --git a/fs/xfs/libxfs/xfs_inode_util.c b/fs/xfs/libxfs/xfs_inode_util.c
index 551fa51befb6..82be54b6f8d3 100644
--- a/fs/xfs/libxfs/xfs_inode_util.c
+++ b/fs/xfs/libxfs/xfs_inode_util.c
@@ -130,6 +130,8 @@ xfs_ip2xflags(
if (xfs_inode_has_attr_fork(ip))
flags |= FS_XFLAG_HASATTR;
+ if (xfs_has_asciici(ip->i_mount))
+ flags |= FS_XFLAG_CASEFOLD;
return flags;
}
diff --git a/fs/xfs/xfs_ioctl.c b/fs/xfs/xfs_ioctl.c
index ed9b4846c05f..f8216f74679f 100644
--- a/fs/xfs/xfs_ioctl.c
+++ b/fs/xfs/xfs_ioctl.c
@@ -755,9 +755,23 @@ xfs_fileattr_set(
trace_xfs_ioctl_setattr(ip);
if (!fa->fsx_valid) {
- if (fa->flags & ~(FS_IMMUTABLE_FL | FS_APPEND_FL |
- FS_NOATIME_FL | FS_NODUMP_FL |
- FS_SYNC_FL | FS_DAX_FL | FS_PROJINHERIT_FL))
+ unsigned int allowed = FS_IMMUTABLE_FL | FS_APPEND_FL |
+ FS_NOATIME_FL | FS_NODUMP_FL |
+ FS_SYNC_FL | FS_DAX_FL |
+ FS_PROJINHERIT_FL;
+
+ /*
+ * FS_CASEFOLD_FL reflects the ASCIICI superblock feature,
+ * a read-only property. Accept it as a no-op so chattr's
+ * RMW round-trip succeeds; reject any attempt to enable
+ * it on a non-ASCIICI filesystem. xfs_flags2diflags()
+ * has no clause for CASEFOLD, so the bit is dropped from
+ * the on-disk diflags regardless.
+ */
+ if (xfs_has_asciici(mp))
+ allowed |= FS_CASEFOLD_FL;
+
+ if (fa->flags & ~allowed)
return -EOPNOTSUPP;
}
--
2.53.0
^ permalink raw reply related
* [PATCH v13 09/15] cifs: Implement fileattr_get for case sensitivity
From: Chuck Lever @ 2026-05-02 14:20 UTC (permalink / raw)
To: Al Viro, Christian Brauner, Jan Kara
Cc: linux-fsdevel, linux-ext4, linux-xfs, linux-cifs, linux-nfs,
linux-api, linux-f2fs-devel, hirofumi, linkinjeon, sj1557.seo,
yuezhang.mo, almaz.alexandrovich, slava, glaubitz, frank.li,
tytso, adilger.kernel, cem, sfrench, pc, ronniesahlberg, sprasad,
trondmy, anna, jaegeuk, chao, hansg, senozhatsky, Chuck Lever,
Steve French, Roland Mainz
In-Reply-To: <20260502-case-sensitivity-v13-0-aa853140311f@oracle.com>
From: Chuck Lever <chuck.lever@oracle.com>
Upper layers such as NFSD need a way to query whether a filesystem
handles filenames in a case-sensitive manner. Report CIFS/SMB case
handling behavior via FS_XFLAG_CASEFOLD and
FS_XFLAG_CASENONPRESERVING.
The authoritative source is the server itself: at mount time CIFS
issues QueryFSInfo(FS_ATTRIBUTE_INFORMATION) and caches the reply
on the tcon. That reply carries FILE_CASE_SENSITIVE_SEARCH and
FILE_CASE_PRESERVED_NAMES, which reflect whatever case handling
the share actually implements after SMB3.1.1 POSIX extensions
negotiation. Translating those two bits into the VFS flags lets
cifs_fileattr_get report what the server advertises rather than
what the client was asked to pretend.
QueryFSInfo is best-effort; the mount completes even if the server
does not answer. MaxPathNameComponentLength is zero in that case
and is used as the "no reply received" sentinel. When no reply is
available, fall back to the nocase mount option so that the reported
behavior agrees with the dentry comparison operations installed on
the superblock.
The callback is registered on cifs_dir_inode_ops so that NFSD,
ksmbd, and other consumers querying case handling against a
directory get a definitive answer, and on cifs_file_inode_ops to
preserve FS_COMPR_FL reporting on regular files. cifs_set_ops()
also installs cifs_namespace_inode_operations on DFS referral
directories that carry IS_AUTOMOUNT; register the same callback
there so the answer does not depend on whether the directory is
a referral point.
Registering fileattr_get routes FS_IOC_GETFLAGS through
vfs_fileattr_get() and short-circuits the syscall's fallback to
cifs_ioctl(). That fallback invoked CIFSGetExtAttr() under
CONFIG_CIFS_POSIX and CONFIG_CIFS_ALLOW_INSECURE_LEGACY on servers
advertising CIFS_UNIX_EXTATTR_CAP, surfacing the SMB1 Unix-extension
immutable, append, and nodump bits. cifs_fileattr_get carries over
only FS_COMPR_FL from cached cifsAttrs; the SMB1 extattr fetch is
not reproduced. SMB1 is deprecated, and acquiring a netfid from
within a dentry-only callback is not worth preserving a path tied
to an insecure legacy dialect.
Acked-by: Steve French <stfrench@microsoft.com>
Reviewed-by: Roland Mainz <roland.mainz@nrubsig.org>
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
---
fs/smb/client/cifsfs.c | 53 +++++++++++++++++++++++++++++++++++++++++++++++
fs/smb/client/cifsfs.h | 3 +++
fs/smb/client/namespace.c | 1 +
3 files changed, 57 insertions(+)
diff --git a/fs/smb/client/cifsfs.c b/fs/smb/client/cifsfs.c
index 2025739f070a..6c113ae7fdd3 100644
--- a/fs/smb/client/cifsfs.c
+++ b/fs/smb/client/cifsfs.c
@@ -30,6 +30,7 @@
#include <linux/xattr.h>
#include <linux/mm.h>
#include <linux/key-type.h>
+#include <linux/fileattr.h>
#include <uapi/linux/magic.h>
#include <net/ipv6.h>
#include "cifsfs.h"
@@ -1199,6 +1200,56 @@ struct file_system_type smb3_fs_type = {
MODULE_ALIAS_FS("smb3");
MODULE_ALIAS("smb3");
+int cifs_fileattr_get(struct dentry *dentry, struct file_kattr *fa)
+{
+ struct cifs_sb_info *cifs_sb = CIFS_SB(dentry->d_sb);
+ struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);
+ struct inode *inode = d_inode(dentry);
+ u32 attrs;
+
+ /* Preserve FS_COMPR_FL previously reported by cifs_ioctl(). */
+ if (CIFS_I(inode)->cifsAttrs & ATTR_COMPRESSED)
+ fa->flags |= FS_COMPR_FL;
+
+ /*
+ * FS_CASEFOLD_FL is defined by UAPI as a folder attribute,
+ * and userspace tools (e.g., lsattr) display it only on
+ * directories. Confine the case-handling bits to directories
+ * to match that convention; for non-directories the share's
+ * case semantics are still discoverable through the parent.
+ */
+ if (!S_ISDIR(inode->i_mode))
+ return 0;
+
+ /*
+ * The server's FS_ATTRIBUTE_INFORMATION response, cached on
+ * the tcon at mount, reflects the share's case-handling
+ * semantics after any POSIX extensions negotiation. Prefer
+ * it over the client-local nocase mount option, which only
+ * governs dentry comparison on this superblock.
+ *
+ * QueryFSInfo is best-effort at mount; when it did not
+ * populate fsAttrInfo, MaxPathNameComponentLength remains
+ * zero. In that case fall back to nocase so the reporting
+ * matches the comparison behavior installed on the sb.
+ */
+ if (le32_to_cpu(tcon->fsAttrInfo.MaxPathNameComponentLength) == 0) {
+ if (tcon->nocase) {
+ fa->fsx_xflags |= FS_XFLAG_CASEFOLD;
+ fa->flags |= FS_CASEFOLD_FL;
+ }
+ return 0;
+ }
+ attrs = le32_to_cpu(tcon->fsAttrInfo.Attributes);
+ if (!(attrs & FILE_CASE_SENSITIVE_SEARCH)) {
+ fa->fsx_xflags |= FS_XFLAG_CASEFOLD;
+ fa->flags |= FS_CASEFOLD_FL;
+ }
+ if (!(attrs & FILE_CASE_PRESERVED_NAMES))
+ fa->fsx_xflags |= FS_XFLAG_CASENONPRESERVING;
+ return 0;
+}
+
const struct inode_operations cifs_dir_inode_ops = {
.create = cifs_create,
.atomic_open = cifs_atomic_open,
@@ -1217,6 +1268,7 @@ const struct inode_operations cifs_dir_inode_ops = {
.listxattr = cifs_listxattr,
.get_acl = cifs_get_acl,
.set_acl = cifs_set_acl,
+ .fileattr_get = cifs_fileattr_get,
};
const struct inode_operations cifs_file_inode_ops = {
@@ -1227,6 +1279,7 @@ const struct inode_operations cifs_file_inode_ops = {
.fiemap = cifs_fiemap,
.get_acl = cifs_get_acl,
.set_acl = cifs_set_acl,
+ .fileattr_get = cifs_fileattr_get,
};
const char *cifs_get_link(struct dentry *dentry, struct inode *inode,
diff --git a/fs/smb/client/cifsfs.h b/fs/smb/client/cifsfs.h
index 7370b38da938..5f0d459d1a89 100644
--- a/fs/smb/client/cifsfs.h
+++ b/fs/smb/client/cifsfs.h
@@ -89,6 +89,9 @@ extern const struct inode_operations cifs_file_inode_ops;
extern const struct inode_operations cifs_symlink_inode_ops;
extern const struct inode_operations cifs_namespace_inode_operations;
+struct file_kattr;
+int cifs_fileattr_get(struct dentry *dentry, struct file_kattr *fa);
+
/* Functions related to files and directories */
extern const struct netfs_request_ops cifs_req_ops;
diff --git a/fs/smb/client/namespace.c b/fs/smb/client/namespace.c
index 52a520349cb7..52a51b032fae 100644
--- a/fs/smb/client/namespace.c
+++ b/fs/smb/client/namespace.c
@@ -294,4 +294,5 @@ struct vfsmount *cifs_d_automount(struct path *path)
}
const struct inode_operations cifs_namespace_inode_operations = {
+ .fileattr_get = cifs_fileattr_get,
};
--
2.53.0
^ permalink raw reply related
* [PATCH v13 10/15] nfs: Implement fileattr_get for case sensitivity
From: Chuck Lever @ 2026-05-02 14:20 UTC (permalink / raw)
To: Al Viro, Christian Brauner, Jan Kara
Cc: linux-fsdevel, linux-ext4, linux-xfs, linux-cifs, linux-nfs,
linux-api, linux-f2fs-devel, hirofumi, linkinjeon, sj1557.seo,
yuezhang.mo, almaz.alexandrovich, slava, glaubitz, frank.li,
tytso, adilger.kernel, cem, sfrench, pc, ronniesahlberg, sprasad,
trondmy, anna, jaegeuk, chao, hansg, senozhatsky, Chuck Lever,
Roland Mainz
In-Reply-To: <20260502-case-sensitivity-v13-0-aa853140311f@oracle.com>
From: Chuck Lever <chuck.lever@oracle.com>
An NFS server re-exporting an NFS mount point needs to report
the case sensitivity behavior of the underlying filesystem to
its clients. NFSD's attribute encoder obtains that information
by calling vfs_fileattr_get() on the lower filesystem, so the
NFS client must implement fileattr_get to surface what it
learned from its own server.
The NFS client already retrieves case sensitivity information
from servers during mount via PATHCONF (NFSv3) or the
FATTR4_CASE_INSENSITIVE/FATTR4_CASE_PRESERVING attributes
(NFSv4). Expose this information through fileattr_get by
reporting the FS_XFLAG_CASEFOLD and FS_XFLAG_CASENONPRESERVING
flags. NFSv2 lacks PATHCONF support, so mounts using that protocol
version default to standard POSIX behavior: case-sensitive and
case-preserving.
PATHCONF is now invoked unconditionally for NFSv2 and NFSv3 mounts
so the case-sensitivity capabilities are established even when the
user pins server->namelen with the namlen= mount option. That option
is orthogonal to case handling, and skipping PATHCONF because
namelen was already known would leave the caps unset.
The two capability bits carry opposite polarity because their POSIX
defaults differ. Most servers are case-sensitive and case-
preserving, matching "neither xflag set." NFS_CAP_CASE_INSENSITIVE
is set only when the server affirms case insensitivity, so "server
said no" and "server did not answer" both collapse to the case-
sensitive default. NFS_CAP_CASE_NONPRESERVING follows the same
pattern in the opposite direction: set only when the server affirms
that it does not preserve case, so that silence or a missing
attribute lands on the case-preserving default. The NFSv4 probe
checks res.attr_bitmask[0] to distinguish "server said false" from
"server omitted the attribute" before setting the bit.
Both capability bits are cleared before each probe so a remount,
an NFSv4 transparent state migration to a server with different
case semantics, or a probe whose reply does not arrive does not
retain stale capabilities from the prior probe.
Reviewed-by: Roland Mainz <roland.mainz@nrubsig.org>
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
---
fs/nfs/client.c | 21 +++++++++++++++------
fs/nfs/inode.c | 15 +++++++++++++++
fs/nfs/internal.h | 3 +++
fs/nfs/namespace.c | 2 ++
fs/nfs/nfs3proc.c | 2 ++
fs/nfs/nfs3xdr.c | 7 +++++--
fs/nfs/nfs4proc.c | 10 +++++++---
fs/nfs/proc.c | 3 +++
fs/nfs/symlink.c | 3 +++
include/linux/nfs_fs_sb.h | 2 +-
include/linux/nfs_xdr.h | 2 ++
11 files changed, 58 insertions(+), 12 deletions(-)
diff --git a/fs/nfs/client.c b/fs/nfs/client.c
index be02bb227741..3db2f18315b8 100644
--- a/fs/nfs/client.c
+++ b/fs/nfs/client.c
@@ -914,6 +914,7 @@ static void nfs_server_set_fsinfo(struct nfs_server *server,
*/
static int nfs_probe_fsinfo(struct nfs_server *server, struct nfs_fh *mntfh, struct nfs_fattr *fattr)
{
+ struct nfs_pathconf pathinfo = { };
struct nfs_fsinfo fsinfo;
struct nfs_client *clp = server->nfs_client;
int error;
@@ -933,15 +934,23 @@ static int nfs_probe_fsinfo(struct nfs_server *server, struct nfs_fh *mntfh, str
nfs_server_set_fsinfo(server, &fsinfo);
- /* Get some general file system info */
- if (server->namelen == 0) {
- struct nfs_pathconf pathinfo;
+ pathinfo.fattr = fattr;
+ nfs_fattr_init(fattr);
- pathinfo.fattr = fattr;
- nfs_fattr_init(fattr);
+ /* Clear before probing so a failed RPC does not retain stale bits. */
+ if (clp->rpc_ops->version < 4)
+ server->caps &= ~(NFS_CAP_CASE_INSENSITIVE |
+ NFS_CAP_CASE_NONPRESERVING);
- if (clp->rpc_ops->pathconf(server, mntfh, &pathinfo) >= 0)
+ if (clp->rpc_ops->pathconf(server, mntfh, &pathinfo) >= 0) {
+ if (server->namelen == 0)
server->namelen = pathinfo.max_namelen;
+ if (clp->rpc_ops->version < 4) {
+ if (pathinfo.case_insensitive)
+ server->caps |= NFS_CAP_CASE_INSENSITIVE;
+ if (!pathinfo.case_preserving)
+ server->caps |= NFS_CAP_CASE_NONPRESERVING;
+ }
}
if (clp->rpc_ops->discover_trunking != NULL &&
diff --git a/fs/nfs/inode.c b/fs/nfs/inode.c
index 98a8f0de1199..fdcbe6f2052c 100644
--- a/fs/nfs/inode.c
+++ b/fs/nfs/inode.c
@@ -41,6 +41,7 @@
#include <linux/freezer.h>
#include <linux/uaccess.h>
#include <linux/iversion.h>
+#include <linux/fileattr.h>
#include "nfs4_fs.h"
#include "callback.h"
@@ -1101,6 +1102,20 @@ int nfs_getattr(struct mnt_idmap *idmap, const struct path *path,
}
EXPORT_SYMBOL_GPL(nfs_getattr);
+int nfs_fileattr_get(struct dentry *dentry, struct file_kattr *fa)
+{
+ struct inode *inode = d_inode(dentry);
+
+ if (nfs_server_capable(inode, NFS_CAP_CASE_INSENSITIVE)) {
+ fa->fsx_xflags |= FS_XFLAG_CASEFOLD;
+ fa->flags |= FS_CASEFOLD_FL;
+ }
+ if (nfs_server_capable(inode, NFS_CAP_CASE_NONPRESERVING))
+ fa->fsx_xflags |= FS_XFLAG_CASENONPRESERVING;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(nfs_fileattr_get);
+
static void nfs_init_lock_context(struct nfs_lock_context *l_ctx)
{
refcount_set(&l_ctx->count, 1);
diff --git a/fs/nfs/internal.h b/fs/nfs/internal.h
index fc5456377160..309d3f679bb3 100644
--- a/fs/nfs/internal.h
+++ b/fs/nfs/internal.h
@@ -449,6 +449,9 @@ extern void nfs_set_cache_invalid(struct inode *inode, unsigned long flags);
extern bool nfs_check_cache_invalid(struct inode *, unsigned long);
extern int nfs_wait_bit_killable(struct wait_bit_key *key, int mode);
+struct file_kattr;
+int nfs_fileattr_get(struct dentry *dentry, struct file_kattr *fa);
+
#if IS_ENABLED(CONFIG_NFS_LOCALIO)
/* localio.c */
struct nfs_local_dio {
diff --git a/fs/nfs/namespace.c b/fs/nfs/namespace.c
index af9be0c5f516..6d0073c24771 100644
--- a/fs/nfs/namespace.c
+++ b/fs/nfs/namespace.c
@@ -246,11 +246,13 @@ nfs_namespace_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
const struct inode_operations nfs_mountpoint_inode_operations = {
.getattr = nfs_getattr,
.setattr = nfs_setattr,
+ .fileattr_get = nfs_fileattr_get,
};
const struct inode_operations nfs_referral_inode_operations = {
.getattr = nfs_namespace_getattr,
.setattr = nfs_namespace_setattr,
+ .fileattr_get = nfs_fileattr_get,
};
static void nfs_expire_automounts(struct work_struct *work)
diff --git a/fs/nfs/nfs3proc.c b/fs/nfs/nfs3proc.c
index 95d7cd564b74..b80d0c5efc27 100644
--- a/fs/nfs/nfs3proc.c
+++ b/fs/nfs/nfs3proc.c
@@ -1053,6 +1053,7 @@ static const struct inode_operations nfs3_dir_inode_operations = {
.permission = nfs_permission,
.getattr = nfs_getattr,
.setattr = nfs_setattr,
+ .fileattr_get = nfs_fileattr_get,
#ifdef CONFIG_NFS_V3_ACL
.listxattr = nfs3_listxattr,
.get_inode_acl = nfs3_get_acl,
@@ -1064,6 +1065,7 @@ static const struct inode_operations nfs3_file_inode_operations = {
.permission = nfs_permission,
.getattr = nfs_getattr,
.setattr = nfs_setattr,
+ .fileattr_get = nfs_fileattr_get,
#ifdef CONFIG_NFS_V3_ACL
.listxattr = nfs3_listxattr,
.get_inode_acl = nfs3_get_acl,
diff --git a/fs/nfs/nfs3xdr.c b/fs/nfs/nfs3xdr.c
index e17d72908412..e745e78faab0 100644
--- a/fs/nfs/nfs3xdr.c
+++ b/fs/nfs/nfs3xdr.c
@@ -2276,8 +2276,11 @@ static int decode_pathconf3resok(struct xdr_stream *xdr,
if (unlikely(!p))
return -EIO;
result->max_link = be32_to_cpup(p++);
- result->max_namelen = be32_to_cpup(p);
- /* ignore remaining fields */
+ result->max_namelen = be32_to_cpup(p++);
+ p++; /* ignore no_trunc */
+ p++; /* ignore chown_restricted */
+ result->case_insensitive = be32_to_cpup(p++) != 0;
+ result->case_preserving = be32_to_cpup(p) != 0;
return 0;
}
diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c
index d839a97df822..62f66684fbc8 100644
--- a/fs/nfs/nfs4proc.c
+++ b/fs/nfs/nfs4proc.c
@@ -3933,7 +3933,8 @@ static int _nfs4_server_capabilities(struct nfs_server *server, struct nfs_fh *f
server->caps &=
~(NFS_CAP_ACLS | NFS_CAP_HARDLINKS | NFS_CAP_SYMLINKS |
NFS_CAP_SECURITY_LABEL | NFS_CAP_FS_LOCATIONS |
- NFS_CAP_OPEN_XOR | NFS_CAP_DELEGTIME);
+ NFS_CAP_OPEN_XOR | NFS_CAP_DELEGTIME |
+ NFS_CAP_CASE_INSENSITIVE | NFS_CAP_CASE_NONPRESERVING);
server->fattr_valid = NFS_ATTR_FATTR_V4;
if (res.attr_bitmask[0] & FATTR4_WORD0_ACL &&
res.acl_bitmask & ACL4_SUPPORT_ALLOW_ACL)
@@ -3944,8 +3945,9 @@ static int _nfs4_server_capabilities(struct nfs_server *server, struct nfs_fh *f
server->caps |= NFS_CAP_SYMLINKS;
if (res.case_insensitive)
server->caps |= NFS_CAP_CASE_INSENSITIVE;
- if (res.case_preserving)
- server->caps |= NFS_CAP_CASE_PRESERVING;
+ if ((res.attr_bitmask[0] & FATTR4_WORD0_CASE_PRESERVING) &&
+ !res.case_preserving)
+ server->caps |= NFS_CAP_CASE_NONPRESERVING;
#ifdef CONFIG_NFS_V4_SECURITY_LABEL
if (res.attr_bitmask[2] & FATTR4_WORD2_SECURITY_LABEL)
server->caps |= NFS_CAP_SECURITY_LABEL;
@@ -10598,6 +10600,7 @@ static const struct inode_operations nfs4_dir_inode_operations = {
.getattr = nfs_getattr,
.setattr = nfs_setattr,
.listxattr = nfs4_listxattr,
+ .fileattr_get = nfs_fileattr_get,
};
static const struct inode_operations nfs4_file_inode_operations = {
@@ -10605,6 +10608,7 @@ static const struct inode_operations nfs4_file_inode_operations = {
.getattr = nfs_getattr,
.setattr = nfs_setattr,
.listxattr = nfs4_listxattr,
+ .fileattr_get = nfs_fileattr_get,
};
static struct nfs_server *nfs4_clone_server(struct nfs_server *source,
diff --git a/fs/nfs/proc.c b/fs/nfs/proc.c
index 70795684b8e8..03c2c1f31be9 100644
--- a/fs/nfs/proc.c
+++ b/fs/nfs/proc.c
@@ -598,6 +598,7 @@ nfs_proc_pathconf(struct nfs_server *server, struct nfs_fh *fhandle,
{
info->max_link = 0;
info->max_namelen = NFS2_MAXNAMLEN;
+ info->case_preserving = true;
return 0;
}
@@ -718,12 +719,14 @@ static const struct inode_operations nfs_dir_inode_operations = {
.permission = nfs_permission,
.getattr = nfs_getattr,
.setattr = nfs_setattr,
+ .fileattr_get = nfs_fileattr_get,
};
static const struct inode_operations nfs_file_inode_operations = {
.permission = nfs_permission,
.getattr = nfs_getattr,
.setattr = nfs_setattr,
+ .fileattr_get = nfs_fileattr_get,
};
const struct nfs_rpc_ops nfs_v2_clientops = {
diff --git a/fs/nfs/symlink.c b/fs/nfs/symlink.c
index 58146e935402..74a072896f8d 100644
--- a/fs/nfs/symlink.c
+++ b/fs/nfs/symlink.c
@@ -22,6 +22,8 @@
#include <linux/mm.h>
#include <linux/string.h>
+#include "internal.h"
+
/* Symlink caching in the page cache is even more simplistic
* and straight-forward than readdir caching.
*/
@@ -74,4 +76,5 @@ const struct inode_operations nfs_symlink_inode_operations = {
.get_link = nfs_get_link,
.getattr = nfs_getattr,
.setattr = nfs_setattr,
+ .fileattr_get = nfs_fileattr_get,
};
diff --git a/include/linux/nfs_fs_sb.h b/include/linux/nfs_fs_sb.h
index 4daee27fa5eb..34d294774f8c 100644
--- a/include/linux/nfs_fs_sb.h
+++ b/include/linux/nfs_fs_sb.h
@@ -306,7 +306,7 @@ struct nfs_server {
#define NFS_CAP_ATOMIC_OPEN (1U << 4)
#define NFS_CAP_LGOPEN (1U << 5)
#define NFS_CAP_CASE_INSENSITIVE (1U << 6)
-#define NFS_CAP_CASE_PRESERVING (1U << 7)
+#define NFS_CAP_CASE_NONPRESERVING (1U << 7)
#define NFS_CAP_REBOOT_LAYOUTRETURN (1U << 8)
#define NFS_CAP_OFFLOAD_STATUS (1U << 9)
#define NFS_CAP_ZERO_RANGE (1U << 10)
diff --git a/include/linux/nfs_xdr.h b/include/linux/nfs_xdr.h
index ff1f12aa73d2..7c2057e40f99 100644
--- a/include/linux/nfs_xdr.h
+++ b/include/linux/nfs_xdr.h
@@ -182,6 +182,8 @@ struct nfs_pathconf {
struct nfs_fattr *fattr; /* Post-op attributes */
__u32 max_link; /* max # of hard links */
__u32 max_namelen; /* max name length */
+ bool case_insensitive;
+ bool case_preserving;
};
struct nfs4_change_info {
--
2.53.0
^ permalink raw reply related
* [PATCH v13 11/15] vboxsf: Implement fileattr_get for case sensitivity
From: Chuck Lever @ 2026-05-02 14:20 UTC (permalink / raw)
To: Al Viro, Christian Brauner, Jan Kara
Cc: linux-fsdevel, linux-ext4, linux-xfs, linux-cifs, linux-nfs,
linux-api, linux-f2fs-devel, hirofumi, linkinjeon, sj1557.seo,
yuezhang.mo, almaz.alexandrovich, slava, glaubitz, frank.li,
tytso, adilger.kernel, cem, sfrench, pc, ronniesahlberg, sprasad,
trondmy, anna, jaegeuk, chao, hansg, senozhatsky, Chuck Lever,
Roland Mainz
In-Reply-To: <20260502-case-sensitivity-v13-0-aa853140311f@oracle.com>
From: Chuck Lever <chuck.lever@oracle.com>
Upper layers such as NFSD need a way to query whether a
filesystem handles filenames in a case-sensitive manner. Report
VirtualBox shared folder case handling behavior via the
FS_XFLAG_CASEFOLD flag.
The case sensitivity property is queried from the VirtualBox host
service at mount time and cached in struct vboxsf_sbi. The host
determines case sensitivity based on the underlying host filesystem
(for example, Windows NTFS is case-insensitive while Linux ext4 is
case-sensitive).
VirtualBox shared folders always preserve filename case exactly
as provided by the guest. The host interface does not expose a
separate case-preserving property; leaving
FS_XFLAG_CASENONPRESERVING unset reports the POSIX-default
case-preserving behavior, which matches vboxsf semantics.
The callback is registered in all three inode_operations
structures (directory, file, and symlink) to ensure consistent
reporting across all inode types.
Reviewed-by: Roland Mainz <roland.mainz@nrubsig.org>
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
---
fs/vboxsf/dir.c | 1 +
fs/vboxsf/file.c | 6 ++++--
fs/vboxsf/super.c | 7 +++++++
fs/vboxsf/utils.c | 30 ++++++++++++++++++++++++++++++
fs/vboxsf/vfsmod.h | 6 ++++++
5 files changed, 48 insertions(+), 2 deletions(-)
diff --git a/fs/vboxsf/dir.c b/fs/vboxsf/dir.c
index 42bedc4ec7af..c5bd3271aa96 100644
--- a/fs/vboxsf/dir.c
+++ b/fs/vboxsf/dir.c
@@ -477,4 +477,5 @@ const struct inode_operations vboxsf_dir_iops = {
.symlink = vboxsf_dir_symlink,
.getattr = vboxsf_getattr,
.setattr = vboxsf_setattr,
+ .fileattr_get = vboxsf_fileattr_get,
};
diff --git a/fs/vboxsf/file.c b/fs/vboxsf/file.c
index 7a7a3fbb2651..943953867e18 100644
--- a/fs/vboxsf/file.c
+++ b/fs/vboxsf/file.c
@@ -222,7 +222,8 @@ const struct file_operations vboxsf_reg_fops = {
const struct inode_operations vboxsf_reg_iops = {
.getattr = vboxsf_getattr,
- .setattr = vboxsf_setattr
+ .setattr = vboxsf_setattr,
+ .fileattr_get = vboxsf_fileattr_get,
};
static int vboxsf_read_folio(struct file *file, struct folio *folio)
@@ -389,5 +390,6 @@ static const char *vboxsf_get_link(struct dentry *dentry, struct inode *inode,
}
const struct inode_operations vboxsf_lnk_iops = {
- .get_link = vboxsf_get_link
+ .get_link = vboxsf_get_link,
+ .fileattr_get = vboxsf_fileattr_get,
};
diff --git a/fs/vboxsf/super.c b/fs/vboxsf/super.c
index a618cb093e00..a61fbab51d37 100644
--- a/fs/vboxsf/super.c
+++ b/fs/vboxsf/super.c
@@ -185,6 +185,13 @@ static int vboxsf_fill_super(struct super_block *sb, struct fs_context *fc)
if (err)
goto fail_unmap;
+ /*
+ * A failed query leaves sbi->case_insensitive false, so the
+ * mount defaults to reporting case-sensitive behavior. Do not
+ * fail the mount over an advisory attribute.
+ */
+ vboxsf_query_case_sensitive(sbi);
+
sb->s_magic = VBOXSF_SUPER_MAGIC;
sb->s_blocksize = 1024;
sb->s_maxbytes = MAX_LFS_FILESIZE;
diff --git a/fs/vboxsf/utils.c b/fs/vboxsf/utils.c
index 440e8c50629d..298bfc93255c 100644
--- a/fs/vboxsf/utils.c
+++ b/fs/vboxsf/utils.c
@@ -11,6 +11,7 @@
#include <linux/sizes.h>
#include <linux/pagemap.h>
#include <linux/vfs.h>
+#include <linux/fileattr.h>
#include "vfsmod.h"
struct inode *vboxsf_new_inode(struct super_block *sb)
@@ -567,3 +568,32 @@ int vboxsf_dir_read_all(struct vboxsf_sbi *sbi, struct vboxsf_dir_info *sf_d,
return err;
}
+
+int vboxsf_query_case_sensitive(struct vboxsf_sbi *sbi)
+{
+ struct shfl_volinfo volinfo = {};
+ u32 buf_len;
+ int err;
+
+ buf_len = sizeof(volinfo);
+ err = vboxsf_fsinfo(sbi->root, 0, SHFL_INFO_GET | SHFL_INFO_VOLUME,
+ &buf_len, &volinfo);
+ if (err)
+ return err;
+ if (buf_len < sizeof(volinfo))
+ return 0;
+
+ sbi->case_insensitive = !volinfo.properties.case_sensitive;
+ return 0;
+}
+
+int vboxsf_fileattr_get(struct dentry *dentry, struct file_kattr *fa)
+{
+ struct vboxsf_sbi *sbi = VBOXSF_SBI(dentry->d_sb);
+
+ if (sbi->case_insensitive) {
+ fa->fsx_xflags |= FS_XFLAG_CASEFOLD;
+ fa->flags |= FS_CASEFOLD_FL;
+ }
+ return 0;
+}
diff --git a/fs/vboxsf/vfsmod.h b/fs/vboxsf/vfsmod.h
index 05973eb89d52..b61afd0ce842 100644
--- a/fs/vboxsf/vfsmod.h
+++ b/fs/vboxsf/vfsmod.h
@@ -47,6 +47,7 @@ struct vboxsf_sbi {
u32 next_generation;
u32 root;
int bdi_id;
+ bool case_insensitive;
};
/* per-inode information */
@@ -111,6 +112,11 @@ void vboxsf_dir_info_free(struct vboxsf_dir_info *p);
int vboxsf_dir_read_all(struct vboxsf_sbi *sbi, struct vboxsf_dir_info *sf_d,
u64 handle);
+int vboxsf_query_case_sensitive(struct vboxsf_sbi *sbi);
+
+struct file_kattr;
+int vboxsf_fileattr_get(struct dentry *dentry, struct file_kattr *fa);
+
/* from vboxsf_wrappers.c */
int vboxsf_connect(void);
void vboxsf_disconnect(void);
--
2.53.0
^ permalink raw reply related
* [PATCH v13 12/15] isofs: Implement fileattr_get for case sensitivity
From: Chuck Lever @ 2026-05-02 14:20 UTC (permalink / raw)
To: Al Viro, Christian Brauner, Jan Kara
Cc: linux-fsdevel, linux-ext4, linux-xfs, linux-cifs, linux-nfs,
linux-api, linux-f2fs-devel, hirofumi, linkinjeon, sj1557.seo,
yuezhang.mo, almaz.alexandrovich, slava, glaubitz, frank.li,
tytso, adilger.kernel, cem, sfrench, pc, ronniesahlberg, sprasad,
trondmy, anna, jaegeuk, chao, hansg, senozhatsky, Chuck Lever,
Roland Mainz
In-Reply-To: <20260502-case-sensitivity-v13-0-aa853140311f@oracle.com>
From: Chuck Lever <chuck.lever@oracle.com>
Upper layers such as NFSD need a way to query whether a
filesystem handles filenames in a case-sensitive manner so
they can provide correct semantics to remote clients. Without
this information, NFS exports of ISO 9660 filesystems cannot
advertise their filename case behavior.
Implement isofs_fileattr_get() to report ISO 9660 case handling
behavior. The 'check=r' (relaxed) mount option enables
case-insensitive lookups and is reported via FS_XFLAG_CASEFOLD.
By default, Joliet extensions operate in relaxed mode while
plain ISO 9660 uses strict (case-sensitive) mode.
Plain ISO 9660 names on the medium are uppercase. When neither
Rock Ridge nor Joliet is in effect, the default 'map=n' option
(and 'map=a') routes lookup and readdir through
isofs_name_translate(), which forces A-Z to a-z. The names
visible to userspace then differ in case from the on-disc form,
so report FS_XFLAG_CASENONPRESERVING in that configuration. Rock
Ridge and Joliet both deliver names as authored, and 'map=o'
emits the raw on-disc name unchanged, so those configurations
remain case-preserving.
Casefolding is a directory property, and the in-tree consumers
(NFSD, ksmbd) issue the query against a directory: NFSD walks
to the parent for non-directory dentries before calling
vfs_fileattr_get(), and ksmbd reports per-share attributes from
the share root. Wire .fileattr_get only on
isofs_dir_inode_operations. The CASEFOLD flag is set in both
fa->fsx_xflags and fa->flags so FS_IOC_FSGETXATTR and
FS_IOC_GETFLAGS agree.
Reviewed-by: Jan Kara <jack@suse.cz>
Reviewed-by: Roland Mainz <roland.mainz@nrubsig.org>
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
---
fs/isofs/dir.c | 16 ++++++++++++++++
fs/isofs/isofs.h | 3 +++
2 files changed, 19 insertions(+)
diff --git a/fs/isofs/dir.c b/fs/isofs/dir.c
index 2fd9948d606e..55385a72a4ce 100644
--- a/fs/isofs/dir.c
+++ b/fs/isofs/dir.c
@@ -14,6 +14,7 @@
#include <linux/gfp.h>
#include <linux/filelock.h>
#include "isofs.h"
+#include <linux/fileattr.h>
int isofs_name_translate(struct iso_directory_record *de, char *new, struct inode *inode)
{
@@ -267,6 +268,20 @@ static int isofs_readdir(struct file *file, struct dir_context *ctx)
return result;
}
+int isofs_fileattr_get(struct dentry *dentry, struct file_kattr *fa)
+{
+ struct isofs_sb_info *sbi = ISOFS_SB(dentry->d_sb);
+
+ if (sbi->s_check == 'r') {
+ fa->fsx_xflags |= FS_XFLAG_CASEFOLD;
+ fa->flags |= FS_CASEFOLD_FL;
+ }
+ if (!sbi->s_joliet_level && !sbi->s_rock &&
+ (sbi->s_mapping == 'n' || sbi->s_mapping == 'a'))
+ fa->fsx_xflags |= FS_XFLAG_CASENONPRESERVING;
+ return 0;
+}
+
const struct file_operations isofs_dir_operations =
{
.llseek = generic_file_llseek,
@@ -281,6 +296,7 @@ const struct file_operations isofs_dir_operations =
const struct inode_operations isofs_dir_inode_operations =
{
.lookup = isofs_lookup,
+ .fileattr_get = isofs_fileattr_get,
};
diff --git a/fs/isofs/isofs.h b/fs/isofs/isofs.h
index 506555837533..0ec8b24a42ed 100644
--- a/fs/isofs/isofs.h
+++ b/fs/isofs/isofs.h
@@ -197,6 +197,9 @@ isofs_normalize_block_and_offset(struct iso_directory_record* de,
}
}
+struct file_kattr;
+int isofs_fileattr_get(struct dentry *dentry, struct file_kattr *fa);
+
extern const struct inode_operations isofs_dir_inode_operations;
extern const struct file_operations isofs_dir_operations;
extern const struct address_space_operations isofs_symlink_aops;
--
2.53.0
^ permalink raw reply related
* [PATCH v13 13/15] nfsd: Report export case-folding via NFSv3 PATHCONF
From: Chuck Lever @ 2026-05-02 14:20 UTC (permalink / raw)
To: Al Viro, Christian Brauner, Jan Kara
Cc: linux-fsdevel, linux-ext4, linux-xfs, linux-cifs, linux-nfs,
linux-api, linux-f2fs-devel, hirofumi, linkinjeon, sj1557.seo,
yuezhang.mo, almaz.alexandrovich, slava, glaubitz, frank.li,
tytso, adilger.kernel, cem, sfrench, pc, ronniesahlberg, sprasad,
trondmy, anna, jaegeuk, chao, hansg, senozhatsky, Chuck Lever,
Roland Mainz
In-Reply-To: <20260502-case-sensitivity-v13-0-aa853140311f@oracle.com>
From: Chuck Lever <chuck.lever@oracle.com>
The hard-coded MSDOS_SUPER_MAGIC check in nfsd3_proc_pathconf()
only recognizes FAT filesystems as case-insensitive. Modern
filesystems like F2FS, exFAT, and CIFS support case-insensitive
directories, but NFSv3 clients cannot discover this capability.
Query the export's actual case behavior through ->fileattr_get
instead. This allows NFSv3 clients to correctly handle case
sensitivity for any filesystem that implements the fileattr
interface. Filesystems without ->fileattr_get continue to report
the default POSIX behavior (case-sensitive, case-preserving).
This change depends on the earlier "fat: Implement fileattr_get
for case sensitivity" patch in this series, which ensures FAT
filesystems report their case behavior correctly via the
fileattr interface.
Case-folding is a per-directory property, so
nfsd_get_case_info() queries the parent dentry for
non-directory filehandles. Three inherent corner cases follow:
a single-file export's parent lies outside the exported
subtree, so the LSM hook evaluates against an unexported
directory; a disconnected dentry from fh_verify() has
d_parent == itself, so the file's own attributes are reported
until the dentry connects; and a hardlinked file resolves
through the alias the dcache currently holds, so when the
inode is linked into both case-folded and case-sensitive
directories the reported value tracks whichever parent is
active. These limitations are not addressable without
redefining the protocol attribute as per-parent rather than
per-object.
RFC 1813 restricts PATHCONF errors to NFS3ERR_STALE,
NFS3ERR_BADHANDLE, and NFS3ERR_SERVERFAULT. When an LSM hook
denies the case-folding query on the parent, NFS3ERR_STALE is
the only correct mapping: NFS3ERR_SERVERFAULT misrepresents a
working server as broken, and NFS3ERR_BADHANDLE implies a
decoding failure that did not occur. A client purging the
filehandle on receipt is the desired outcome, since the server
has refused to read attributes through it. Substituting POSIX
defaults instead would let the same handle report
casefold=false now and casefold=true once policy permits,
opening a silent name-collision window on case-insensitive
exports.
Reviewed-by: Roland Mainz <roland.mainz@nrubsig.org>
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
---
fs/nfsd/nfs3proc.c | 36 +++++++++++++++++++++------
fs/nfsd/vfs.c | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
fs/nfsd/vfs.h | 3 +++
fs/nfsd/xdr3.h | 4 +--
4 files changed, 105 insertions(+), 10 deletions(-)
diff --git a/fs/nfsd/nfs3proc.c b/fs/nfsd/nfs3proc.c
index 42adc5461db0..62ebc65b8af2 100644
--- a/fs/nfsd/nfs3proc.c
+++ b/fs/nfsd/nfs3proc.c
@@ -710,23 +710,43 @@ nfsd3_proc_pathconf(struct svc_rqst *rqstp)
resp->p_name_max = 255; /* at least */
resp->p_no_trunc = 0;
resp->p_chown_restricted = 1;
- resp->p_case_insensitive = 0;
- resp->p_case_preserving = 1;
+ resp->p_case_insensitive = false;
+ resp->p_case_preserving = true;
resp->status = fh_verify(rqstp, &argp->fh, 0, NFSD_MAY_NOP);
if (resp->status == nfs_ok) {
struct super_block *sb = argp->fh.fh_dentry->d_sb;
+ int err;
- /* Note that we don't care for remote fs's here */
- switch (sb->s_magic) {
- case EXT2_SUPER_MAGIC:
+ if (sb->s_magic == EXT2_SUPER_MAGIC) {
resp->p_link_max = EXT2_LINK_MAX;
resp->p_name_max = EXT2_NAME_LEN;
+ }
+
+ err = nfsd_get_case_info(argp->fh.fh_dentry,
+ &resp->p_case_insensitive,
+ &resp->p_case_preserving);
+ /*
+ * RFC 1813 lists NFS3ERR_STALE, NFS3ERR_BADHANDLE, and
+ * NFS3ERR_SERVERFAULT as the only PATHCONF errors.
+ */
+ switch (err) {
+ case 0:
+ case -EOPNOTSUPP:
+ /* Both arms leave the output booleans valid. */
break;
- case MSDOS_SUPER_MAGIC:
- resp->p_case_insensitive = 1;
- resp->p_case_preserving = 0;
+ case -EACCES:
+ case -EPERM:
+ /*
+ * Policy denied the query. Report STALE so the
+ * handle is unusable without implying a server
+ * malfunction.
+ */
+ resp->status = nfserr_stale;
+ break;
+ default:
+ resp->status = nfserr_serverfault;
break;
}
}
diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c
index eafdf7b7890f..4bd63d8efbf7 100644
--- a/fs/nfsd/vfs.c
+++ b/fs/nfsd/vfs.c
@@ -32,6 +32,7 @@
#include <linux/writeback.h>
#include <linux/security.h>
#include <linux/sunrpc/xdr.h>
+#include <linux/fileattr.h>
#include "xdr3.h"
@@ -2891,3 +2892,74 @@ nfsd_permission(struct svc_cred *cred, struct svc_export *exp,
return err? nfserrno(err) : 0;
}
+
+/**
+ * nfsd_get_case_info - get case sensitivity info for a dentry
+ * @dentry: dentry to query
+ * @case_insensitive: set to true if the filesystem is case-insensitive
+ * @case_preserving: set to true if the filesystem preserves case
+ *
+ * On casefold-capable filesystems the flag lives on the directory,
+ * not on its entries, so for a non-directory @dentry the parent is
+ * queried instead. A directory (including an export root, whose
+ * parent lies outside the export) is queried as-is so its own
+ * contents' lookup behavior is reported.
+ *
+ * When the filesystem does not expose case-folding state (no
+ * ->fileattr_get, or the callback returns -EOPNOTSUPP /
+ * -ENOIOCTLCMD / -ENOTTY / -EINVAL), the outputs are filled with
+ * POSIX defaults (case-sensitive, case-preserving) on the premise
+ * that a filesystem with case-folding support wires up
+ * fileattr_get.
+ *
+ * Other errors propagate unmodified (-EACCES, -EPERM from LSM
+ * hooks; -EIO, -ESTALE, ... from the filesystem). Case-folding
+ * behavior is a property of the exported filesystem, not of the
+ * caller's credentials, so silently substituting defaults would
+ * let the same dentry report POSIX while LSM denies and report
+ * casefolding once LSM allows -- a client could race against
+ * silent name collisions on a case-insensitive export.
+ *
+ * Return: 0 with outputs filled, -EOPNOTSUPP with outputs filled
+ * to POSIX defaults, or a negative errno with outputs
+ * unmodified.
+ */
+int
+nfsd_get_case_info(struct dentry *dentry, bool *case_insensitive,
+ bool *case_preserving)
+{
+ struct file_kattr fa = {};
+ struct dentry *cd;
+ bool put = false;
+ int err;
+
+ if (d_is_dir(dentry)) {
+ cd = dentry;
+ } else {
+ cd = dget_parent(dentry);
+ put = true;
+ }
+ err = vfs_fileattr_get(cd, &fa);
+ if (put)
+ dput(cd);
+ switch (err) {
+ case 0:
+ *case_insensitive = fa.fsx_xflags & FS_XFLAG_CASEFOLD;
+ *case_preserving =
+ !(fa.fsx_xflags & FS_XFLAG_CASENONPRESERVING);
+ return 0;
+ case -EINVAL:
+ case -ENOTTY:
+ case -ENOIOCTLCMD:
+ case -EOPNOTSUPP:
+ /*
+ * Filesystem does not expose case state.
+ * Report POSIX defaults.
+ */
+ *case_insensitive = false;
+ *case_preserving = true;
+ return -EOPNOTSUPP;
+ default:
+ return err;
+ }
+}
diff --git a/fs/nfsd/vfs.h b/fs/nfsd/vfs.h
index 702a844f2106..e09ea04a51b9 100644
--- a/fs/nfsd/vfs.h
+++ b/fs/nfsd/vfs.h
@@ -156,6 +156,9 @@ __be32 nfsd_readdir(struct svc_rqst *, struct svc_fh *,
loff_t *, struct readdir_cd *, nfsd_filldir_t);
__be32 nfsd_statfs(struct svc_rqst *, struct svc_fh *,
struct kstatfs *, int access);
+int nfsd_get_case_info(struct dentry *dentry,
+ bool *case_insensitive,
+ bool *case_preserving);
__be32 nfsd_permission(struct svc_cred *cred, struct svc_export *exp,
struct dentry *dentry, int acc);
diff --git a/fs/nfsd/xdr3.h b/fs/nfsd/xdr3.h
index 522067b7fd75..a7c9714b0b0e 100644
--- a/fs/nfsd/xdr3.h
+++ b/fs/nfsd/xdr3.h
@@ -209,8 +209,8 @@ struct nfsd3_pathconfres {
__u32 p_name_max;
__u32 p_no_trunc;
__u32 p_chown_restricted;
- __u32 p_case_insensitive;
- __u32 p_case_preserving;
+ bool p_case_insensitive;
+ bool p_case_preserving;
};
struct nfsd3_commitres {
--
2.53.0
^ permalink raw reply related
* [PATCH v13 14/15] nfsd: Implement NFSv4 FATTR4_CASE_INSENSITIVE and FATTR4_CASE_PRESERVING
From: Chuck Lever @ 2026-05-02 14:20 UTC (permalink / raw)
To: Al Viro, Christian Brauner, Jan Kara
Cc: linux-fsdevel, linux-ext4, linux-xfs, linux-cifs, linux-nfs,
linux-api, linux-f2fs-devel, hirofumi, linkinjeon, sj1557.seo,
yuezhang.mo, almaz.alexandrovich, slava, glaubitz, frank.li,
tytso, adilger.kernel, cem, sfrench, pc, ronniesahlberg, sprasad,
trondmy, anna, jaegeuk, chao, hansg, senozhatsky, Chuck Lever,
Roland Mainz
In-Reply-To: <20260502-case-sensitivity-v13-0-aa853140311f@oracle.com>
From: Chuck Lever <chuck.lever@oracle.com>
NFSD currently provides NFSv4 clients with hard-coded responses
indicating all exported filesystems are case-sensitive and
case-preserving. This is incorrect for case-insensitive filesystems
and ext4 directories with casefold enabled.
Query the underlying filesystem's actual case sensitivity via
nfsd_get_case_info() and return accurate values to clients. This
supports per-directory settings for filesystems that allow mixing
case-sensitive and case-insensitive directories within an export.
The helper queries the parent dentry for non-directory filehandles
because case-folding is a per-directory property. That resolution
has the same corner cases here as for NFSv3 PATHCONF: single-file
exports query an unexported parent, disconnected dentries report
defaults until reconnected, and hardlinked files track whichever
alias the dcache currently holds.
Reviewed-by: Roland Mainz <roland.mainz@nrubsig.org>
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
---
fs/nfsd/nfs4xdr.c | 52 +++++++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 49 insertions(+), 3 deletions(-)
diff --git a/fs/nfsd/nfs4xdr.c b/fs/nfsd/nfs4xdr.c
index 2a0946c630e1..d77304692e11 100644
--- a/fs/nfsd/nfs4xdr.c
+++ b/fs/nfsd/nfs4xdr.c
@@ -3158,6 +3158,8 @@ struct nfsd4_fattr_args {
u32 rdattr_err;
bool contextsupport;
bool ignore_crossmnt;
+ bool case_insensitive;
+ bool case_preserving;
};
typedef __be32(*nfsd4_enc_attr)(struct xdr_stream *xdr,
@@ -3356,6 +3358,33 @@ static __be32 nfsd4_encode_fattr4_acl(struct xdr_stream *xdr,
return nfs_ok;
}
+static __be32 nfsd4_encode_fattr4_case_insensitive(struct xdr_stream *xdr,
+ const struct nfsd4_fattr_args *args)
+{
+ return nfsd4_encode_bool(xdr, args->case_insensitive);
+}
+
+static __be32 nfsd4_encode_fattr4_case_preserving(struct xdr_stream *xdr,
+ const struct nfsd4_fattr_args *args)
+{
+ return nfsd4_encode_bool(xdr, args->case_preserving);
+}
+
+static __be32 nfsd4_encode_fattr4_homogeneous(struct xdr_stream *xdr,
+ const struct nfsd4_fattr_args *args)
+{
+ /*
+ * Casefold-capable filesystems (e.g. ext4 or f2fs with the
+ * casefold feature) attach a Unicode encoding at mount time
+ * but apply case folding per directory. The per-file-system
+ * case_insensitive and case_preserving values can therefore
+ * legitimately differ across objects that share the same fsid.
+ * Report FATTR4_HOMOGENEOUS = FALSE on such filesystems to
+ * keep that variation consistent with RFC 8881 Section 5.8.2.16.
+ */
+ return nfsd4_encode_bool(xdr, !sb_has_encoding(args->dentry->d_sb));
+}
+
static __be32 nfsd4_encode_fattr4_filehandle(struct xdr_stream *xdr,
const struct nfsd4_fattr_args *args)
{
@@ -3748,8 +3777,8 @@ static const nfsd4_enc_attr nfsd4_enc_fattr4_encode_ops[] = {
[FATTR4_ACLSUPPORT] = nfsd4_encode_fattr4_aclsupport,
[FATTR4_ARCHIVE] = nfsd4_encode_fattr4__noop,
[FATTR4_CANSETTIME] = nfsd4_encode_fattr4__true,
- [FATTR4_CASE_INSENSITIVE] = nfsd4_encode_fattr4__false,
- [FATTR4_CASE_PRESERVING] = nfsd4_encode_fattr4__true,
+ [FATTR4_CASE_INSENSITIVE] = nfsd4_encode_fattr4_case_insensitive,
+ [FATTR4_CASE_PRESERVING] = nfsd4_encode_fattr4_case_preserving,
[FATTR4_CHOWN_RESTRICTED] = nfsd4_encode_fattr4__true,
[FATTR4_FILEHANDLE] = nfsd4_encode_fattr4_filehandle,
[FATTR4_FILEID] = nfsd4_encode_fattr4_fileid,
@@ -3758,7 +3787,7 @@ static const nfsd4_enc_attr nfsd4_enc_fattr4_encode_ops[] = {
[FATTR4_FILES_TOTAL] = nfsd4_encode_fattr4_files_total,
[FATTR4_FS_LOCATIONS] = nfsd4_encode_fattr4_fs_locations,
[FATTR4_HIDDEN] = nfsd4_encode_fattr4__noop,
- [FATTR4_HOMOGENEOUS] = nfsd4_encode_fattr4__true,
+ [FATTR4_HOMOGENEOUS] = nfsd4_encode_fattr4_homogeneous,
[FATTR4_MAXFILESIZE] = nfsd4_encode_fattr4_maxfilesize,
[FATTR4_MAXLINK] = nfsd4_encode_fattr4_maxlink,
[FATTR4_MAXNAME] = nfsd4_encode_fattr4_maxname,
@@ -3968,6 +3997,23 @@ nfsd4_encode_fattr4(struct svc_rqst *rqstp, struct xdr_stream *xdr,
args.fhp = tempfh;
} else
args.fhp = fhp;
+ if (attrmask[0] & (FATTR4_WORD0_CASE_INSENSITIVE |
+ FATTR4_WORD0_CASE_PRESERVING)) {
+ err = nfsd_get_case_info(dentry, &args.case_insensitive,
+ &args.case_preserving);
+ /*
+ * SUPPORTED_ATTRS unconditionally advertises both
+ * bits, and the Linux client treats an absent
+ * CASE_PRESERVING in a GETATTR reply as false. When
+ * the filesystem does not expose case state,
+ * nfsd_get_case_info() fills POSIX defaults
+ * (case-sensitive, case-preserving) and returns
+ * -EOPNOTSUPP; encode those defaults so the reply
+ * agrees with what the server claims to support.
+ */
+ if (err && err != -EOPNOTSUPP)
+ goto out_nfserr;
+ }
if (attrmask[0] & FATTR4_WORD0_ACL) {
err = nfsd4_get_nfs4_acl(rqstp, dentry, &args.acl);
--
2.53.0
^ permalink raw reply related
* [PATCH v13 15/15] ksmbd: Report filesystem case sensitivity via FS_ATTRIBUTE_INFORMATION
From: Chuck Lever @ 2026-05-02 14:21 UTC (permalink / raw)
To: Al Viro, Christian Brauner, Jan Kara
Cc: linux-fsdevel, linux-ext4, linux-xfs, linux-cifs, linux-nfs,
linux-api, linux-f2fs-devel, hirofumi, linkinjeon, sj1557.seo,
yuezhang.mo, almaz.alexandrovich, slava, glaubitz, frank.li,
tytso, adilger.kernel, cem, sfrench, pc, ronniesahlberg, sprasad,
trondmy, anna, jaegeuk, chao, hansg, senozhatsky, Chuck Lever,
Roland Mainz
In-Reply-To: <20260502-case-sensitivity-v13-0-aa853140311f@oracle.com>
From: Chuck Lever <chuck.lever@oracle.com>
FS_ATTRIBUTE_INFORMATION responses have always reported
FILE_CASE_SENSITIVE_SEARCH and FILE_CASE_PRESERVED_NAMES
unconditionally. Case-insensitive filesystems like exFAT, and
casefolded directories on ext4 or f2fs, have no way to signal
their actual semantics to SMB clients.
Now that filesystems expose case behavior through ->fileattr_get,
query it via vfs_fileattr_get() and translate the FS_XFLAG_CASEFOLD
and FS_XFLAG_CASENONPRESERVING flags into the corresponding SMB
attributes. Filesystems without ->fileattr_get continue reporting
default POSIX behavior (case-sensitive, case-preserving).
SMB's FS_ATTRIBUTE_INFORMATION reports per-share attributes from
the share root, not per-file. Shares mixing casefold and
non-casefold directories report the root directory's behavior.
Acked-by: Namjae Jeon <linkinjeon@kernel.org>
Reviewed-by: Roland Mainz <roland.mainz@nrubsig.org>
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
---
fs/smb/server/smb2pdu.c | 30 ++++++++++++++++++++++++------
1 file changed, 24 insertions(+), 6 deletions(-)
diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index ee32e61b6d3c..cf0bc453a036 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -14,6 +14,7 @@
#include <linux/falloc.h>
#include <linux/mount.h>
#include <linux/filelock.h>
+#include <linux/fileattr.h>
#include "glob.h"
#include "smbfsctl.h"
@@ -5541,16 +5542,33 @@ static int smb2_get_info_filesystem(struct ksmbd_work *work,
case FS_ATTRIBUTE_INFORMATION:
{
FILE_SYSTEM_ATTRIBUTE_INFO *info;
+ struct file_kattr fa = {};
size_t sz;
+ u32 attrs;
+ int err;
info = (FILE_SYSTEM_ATTRIBUTE_INFO *)rsp->Buffer;
- info->Attributes = cpu_to_le32(FILE_SUPPORTS_OBJECT_IDS |
- FILE_PERSISTENT_ACLS |
- FILE_UNICODE_ON_DISK |
- FILE_CASE_PRESERVED_NAMES |
- FILE_CASE_SENSITIVE_SEARCH |
- FILE_SUPPORTS_BLOCK_REFCOUNTING);
+ attrs = FILE_SUPPORTS_OBJECT_IDS |
+ FILE_PERSISTENT_ACLS |
+ FILE_UNICODE_ON_DISK |
+ FILE_SUPPORTS_BLOCK_REFCOUNTING;
+ err = vfs_fileattr_get(path.dentry, &fa);
+ /*
+ * -EINVAL, -EOPNOTSUPP: ntfs-3g and other FUSE
+ * filesystems that lack FS_IOC_FSGETXATTR support.
+ */
+ if (err && err != -ENOIOCTLCMD && err != -ENOTTY &&
+ err != -EINVAL && err != -EOPNOTSUPP) {
+ path_put(&path);
+ return err;
+ }
+ if (!(fa.fsx_xflags & FS_XFLAG_CASEFOLD))
+ attrs |= FILE_CASE_SENSITIVE_SEARCH;
+ if (!(fa.fsx_xflags & FS_XFLAG_CASENONPRESERVING))
+ attrs |= FILE_CASE_PRESERVED_NAMES;
+
+ info->Attributes = cpu_to_le32(attrs);
info->Attributes |= cpu_to_le32(server_conf.share_fake_fscaps);
if (test_share_config_flag(work->tcon->share_conf,
--
2.53.0
^ permalink raw reply related
* Re: [GIT PULL v5.1] libfuse: run fuse servers as a contained service
From: Bernd Schubert @ 2026-05-02 15:59 UTC (permalink / raw)
To: Darrick J. Wong
Cc: fuse-devel, joannelkoong, linux-ext4, linux-fsdevel, miklos, neal
In-Reply-To: <20260430224911.GN7765@frogsfrogsfrogs>
On 5/1/26 00:49, Darrick J. Wong wrote:
> On Thu, Apr 30, 2026 at 11:34:06PM +0200, Bernd Schubert wrote:
>> Hi Darrick,
>>
>> On 4/30/26 23:18, Darrick J. Wong wrote:
>>> Hi Bernd,
>>>
>>> Please pull this branch with changes for libfuse.
>>>
>>> As usual, I did a test-merge with the main upstream branch as of a few
>>> minutes ago, and didn't see any conflicts. Please let me know if you
>>> encounter any problems.
>>
>> pushed to my github branch. BSD build fails with
>>
>> 2026-04-30T21:25:16.3874802Z FAILED: [code=1] lib/libfuse3.so.3.19.0
>> 2026-04-30T21:25:16.3906762Z cc -o lib/libfuse3.so.3.19.0 lib/libfuse3.so.3.19.0.p/fuse.c.o lib/libfuse3.so.3.19.0.p/fuse_loop.c.o lib/libfuse3.so.3.19.0.p/fuse_loop_mt.c.o lib/libfuse3.so.3.19.0.p/fuse_lowlevel.c.o lib/libfuse3.so.3.19.0.p/fuse_opt.c.o lib/libfuse3.so.3.19.0.p/fuse_signals.c.o lib/libfuse3.so.3.19.0.p/buffer.c.o lib/libfuse3.so.3.19.0.p/cuse_lowlevel.c.o lib/libfuse3.so.3.19.0.p/helper.c.o lib/libfuse3.so.3.19.0.p/modules_subdir.c.o lib/libfuse3.so.3.19.0.p/mount_util.c.o lib/libfuse3.so.3.19.0.p/fuse_log.c.o lib/libfuse3.so.3.19.0.p/compat.c.o lib/libfuse3.so.3.19.0.p/util.c.o lib/libfuse3.so.3.19.0.p/mount_bsd.c.o lib/libfuse3.so.3.19.0.p/fuse_service_stub.c.o lib/libfuse3.so.3.19.0.p/modules_iconv.c.o -Wl,--as-needed -Wl,--no-undefined -shared -fPIC -Wl,-soname,libfuse3.so.4 -Wl,--version-script,/home/runner/work/libfuse/libfuse/lib/fuse_versionscript -pthread -Wl,--start-group -ldl -lrt -Wl,--end-group
>> 2026-04-30T21:25:16.3939590Z ld: error: version script assignment of 'FUSE_3.19' to symbol 'fuse_service_can_allow_other' failed: symbol not defined
>
> Aha, that function got left out of the stub. :(
>
> Annoyingly, on Linux the build succeeds despite the missing symbol
> when I tweak meson so that it doesn't build the service stuff. I would
> have thought that --no-undefined would have done that, but alas.
>
> Sorry about that too. The following patch fixes it.
>
> diff --git i/lib/fuse_service_stub.c w/lib/fuse_service_stub.c
> index d34df3891a6e31..231b98423df628 100644
> --- i/lib/fuse_service_stub.c
> +++ w/lib/fuse_service_stub.c
> @@ -49,12 +49,17 @@ int fuse_service_send_goodbye(struct fuse_service *sf, int error)
> int fuse_service_accept(struct fuse_service **sfp)
> {
> *sfp = NULL;
> return 0;
> }
>
> +bool fuse_service_can_allow_other(struct fuse_service *sf)
> +{
> + return false;
> +}
> +
> int fuse_service_append_args(struct fuse_service *sf,
> struct fuse_args *existing_args)
> {
> return -EOPNOTSUPP;
> }
>
>
>> 2026-04-30T21:25:16.3951874Z cc: error: linker command failed with exit code 1 (use -v to see invocation)
>> 2026-04-30T21:25:16.4291582Z [44/82] cc -Itest/test_teardown_watchdog.p -Itest -I../test -Iinclude -I../include -Ilib -I../lib -I. -I.. -fdiagnostics-color=always -
>>
>>
>> checkpatch, CodeChecker-cppcheck, CodeChecker-gcc also all fail. This CodeQL
>
> Not sure why checkpatch fails, this is what I got (Debian 13):
>
> $ git diff origin/master.. | ./checkpatch.pl --max-line-length=100 --no-tree --ignore MAINTAINERS,SPDX_LICENSE_TAG,COMMIT_MESSAGE,FILE_PATH_CHANGES,EMAIL_SUBJECT,AVOID_EXTERNS,GIT_COMMIT_ID,ENOSYS_SYSCALL,ENOSYS,FROM_SIGN_OFF_MISMATCH,QUOTED_COMMIT_ID,,PREFER_ATTRIBUTE_ALWAYS_UNUSED,PREFER_DEFINED_ATTRIBUTE_MACRO,STRCPY,STRNCPY -
> No typos will be found - file '/storage/home/djwong/cdev/work/libfuse/spelling.txt': No such file or directory
> No structs that should be const will be found - file '/storage/home/djwong/cdev/work/libfuse/const_structs.checkpatch': No such file or directory
> total: 0 errors, 0 warnings, 3908 lines checked
>
> Your patch has no obvious style problems and is ready for submission.
>
> NOTE: Ignored message types: AVOID_EXTERNS COMMIT_MESSAGE EMAIL_SUBJECT ENOSYS ENOSYS_SYSCALL FILE_PATH_CHANGES FROM_SIGN_OFF_MISMATCH GIT_COMMIT_ID MAINTAINERS PREFER_ATTRIBUTE_ALWAYS_UNUSED PREFER_DEFINED_ATTRIBUTE_MACRO QUOTED_COMMIT_ID SPDX_LICENSE_TAG STRCPY STRNCPY
>
> cppcheck had a few things to say, but none of it was about the changed
> lines.
>
>> report is funny
>>
>>> int mount_service_main(int argc, char *argv[])
>>> Warning
>>> Poorly documented large function
>>> Poorly documented function: fewer than 2% comments for a function of 113 lines.
>>> CodeQL
>
> Hrmm. I guess I'll have to figure out how to get those things running.
> That said, the stuff in mount_service.c is internal to libfuse (i.e.
> it's not public library API) so I didn't comment them as intensely.
> Would you like more?
>
>> I think I'm going to merge my sync fuse init series tomorrow.
>
> Yay!
>
>> Are you ok if skip posting another version of the series?
>
> ^ is there an "I" here? e.g. "...if I skip posting..."?
>
> /My/ normal practice (from xfs) was to repost the series as it was
> merged, followed by an announcement. That way the mailing list is a
> complete record of what was merged. However, very very few people
> actually do that, even in the kernel.
>
> I'm ok with you not posting another version of the series.
>
>> Or do you prefer to review the recent changes and last piece I'm going
>> to do tomorrow?
>
> Nah, just merge it. I'll look over the changes once it's in the branch
> and if there's anything weird, you or I or anyone else can send patches.
> As long as you're not planning to tag it as a release, nothing's set in
> stone.
>
>> Basically my goal is to rebase your series against it immediately and
>> to merge your series during the next few days.
>
> <nod> Let me know what you want changed, I'll be around since I am not
> travelling anywhere for a couple of weeks. :)
>
> I can reflow changes into the patchset, or if you'd prefer, I can add
> them as new patches that would go on the end of the series.
>
Hi Darrick,
eventually merged my patches and made a quick rebase and conflict
resolving with your branch. Github still reports plenty of issues ;)
Could you fix all of the checkpatch isssues? This one shows plenty
"util: hoist the fuse.conf parsing and setuid mode enforcement code"
bschubert2@imesrv6 libfuse.git>stg series
+ mount_service-add-systemd
+ mount_service-create-high
+ mount_service-use-the-new
+ mount_service-update-mtab
> util-hoist-the-fuse.conf
- util-fix-checkpatch-complaints
- mount_service-enable
- mount.fuse3-integrate-systemd
- mount_service-allow
- example-service_ll-create-a
- example-service-create-a
- nullfs-support-fuse-systemd
- meson-show-feature-summary
bschubert2@imesrv6 libfuse.git>.github/workflows/run-checkpatch.sh
No typos will be found - file '/home/bschubert2/src/libfuse/libfuse.git/spelling.txt': No such file or directory
No structs that should be const will be found - file '/home/bschubert2/src/libfuse/libfuse.git/const_structs.checkpatch': No such file or directory
ERROR:GLOBAL_INITIALISERS: do not initialise globals to 0
#61: FILE: util/fuser_conf.c:33:
+int user_allow_other = 0;
WARNING:LINE_SPACING: Missing a blank line after declarations
#74: FILE: util/fuser_conf.c:46:
+ char *dest = buf;
+ while (1) {
...
I don't have a strong opinion about these two above - feel
free to disable them. However, in order to merge it, it would
be good to run without any checkpatch report.
And everything that cppcheck and gcc-checker reports, definitely
need to be fixed.
Thanks,
Bernd
^ permalink raw reply
* Re: [GIT PULL v5.1] libfuse: run fuse servers as a contained service
From: Bernd Schubert @ 2026-05-02 16:26 UTC (permalink / raw)
To: Darrick J. Wong
Cc: fuse-devel, joannelkoong, linux-ext4, linux-fsdevel, miklos, neal
In-Reply-To: <2f2ba053-53a7-43e5-969a-32d185e9c7b7@bsbernd.com>
On 5/2/26 17:59, Bernd Schubert wrote:
>
>
> On 5/1/26 00:49, Darrick J. Wong wrote:
>> On Thu, Apr 30, 2026 at 11:34:06PM +0200, Bernd Schubert wrote:
>>> Hi Darrick,
>>>
>>> On 4/30/26 23:18, Darrick J. Wong wrote:
>>>> Hi Bernd,
>>>>
>>>> Please pull this branch with changes for libfuse.
>>>>
>>>> As usual, I did a test-merge with the main upstream branch as of a few
>>>> minutes ago, and didn't see any conflicts. Please let me know if you
>>>> encounter any problems.
>>>
>>> pushed to my github branch. BSD build fails with
>>>
>>> 2026-04-30T21:25:16.3874802Z FAILED: [code=1] lib/libfuse3.so.3.19.0
>>> 2026-04-30T21:25:16.3906762Z cc -o lib/libfuse3.so.3.19.0 lib/libfuse3.so.3.19.0.p/fuse.c.o lib/libfuse3.so.3.19.0.p/fuse_loop.c.o lib/libfuse3.so.3.19.0.p/fuse_loop_mt.c.o lib/libfuse3.so.3.19.0.p/fuse_lowlevel.c.o lib/libfuse3.so.3.19.0.p/fuse_opt.c.o lib/libfuse3.so.3.19.0.p/fuse_signals.c.o lib/libfuse3.so.3.19.0.p/buffer.c.o lib/libfuse3.so.3.19.0.p/cuse_lowlevel.c.o lib/libfuse3.so.3.19.0.p/helper.c.o lib/libfuse3.so.3.19.0.p/modules_subdir.c.o lib/libfuse3.so.3.19.0.p/mount_util.c.o lib/libfuse3.so.3.19.0.p/fuse_log.c.o lib/libfuse3.so.3.19.0.p/compat.c.o lib/libfuse3.so.3.19.0.p/util.c.o lib/libfuse3.so.3.19.0.p/mount_bsd.c.o lib/libfuse3.so.3.19.0.p/fuse_service_stub.c.o lib/libfuse3.so.3.19.0.p/modules_iconv.c.o -Wl,--as-needed -Wl,--no-undefined -shared -fPIC -Wl,-soname,libfuse3.so.4 -Wl,--version-script,/home/runner/work/libfuse/libfuse/lib/fuse_versionscript -pthread -Wl,--start-group -ldl -lrt -Wl,--end-group
>>> 2026-04-30T21:25:16.3939590Z ld: error: version script assignment of 'FUSE_3.19' to symbol 'fuse_service_can_allow_other' failed: symbol not defined
>>
>> Aha, that function got left out of the stub. :(
>>
>> Annoyingly, on Linux the build succeeds despite the missing symbol
>> when I tweak meson so that it doesn't build the service stuff. I would
>> have thought that --no-undefined would have done that, but alas.
>>
>> Sorry about that too. The following patch fixes it.
>>
>> diff --git i/lib/fuse_service_stub.c w/lib/fuse_service_stub.c
>> index d34df3891a6e31..231b98423df628 100644
>> --- i/lib/fuse_service_stub.c
>> +++ w/lib/fuse_service_stub.c
>> @@ -49,12 +49,17 @@ int fuse_service_send_goodbye(struct fuse_service *sf, int error)
>> int fuse_service_accept(struct fuse_service **sfp)
>> {
>> *sfp = NULL;
>> return 0;
>> }
>>
>> +bool fuse_service_can_allow_other(struct fuse_service *sf)
>> +{
>> + return false;
>> +}
>> +
>> int fuse_service_append_args(struct fuse_service *sf,
>> struct fuse_args *existing_args)
>> {
>> return -EOPNOTSUPP;
>> }
>>
>>
>>> 2026-04-30T21:25:16.3951874Z cc: error: linker command failed with exit code 1 (use -v to see invocation)
>>> 2026-04-30T21:25:16.4291582Z [44/82] cc -Itest/test_teardown_watchdog.p -Itest -I../test -Iinclude -I../include -Ilib -I../lib -I. -I.. -fdiagnostics-color=always -
>>>
>>>
>>> checkpatch, CodeChecker-cppcheck, CodeChecker-gcc also all fail. This CodeQL
>>
>> Not sure why checkpatch fails, this is what I got (Debian 13):
>>
>> $ git diff origin/master.. | ./checkpatch.pl --max-line-length=100 --no-tree --ignore MAINTAINERS,SPDX_LICENSE_TAG,COMMIT_MESSAGE,FILE_PATH_CHANGES,EMAIL_SUBJECT,AVOID_EXTERNS,GIT_COMMIT_ID,ENOSYS_SYSCALL,ENOSYS,FROM_SIGN_OFF_MISMATCH,QUOTED_COMMIT_ID,,PREFER_ATTRIBUTE_ALWAYS_UNUSED,PREFER_DEFINED_ATTRIBUTE_MACRO,STRCPY,STRNCPY -
>> No typos will be found - file '/storage/home/djwong/cdev/work/libfuse/spelling.txt': No such file or directory
>> No structs that should be const will be found - file '/storage/home/djwong/cdev/work/libfuse/const_structs.checkpatch': No such file or directory
>> total: 0 errors, 0 warnings, 3908 lines checked
>>
>> Your patch has no obvious style problems and is ready for submission.
>>
>> NOTE: Ignored message types: AVOID_EXTERNS COMMIT_MESSAGE EMAIL_SUBJECT ENOSYS ENOSYS_SYSCALL FILE_PATH_CHANGES FROM_SIGN_OFF_MISMATCH GIT_COMMIT_ID MAINTAINERS PREFER_ATTRIBUTE_ALWAYS_UNUSED PREFER_DEFINED_ATTRIBUTE_MACRO QUOTED_COMMIT_ID SPDX_LICENSE_TAG STRCPY STRNCPY
>>
>> cppcheck had a few things to say, but none of it was about the changed
>> lines.
>>
>>> report is funny
>>>
>>>> int mount_service_main(int argc, char *argv[])
>>>> Warning
>>>> Poorly documented large function
>>>> Poorly documented function: fewer than 2% comments for a function of 113 lines.
>>>> CodeQL
>>
>> Hrmm. I guess I'll have to figure out how to get those things running.
>> That said, the stuff in mount_service.c is internal to libfuse (i.e.
>> it's not public library API) so I didn't comment them as intensely.
>> Would you like more?
>>
>>> I think I'm going to merge my sync fuse init series tomorrow.
>>
>> Yay!
>>
>>> Are you ok if skip posting another version of the series?
>>
>> ^ is there an "I" here? e.g. "...if I skip posting..."?
>>
>> /My/ normal practice (from xfs) was to repost the series as it was
>> merged, followed by an announcement. That way the mailing list is a
>> complete record of what was merged. However, very very few people
>> actually do that, even in the kernel.
>>
>> I'm ok with you not posting another version of the series.
>>
>>> Or do you prefer to review the recent changes and last piece I'm going
>>> to do tomorrow?
>>
>> Nah, just merge it. I'll look over the changes once it's in the branch
>> and if there's anything weird, you or I or anyone else can send patches.
>> As long as you're not planning to tag it as a release, nothing's set in
>> stone.
>>
>>> Basically my goal is to rebase your series against it immediately and
>>> to merge your series during the next few days.
>>
>> <nod> Let me know what you want changed, I'll be around since I am not
>> travelling anywhere for a couple of weeks. :)
>>
>> I can reflow changes into the patchset, or if you'd prefer, I can add
>> them as new patches that would go on the end of the series.
>>
>
> Hi Darrick,
>
> eventually merged my patches and made a quick rebase and conflict
> resolving with your branch. Github still reports plenty of issues ;)
>
> Could you fix all of the checkpatch isssues? This one shows plenty
> "util: hoist the fuse.conf parsing and setuid mode enforcement code"
>
> bschubert2@imesrv6 libfuse.git>stg series
> + mount_service-add-systemd
> + mount_service-create-high
> + mount_service-use-the-new
> + mount_service-update-mtab
>> util-hoist-the-fuse.conf
> - util-fix-checkpatch-complaints
> - mount_service-enable
> - mount.fuse3-integrate-systemd
> - mount_service-allow
> - example-service_ll-create-a
> - example-service-create-a
> - nullfs-support-fuse-systemd
> - meson-show-feature-summary
> bschubert2@imesrv6 libfuse.git>.github/workflows/run-checkpatch.sh
> No typos will be found - file '/home/bschubert2/src/libfuse/libfuse.git/spelling.txt': No such file or directory
> No structs that should be const will be found - file '/home/bschubert2/src/libfuse/libfuse.git/const_structs.checkpatch': No such file or directory
> ERROR:GLOBAL_INITIALISERS: do not initialise globals to 0
> #61: FILE: util/fuser_conf.c:33:
> +int user_allow_other = 0;
>
> WARNING:LINE_SPACING: Missing a blank line after declarations
> #74: FILE: util/fuser_conf.c:46:
> + char *dest = buf;
> + while (1) {
>
> ...
>
> I don't have a strong opinion about these two above - feel
> free to disable them. However, in order to merge it, it would
> be good to run without any checkpatch report.
> And everything that cppcheck and gcc-checker reports, definitely
> need to be fixed.
Basically
cd <$FUSEDIR>
BUILDDIR="build-without-examples"
mkdir -p ${BUILDDIR}
meson setup ${BUILDDIR} -Dexamples=false
ninja -C ${BUILDDIR}
./.github/workflows/codechecker.sh --gcc --codechecker --build-dir ${BUILDDIR}
gcc reports are starting here
bschubert2@imesrv6 libfuse.git>stg series
+ mount_service-add-systemd
+ mount_service-create-high
+ mount_service-use-the-new
+ mount_service-update-mtab
+ util-hoist-the-fuse.conf
+ util-fix-checkpatch-complaints
+ mount_service-enable
> mount.fuse3-integrate-systemd
bschubert2@imesrv6 libfuse.git>git show |head -n5
commit f908bb1504eca167e156671dde957c2e7192b7d2
Author: Darrick J. Wong <djwong@kernel.org>
Date: Wed Mar 4 13:48:55 2026 -0800
mount.fuse3: integrate systemd service startup
---==== Severity Statistics ====----
----------------------------
Severity | Number of reports
----------------------------
MEDIUM | 1
----------------------------
----=================----
----==== Checker Statistics ====----
-----------------------------------------------------
Checker name | Severity | Number of reports
-----------------------------------------------------
gcc-deref-before-check | MEDIUM | 1
-----------------------------------------------------
----=================----
----==== File Statistics ====----
--------------------------------
File name | Number of reports
--------------------------------
mount.fuse.c | 1
--------------------------------
----=================----
----======== Summary ========----
----------------------------------------------
Number of processed analyzer result files | 45
Number of analyzer reports | 1
----------------------------------------------
----=================----
To view statistics in a browser run:
> firefox /home/bschubert2/src/libfuse/libfuse.git/build-without-examples/codechecker-html/statistics.html
To view the results in a browser run:
> firefox /home/bschubert2/src/libfuse/libfuse.git/build-without-examples/codechecker-html/index.html
[INFO 2026-05-02 18:25] - ----==== Summary ====----
[INFO 2026-05-02 18:25] - Up-to-date analysis results
[INFO 2026-05-02 18:25] - gcc: 37
[INFO 2026-05-02 18:25] - Outdated analysis results
[INFO 2026-05-02 18:25] - Failed to analyze
[INFO 2026-05-02 18:25] - Missing analysis results
[INFO 2026-05-02 18:25] - clangsa: 37
[INFO 2026-05-02 18:25] - clang-tidy: 37
[INFO 2026-05-02 18:25] - cppcheck: 37
[INFO 2026-05-02 18:25] - infer: 37
[INFO 2026-05-02 18:25] - Total analyzed compilation commands: 45
[INFO 2026-05-02 18:25] - Total available compilation commands: 45
[INFO 2026-05-02 18:25] - ----=================----
bschubert2@imesrv6 libfuse.git>
^ permalink raw reply
* Re: [GIT PULL v5.1] libfuse: run fuse servers as a contained service
From: Darrick J. Wong @ 2026-05-02 16:30 UTC (permalink / raw)
To: Bernd Schubert
Cc: fuse-devel, joannelkoong, linux-ext4, linux-fsdevel, miklos, neal
In-Reply-To: <2f2ba053-53a7-43e5-969a-32d185e9c7b7@bsbernd.com>
On Sat, May 02, 2026 at 05:59:06PM +0200, Bernd Schubert wrote:
>
>
> On 5/1/26 00:49, Darrick J. Wong wrote:
> > On Thu, Apr 30, 2026 at 11:34:06PM +0200, Bernd Schubert wrote:
> >> Hi Darrick,
> >>
> >> On 4/30/26 23:18, Darrick J. Wong wrote:
> >>> Hi Bernd,
> >>>
> >>> Please pull this branch with changes for libfuse.
> >>>
> >>> As usual, I did a test-merge with the main upstream branch as of a few
> >>> minutes ago, and didn't see any conflicts. Please let me know if you
> >>> encounter any problems.
> >>
> >> pushed to my github branch. BSD build fails with
> >>
> >> 2026-04-30T21:25:16.3874802Z FAILED: [code=1] lib/libfuse3.so.3.19.0
> >> 2026-04-30T21:25:16.3906762Z cc -o lib/libfuse3.so.3.19.0 lib/libfuse3.so.3.19.0.p/fuse.c.o lib/libfuse3.so.3.19.0.p/fuse_loop.c.o lib/libfuse3.so.3.19.0.p/fuse_loop_mt.c.o lib/libfuse3.so.3.19.0.p/fuse_lowlevel.c.o lib/libfuse3.so.3.19.0.p/fuse_opt.c.o lib/libfuse3.so.3.19.0.p/fuse_signals.c.o lib/libfuse3.so.3.19.0.p/buffer.c.o lib/libfuse3.so.3.19.0.p/cuse_lowlevel.c.o lib/libfuse3.so.3.19.0.p/helper.c.o lib/libfuse3.so.3.19.0.p/modules_subdir.c.o lib/libfuse3.so.3.19.0.p/mount_util.c.o lib/libfuse3.so.3.19.0.p/fuse_log.c.o lib/libfuse3.so.3.19.0.p/compat.c.o lib/libfuse3.so.3.19.0.p/util.c.o lib/libfuse3.so.3.19.0.p/mount_bsd.c.o lib/libfuse3.so.3.19.0.p/fuse_service_stub.c.o lib/libfuse3.so.3.19.0.p/modules_iconv.c.o -Wl,--as-needed -Wl,--no-undefined -shared -fPIC -Wl,-soname,libfuse3.so.4 -Wl,--version-script,/home/runner/work/libfuse/libfuse/lib/fuse_versionscript -pthread -Wl,--start-group -ldl -lrt -Wl,--end-group
> >> 2026-04-30T21:25:16.3939590Z ld: error: version script assignment of 'FUSE_3.19' to symbol 'fuse_service_can_allow_other' failed: symbol not defined
> >
> > Aha, that function got left out of the stub. :(
> >
> > Annoyingly, on Linux the build succeeds despite the missing symbol
> > when I tweak meson so that it doesn't build the service stuff. I would
> > have thought that --no-undefined would have done that, but alas.
> >
> > Sorry about that too. The following patch fixes it.
> >
> > diff --git i/lib/fuse_service_stub.c w/lib/fuse_service_stub.c
> > index d34df3891a6e31..231b98423df628 100644
> > --- i/lib/fuse_service_stub.c
> > +++ w/lib/fuse_service_stub.c
> > @@ -49,12 +49,17 @@ int fuse_service_send_goodbye(struct fuse_service *sf, int error)
> > int fuse_service_accept(struct fuse_service **sfp)
> > {
> > *sfp = NULL;
> > return 0;
> > }
> >
> > +bool fuse_service_can_allow_other(struct fuse_service *sf)
> > +{
> > + return false;
> > +}
> > +
> > int fuse_service_append_args(struct fuse_service *sf,
> > struct fuse_args *existing_args)
> > {
> > return -EOPNOTSUPP;
> > }
> >
> >
> >> 2026-04-30T21:25:16.3951874Z cc: error: linker command failed with exit code 1 (use -v to see invocation)
> >> 2026-04-30T21:25:16.4291582Z [44/82] cc -Itest/test_teardown_watchdog.p -Itest -I../test -Iinclude -I../include -Ilib -I../lib -I. -I.. -fdiagnostics-color=always -
> >>
> >>
> >> checkpatch, CodeChecker-cppcheck, CodeChecker-gcc also all fail. This CodeQL
> >
> > Not sure why checkpatch fails, this is what I got (Debian 13):
> >
> > $ git diff origin/master.. | ./checkpatch.pl --max-line-length=100 --no-tree --ignore MAINTAINERS,SPDX_LICENSE_TAG,COMMIT_MESSAGE,FILE_PATH_CHANGES,EMAIL_SUBJECT,AVOID_EXTERNS,GIT_COMMIT_ID,ENOSYS_SYSCALL,ENOSYS,FROM_SIGN_OFF_MISMATCH,QUOTED_COMMIT_ID,,PREFER_ATTRIBUTE_ALWAYS_UNUSED,PREFER_DEFINED_ATTRIBUTE_MACRO,STRCPY,STRNCPY -
> > No typos will be found - file '/storage/home/djwong/cdev/work/libfuse/spelling.txt': No such file or directory
> > No structs that should be const will be found - file '/storage/home/djwong/cdev/work/libfuse/const_structs.checkpatch': No such file or directory
> > total: 0 errors, 0 warnings, 3908 lines checked
> >
> > Your patch has no obvious style problems and is ready for submission.
> >
> > NOTE: Ignored message types: AVOID_EXTERNS COMMIT_MESSAGE EMAIL_SUBJECT ENOSYS ENOSYS_SYSCALL FILE_PATH_CHANGES FROM_SIGN_OFF_MISMATCH GIT_COMMIT_ID MAINTAINERS PREFER_ATTRIBUTE_ALWAYS_UNUSED PREFER_DEFINED_ATTRIBUTE_MACRO QUOTED_COMMIT_ID SPDX_LICENSE_TAG STRCPY STRNCPY
> >
> > cppcheck had a few things to say, but none of it was about the changed
> > lines.
> >
> >> report is funny
> >>
> >>> int mount_service_main(int argc, char *argv[])
> >>> Warning
> >>> Poorly documented large function
> >>> Poorly documented function: fewer than 2% comments for a function of 113 lines.
> >>> CodeQL
> >
> > Hrmm. I guess I'll have to figure out how to get those things running.
> > That said, the stuff in mount_service.c is internal to libfuse (i.e.
> > it's not public library API) so I didn't comment them as intensely.
> > Would you like more?
> >
> >> I think I'm going to merge my sync fuse init series tomorrow.
> >
> > Yay!
> >
> >> Are you ok if skip posting another version of the series?
> >
> > ^ is there an "I" here? e.g. "...if I skip posting..."?
> >
> > /My/ normal practice (from xfs) was to repost the series as it was
> > merged, followed by an announcement. That way the mailing list is a
> > complete record of what was merged. However, very very few people
> > actually do that, even in the kernel.
> >
> > I'm ok with you not posting another version of the series.
> >
> >> Or do you prefer to review the recent changes and last piece I'm going
> >> to do tomorrow?
> >
> > Nah, just merge it. I'll look over the changes once it's in the branch
> > and if there's anything weird, you or I or anyone else can send patches.
> > As long as you're not planning to tag it as a release, nothing's set in
> > stone.
> >
> >> Basically my goal is to rebase your series against it immediately and
> >> to merge your series during the next few days.
> >
> > <nod> Let me know what you want changed, I'll be around since I am not
> > travelling anywhere for a couple of weeks. :)
> >
> > I can reflow changes into the patchset, or if you'd prefer, I can add
> > them as new patches that would go on the end of the series.
> >
>
> Hi Darrick,
>
> eventually merged my patches and made a quick rebase and conflict
> resolving with your branch. Github still reports plenty of issues ;)
>
> Could you fix all of the checkpatch isssues? This one shows plenty
> "util: hoist the fuse.conf parsing and setuid mode enforcement code"
>
> bschubert2@imesrv6 libfuse.git>stg series
> + mount_service-add-systemd
> + mount_service-create-high
> + mount_service-use-the-new
> + mount_service-update-mtab
> > util-hoist-the-fuse.conf
> - util-fix-checkpatch-complaints
> - mount_service-enable
> - mount.fuse3-integrate-systemd
> - mount_service-allow
> - example-service_ll-create-a
> - example-service-create-a
> - nullfs-support-fuse-systemd
> - meson-show-feature-summary
> bschubert2@imesrv6 libfuse.git>.github/workflows/run-checkpatch.sh
> No typos will be found - file '/home/bschubert2/src/libfuse/libfuse.git/spelling.txt': No such file or directory
> No structs that should be const will be found - file '/home/bschubert2/src/libfuse/libfuse.git/const_structs.checkpatch': No such file or directory
> ERROR:GLOBAL_INITIALISERS: do not initialise globals to 0
> #61: FILE: util/fuser_conf.c:33:
> +int user_allow_other = 0;
>
> WARNING:LINE_SPACING: Missing a blank line after declarations
> #74: FILE: util/fuser_conf.c:46:
> + char *dest = buf;
> + while (1) {
Oh! The checkpatch fixes are all in the next commit
"util-fix-checkpatch-complaints". I kept the checkpatch fixes as a
separate commit so that the hoist change can be inspected more easily.
(XFS practice is to have separate patches for moving the code and
cleaning it up)
> ...
>
> I don't have a strong opinion about these two above - feel
> free to disable them. However, in order to merge it, it would
> be good to run without any checkpatch report.
> And everything that cppcheck and gcc-checker reports, definitely
> need to be fixed.
Hrm, should I try to fix the things that cppcheck complains about in
HEAD? There's an awful lot of them...
(oh, I see you pushed to master, I'll go rebase now)
--D
Checking build-aarch64/meson-private/sanitycheckc.c ...
Checking build-aarch64/meson-private/sanitycheckc.c: FUSE_USE_VERSION=319...
1/75 files checked 0% done
Checking build-aarch64/fuse_config.h ...
Checking build-aarch64/fuse_config.h: FUSE_USE_VERSION=319...
2/75 files checked 0% done
Checking build-aarch64/libfuse_config.h ...
Checking build-aarch64/libfuse_config.h: FUSE_USE_VERSION=319...
3/75 files checked 0% done
Checking example/cuse.c ...
Checking example/cuse.c: FUSE_USE_VERSION=319...
example/cuse.c:75:18: portability: 'new_buf' is of type 'void *'. When using void pointers in calculations, the behaviour is undefined. [arithOperationsOnVoidPointer]
memset(new_buf + cusexmp_size, 0, new_size - cusexmp_size);
^
example/cuse.c:113:34: portability: 'cusexmp_buf' is of type 'void *'. When using void pointers in calculations, the behaviour is undefined. [arithOperationsOnVoidPointer]
fuse_reply_buf(req, cusexmp_buf + off, size);
^
example/cuse.c:126:21: portability: 'cusexmp_buf' is of type 'void *'. When using void pointers in calculations, the behaviour is undefined. [arithOperationsOnVoidPointer]
memcpy(cusexmp_buf + off, buf, size);
^
example/cuse.c:145:9: portability: 'in_buf' is of type 'const void *'. When using void pointers in calculations, the behaviour is undefined. [arithOperationsOnVoidPointer]
in_buf += sizeof(*arg);
^
example/cuse.c:150:8: portability: 'addr' is of type 'void *'. When using void pointers in calculations, the behaviour is undefined. [arithOperationsOnVoidPointer]
addr + offsetof(struct fioc_rw_arg, prev_size);
^
example/cuse.c:154:8: portability: 'addr' is of type 'void *'. When using void pointers in calculations, the behaviour is undefined. [arithOperationsOnVoidPointer]
addr + offsetof(struct fioc_rw_arg, new_size);
^
example/cuse.c:191:33: portability: 'cusexmp_buf' is of type 'void *'. When using void pointers in calculations, the behaviour is undefined. [arithOperationsOnVoidPointer]
iov[2].iov_base = cusexmp_buf + off;
^
example/cuse.c:200:22: portability: 'cusexmp_buf' is of type 'void *'. When using void pointers in calculations, the behaviour is undefined. [arithOperationsOnVoidPointer]
memcpy(cusexmp_buf + arg->offset, in_buf, in_bufsz);
^
Checking example/cuse.c: FUSE_USE_VERSION=319;FUSE_H_;FUSE_LOWLEVEL_H_...
Checking example/cuse.c: FUSE_USE_VERSION=319;HAVE_LIBFUSE_PRIVATE_CONFIG_H...
Checking example/cuse.c: FUSE_USE_VERSION=319;__STDC_VERSION__...
4/75 files checked 0% done
Checking example/cuse_client.c ...
Checking example/cuse_client.c: FUSE_USE_VERSION=319...
5/75 files checked 1% done
Checking example/hello.c ...
Checking example/hello.c: FUSE_USE_VERSION=319...
Checking example/hello.c: FUSE_USE_VERSION=319;FUSE_LOWLEVEL_H_...
Checking example/hello.c: FUSE_USE_VERSION=319;HAVE_LIBFUSE_PRIVATE_CONFIG_H...
Checking example/hello.c: FUSE_USE_VERSION=319;__STDC_VERSION__...
6/75 files checked 1% done
Checking example/hello_ll_uds.c ...
Checking example/hello_ll_uds.c: FUSE_USE_VERSION=319...
example/hello_ll_uds.c:212:3: error: Resource leak: sfd [resourceLeak]
return -1;
^
example/hello_ll_uds.c:216:3: error: Resource leak: sfd [resourceLeak]
return -1;
^
example/hello_ll_uds.c:223:3: error: Resource leak: sfd [resourceLeak]
return -1;
^
example/hello_ll_uds.c:227:2: error: Resource leak: sfd [resourceLeak]
return cfd;
^
Checking example/hello_ll_uds.c: FUSE_USE_VERSION=319;FUSE_H_;FUSE_LOWLEVEL_H_...
Checking example/hello_ll_uds.c: FUSE_USE_VERSION=319;HAVE_LIBFUSE_PRIVATE_CONFIG_H...
Checking example/hello_ll_uds.c: FUSE_USE_VERSION=319;__KERNEL__...
Checking example/hello_ll_uds.c: FUSE_USE_VERSION=319;__STDC_VERSION__...
7/75 files checked 2% done
Checking example/invalidate_path.c ...
Checking example/invalidate_path.c: FUSE_USE_VERSION=319...
example/invalidate_path.c:169:8: portability: Non reentrant function 'localtime' called. For threadsafe applications it is recommended to use the reentrant replacement function 'localtime_r'. [localtimeCalled]
now = localtime(&t);
^
example/invalidate_path.c:166:13: style: Variable 'now' can be declared as pointer to const [constVariablePointer]
struct tm *now;
^
Checking example/invalidate_path.c: FUSE_USE_VERSION=319;FUSE_LOWLEVEL_H_...
Checking example/invalidate_path.c: FUSE_USE_VERSION=319;HAVE_LIBFUSE_PRIVATE_CONFIG_H...
Checking example/invalidate_path.c: FUSE_USE_VERSION=319;__STDC_VERSION__...
8/75 files checked 3% done
Checking example/ioctl.c ...
Checking example/ioctl.c: FUSE_USE_VERSION=319...
example/ioctl.c:60:18: portability: 'new_buf' is of type 'void *'. When using void pointers in calculations, the behaviour is undefined. [arithOperationsOnVoidPointer]
memset(new_buf + fioc_size, 0, new_size - fioc_size);
^
example/ioctl.c:126:23: portability: 'fioc_buf' is of type 'void *'. When using void pointers in calculations, the behaviour is undefined. [arithOperationsOnVoidPointer]
memcpy(buf, fioc_buf + offset, size);
^
example/ioctl.c:147:18: portability: 'fioc_buf' is of type 'void *'. When using void pointers in calculations, the behaviour is undefined. [arithOperationsOnVoidPointer]
memcpy(fioc_buf + offset, buf, size);
^
Checking example/ioctl.c: FUSE_USE_VERSION=319;FUSE_LOWLEVEL_H_...
Checking example/ioctl.c: FUSE_USE_VERSION=319;HAVE_LIBFUSE_PRIVATE_CONFIG_H...
Checking example/ioctl.c: FUSE_USE_VERSION=319;__STDC_VERSION__...
9/75 files checked 3% done
Checking example/ioctl.h ...
Checking example/ioctl.h: FUSE_USE_VERSION=319...
10/75 files checked 3% done
Checking example/ioctl_client.c ...
Checking example/ioctl_client.c: FUSE_USE_VERSION=319...
11/75 files checked 4% done
Checking example/notify_inval_entry.c ...
Checking example/notify_inval_entry.c: FUSE_USE_VERSION=319...
example/notify_inval_entry.c:264:11: portability: Non reentrant function 'localtime' called. For threadsafe applications it is recommended to use the reentrant replacement function 'localtime_r'. [localtimeCalled]
now = localtime(&t);
^
example/notify_inval_entry.c:141:34: style: Operator '|' with one operand equal to zero is redundant. [badBitmaskCheck]
stbuf->st_mode = S_IFREG | 0000;
^
example/notify_inval_entry.c:274:11: style: The scope of the variable 'old_name' can be reduced. [variableScope]
char *old_name;
^
example/notify_inval_entry.c:260:16: style: Variable 'now' can be declared as pointer to const [constVariablePointer]
struct tm *now;
^
Checking example/notify_inval_entry.c: FUSE_USE_VERSION=319;FUSE_H_;FUSE_LOWLEVEL_H_...
Checking example/notify_inval_entry.c: FUSE_USE_VERSION=319;HAVE_LIBFUSE_PRIVATE_CONFIG_H...
Checking example/notify_inval_entry.c: FUSE_USE_VERSION=319;__STDC_VERSION__...
12/75 files checked 5% done
Checking example/notify_inval_inode.c ...
Checking example/notify_inval_inode.c: FUSE_USE_VERSION=319...
example/notify_inval_inode.c:274:11: portability: Non reentrant function 'localtime' called. For threadsafe applications it is recommended to use the reentrant replacement function 'localtime_r'. [localtimeCalled]
now = localtime(&t);
^
example/notify_inval_inode.c:271:16: style: Variable 'now' can be declared as pointer to const [constVariablePointer]
struct tm *now;
^
Checking example/notify_inval_inode.c: FUSE_USE_VERSION=319;FUSE_H_;FUSE_LOWLEVEL_H_...
Checking example/notify_inval_inode.c: FUSE_USE_VERSION=319;HAVE_LIBFUSE_PRIVATE_CONFIG_H...
Checking example/notify_inval_inode.c: FUSE_USE_VERSION=319;__STDC_VERSION__...
13/75 files checked 6% done
Checking example/notify_store_retrieve.c ...
Checking example/notify_store_retrieve.c: FUSE_USE_VERSION=319...
example/notify_store_retrieve.c:329:11: portability: Non reentrant function 'localtime' called. For threadsafe applications it is recommended to use the reentrant replacement function 'localtime_r'. [localtimeCalled]
now = localtime(&t);
^
example/notify_store_retrieve.c:326:16: style: Variable 'now' can be declared as pointer to const [constVariablePointer]
struct tm *now;
^
example/notify_store_retrieve.c:297:20: error: Uninitialized variable: buf [uninitvar]
assert(strncmp(buf, expected, ret) == 0);
^
Checking example/notify_store_retrieve.c: FUSE_USE_VERSION=319;FUSE_H_;FUSE_LOWLEVEL_H_...
Checking example/notify_store_retrieve.c: FUSE_USE_VERSION=319;HAVE_LIBFUSE_PRIVATE_CONFIG_H...
Checking example/notify_store_retrieve.c: FUSE_USE_VERSION=319;__STDC_VERSION__...
14/75 files checked 7% done
Checking example/passthrough.c ...
Checking example/passthrough.c: FUSE_USE_VERSION=319...
example/passthrough.c:134:15: portability: Non reentrant function 'readdir' called. For threadsafe applications it is recommended to use the reentrant replacement function 'readdir_r'. [readdirCalled]
while ((de = readdir(dp)) != NULL) {
^
example/passthrough_helpers.h:119:2: error: Returning/dereferencing 'res' after it is deallocated / released [deallocret]
return res;
^
example/passthrough_helpers.h:82:10: note: Returning/dereferencing 'res' after it is deallocated / released
res = close(res);
^
example/passthrough_helpers.h:119:2: note: Returning/dereferencing 'res' after it is deallocated / released
return res;
^
Checking example/passthrough.c: FUSE_USE_VERSION=319;FUSE_LOWLEVEL_H_...
Checking example/passthrough.c: FUSE_USE_VERSION=319;HAVE_COPY_FILE_RANGE...
Checking example/passthrough.c: FUSE_USE_VERSION=319;HAVE_FSPACECTL...
Checking example/passthrough.c: FUSE_USE_VERSION=319;HAVE_LIBFUSE_PRIVATE_CONFIG_H...
Checking example/passthrough.c: FUSE_USE_VERSION=319;HAVE_SETXATTR...
Checking example/passthrough.c: FUSE_USE_VERSION=319;HAVE_STATX...
Checking example/passthrough.c: FUSE_USE_VERSION=319;HAVE_UTIMENSAT...
Checking example/passthrough.c: FUSE_USE_VERSION=319;__FreeBSD__...
Checking example/passthrough.c: FUSE_USE_VERSION=319;__STDC_VERSION__...
Checking example/passthrough.c: FUSE_USE_VERSION=319;linux...
15/75 files checked 8% done
Checking example/passthrough_fh.c ...
Checking example/passthrough_fh.c: FUSE_USE_VERSION=319...
example/passthrough_fh.c:175:15: portability: Non reentrant function 'readdir' called. For threadsafe applications it is recommended to use the reentrant replacement function 'readdir_r'. [readdirCalled]
d->entry = readdir(d->dp);
^
example/passthrough_fh.c:143:2: error: Resource leak: d.dp [resourceLeak]
return 0;
^
example/passthrough_fh.c:128:6: style: The scope of the variable 'res' can be reduced. [variableScope]
int res;
^
example/passthrough_fh.c:146:64: style: Parameter 'fi' can be declared as pointer to const [constParameterPointer]
static inline struct xmp_dirp *get_dirp(struct fuse_file_info *fi)
^
Checking example/passthrough_fh.c: FUSE_USE_VERSION=319;FUSE_LOWLEVEL_H_...
Checking example/passthrough_fh.c: FUSE_USE_VERSION=319;HAVE_COPY_FILE_RANGE...
Checking example/passthrough_fh.c: FUSE_USE_VERSION=319;HAVE_FDATASYNC...
Checking example/passthrough_fh.c: FUSE_USE_VERSION=319;HAVE_FSPACECTL...
Checking example/passthrough_fh.c: FUSE_USE_VERSION=319;HAVE_FSTATAT...
Checking example/passthrough_fh.c: FUSE_USE_VERSION=319;HAVE_LIBFUSE_PRIVATE_CONFIG_H...
Checking example/passthrough_fh.c: FUSE_USE_VERSION=319;HAVE_LIBULOCKMGR...
Checking example/passthrough_fh.c: FUSE_USE_VERSION=319;HAVE_SETXATTR...
Checking example/passthrough_fh.c: FUSE_USE_VERSION=319;HAVE_STATX...
Checking example/passthrough_fh.c: FUSE_USE_VERSION=319;HAVE_UTIMENSAT...
Checking example/passthrough_fh.c: FUSE_USE_VERSION=319;__FreeBSD__...
Checking example/passthrough_fh.c: FUSE_USE_VERSION=319;__STDC_VERSION__...
16/75 files checked 10% done
Checking example/passthrough_helpers.h ...
Checking example/passthrough_helpers.h: FUSE_USE_VERSION=319...
Checking example/passthrough_helpers.h: FUSE_USE_VERSION=319;HAVE_FALLOCATE...
Checking example/passthrough_helpers.h: FUSE_USE_VERSION=319;HAVE_FSPACECTL...
Checking example/passthrough_helpers.h: FUSE_USE_VERSION=319;HAVE_POSIX_FALLOCATE...
Checking example/passthrough_helpers.h: FUSE_USE_VERSION=319;__FreeBSD__...
17/75 files checked 10% done
Checking example/passthrough_ll.c ...
Checking example/passthrough_ll.c: FUSE_USE_VERSION=319...
example/passthrough_ll.c:705:15: portability: Non reentrant function 'readdir' called. For threadsafe applications it is recommended to use the reentrant replacement function 'readdir_r'. [readdirCalled]
d->entry = readdir(d->dp);
^
example/passthrough_ll.c:656:2: error: Resource leak: d.dp [resourceLeak]
return;
^
example/passthrough_ll.c:167:18: style: Variable 'lo' can be declared as pointer to const [constVariablePointer]
struct lo_data *lo = (struct lo_data *)userdata;
^
example/passthrough_ll.c:204:18: style: Variable 'lo' can be declared as pointer to const [constVariablePointer]
struct lo_data *lo = lo_data(req);
^
example/passthrough_ll.c:221:19: style: Variable 'inode' can be declared as pointer to const [constVariablePointer]
struct lo_inode *inode = lo_inode(req, ino);
^
example/passthrough_ll.c:291:66: style: Parameter 'st' can be declared as pointer to const [constParameterPointer]
static struct lo_inode *lo_find(struct lo_data *lo, struct stat *st)
^
example/passthrough_ll.c:425:19: style: Variable 'dir' can be declared as pointer to const [constVariablePointer]
struct lo_inode *dir = lo_inode(req, parent);
^
example/passthrough_ll.c:631:18: style: Variable 'lo' can be declared as pointer to const [constVariablePointer]
struct lo_data *lo = lo_data(req);
^
example/passthrough_ll.c:793:18: style: Variable 'lo' can be declared as pointer to const [constVariablePointer]
struct lo_data *lo = lo_data(req);
^
example/passthrough_ll.c:828:18: style: Variable 'lo' can be declared as pointer to const [constVariablePointer]
struct lo_data *lo = lo_data(req);
^
example/passthrough_ll.c:876:18: style: Variable 'lo' can be declared as pointer to const [constVariablePointer]
struct lo_data *lo = lo_data(req);
^
example/passthrough_ll.c:1029:19: style: Variable 'inode' can be declared as pointer to const [constVariablePointer]
struct lo_inode *inode = lo_inode(req, ino);
^
example/passthrough_ll.c:1079:19: style: Variable 'inode' can be declared as pointer to const [constVariablePointer]
struct lo_inode *inode = lo_inode(req, ino);
^
example/passthrough_ll.c:1129:19: style: Variable 'inode' can be declared as pointer to const [constVariablePointer]
struct lo_inode *inode = lo_inode(req, ino);
^
example/passthrough_ll.c:1154:19: style: Variable 'inode' can be declared as pointer to const [constVariablePointer]
struct lo_inode *inode = lo_inode(req, ino);
^
example/passthrough_ll.c:382:9: style: Variable 'newfd' is assigned a value that is never used. [unreadVariable]
newfd = -1;
^
Checking example/passthrough_ll.c: FUSE_USE_VERSION=319;FUSE_H_;FUSE_LOWLEVEL_H_...
Checking example/passthrough_ll.c: FUSE_USE_VERSION=319;HAVE_COPY_FILE_RANGE...
Checking example/passthrough_ll.c: FUSE_USE_VERSION=319;HAVE_FSPACECTL...
Checking example/passthrough_ll.c: FUSE_USE_VERSION=319;HAVE_LIBFUSE_PRIVATE_CONFIG_H...
example/passthrough_ll.c:1220:18: style: Variable 'lo' can be declared as pointer to const [constVariablePointer]
struct lo_data *lo = lo_data(req);
^
Checking example/passthrough_ll.c: FUSE_USE_VERSION=319;HAVE_STATX...
Checking example/passthrough_ll.c: FUSE_USE_VERSION=319;__FreeBSD__...
Checking example/passthrough_ll.c: FUSE_USE_VERSION=319;__GNUC__...
Checking example/passthrough_ll.c: FUSE_USE_VERSION=319;__STDC_VERSION__...
18/75 files checked 14% done
Checking example/poll.c ...
Checking example/poll.c: FUSE_USE_VERSION=319...
Checking example/poll.c: FUSE_USE_VERSION=319;FUSE_LOWLEVEL_H_...
Checking example/poll.c: FUSE_USE_VERSION=319;HAVE_LIBFUSE_PRIVATE_CONFIG_H...
Checking example/poll.c: FUSE_USE_VERSION=319;__STDC_VERSION__...
19/75 files checked 14% done
Checking example/poll_client.c ...
Checking example/poll_client.c: FUSE_USE_VERSION=319...
example/poll_client.c:43:8: style: Variable 'name' can be declared as const array [constVariable]
char name[] = { hex_map[i], '\0' };
^
20/75 files checked 14% done
Checking example/ioctl_ll.c ...
Checking example/ioctl_ll.c: FUSE_USE_VERSION=319...
Checking example/ioctl_ll.c: FUSE_USE_VERSION=319;FUSE_H_;FUSE_LOWLEVEL_H_...
Checking example/ioctl_ll.c: FUSE_USE_VERSION=319;HAVE_LIBFUSE_PRIVATE_CONFIG_H...
Checking example/ioctl_ll.c: FUSE_USE_VERSION=319;__STDC_VERSION__...
21/75 files checked 16% done
Checking example/ioctl_ll_client.c ...
Checking example/ioctl_ll_client.c: FUSE_USE_VERSION=319...
22/75 files checked 16% done
Checking example/notify_prune.c ...
Checking example/notify_prune.c: FUSE_USE_VERSION=319...
example/notify_prune.c:108:8: portability: Non reentrant function 'localtime' called. For threadsafe applications it is recommended to use the reentrant replacement function 'localtime_r'. [localtimeCalled]
now = localtime(&t);
^
example/notify_prune.c:105:13: style: Variable 'now' can be declared as pointer to const [constVariablePointer]
struct tm *now;
^
Checking example/notify_prune.c: FUSE_USE_VERSION=319;FUSE_H_;FUSE_LOWLEVEL_H_...
Checking example/notify_prune.c: FUSE_USE_VERSION=319;HAVE_LIBFUSE_PRIVATE_CONFIG_H...
Checking example/notify_prune.c: FUSE_USE_VERSION=319;__STDC_VERSION__...
23/75 files checked 17% done
Checking example/hello_ll.c ...
Checking example/hello_ll.c: FUSE_USE_VERSION=319...
Checking example/hello_ll.c: FUSE_USE_VERSION=319;FUSE_H_;FUSE_LOWLEVEL_H_...
Checking example/hello_ll.c: FUSE_USE_VERSION=319;HAVE_LIBFUSE_PRIVATE_CONFIG_H...
Checking example/hello_ll.c: FUSE_USE_VERSION=319;__STDC_VERSION__...
24/75 files checked 18% done
Checking example/null.c ...
Checking example/null.c: FUSE_USE_VERSION=319...
Checking example/null.c: FUSE_USE_VERSION=319;FUSE_LOWLEVEL_H_...
Checking example/null.c: FUSE_USE_VERSION=319;HAVE_LIBFUSE_PRIVATE_CONFIG_H...
Checking example/null.c: FUSE_USE_VERSION=319;__STDC_VERSION__...
25/75 files checked 18% done
Checking example/printcap.c ...
Checking example/printcap.c: FUSE_USE_VERSION=319...
example/printcap.c:88:2: warning: %d in format string (no. 1) requires 'int' but the argument type is 'unsigned int'. [invalidPrintfArgType_sint]
printf("Protocol version: %d.%d\n", conn->proto_major,
^
example/printcap.c:88:2: warning: %d in format string (no. 2) requires 'int' but the argument type is 'unsigned int'. [invalidPrintfArgType_sint]
printf("Protocol version: %d.%d\n", conn->proto_major,
^
example/printcap.c:108:3: error: Memory leak: mountpoint [memleak]
return 1;
^
example/printcap.c:106:13: warning: If memory allocation fails, then there is a possible null pointer dereference: mountpoint [nullPointerOutOfMemory]
if(mkdtemp(mountpoint) == NULL) {
^
example/printcap.c:105:21: note: Assuming allocation function fails
mountpoint = strdup("/tmp/fuse_printcap_XXXXXX");
^
example/printcap.c:105:21: note: Assignment 'mountpoint=strdup("/tmp/fuse_printcap_XXXXXX")', assigned value is 0
mountpoint = strdup("/tmp/fuse_printcap_XXXXXX");
^
example/printcap.c:106:13: note: Null pointer dereference
if(mkdtemp(mountpoint) == NULL) {
^
example/printcap.c:74:55: style: Parameter 'conn' can be declared as pointer to const [constParameterPointer]
static void print_capabilities(struct fuse_conn_info *conn)
^
Checking example/printcap.c: FUSE_USE_VERSION=319;FUSE_H_;FUSE_LOWLEVEL_H_...
Checking example/printcap.c: FUSE_USE_VERSION=319;HAVE_LIBFUSE_PRIVATE_CONFIG_H...
Checking example/printcap.c: FUSE_USE_VERSION=319;__STDC_VERSION__...
26/75 files checked 18% done
Checking include/cuse_lowlevel.h ...
Checking include/cuse_lowlevel.h: FUSE_USE_VERSION=319...
Checking include/cuse_lowlevel.h: FUSE_USE_VERSION=319;FUSE_H_;FUSE_LOWLEVEL_H_...
Checking include/cuse_lowlevel.h: FUSE_USE_VERSION=319;HAVE_LIBFUSE_PRIVATE_CONFIG_H...
Checking include/cuse_lowlevel.h: FUSE_USE_VERSION=319;__STDC_VERSION__...
27/75 files checked 19% done
Checking include/fuse_log.h ...
Checking include/fuse_log.h: FUSE_USE_VERSION=319...
28/75 files checked 19% done
Checking include/fuse_mount_compat.h ...
Checking include/fuse_mount_compat.h: FUSE_USE_VERSION=319...
29/75 files checked 19% done
Checking include/fuse_opt.h ...
Checking include/fuse_opt.h: FUSE_USE_VERSION=319...
30/75 files checked 20% done
Checking include/fuse.h ...
Checking include/fuse.h: FUSE_USE_VERSION=319...
Checking include/fuse.h: FUSE_USE_VERSION=319;FUSE_LOWLEVEL_H_...
Checking include/fuse.h: FUSE_USE_VERSION=319;FUSE_USE_VERSION=30...
Checking include/fuse.h: FUSE_USE_VERSION=319;HAVE_LIBFUSE_PRIVATE_CONFIG_H...
Checking include/fuse.h: FUSE_USE_VERSION=319;LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS...
Checking include/fuse.h: FUSE_USE_VERSION=319;__STDC_VERSION__...
31/75 files checked 25% done
Checking include/fuse_common.h ...
Checking include/fuse_common.h: FUSE_USE_VERSION=319;FUSE_H_;FUSE_LOWLEVEL_H_...
32/75 files checked 29% done
Checking include/fuse_kernel.h ...
Checking include/fuse_kernel.h: FUSE_USE_VERSION=319...
Checking include/fuse_kernel.h: FUSE_USE_VERSION=319;__KERNEL__...
33/75 files checked 32% done
Checking include/fuse_lowlevel.h ...
Checking include/fuse_lowlevel.h: FUSE_USE_VERSION=319...
Checking include/fuse_lowlevel.h: FUSE_USE_VERSION=319;FUSE_LOWLEVEL_H_...
Checking include/fuse_lowlevel.h: FUSE_USE_VERSION=319;HAVE_LIBFUSE_PRIVATE_CONFIG_H...
Checking include/fuse_lowlevel.h: FUSE_USE_VERSION=319;LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS...
Checking include/fuse_lowlevel.h: FUSE_USE_VERSION=319;__STDC_VERSION__...
34/75 files checked 41% done
Checking lib/modules/iconv.c ...
Checking lib/modules/iconv.c: FUSE_USE_VERSION=319...
lib/modules/iconv.c:673:3: warning: If memory allocation fails, then there is a possible null pointer dereference: charmap [nullPointerOutOfMemory]
charmap);
^
lib/modules/iconv.c:664:18: note: Assuming allocation function fails
charmap = strdup(nl_langinfo(CODESET));
^
lib/modules/iconv.c:664:18: note: Assignment 'charmap=strdup(nl_langinfo(CODESET))', assigned value is 0
charmap = strdup(nl_langinfo(CODESET));
^
lib/modules/iconv.c:673:3: note: Null pointer dereference
charmap);
^
Checking lib/modules/iconv.c: FUSE_USE_VERSION=319;FUSE_LOWLEVEL_H_...
Checking lib/modules/iconv.c: FUSE_USE_VERSION=319;FUSE_USE_VERSION=30...
Checking lib/modules/iconv.c: FUSE_USE_VERSION=319;HAVE_LIBFUSE_PRIVATE_CONFIG_H...
Checking lib/modules/iconv.c: FUSE_USE_VERSION=319;HAVE_STATX...
Checking lib/modules/iconv.c: FUSE_USE_VERSION=319;__STDC_VERSION__...
35/75 files checked 42% done
Checking lib/modules/subdir.c ...
Checking lib/modules/subdir.c: FUSE_USE_VERSION=319...
lib/modules/subdir.c:104:46: style: Parameter 'd' can be declared as pointer to const [constParameterPointer]
static void transform_symlink(struct subdir *d, const char *path,
^
Checking lib/modules/subdir.c: FUSE_USE_VERSION=319;FUSE_LOWLEVEL_H_...
Checking lib/modules/subdir.c: FUSE_USE_VERSION=319;FUSE_USE_VERSION=30...
Checking lib/modules/subdir.c: FUSE_USE_VERSION=319;HAVE_LIBFUSE_PRIVATE_CONFIG_H...
Checking lib/modules/subdir.c: FUSE_USE_VERSION=319;HAVE_STATX...
Checking lib/modules/subdir.c: FUSE_USE_VERSION=319;__STDC_VERSION__...
36/75 files checked 44% done
Checking lib/buffer.c ...
Checking lib/buffer.c: FUSE_USE_VERSION=319...
Checking lib/buffer.c: FUSE_USE_VERSION=319;FUSE_LOWLEVEL_H_;FUSE_USE_VERSION...
Checking lib/buffer.c: FUSE_USE_VERSION=319;FUSE_USE_VERSION=30...
Checking lib/buffer.c: FUSE_USE_VERSION=319;HAVE_LIBFUSE_PRIVATE_CONFIG_H;FUSE_USE_VERSION...
Checking lib/buffer.c: FUSE_USE_VERSION=319;HAVE_SPLICE;FUSE_USE_VERSION...
Checking lib/buffer.c: FUSE_USE_VERSION=319;__STDC_VERSION__;FUSE_USE_VERSION...
37/75 files checked 45% done
Checking lib/compat.c ...
Checking lib/compat.c: FUSE_USE_VERSION=319...
Checking lib/compat.c: FUSE_USE_VERSION=319;LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS...
Checking lib/compat.c: FUSE_USE_VERSION=319;LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS;fuse_parse_cmdline...
38/75 files checked 45% done
Checking lib/cuse_lowlevel.c ...
Checking lib/cuse_lowlevel.c: FUSE_USE_VERSION=319...
Checking lib/cuse_lowlevel.c: FUSE_USE_VERSION=319;FUSE_LOWLEVEL_H_...
Checking lib/cuse_lowlevel.c: FUSE_USE_VERSION=319;HAVE_LIBFUSE_PRIVATE_CONFIG_H...
Checking lib/cuse_lowlevel.c: FUSE_USE_VERSION=319;__KERNEL__...
Checking lib/cuse_lowlevel.c: FUSE_USE_VERSION=319;__STDC_VERSION__...
39/75 files checked 46% done
Checking lib/fuse_log.c ...
Checking lib/fuse_log.c: FUSE_USE_VERSION=319...
40/75 files checked 46% done
Checking lib/fuse_loop.c ...
Checking lib/fuse_loop.c: FUSE_USE_VERSION=319...
Checking lib/fuse_loop.c: FUSE_USE_VERSION=319;FUSE_LOWLEVEL_H_;FUSE_USE_VERSION...
Checking lib/fuse_loop.c: FUSE_USE_VERSION=319;FUSE_USE_VERSION=30...
Checking lib/fuse_loop.c: FUSE_USE_VERSION=319;HAVE_LIBFUSE_PRIVATE_CONFIG_H;FUSE_USE_VERSION...
Checking lib/fuse_loop.c: FUSE_USE_VERSION=319;__KERNEL__;FUSE_USE_VERSION...
Checking lib/fuse_loop.c: FUSE_USE_VERSION=319;__STDC_VERSION__;FUSE_USE_VERSION...
41/75 files checked 46% done
Checking lib/fuse_loop_mt.c ...
Checking lib/fuse_loop_mt.c: FUSE_USE_VERSION=319...
Checking lib/fuse_loop_mt.c: FUSE_USE_VERSION=319;FUSE_LOWLEVEL_H_;FUSE_USE_VERSION...
Checking lib/fuse_loop_mt.c: FUSE_USE_VERSION=319;FUSE_USE_VERSION=30...
Checking lib/fuse_loop_mt.c: FUSE_USE_VERSION=319;HAVE_LIBFUSE_PRIVATE_CONFIG_H;FUSE_USE_VERSION...
Checking lib/fuse_loop_mt.c: FUSE_USE_VERSION=319;HAVE_STRUCT_STAT_ST_ATIMESPEC...
Checking lib/fuse_loop_mt.c: FUSE_USE_VERSION=319;HAVE_SYMVER_ATTRIBUTE...
Checking lib/fuse_loop_mt.c: FUSE_USE_VERSION=319;__KERNEL__;FUSE_USE_VERSION...
Checking lib/fuse_loop_mt.c: FUSE_USE_VERSION=319;__STDC_VERSION__;FUSE_USE_VERSION...
42/75 files checked 47% done
Checking lib/fuse_misc.h ...
Checking lib/fuse_misc.h: FUSE_USE_VERSION=319...
Checking lib/fuse_misc.h: FUSE_USE_VERSION=319;HAVE_STRUCT_STAT_ST_ATIM...
Checking lib/fuse_misc.h: FUSE_USE_VERSION=319;HAVE_STRUCT_STAT_ST_ATIMESPEC...
Checking lib/fuse_misc.h: FUSE_USE_VERSION=319;HAVE_SYMVER_ATTRIBUTE;LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS...
Checking lib/fuse_misc.h: FUSE_USE_VERSION=319;LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS...
43/75 files checked 48% done
Checking lib/fuse_opt.c ...
Checking lib/fuse_opt.c: FUSE_USE_VERSION=319...
Checking lib/fuse_opt.c: FUSE_USE_VERSION=319;FUSE_LOWLEVEL_H_;FUSE_USE_VERSION...
Checking lib/fuse_opt.c: FUSE_USE_VERSION=319;FUSE_USE_VERSION=30...
Checking lib/fuse_opt.c: FUSE_USE_VERSION=319;HAVE_LIBFUSE_PRIVATE_CONFIG_H;FUSE_USE_VERSION...
Checking lib/fuse_opt.c: FUSE_USE_VERSION=319;HAVE_STRUCT_STAT_ST_ATIMESPEC...
Checking lib/fuse_opt.c: FUSE_USE_VERSION=319;HAVE_SYMVER_ATTRIBUTE...
Checking lib/fuse_opt.c: FUSE_USE_VERSION=319;__STDC_VERSION__;FUSE_USE_VERSION...
44/75 files checked 48% done
Checking lib/fuse_signals.c ...
Checking lib/fuse_signals.c: FUSE_USE_VERSION=319...
Checking lib/fuse_signals.c: FUSE_USE_VERSION=319;FUSE_LOWLEVEL_H_;FUSE_USE_VERSION...
Checking lib/fuse_signals.c: FUSE_USE_VERSION=319;FUSE_USE_VERSION=30...
Checking lib/fuse_signals.c: FUSE_USE_VERSION=319;HAVE_BACKTRACE;FUSE_USE_VERSION...
Checking lib/fuse_signals.c: FUSE_USE_VERSION=319;HAVE_LIBFUSE_PRIVATE_CONFIG_H;FUSE_USE_VERSION...
Checking lib/fuse_signals.c: FUSE_USE_VERSION=319;__STDC_VERSION__;FUSE_USE_VERSION...
45/75 files checked 49% done
Checking lib/fuse_uring_i.h ...
Checking lib/fuse_uring_i.h: FUSE_USE_VERSION=319...
Checking lib/fuse_uring_i.h: FUSE_USE_VERSION=319;FUSE_H_;FUSE_LOWLEVEL_H_;FUSE_USE_VERSION...
Checking lib/fuse_uring_i.h: FUSE_USE_VERSION=319;HAVE_LIBFUSE_PRIVATE_CONFIG_H;FUSE_USE_VERSION...
Checking lib/fuse_uring_i.h: FUSE_USE_VERSION=319;HAVE_URING;FUSE_USE_VERSION...
Checking lib/fuse_uring_i.h: FUSE_USE_VERSION=319;__KERNEL__;FUSE_USE_VERSION...
Checking lib/fuse_uring_i.h: FUSE_USE_VERSION=319;__STDC_VERSION__;FUSE_USE_VERSION...
46/75 files checked 49% done
Checking lib/mount_bsd.c ...
Checking lib/mount_bsd.c: FUSE_USE_VERSION=319...
lib/mount_bsd.c:190:8: style: The scope of the variable 'ret' can be reduced. [variableScope]
int ret = -1;
^
lib/mount_bsd.c:138:16: style: Variable 'dev' can be declared as pointer to const [constVariablePointer]
char *fdnam, *dev;
^
lib/mount_bsd.c:261:64: style: Parameter 'mo' can be declared as pointer to const [constParameterPointer]
int fuse_kern_mount(const char *mountpoint, struct mount_opts *mo)
^
lib/mount_bsd.c:190:12: style: Variable 'ret' is assigned a value that is never used. [unreadVariable]
int ret = -1;
^
Checking lib/mount_bsd.c: FUSE_USE_VERSION=319;FUSE_LOWLEVEL_H_;FUSE_USE_VERSION...
Checking lib/mount_bsd.c: FUSE_USE_VERSION=319;FUSE_USE_VERSION=30...
Checking lib/mount_bsd.c: FUSE_USE_VERSION=319;HAVE_LIBFUSE_PRIVATE_CONFIG_H;FUSE_USE_VERSION...
Checking lib/mount_bsd.c: FUSE_USE_VERSION=319;HAVE_STRUCT_STAT_ST_ATIMESPEC...
Checking lib/mount_bsd.c: FUSE_USE_VERSION=319;HAVE_SYMVER_ATTRIBUTE...
Checking lib/mount_bsd.c: FUSE_USE_VERSION=319;__STDC_VERSION__;FUSE_USE_VERSION...
47/75 files checked 50% done
Checking lib/usdt.h ...
Checking lib/usdt.h: FUSE_USE_VERSION=319...
Checking lib/usdt.h: FUSE_USE_VERSION=319;__LP64__...
Checking lib/usdt.h: FUSE_USE_VERSION=319;__STDC_VERSION__...
Checking lib/usdt.h: FUSE_USE_VERSION=319;__arm__...
Checking lib/usdt.h: FUSE_USE_VERSION=319;__i386__...
Checking lib/usdt.h: FUSE_USE_VERSION=319;__ia64__;__s390__;__s390x__...
Checking lib/usdt.h: FUSE_USE_VERSION=319;__loongarch__...
Checking lib/usdt.h: FUSE_USE_VERSION=319;__powerpc64__;__powerpc__...
Checking lib/usdt.h: FUSE_USE_VERSION=319;__powerpc__...
48/75 files checked 52% done
Checking lib/util.c ...
Checking lib/util.c: FUSE_USE_VERSION=319...
Checking lib/util.c: FUSE_USE_VERSION=319;FUSE_H_;FUSE_LOWLEVEL_H_...
Checking lib/util.c: FUSE_USE_VERSION=319;HAVE_LIBFUSE_PRIVATE_CONFIG_H...
Checking lib/util.c: FUSE_USE_VERSION=319;HAVE_PTHREAD_SETNAME_NP...
Checking lib/util.c: FUSE_USE_VERSION=319;__STDC_VERSION__...
49/75 files checked 52% done
Checking lib/fuse.c ...
Checking lib/fuse.c: FUSE_USE_VERSION=319...
lib/fuse.c:5025:30: style: Parameter 'version' can be declared as pointer to const [constParameterPointer]
struct libfuse_version *version, void *user_data)
^
Checking lib/fuse.c: FUSE_USE_VERSION=319;FUSE_LOWLEVEL_H_;FUSE_USE_VERSION...
lib/fuse.c:2971:19: warning: Uninitialized variable: &e [uninitvar]
reply_entry(req, &e, err);
^
lib/fuse.c:2945:6: note: Assuming condition is false
if (!err) {
^
lib/fuse.c:2971:19: note: Uninitialized variable: &e
reply_entry(req, &e, err);
^
lib/fuse.c:2993:19: warning: Uninitialized variable: &e [uninitvar]
reply_entry(req, &e, err);
^
lib/fuse.c:2983:6: note: Assuming condition is false
if (!err) {
^
lib/fuse.c:2993:19: note: Uninitialized variable: &e
reply_entry(req, &e, err);
^
lib/fuse.c:3076:19: warning: Uninitialized variable: &e [uninitvar]
reply_entry(req, &e, err);
^
lib/fuse.c:3066:6: note: Assuming condition is false
if (!err) {
^
lib/fuse.c:3076:19: note: Uninitialized variable: &e
reply_entry(req, &e, err);
^
lib/fuse.c:3139:19: warning: Uninitialized variable: &e [uninitvar]
reply_entry(req, &e, err);
^
lib/fuse.c:3128:6: note: Assuming condition is false
if (!err) {
^
lib/fuse.c:3139:19: note: Uninitialized variable: &e
reply_entry(req, &e, err);
^
Checking lib/fuse.c: FUSE_USE_VERSION=319;FUSE_USE_VERSION=30...
Checking lib/fuse.c: FUSE_USE_VERSION=319;HAVE_ICONV;FUSE_USE_VERSION...
Checking lib/fuse.c: FUSE_USE_VERSION=319;HAVE_LIBFUSE_PRIVATE_CONFIG_H;FUSE_USE_VERSION...
Checking lib/fuse.c: FUSE_USE_VERSION=319;HAVE_STATX;FUSE_USE_VERSION...
Checking lib/fuse.c: FUSE_USE_VERSION=319;HAVE_STRUCT_STAT_ST_ATIMESPEC...
Checking lib/fuse.c: FUSE_USE_VERSION=319;HAVE_SYMVER_ATTRIBUTE...
Checking lib/fuse.c: FUSE_USE_VERSION=319;HAVE_UTIMENSAT;FUSE_USE_VERSION...
Checking lib/fuse.c: FUSE_USE_VERSION=319;__FreeBSD__;FUSE_USE_VERSION...
Checking lib/fuse.c: FUSE_USE_VERSION=319;__FreeBSD__;__NetBSD__;FUSE_USE_VERSION...
Checking lib/fuse.c: FUSE_USE_VERSION=319;__KERNEL__;FUSE_USE_VERSION...
Checking lib/fuse.c: FUSE_USE_VERSION=319;__STDC_VERSION__;FUSE_USE_VERSION...
50/75 files checked 65% done
Checking lib/fuse_i.h ...
Checking lib/fuse_i.h: FUSE_USE_VERSION=319...
Checking lib/fuse_i.h: FUSE_USE_VERSION=319;FUSE_LOWLEVEL_H_;FUSE_USE_VERSION...
Checking lib/fuse_i.h: FUSE_USE_VERSION=319;FUSE_USE_VERSION=30...
Checking lib/fuse_i.h: FUSE_USE_VERSION=319;HAVE_LIBFUSE_PRIVATE_CONFIG_H;FUSE_USE_VERSION...
Checking lib/fuse_i.h: FUSE_USE_VERSION=319;__STDC_VERSION__;FUSE_USE_VERSION...
51/75 files checked 66% done
Checking lib/fuse_lowlevel.c ...
Checking lib/fuse_lowlevel.c: FUSE_USE_VERSION=319...
lib/fuse_lowlevel.c:1336:15: style: The scope of the variable 'i' can be reduced. [variableScope]
unsigned int i;
^
lib/fuse_lowlevel.c:1445:31: style: The scope of the variable 'arg' can be reduced. [variableScope]
const struct fuse_access_in *arg = op_in;
^
lib/fuse_lowlevel.c:1481:14: style: The scope of the variable 'name' can be reduced. [variableScope]
const char *name = in_payload;
^
lib/fuse_lowlevel.c:1506:14: style: The scope of the variable 'name' can be reduced. [variableScope]
const char *name = in_payload;
^
lib/fuse_lowlevel.c:1530:14: style: The scope of the variable 'name' can be reduced. [variableScope]
const char *name = in_payload;
^
lib/fuse_lowlevel.c:1548:14: style: The scope of the variable 'name' can be reduced. [variableScope]
const char *name = in_payload;
^
lib/fuse_lowlevel.c:1606:32: style: The scope of the variable 'arg' can be reduced. [variableScope]
const struct fuse_rename2_in *arg = op_in;
^
lib/fuse_lowlevel.c:1676:14: style: The scope of the variable 'name' can be reduced. [variableScope]
const char *name = in_payload;
^
lib/fuse_lowlevel.c:1761:14: style: The scope of the variable 'buf' can be reduced. [variableScope]
const char *buf = in_payload;
^
lib/fuse_lowlevel.c:2094:33: style: The scope of the variable 'arg' can be reduced. [variableScope]
const struct fuse_getxattr_in *arg = op_in;
^
lib/fuse_lowlevel.c:2115:33: style: The scope of the variable 'arg' can be reduced. [variableScope]
const struct fuse_getxattr_in *arg = inarg;
^
lib/fuse_lowlevel.c:2132:14: style: The scope of the variable 'name' can be reduced. [variableScope]
const char *name = in_payload;
^
lib/fuse_lowlevel.c:2342:29: style: The scope of the variable 'arg' can be reduced. [variableScope]
const struct fuse_bmap_in *arg = op_in;
^
lib/fuse_lowlevel.c:2359:14: style: The scope of the variable 'in_buf' can be reduced. [variableScope]
const void *in_buf = in_payload;
^
Checking lib/fuse_lowlevel.c: FUSE_USE_VERSION=319;FUSE_LOWLEVEL_H_;FUSE_USE_VERSION...
Checking lib/fuse_lowlevel.c: FUSE_USE_VERSION=319;FUSE_USE_VERSION=30...
Checking lib/fuse_lowlevel.c: FUSE_USE_VERSION=319;HAVE_LIBFUSE_PRIVATE_CONFIG_H;FUSE_USE_VERSION...
Checking lib/fuse_lowlevel.c: FUSE_USE_VERSION=319;HAVE_PIPE2;O_CLOEXEC;HAVE_SPLICE;FUSE_USE_VERSION...
Checking lib/fuse_lowlevel.c: FUSE_USE_VERSION=319;HAVE_SPLICE;FUSE_USE_VERSION...
Checking lib/fuse_lowlevel.c: FUSE_USE_VERSION=319;HAVE_SPLICE;HAVE_VMSPLICE;FUSE_USE_VERSION...
Checking lib/fuse_lowlevel.c: FUSE_USE_VERSION=319;HAVE_STATX;FUSE_USE_VERSION...
Checking lib/fuse_lowlevel.c: FUSE_USE_VERSION=319;HAVE_STRUCT_STAT_ST_ATIMESPEC...
Checking lib/fuse_lowlevel.c: FUSE_USE_VERSION=319;HAVE_SYMVER_ATTRIBUTE...
Checking lib/fuse_lowlevel.c: FUSE_USE_VERSION=319;USDT_ENABLED;FUSE_USE_VERSION...
Checking lib/fuse_lowlevel.c: FUSE_USE_VERSION=319;__KERNEL__;FUSE_USE_VERSION...
Checking lib/fuse_lowlevel.c: FUSE_USE_VERSION=319;__LP64__...
Checking lib/fuse_lowlevel.c: FUSE_USE_VERSION=319;__STDC_VERSION__...
Checking lib/fuse_lowlevel.c: FUSE_USE_VERSION=319;__STDC_VERSION__;FUSE_USE_VERSION...
Checking lib/fuse_lowlevel.c: FUSE_USE_VERSION=319;__arm__...
Checking lib/fuse_lowlevel.c: FUSE_USE_VERSION=319;__i386__...
Checking lib/fuse_lowlevel.c: FUSE_USE_VERSION=319;__ia64__;__s390__;__s390x__...
Checking lib/fuse_lowlevel.c: FUSE_USE_VERSION=319;__loongarch__...
Checking lib/fuse_lowlevel.c: FUSE_USE_VERSION=319;__powerpc64__;__powerpc__...
Checking lib/fuse_lowlevel.c: FUSE_USE_VERSION=319;__powerpc__...
Checking lib/fuse_lowlevel.c: FUSE_USE_VERSION=319;linux;FUSE_USE_VERSION...
52/75 files checked 78% done
Checking lib/helper.c ...
Checking lib/helper.c: FUSE_USE_VERSION=319...
Checking lib/helper.c: FUSE_USE_VERSION=319;FUSE_LOWLEVEL_H_;FUSE_USE_VERSION...
Checking lib/helper.c: FUSE_USE_VERSION=319;FUSE_USE_VERSION=30...
Checking lib/helper.c: FUSE_USE_VERSION=319;HAVE_LIBFUSE_PRIVATE_CONFIG_H;FUSE_USE_VERSION...
Checking lib/helper.c: FUSE_USE_VERSION=319;HAVE_STRUCT_STAT_ST_ATIMESPEC...
Checking lib/helper.c: FUSE_USE_VERSION=319;HAVE_SYMVER_ATTRIBUTE...
Checking lib/helper.c: FUSE_USE_VERSION=319;__FreeBSD__;FUSE_USE_VERSION...
Checking lib/helper.c: FUSE_USE_VERSION=319;__STDC_VERSION__;FUSE_USE_VERSION...
53/75 files checked 80% done
Checking lib/fuse_uring.c ...
Checking lib/fuse_uring.c: FUSE_USE_VERSION=319...
Checking lib/fuse_uring.c: FUSE_USE_VERSION=319;FUSE_LOWLEVEL_H_;FUSE_USE_VERSION...
Checking lib/fuse_uring.c: FUSE_USE_VERSION=319;FUSE_USE_VERSION=30...
Checking lib/fuse_uring.c: FUSE_USE_VERSION=319;HAVE_LIBFUSE_PRIVATE_CONFIG_H;FUSE_USE_VERSION...
Checking lib/fuse_uring.c: FUSE_USE_VERSION=319;__KERNEL__;FUSE_USE_VERSION...
Checking lib/fuse_uring.c: FUSE_USE_VERSION=319;__STDC_VERSION__;FUSE_USE_VERSION...
54/75 files checked 82% done
Checking lib/mount.c ...
Checking lib/mount.c: FUSE_USE_VERSION=319...
lib/mount.c:130:63: style: Parameter 'action' can be declared as pointer to const [constParameterPointer]
static int fusermount_posix_spawn(posix_spawn_file_actions_t *action,
^
Checking lib/mount.c: FUSE_USE_VERSION=319;FUSE_LOWLEVEL_H_;FUSE_USE_VERSION...
Checking lib/mount.c: FUSE_USE_VERSION=319;FUSE_USE_VERSION=30...
Checking lib/mount.c: FUSE_USE_VERSION=319;HAVE_LIBFUSE_PRIVATE_CONFIG_H;FUSE_USE_VERSION...
Checking lib/mount.c: FUSE_USE_VERSION=319;HAVE_STRUCT_STAT_ST_ATIMESPEC...
Checking lib/mount.c: FUSE_USE_VERSION=319;HAVE_SYMVER_ATTRIBUTE...
Checking lib/mount.c: FUSE_USE_VERSION=319;__NetBSD__;FUSE_USE_VERSION...
Checking lib/mount.c: FUSE_USE_VERSION=319;__STDC_VERSION__;FUSE_USE_VERSION...
55/75 files checked 84% done
Checking lib/mount_util.c ...
Checking lib/mount_util.c: FUSE_USE_VERSION=319...
Checking lib/mount_util.c: FUSE_USE_VERSION=319;__ANDROID__;__DragonFly__;__FreeBSD__;__NetBSD__...
Checking lib/mount_util.c: FUSE_USE_VERSION=319;__DragonFly__;__FreeBSD__;__FreeBSD_kernel__;__NetBSD__...
56/75 files checked 85% done
Checking lib/mount_util.h ...
Checking lib/mount_util.h: FUSE_USE_VERSION=319...
57/75 files checked 85% done
Checking lib/util.h ...
Checking lib/util.h: FUSE_USE_VERSION=319...
58/75 files checked 85% done
Checking test/hello.c ...
Checking test/hello.c: FUSE_USE_VERSION=319...
Checking test/hello.c: FUSE_USE_VERSION=319;FUSE_LOWLEVEL_H_...
Checking test/hello.c: FUSE_USE_VERSION=319;HAVE_LIBFUSE_PRIVATE_CONFIG_H...
Checking test/hello.c: FUSE_USE_VERSION=319;__STDC_VERSION__...
59/75 files checked 86% done
Checking test/readdir_inode.c ...
Checking test/readdir_inode.c: FUSE_USE_VERSION=319...
test/readdir_inode.c:31:12: portability: Non reentrant function 'readdir' called. For threadsafe applications it is recommended to use the reentrant replacement function 'readdir_r'. [readdirCalled]
dent = readdir(dirp);
^
test/readdir_inode.c:47:16: portability: Non reentrant function 'readdir' called. For threadsafe applications it is recommended to use the reentrant replacement function 'readdir_r'. [readdirCalled]
dent = readdir(dirp);
^
test/readdir_inode.c:17:20: style: Variable 'dent' can be declared as pointer to const [constVariablePointer]
struct dirent* dent;
^
60/75 files checked 86% done
Checking test/release_unlink_race.c ...
Checking test/release_unlink_race.c: FUSE_USE_VERSION=319...
test/release_unlink_race.c:66:56: style: Obsolescent function 'usleep' called. It is recommended to use 'nanosleep' or 'setitimer' instead. [usleepCalled]
if(!getenv("RELEASEUNLINKRACE_DELAY_DISABLE")) usleep(100000);
^
test/release_unlink_race.c:91:56: style: Obsolescent function 'usleep' called. It is recommended to use 'nanosleep' or 'setitimer' instead. [usleepCalled]
if(!getenv("RELEASEUNLINKRACE_DELAY_DISABLE")) usleep(100000);
^
Checking test/release_unlink_race.c: FUSE_USE_VERSION=319;FUSE_LOWLEVEL_H_...
Checking test/release_unlink_race.c: FUSE_USE_VERSION=319;HAVE_LIBFUSE_PRIVATE_CONFIG_H...
Checking test/release_unlink_race.c: FUSE_USE_VERSION=319;__STDC_VERSION__...
61/75 files checked 86% done
Checking test/stracedecode.c ...
Checking test/stracedecode.c: FUSE_USE_VERSION=319...
test/stracedecode.c:68:3: warning: %i in format string (no. 3) requires 'int' but the argument type is 'unsigned int'. [invalidPrintfArgType_sint]
printf("unique: %llu, opcode: %s (%i), nodeid: %lu, len: %i, insize: %i\n",
^
test/stracedecode.c:68:3: warning: %i in format string (no. 5) requires 'int' but the argument type is 'unsigned int'. [invalidPrintfArgType_sint]
printf("unique: %llu, opcode: %s (%i), nodeid: %lu, len: %i, insize: %i\n",
^
test/stracedecode.c:76:4: warning: %llu in format string (no. 1) requires 'unsigned long long' but the argument type is 'unsigned long'. [invalidPrintfArgType_uint]
printf("-READ fh:%llu off:%llu siz:%u rfl:%u own:%llu fl:%u\n",
^
test/stracedecode.c:76:4: warning: %llu in format string (no. 2) requires 'unsigned long long' but the argument type is 'unsigned long'. [invalidPrintfArgType_uint]
printf("-READ fh:%llu off:%llu siz:%u rfl:%u own:%llu fl:%u\n",
^
test/stracedecode.c:76:4: warning: %llu in format string (no. 5) requires 'unsigned long long' but the argument type is 'unsigned long'. [invalidPrintfArgType_uint]
printf("-READ fh:%llu off:%llu siz:%u rfl:%u own:%llu fl:%u\n",
^
test/stracedecode.c:83:4: warning: %llu in format string (no. 1) requires 'unsigned long long' but the argument type is 'unsigned long'. [invalidPrintfArgType_uint]
printf("-WRITE fh:%llu off:%llu siz:%u wfl:%u own:%llu fl:%u\n",
^
test/stracedecode.c:83:4: warning: %llu in format string (no. 2) requires 'unsigned long long' but the argument type is 'unsigned long'. [invalidPrintfArgType_uint]
printf("-WRITE fh:%llu off:%llu siz:%u wfl:%u own:%llu fl:%u\n",
^
test/stracedecode.c:83:4: warning: %llu in format string (no. 5) requires 'unsigned long long' but the argument type is 'unsigned long'. [invalidPrintfArgType_uint]
printf("-WRITE fh:%llu off:%llu siz:%u wfl:%u own:%llu fl:%u\n",
^
test/stracedecode.c:95:3: warning: %i in format string (no. 4) requires 'int' but the argument type is 'unsigned int'. [invalidPrintfArgType_sint]
printf(" unique: %llu, error: %i (%s), len: %i, outsize: %i\n",
^
test/stracedecode.c:103:5: warning: %llu in format string (no. 1) requires 'unsigned long long' but the argument type is 'unsigned long'. [invalidPrintfArgType_uint]
printf("+ATTR v:%llu.%09u i:%llu s:%llu b:%llu\n",
^
test/stracedecode.c:103:5: warning: %llu in format string (no. 3) requires 'unsigned long long' but the argument type is 'unsigned long'. [invalidPrintfArgType_uint]
printf("+ATTR v:%llu.%09u i:%llu s:%llu b:%llu\n",
^
test/stracedecode.c:103:5: warning: %llu in format string (no. 4) requires 'unsigned long long' but the argument type is 'unsigned long'. [invalidPrintfArgType_uint]
printf("+ATTR v:%llu.%09u i:%llu s:%llu b:%llu\n",
^
test/stracedecode.c:103:5: warning: %llu in format string (no. 5) requires 'unsigned long long' but the argument type is 'unsigned long'. [invalidPrintfArgType_uint]
printf("+ATTR v:%llu.%09u i:%llu s:%llu b:%llu\n",
^
test/stracedecode.c:110:5: warning: %llu in format string (no. 1) requires 'unsigned long long' but the argument type is 'unsigned long'. [invalidPrintfArgType_uint]
printf("+ENTRY nodeid:%llu v:%llu.%09u i:%llu s:%llu b:%llu\n",
^
test/stracedecode.c:110:5: warning: %llu in format string (no. 2) requires 'unsigned long long' but the argument type is 'unsigned long'. [invalidPrintfArgType_uint]
printf("+ENTRY nodeid:%llu v:%llu.%09u i:%llu s:%llu b:%llu\n",
^
test/stracedecode.c:110:5: warning: %llu in format string (no. 4) requires 'unsigned long long' but the argument type is 'unsigned long'. [invalidPrintfArgType_uint]
printf("+ENTRY nodeid:%llu v:%llu.%09u i:%llu s:%llu b:%llu\n",
^
test/stracedecode.c:110:5: warning: %llu in format string (no. 5) requires 'unsigned long long' but the argument type is 'unsigned long'. [invalidPrintfArgType_uint]
printf("+ENTRY nodeid:%llu v:%llu.%09u i:%llu s:%llu b:%llu\n",
^
test/stracedecode.c:110:5: warning: %llu in format string (no. 6) requires 'unsigned long long' but the argument type is 'unsigned long'. [invalidPrintfArgType_uint]
printf("+ENTRY nodeid:%llu v:%llu.%09u i:%llu s:%llu b:%llu\n",
^
test/stracedecode.c:178:14: warning: %x in format string (no. 1) requires 'unsigned int *' but the argument type is 'signed int *'. [invalidScanfArgType_int]
res = scanf("%x", &val);
^
test/stracedecode.c:65:26: style: Variable 'in' can be declared as pointer to const [constVariablePointer]
struct fuse_in_header *in = (struct fuse_in_header *) buf;
^
test/stracedecode.c:75:25: style: Variable 'arg' can be declared as pointer to const [constVariablePointer]
struct fuse_read_in *arg = (struct fuse_read_in *) buf;
^
test/stracedecode.c:82:26: style: Variable 'arg' can be declared as pointer to const [constVariablePointer]
struct fuse_write_in *arg = (struct fuse_write_in *) buf;
^
test/stracedecode.c:92:27: style: Variable 'out' can be declared as pointer to const [constVariablePointer]
struct fuse_out_header *out = (struct fuse_out_header *) buf;
^
test/stracedecode.c:102:27: style: Variable 'arg' can be declared as pointer to const [constVariablePointer]
struct fuse_attr_out *arg = (struct fuse_attr_out *) buf;
^
test/stracedecode.c:109:28: style: Variable 'arg' can be declared as pointer to const [constVariablePointer]
struct fuse_entry_out *arg = (struct fuse_entry_out *) buf;
^
Checking test/stracedecode.c: FUSE_USE_VERSION=319;__KERNEL__...
62/75 files checked 86% done
Checking test/test_abi.c ...
Checking test/test_abi.c: FUSE_USE_VERSION=319...
Checking test/test_abi.c: FUSE_USE_VERSION=319;FUSE_LOWLEVEL_H_...
Checking test/test_abi.c: FUSE_USE_VERSION=319;HAVE_LIBFUSE_PRIVATE_CONFIG_H...
Checking test/test_abi.c: FUSE_USE_VERSION=319;__STDC_VERSION__...
63/75 files checked 86% done
Checking test/test_setattr.c ...
Checking test/test_setattr.c: FUSE_USE_VERSION=319...
test/test_setattr.c:142:12: warning: Assert statement calls a function which may have desired side effects: 'snprintf'. [assertWithSideEffect]
assert(snprintf(fname, PATH_MAX, "%s/" FILE_NAME,
^
test/test_setattr.c:150:12: warning: Assert statement calls a function which may have desired side effects: 'fchmod'. [assertWithSideEffect]
assert(fchmod(fd, 0600) == 0);
^
test/test_setattr.c:171:12: warning: Assert statement calls a function which may have desired side effects: 'pthread_create'. [assertWithSideEffect]
assert(pthread_create(&fs_thread, NULL, run_fs, (void *)se) == 0);
^
test/test_setattr.c:150:19: warning: Either the condition 'fd==-1' is redundant or fchmod() argument nr 1 can have invalid value. The value is -1 but the valid values are '0:'. [invalidFunctionArg]
assert(fchmod(fd, 0600) == 0);
^
test/test_setattr.c:145:12: note: Assuming that condition 'fd==-1' is not redundant
if (fd == -1) {
^
test/test_setattr.c:150:19: note: Invalid argument
assert(fchmod(fd, 0600) == 0);
^
test/test_setattr.c:151:11: warning: Either the condition 'fd==-1' is redundant or close() argument nr 1 can have invalid value. The value is -1 but the valid values are '0:'. [invalidFunctionArg]
close(fd);
^
test/test_setattr.c:145:12: note: Assuming that condition 'fd==-1' is not redundant
if (fd == -1) {
^
test/test_setattr.c:151:11: note: Invalid argument
close(fd);
^
Checking test/test_setattr.c: FUSE_USE_VERSION=319;FUSE_LOWLEVEL_H_...
Checking test/test_setattr.c: FUSE_USE_VERSION=319;HAVE_LIBFUSE_PRIVATE_CONFIG_H...
Checking test/test_setattr.c: FUSE_USE_VERSION=319;__STDC_VERSION__...
Checking test/test_setattr.c: FUSE_USE_VERSION=319;__linux__...
64/75 files checked 87% done
Checking test/test_signals.c ...
Checking test/test_signals.c: FUSE_USE_VERSION=319...
test/test_signals.c:56:2: style: Obsolescent function 'usleep' called. It is recommended to use 'nanosleep' or 'setitimer' instead. [usleepCalled]
usleep(2 * 1000 * 1000);
^
test/test_signals.c:56:18: error: Invalid usleep() argument nr 1. The value is 2000000 but the valid values are '0:999999'. [invalidFunctionArg]
usleep(2 * 1000 * 1000);
^
Checking test/test_signals.c: FUSE_USE_VERSION=319;FUSE_LOWLEVEL_H_...
Checking test/test_signals.c: FUSE_USE_VERSION=319;HAVE_LIBFUSE_PRIVATE_CONFIG_H...
Checking test/test_signals.c: FUSE_USE_VERSION=319;__STDC_VERSION__...
65/75 files checked 87% done
Checking test/test_syscalls.c ...
Checking test/test_syscalls.c: FUSE_USE_VERSION=319...
test/test_syscalls.c:420:8: portability: Non reentrant function 'readdir' called. For threadsafe applications it is recommended to use the reentrant replacement function 'readdir_r'. [readdirCalled]
de = readdir(dp);
^
test/test_syscalls.c:794:8: portability: Non reentrant function 'readdir' called. For threadsafe applications it is recommended to use the reentrant replacement function 'readdir_r'. [readdirCalled]
de = readdir(dp);
^
test/test_syscalls.c:806:8: portability: Non reentrant function 'readdir' called. For threadsafe applications it is recommended to use the reentrant replacement function 'readdir_r'. [readdirCalled]
de = readdir(dp);
^
test/test_syscalls.c:811:8: portability: Non reentrant function 'readdir' called. For threadsafe applications it is recommended to use the reentrant replacement function 'readdir_r'. [readdirCalled]
de = readdir(dp);
^
test/test_syscalls.c:984:8: style: Obsolescent function 'utime' called. It is recommended to use 'utimensat' instead. [utimeCalled]
res = utime(testfile, &utm);
^
test/test_syscalls.c:1934:3: error: Resource leak: fd [resourceLeak]
return -1;
^
test/test_syscalls.c:2038:3: error: Resource leak: fd [resourceLeak]
return -1;
^
test/test_syscalls.c:2072:3: error: Resource leak: fd [resourceLeak]
return -1;
^
test/test_syscalls.c:2083:3: error: Resource leak: fd [resourceLeak]
return -1;
^
test/test_syscalls.c:2088:3: error: Resource leak: fd [resourceLeak]
return -1;
^
test/test_syscalls.c:275:56: style: Parameter 'st' can be declared as pointer to const [constParameterPointer]
static int fcheck_stat(int fd, int flags, struct stat *st)
^
Checking test/test_syscalls.c: FUSE_USE_VERSION=319;HAVE_COPY_FILE_RANGE...
Checking test/test_syscalls.c: FUSE_USE_VERSION=319;HAVE_STATX...
66/75 files checked 92% done
Checking test/test_want_conversion.c ...
Checking test/test_want_conversion.c: FUSE_USE_VERSION=319...
Checking test/test_want_conversion.c: FUSE_USE_VERSION=319;FUSE_LOWLEVEL_H_...
Checking test/test_want_conversion.c: FUSE_USE_VERSION=319;HAVE_LIBFUSE_PRIVATE_CONFIG_H...
Checking test/test_want_conversion.c: FUSE_USE_VERSION=319;__STDC_VERSION__...
67/75 files checked 93% done
Checking test/test_write_cache.c ...
Checking test/test_write_cache.c: FUSE_USE_VERSION=319...
test/test_write_cache.c:232:9: warning: Assert statement calls a function which may have desired side effects: 'read'. [assertWithSideEffect]
assert(read(fd, buf, dsize) == dsize);
^
test/test_write_cache.c:235:9: warning: Assert statement calls a function which may have desired side effects: 'snprintf'. [assertWithSideEffect]
assert(snprintf(fname, PATH_MAX, "%s/" FILE_NAME, mountpoint) > 0);
^
test/test_write_cache.c:245:10: warning: Assert statement calls a function which may have desired side effects: 'pthread_create'. [assertWithSideEffect]
assert(pthread_create(&rofd_thread, NULL, close_rofd,
^
test/test_write_cache.c:252:10: warning: Assert statement calls a function which may have desired side effects: 'pwrite'. [assertWithSideEffect]
assert(pwrite(fd, buf + off, iosize, off) == iosize);
^
test/test_write_cache.c:262:10: warning: Assert statement calls a function which may have desired side effects: 'pthread_join'. [assertWithSideEffect]
assert(pthread_join(rofd_thread, NULL) == 0);
^
test/test_write_cache.c:285:9: warning: Assert statement calls a function which may have desired side effects: 'pthread_create'. [assertWithSideEffect]
assert(pthread_create(&fs_thread, NULL, run_fs, (void *)se) == 0);
^
test/test_write_cache.c:294:9: warning: Assert statement calls a function which may have desired side effects: 'pthread_join'. [assertWithSideEffect]
assert(pthread_join(fs_thread, NULL) == 0);
^
test/test_write_cache.c:170:3: style: Obsolescent function 'usleep' called. It is recommended to use 'nanosleep' or 'setitimer' instead. [usleepCalled]
usleep(options.delay_ms * 1000);
^
test/test_write_cache.c:248:3: style: Obsolescent function 'usleep' called. It is recommended to use 'nanosleep' or 'setitimer' instead. [usleepCalled]
usleep(options.delay_ms * 1000);
^
test/test_write_cache.c:249:2: error: Resource leak: rofd [resourceLeak]
}
^
Checking test/test_write_cache.c: FUSE_USE_VERSION=319;FUSE_LOWLEVEL_H_...
Checking test/test_write_cache.c: FUSE_USE_VERSION=319;HAVE_LIBFUSE_PRIVATE_CONFIG_H...
Checking test/test_write_cache.c: FUSE_USE_VERSION=319;__STDC_VERSION__...
Checking test/test_write_cache.c: FUSE_USE_VERSION=319;__linux__...
68/75 files checked 93% done
Checking test/wrong_command.c ...
Checking test/wrong_command.c: FUSE_USE_VERSION=319...
Checking test/wrong_command.c: FUSE_USE_VERSION=319;MESON_IS_SUBPROJECT...
69/75 files checked 93% done
Checking test/test_teardown_watchdog.c ...
Checking test/test_teardown_watchdog.c: FUSE_USE_VERSION=319...
test/test_teardown_watchdog.c:71:2: style: Obsolescent function 'usleep' called. It is recommended to use 'nanosleep' or 'setitimer' instead. [usleepCalled]
usleep(uta->delay_ms * 1000);
^
test/test_teardown_watchdog.c:165:2: style: Obsolescent function 'usleep' called. It is recommended to use 'nanosleep' or 'setitimer' instead. [usleepCalled]
usleep(500 * 1000);
^
test/test_teardown_watchdog.c:180:3: style: Obsolescent function 'usleep' called. It is recommended to use 'nanosleep' or 'setitimer' instead. [usleepCalled]
usleep(100 * 1000);
^
Checking test/test_teardown_watchdog.c: FUSE_USE_VERSION=319;FUSE_LOWLEVEL_H_...
Checking test/test_teardown_watchdog.c: FUSE_USE_VERSION=319;HAVE_LIBFUSE_PRIVATE_CONFIG_H...
Checking test/test_teardown_watchdog.c: FUSE_USE_VERSION=319;__STDC_VERSION__...
70/75 files checked 94% done
Checking util/fusermount.c ...
Checking util/fusermount.c: FUSE_USE_VERSION=319...
util/fusermount.c:185:22: portability: Non reentrant function 'getpwuid' called. For threadsafe applications it is recommended to use the reentrant replacement function 'getpwuid_r'. [getpwuidCalled]
struct passwd *pw = getpwuid(getuid());
^
util/fusermount.c:1328:12: warning: If memory allocation fails, then there is a possible null pointer dereference: x_mnt_opts [nullPointerOutOfMemory]
strncat(x_mnt_opts, x_opts,
^
util/fusermount.c:1321:29: note: Assuming allocation function fails
char *x_mnt_opts = calloc(1, x_mnt_opts_len);
^
util/fusermount.c:1321:29: note: Assignment 'x_mnt_opts=calloc(1,x_mnt_opts_len)', assigned value is 0
char *x_mnt_opts = calloc(1, x_mnt_opts_len);
^
util/fusermount.c:1328:12: note: Null pointer dereference
strncat(x_mnt_opts, x_opts,
^
util/fusermount.c:357:14: style: The scope of the variable 'mnt' can be reduced. [variableScope]
const char *mnt = a[1];
^
util/fusermount.c:602:6: style: The scope of the variable 'n_mounts' can be reduced. [variableScope]
int n_mounts = 0;
^
util/fusermount.c:609:20: style: Variable 'sm' can be declared as pointer to const [constVariablePointer]
struct statmount *sm;
^
util/fusermount.c:602:15: style: Variable 'n_mounts' is assigned a value that is never used. [unreadVariable]
int n_mounts = 0;
^
util/fusermount.c:603:10: style: Variable 'ret' is assigned a value that is never used. [unreadVariable]
int ret = 0;
^
Checking util/fusermount.c: FUSE_USE_VERSION=319;GETMNTENT_NEEDS_UNESCAPING...
Checking util/fusermount.c: FUSE_USE_VERSION=319;HAVE_CLOSE_RANGE...
Checking util/fusermount.c: FUSE_USE_VERSION=319;HAVE_CLOSE_RANGE;linux...
Checking util/fusermount.c: FUSE_USE_VERSION=319;HAVE_LISTMOUNT...
Checking util/fusermount.c: FUSE_USE_VERSION=319;IGNORE_MTAB...
Checking util/fusermount.c: FUSE_USE_VERSION=319;__ia64__...
71/75 files checked 98% done
Checking util/mount.fuse.c ...
Checking util/mount.fuse.c: FUSE_USE_VERSION=319...
util/mount.fuse.c:314:10: portability: Non reentrant function 'strtok' called. For threadsafe applications it is recommended to use the reentrant replacement function 'strtok_r'. [strtokCalled]
opt = strtok(opts, ",");
^
util/mount.fuse.c:348:11: portability: Non reentrant function 'strtok' called. For threadsafe applications it is recommended to use the reentrant replacement function 'strtok_r'. [strtokCalled]
opt = strtok(NULL, ",");
^
util/mount.fuse.c:409:24: portability: Non reentrant function 'getpwnam' called. For threadsafe applications it is recommended to use the reentrant replacement function 'getpwnam_r'. [getpwnamCalled]
struct passwd *pwd = getpwnam(setuid_name);
^
util/mount.fuse.c:409:18: style: Variable 'pwd' can be declared as pointer to const [constVariablePointer]
struct passwd *pwd = getpwnam(setuid_name);
^
Checking util/mount.fuse.c: FUSE_USE_VERSION=319;FUSE_LOWLEVEL_H_...
Checking util/mount.fuse.c: FUSE_USE_VERSION=319;FUSE_USE_VERSION=30...
Checking util/mount.fuse.c: FUSE_USE_VERSION=319;HAVE_LIBFUSE_PRIVATE_CONFIG_H...
Checking util/mount.fuse.c: FUSE_USE_VERSION=319;SECBIT_KEEP_CAPS;SECURE_KEEP_CAPS;linux...
util/mount.fuse.c:212:4: warning: %u in format string (no. 2) requires 'unsigned int' but the argument type is 'signed int'. [invalidPrintfArgType_uint]
fprintf(stderr,
^
util/mount.fuse.c:218:4: warning: %u in format string (no. 2) requires 'unsigned int' but the argument type is 'signed int'. [invalidPrintfArgType_uint]
fprintf(stderr,
^
Checking util/mount.fuse.c: FUSE_USE_VERSION=319;SECBIT_KEEP_CAPS_LOCKED;SECURE_KEEP_CAPS_LOCKED;linux...
Checking util/mount.fuse.c: FUSE_USE_VERSION=319;SECBIT_NOROOT;SECURE_NOROOT;linux...
Checking util/mount.fuse.c: FUSE_USE_VERSION=319;SECBIT_NOROOT_LOCKED;SECURE_NOROOT_LOCKED;linux...
Checking util/mount.fuse.c: FUSE_USE_VERSION=319;SECBIT_NO_SETUID_FIXUP;SECURE_NO_SETUID_FIXUP;linux...
Checking util/mount.fuse.c: FUSE_USE_VERSION=319;SECBIT_NO_SETUID_FIXUP_LOCKED;SECURE_NO_SETUID_FIXUP_LOCKED;linux...
Checking util/mount.fuse.c: FUSE_USE_VERSION=319;__STDC_VERSION__...
Checking util/mount.fuse.c: FUSE_USE_VERSION=319;linux...
72/75 files checked 99% done
Checking build-x86_64/meson-private/sanitycheckc.c ...
Checking build-x86_64/meson-private/sanitycheckc.c: FUSE_USE_VERSION=319...
73/75 files checked 99% done
Checking build-x86_64/fuse_config.h ...
Checking build-x86_64/fuse_config.h: FUSE_USE_VERSION=319...
74/75 files checked 99% done
Checking build-x86_64/libfuse_config.h ...
Checking build-x86_64/libfuse_config.h: FUSE_USE_VERSION=319...
75/75 files checked 100% done
include/fuse.h:923:0: style: The function 'fuse_main_real' is never used. [unusedFunction]
static inline int fuse_main_real(int argc, char *argv[],
^
lib/fuse.c:370:0: style: The function 'list_add_head' is never used. [unusedFunction]
static inline void list_add_head(struct list_head *new, struct list_head *head)
^
lib/fuse.c:1819:0: style: The function 'fuse_fs_read' is never used. [unusedFunction]
int fuse_fs_read(struct fuse_fs *fs, const char *path, char *mem, size_t size,
^
lib/fuse.c:1924:0: style: The function 'fuse_fs_write' is never used. [unusedFunction]
int fuse_fs_write(struct fuse_fs *fs, const char *path, const char *mem,
^
lib/fuse.c:4718:0: style: The function 'fuse_loop_mt_31' is never used. [unusedFunction]
int fuse_loop_mt_31(struct fuse *f, int clone_fd)
^
lib/fuse.c:4735:0: style: The function 'fuse_exit' is never used. [unusedFunction]
void fuse_exit(struct fuse *f)
^
lib/fuse.c:4750:0: style: The function 'fuse_getgroups' is never used. [unusedFunction]
int fuse_getgroups(int size, gid_t list[])
^
lib/fuse.c:4759:0: style: The function 'fuse_interrupted' is never used. [unusedFunction]
int fuse_interrupted(void)
^
lib/fuse.c:5213:0: style: The function 'fuse_new_30' is never used. [unusedFunction]
struct fuse *fuse_new_30(struct fuse_args *args,
^
lib/fuse.c:5299:0: style: The function 'fuse_version' is never used. [unusedFunction]
int fuse_version(void)
^
lib/fuse_log.c:32:0: style: The function 'fuse_set_log_func' is never used. [unusedFunction]
void fuse_set_log_func(fuse_log_func_t func)
^
lib/fuse_log.c:49:0: style: The function 'fuse_log_enable_syslog' is never used. [unusedFunction]
void fuse_log_enable_syslog(const char *ident, int option, int facility)
^
lib/fuse_log.c:56:0: style: The function 'fuse_log_close_syslog' is never used. [unusedFunction]
void fuse_log_close_syslog(void)
^
lib/fuse_loop_mt.c:439:0: style: The function 'fuse_session_loop_mt_32' is never used. [unusedFunction]
int fuse_session_loop_mt_32(struct fuse_session *se, const struct fuse_loop_config_v1 *config_v1)
^
lib/fuse_loop_mt.c:463:0: style: The function 'fuse_session_loop_mt_31' is never used. [unusedFunction]
int fuse_session_loop_mt_31(struct fuse_session *se, int clone_fd)
^
lib/fuse_lowlevel.c:365:0: style: The function 'fuse_reply_iov' is never used. [unusedFunction]
int fuse_reply_iov(fuse_req_t req, const struct iovec *iov, int count)
^
lib/fuse_lowlevel.c:580:0: style: The function 'fuse_passthrough_open' is never used. [unusedFunction]
int fuse_passthrough_open(fuse_req_t req, int fd)
^
lib/fuse_lowlevel.c:594:0: style: The function 'fuse_passthrough_close' is never used. [unusedFunction]
int fuse_passthrough_close(fuse_req_t req, int backing_id)
^
lib/fuse_lowlevel.c:3240:0: style: The function 'fuse_lowlevel_notify_delete' is never used. [unusedFunction]
int fuse_lowlevel_notify_delete(struct fuse_session *se,
^
lib/fuse_lowlevel.c:3450:0: style: The function 'fuse_req_is_uring' is never used. [unusedFunction]
bool fuse_req_is_uring(fuse_req_t req)
^
lib/fuse_lowlevel.c:4177:0: style: The function 'fuse_session_receive_buf' is never used. [unusedFunction]
int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf)
^
lib/fuse_lowlevel.c:4397:0: style: The function 'fuse_session_custom_io_30' is never used. [unusedFunction]
int fuse_session_custom_io_30(struct fuse_session *se,
^
lib/fuse_lowlevel.c:4464:0: style: The function 'fuse_session_fd' is never used. [unusedFunction]
int fuse_session_fd(const struct fuse_session *se)
^
lib/fuse_lowlevel.c:4561:0: style: The function 'fuse_session_reset' is never used. [unusedFunction]
void fuse_session_reset(struct fuse_session *se)
^
lib/fuse_opt.c:201:0: style: The function 'fuse_opt_match' is never used. [unusedFunction]
int fuse_opt_match(const struct fuse_opt *opts, const char *opt)
^
lib/fuse_signals.c:175:0: style: The function 'fuse_set_fail_signal_handlers' is never used. [unusedFunction]
int fuse_set_fail_signal_handlers(struct fuse_session *se)
^
lib/fuse_uring.c:209:0: style: The function 'fuse_req_get_payload' is never used. [unusedFunction]
int fuse_req_get_payload(fuse_req_t req, char **payload, size_t *payload_sz,
^
lib/helper.c:237:0: style: The function 'fuse_parse_cmdline_30' is never used. [unusedFunction]
int fuse_parse_cmdline_30(struct fuse_args *args,
^
lib/helper.c:422:0: style: The function 'fuse_apply_conn_info_opts' is never used. [unusedFunction]
void fuse_apply_conn_info_opts(const struct fuse_conn_info_opts *opts,
^
lib/helper.c:476:0: style: The function 'fuse_parse_conn_info_opts' is never used. [unusedFunction]
struct fuse_conn_info_opts* fuse_parse_conn_info_opts(struct fuse_args *args)
^
lib/cuse_lowlevel.c:147:0: style: The function 'cuse_lowlevel_new' should have static linkage since it is not used outside of its translation unit. [staticFunction]
struct fuse_session *cuse_lowlevel_new(struct fuse_args *args,
^
lib/cuse_lowlevel.c:273:0: style: The function 'cuse_lowlevel_setup' should have static linkage since it is not used outside of its translation unit. [staticFunction]
struct fuse_session *cuse_lowlevel_setup(int argc, char *argv[],
^
lib/cuse_lowlevel.c:344:0: style: The function 'cuse_lowlevel_teardown' should have static linkage since it is not used outside of its translation unit. [staticFunction]
void cuse_lowlevel_teardown(struct fuse_session *se)
^
lib/fuse.c:2253:0: style: The function 'fuse_fs_ioctl' should have static linkage since it is not used outside of its translation unit. [staticFunction]
int fuse_fs_ioctl(struct fuse_fs *fs, const char *path, unsigned int cmd,
^
lib/fuse.c:2267:0: style: The function 'fuse_fs_poll' should have static linkage since it is not used outside of its translation unit. [staticFunction]
int fuse_fs_poll(struct fuse_fs *fs, const char *path,
^
lib/fuse.c:2290:0: style: The function 'fuse_fs_fallocate' should have static linkage since it is not used outside of its translation unit. [staticFunction]
int fuse_fs_fallocate(struct fuse_fs *fs, const char *path, int mode,
^
lib/fuse.c:2306:0: style: The function 'fuse_fs_copy_file_range' should have static linkage since it is not used outside of its translation unit. [staticFunction]
ssize_t fuse_fs_copy_file_range(struct fuse_fs *fs, const char *path_in,
^
lib/fuse.c:2377:0: style: The function 'fuse_fs_syncfs' should have static linkage since it is not used outside of its translation unit. [staticFunction]
int fuse_fs_syncfs(struct fuse_fs *fs, const char *path)
^
lib/fuse.c:4526:0: style: The function 'fuse_clean_cache' should have static linkage since it is not used outside of its translation unit. [staticFunction]
int fuse_clean_cache(struct fuse *f)
^
lib/fuse.c:4685:0: style: The function 'fuse_loop_mt_312' should have static linkage since it is not used outside of its translation unit. [staticFunction]
int fuse_loop_mt_312(struct fuse *f, struct fuse_loop_config *config)
^
lib/fuse.c:5001:0: style: The function 'fuse_start_cleanup_thread' should have static linkage since it is not used outside of its translation unit. [staticFunction]
int fuse_start_cleanup_thread(struct fuse *f)
^
lib/fuse.c:5009:0: style: The function 'fuse_stop_cleanup_thread' should have static linkage since it is not used outside of its translation unit. [staticFunction]
void fuse_stop_cleanup_thread(struct fuse *f)
^
lib/fuse.c:5196:0: style: The function 'fuse_new_31' should have static linkage since it is not used outside of its translation unit. [staticFunction]
struct fuse *fuse_new_31(struct fuse_args *args,
^
lib/fuse_loop_mt.c:497:0: style: The function 'fuse_loop_cfg_verify' should have static linkage since it is not used outside of its translation unit. [staticFunction]
int fuse_loop_cfg_verify(const struct fuse_loop_config *config)
^
lib/fuse_opt.c:95:0: style: The function 'fuse_opt_insert_arg' should have static linkage since it is not used outside of its translation unit. [staticFunction]
int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg)
^
lib/helper.c:208:0: style: The function 'fuse_parse_cmdline_312' should have static linkage since it is not used outside of its translation unit. [staticFunction]
int fuse_parse_cmdline_312(struct fuse_args *args,
^
nofile:0:0: information: Active checkers: 109/856 (use --checkers-report=<filename> to see details) [checkersReport]
^ permalink raw reply
* Re: [GIT PULL v5.1] libfuse: run fuse servers as a contained service
From: Bernd Schubert @ 2026-05-02 16:58 UTC (permalink / raw)
To: Darrick J. Wong
Cc: fuse-devel, joannelkoong, linux-ext4, linux-fsdevel, miklos, neal
In-Reply-To: <20260502163027.GO7765@frogsfrogsfrogs>
On 5/2/26 18:30, Darrick J. Wong wrote:
> On Sat, May 02, 2026 at 05:59:06PM +0200, Bernd Schubert wrote:
>>
>>
>> On 5/1/26 00:49, Darrick J. Wong wrote:
>>> On Thu, Apr 30, 2026 at 11:34:06PM +0200, Bernd Schubert wrote:
>>>> Hi Darrick,
>>>>
>>>> On 4/30/26 23:18, Darrick J. Wong wrote:
>>>>> Hi Bernd,
>>>>>
>>>>> Please pull this branch with changes for libfuse.
>>>>>
>>>>> As usual, I did a test-merge with the main upstream branch as of a few
>>>>> minutes ago, and didn't see any conflicts. Please let me know if you
>>>>> encounter any problems.
>>>>
>>>> pushed to my github branch. BSD build fails with
>>>>
>>>> 2026-04-30T21:25:16.3874802Z FAILED: [code=1] lib/libfuse3.so.3.19.0
>>>> 2026-04-30T21:25:16.3906762Z cc -o lib/libfuse3.so.3.19.0 lib/libfuse3.so.3.19.0.p/fuse.c.o lib/libfuse3.so.3.19.0.p/fuse_loop.c.o lib/libfuse3.so.3.19.0.p/fuse_loop_mt.c.o lib/libfuse3.so.3.19.0.p/fuse_lowlevel.c.o lib/libfuse3.so.3.19.0.p/fuse_opt.c.o lib/libfuse3.so.3.19.0.p/fuse_signals.c.o lib/libfuse3.so.3.19.0.p/buffer.c.o lib/libfuse3.so.3.19.0.p/cuse_lowlevel.c.o lib/libfuse3.so.3.19.0.p/helper.c.o lib/libfuse3.so.3.19.0.p/modules_subdir.c.o lib/libfuse3.so.3.19.0.p/mount_util.c.o lib/libfuse3.so.3.19.0.p/fuse_log.c.o lib/libfuse3.so.3.19.0.p/compat.c.o lib/libfuse3.so.3.19.0.p/util.c.o lib/libfuse3.so.3.19.0.p/mount_bsd.c.o lib/libfuse3.so.3.19.0.p/fuse_service_stub.c.o lib/libfuse3.so.3.19.0.p/modules_iconv.c.o -Wl,--as-needed -Wl,--no-undefined -shared -fPIC -Wl,-soname,libfuse3.so.4 -Wl,--version-script,/home/runner/work/libfuse/libfuse/lib/fuse_versionscript -pthread -Wl,--start-group -ldl -lrt -Wl,--end-group
>>>> 2026-04-30T21:25:16.3939590Z ld: error: version script assignment of 'FUSE_3.19' to symbol 'fuse_service_can_allow_other' failed: symbol not defined
>>>
>>> Aha, that function got left out of the stub. :(
>>>
>>> Annoyingly, on Linux the build succeeds despite the missing symbol
>>> when I tweak meson so that it doesn't build the service stuff. I would
>>> have thought that --no-undefined would have done that, but alas.
>>>
>>> Sorry about that too. The following patch fixes it.
>>>
>>> diff --git i/lib/fuse_service_stub.c w/lib/fuse_service_stub.c
>>> index d34df3891a6e31..231b98423df628 100644
>>> --- i/lib/fuse_service_stub.c
>>> +++ w/lib/fuse_service_stub.c
>>> @@ -49,12 +49,17 @@ int fuse_service_send_goodbye(struct fuse_service *sf, int error)
>>> int fuse_service_accept(struct fuse_service **sfp)
>>> {
>>> *sfp = NULL;
>>> return 0;
>>> }
>>>
>>> +bool fuse_service_can_allow_other(struct fuse_service *sf)
>>> +{
>>> + return false;
>>> +}
>>> +
>>> int fuse_service_append_args(struct fuse_service *sf,
>>> struct fuse_args *existing_args)
>>> {
>>> return -EOPNOTSUPP;
>>> }
>>>
>>>
>>>> 2026-04-30T21:25:16.3951874Z cc: error: linker command failed with exit code 1 (use -v to see invocation)
>>>> 2026-04-30T21:25:16.4291582Z [44/82] cc -Itest/test_teardown_watchdog.p -Itest -I../test -Iinclude -I../include -Ilib -I../lib -I. -I.. -fdiagnostics-color=always -
>>>>
>>>>
>>>> checkpatch, CodeChecker-cppcheck, CodeChecker-gcc also all fail. This CodeQL
>>>
>>> Not sure why checkpatch fails, this is what I got (Debian 13):
>>>
>>> $ git diff origin/master.. | ./checkpatch.pl --max-line-length=100 --no-tree --ignore MAINTAINERS,SPDX_LICENSE_TAG,COMMIT_MESSAGE,FILE_PATH_CHANGES,EMAIL_SUBJECT,AVOID_EXTERNS,GIT_COMMIT_ID,ENOSYS_SYSCALL,ENOSYS,FROM_SIGN_OFF_MISMATCH,QUOTED_COMMIT_ID,,PREFER_ATTRIBUTE_ALWAYS_UNUSED,PREFER_DEFINED_ATTRIBUTE_MACRO,STRCPY,STRNCPY -
>>> No typos will be found - file '/storage/home/djwong/cdev/work/libfuse/spelling.txt': No such file or directory
>>> No structs that should be const will be found - file '/storage/home/djwong/cdev/work/libfuse/const_structs.checkpatch': No such file or directory
>>> total: 0 errors, 0 warnings, 3908 lines checked
>>>
>>> Your patch has no obvious style problems and is ready for submission.
>>>
>>> NOTE: Ignored message types: AVOID_EXTERNS COMMIT_MESSAGE EMAIL_SUBJECT ENOSYS ENOSYS_SYSCALL FILE_PATH_CHANGES FROM_SIGN_OFF_MISMATCH GIT_COMMIT_ID MAINTAINERS PREFER_ATTRIBUTE_ALWAYS_UNUSED PREFER_DEFINED_ATTRIBUTE_MACRO QUOTED_COMMIT_ID SPDX_LICENSE_TAG STRCPY STRNCPY
>>>
>>> cppcheck had a few things to say, but none of it was about the changed
>>> lines.
>>>
>>>> report is funny
>>>>
>>>>> int mount_service_main(int argc, char *argv[])
>>>>> Warning
>>>>> Poorly documented large function
>>>>> Poorly documented function: fewer than 2% comments for a function of 113 lines.
>>>>> CodeQL
>>>
>>> Hrmm. I guess I'll have to figure out how to get those things running.
>>> That said, the stuff in mount_service.c is internal to libfuse (i.e.
>>> it's not public library API) so I didn't comment them as intensely.
>>> Would you like more?
>>>
>>>> I think I'm going to merge my sync fuse init series tomorrow.
>>>
>>> Yay!
>>>
>>>> Are you ok if skip posting another version of the series?
>>>
>>> ^ is there an "I" here? e.g. "...if I skip posting..."?
>>>
>>> /My/ normal practice (from xfs) was to repost the series as it was
>>> merged, followed by an announcement. That way the mailing list is a
>>> complete record of what was merged. However, very very few people
>>> actually do that, even in the kernel.
>>>
>>> I'm ok with you not posting another version of the series.
>>>
>>>> Or do you prefer to review the recent changes and last piece I'm going
>>>> to do tomorrow?
>>>
>>> Nah, just merge it. I'll look over the changes once it's in the branch
>>> and if there's anything weird, you or I or anyone else can send patches.
>>> As long as you're not planning to tag it as a release, nothing's set in
>>> stone.
>>>
>>>> Basically my goal is to rebase your series against it immediately and
>>>> to merge your series during the next few days.
>>>
>>> <nod> Let me know what you want changed, I'll be around since I am not
>>> travelling anywhere for a couple of weeks. :)
>>>
>>> I can reflow changes into the patchset, or if you'd prefer, I can add
>>> them as new patches that would go on the end of the series.
>>>
>>
>> Hi Darrick,
>>
>> eventually merged my patches and made a quick rebase and conflict
>> resolving with your branch. Github still reports plenty of issues ;)
>>
>> Could you fix all of the checkpatch isssues? This one shows plenty
>> "util: hoist the fuse.conf parsing and setuid mode enforcement code"
>>
>> bschubert2@imesrv6 libfuse.git>stg series
>> + mount_service-add-systemd
>> + mount_service-create-high
>> + mount_service-use-the-new
>> + mount_service-update-mtab
>>> util-hoist-the-fuse.conf
>> - util-fix-checkpatch-complaints
>> - mount_service-enable
>> - mount.fuse3-integrate-systemd
>> - mount_service-allow
>> - example-service_ll-create-a
>> - example-service-create-a
>> - nullfs-support-fuse-systemd
>> - meson-show-feature-summary
>> bschubert2@imesrv6 libfuse.git>.github/workflows/run-checkpatch.sh
>> No typos will be found - file '/home/bschubert2/src/libfuse/libfuse.git/spelling.txt': No such file or directory
>> No structs that should be const will be found - file '/home/bschubert2/src/libfuse/libfuse.git/const_structs.checkpatch': No such file or directory
>> ERROR:GLOBAL_INITIALISERS: do not initialise globals to 0
>> #61: FILE: util/fuser_conf.c:33:
>> +int user_allow_other = 0;
>>
>> WARNING:LINE_SPACING: Missing a blank line after declarations
>> #74: FILE: util/fuser_conf.c:46:
>> + char *dest = buf;
>> + while (1) {
>
> Oh! The checkpatch fixes are all in the next commit
> "util-fix-checkpatch-complaints". I kept the checkpatch fixes as a
> separate commit so that the hoist change can be inspected more easily.
That is not going to work, the script it set up to check every commit in
the PR branch.
>
> (XFS practice is to have separate patches for moving the code and
> cleaning it up)
>
>> ...
>>
>> I don't have a strong opinion about these two above - feel
>> free to disable them. However, in order to merge it, it would
>> be good to run without any checkpatch report.
>> And everything that cppcheck and gcc-checker reports, definitely
>> need to be fixed.
>
> Hrm, should I try to fix the things that cppcheck complains about in
> HEAD? There's an awful lot of them...
>
> (oh, I see you pushed to master, I'll go rebase now)
I had already rebased:
https://github.com/bsbernd/libfuse/tree/fuse-service-container
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox