| 0/0 |
2026/08/27 00:50 |
flow |
assessment-security |
31m
Results: map[DenialOfService:true Explanation:**Analysis of the Bug Report**
**1. Bug Mechanism & Impact:**
The bug report indicates a `WARNING` at `fs/btrfs/inode.c` inside
`btrfs_add_link()`. The warning is triggered by the `btrfs_abort_transaction()`
macro.
In Btrfs, `btrfs_abort_transaction()` is called when an unexpected,
unrecoverable error occurs during an active transaction (e.g., failing to insert
a directory item or a delayed directory index due to `-ENOSPC` or other errors
when space was supposedly already reserved).
When `btrfs_abort_transaction()` is invoked, it safely halts the current
transaction, prints a stack trace (via
`WARN_ON(btrfs_abort_should_print_stack(__error))`), and flips the filesystem
into a read-only state to prevent any potential metadata corruption.
Because the filesystem gracefully handles the error by aborting the transaction
and transitioning to read-only mode, there is no memory corruption,
out-of-bounds access, or use-after-free involved. Therefore, this is not a
privilege escalation or information leak vulnerability. However, because it
forces the filesystem into a read-only state, it results in a local **Denial of
Service (DoS)**.
**2. Reachability:**
The stack trace shows the bug is triggered via the standard `openat()` system
call path (`do_sys_openat2` -> `do_file_open` -> `path_openat` -> `lookup_open`
-> `btrfs_create_common` -> `btrfs_create_new_inode` -> `btrfs_add_link`).
Creating a new file or directory is a fundamental filesystem operation that does
not require any special capabilities. Thus, any unprivileged local user (or a
process inside a user namespace) with write access to a Btrfs mount can
potentially trigger this bug if they can manipulate the filesystem into the
edge-case state that causes the transaction to fail (e.g., specific ENOSPC
conditions, hash collisions, or quota limits).
**3. Trigger Contexts:**
* **Local Unprivileged Users / User Namespaces:** Yes, it is accessible through
standard file creation syscalls.
* **VM Guests:** Yes, a user inside a VM guest backed by a Btrfs filesystem can
trigger this.
* **Malicious Filesystem:** Yes, a specially crafted Btrfs image could be
designed to reliably trigger this transaction abort upon the first file creation
attempt after being mounted.
* **Remote / Network / Peripheral:** No, this is strictly tied to local
filesystem operations and does not involve network packets or peripheral device
parsing. Exploitable:false FilesystemTrigger:true NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:true UserNamespace:true VMGuestTrigger:true VMHostTrigger:false]
|
| 1/1 |
2026/08/27 00:50 |
action |
syz-repro-to-c-repro |
0m
Results:
|
| 2/1 |
2026/08/27 00:50 |
action |
kernel-checkouter |
0m
Results: map[KernelSrc:/app/workdir/cache/src/d3e5e845ea1e86991b96377ebe5c58374353a72c]
|
| 3/1 |
2026/08/27 00:50 |
action |
kernel-builder |
13m
Results: map[KernelObj:/app/workdir/cache/build/9b090ba62275329d4cf23f0f7587dae755e54fdf]
|
| 4/1 |
2026/08/27 01:04 |
action |
codesearch-prepare |
9m
Results: map[Index:codesearch-index]
|
| 5/1 |
2026/08/27 01:13 |
agent |
expert |
8m
Model:
Results: map[DenialOfService:true Exploitable:false FilesystemTrigger:true NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:true UserNamespace:true VMGuestTrigger:true VMHostTrigger:false]
Instruction: You are an experienced Linux kernel security engineer. Your task is to analyze given kernel bug report
and determine its security impact based on the following dimensions.
Use the provided tools to examine the source code, check for capability checks (e.g., capable(), ns_capable()),
and understand the nature of the bug. Analyze the given kernel build and configuration.
You can check the kernel config by grepping ".config" file; you can check kernel cmdline by grepping
".config" file for "CONFIG_CMDLINE=". Assume sysctl parameters have default values.
But analyze for the corresponding production build w/o debugging tools enabled (like KASAN, KMSAN, UBSAN).
Try different strategies when analyzing the bug:
- think of ways in which the vulnerable code is unreachable
- or the other way around: try to come up with different ideas of how an unprivileged user can reach the bug
If still unsure err on the side of the bug being non-exploitable/not-accessible.
In the final reply, provide a reasoning for your assessment.
Analysis dimensions:
* Exploitable:
Determine if the bug can result in memory corruption, elevated privileges, or an information leak.
Memory safety issues are almost always exploitable (KASAN or UBSAN reports for use-after-free, out-of-bounds;
refcounting issues, corrupted lists, etc). When kernel is crashing on a completely wild pointer access
(e.g. user-space address, or non-canonical address, but not on NULL or address corresponding to KASAN shadow
for NULL address), including both data accesses and control transfers, that also usually implies possibility
of exploitation. Such reports usually say "unable to handle kernel paging request".
Uses of uninitialized values detected by KMSAN may be exploitable b/c attacker frequently can affect uninit
values with spraying techniques. However, for these exploitability depends on how exactly the uninit value
is used in the code, and what it affects.
Information leaks are exploitable on their own and should be classified as such. A bug that copies kernel
memory contents to userspace (e.g. an out-of-bounds read whose result is returned to the caller, or
uninitialized stack/heap bytes written to a user buffer) is exploitable: it can reveal kernel pointer
values and defeat KASLR, expose sensitive data such as cryptographic keys or other processes' memory, and
serves as a necessary building block in most modern kernel privilege-escalation exploit chains. Do not classify
an information leak as non-exploitable solely because it does not directly cause a memory write or control-flow
hijack; the leak itself is the exploit primitive.
Think of what happens after the bug is triggered. Some bugs cause kernel panic and halt execution,
they are harder to exploit. For example, BUG reports halts the kernel. However, WARNING reports don't halt
execution in production builds. Debug bug detection tools (like KASAN, KMSAN, KCSAN, UBSAN) are also not enabled
in production builds, so attacker can freely exploit these bugs w/o being detected by these tools.
If you see an integer overflow, think how the overflowed value used later (if it's used as allocation size,
or an array index). If you see an out-of-bounds read, think if it's followed by an out-of-bounds write as well.
Some KCSAN data-races may be exploitable by skilled attackers as well. Think what data structures got corrupted
as the result of data races and how. However, note that kernel has lots of "benign" data races that don't lead
to any runtime misbehavior at all.
* Denial Of Service:
Determine if the bug can result in denial-of-service. Most bugs can, since they cause system crash,
hangs, deadlocks, or resource leaks. This is mostly applicable to WARNING bugs that won't cause system crash
in production. For these think what will be consequences of the violation of the kernel assumptions flagged
by the WARNING. In some cases the unexpected condition is also properly handled by the normal control flow
(e.g. with "if (WARN_ON(...))"), these won't cause denial-of-service. If the condition is not handled,
then it may or may not cause denial-of-service.
* Accessible From Unprivileged Processes:
Determine if the bug can be reached from a typical (non-root) user process that does NOT have any special capabilities
(like CAP_SYS_ADMIN, CAP_NET_ADMIN, CAP_NET_RAW, CAP_PERFMON) or access to device nodes restricted to root.
Assume that unprivileged_bpf_disabled=1, that is eBPF loading is not accessible. However, cBPF (classical BPF)
is still accessible to non-root processes.
Assume that user namespaces are not accessible, that is, the process cannot get the mentioned capabilities even
within a new user namespace (checked by ns_capable() function in the kernel sources).
* Accessible From User Namespaces:
Determine if the bug can be reached within a user-namespace where the process has all capabilities
(including CAP_SYS_ADMIN, CAP_NET_ADMIN, CAP_NET_RAW, CAP_PERFMON). Such capabilities are checked with ns_capable()
function in the kernel sources.
* VM Guest Trigger:
Determine if the bug can be triggered from the context of a typical KVM guest (e.g., set up by a QEMU VMM).
Consider accesses to standard Linux host paravirtualized features (virtio-blk, virtio-net, etc.),
and handling of VM exits in the KVM code.
* VM Host Trigger in The Confidential Computing Context:
Determine if the bug can be triggered in a confidential computing guest kernel from the context of a KVM host.
Consider access to standard Linux guest paravirtualized features (virtio-blk, virtio-net, etc.).
* Ethernet Network Trigger:
Determine if the bug can be triggered by processing ingress network Ethernet traffic, either directly (network stack)
or via drivers exposed to network data.
* Other Remote Trigger:
Determine if the bug can be triggered by processing remote traffic other than Ethernet (Wifi, Bluetooth, NFC, etc).
* Peripheral Trigger:
Determine if the bug can be triggered via an untrusted peripheral device that can be physically plugged
into a system, such as a USB device or a niche hardware driver handling external hardware inputs.
This is particularly important for mobile and desktop environments where users can plug in unknown devices.
* Malicious Filesystem Trigger:
Determine if the bug can be triggered by the kernel mounting and parsing a malicious filesystem image.
This is highly critical for Desktop and Mobile environments where external media or downloaded images
might be auto-mounted.
Don't make assumptions about the kernel source code (it may be different from what you assume it is).
Extensively use the provided code access tools (codesearch-*, git-*, grepper, etc)
to examine the actual source code, and confirm any assumptions.
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt:
The kernel bug report is:
------------[ cut here ]------------
btrfs_abort_should_print_stack(__error)
WARNING: fs/btrfs/inode.c:6931 at btrfs_add_link+0x932/0xa50 fs/btrfs/inode.c:6931, CPU#0: syz.0.0/5309
Modules linked in:
CPU: 0 UID: 0 PID: 5309 Comm: syz.0.0 Not tainted syzkaller #0 PREEMPT(full)
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
RIP: 0010:btrfs_add_link+0x932/0xa50 fs/btrfs/inode.c:6931
Code: e2 74 13 41 83 fe fb 74 14 41 83 fe f4 75 15 e8 84 ca ae fd eb 17 e8 7d ca ae fd eb 10 e8 76 ca ae fd eb 09 e8 6f ca ae fd 90 <0f> 0b 90 44 89 f1 f7 d9 e9 10 fe ff ff 89 df 48 c7 c6 c0 22 3d 8f
RSP: 0000:ffffc9000e3274e0 EFLAGS: 00010283
RAX: ffffffff84186a51 RBX: ffff888051bbc001 RCX: 0000000000100000
RDX: ffffc90020802000 RSI: 0000000000002779 RDI: 000000000000277a
RBP: ffffc9000e327610 R08: ffff888000332580 R09: 0000000000000003
R10: 00000000fffffffb R11: 0000000000000002 R12: 1ffff11003f8ba64
R13: 00000000ffffffe4 R14: 00000000ffffffe4 R15: ffff88801fc5d320
FS: 00007fe74012e6c0(0000) GS:ffff88808c30a000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00007fe73300f003 CR3: 0000000012711000 CR4: 0000000000352ef0
Call Trace:
<TASK>
btrfs_create_new_inode+0x14a1/0x1ee0 fs/btrfs/inode.c:6864
btrfs_create_common+0x189/0x310 fs/btrfs/inode.c:6990
lookup_open+0xc0d/0x1a40 fs/namei.c:4567
open_last_lookups fs/namei.c:4767 [inline]
path_openat+0x677/0x1d60 fs/namei.c:4997
do_file_open+0x23e/0x4a0 fs/namei.c:5029
do_sys_openat2+0x115/0x200 fs/open.c:1417
do_sys_open fs/open.c:1423 [inline]
__do_sys_openat fs/open.c:1439 [inline]
__se_sys_openat fs/open.c:1434 [inline]
__x64_sys_openat+0x138/0x170 fs/open.c:1434
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7fe73f19e0d9
Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 e8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007fe74012dfe8 EFLAGS: 00000246 ORIG_RAX: 0000000000000101
RAX: ffffffffffffffda RBX: 00007fe73f426090 RCX: 00007fe73f19e0d9
RDX: 000000000000275a RSI: 0000200000000040 RDI: ffffffffffffff9c
RBP: 00007fe73f235024 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
R13: 00007fe73f426128 R14: 00007fe73f426090 R15: 00007ffc333b4428
</TASK>
Reply: **Analysis of the Bug Report**
**1. Bug Mechanism & Impact:**
The bug report indicates a `WARNING` at `fs/btrfs/inode.c` inside `btrfs_add_link()`. The warning is triggered by the `btrfs_abort_transaction()` macro.
In Btrfs, `btrfs_abort_transaction()` is called when an unexpected, unrecoverable error occurs during an active transaction (e.g., failing to insert a directory item or a delayed directory index due to `-ENOSPC` or other errors when space was supposedly already reserved).
When `btrfs_abort_transaction()` is invoked, it safely halts the current transaction, prints a stack trace (via `WARN_ON(btrfs_abort_should_print_stack(__error))`), and flips the filesystem into a read-only state to prevent any potential metadata corruption.
Because the filesystem gracefully handles the error by aborting the transaction and transitioning to read-only mode, there is no memory corruption, out-of-bounds access, or use-after-free involved. Therefore, this is not a privilege escalation or information leak vulnerability. However, because it forces the filesystem into a read-only state, it results in a local **Denial of Service (DoS)**.
**2. Reachability:**
The stack trace shows the bug is triggered via the standard `openat()` system call path (`do_sys_openat2` -> `do_file_open` -> `path_openat` -> `lookup_open` -> `btrfs_create_common` -> `btrfs_create_new_inode` -> `btrfs_add_link`).
Creating a new file or directory is a fundamental filesystem operation that does not require any special capabilities. Thus, any unprivileged local user (or a process inside a user namespace) with write access to a Btrfs mount can potentially trigger this bug if they can manipulate the filesystem into the edge-case state that causes the transaction to fail (e.g., specific ENOSPC conditions, hash collisions, or quota limits).
**3. Trigger Contexts:**
* **Local Unprivileged Users / User Namespaces:** Yes, it is accessible through standard file creation syscalls.
* **VM Guests:** Yes, a user inside a VM guest backed by a Btrfs filesystem can trigger this.
* **Malicious Filesystem:** Yes, a specially crafted Btrfs image could be designed to reliably trigger this transaction abort upon the first file creation attempt after being mounted.
* **Remote / Network / Peripheral:** No, this is strictly tied to local filesystem operations and does not involve network packets or peripheral device parsing.
|
| 6/2 |
2026/08/27 01:13 |
llm |
expert |
0m
Model:
Tokens:
input: 7625
output: 38
thoughts: 83
Thoughts: **Investigating a Btrfs Warning**
Okay, here's what I'm looking at: there's a WARNING flagged in `btrfs_add_link`. The kernel is pointing me right to `fs/btrfs/inode.c:6931`. Based on the call trace, I see that `btrfs_add_link` is called from `btrfs_create_new_inode`. Alright, time to dive into `fs/btrfs/inode.c` and take a look around that specific line. I need to understand what's triggering this warning within the link addition process. Let's see what's going on.
|
| 7/2 |
2026/08/27 01:13 |
tool |
read-file |
0m
Args: map[File:fs/btrfs/inode.c FirstLine:6900 LineCount:50]
Results: map[Contents:6900: struct btrfs_key key;
6901: struct btrfs_root *root = parent_inode->root;
6902: u64 ino = btrfs_ino(inode);
6903: u64 parent_ino = btrfs_ino(parent_inode);
6904:
6905: if (unlikely(ino == BTRFS_FIRST_FREE_OBJECTID)) {
6906: memcpy(&key, &inode->root->root_key, sizeof(key));
6907: } else {
6908: key.objectid = ino;
6909: key.type = BTRFS_INODE_ITEM_KEY;
6910: key.offset = 0;
6911: }
6912:
6913: if (unlikely(ino == BTRFS_FIRST_FREE_OBJECTID)) {
6914: ret = btrfs_add_root_ref(trans, key.objectid,
6915: btrfs_root_id(root), parent_ino,
6916: index, name);
6917: } else if (add_backref) {
6918: ret = btrfs_insert_inode_ref(trans, root, name,
6919: ino, parent_ino, index);
6920: }
6921:
6922: /* Nothing to clean up yet */
6923: if (ret)
6924: return ret;
6925:
6926: ret = btrfs_insert_dir_item(trans, name, parent_inode, &key,
6927: btrfs_inode_type(inode), index);
6928: if (ret == -EEXIST || ret == -EOVERFLOW)
6929: goto fail_dir_item;
6930: else if (unlikely(ret)) {
6931: btrfs_abort_transaction(trans, ret);
6932: return ret;
6933: }
6934:
6935: btrfs_i_size_write(parent_inode, parent_inode->vfs_inode.i_size +
6936: name->len * 2);
6937: inode_inc_iversion(&parent_inode->vfs_inode);
6938: update_time_after_link_or_unlink(parent_inode);
6939:
6940: ret = btrfs_update_inode(trans, parent_inode);
6941: if (ret)
6942: btrfs_abort_transaction(trans, ret);
6943: return ret;
6944:
6945: fail_dir_item:
6946: if (unlikely(ino == BTRFS_FIRST_FREE_OBJECTID)) {
6947: u64 local_index;
6948: int ret2;
6949:
]
|
| 8/2 |
2026/08/27 01:13 |
llm |
expert |
0m
Model:
Tokens:
input: 8623
output: 38
thoughts: 171
Thoughts: **Analyzing a Btrfs Bug**
Okay, so I've got a warning popping up in `btrfs_add_link`, specifically at `fs/btrfs/inode.c:6931`. That line, `btrfs_abort_transaction(trans, ret);`, which, I know, expands to `btrfs_abort_should_print_stack(__error)` and then a `WARNING`. The call trace is showing it's being triggered in a pretty standard way: `btrfs_create_new_inode` calls `btrfs_add_link` (or at least, *looks* inlined). Then, `btrfs_add_link` calls `btrfs_abort_transaction`. Hang on... `btrfs_add_link` is *definitely* called from `btrfs_create_new_inode`, isn't it? Okay, time to pull up `fs/btrfs/inode.c` and have a good look at `btrfs_add_link`. I need to figure out *why* that transaction is being aborted and, more importantly, why it's generating a warning at this point in the process.
|
| 9/2 |
2026/08/27 01:13 |
tool |
read-file |
0m
Args: map[File:fs/btrfs/inode.c FirstLine:6860 LineCount:50]
Results: map[Contents:6860: btrfs_abort_transaction(trans, ret);
6861: goto discard;
6862: }
6863: } else {
6864: ret = btrfs_add_link(trans, BTRFS_I(dir), BTRFS_I(inode), name,
6865: false, BTRFS_I(inode)->dir_index);
6866: if (unlikely(ret)) {
6867: btrfs_abort_transaction(trans, ret);
6868: goto discard;
6869: }
6870: }
6871:
6872: return 0;
6873:
6874: discard:
6875: /*
6876: * discard_new_inode() calls iput(), but the caller owns the reference
6877: * to the inode.
6878: */
6879: ihold(inode);
6880: discard_new_inode(inode);
6881: out:
6882: if (xa_reserved)
6883: xa_release(&root->inodes, objectid);
6884:
6885: btrfs_free_path(path);
6886: return ret;
6887: }
6888:
6889: /*
6890: * utility function to add 'inode' into 'parent_inode' with
6891: * a give name and a given sequence number.
6892: * if 'add_backref' is true, also insert a backref from the
6893: * inode to the parent directory.
6894: */
6895: int btrfs_add_link(struct btrfs_trans_handle *trans,
6896: struct btrfs_inode *parent_inode, struct btrfs_inode *inode,
6897: const struct fscrypt_str *name, bool add_backref, u64 index)
6898: {
6899: int ret = 0;
6900: struct btrfs_key key;
6901: struct btrfs_root *root = parent_inode->root;
6902: u64 ino = btrfs_ino(inode);
6903: u64 parent_ino = btrfs_ino(parent_inode);
6904:
6905: if (unlikely(ino == BTRFS_FIRST_FREE_OBJECTID)) {
6906: memcpy(&key, &inode->root->root_key, sizeof(key));
6907: } else {
6908: key.objectid = ino;
6909: key.type = BTRFS_INODE_ITEM_KEY;
]
|
| 10/2 |
2026/08/27 01:13 |
llm |
expert |
0m
Model:
Tokens:
input: 9941
output: 55
thoughts: 116
Thoughts: **Unexpected Error in Directory Insertion**
Okay, so I'm looking at this `btrfs_add_link` function and it's calling `btrfs_insert_dir_item`. Now, that's interesting because `btrfs_insert_dir_item` is returning an error that isn't `-EEXIST` or `-EOVERFLOW`. That's not good. The code then calls `btrfs_abort_transaction` with the error code, and as a macro, that's going to trigger a warning. This is a filesystem abort, and it's happening specifically because `btrfs_insert_dir_item` encountered something it didn't expect. I need to dig into the possible return values of `btrfs_insert_dir_item` to understand exactly what's going on here. We can't have unexpected aborts. Let's see what we're dealing with.
|
| 11/2 |
2026/08/27 01:13 |
tool |
codesearch-find-references |
0m
Args: map[ContextFile:fs/btrfs/inode.c IncludeSnippetLines:0 Name:btrfs_insert_dir_item SourceTreePrefix:]
Results: map[References:[map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:btrfs_add_link SourceFile:fs/btrfs/inode.c SourceLine:6926] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:create_pending_snapshot SourceFile:fs/btrfs/transaction.c SourceLine:1881]] TruncatedOutput:false]
|
| 12/2 |
2026/08/27 01:13 |
llm |
expert |
0m
Model:
Tokens:
input: 10439
output: 42
thoughts: 18
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Diving into `btrfs_insert_dir_item`**
Okay, time to dig into the `btrfs_insert_dir_item` function. I need to refresh my memory on the specifics of directory item insertion in Btrfs. My immediate focus is on the implementation details and how it handles different scenarios. I'm thinking about the various metadata structures involved: the extent tree, the inode item, the directory index, and how they interact during an insertion operation. I need to consider potential issues related to concurrency and data consistency, particularly around transaction management and journal operations. I'm also anticipating that the code will have optimizations to minimize fragmentation and maximize performance. Let's see if those are implemented and how it handles errors during allocation and block writes. There could also be interesting aspects to think about in regards to checksumming, deduplication, and compression. Let's see what we've got.
|
| 13/2 |
2026/08/27 01:13 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/btrfs/inode.c Name:btrfs_insert_dir_item]
Results: map[SourceCode: 100: /*
101: * insert a directory item in the tree, doing all the magic for
102: * both indexes. 'dir' indicates which objectid to insert it into,
103: * 'location' is the key to stuff into the directory item, 'type' is the
104: * type of the inode we're pointing to, and 'index' is the sequence number
105: * to use for the second index (if one is created).
106: * Will return 0 or -ENOMEM
107: */
108: int btrfs_insert_dir_item(struct btrfs_trans_handle *trans,
109: const struct fscrypt_str *name, struct btrfs_inode *dir,
110: const struct btrfs_key *location, u8 type, u64 index)
111: {
112: int ret = 0;
113: int ret2 = 0;
114: struct btrfs_root *root = dir->root;
115: BTRFS_PATH_AUTO_FREE(path);
116: struct btrfs_dir_item *dir_item;
117: struct extent_buffer *leaf;
118: unsigned long name_ptr;
119: struct btrfs_key key;
120: struct btrfs_disk_key disk_key;
121: u32 data_size;
122:
123: key.objectid = btrfs_ino(dir);
124: key.type = BTRFS_DIR_ITEM_KEY;
125: key.offset = btrfs_name_hash(name->name, name->len);
126:
127: path = btrfs_alloc_path();
128: if (!path)
129: return -ENOMEM;
130:
131: btrfs_cpu_key_to_disk(&disk_key, location);
132:
133: data_size = sizeof(*dir_item) + name->len;
134: dir_item = insert_with_overflow(trans, root, path, &key, data_size,
135: name->name, name->len);
136: if (IS_ERR(dir_item)) {
137: ret = PTR_ERR(dir_item);
138: if (ret == -EEXIST)
139: goto second_insert;
140: goto out_free;
141: }
142:
143: if (IS_ENCRYPTED(&dir->vfs_inode))
144: type |= BTRFS_FT_ENCRYPTED;
145:
146: leaf = path->nodes[0];
147: btrfs_set_dir_item_key(leaf, dir_item, &disk_key);
148: btrfs_set_dir_flags(leaf, dir_item, type);
149: btrfs_set_dir_data_len(leaf, dir_item, 0);
150: btrfs_set_dir_name_len(leaf, dir_item, name->len);
151: btrfs_set_dir_transid(leaf, dir_item, trans->transid);
152: name_ptr = (unsigned long)(dir_item + 1);
153:
154: write_extent_buffer(leaf, name->name, name_ptr, name->len);
155:
156: second_insert:
157: /* FIXME, use some real flag for selecting the extra index */
158: if (root == root->fs_info->tree_root) {
159: ret = 0;
160: goto out_free;
161: }
162: btrfs_release_path(path);
163:
164: ret2 = btrfs_insert_delayed_dir_index(trans, name->name, name->len, dir,
165: &disk_key, type, index);
166: out_free:
167: if (ret)
168: return ret;
169: if (ret2)
170: return ret2;
171: return 0;
172: }
SourceFile:fs/btrfs/dir-item.c]
|
| 14/2 |
2026/08/27 01:13 |
llm |
expert |
0m
Model:
Tokens:
input: 11963
output: 40
thoughts: 196
Thoughts: **Analyzing a Btrfs Bug Warning**
Okay, so I've got a warning flag in `btrfs_abort_transaction`. It's triggered when `btrfs_insert_dir_item` throws an error, but not the expected `-EEXIST` or `-EOVERFLOW`. My immediate thought is, "Why are we aborting the transaction for something other than those?" `btrfs_insert_dir_item` is a pretty core function, it can error out for a few reasons. I see that it calls `btrfs_alloc_path`, and if that fails with `-ENOMEM`, then `btrfs_insert_dir_item` errors with `-ENOMEM`.
Wait a minute, `-ENOMEM`? That's a classic out-of-memory error. Now, it's not unheard of, but is it *unexpected* in this context? The code calls `btrfs_abort_transaction` on any error other than `-EEXIST` or `-EOVERFLOW`, but perhaps `-ENOMEM` shouldn't be triggering a complete abort. Itβs certainly possible to run out of memory, but maybe it should be handled differently. Let me check the macro definition of `btrfs_abort_transaction` to confirm my suspicion. I need to get a better handle on what causes the warning and whether it's the correct response in this particular `-ENOMEM` scenario.
|
| 15/2 |
2026/08/27 01:13 |
tool |
codesearch-definition-source |
0m
Error:
Args: map[ContextFile:fs/btrfs/inode.c Name:btrfs_abort_transaction]
Results: map[SourceCode: SourceFile:]
|
| 16/2 |
2026/08/27 01:14 |
llm |
expert |
0m
Model:
Tokens:
input: 11944
output: 35
thoughts: 0
|
| 17/2 |
2026/08/27 01:14 |
tool |
grepper |
0m
Args: map[Expression:#define btrfs_abort_transaction PathPrefix:fs/btrfs/]
Results: map[Output:fs/btrfs/transaction.h=279=do { \
--
fs/btrfs/transaction.h-292- */
fs/btrfs/transaction.h:293:#define btrfs_abort_transaction(trans, error) \
fs/btrfs/transaction.h-294-do { \
]
|
| 18/2 |
2026/08/27 01:14 |
llm |
expert |
0m
Model:
Tokens:
input: 12661
output: 37
thoughts: 22
Thoughts: Here's my summary, as if I were thinking these thoughts:
**Diving into Btrfs Transaction Handling**
Okay, time to dig into the heart of Btrfs's transaction management. I want to understand how it's all wired up. Let's start with `fs/btrfs/transaction.h` and see what's happening around line 293. I'm expecting to find some critical definitions or structures here related to how transactions are initiated, committed, and rolled back. This should illuminate the fundamental building blocks for atomic operations within the filesystem, and from there I can better understand how it handles data consistency. I'm hoping to get a clearer picture of the mechanics behind its CoW (Copy-on-Write) strategy at this level. Let's see what we find!
|
| 19/2 |
2026/08/27 01:14 |
tool |
read-file |
0m
Args: map[File:fs/btrfs/transaction.h FirstLine:280 LineCount:30]
Results: map[Contents: 280: (void)sizeof(char[-!(__builtin_constant_p(error) ? (error) < 0 : 1)]); \
281: if (IS_ENABLED(CONFIG_BTRFS_DEBUG)) { \
282: if (sizeof(char[(error) < 0]) != 1) \
283: DEBUG_WARN("error >= 0 passed to btrfs_abort_transaction()"); \
284: } \
285: } while(0)
286:
287: /*
288: * Call btrfs_abort_transaction() as early as possible when an error condition
289: * is detected, that way the exact stack trace is reported for some errors.
290: *
291: * Error number must be negative as it encodes wheather it's the first abort.
292: */
293: #define btrfs_abort_transaction(trans, error) \
294: do { \
295: int __error = (error); \
296: \
297: VERIFY_NEGATIVE_ERROR(error); \
298: /* Report first abort since mount */ \
299: if (!test_and_set_bit(BTRFS_FS_STATE_TRANS_ABORTED, \
300: &((trans)->fs_info->fs_state))) { \
301: WARN_ON(btrfs_abort_should_print_stack(__error)); \
302: __error = -__error; \
303: } \
304: __btrfs_abort_transaction((trans), __func__, \
305: __LINE__, __error); \
306: } while (0)
307:
308: int btrfs_end_transaction(struct btrfs_trans_handle *trans);
309: struct btrfs_trans_handle *btrfs_start_transaction(struct btrfs_root *root,
]
|
| 20/2 |
2026/08/27 01:14 |
llm |
expert |
0m
Model:
Tokens:
input: 12855
output: 44
thoughts: 0
|
| 21/2 |
2026/08/27 01:14 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/btrfs/transaction.h Name:btrfs_abort_should_print_stack]
Results: map[SourceCode: 248: /*
249: * We want the transaction abort to print stack trace only for errors where the
250: * cause could be a bug, eg. due to ENOSPC, and not for common errors that are
251: * caused by external factors.
252: */
253: static inline bool btrfs_abort_should_print_stack(int error)
254: {
255: switch (error) {
256: case -EIO:
257: case -EROFS:
258: case -ENOMEM:
259: return false;
260: }
261: return true;
262: }
SourceFile:fs/btrfs/transaction.h]
|
| 22/2 |
2026/08/27 01:14 |
llm |
expert |
0m
Model:
Tokens:
input: 13129
output: 38
thoughts: 0
|
| 23/2 |
2026/08/27 01:14 |
tool |
read-file |
0m
Args: map[File:fs/btrfs/inode.c FirstLine:6920 LineCount:20]
Results: map[Contents:6920: }
6921:
6922: /* Nothing to clean up yet */
6923: if (ret)
6924: return ret;
6925:
6926: ret = btrfs_insert_dir_item(trans, name, parent_inode, &key,
6927: btrfs_inode_type(inode), index);
6928: if (ret == -EEXIST || ret == -EOVERFLOW)
6929: goto fail_dir_item;
6930: else if (unlikely(ret)) {
6931: btrfs_abort_transaction(trans, ret);
6932: return ret;
6933: }
6934:
6935: btrfs_i_size_write(parent_inode, parent_inode->vfs_inode.i_size +
6936: name->len * 2);
6937: inode_inc_iversion(&parent_inode->vfs_inode);
6938: update_time_after_link_or_unlink(parent_inode);
6939:
]
|
| 24/2 |
2026/08/27 01:14 |
llm |
expert |
0m
Model:
Tokens:
input: 13485
output: 40
thoughts: 0
|
| 25/2 |
2026/08/27 01:14 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/btrfs/dir-item.c Name:insert_with_overflow]
Results: map[SourceCode: 14: /*
15: * insert a name into a directory, doing overflow properly if there is a hash
16: * collision. data_size indicates how big the item inserted should be. On
17: * success a struct btrfs_dir_item pointer is returned, otherwise it is
18: * an ERR_PTR.
19: *
20: * The name is not copied into the dir item, you have to do that yourself.
21: */
22: static struct btrfs_dir_item *insert_with_overflow(struct btrfs_trans_handle
23: *trans,
24: struct btrfs_root *root,
25: struct btrfs_path *path,
26: const struct btrfs_key *cpu_key,
27: u32 data_size,
28: const char *name,
29: int name_len)
30: {
31: int ret;
32: char *ptr;
33: struct extent_buffer *leaf;
34:
35: ret = btrfs_insert_empty_item(trans, root, path, cpu_key, data_size);
36: if (ret == -EEXIST) {
37: struct btrfs_dir_item *di;
38: di = btrfs_match_dir_item_name(path, name, name_len);
39: if (di)
40: return ERR_PTR(-EEXIST);
41: btrfs_extend_item(trans, path, data_size);
42: } else if (ret < 0)
43: return ERR_PTR(ret);
44: WARN_ON(ret > 0);
45: leaf = path->nodes[0];
46: ptr = btrfs_item_ptr(leaf, path->slots[0], char);
47: ASSERT(data_size <= btrfs_item_size(leaf, path->slots[0]));
48: ptr += btrfs_item_size(leaf, path->slots[0]) - data_size;
49: return (struct btrfs_dir_item *)ptr;
50: }
SourceFile:fs/btrfs/dir-item.c]
|
| 26/2 |
2026/08/27 01:14 |
llm |
expert |
0m
Model:
Tokens:
input: 14770
output: 42
thoughts: 170
Thoughts: **Analyzing the btrfs Directory Insertion Process**
Okay, so I'm tracing this `btrfs_insert_dir_item` function, and it looks like it flows into `insert_with_overflow`, which then calls `btrfs_insert_empty_item`. Seems pretty standard for this type of operation, create the base structure and handle potential overflows. Now, the interesting part is what happens if `btrfs_insert_empty_item` comes back with `-EEXIST`. Right, it calls `btrfs_match_dir_item_name`. If that match returns a non-null result, it's a conflict and we return `-EEXIST`. Makes sense, prevent a duplicate entry.
But if `btrfs_match_dir_item_name` *doesn't* find a match (returns null), it then calls `btrfs_extend_item`. My initial thought is... wait a minute, can `btrfs_extend_item` actually return an error here? We're trying to add the extra space to a possibly pre-existing item, so a failure could happen, right? This deserves further inspection. Hold on, let's just confirm the definition of `btrfs_extend_item`. I need to be sure. It's too important to just make assumptions about a fundamental function like that, it would have serious effects if this step didn't go as expected.
|
| 27/2 |
2026/08/27 01:14 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/btrfs/dir-item.c Name:btrfs_extend_item]
Results: map[SourceCode:4111: /*
4112: * make the item pointed to by the path bigger, data_size is the added size.
4113: */
4114: void btrfs_extend_item(struct btrfs_trans_handle *trans,
4115: const struct btrfs_path *path, u32 data_size)
4116: {
4117: int slot;
4118: struct extent_buffer *leaf;
4119: u32 nritems;
4120: unsigned int data_end;
4121: unsigned int old_data;
4122: unsigned int old_size;
4123: int i;
4124:
4125: leaf = path->nodes[0];
4126:
4127: nritems = btrfs_header_nritems(leaf);
4128: data_end = leaf_data_end(leaf);
4129:
4130: if (unlikely(btrfs_leaf_free_space(leaf) < data_size)) {
4131: btrfs_print_leaf(leaf);
4132: BUG();
4133: }
4134: slot = path->slots[0];
4135: old_data = btrfs_item_data_end(leaf, slot);
4136:
4137: BUG_ON(slot < 0);
4138: if (unlikely(slot >= nritems)) {
4139: btrfs_print_leaf(leaf);
4140: btrfs_crit(leaf->fs_info, "slot %d too large, nritems %d",
4141: slot, nritems);
4142: BUG();
4143: }
4144:
4145: /*
4146: * item0..itemN ... dataN.offset..dataN.size .. data0.size
4147: */
4148: /* first correct the data pointers */
4149: for (i = slot; i < nritems; i++) {
4150: u32 ioff;
4151:
4152: ioff = btrfs_item_offset(leaf, i);
4153: btrfs_set_item_offset(leaf, i, ioff - data_size);
4154: }
4155:
4156: /* shift the data */
4157: memmove_leaf_data(leaf, data_end - data_size, data_end,
4158: old_data - data_end);
4159:
4160: old_size = btrfs_item_size(leaf, slot);
4161: btrfs_set_item_size(leaf, slot, old_size + data_size);
4162: btrfs_mark_buffer_dirty(trans, leaf);
4163:
4164: if (unlikely(btrfs_leaf_free_space(leaf) < 0)) {
4165: btrfs_print_leaf(leaf);
4166: BUG();
4167: }
4168: }
SourceFile:fs/btrfs/ctree.c]
|
| 28/2 |
2026/08/27 01:14 |
llm |
expert |
0m
Model:
Tokens:
input: 15422
output: 44
thoughts: 0
|
| 29/2 |
2026/08/27 01:14 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/btrfs/dir-item.c Name:btrfs_insert_empty_item]
Results: map[SourceCode: 657: static inline int btrfs_insert_empty_item(struct btrfs_trans_handle *trans,
658: struct btrfs_root *root,
659: struct btrfs_path *path,
660: const struct btrfs_key *key,
661: u32 data_size)
662: {
663: struct btrfs_item_batch batch;
664:
665: batch.keys = key;
666: batch.data_sizes = &data_size;
667: batch.total_data_size = data_size;
668: batch.nr = 1;
669:
670: return btrfs_insert_empty_items(trans, root, path, &batch);
671: }
SourceFile:fs/btrfs/ctree.h]
|
| 30/2 |
2026/08/27 01:14 |
llm |
expert |
0m
Model:
Tokens:
input: 15734
output: 42
thoughts: 0
|
| 31/2 |
2026/08/27 01:14 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/btrfs/ctree.h Name:btrfs_insert_empty_items]
Results: map[SourceCode:4292: /*
4293: * Given a key and some data, insert items into the tree.
4294: * This does all the path init required, making room in the tree if needed.
4295: *
4296: * Returns: 0 on success
4297: * -EEXIST if the first key already exists
4298: * < 0 on other errors
4299: */
4300: int btrfs_insert_empty_items(struct btrfs_trans_handle *trans,
4301: struct btrfs_root *root,
4302: struct btrfs_path *path,
4303: const struct btrfs_item_batch *batch)
4304: {
4305: int ret = 0;
4306: int slot;
4307: u32 total_size;
4308:
4309: total_size = batch->total_data_size + (batch->nr * sizeof(struct btrfs_item));
4310: ret = btrfs_search_slot(trans, root, &batch->keys[0], path, total_size, 1);
4311: if (ret == 0)
4312: return -EEXIST;
4313: if (ret < 0)
4314: return ret;
4315:
4316: slot = path->slots[0];
4317: BUG_ON(slot < 0);
4318:
4319: setup_items_for_insert(trans, root, path, batch);
4320: return 0;
4321: }
SourceFile:fs/btrfs/ctree.c]
|
| 32/2 |
2026/08/27 01:14 |
llm |
expert |
0m
Model:
Tokens:
input: 16269
output: 40
thoughts: 0
|
| 33/2 |
2026/08/27 01:14 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/btrfs/ctree.c Name:setup_items_for_insert]
Results: map[SourceCode:4170: /*
4171: * Make space in the node before inserting one or more items.
4172: *
4173: * @trans: transaction handle
4174: * @root: root we are inserting items to
4175: * @path: points to the leaf/slot where we are going to insert new items
4176: * @batch: information about the batch of items to insert
4177: *
4178: * Main purpose is to save stack depth by doing the bulk of the work in a
4179: * function that doesn't call btrfs_search_slot
4180: */
4181: static void setup_items_for_insert(struct btrfs_trans_handle *trans,
4182: struct btrfs_root *root, struct btrfs_path *path,
4183: const struct btrfs_item_batch *batch)
4184: {
4185: struct btrfs_fs_info *fs_info = root->fs_info;
4186: int i;
4187: u32 nritems;
4188: unsigned int data_end;
4189: struct btrfs_disk_key disk_key;
4190: struct extent_buffer *leaf;
4191: int slot;
4192: u32 total_size;
4193:
4194: /*
4195: * Before anything else, update keys in the parent and other ancestors
4196: * if needed, then release the write locks on them, so that other tasks
4197: * can use them while we modify the leaf.
4198: */
4199: if (path->slots[0] == 0) {
4200: btrfs_cpu_key_to_disk(&disk_key, &batch->keys[0]);
4201: fixup_low_keys(trans, path, &disk_key, 1);
4202: }
4203: btrfs_unlock_up_safe(path, 1);
4204:
4205: leaf = path->nodes[0];
4206: slot = path->slots[0];
4207:
4208: nritems = btrfs_header_nritems(leaf);
4209: data_end = leaf_data_end(leaf);
4210: total_size = batch->total_data_size + (batch->nr * sizeof(struct btrfs_item));
4211:
4212: if (unlikely(btrfs_leaf_free_space(leaf) < total_size)) {
4213: btrfs_print_leaf(leaf);
4214: btrfs_crit(fs_info, "not enough freespace need %u have %d",
4215: total_size, btrfs_leaf_free_space(leaf));
4216: BUG();
4217: }
4218:
4219: if (slot != nritems) {
4220: unsigned int old_data = btrfs_item_data_end(leaf, slot);
4221:
4222: if (unlikely(old_data < data_end)) {
4223: btrfs_print_leaf(leaf);
4224: btrfs_crit(fs_info,
4225: "item at slot %d with data offset %u beyond data end of leaf %u",
4226: slot, old_data, data_end);
4227: BUG();
4228: }
4229: /*
4230: * item0..itemN ... dataN.offset..dataN.size .. data0.size
4231: */
4232: /* first correct the data pointers */
4233: for (i = slot; i < nritems; i++) {
4234: u32 ioff;
4235:
4236: ioff = btrfs_item_offset(leaf, i);
4237: btrfs_set_item_offset(leaf, i,
4238: ioff - batch->total_data_size);
4239: }
4240: /* shift the items */
4241: memmove_leaf_items(leaf, slot + batch->nr, slot, nritems - slot);
4242:
4243: /* shift the data */
4244: memmove_leaf_data(leaf, data_end - batch->total_data_size,
4245: data_end, old_data - data_end);
4246: data_end = old_data;
4247: }
4248:
4249: /* setup the item for the new data */
4250: for (i = 0; i < batch->nr; i++) {
4251: btrfs_cpu_key_to_disk(&disk_key, &batch->keys[i]);
4252: btrfs_set_item_key(leaf, &disk_key, slot + i);
4253: data_end -= batch->data_sizes[i];
4254: btrfs_set_item_offset(leaf, slot + i, data_end);
4255: btrfs_set_item_size(leaf, slot + i, batch->data_sizes[i]);
4256: }
4257:
4258: btrfs_set_header_nritems(leaf, nritems + batch->nr);
4259: btrfs_mark_buffer_dirty(trans, leaf);
4260:
4261: if (unlikely(btrfs_leaf_free_space(leaf) < 0)) {
4262: btrfs_print_leaf(leaf);
4263: BUG();
4264: }
4265: }
SourceFile:fs/btrfs/ctree.c]
|
| 34/2 |
2026/08/27 01:14 |
llm |
expert |
0m
Model:
Tokens:
input: 17949
output: 46
thoughts: 0
|
| 35/2 |
2026/08/27 01:14 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/btrfs/dir-item.c Name:btrfs_match_dir_item_name]
Results: map[SourceCode: 364: /*
365: * helper function to look at the directory item pointed to by 'path'
366: * this walks through all the entries in a dir item and finds one
367: * for a specific name.
368: */
369: struct btrfs_dir_item *btrfs_match_dir_item_name(const struct btrfs_path *path,
370: const char *name, int name_len)
371: {
372: struct btrfs_dir_item *dir_item;
373: unsigned long name_ptr;
374: u32 total_len;
375: u32 cur = 0;
376: u32 this_len;
377: struct extent_buffer *leaf;
378:
379: leaf = path->nodes[0];
380: dir_item = btrfs_item_ptr(leaf, path->slots[0], struct btrfs_dir_item);
381:
382: total_len = btrfs_item_size(leaf, path->slots[0]);
383: while (cur < total_len) {
384: this_len = sizeof(*dir_item) +
385: btrfs_dir_name_len(leaf, dir_item) +
386: btrfs_dir_data_len(leaf, dir_item);
387: name_ptr = (unsigned long)(dir_item + 1);
388:
389: if (btrfs_dir_name_len(leaf, dir_item) == name_len &&
390: memcmp_extent_buffer(leaf, name, name_ptr, name_len) == 0)
391: return dir_item;
392:
393: cur += this_len;
394: dir_item = (struct btrfs_dir_item *)((char *)dir_item +
395: this_len);
396: }
397: return NULL;
398: }
SourceFile:fs/btrfs/dir-item.c]
|
| 36/2 |
2026/08/27 01:14 |
llm |
expert |
0m
Model:
Tokens:
input: 18615
output: 44
thoughts: 0
|
| 37/2 |
2026/08/27 01:14 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/btrfs/inode.c Name:btrfs_insert_delayed_dir_index]
Results: map[SourceCode:1472: /* Will return 0, -ENOMEM or -EEXIST (index number collision, unexpected). */
1473: int btrfs_insert_delayed_dir_index(struct btrfs_trans_handle *trans,
1474: const char *name, int name_len,
1475: struct btrfs_inode *dir,
1476: const struct btrfs_disk_key *disk_key, u8 flags,
1477: u64 index)
1478: {
1479: struct btrfs_fs_info *fs_info = trans->fs_info;
1480: const unsigned int leaf_data_size = BTRFS_LEAF_DATA_SIZE(fs_info);
1481: struct btrfs_delayed_node *delayed_node;
1482: struct btrfs_ref_tracker delayed_node_tracker;
1483: struct btrfs_delayed_item *delayed_item;
1484: struct btrfs_dir_item *dir_item;
1485: bool reserve_leaf_space;
1486: u32 data_len;
1487: int ret;
1488:
1489: delayed_node = btrfs_get_or_create_delayed_node(dir, &delayed_node_tracker);
1490: if (IS_ERR(delayed_node))
1491: return PTR_ERR(delayed_node);
1492:
1493: delayed_item = btrfs_alloc_delayed_item(sizeof(*dir_item) + name_len,
1494: delayed_node,
1495: BTRFS_DELAYED_INSERTION_ITEM);
1496: if (!delayed_item) {
1497: ret = -ENOMEM;
1498: goto release_node;
1499: }
1500:
1501: delayed_item->index = index;
1502:
1503: dir_item = (struct btrfs_dir_item *)delayed_item->data;
1504: dir_item->location = *disk_key;
1505: btrfs_set_stack_dir_transid(dir_item, trans->transid);
1506: btrfs_set_stack_dir_data_len(dir_item, 0);
1507: btrfs_set_stack_dir_name_len(dir_item, name_len);
1508: btrfs_set_stack_dir_flags(dir_item, flags);
1509: memcpy((char *)(dir_item + 1), name, name_len);
1510:
1511: data_len = delayed_item->data_len + sizeof(struct btrfs_item);
1512:
1513: mutex_lock(&delayed_node->mutex);
1514:
1515: /*
1516: * First attempt to insert the delayed item. This is to make the error
1517: * handling path simpler in case we fail (-EEXIST). There's no risk of
1518: * any other task coming in and running the delayed item before we do
1519: * the metadata space reservation below, because we are holding the
1520: * delayed node's mutex and that mutex must also be locked before the
1521: * node's delayed items can be run.
1522: */
1523: ret = __btrfs_add_delayed_item(delayed_node, delayed_item);
1524: if (unlikely(ret)) {
1525: btrfs_err(trans->fs_info,
1526: "error adding delayed dir index item, name: %.*s, index: %llu, root: %llu, dir: %llu, dir->index_cnt: %llu, delayed_node->index_cnt: %llu, error: %pe",
1527: name_len, name, index, btrfs_root_id(delayed_node->root),
1528: delayed_node->inode_id, dir->index_cnt,
1529: delayed_node->index_cnt, ERR_PTR(ret));
1530: btrfs_release_delayed_item(delayed_item);
1531: btrfs_release_dir_index_item_space(trans);
1532: mutex_unlock(&delayed_node->mutex);
1533: goto release_node;
1534: }
1535:
1536: if (delayed_node->index_item_leaves == 0 ||
1537: delayed_node->curr_index_batch_size + data_len > leaf_data_size) {
1538: delayed_node->curr_index_batch_size = data_len;
1539: reserve_leaf_space = true;
1540: } else {
1541: delayed_node->curr_index_batch_size += data_len;
1542: reserve_leaf_space = false;
1543: }
1544:
1545: if (reserve_leaf_space) {
1546: ret = btrfs_delayed_item_reserve_metadata(trans, delayed_item);
1547: /*
1548: * Space was reserved for a dir index item insertion when we
1549: * started the transaction, so getting a failure here should be
1550: * impossible.
1551: */
1552: if (WARN_ON(ret)) {
1553: btrfs_release_delayed_item(delayed_item);
1554: mutex_unlock(&delayed_node->mutex);
1555: goto release_node;
1556: }
1557:
1558: delayed_node->index_item_leaves++;
1559: } else {
1560: btrfs_release_dir_index_item_space(trans);
1561: }
1562: mutex_unlock(&delayed_node->mutex);
1563:
1564: release_node:
1565: btrfs_release_delayed_node(delayed_node, &delayed_node_tracker);
1566: return ret;
1567: }
SourceFile:fs/btrfs/delayed-inode.c]
|
| 38/2 |
2026/08/27 01:14 |
llm |
expert |
0m
Model:
Tokens:
input: 20390
output: 45
thoughts: 0
|
| 39/2 |
2026/08/27 01:14 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/btrfs/delayed-inode.c Name:__btrfs_add_delayed_item]
Results: map[SourceCode: 408: static int __btrfs_add_delayed_item(struct btrfs_delayed_node *delayed_node,
409: struct btrfs_delayed_item *ins)
410: {
411: struct rb_root_cached *root;
412: struct rb_node *exist;
413:
414: if (ins->type == BTRFS_DELAYED_INSERTION_ITEM)
415: root = &delayed_node->ins_root;
416: else
417: root = &delayed_node->del_root;
418:
419: exist = rb_find_add_cached(&ins->rb_node, root, btrfs_delayed_item_cmp);
420: if (exist)
421: return -EEXIST;
422:
423: if (ins->type == BTRFS_DELAYED_INSERTION_ITEM &&
424: ins->index >= delayed_node->index_cnt)
425: delayed_node->index_cnt = ins->index + 1;
426:
427: delayed_node->count++;
428: atomic_inc(&delayed_node->root->fs_info->delayed_root.items);
429: return 0;
430: }
SourceFile:fs/btrfs/delayed-inode.c]
|
| 40/2 |
2026/08/27 01:14 |
llm |
expert |
0m
Model:
Tokens:
input: 21616
output: 44
thoughts: 437
Thoughts: **Debugging a Btrfs Directory Insertion**
Okay, let me think this through. `btrfs_insert_delayed_dir_index` returns `-EEXIST` if `__btrfs_add_delayed_item` returns `-EEXIST`, which then propagates up to `btrfs_insert_dir_item`. And I know `btrfs_add_link` handles `-EEXIST` by jumping to `fail_dir_item`. Actually, it also handles `-EOVERFLOW` there too. So that's not it. The question is, what error *is* `btrfs_insert_dir_item` returning that's triggering `btrfs_abort_transaction`?
It *could* be `-ENOMEM`, from something like `btrfs_alloc_path` or `btrfs_alloc_delayed_item`, but hang on... doesn't `btrfs_abort_should_print_stack` return `false` for `-ENOMEM`, and also `-EIO`, `-EROFS`? Which means `WARN_ON(btrfs_abort_should_print_stack(__error))` won't actually trigger a warning when those errors are encountered.
Right, let me check that `btrfs_abort_transaction` macro again:
```c
if (!test_and_set_bit(BTRFS_FS_STATE_TRANS_ABORTED,
&((trans)->fs_info->fs_state))) {
WARN_ON(btrfs_abort_should_print_stack(__error));
__error = -__error;
}
```
If `__error` *is* `-ENOMEM`, then `btrfs_abort_should_print_stack(-ENOMEM)` returns `false`. `WARN_ON(false)` does *not* trigger a warning. Therefore, the error *can't* be `-ENOMEM`! It has to be something else.
Okay, back to the source. What else could `btrfs_insert_dir_item` possibly return? I need to review that function again.
|
| 41/2 |
2026/08/27 01:15 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/btrfs/dir-item.c Name:btrfs_insert_dir_item]
Results: map[SourceCode: 100: /*
101: * insert a directory item in the tree, doing all the magic for
102: * both indexes. 'dir' indicates which objectid to insert it into,
103: * 'location' is the key to stuff into the directory item, 'type' is the
104: * type of the inode we're pointing to, and 'index' is the sequence number
105: * to use for the second index (if one is created).
106: * Will return 0 or -ENOMEM
107: */
108: int btrfs_insert_dir_item(struct btrfs_trans_handle *trans,
109: const struct fscrypt_str *name, struct btrfs_inode *dir,
110: const struct btrfs_key *location, u8 type, u64 index)
111: {
112: int ret = 0;
113: int ret2 = 0;
114: struct btrfs_root *root = dir->root;
115: BTRFS_PATH_AUTO_FREE(path);
116: struct btrfs_dir_item *dir_item;
117: struct extent_buffer *leaf;
118: unsigned long name_ptr;
119: struct btrfs_key key;
120: struct btrfs_disk_key disk_key;
121: u32 data_size;
122:
123: key.objectid = btrfs_ino(dir);
124: key.type = BTRFS_DIR_ITEM_KEY;
125: key.offset = btrfs_name_hash(name->name, name->len);
126:
127: path = btrfs_alloc_path();
128: if (!path)
129: return -ENOMEM;
130:
131: btrfs_cpu_key_to_disk(&disk_key, location);
132:
133: data_size = sizeof(*dir_item) + name->len;
134: dir_item = insert_with_overflow(trans, root, path, &key, data_size,
135: name->name, name->len);
136: if (IS_ERR(dir_item)) {
137: ret = PTR_ERR(dir_item);
138: if (ret == -EEXIST)
139: goto second_insert;
140: goto out_free;
141: }
142:
143: if (IS_ENCRYPTED(&dir->vfs_inode))
144: type |= BTRFS_FT_ENCRYPTED;
145:
146: leaf = path->nodes[0];
147: btrfs_set_dir_item_key(leaf, dir_item, &disk_key);
148: btrfs_set_dir_flags(leaf, dir_item, type);
149: btrfs_set_dir_data_len(leaf, dir_item, 0);
150: btrfs_set_dir_name_len(leaf, dir_item, name->len);
151: btrfs_set_dir_transid(leaf, dir_item, trans->transid);
152: name_ptr = (unsigned long)(dir_item + 1);
153:
154: write_extent_buffer(leaf, name->name, name_ptr, name->len);
155:
156: second_insert:
157: /* FIXME, use some real flag for selecting the extra index */
158: if (root == root->fs_info->tree_root) {
159: ret = 0;
160: goto out_free;
161: }
162: btrfs_release_path(path);
163:
164: ret2 = btrfs_insert_delayed_dir_index(trans, name->name, name->len, dir,
165: &disk_key, type, index);
166: out_free:
167: if (ret)
168: return ret;
169: if (ret2)
170: return ret2;
171: return 0;
172: }
SourceFile:fs/btrfs/dir-item.c]
|
| 42/2 |
2026/08/27 01:15 |
llm |
expert |
0m
Model:
Tokens:
input: 22605
output: 63
thoughts: 0
|
| 43/2 |
2026/08/27 01:15 |
tool |
git-log |
0m
Args: map[CodeRegexp: Count:10 MessageRegexps:[btrfs_abort_should_print_stack] PathPrefix: Since: SourcePath: SymbolName:]
Results: map[Output:ae4316f332e0 btrfs: do not try compression for data reloc inodes
]
|
| 44/2 |
2026/08/27 01:15 |
llm |
expert |
0m
Model:
Tokens:
input: 22705
output: 26
thoughts: 0
|
| 45/2 |
2026/08/27 01:15 |
tool |
git-show |
0m
Args:
Results: map[Output:commit ae4316f332e03e628712e9dfb89f2b7d3c70c21a
Author: Qu Wenruo <wqu@suse.com>
Date: Tue Jun 23 21:07:14 2026 +0930
btrfs: do not try compression for data reloc inodes
[BUG]
There is a syzbot report that the check inside get_new_location()
triggered:
BTRFS info (device loop0): found 31 extents, stage: move data extents
BTRFS info (device loop0): leaf 8908800 gen 16 total ptrs 28 free space 1676 owner 18446744073709551607
item 0 key (256 INODE_ITEM 0) itemoff 3835 itemsize 160
inode generation 5 transid 0 size 0 nbytes 0
block group 0 mode 40755 links 1 uid 0 gid 0
rdev 0 sequence 0 flags 0x0
atime 1669132761.0
ctime 1669132761.0
mtime 1669132761.0
otime 0.0
item 1 key (256 INODE_REF 256) itemoff 3823 itemsize 12
index 0 name_len 2
item 2 key (258 INODE_ITEM 0) itemoff 3663 itemsize 160
inode generation 1 transid 16 size 733184 nbytes 106496
block group 0 mode 100600 links 0 uid 0 gid 0
rdev 0 sequence 24 flags 0x18
item 3 key (258 EXTENT_DATA 0) itemoff 3595 itemsize 68
generation 16 type 0
inline extent data size 47 ram_bytes 4096 compression 1
[...]
item 27 key (18446744073709551611 ORPHAN_ITEM 258) itemoff 2376 itemsize 0
BTRFS error (device loop0): unexpected non-zero offset in file extent item for data reloc inode 258 key offset 0 offset 9277520992061368337
------------[ cut here ]------------
btrfs_abort_should_print_stack(__error)
[CAUSE]
The above dump tree shows the first file extent item is inlined, which
should make no sense for data reloc inodes, as such inodes just
represent where the data extents are in the relocation destination chunk.
However the relocation path preallocates space for each block,
then dirties them, cluster by cluster.
It's possible to have a single block at the beginning of the block
group, and no other block in the same cluster.
So relocation will preallocate a file extent for that block and dirty
the first block. Then memory pressure forces the data reloc inode to be
written back, before any other blocks are dirtied/allocated.
Finally commit 3eaf5f082c4c ("btrfs: extract inlined creation into a dedicated
delalloc helper") changed the sequence of delalloc. Before that commit we
always tried NOCOW first, so that dirtied block would be written back into
the preallocated space, and appear as a regular extent.
But with that commit, we always try inline first, and since compression
is forced, we try compressing the first block, and then inline the
compressed data, resulting in the above inlined file extent in the data
reloc tree.
Then the check in get_new_location() will check the file offset, without
checking if the file extent is inlined or not, resulting in the above
failure.
[FIX]
Do not allow compression for data reloc inodes.
Since data reloc inode sizes are always block aligned, as long as we do
not compress, @data_len will always be at least one block, and
that will cause can_cow_file_range_inline() to return false, thus no
inlined extent will be created.
Reported-by: syzbot+d950c6ba09b79f6e1864@syzkaller.appspotmail.com
Link: https://lore.kernel.org/linux-btrfs/6a373dc5.764cf64f.168fbe.0001.GAE@google.com/
Fixes: 3eaf5f082c4c ("btrfs: extract inlined creation into a dedicated delalloc helper")
CC: stable@vger.kernel.org
Reviewed-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
diff --git a/fs/btrfs/btrfs_inode.h b/fs/btrfs/btrfs_inode.h
index d5d81f9546c3..7fdc6c3fd066 100644
--- a/fs/btrfs/btrfs_inode.h
+++ b/fs/btrfs/btrfs_inode.h
@@ -476,6 +476,8 @@ static inline bool btrfs_inode_can_compress(const struct btrfs_inode *inode)
if (inode->flags & BTRFS_INODE_NODATACOW ||
inode->flags & BTRFS_INODE_NODATASUM)
return false;
+ if (btrfs_is_data_reloc_root(inode->root))
+ return false;
return true;
}
]
|
| 46/2 |
2026/08/27 01:15 |
llm |
expert |
0m
Model:
Tokens:
input: 25483
output: 40
thoughts: 1252
Thoughts: **Analyzing a Btrfs ENOSPC Warning During File Creation**
Okay, let's break this down. `btrfs_abort_should_print_stack` is a simple function that tells us *when* to print the stack trace during a transaction abort. It uses `WARN_ON`, which triggers a warning if the condition is true. The function returns true for errors *other* than the "common" ones like `-EIO`, `-EROFS`, or `-ENOMEM`. So, the fact that we're getting a stack trace *means* the error isn't one of those three.
The reported error is happening in `btrfs_add_link` which, after reviewing the code, means that the function returned from one of several called functions. Let's retrace the calls in `btrfs_insert_dir_item` and its callees to understand the code flow. We can see that `btrfs_search_slot` (called by `btrfs_insert_empty_items`) can return a variety of errors, including `-EUCLEAN`, which signals a corrupted B-tree. And `btrfs_insert_delayed_dir_index` can call `btrfs_delayed_item_reserve_metadata`, which can return `-ENOSPC` or `-EDQUOT`, which are also options.
The bug report points to `-ENOSPC`. Looking back, if `btrfs_delayed_item_reserve_metadata` returns `-ENOSPC`, the code propagates that error all the way up to `btrfs_add_link` where it hits `btrfs_abort_transaction`. That's where `btrfs_abort_should_print_stack(-ENOSPC)` returns true, and the stack trace prints. The comments in `btrfs_abort_should_print_stack` *explicitly* call out `-ENOSPC` as a potential "bug" condition because the code already reserved space.
Wait...the code *shouldn't* fail with `-ENOSPC` *here*, because space was supposed to be reserved earlier. There's a `WARN_ON` within `btrfs_insert_delayed_dir_index` that *should* catch the specific failure in `btrfs_delayed_item_reserve_metadata`! If that `WARN_ON` had triggered, we would have seen that warning in the log before `btrfs_add_link`. The log only shows the warning from `btrfs_add_link`, so it's *probably not* that failure.
Now, what *else* could trigger the warning? Could `btrfs_insert_dir_item` return `-EUCLEAN`? That seems possible because `btrfs_search_slot` *could* encounter a corrupted B-tree. However, the system is new or mounted from an image, which would make the probability of corruption very low. This isn't likely but worth noting.
Let's look at the call stack, though: it's a normal file creation sequence: `do_sys_openat2` -> `do_file_open` -> `path_openat` -> `lookup_open` -> `btrfs_create_common` -> `btrfs_create_new_inode` -> `btrfs_add_link`. It *could* be a corrupted image, and the failure is happening during that process. However, that feels like a long shot. Let's look at `btrfs_insert_empty_items` to see if there is another way to return `-EUCLEAN` and cause a problem.
|
| 47/2 |
2026/08/27 01:15 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/btrfs/ctree.c Name:btrfs_search_slot]
Results: map[SourceCode:1963: /*
1964: * Look for a key in a tree and perform necessary modifications to preserve
1965: * tree invariants.
1966: *
1967: * @trans: Handle of transaction, used when modifying the tree
1968: * @p: Holds all btree nodes along the search path
1969: * @root: The root node of the tree
1970: * @key: The key we are looking for
1971: * @ins_len: Indicates purpose of search:
1972: * >0 for inserts it's size of item inserted (*)
1973: * <0 for deletions
1974: * 0 for plain searches, not modifying the tree
1975: *
1976: * (*) If size of item inserted doesn't include
1977: * sizeof(struct btrfs_item), then p->search_for_extension must
1978: * be set.
1979: * @cow: boolean should CoW operations be performed. Must always be 1
1980: * when modifying the tree.
1981: *
1982: * If @ins_len > 0, nodes and leaves will be split as we walk down the tree.
1983: * If @ins_len < 0, nodes will be merged as we walk down the tree (if possible)
1984: *
1985: * If @key is found, 0 is returned and you can find the item in the leaf level
1986: * of the path (level 0)
1987: *
1988: * If @key isn't found, 1 is returned and the leaf level of the path (level 0)
1989: * points to the slot where it should be inserted
1990: *
1991: * If an error is encountered while searching the tree a negative error number
1992: * is returned
1993: */
1994: int btrfs_search_slot(struct btrfs_trans_handle *trans, struct btrfs_root *root,
1995: const struct btrfs_key *key, struct btrfs_path *p,
1996: int ins_len, int cow)
1997: {
1998: struct btrfs_fs_info *fs_info;
1999: struct extent_buffer *b;
2000: int slot;
2001: int ret;
2002: int level;
2003: int lowest_unlock = 1;
2004: /* everything at write_lock_level or lower must be write locked */
2005: int write_lock_level = 0;
2006: u8 lowest_level = 0;
2007: int min_write_lock_level;
2008: int prev_cmp;
2009: struct btrfs_eb_prealloc pa = { .supports_nowait = true };
2010:
2011: if (!root)
2012: return -EINVAL;
2013:
2014: fs_info = root->fs_info;
2015: might_sleep();
2016:
2017: lowest_level = p->lowest_level;
2018: WARN_ON(lowest_level && ins_len > 0);
2019: WARN_ON(p->nodes[0] != NULL);
2020: BUG_ON(!cow && ins_len);
2021:
2022: /*
2023: * For now only allow nowait for read only operations. There's no
2024: * strict reason why we can't, we just only need it for reads so it's
2025: * only implemented for reads.
2026: */
2027: ASSERT(!p->nowait || !cow);
2028:
2029: if (ins_len < 0) {
2030: lowest_unlock = 2;
2031:
2032: /* when we are removing items, we might have to go up to level
2033: * two as we update tree pointers Make sure we keep write
2034: * for those levels as well
2035: */
2036: write_lock_level = 2;
2037: } else if (ins_len > 0) {
2038: /*
2039: * for inserting items, make sure we have a write lock on
2040: * level 1 so we can update keys
2041: */
2042: write_lock_level = 1;
2043: }
2044:
2045: if (!cow)
2046: write_lock_level = -1;
2047:
2048: if (cow && (p->keep_locks || p->lowest_level))
2049: write_lock_level = BTRFS_MAX_LEVEL;
2050:
2051: min_write_lock_level = write_lock_level;
2052:
2053: if (p->need_commit_sem) {
2054: ASSERT(p->search_commit_root);
2055: if (p->nowait) {
2056: if (!down_read_trylock(&fs_info->commit_root_sem))
2057: return -EAGAIN;
2058: } else {
2059: down_read(&fs_info->commit_root_sem);
2060: }
2061: }
2062:
2063: again:
2064: if (pa.needs_prealloc) {
2065: ret = btrfs_init_eb_prealloc(fs_info, &pa, false);
2066: if (ret)
2067: goto done;
2068: }
2069: prev_cmp = -1;
2070: b = btrfs_search_slot_get_root(root, p, write_lock_level);
2071: if (IS_ERR(b)) {
2072: ret = PTR_ERR(b);
2073: goto done;
2074: }
2075:
2076: while (b) {
2077: bool dec = false;
2078: int ret2;
2079:
2080: level = btrfs_header_level(b);
2081:
2082: if (cow) {
2083: bool last_level = (level == (BTRFS_MAX_LEVEL - 1));
2084:
2085: /*
2086: * if we don't really need to cow this block
2087: * then we don't want to set the path blocking,
2088: * so we test it here
2089: */
2090: if (!should_cow_block(trans, root, b))
2091: goto cow_done;
2092:
2093: /*
2094: * must have write locks on this node and the
2095: * parent
2096: */
2097: if (level > write_lock_level ||
2098: (level + 1 > write_lock_level &&
2099: level + 1 < BTRFS_MAX_LEVEL &&
2100: p->nodes[level + 1])) {
2101: write_lock_level = level + 1;
2102: btrfs_release_path(p);
2103: trace_btrfs_search_slot_restart(root, level, "write_lock");
2104: goto again;
2105: }
2106:
2107: if (last_level)
2108: ret2 = btrfs_cow_block(trans, root, b, NULL, 0,
2109: &b, BTRFS_NESTING_COW);
2110: else
2111: ret2 = btrfs_cow_block(trans, root, b,
2112: p->nodes[level + 1],
2113: p->slots[level + 1], &b,
2114: BTRFS_NESTING_COW);
2115: if (ret2) {
2116: ret = ret2;
2117: goto done;
2118: }
2119: }
2120: cow_done:
2121: p->nodes[level] = b;
2122:
2123: /*
2124: * we have a lock on b and as long as we aren't changing
2125: * the tree, there is no way to for the items in b to change.
2126: * It is safe to drop the lock on our parent before we
2127: * go through the expensive btree search on b.
2128: *
2129: * If we're inserting or deleting (ins_len != 0), then we might
2130: * be changing slot zero, which may require changing the parent.
2131: * So, we can't drop the lock until after we know which slot
2132: * we're operating on.
2133: */
2134: if (!ins_len && !p->keep_locks) {
2135: int u = level + 1;
2136:
2137: if (u < BTRFS_MAX_LEVEL && p->locks[u]) {
2138: btrfs_tree_unlock_rw(p->nodes[u], p->locks[u]);
2139: p->locks[u] = 0;
2140: }
2141: }
2142:
2143: if (level == 0) {
2144: if (ins_len > 0)
2145: ASSERT(write_lock_level >= 1);
2146:
2147: ret = search_leaf(trans, root, key, p, ins_len, prev_cmp);
2148: if (!p->search_for_split)
2149: unlock_up(p, level, lowest_unlock,
2150: min_write_lock_level, NULL);
2151: goto done;
2152: }
2153:
2154: ret = search_for_key_slot(b, 0, key, prev_cmp, &slot);
2155: if (ret < 0)
2156: goto done;
2157: prev_cmp = ret;
2158:
2159: if (ret && slot > 0) {
2160: dec = true;
2161: slot--;
2162: }
2163: p->slots[level] = slot;
2164: ret2 = setup_nodes_for_search(trans, root, p, b, level, ins_len,
2165: &write_lock_level);
2166: if (ret2 == -EAGAIN) {
2167: trace_btrfs_search_slot_restart(root, level, "setup_nodes");
2168: goto again;
2169: }
2170: if (ret2) {
2171: ret = ret2;
2172: goto done;
2173: }
2174: b = p->nodes[level];
2175: slot = p->slots[level];
2176:
2177: /*
2178: * Slot 0 is special, if we change the key we have to update
2179: * the parent pointer which means we must have a write lock on
2180: * the parent
2181: */
2182: if (slot == 0 && ins_len && write_lock_level < level + 1) {
2183: write_lock_level = level + 1;
2184: btrfs_release_path(p);
2185: trace_btrfs_search_slot_restart(root, level, "slot_zero");
2186: goto again;
2187: }
2188:
2189: unlock_up(p, level, lowest_unlock, min_write_lock_level,
2190: &write_lock_level);
2191:
2192: if (level == lowest_level) {
2193: if (dec)
2194: p->slots[level]++;
2195: goto done;
2196: }
2197:
2198: ret2 = read_block_for_search(root, p, &pa, &b, slot, key);
2199: if (ret2 == -EAGAIN && !p->nowait) {
2200: trace_btrfs_search_slot_restart(root, level, "read_block");
2201: goto again;
2202: }
2203: if (ret2) {
2204: ret = ret2;
2205: goto done;
2206: }
2207:
2208: if (!p->skip_locking) {
2209: level = btrfs_header_level(b);
2210:
2211: btrfs_maybe_reset_lockdep_class(root, b);
2212:
2213: if (level <= write_lock_level) {
2214: btrfs_tree_lock(b);
2215: p->locks[level] = BTRFS_WRITE_LOCK;
2216: } else {
2217: if (p->nowait) {
2218: if (!btrfs_try_tree_read_lock(b)) {
2219: free_extent_buffer(b);
2220: ret = -EAGAIN;
2221: goto done;
2222: }
2223: } else {
2224: btrfs_tree_read_lock(b);
2225: }
2226: p->locks[level] = BTRFS_READ_LOCK;
2227: }
2228: p->nodes[level] = b;
2229: }
2230: }
2231: ret = 1;
2232: done:
2233: if (ret < 0 && !p->skip_release_on_error)
2234: btrfs_release_path(p);
2235:
2236: if (p->need_commit_sem) {
2237: int ret2;
2238:
2239: ret2 = finish_need_commit_sem_search(p);
2240: up_read(&fs_info->commit_root_sem);
2241: if (ret2)
2242: ret = ret2;
2243: }
2244:
2245: btrfs_free_eb_prealloc(&pa);
2246:
2247: return ret;
2248: }
SourceFile:fs/btrfs/ctree.c]
|
| 48/2 |
2026/08/27 01:15 |
llm |
expert |
0m
Model:
Tokens:
input: 29365
output: 42
thoughts: 0
|
| 49/2 |
2026/08/27 01:15 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/btrfs/inode.c Name:btrfs_create_new_inode]
Results: map[SourceCode:6612: int btrfs_create_new_inode(struct btrfs_trans_handle *trans,
6613: struct btrfs_new_inode_args *args)
6614: {
6615: struct timespec64 ts;
6616: struct inode *dir = args->dir;
6617: struct inode *inode = args->inode;
6618: const struct fscrypt_str *name = args->orphan ? NULL : &args->fname.disk_name;
6619: struct btrfs_fs_info *fs_info = inode_to_fs_info(dir);
6620: struct btrfs_root *root;
6621: struct btrfs_inode_item *inode_item;
6622: struct btrfs_path *path;
6623: u64 objectid;
6624: struct btrfs_inode_ref *ref;
6625: struct btrfs_key key[2];
6626: u32 sizes[2];
6627: struct btrfs_item_batch batch;
6628: unsigned long ptr;
6629: int ret;
6630: bool xa_reserved = false;
6631:
6632: if (!args->orphan && !args->subvol) {
6633: /*
6634: * Before anything else, check if we can add the name to the
6635: * parent directory. We want to avoid a dir item overflow in
6636: * case we have an existing dir item due to existing name
6637: * hash collisions. We do this check here before we call
6638: * btrfs_add_link() down below so that we can avoid a
6639: * transaction abort (which could be exploited by malicious
6640: * users).
6641: *
6642: * For subvolumes we already do this in btrfs_mksubvol().
6643: */
6644: ret = btrfs_check_dir_item_collision(BTRFS_I(dir)->root,
6645: btrfs_ino(BTRFS_I(dir)),
6646: name);
6647: if (ret < 0)
6648: return ret;
6649: }
6650:
6651: path = btrfs_alloc_path();
6652: if (!path)
6653: return -ENOMEM;
6654:
6655: if (!args->subvol)
6656: BTRFS_I(inode)->root = btrfs_grab_root(BTRFS_I(dir)->root);
6657: root = BTRFS_I(inode)->root;
6658:
6659: ret = btrfs_init_file_extent_tree(BTRFS_I(inode));
6660: if (ret)
6661: goto out;
6662:
6663: ret = btrfs_get_free_objectid(root, &objectid);
6664: if (ret)
6665: goto out;
6666: btrfs_set_inode_number(BTRFS_I(inode), objectid);
6667:
6668: ret = xa_reserve(&root->inodes, objectid, GFP_NOFS);
6669: if (ret)
6670: goto out;
6671: xa_reserved = true;
6672:
6673: if (args->orphan) {
6674: /*
6675: * O_TMPFILE, set link count to 0, so that after this point, we
6676: * fill in an inode item with the correct link count.
6677: */
6678: set_nlink(inode, 0);
6679: } else {
6680: trace_btrfs_inode_request(dir);
6681:
6682: ret = btrfs_set_inode_index(BTRFS_I(dir), &BTRFS_I(inode)->dir_index);
6683: if (ret)
6684: goto out;
6685: }
6686:
6687: if (S_ISDIR(inode->i_mode))
6688: BTRFS_I(inode)->index_cnt = BTRFS_DIR_START_INDEX;
6689:
6690: BTRFS_I(inode)->generation = trans->transid;
6691: inode->i_generation = BTRFS_I(inode)->generation;
6692:
6693: /*
6694: * We don't have any capability xattrs set here yet, shortcut any
6695: * queries for the xattrs here. If we add them later via the inode
6696: * security init path or any other path this flag will be cleared.
6697: */
6698: set_bit(BTRFS_INODE_NO_CAP_XATTR, &BTRFS_I(inode)->runtime_flags);
6699:
6700: /*
6701: * Subvolumes don't inherit flags from their parent directory.
6702: * Originally this was probably by accident, but we probably can't
6703: * change it now without compatibility issues.
6704: */
6705: if (!args->subvol)
6706: btrfs_inherit_iflags(BTRFS_I(inode), BTRFS_I(dir));
6707:
6708: btrfs_set_inode_mapping_order(BTRFS_I(inode));
6709: if (S_ISREG(inode->i_mode)) {
6710: if (btrfs_test_opt(fs_info, NODATASUM))
6711: BTRFS_I(inode)->flags |= BTRFS_INODE_NODATASUM;
6712: if (btrfs_test_opt(fs_info, NODATACOW))
6713: BTRFS_I(inode)->flags |= BTRFS_INODE_NODATACOW |
6714: BTRFS_INODE_NODATASUM;
6715: btrfs_update_inode_mapping_flags(BTRFS_I(inode));
6716: }
6717:
6718: ret = btrfs_insert_inode_locked(inode);
6719: if (ret < 0) {
6720: if (!args->orphan)
6721: BTRFS_I(dir)->index_cnt--;
6722: goto out;
6723: }
6724:
6725: /*
6726: * We could have gotten an inode number from somebody who was fsynced
6727: * and then removed in this same transaction, so let's just set full
6728: * sync since it will be a full sync anyway and this will blow away the
6729: * old info in the log.
6730: */
6731: btrfs_set_inode_full_sync(BTRFS_I(inode));
6732:
6733: key[0].objectid = objectid;
6734: key[0].type = BTRFS_INODE_ITEM_KEY;
6735: key[0].offset = 0;
6736:
6737: sizes[0] = sizeof(struct btrfs_inode_item);
6738:
6739: if (!args->orphan) {
6740: /*
6741: * Start new inodes with an inode_ref. This is slightly more
6742: * efficient for small numbers of hard links since they will
6743: * be packed into one item. Extended refs will kick in if we
6744: * add more hard links than can fit in the ref item.
6745: */
6746: key[1].objectid = objectid;
6747: key[1].type = BTRFS_INODE_REF_KEY;
6748: if (args->subvol) {
6749: key[1].offset = objectid;
6750: sizes[1] = 2 + sizeof(*ref);
6751: } else {
6752: key[1].offset = btrfs_ino(BTRFS_I(dir));
6753: sizes[1] = name->len + sizeof(*ref);
6754: }
6755: }
6756:
6757: batch.keys = &key[0];
6758: batch.data_sizes = &sizes[0];
6759: batch.total_data_size = sizes[0] + (args->orphan ? 0 : sizes[1]);
6760: batch.nr = args->orphan ? 1 : 2;
6761: ret = btrfs_insert_empty_items(trans, root, path, &batch);
6762: if (unlikely(ret != 0)) {
6763: btrfs_abort_transaction(trans, ret);
6764: goto discard;
6765: }
6766:
6767: ts = simple_inode_init_ts(inode);
6768: BTRFS_I(inode)->i_otime_sec = ts.tv_sec;
6769: BTRFS_I(inode)->i_otime_nsec = ts.tv_nsec;
6770:
6771: /*
6772: * We're going to fill the inode item now, so at this point the inode
6773: * must be fully initialized.
6774: */
6775:
6776: inode_item = btrfs_item_ptr(path->nodes[0], path->slots[0],
6777: struct btrfs_inode_item);
6778: memzero_extent_buffer(path->nodes[0], (unsigned long)inode_item,
6779: sizeof(*inode_item));
6780: fill_inode_item(trans, path->nodes[0], inode_item, inode);
6781:
6782: if (!args->orphan) {
6783: ref = btrfs_item_ptr(path->nodes[0], path->slots[0] + 1,
6784: struct btrfs_inode_ref);
6785: ptr = (unsigned long)(ref + 1);
6786: if (args->subvol) {
6787: btrfs_set_inode_ref_name_len(path->nodes[0], ref, 2);
6788: btrfs_set_inode_ref_index(path->nodes[0], ref, 0);
6789: write_extent_buffer(path->nodes[0], "..", ptr, 2);
6790: } else {
6791: btrfs_set_inode_ref_name_len(path->nodes[0], ref,
6792: name->len);
6793: btrfs_set_inode_ref_index(path->nodes[0], ref,
6794: BTRFS_I(inode)->dir_index);
6795: write_extent_buffer(path->nodes[0], name->name, ptr,
6796: name->len);
6797: }
6798: }
6799:
6800: /*
6801: * We don't need the path anymore, plus inheriting properties, adding
6802: * ACLs, security xattrs, orphan item or adding the link, will result in
6803: * allocating yet another path. So just free our path.
6804: */
6805: btrfs_free_path(path);
6806: path = NULL;
6807:
6808: if (args->subvol) {
6809: struct btrfs_inode *parent;
6810:
6811: /*
6812: * Subvolumes inherit properties from their parent subvolume,
6813: * not the directory they were created in.
6814: */
6815: parent = btrfs_iget(BTRFS_FIRST_FREE_OBJECTID, BTRFS_I(dir)->root);
6816: if (IS_ERR(parent)) {
6817: ret = PTR_ERR(parent);
6818: } else {
6819: ret = btrfs_inode_inherit_props(trans, BTRFS_I(inode),
6820: parent);
6821: iput(&parent->vfs_inode);
6822: }
6823: } else {
6824: ret = btrfs_inode_inherit_props(trans, BTRFS_I(inode),
6825: BTRFS_I(dir));
6826: }
6827: if (ret) {
6828: btrfs_err(fs_info,
6829: "error inheriting props for ino %llu (root %llu): %pe",
6830: btrfs_ino(BTRFS_I(inode)), btrfs_root_id(root), ERR_PTR(ret));
6831: }
6832:
6833: /*
6834: * Subvolumes don't inherit ACLs or get passed to the LSM. This is
6835: * probably a bug.
6836: */
6837: if (!args->subvol) {
6838: ret = btrfs_init_inode_security(trans, args);
6839: if (unlikely(ret)) {
6840: btrfs_abort_transaction(trans, ret);
6841: goto discard;
6842: }
6843: }
6844:
6845: ret = btrfs_add_inode_to_root(BTRFS_I(inode), false);
6846: if (WARN_ON(ret)) {
6847: /* Shouldn't happen, we used xa_reserve() before. */
6848: btrfs_abort_transaction(trans, ret);
6849: goto discard;
6850: }
6851:
6852: trace_btrfs_inode_new(inode);
6853: btrfs_set_inode_last_trans(trans, BTRFS_I(inode));
6854:
6855: btrfs_update_root_times(trans, root);
6856:
6857: if (args->orphan) {
6858: ret = btrfs_orphan_add(trans, BTRFS_I(inode));
6859: if (unlikely(ret)) {
6860: btrfs_abort_transaction(trans, ret);
6861: goto discard;
6862: }
6863: } else {
6864: ret = btrfs_add_link(trans, BTRFS_I(dir), BTRFS_I(inode), name,
6865: false, BTRFS_I(inode)->dir_index);
6866: if (unlikely(ret)) {
6867: btrfs_abort_transaction(trans, ret);
6868: goto discard;
6869: }
6870: }
6871:
6872: return 0;
6873:
6874: discard:
6875: /*
6876: * discard_new_inode() calls iput(), but the caller owns the reference
6877: * to the inode.
6878: */
6879: ihold(inode);
6880: discard_new_inode(inode);
6881: out:
6882: if (xa_reserved)
6883: xa_release(&root->inodes, objectid);
6884:
6885: btrfs_free_path(path);
6886: return ret;
6887: }
SourceFile:fs/btrfs/inode.c]
|
| 50/2 |
2026/08/27 01:15 |
llm |
expert |
0m
Model:
Tokens:
input: 33865
output: 40
thoughts: 0
|
| 51/2 |
2026/08/27 01:15 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/btrfs/inode.c Name:btrfs_create_common]
Results: map[SourceCode:6966: static int btrfs_create_common(struct inode *dir, struct dentry *dentry,
6967: struct inode *inode)
6968: {
6969: struct btrfs_fs_info *fs_info = inode_to_fs_info(dir);
6970: struct btrfs_root *root = BTRFS_I(dir)->root;
6971: struct btrfs_new_inode_args new_inode_args = {
6972: .dir = dir,
6973: .dentry = dentry,
6974: .inode = inode,
6975: };
6976: unsigned int trans_num_items;
6977: struct btrfs_trans_handle *trans;
6978: int ret;
6979:
6980: ret = btrfs_new_inode_prepare(&new_inode_args, &trans_num_items);
6981: if (ret)
6982: goto out_inode;
6983:
6984: trans = btrfs_start_transaction(root, trans_num_items);
6985: if (IS_ERR(trans)) {
6986: ret = PTR_ERR(trans);
6987: goto out_new_inode_args;
6988: }
6989:
6990: ret = btrfs_create_new_inode(trans, &new_inode_args);
6991: if (!ret) {
6992: if (S_ISDIR(inode->i_mode))
6993: inode->i_opflags |= IOP_FASTPERM_MAY_EXEC;
6994: d_instantiate_new(dentry, inode);
6995: }
6996:
6997: btrfs_end_transaction(trans);
6998: btrfs_btree_balance_dirty(fs_info);
6999: out_new_inode_args:
7000: btrfs_new_inode_args_destroy(&new_inode_args);
7001: out_inode:
7002: if (ret)
7003: iput(inode);
7004: return ret;
7005: }
SourceFile:fs/btrfs/inode.c]
|
| 52/2 |
2026/08/27 01:15 |
llm |
expert |
0m
Model:
Tokens:
input: 34560
output: 44
thoughts: 0
|
| 53/2 |
2026/08/27 01:16 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/btrfs/inode.c Name:btrfs_check_dir_item_collision]
Results: map[SourceCode: 230: int btrfs_check_dir_item_collision(struct btrfs_root *root, u64 dir_ino,
231: const struct fscrypt_str *name)
232: {
233: int ret;
234: struct btrfs_key key;
235: struct btrfs_dir_item *di;
236: int data_size;
237: struct extent_buffer *leaf;
238: int slot;
239: BTRFS_PATH_AUTO_FREE(path);
240:
241: path = btrfs_alloc_path();
242: if (!path)
243: return -ENOMEM;
244:
245: key.objectid = dir_ino;
246: key.type = BTRFS_DIR_ITEM_KEY;
247: key.offset = btrfs_name_hash(name->name, name->len);
248:
249: di = btrfs_lookup_match_dir(NULL, root, path, &key, name->name,
250: name->len, 0);
251: if (IS_ERR(di)) {
252: ret = PTR_ERR(di);
253: /* Nothing found, we're safe */
254: if (ret == -ENOENT)
255: return 0;
256: return ret;
257: }
258:
259: /* we found an item, look for our name in the item */
260: if (di) {
261: /* our exact name was found */
262: return -EEXIST;
263: }
264:
265: /* See if there is room in the item to insert this name. */
266: data_size = sizeof(*di) + name->len;
267: leaf = path->nodes[0];
268: slot = path->slots[0];
269: if (data_size + btrfs_item_size(leaf, slot) +
270: sizeof(struct btrfs_item) > BTRFS_LEAF_DATA_SIZE(root->fs_info)) {
271: return -EOVERFLOW;
272: }
273:
274: /* Plenty of insertion room. */
275: return 0;
276: }
SourceFile:fs/btrfs/dir-item.c]
|
| 54/2 |
2026/08/27 01:16 |
llm |
expert |
0m
Model:
Tokens:
input: 37803
output: 36
thoughts: 776
Thoughts: **Analyzing Btrfs Directory Item Insertion**
Okay, let's break this down. My initial thought was that `btrfs_check_dir_item_collision` simply prevents the creation of a new inode if there isn't room in the item, returning `-EOVERFLOW` early. Specifically, `btrfs_check_dir_item_collision` is supposed to prevent this by checking `data_size + btrfs_item_size(leaf, slot) + sizeof(struct btrfs_item) > BTRFS_LEAF_DATA_SIZE(root->fs_info)`. But I'm now realizing there's more to it.
`btrfs_create_new_inode` returns `-EOVERFLOW` if the previous check returns it. However, the subsequent flow is more complex. I need to understand what happens in the insertion process. So, `btrfs_insert_dir_item` calls `insert_with_overflow`. This function then calls `btrfs_insert_empty_item`. If the key already exists (i.e. `-EEXIST` is returned), `btrfs_extend_item` is called.
Here's where it gets interesting: `btrfs_extend_item` is supposed to extend the existing item to accommodate the new data. But I see checks within `btrfs_extend_item` and `btrfs_insert_empty_items` that suggest a potential `BUG()` if the leaf doesn't have enough free space. For example, `btrfs_extend_item` checks `if (unlikely(btrfs_leaf_free_space(leaf) < data_size)) { btrfs_print_leaf(leaf); BUG(); }`. Also, `btrfs_insert_empty_items` checks `if (unlikely(btrfs_leaf_free_space(leaf) < total_size)) { btrfs_print_leaf(leaf); btrfs_crit(...); BUG(); }`. This seems counter-intuitive; why is this a `BUG` and not just another `-EOVERFLOW` return or a leaf split?
Then, the key is `btrfs_search_slot`. It *should* handle leaf splitting if `ins_len > 0`. Therefore `btrfs_search_slot` ensures there's enough space *before* the item is extended, right? The size of the data is `data_size = sizeof(*dir_item) + name->len`. Then `insert_with_overflow` calls `btrfs_insert_empty_item` with this `data_size`. If the key already exists, `btrfs_match_dir_item_name` is called to check and then potentially `btrfs_extend_item`.
Aha! Here's the critical point. `btrfs_extend_item` *doesn't* split the leaf. Instead, the implication is that `btrfs_search_slot` was called *beforehand* with an appropriate `ins_len` value to guarantee the space. However, `btrfs_insert_empty_item` calls `btrfs_insert_empty_items`, and here's the size: `btrfs_search_slot` *is* called with `ins_len = data_size + sizeof(struct btrfs_item)`.
So, if the key *already* exists, `btrfs_search_slot` returns `-EEXIST`. The question is, does `btrfs_search_slot` *guarantee* that there will be `ins_len` free space in the leaf even if it returns `-EEXIST`? That's what I need to investigate now... let's check `btrfs_search_slot`.
|
| 55/2 |
2026/08/27 01:16 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/btrfs/ctree.c Name:search_leaf]
Results: map[SourceCode:1836: static int search_leaf(struct btrfs_trans_handle *trans,
1837: struct btrfs_root *root,
1838: const struct btrfs_key *key,
1839: struct btrfs_path *path,
1840: int ins_len,
1841: int prev_cmp)
1842: {
1843: struct extent_buffer *leaf = path->nodes[0];
1844: int leaf_free_space = -1;
1845: int search_low_slot = 0;
1846: int ret;
1847: bool do_bin_search = true;
1848:
1849: /*
1850: * If we are doing an insertion, the leaf has enough free space and the
1851: * destination slot for the key is not slot 0, then we can unlock our
1852: * write lock on the parent, and any other upper nodes, before doing the
1853: * binary search on the leaf (with search_for_key_slot()), allowing other
1854: * tasks to lock the parent and any other upper nodes.
1855: */
1856: if (ins_len > 0) {
1857: /*
1858: * Cache the leaf free space, since we will need it later and it
1859: * will not change until then.
1860: */
1861: leaf_free_space = btrfs_leaf_free_space(leaf);
1862:
1863: /*
1864: * !path->locks[1] means we have a single node tree, the leaf is
1865: * the root of the tree.
1866: */
1867: if (path->locks[1] && leaf_free_space >= ins_len) {
1868: struct btrfs_disk_key first_key;
1869:
1870: ASSERT(btrfs_header_nritems(leaf) > 0);
1871: btrfs_item_key(leaf, &first_key, 0);
1872:
1873: /*
1874: * Doing the extra comparison with the first key is cheap,
1875: * taking into account that the first key is very likely
1876: * already in a cache line because it immediately follows
1877: * the extent buffer's header and we have recently accessed
1878: * the header's level field.
1879: */
1880: ret = btrfs_comp_keys(&first_key, key);
1881: if (ret < 0) {
1882: /*
1883: * The first key is smaller than the key we want
1884: * to insert, so we are safe to unlock all upper
1885: * nodes and we have to do the binary search.
1886: *
1887: * We do use btrfs_unlock_up_safe() and not
1888: * unlock_up() because the later does not unlock
1889: * nodes with a slot of 0 - we can safely unlock
1890: * any node even if its slot is 0 since in this
1891: * case the key does not end up at slot 0 of the
1892: * leaf and there's no need to split the leaf.
1893: */
1894: btrfs_unlock_up_safe(path, 1);
1895: search_low_slot = 1;
1896: } else {
1897: /*
1898: * The first key is >= then the key we want to
1899: * insert, so we can skip the binary search as
1900: * the target key will be at slot 0.
1901: *
1902: * We can not unlock upper nodes when the key is
1903: * less than the first key, because we will need
1904: * to update the key at slot 0 of the parent node
1905: * and possibly of other upper nodes too.
1906: * If the key matches the first key, then we can
1907: * unlock all the upper nodes, using
1908: * btrfs_unlock_up_safe() instead of unlock_up()
1909: * as stated above.
1910: */
1911: if (ret == 0)
1912: btrfs_unlock_up_safe(path, 1);
1913: /*
1914: * ret is already 0 or 1, matching the result of
1915: * a btrfs_bin_search() call, so there is no need
1916: * to adjust it.
1917: */
1918: do_bin_search = false;
1919: path->slots[0] = 0;
1920: }
1921: }
1922: }
1923:
1924: if (do_bin_search) {
1925: ret = search_for_key_slot(leaf, search_low_slot, key,
1926: prev_cmp, &path->slots[0]);
1927: if (ret < 0)
1928: return ret;
1929: }
1930:
1931: if (ins_len > 0) {
1932: /*
1933: * Item key already exists. In this case, if we are allowed to
1934: * insert the item (for example, in dir_item case, item key
1935: * collision is allowed), it will be merged with the original
1936: * item. Only the item size grows, no new btrfs item will be
1937: * added. If search_for_extension is not set, ins_len already
1938: * accounts the size btrfs_item, deduct it here so leaf space
1939: * check will be correct.
1940: */
1941: if (ret == 0 && !path->search_for_extension) {
1942: ASSERT(ins_len >= sizeof(struct btrfs_item));
1943: ins_len -= sizeof(struct btrfs_item);
1944: }
1945:
1946: ASSERT(leaf_free_space >= 0);
1947:
1948: if (leaf_free_space < ins_len) {
1949: int ret2;
1950:
1951: ret2 = split_leaf(trans, root, key, path, ins_len, (ret == 0));
1952: ASSERT(ret2 <= 0);
1953: if (WARN_ON(ret2 > 0))
1954: ret2 = -EUCLEAN;
1955: if (ret2)
1956: ret = ret2;
1957: }
1958: }
1959:
1960: return ret;
1961: }
SourceFile:fs/btrfs/ctree.c]
|
| 56/2 |
2026/08/27 01:16 |
llm |
expert |
0m
Model:
Tokens:
input: 38183
output: 36
thoughts: 0
|
| 57/2 |
2026/08/27 01:16 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/btrfs/ctree.c Name:split_leaf]
Results: map[SourceCode:3663: /*
3664: * split the path's leaf in two, making sure there is at least data_size
3665: * available for the resulting leaf level of the path.
3666: *
3667: * returns 0 if all went well and < 0 on failure.
3668: */
3669: static noinline int split_leaf(struct btrfs_trans_handle *trans,
3670: struct btrfs_root *root,
3671: const struct btrfs_key *ins_key,
3672: struct btrfs_path *path, int data_size,
3673: bool extend)
3674: {
3675: struct btrfs_disk_key disk_key;
3676: struct extent_buffer *l;
3677: u32 nritems;
3678: int mid;
3679: int slot;
3680: struct extent_buffer *right;
3681: struct btrfs_fs_info *fs_info = root->fs_info;
3682: int ret = 0;
3683: int wret;
3684: int split;
3685: int num_doubles = 0;
3686: bool tried_avoid_double = false;
3687:
3688: l = path->nodes[0];
3689: slot = path->slots[0];
3690: if (extend && data_size + btrfs_item_size(l, slot) +
3691: sizeof(struct btrfs_item) > BTRFS_LEAF_DATA_SIZE(fs_info))
3692: return -EOVERFLOW;
3693:
3694: /* first try to make some room by pushing left and right */
3695: if (data_size && path->nodes[1]) {
3696: int space_needed = data_size;
3697:
3698: if (slot < btrfs_header_nritems(l))
3699: space_needed -= btrfs_leaf_free_space(l);
3700:
3701: wret = push_leaf_right(trans, root, path, space_needed,
3702: space_needed, 0, 0);
3703: if (wret < 0)
3704: return wret;
3705: if (wret) {
3706: space_needed = data_size;
3707: if (slot > 0)
3708: space_needed -= btrfs_leaf_free_space(l);
3709: wret = push_leaf_left(trans, root, path, space_needed,
3710: space_needed, 0, (u32)-1);
3711: if (wret < 0)
3712: return wret;
3713: }
3714: l = path->nodes[0];
3715:
3716: /* did the pushes work? */
3717: if (btrfs_leaf_free_space(l) >= data_size)
3718: return 0;
3719: }
3720:
3721: if (!path->nodes[1]) {
3722: ret = insert_new_root(trans, root, path, 1);
3723: if (ret)
3724: return ret;
3725: }
3726: again:
3727: split = 1;
3728: l = path->nodes[0];
3729: slot = path->slots[0];
3730: nritems = btrfs_header_nritems(l);
3731: mid = (nritems + 1) / 2;
3732:
3733: if (mid <= slot) {
3734: if (nritems == 1 ||
3735: leaf_space_used(l, mid, nritems - mid) + data_size >
3736: BTRFS_LEAF_DATA_SIZE(fs_info)) {
3737: if (slot >= nritems) {
3738: split = 0;
3739: } else {
3740: mid = slot;
3741: if (mid != nritems &&
3742: leaf_space_used(l, mid, nritems - mid) +
3743: data_size > BTRFS_LEAF_DATA_SIZE(fs_info)) {
3744: if (data_size && !tried_avoid_double)
3745: goto push_for_double;
3746: split = 2;
3747: }
3748: }
3749: }
3750: } else {
3751: if (leaf_space_used(l, 0, mid) + data_size >
3752: BTRFS_LEAF_DATA_SIZE(fs_info)) {
3753: if (!extend && data_size && slot == 0) {
3754: split = 0;
3755: } else if ((extend || !data_size) && slot == 0) {
3756: mid = 1;
3757: } else {
3758: mid = slot;
3759: if (mid != nritems &&
3760: leaf_space_used(l, mid, nritems - mid) +
3761: data_size > BTRFS_LEAF_DATA_SIZE(fs_info)) {
3762: if (data_size && !tried_avoid_double)
3763: goto push_for_double;
3764: split = 2;
3765: }
3766: }
3767: }
3768: }
3769:
3770: if (split == 0)
3771: btrfs_cpu_key_to_disk(&disk_key, ins_key);
3772: else
3773: btrfs_item_key(l, &disk_key, mid);
3774:
3775: /*
3776: * We have to about BTRFS_NESTING_NEW_ROOT here if we've done a double
3777: * split, because we're only allowed to have MAX_LOCKDEP_SUBCLASSES
3778: * subclasses, which is 8 at the time of this patch, and we've maxed it
3779: * out. In the future we could add a
3780: * BTRFS_NESTING_SPLIT_THE_SPLITTENING if we need to, but for now just
3781: * use BTRFS_NESTING_NEW_ROOT.
3782: */
3783: right = btrfs_alloc_tree_block(trans, root, 0, btrfs_root_id(root),
3784: &disk_key, 0, l->start, 0, 0,
3785: num_doubles ? BTRFS_NESTING_NEW_ROOT :
3786: BTRFS_NESTING_SPLIT);
3787: if (IS_ERR(right))
3788: return PTR_ERR(right);
3789:
3790: root_add_used_bytes(root);
3791:
3792: if (split == 0) {
3793: if (mid <= slot) {
3794: btrfs_set_header_nritems(right, 0);
3795: ret = insert_ptr(trans, path, &disk_key,
3796: right->start, path->slots[1] + 1, 1);
3797: if (ret < 0) {
3798: btrfs_tree_unlock(right);
3799: free_extent_buffer(right);
3800: return ret;
3801: }
3802: btrfs_tree_unlock(path->nodes[0]);
3803: free_extent_buffer(path->nodes[0]);
3804: path->nodes[0] = right;
3805: path->slots[0] = 0;
3806: path->slots[1] += 1;
3807: } else {
3808: btrfs_set_header_nritems(right, 0);
3809: ret = insert_ptr(trans, path, &disk_key,
3810: right->start, path->slots[1], 1);
3811: if (ret < 0) {
3812: btrfs_tree_unlock(right);
3813: free_extent_buffer(right);
3814: return ret;
3815: }
3816: btrfs_tree_unlock(path->nodes[0]);
3817: free_extent_buffer(path->nodes[0]);
3818: path->nodes[0] = right;
3819: path->slots[0] = 0;
3820: if (path->slots[1] == 0)
3821: fixup_low_keys(trans, path, &disk_key, 1);
3822: }
3823: /*
3824: * We create a new leaf 'right' for the required ins_len and
3825: * we'll do btrfs_mark_buffer_dirty() on this leaf after copying
3826: * the content of ins_len to 'right'.
3827: */
3828: return ret;
3829: }
3830:
3831: ret = copy_for_split(trans, path, l, right, slot, mid, nritems);
3832: if (ret < 0) {
3833: btrfs_tree_unlock(right);
3834: free_extent_buffer(right);
3835: return ret;
3836: }
3837:
3838: if (split == 2) {
3839: BUG_ON(num_doubles != 0);
3840: num_doubles++;
3841: goto again;
3842: }
3843:
3844: return 0;
3845:
3846: push_for_double:
3847: push_for_double_split(trans, root, path, data_size);
3848: tried_avoid_double = true;
3849: if (btrfs_leaf_free_space(path->nodes[0]) >= data_size)
3850: return 0;
3851: goto again;
3852: }
SourceFile:fs/btrfs/ctree.c]
|
| 58/2 |
2026/08/27 01:16 |
llm |
expert |
0m
Model:
Tokens:
input: 44489
output: 46
thoughts: 1600
Thoughts: **Analyzing a Btrfs `-EOVERFLOW` Mystery**
Okay, let's break this down. My initial thought was: why is `split_leaf` returning `-EOVERFLOW` when it seems like the check is already done by `btrfs_check_dir_item_collision`? If the collision check fails due to size, the new inode should return early, and `btrfs_insert_dir_item` should not trigger the error. The sizes checked are the same, so there seems to be a redundancy here.
Then the thought crossed my mind if `btrfs_check_dir_item_collision` is even always called, but it is. However, the order of operation is a potential issue. `btrfs_check_dir_item_collision` is called BEFORE the transaction starts, and the actual directory insertion happens during the transaction. It's possible for another thread to insert an item *between* the collision check and the insertion, causing a subsequent overflow. But if that happened, surely `-EOVERFLOW` would be handled in the call to `btrfs_add_link`? And it is! Specifically, `-EOVERFLOW` is treated as a retry. So the error is *not* `-EOVERFLOW`.
Now I'm thinking about other possible errors. Could `btrfs_insert_dir_item` return `-ENOSPC`? It could, through a call to `btrfs_cow_block`, and that would happen if there wasn't enough space for a new block. But we *did* start a transaction with a certain number of items reserved, so this should be a bug. If there is a bug, the stack trace would print out.
What about filesystem corruption? Could `-EUCLEAN` be returned? Yes, `btrfs_search_slot` can return `-EUCLEAN` if there is a corruption, and the stack trace would print out. The fact that the syzbot report doesn't mention corruption is a strong clue that it is not the issue, and the report is not tainted, suggesting that corruption is not happening during the repro.
So, I'm left to look for other errors. What about `-EINVAL`? Let's go back and examine `btrfs_insert_delayed_dir_index` again.
|
| 59/2 |
2026/08/27 01:16 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/btrfs/delayed-inode.c Name:btrfs_insert_delayed_dir_index]
Results: map[SourceCode:1472: /* Will return 0, -ENOMEM or -EEXIST (index number collision, unexpected). */
1473: int btrfs_insert_delayed_dir_index(struct btrfs_trans_handle *trans,
1474: const char *name, int name_len,
1475: struct btrfs_inode *dir,
1476: const struct btrfs_disk_key *disk_key, u8 flags,
1477: u64 index)
1478: {
1479: struct btrfs_fs_info *fs_info = trans->fs_info;
1480: const unsigned int leaf_data_size = BTRFS_LEAF_DATA_SIZE(fs_info);
1481: struct btrfs_delayed_node *delayed_node;
1482: struct btrfs_ref_tracker delayed_node_tracker;
1483: struct btrfs_delayed_item *delayed_item;
1484: struct btrfs_dir_item *dir_item;
1485: bool reserve_leaf_space;
1486: u32 data_len;
1487: int ret;
1488:
1489: delayed_node = btrfs_get_or_create_delayed_node(dir, &delayed_node_tracker);
1490: if (IS_ERR(delayed_node))
1491: return PTR_ERR(delayed_node);
1492:
1493: delayed_item = btrfs_alloc_delayed_item(sizeof(*dir_item) + name_len,
1494: delayed_node,
1495: BTRFS_DELAYED_INSERTION_ITEM);
1496: if (!delayed_item) {
1497: ret = -ENOMEM;
1498: goto release_node;
1499: }
1500:
1501: delayed_item->index = index;
1502:
1503: dir_item = (struct btrfs_dir_item *)delayed_item->data;
1504: dir_item->location = *disk_key;
1505: btrfs_set_stack_dir_transid(dir_item, trans->transid);
1506: btrfs_set_stack_dir_data_len(dir_item, 0);
1507: btrfs_set_stack_dir_name_len(dir_item, name_len);
1508: btrfs_set_stack_dir_flags(dir_item, flags);
1509: memcpy((char *)(dir_item + 1), name, name_len);
1510:
1511: data_len = delayed_item->data_len + sizeof(struct btrfs_item);
1512:
1513: mutex_lock(&delayed_node->mutex);
1514:
1515: /*
1516: * First attempt to insert the delayed item. This is to make the error
1517: * handling path simpler in case we fail (-EEXIST). There's no risk of
1518: * any other task coming in and running the delayed item before we do
1519: * the metadata space reservation below, because we are holding the
1520: * delayed node's mutex and that mutex must also be locked before the
1521: * node's delayed items can be run.
1522: */
1523: ret = __btrfs_add_delayed_item(delayed_node, delayed_item);
1524: if (unlikely(ret)) {
1525: btrfs_err(trans->fs_info,
1526: "error adding delayed dir index item, name: %.*s, index: %llu, root: %llu, dir: %llu, dir->index_cnt: %llu, delayed_node->index_cnt: %llu, error: %pe",
1527: name_len, name, index, btrfs_root_id(delayed_node->root),
1528: delayed_node->inode_id, dir->index_cnt,
1529: delayed_node->index_cnt, ERR_PTR(ret));
1530: btrfs_release_delayed_item(delayed_item);
1531: btrfs_release_dir_index_item_space(trans);
1532: mutex_unlock(&delayed_node->mutex);
1533: goto release_node;
1534: }
1535:
1536: if (delayed_node->index_item_leaves == 0 ||
1537: delayed_node->curr_index_batch_size + data_len > leaf_data_size) {
1538: delayed_node->curr_index_batch_size = data_len;
1539: reserve_leaf_space = true;
1540: } else {
1541: delayed_node->curr_index_batch_size += data_len;
1542: reserve_leaf_space = false;
1543: }
1544:
1545: if (reserve_leaf_space) {
1546: ret = btrfs_delayed_item_reserve_metadata(trans, delayed_item);
1547: /*
1548: * Space was reserved for a dir index item insertion when we
1549: * started the transaction, so getting a failure here should be
1550: * impossible.
1551: */
1552: if (WARN_ON(ret)) {
1553: btrfs_release_delayed_item(delayed_item);
1554: mutex_unlock(&delayed_node->mutex);
1555: goto release_node;
1556: }
1557:
1558: delayed_node->index_item_leaves++;
1559: } else {
1560: btrfs_release_dir_index_item_space(trans);
1561: }
1562: mutex_unlock(&delayed_node->mutex);
1563:
1564: release_node:
1565: btrfs_release_delayed_node(delayed_node, &delayed_node_tracker);
1566: return ret;
1567: }
SourceFile:fs/btrfs/delayed-inode.c]
|
| 60/2 |
2026/08/27 01:16 |
llm |
expert |
0m
Model:
Tokens:
input: 43523
output: 61
thoughts: 0
|
| 61/2 |
2026/08/27 01:16 |
tool |
git-log |
0m
Args: map[CodeRegexp: Count:10 MessageRegexps:[btrfs_insert_dir_item] PathPrefix: Since: SourcePath: SymbolName:]
Results: map[Output:05aa02438223 btrfs: remove pointless BUG_ON() when creating snapshot
eb96e221937a btrfs: fix unwritten extent buffer after snapshotting a new subvolume
8e7f82deb0c0 btrfs: fix race between reading a directory and adding entries to it
314c4cd6d9e6 btrfs: Fix build error while LIBCRC32C is module
1690dd41e0cb btrfs: improve error handling of btrfs_add_link
684572df9401 btrfs: Remove root parameter from btrfs_insert_dir_item
8e7611cf3876 btrfs: Make btrfs_insert_dir_item take btrfs_inode
6f5405bc2ee0 powerpc: use _GLOBAL_TOC for memmove
fe66a05a0679 Btrfs: improve error handling for btrfs_insert_dir_item callers
199c36eaa950 Btrfs: fix BUG_ON() caused by ENOSPC when relocating space
]
|
| 62/2 |
2026/08/27 01:16 |
llm |
expert |
0m
Model:
Tokens:
input: 48713
output: 69
thoughts: 1106
Thoughts: **Analyzing a Btrfs Syzbot Report**
Okay, let's break this down. First, I see `btrfs_insert_delayed_dir_index` potentially returning `-EEXIST` if a lower-level function already encountered an existing entry. However, `btrfs_add_link` handles `-EEXIST`, so that's not the issue here. `btrfs_insert_delayed_dir_index` can also return `-ENOSPC` if metadata reservation fails. A failure there would trigger `WARN_ON`, but the report only shows *one* warning. Since `WARN_ON` typically causes a report, a metadata failure is unlikely since two warnings would probably be present.
Let's think. If `-ENOSPC` occurs, `btrfs_abort_transaction` is called, and a warning is triggered. The syzbot report is at `fs/btrfs/inode.c:6931`, so a failure during delayed item reservation isn't the problem. If it was, we would see the delayed-inode warning, not the inode one.
Could `btrfs_insert_dir_item` return `-EUCLEAN`? That seems like a possibility, especially if the file system is corrupted. Syzbot runs with a newly created filesystem, but wait, it *can* mount corrupted images. If the image is corrupted, `btrfs_search_slot` could return `-EUCLEAN`, leading to the transaction abort. And yes, in that case, `btrfs_abort_should_print_stack(-EUCLEAN)` returns true, and the warning at `fs/btrfs/inode.c:6931` would be printed!
Is this a bug? It is, but it's a known behavior when a btree is corrupted during a transaction! Btrfs aborts the transaction to prevent further damage. It's a warning, but itβs not really exploitable; it's just a transaction abort (DoS). A malicious filesystem could trigger this. An unprivileged user could, if they can mount a malicious filesystem.
But wait, if it's just a corrupted filesystem, it's standard. Let's dig deeper. What if `btrfs_insert_dir_item` returns `-ENOSPC` from `btrfs_insert_empty_items`? That function calls `btrfs_search_slot`, which in turn calls `btrfs_cow_block`. `btrfs_cow_block` *can* return `-ENOSPC`. If that happens, then `btrfs_insert_dir_item` returns `-ENOSPC` and so transaction is aborted. This would also trigger the warning at `fs/btrfs/inode.c:6931`.
Why would `btrfs_cow_block` return `-ENOSPC`? Because the filesystem is full, and the transaction reservation was insufficient. *That* is a btrfs bug, an `ENOSPC` abort! It's still just a denial-of-service, it aborts the transaction and makes the filesystem read-only. An unprivileged user can trigger this, by filling the filesystem and performing operations that trigger the insufficient reservation. But syzbot rarely fills the filesystem up completely unless it's a specific test. I should check for any recent fixes addressing this.
|
| 63/2 |
2026/08/27 01:17 |
tool |
git-log |
0m
Args: map[CodeRegexp: Count:10 MessageRegexps:[btrfs_add_link btrfs_abort_transaction] PathPrefix: Since: SourcePath: SymbolName:]
Results: map[Output:3f757b56f1c4 btrfs: unfold transaction aborts at btrfs_create_new_inode()
]
|
| 64/2 |
2026/08/27 01:17 |
llm |
expert |
0m
Model:
Tokens:
input: 50665
output: 27
thoughts: 19
Thoughts: Okay, here's my interpretation of that request, formatted as requested:
**Checking Commit 3f757b56f1c4**
Alright, let's take a look at commit `3f757b56f1c4`. I need to understand what changed there. From a cursory glance, I need to understand the impact of the changes in this commit. First I need to consider which files were modified and what the diffs look like. What were the original assumptions and what are the new assumptions being made in this code? This is where the real work begins. I'm already thinking about potential regressions, performance implications, and if the changes adhere to the style guide, of course. Are there unit tests associated with this? If not, that's a red flag, and I'll need to dig deeper. I'll need to consider the context of the larger codebase too, what parts are touched, what new dependencies have been introduced. Ultimately, I need to quickly ascertain the purpose and the quality of this commit. Let's get to work!
|
| 65/2 |
2026/08/27 01:17 |
tool |
git-show |
0m
Args:
Results: map[Output:commit 3f757b56f1c4579fe32b810bce1d39f202964412
Author: Filipe Manana <fdmanana@suse.com>
Date: Fri May 16 17:07:40 2025 +0100
btrfs: unfold transaction aborts at btrfs_create_new_inode()
Instead of having a common btrfs_abort_transaction() call for when either
btrfs_orphan_add() failed or when btrfs_add_link() failed, move the
btrfs_abort_transaction() to happen immediately after each one of those
calls, so that when analysing a stack trace with a transaction abort we
know which call failed.
Reviewed-by: Boris Burkov <boris@bur.io>
Reviewed-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.cz>
Signed-off-by: David Sterba <dsterba@suse.com>
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index fc66872b4c74..bb938d5c0c9b 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -6610,13 +6610,17 @@ int btrfs_create_new_inode(struct btrfs_trans_handle *trans,
if (args->orphan) {
ret = btrfs_orphan_add(trans, BTRFS_I(inode));
+ if (ret) {
+ btrfs_abort_transaction(trans, ret);
+ goto discard;
+ }
} else {
ret = btrfs_add_link(trans, BTRFS_I(dir), BTRFS_I(inode), name,
0, BTRFS_I(inode)->dir_index);
- }
- if (ret) {
- btrfs_abort_transaction(trans, ret);
- goto discard;
+ if (ret) {
+ btrfs_abort_transaction(trans, ret);
+ goto discard;
+ }
}
return 0;
]
|
| 66/2 |
2026/08/27 01:17 |
llm |
expert |
0m
Model:
Tokens:
input: 51575
output: 38
thoughts: 144
Thoughts: **Analyzing the Btrfs Commit and Bug Report**
Okay, so this commit shifted the abort call to right after `btrfs_add_link`. Interesting. But hang on, the bug report... right, it's warning about something specific within `btrfs_add_link` itself! The report pinpoints the warning: `fs/btrfs/inode.c:6931 at btrfs_add_link+0x932/0xa50`. That's not in `btrfs_create_new_inode` like I initially thought. It's *inside* `btrfs_add_link`. Time to dive into the code. I need to examine `fs/btrfs/inode.c` around line 6931 within the `btrfs_add_link` function. That's where the problem likely resides. Let's see what's happening in there.
|
| 67/2 |
2026/08/27 01:17 |
tool |
read-file |
0m
Args: map[File:fs/btrfs/inode.c FirstLine:6920 LineCount:20]
Results: map[Contents:6920: }
6921:
6922: /* Nothing to clean up yet */
6923: if (ret)
6924: return ret;
6925:
6926: ret = btrfs_insert_dir_item(trans, name, parent_inode, &key,
6927: btrfs_inode_type(inode), index);
6928: if (ret == -EEXIST || ret == -EOVERFLOW)
6929: goto fail_dir_item;
6930: else if (unlikely(ret)) {
6931: btrfs_abort_transaction(trans, ret);
6932: return ret;
6933: }
6934:
6935: btrfs_i_size_write(parent_inode, parent_inode->vfs_inode.i_size +
6936: name->len * 2);
6937: inode_inc_iversion(&parent_inode->vfs_inode);
6938: update_time_after_link_or_unlink(parent_inode);
6939:
]
|
| 68/2 |
2026/08/27 01:17 |
llm |
expert |
0m
Model:
Tokens:
input: 52280
output: 65
thoughts: 111
Thoughts: **Debugging a Btrfs Issue**
Okay, so the warning is specifically triggered at line 6931, the `btrfs_abort_transaction(trans, ret);` call *within* `btrfs_add_link`. That pinpoints it pretty well. It means `btrfs_insert_dir_item` returned an error code, and crucially, it wasn't either `-EEXIST` (the file already exists) or `-EOVERFLOW` (the directory entry is too large). So, we have an unexpected failure mode there. I need to figure out *what* this error code is. My next move is clear: I need to quickly scan the commit history. Let's see if there are any recent commits addressing failures, or even more specifically, any that touch both `btrfs_add_link` *and* `btrfs_insert_dir_item`. Anything related to those functions failing would be a good lead.
|
| 69/2 |
2026/08/27 01:17 |
tool |
git-log |
0m
Args: map[CodeRegexp: Count:10 MessageRegexps:[btrfs_insert_dir_item abort] PathPrefix: Since: SourcePath: SymbolName:]
Results: map[Output:05aa02438223 btrfs: remove pointless BUG_ON() when creating snapshot
8e7f82deb0c0 btrfs: fix race between reading a directory and adding entries to it
1690dd41e0cb btrfs: improve error handling of btrfs_add_link
]
|
| 70/2 |
2026/08/27 01:17 |
llm |
expert |
0m
Model:
Tokens:
input: 52752
output: 25
thoughts: 0
|
| 71/2 |
2026/08/27 01:18 |
tool |
git-show |
0m
Args:
Results: map[Output:commit 1690dd41e0cb1dade80850ed8a3eb0121b96d22f
Author: Johannes Thumshirn <jthumshirn@suse.de>
Date: Wed Dec 12 15:14:17 2018 +0100
btrfs: improve error handling of btrfs_add_link
In the error handling block, err holds the return value of either
btrfs_del_root_ref() or btrfs_del_inode_ref() but it hasn't been checked
since it's introduction with commit fe66a05a0679 (Btrfs: improve error
handling for btrfs_insert_dir_item callers) in 2012.
If the error handling in the error handling fails, there's not much left
to do and the abort either happened earlier in the callees or is
necessary here.
So if one of btrfs_del_root_ref() or btrfs_del_inode_ref() failed, abort
the transaction, but still return the original code of the failure
stored in 'ret' as this will be reported to the user.
Signed-off-by: Johannes Thumshirn <jthumshirn@suse.de>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index c5c27d5457cc..3d29505971fe 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -6359,14 +6359,19 @@ int btrfs_add_link(struct btrfs_trans_handle *trans,
err = btrfs_del_root_ref(trans, key.objectid,
root->root_key.objectid, parent_ino,
&local_index, name, name_len);
-
+ if (err)
+ btrfs_abort_transaction(trans, err);
} else if (add_backref) {
u64 local_index;
int err;
err = btrfs_del_inode_ref(trans, root, name, name_len,
ino, parent_ino, &local_index);
+ if (err)
+ btrfs_abort_transaction(trans, err);
}
+
+ /* Return the original error code */
return ret;
}
]
|
| 72/2 |
2026/08/27 01:18 |
llm |
expert |
0m
Model:
Tokens:
input: 53479
output: 46
thoughts: 296
Thoughts: **Analyzing Error Handling in btrfs Directory Insertion**
Okay, so I'm looking at this `btrfs_insert_dir_item` function, and I see it can potentially return `-EUCLEAN`, which makes sense because tree corruption could be a factor here. But, I'm trying to trace the potential error paths, and I'm wondering if `-EEXIST` is possible from `btrfs_insert_delayed_dir_index`. If that function *does* return `-EEXIST`, then `btrfs_insert_dir_item` just passes that along. Good, because I see that `btrfs_add_link` handles `-EEXIST`.
Now, what about the more critical errors? What if `btrfs_insert_delayed_dir_index` returns `-ENOMEM`? If that happens, `btrfs_abort_should_print_stack(-ENOMEM)` returns false, so there won't be a warning. That's probably correct behavior, but I'll make a note of it.
Okay, let's look at `-ENOSPC`. If `btrfs_insert_delayed_dir_index` returns `-ENOSPC`, that's interesting. I see now that `btrfs_delayed_item_reserve_metadata` is called inside it and that can return `-ENOSPC`, which, in turn, triggers a warning inside of `btrfs_insert_delayed_dir_index`. Ah, I see that this metadata reservation function is the key here.
Wait, `btrfs_delayed_item_reserve_metadata` can return either `-ENOSPC` or `-EDQUOT`! If it returns `-EDQUOT`, that also triggers a warning inside `btrfs_insert_delayed_dir_index`. I should probably refresh my memory and check the exact definition of `btrfs_delayed_item_reserve_metadata`.
|
| 73/2 |
2026/08/27 01:18 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/btrfs/delayed-inode.c Name:btrfs_delayed_item_reserve_metadata]
Results: map[SourceCode: 498: static int btrfs_delayed_item_reserve_metadata(struct btrfs_trans_handle *trans,
499: struct btrfs_delayed_item *item)
500: {
501: struct btrfs_block_rsv *src_rsv;
502: struct btrfs_block_rsv *dst_rsv;
503: struct btrfs_fs_info *fs_info = trans->fs_info;
504: u64 num_bytes;
505: int ret;
506:
507: if (!trans->bytes_reserved)
508: return 0;
509:
510: src_rsv = trans->block_rsv;
511: dst_rsv = &fs_info->delayed_block_rsv;
512:
513: num_bytes = btrfs_calc_insert_metadata_size(fs_info, 1);
514:
515: /*
516: * Here we migrate space rsv from transaction rsv, since have already
517: * reserved space when starting a transaction. So no need to reserve
518: * qgroup space here.
519: */
520: ret = btrfs_block_rsv_migrate(src_rsv, dst_rsv, num_bytes, true);
521: if (!ret) {
522: trace_btrfs_space_reservation(fs_info, "delayed_item",
523: item->delayed_node->inode_id,
524: num_bytes, 1);
525: /*
526: * For insertions we track reserved metadata space by accounting
527: * for the number of leaves that will be used, based on the delayed
528: * node's curr_index_batch_size and index_item_leaves fields.
529: */
530: if (item->type == BTRFS_DELAYED_DELETION_ITEM)
531: item->bytes_reserved = num_bytes;
532: }
533:
534: return ret;
535: }
SourceFile:fs/btrfs/delayed-inode.c]
|
| 74/2 |
2026/08/27 01:18 |
llm |
expert |
0m
Model:
Tokens:
input: 54888
output: 42
thoughts: 623
Thoughts: **My Analysis of the Btrfs `-ENOSPC` Issue**
Okay, so here's what I've figured out. We've got a situation where a syzbot report is flagging a warning, and it's got me digging into the `btrfs` code. The key is this `-ENOSPC` error and this is what I think.
`btrfs_block_rsv_migrate` can return `-ENOSPC` if the source reserve runs dry, which can then bubble up to `btrfs_delayed_item_reserve_metadata` returning `-ENOSPC`. That, in turn, *should* trigger `WARN_ON(ret)` within `btrfs_insert_delayed_dir_index`. But the syzbot report isn't showing a warning at that location; it's showing a warning at `fs/btrfs/inode.c:6931`. That doesn't make sense...
My hunch is that the `-ENOSPC` is coming from *elsewhere* within the `btrfs_insert_dir_item` sequence. I started by looking at `insert_with_overflow`. It calls `btrfs_insert_empty_item`, then `btrfs_insert_empty_items`. This leads to `btrfs_search_slot` and ultimately `btrfs_cow_block`. Here's where it clicks: if `btrfs_cow_block` returns `-ENOSPC`, the code *doesn't* trigger a warning within `btrfs_search_slot`. It just returns the error. That `-ENOSPC` gets passed all the way back up to `insert_with_overflow` which then returns `ERR_PTR(-ENOSPC)`. `btrfs_insert_dir_item` receives the `-ENOSPC` and returns it.
This `-ENOSPC` is then handled by `btrfs_add_link` which calls `btrfs_abort_transaction(trans, -ENOSPC)`. *That's* the crucial call! `btrfs_abort_transaction` then triggers `WARN_ON(btrfs_abort_should_print_stack(-ENOSPC))`. Since `btrfs_abort_should_print_stack(-ENOSPC)` returns true, it prints the warning, and it prints it at the location `fs/btrfs/inode.c:6931`. Exactly what the syzbot report is showing! So, that's it: the bug is in `btrfs_cow_block`.
The root cause then, is `btrfs_cow_block` returning `-ENOSPC` during `btrfs_insert_dir_item`. Why would that happen? Well, the transaction block reserve (`trans->block_rsv`) could be out of space, the global block reserve could be empty, or the filesystem might be genuinely full, and we can't allocate a new tree block. But `btrfs_create_new_inode` *should* have already started a transaction with `trans_num_items` reserved. I need to check `btrfs_new_inode_prepare` now to see how many items are reserved during inode creation to ensure enough space.
|
| 75/2 |
2026/08/27 01:18 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/btrfs/inode.c Name:btrfs_new_inode_prepare]
Results: map[SourceCode:6523: int btrfs_new_inode_prepare(struct btrfs_new_inode_args *args,
6524: unsigned int *trans_num_items)
6525: {
6526: struct inode *dir = args->dir;
6527: struct inode *inode = args->inode;
6528: int ret;
6529:
6530: if (!args->orphan) {
6531: ret = fscrypt_setup_filename(dir, &args->dentry->d_name, 0,
6532: &args->fname);
6533: if (ret)
6534: return ret;
6535: }
6536:
6537: ret = posix_acl_create(dir, &inode->i_mode, &args->default_acl, &args->acl);
6538: if (ret) {
6539: fscrypt_free_filename(&args->fname);
6540: return ret;
6541: }
6542:
6543: /* 1 to add inode item */
6544: *trans_num_items = 1;
6545: /* 1 to add compression property */
6546: if (BTRFS_I(dir)->prop_compress)
6547: (*trans_num_items)++;
6548: /* 1 to add default ACL xattr */
6549: if (args->default_acl)
6550: (*trans_num_items)++;
6551: /* 1 to add access ACL xattr */
6552: if (args->acl)
6553: (*trans_num_items)++;
6554: #ifdef CONFIG_SECURITY
6555: /* 1 to add LSM xattr */
6556: if (dir->i_security)
6557: (*trans_num_items)++;
6558: #endif
6559: if (args->orphan) {
6560: /* 1 to add orphan item */
6561: (*trans_num_items)++;
6562: } else {
6563: /*
6564: * 1 to add dir item
6565: * 1 to add dir index
6566: * 1 to update parent inode item
6567: *
6568: * No need for 1 unit for the inode ref item because it is
6569: * inserted in a batch together with the inode item at
6570: * btrfs_create_new_inode().
6571: */
6572: *trans_num_items += 3;
6573: }
6574: return 0;
6575: }
SourceFile:fs/btrfs/inode.c]
|
| 76/2 |
2026/08/27 01:18 |
llm |
expert |
0m
Model:
Tokens:
input: 57053
output: 37
thoughts: 813
Thoughts: **Analyzing a Btrfs ENOSPC Transaction Abort**
Okay, so I'm looking at this code and trying to figure out why we're hitting an `-ENOSPC` error in a Btrfs transaction. It looks like we're reserving a certain number of items, `trans_num_items`, for the transaction. In a typical file creation, that's inode, directory item, directory index, parent inode, and potentially ACLs and xattrs. Then, `btrfs_start_transaction` is called, which should ensure enough space for these items.
The problem is, if `btrfs_cow_block` returns `-ENOSPC`, the reserved space wasn't enough! I need to understand why. My initial thought is a leaf split, where inserting a directory item causes the leaf to split, requiring more space than was originally calculated. But wait, `btrfs_start_transaction` *should* already account for this, using `btrfs_calc_insert_metadata_size`. This function considers the potential for leaf splits all the way up to the root, so it *should* be sufficient.
So, either there's a bug in the reservation logic, or something else is consuming space unexpectedly. I'll need to check the call chain. `btrfs_check_dir_item_collision` doesn't consume space, neither does `btrfs_insert_inode_locked`. However, `btrfs_insert_empty_items` for the inode and inode reference does. Then there's `btrfs_inode_inherit_props` and `btrfs_init_inode_security`, both consuming space. Finally, `btrfs_add_link` consumes space for the dir item and dir index. If any of these earlier operations consume more space than initially expected, `btrfs_add_link` could run out of space and hit the `-ENOSPC`. This is a classic Btrfs `ENOSPC` transaction abort.
The implications? This is *not* exploitable in the traditional sense, as it just causes a transaction abort, leading to a read-only filesystem. It's a Denial of Service, though. Can an unprivileged user trigger this? Yes! By creating files, setting ACLs, xattrs, and crafting a specific combination that consumes more metadata space than reserved, they *could* trigger the `-ENOSPC` and effectively turn the filesystem read-only.
Hang on, what other errors could `btrfs_insert_dir_item` return? Could it be `-EUCLEAN`, indicating filesystem corruption? Syzbot often uses corrupted images. If `btrfs_search_slot` returns `-EUCLEAN`, then `btrfs_abort_transaction` will be called with `-EUCLEAN`. `btrfs_abort_should_print_stack` would return true and print a warning. This is also a Denial of Service (transaction abort), but it requires a corrupted image. If it's corruption, it's a Malicious Filesystem Trigger; if it's the ENOSPC bug, it's an Unprivileged Trigger.
Let me check the Syzbot report again. It says "Not tainted syzkaller #0 PREEMPT(full)". Normally, with a corrupted image, Syzbot uses `syz_mount_image`, but the call trace is just `openat2` -> `btrfs_create_new_inode`. There's no mount call in the trace, but it could have been mounted earlier in the test. Okay, I need to search the kernel bugzilla and the Syzbot dashboard for similar bugs to get a clearer picture.
|
| 77/2 |
2026/08/27 01:18 |
tool |
grepper |
0m
Args: map[Expression:btrfs_abort_should_print_stack PathPrefix:fs/btrfs/]
Results: map[Output:fs/btrfs/transaction.h=239=static inline void btrfs_clear_skip_qgroup(struct btrfs_trans_handle *trans)
--
fs/btrfs/transaction.h-252- */
fs/btrfs/transaction.h:253:static inline bool btrfs_abort_should_print_stack(int error)
fs/btrfs/transaction.h-254-{
--
fs/btrfs/transaction.h=294=do { \
--
fs/btrfs/transaction.h-300- &((trans)->fs_info->fs_state))) { \
fs/btrfs/transaction.h:301: WARN_ON(btrfs_abort_should_print_stack(__error)); \
fs/btrfs/transaction.h-302- __error = -__error; \
]
|
| 78/2 |
2026/08/27 01:18 |
llm |
expert |
0m
Model:
Tokens:
input: 50972
output: 69
thoughts: 0
|
| 79/2 |
2026/08/27 01:18 |
tool |
git-log |
0m
Args: map[CodeRegexp: Count:10 MessageRegexps:[btrfs_abort_transaction btrfs_add_link] PathPrefix: Since: SourcePath: SymbolName:]
Results: map[Output:3f757b56f1c4 btrfs: unfold transaction aborts at btrfs_create_new_inode()
]
|
| 80/2 |
2026/08/27 01:19 |
llm |
expert |
0m
Model:
Tokens:
input: 51086
output: 71
thoughts: 0
|
| 81/2 |
2026/08/27 01:19 |
tool |
git-log |
0m
Args: map[CodeRegexp: Count:10 MessageRegexps:[btrfs_abort_transaction btrfs_insert_dir_item] PathPrefix: Since: SourcePath: SymbolName:]
Results: map[Output:05aa02438223 btrfs: remove pointless BUG_ON() when creating snapshot
]
|
| 82/2 |
2026/08/27 01:19 |
llm |
expert |
0m
Model:
Tokens:
input: 51195
output: 73
thoughts: 0
|
| 83/2 |
2026/08/27 01:19 |
tool |
git-log |
0m
Args: map[CodeRegexp: Count:10 MessageRegexps:[btrfs_abort_transaction btrfs_insert_delayed_dir_index] PathPrefix: Since: SourcePath: SymbolName:]
Results:
|
| 84/2 |
2026/08/27 01:19 |
llm |
expert |
0m
Model:
Tokens:
input: 59234
output: 65
thoughts: 987
Thoughts: **Analyzing Potential Denial of Service in `btrfs_add_link` Failure**
Okay, let's break this down. My initial thought is to figure out the implications of `btrfs_add_link` failing. We know it calls `btrfs_abort_transaction`, the standard Btrfs mechanism for handling transaction errors, which ultimately leads to a read-only filesystem and a warning, potentially a Denial of Service. My key question is: can a regular user trigger this?
If the error is `-ENOSPC`, like due to a reservation bug, then, yes, a user could conceivably hit this by exhausting the reserved space. A corrupted image resulting in `-EUCLEAN` would require a malicious mount. But, what about `-EEXIST` from `btrfs_insert_delayed_dir_index`? Wait, no. `btrfs_add_link` handles `-EEXIST` by jumping to `fail_dir_item` which handles the error. It's only handling `-EEXIST` from `btrfs_insert_dir_item`.
So, `-EEXIST` is a red herring. Let's look at what could happen from `btrfs_insert_dir_item`. Does it return `-EFAULT`, `-EFBIG`, or `-EUCLEAN`? The answer is no for `-EFAULT` and `-EFBIG`, but yes for `-EUCLEAN` due to corruption, got it. What other errors are possible? What about `-EDQUOT`? No, because `btrfs_delayed_item_reserve_metadata` does not return it. It calls `btrfs_block_rsv_migrate` which returns `-ENOSPC`.
Now, looking deeper. `btrfs_delayed_item_reserve_metadata` has a `WARN_ON(ret)`. The syzbot report is crucial here. The warning wasn't at `delayed-inode.c:1552` as I would have expected, but at `inode.c:6931`. This means the `btrfs_delayed_item_reserve_metadata` did NOT fail. The error *must* be coming from `insert_with_overflow` or `btrfs_insert_empty_item`. Those functions ultimately call `btrfs_search_slot`.
`btrfs_search_slot` can return `-EUCLEAN` (corruption), `-ENOSPC` (from `btrfs_cow_block`), or `-EAGAIN`. Importantly, it handles `-EAGAIN` internally. If it returns `-ENOSPC` from `btrfs_cow_block`, there won't be a warning inside `btrfs_search_slot`. This implies that `btrfs_insert_dir_item` *does* return `-ENOSPC`. Therefore, `btrfs_add_link` then aborts the transaction with `-ENOSPC`, triggering the warning at `inode.c:6931`. I can conclude that this is due to an `-ENOSPC` abort.
So, why would `btrfs_cow_block` return `-ENOSPC`? Because the transaction block reserve is empty. And why would it be empty? Likely because the reservation in `btrfs_new_inode_prepare` was insufficient. It's time to check for known bugs related to insufficient reservation in `btrfs_new_inode_prepare`.
|
| 85/2 |
2026/08/27 01:19 |
tool |
git-log |
0m
Args: map[CodeRegexp: Count:10 MessageRegexps:[btrfs_new_inode_prepare reserve] PathPrefix: Since: SourcePath: SymbolName:]
Results: map[Output:3538d68dbd97 btrfs: reserve correct number of items for inode creation
]
|
| 86/2 |
2026/08/27 01:20 |
llm |
expert |
0m
Model:
Tokens:
input: 52126
output: 26
thoughts: 0
|
| 87/2 |
2026/08/27 01:20 |
tool |
git-show |
0m
Args:
Results: map[Output:commit 3538d68dbd97a2f5599bf39aeee47f027417fc39
Author: Omar Sandoval <osandov@fb.com>
Date: Mon Mar 14 18:12:34 2022 -0700
btrfs: reserve correct number of items for inode creation
The various inode creation code paths do not account for the compression
property, POSIX ACLs, or the parent inode item when starting a
transaction. Fix it by refactoring all of these code paths to use a new
function, btrfs_new_inode_prepare(), which computes the correct number
of items. To do so, it needs to know whether POSIX ACLs will be created,
so move the ACL creation into that function. To reduce the number of
arguments that need to be passed around for inode creation, define
struct btrfs_new_inode_args containing all of the relevant information.
btrfs_new_inode_prepare() will also be a good place to set up the
fscrypt context and encrypted filename in the future.
Reviewed-by: Sweet Tea Dorminy <sweettea-kernel@dorminy.me>
Signed-off-by: Omar Sandoval <osandov@fb.com>
Signed-off-by: David Sterba <dsterba@suse.com>
diff --git a/fs/btrfs/acl.c b/fs/btrfs/acl.c
index a6909ec9bc38..548d6a5477b4 100644
--- a/fs/btrfs/acl.c
+++ b/fs/btrfs/acl.c
@@ -55,8 +55,8 @@ struct posix_acl *btrfs_get_acl(struct inode *inode, int type, bool rcu)
return acl;
}
-static int __btrfs_set_acl(struct btrfs_trans_handle *trans,
- struct inode *inode, struct posix_acl *acl, int type)
+int __btrfs_set_acl(struct btrfs_trans_handle *trans, struct inode *inode,
+ struct posix_acl *acl, int type)
{
int ret, size = 0;
const char *name;
@@ -127,35 +127,3 @@ int btrfs_set_acl(struct user_namespace *mnt_userns, struct inode *inode,
inode->i_mode = old_mode;
return ret;
}
-
-int btrfs_init_acl(struct btrfs_trans_handle *trans,
- struct inode *inode, struct inode *dir)
-{
- struct posix_acl *default_acl, *acl;
- int ret = 0;
-
- /* this happens with subvols */
- if (!dir)
- return 0;
-
- ret = posix_acl_create(dir, &inode->i_mode, &default_acl, &acl);
- if (ret)
- return ret;
-
- if (default_acl) {
- ret = __btrfs_set_acl(trans, inode, default_acl,
- ACL_TYPE_DEFAULT);
- posix_acl_release(default_acl);
- }
-
- if (acl) {
- if (!ret)
- ret = __btrfs_set_acl(trans, inode, acl,
- ACL_TYPE_ACCESS);
- posix_acl_release(acl);
- }
-
- if (!default_acl && !acl)
- cache_no_acl(inode);
- return ret;
-}
diff --git a/fs/btrfs/ctree.h b/fs/btrfs/ctree.h
index d8f7479c9d17..768339a3f99d 100644
--- a/fs/btrfs/ctree.h
+++ b/fs/btrfs/ctree.h
@@ -3284,11 +3284,32 @@ int btrfs_start_delalloc_roots(struct btrfs_fs_info *fs_info, long nr,
int btrfs_set_extent_delalloc(struct btrfs_inode *inode, u64 start, u64 end,
unsigned int extra_bits,
struct extent_state **cached_state);
+struct btrfs_new_inode_args {
+ /* Input */
+ struct inode *dir;
+ struct dentry *dentry;
+ struct inode *inode;
+ bool orphan;
+ bool subvol;
+
+ /*
+ * Output from btrfs_new_inode_prepare(), input to
+ * btrfs_create_new_inode().
+ */
+ struct posix_acl *default_acl;
+ struct posix_acl *acl;
+};
+int btrfs_new_inode_prepare(struct btrfs_new_inode_args *args,
+ unsigned int *trans_num_items);
+int btrfs_create_new_inode(struct btrfs_trans_handle *trans,
+ struct btrfs_new_inode_args *args,
+ u64 *index);
+void btrfs_new_inode_args_destroy(struct btrfs_new_inode_args *args);
struct inode *btrfs_new_subvol_inode(struct user_namespace *mnt_userns,
struct inode *dir);
int btrfs_create_subvol_root(struct btrfs_trans_handle *trans,
struct btrfs_root *parent_root,
- struct inode *inode);
+ struct btrfs_new_inode_args *args);
void btrfs_set_delalloc_extent(struct inode *inode, struct extent_state *state,
unsigned *bits);
void btrfs_clear_delalloc_extent(struct inode *inode,
@@ -3846,15 +3867,16 @@ static inline int __btrfs_fs_compat_ro(struct btrfs_fs_info *fs_info, u64 flag)
struct posix_acl *btrfs_get_acl(struct inode *inode, int type, bool rcu);
int btrfs_set_acl(struct user_namespace *mnt_userns, struct inode *inode,
struct posix_acl *acl, int type);
-int btrfs_init_acl(struct btrfs_trans_handle *trans,
- struct inode *inode, struct inode *dir);
+int __btrfs_set_acl(struct btrfs_trans_handle *trans, struct inode *inode,
+ struct posix_acl *acl, int type);
#else
#define btrfs_get_acl NULL
#define btrfs_set_acl NULL
-static inline int btrfs_init_acl(struct btrfs_trans_handle *trans,
- struct inode *inode, struct inode *dir)
+static inline int __btrfs_set_acl(struct btrfs_trans_handle *trans,
+ struct inode *inode, struct posix_acl *acl,
+ int type)
{
- return 0;
+ return -EOPNOTSUPP;
}
#endif
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index ad446bbebf23..beafdb4ff873 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -222,15 +222,25 @@ static inline void btrfs_cleanup_ordered_extents(struct btrfs_inode *inode,
static int btrfs_dirty_inode(struct inode *inode);
static int btrfs_init_inode_security(struct btrfs_trans_handle *trans,
- struct inode *inode, struct inode *dir,
- const struct qstr *qstr)
+ struct btrfs_new_inode_args *args)
{
int err;
- err = btrfs_init_acl(trans, inode, dir);
- if (!err)
- err = btrfs_xattr_security_init(trans, inode, dir, qstr);
- return err;
+ if (args->default_acl) {
+ err = __btrfs_set_acl(trans, args->inode, args->default_acl,
+ ACL_TYPE_DEFAULT);
+ if (err)
+ return err;
+ }
+ if (args->acl) {
+ err = __btrfs_set_acl(trans, args->inode, args->acl, ACL_TYPE_ACCESS);
+ if (err)
+ return err;
+ }
+ if (!args->default_acl && !args->acl)
+ cache_no_acl(args->inode);
+ return btrfs_xattr_security_init(trans, args->inode, args->dir,
+ &args->dentry->d_name);
}
/*
@@ -6038,6 +6048,54 @@ static int btrfs_insert_inode_locked(struct inode *inode)
btrfs_find_actor, &args);
}
+int btrfs_new_inode_prepare(struct btrfs_new_inode_args *args,
+ unsigned int *trans_num_items)
+{
+ struct inode *dir = args->dir;
+ struct inode *inode = args->inode;
+ int ret;
+
+ ret = posix_acl_create(dir, &inode->i_mode, &args->default_acl, &args->acl);
+ if (ret)
+ return ret;
+
+ /* 1 to add inode item */
+ *trans_num_items = 1;
+ /* 1 to add compression property */
+ if (BTRFS_I(dir)->prop_compress)
+ (*trans_num_items)++;
+ /* 1 to add default ACL xattr */
+ if (args->default_acl)
+ (*trans_num_items)++;
+ /* 1 to add access ACL xattr */
+ if (args->acl)
+ (*trans_num_items)++;
+#ifdef CONFIG_SECURITY
+ /* 1 to add LSM xattr */
+ if (dir->i_security)
+ (*trans_num_items)++;
+#endif
+ if (args->orphan) {
+ /* 1 to add orphan item */
+ (*trans_num_items)++;
+ } else {
+ /*
+ * 1 to add inode ref
+ * 1 to add dir item
+ * 1 to add dir index
+ * 1 to update parent inode item
+ */
+ *trans_num_items += 4;
+ }
+ return 0;
+}
+
+void btrfs_new_inode_args_destroy(struct btrfs_new_inode_args *args)
+{
+ posix_acl_release(args->acl);
+ posix_acl_release(args->default_acl);
+}
+
/*
* Inherit flags from the parent inode.
*
@@ -6069,12 +6127,16 @@ static void btrfs_inherit_iflags(struct inode *inode, struct inode *dir)
btrfs_sync_inode_flags_to_i_flags(inode);
}
-static int btrfs_new_inode(struct btrfs_trans_handle *trans,
- struct btrfs_root *root, struct inode *inode,
- struct inode *dir, const char *name, int name_len,
+int btrfs_create_new_inode(struct btrfs_trans_handle *trans,
+ struct btrfs_new_inode_args *args,
u64 *index)
{
- struct btrfs_fs_info *fs_info = root->fs_info;
+ struct inode *dir = args->subvol ? NULL : args->dir;
+ struct inode *inode = args->inode;
+ const char *name;
+ int name_len;
+ struct btrfs_fs_info *fs_info = btrfs_sb(inode->i_sb);
+ struct btrfs_root *root;
struct btrfs_inode_item *inode_item;
struct btrfs_key *location;
struct btrfs_path *path;
@@ -6086,6 +6148,17 @@ static int btrfs_new_inode(struct btrfs_trans_handle *trans,
unsigned long ptr;
int ret;
+ if (args->subvol) {
+ name = "..";
+ name_len = 2;
+ } else if (args->orphan) {
+ name = NULL;
+ name_len = 0;
+ } else {
+ name = args->dentry->d_name.name;
+ name_len = args->dentry->d_name.len;
+ }
+
path = btrfs_alloc_path();
if (!path)
return -ENOMEM;
@@ -6097,6 +6170,10 @@ static int btrfs_new_inode(struct btrfs_trans_handle *trans,
if (!name)
set_nlink(inode, 0);
+ if (!args->subvol)
+ BTRFS_I(inode)->root = btrfs_grab_root(BTRFS_I(dir)->root);
+ root = BTRFS_I(inode)->root;
+
ret = btrfs_get_free_objectid(root, &objectid);
if (ret) {
btrfs_free_path(path);
@@ -6122,8 +6199,6 @@ static int btrfs_new_inode(struct btrfs_trans_handle *trans,
*/
BTRFS_I(inode)->index_cnt = 2;
BTRFS_I(inode)->dir_index = *index;
- if (!BTRFS_I(inode)->root)
- BTRFS_I(inode)->root = btrfs_grab_root(root);
BTRFS_I(inode)->generation = trans->transid;
inode->i_generation = BTRFS_I(inode)->generation;
@@ -6331,30 +6406,37 @@ static int btrfs_create_common(struct inode *dir, struct dentry *dentry,
{
struct btrfs_fs_info *fs_info = btrfs_sb(dir->i_sb);
struct btrfs_root *root = BTRFS_I(dir)->root;
+ struct btrfs_new_inode_args new_inode_args = {
+ .dir = dir,
+ .dentry = dentry,
+ .inode = inode,
+ };
+ unsigned int trans_num_items;
struct btrfs_trans_handle *trans;
int err;
u64 index = 0;
- /*
- * 2 for inode item and ref
- * 2 for dir items
- * 1 for xattr if selinux is on
- */
- trans = btrfs_start_transaction(root, 5);
+ err = btrfs_new_inode_prepare(&new_inode_args, &trans_num_items);
+ if (err) {
+ iput(inode);
+ return err;
+ }
+
+ trans = btrfs_start_transaction(root, trans_num_items);
if (IS_ERR(trans)) {
iput(inode);
- return PTR_ERR(trans);
+ err = PTR_ERR(trans);
+ goto out_new_inode_args;
}
- err = btrfs_new_inode(trans, root, inode, dir, dentry->d_name.name,
- dentry->d_name.len, &index);
+ err = btrfs_create_new_inode(trans, &new_inode_args, &index);
if (err) {
iput(inode);
inode = NULL;
goto out_unlock;
}
- err = btrfs_init_inode_security(trans, inode, dir, &dentry->d_name);
+ err = btrfs_init_inode_security(trans, &new_inode_args);
if (err)
goto out_unlock;
@@ -6376,6 +6458,8 @@ static int btrfs_create_common(struct inode *dir, struct dentry *dentry,
discard_new_inode(inode);
}
btrfs_btree_balance_dirty(fs_info);
+out_new_inode_args:
+ btrfs_new_inode_args_destroy(&new_inode_args);
return err;
}
@@ -8653,13 +8737,14 @@ struct inode *btrfs_new_subvol_inode(struct user_namespace *mnt_userns,
*/
int btrfs_create_subvol_root(struct btrfs_trans_handle *trans,
struct btrfs_root *parent_root,
- struct inode *inode)
+ struct btrfs_new_inode_args *args)
{
+ struct inode *inode = args->inode;
struct btrfs_root *new_root = BTRFS_I(inode)->root;
int err;
u64 index = 0;
- err = btrfs_new_inode(trans, new_root, inode, NULL, "..", 2, &index);
+ err = btrfs_create_new_inode(trans, args, &index);
if (err)
return err;
@@ -9164,22 +9249,22 @@ static struct inode *new_whiteout_inode(struct user_namespace *mnt_userns,
}
static int btrfs_whiteout_for_rename(struct btrfs_trans_handle *trans,
- struct btrfs_root *root,
- struct inode *inode, struct inode *dir,
- struct dentry *dentry)
+ struct btrfs_new_inode_args *args)
{
+ struct inode *inode = args->inode;
+ struct inode *dir = args->dir;
+ struct btrfs_root *root = BTRFS_I(dir)->root;
+ struct dentry *dentry = args->dentry;
int ret;
u64 index;
- ret = btrfs_new_inode(trans, root, inode, dir, dentry->d_name.name,
- dentry->d_name.len, &index);
+ ret = btrfs_create_new_inode(trans, args, &index);
if (ret) {
iput(inode);
return ret;
}
- ret = btrfs_init_inode_security(trans, inode, dir,
- &dentry->d_name);
+ ret = btrfs_init_inode_security(trans, args);
if (ret)
goto out;
@@ -9204,7 +9289,10 @@ static int btrfs_rename(struct user_namespace *mnt_userns,
unsigned int flags)
{
struct btrfs_fs_info *fs_info = btrfs_sb(old_dir->i_sb);
- struct inode *whiteout_inode;
+ struct btrfs_new_inode_args whiteout_args = {
+ .dir = old_dir,
+ .dentry = old_dentry,
+ };
struct btrfs_trans_handle *trans;
unsigned int trans_num_items;
struct btrfs_root *root = BTRFS_I(old_dir)->root;
@@ -9260,9 +9348,15 @@ static int btrfs_rename(struct user_namespace *mnt_userns,
filemap_flush(old_inode->i_mapping);
if (flags & RENAME_WHITEOUT) {
- whiteout_inode = new_whiteout_inode(mnt_userns, old_dir);
- if (!whiteout_inode)
+ whiteout_args.inode = new_whiteout_inode(mnt_userns, old_dir);
+ if (!whiteout_args.inode)
return -ENOMEM;
+ ret = btrfs_new_inode_prepare(&whiteout_args, &trans_num_items);
+ if (ret)
+ goto out_whiteout_inode;
+ } else {
+ /* 1 to update the old parent inode. */
+ trans_num_items = 1;
}
if (old_ino == BTRFS_FIRST_FREE_OBJECTID) {
@@ -9274,24 +9368,23 @@ static int btrfs_rename(struct user_namespace *mnt_userns,
* 1 to add new root ref
* 1 to add new root backref
*/
- trans_num_items = 4;
+ trans_num_items += 4;
} else {
/*
* 1 to update inode
* 1 to remove old inode ref
* 1 to add new inode ref
*/
- trans_num_items = 3;
+ trans_num_items += 3;
}
/*
* 1 to remove old dir item
* 1 to remove old dir index
- * 1 to update old parent inode
* 1 to add new dir item
* 1 to add new dir index
- * 1 to update new parent inode (if it's not the same as the old parent)
*/
- trans_num_items += 6;
+ trans_num_items += 4;
+ /* 1 to update new parent inode if it's not the same as the old parent */
if (new_dir != old_dir)
trans_num_items++;
if (new_inode) {
@@ -9304,8 +9397,6 @@ static int btrfs_rename(struct user_namespace *mnt_userns,
*/
trans_num_items += 5;
}
- if (flags & RENAME_WHITEOUT)
- trans_num_items += 5;
trans = btrfs_start_transaction(root, trans_num_items);
if (IS_ERR(trans)) {
ret = PTR_ERR(trans);
@@ -9401,9 +9492,8 @@ static int btrfs_rename(struct user_namespace *mnt_userns,
rename_ctx.index, new_dentry->d_parent);
if (flags & RENAME_WHITEOUT) {
- ret = btrfs_whiteout_for_rename(trans, root, whiteout_inode,
- old_dir, old_dentry);
- whiteout_inode = NULL;
+ ret = btrfs_whiteout_for_rename(trans, &whiteout_args);
+ whiteout_args.inode = NULL;
if (ret) {
btrfs_abort_transaction(trans, ret);
goto out_fail;
@@ -9416,7 +9506,10 @@ static int btrfs_rename(struct user_namespace *mnt_userns,
if (old_ino == BTRFS_FIRST_FREE_OBJECTID)
up_read(&fs_info->subvol_sem);
if (flags & RENAME_WHITEOUT)
- iput(whiteout_inode);
+ btrfs_new_inode_args_destroy(&whiteout_args);
+out_whiteout_inode:
+ if (flags & RENAME_WHITEOUT)
+ iput(whiteout_args.inode);
return ret;
}
@@ -9636,6 +9729,11 @@ static int btrfs_symlink(struct user_namespace *mnt_userns, struct inode *dir,
struct btrfs_path *path;
struct btrfs_key key;
struct inode *inode;
+ struct btrfs_new_inode_args new_inode_args = {
+ .dir = dir,
+ .dentry = dentry,
+ };
+ unsigned int trans_num_items;
int err;
u64 index = 0;
int name_len;
@@ -9656,28 +9754,30 @@ static int btrfs_symlink(struct user_namespace *mnt_userns, struct inode *dir,
inode_nohighmem(inode);
inode->i_mapping->a_ops = &btrfs_aops;
- /*
- * 2 items for inode item and ref
- * 2 items for dir items
- * 1 item for updating parent inode item
- * 1 item for the inline extent item
- * 1 item for xattr if selinux is on
- */
- trans = btrfs_start_transaction(root, 7);
+ new_inode_args.inode = inode;
+ err = btrfs_new_inode_prepare(&new_inode_args, &trans_num_items);
+ if (err) {
+ iput(inode);
+ return err;
+ }
+ /* 1 additional item for the inline extent */
+ trans_num_items++;
+
+ trans = btrfs_start_transaction(root, trans_num_items);
if (IS_ERR(trans)) {
iput(inode);
- return PTR_ERR(trans);
+ err = PTR_ERR(trans);
+ goto out_new_inode_args;
}
- err = btrfs_new_inode(trans, root, inode, dir, dentry->d_name.name,
- dentry->d_name.len, &index);
+ err = btrfs_create_new_inode(trans, &new_inode_args, &index);
if (err) {
iput(inode);
inode = NULL;
goto out_unlock;
}
- err = btrfs_init_inode_security(trans, inode, dir, &dentry->d_name);
+ err = btrfs_init_inode_security(trans, &new_inode_args);
if (err)
goto out_unlock;
@@ -9736,6 +9836,8 @@ static int btrfs_symlink(struct user_namespace *mnt_userns, struct inode *dir,
discard_new_inode(inode);
}
btrfs_btree_balance_dirty(fs_info);
+out_new_inode_args:
+ btrfs_new_inode_args_destroy(&new_inode_args);
return err;
}
@@ -9987,6 +10089,12 @@ static int btrfs_tmpfile(struct user_namespace *mnt_userns, struct inode *dir,
struct btrfs_trans_handle *trans;
struct btrfs_root *root = BTRFS_I(dir)->root;
struct inode *inode;
+ struct btrfs_new_inode_args new_inode_args = {
+ .dir = dir,
+ .dentry = dentry,
+ .orphan = true,
+ };
+ unsigned int trans_num_items;
u64 index;
int ret;
@@ -9998,23 +10106,28 @@ static int btrfs_tmpfile(struct user_namespace *mnt_userns, struct inode *dir,
inode->i_op = &btrfs_file_inode_operations;
inode->i_mapping->a_ops = &btrfs_aops;
- /*
- * 5 units required for adding orphan entry
- */
- trans = btrfs_start_transaction(root, 5);
+ new_inode_args.inode = inode;
+ ret = btrfs_new_inode_prepare(&new_inode_args, &trans_num_items);
+ if (ret) {
+ iput(inode);
+ return ret;
+ }
+
+ trans = btrfs_start_transaction(root, trans_num_items);
if (IS_ERR(trans)) {
iput(inode);
- return PTR_ERR(trans);
+ ret = PTR_ERR(trans);
+ goto out_new_inode_args;
}
- ret = btrfs_new_inode(trans, root, inode, dir, NULL, 0, &index);
+ ret = btrfs_create_new_inode(trans, &new_inode_args, &index);
if (ret) {
iput(inode);
inode = NULL;
goto out;
}
- ret = btrfs_init_inode_security(trans, inode, dir, NULL);
+ ret = btrfs_init_inode_security(trans, &new_inode_args);
if (ret)
goto out;
@@ -10026,9 +10139,9 @@ static int btrfs_tmpfile(struct user_namespace *mnt_userns, struct inode *dir,
goto out;
/*
- * We set number of links to 0 in btrfs_new_inode(), and here we set
- * it to 1 because d_tmpfile() will issue a warning if the count is 0,
- * through:
+ * We set number of links to 0 in btrfs_create_new_inode(), and here we
+ * set it to 1 because d_tmpfile() will issue a warning if the count is
+ * 0, through:
*
* d_tmpfile() -> inode_dec_link_count() -> drop_nlink()
*/
@@ -10041,6 +10154,8 @@ static int btrfs_tmpfile(struct user_namespace *mnt_userns, struct inode *dir,
if (ret && inode)
discard_new_inode(inode);
btrfs_btree_balance_dirty(fs_info);
+out_new_inode_args:
+ btrfs_new_inode_args_destroy(&new_inode_args);
return ret;
}
diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c
index 4b903517fdc3..bd10b8d44b5d 100644
--- a/fs/btrfs/ioctl.c
+++ b/fs/btrfs/ioctl.c
@@ -544,6 +544,33 @@ int __pure btrfs_is_empty_uuid(u8 *uuid)
return 1;
}
+/*
+ * Calculate the number of transaction items to reserve for creating a subvolume
+ * or snapshot, not including the inode, directory entries, or parent directory.
+ */
+static unsigned int create_subvol_num_items(struct btrfs_qgroup_inherit *inherit)
+{
+ /*
+ * 1 to add root block
+ * 1 to add root item
+ * 1 to add root ref
+ * 1 to add root backref
+ * 1 to add UUID item
+ * 1 to add qgroup info
+ * 1 to add qgroup limit
+ *
+ * Ideally the last two would only be accounted if qgroups are enabled,
+ * but that can change between now and the time we would insert them.
+ */
+ unsigned int num_items = 7;
+
+ if (inherit) {
+ /* 2 to add qgroup relations for each inherited qgroup */
+ num_items += 2 * inherit->num_qgroups;
+ }
+ return num_items;
+}
+
static noinline int create_subvol(struct user_namespace *mnt_userns,
struct inode *dir, struct dentry *dentry,
struct btrfs_qgroup_inherit *inherit)
@@ -560,7 +587,12 @@ static noinline int create_subvol(struct user_namespace *mnt_userns,
struct btrfs_root *new_root;
struct btrfs_block_rsv block_rsv;
struct timespec64 cur_time = current_time(dir);
- struct inode *inode;
+ struct btrfs_new_inode_args new_inode_args = {
+ .dir = dir,
+ .dentry = dentry,
+ .subvol = true,
+ };
+ unsigned int trans_num_items;
int ret;
dev_t anon_dev;
u64 objectid;
@@ -587,26 +619,27 @@ static noinline int create_subvol(struct user_namespace *mnt_userns,
if (ret < 0)
goto out_root_item;
- inode = btrfs_new_subvol_inode(mnt_userns, dir);
- if (!inode) {
+ new_inode_args.inode = btrfs_new_subvol_inode(mnt_userns, dir);
+ if (!new_inode_args.inode) {
ret = -ENOMEM;
goto out_anon_dev;
}
+ ret = btrfs_new_inode_prepare(&new_inode_args, &trans_num_items);
+ if (ret)
+ goto out_inode;
+ trans_num_items += create_subvol_num_items(inherit);
btrfs_init_block_rsv(&block_rsv, BTRFS_BLOCK_RSV_TEMP);
- /*
- * The same as the snapshot creation, please see the comment
- * of create_snapshot().
- */
- ret = btrfs_subvolume_reserve_metadata(root, &block_rsv, 8, false);
+ ret = btrfs_subvolume_reserve_metadata(root, &block_rsv,
+ trans_num_items, false);
if (ret)
- goto out_inode;
+ goto out_new_inode_args;
trans = btrfs_start_transaction(root, 0);
if (IS_ERR(trans)) {
ret = PTR_ERR(trans);
btrfs_subvolume_release_metadata(root, &block_rsv);
- goto out_inode;
+ goto out_new_inode_args;
}
trans->block_rsv = &block_rsv;
trans->bytes_reserved = block_rsv.size;
@@ -689,8 +722,8 @@ static noinline int create_subvol(struct user_namespace *mnt_userns,
}
/* anon_dev is owned by new_root now. */
anon_dev = 0;
- BTRFS_I(inode)->root = new_root;
- /* ... and new_root is owned by inode now. */
+ BTRFS_I(new_inode_args.inode)->root = new_root;
+ /* ... and new_root is owned by new_inode_args.inode now. */
ret = btrfs_record_root_in_trans(trans, new_root);
if (ret) {
@@ -698,7 +731,7 @@ static noinline int create_subvol(struct user_namespace *mnt_userns,
goto out;
}
- ret = btrfs_create_subvol_root(trans, root, inode);
+ ret = btrfs_create_subvol_root(trans, root, &new_inode_args);
if (ret) {
/* We potentially lose an unused inode item here */
btrfs_abort_transaction(trans, ret);
@@ -751,11 +784,13 @@ static noinline int create_subvol(struct user_namespace *mnt_userns,
ret = btrfs_commit_transaction(trans);
if (!ret) {
- d_instantiate(dentry, inode);
- inode = NULL;
+ d_instantiate(dentry, new_inode_args.inode);
+ new_inode_args.inode = NULL;
}
+out_new_inode_args:
+ btrfs_new_inode_args_destroy(&new_inode_args);
out_inode:
- iput(inode);
+ iput(new_inode_args.inode);
out_anon_dev:
if (anon_dev)
free_anon_bdev(anon_dev);
@@ -771,6 +806,7 @@ static int create_snapshot(struct btrfs_root *root, struct inode *dir,
struct btrfs_fs_info *fs_info = btrfs_sb(dir->i_sb);
struct inode *inode;
struct btrfs_pending_snapshot *pending_snapshot;
+ unsigned int trans_num_items;
struct btrfs_trans_handle *trans;
int ret;
@@ -808,16 +844,14 @@ static int create_snapshot(struct btrfs_root *root, struct inode *dir,
btrfs_init_block_rsv(&pending_snapshot->block_rsv,
BTRFS_BLOCK_RSV_TEMP);
/*
- * 1 - parent dir inode
- * 2 - dir entries
- * 1 - root item
- * 2 - root ref/backref
- * 1 - root of snapshot
- * 1 - UUID item
+ * 1 to add dir item
+ * 1 to add dir index
+ * 1 to update parent inode item
*/
+ trans_num_items = create_subvol_num_items(inherit) + 3;
ret = btrfs_subvolume_reserve_metadata(BTRFS_I(dir)->root,
- &pending_snapshot->block_rsv, 8,
- false);
+ &pending_snapshot->block_rsv,
+ trans_num_items, false);
if (ret)
goto free_pending;
]
|
| 88/2 |
2026/08/27 01:20 |
llm |
expert |
0m
Model:
Tokens:
input: 71027
output: 61
thoughts: 210
Thoughts: **Analyzing Btrfs Inode Creation Reservation**
Okay, so I'm looking at `btrfs_new_inode_prepare` and I see it's responsible for calculating `trans_num_items`. That makes sense; it needs to figure out how much space to reserve for creating a new inode. The code shows a conditional: if it's an orphan inode, it adds one item; otherwise, it adds four. That's for the inode ref, dir item, dir index, and the parent inode update.
Ah, I remember that commit `3538d68dbd97a2f5599bf39aeee47f027417fc39` specifically addressed reservation issues related to inode creation. Good to know. It had something to do with the reservation. I wonder, though, if there are any *other* recent commits that also touch `trans_num_items` in `btrfs_new_inode_prepare` with similar fixes? Itβs always good to be thorough and see if any other related issues have been addressed. I should probably start by searching for those.
|
| 89/2 |
2026/08/27 01:20 |
tool |
git-log |
0m
Args: map[CodeRegexp: Count:10 MessageRegexps:[btrfs_new_inode_prepare] PathPrefix: Since: SourcePath: SymbolName:]
Results: map[Output:3538d68dbd97 btrfs: reserve correct number of items for inode creation
]
|
| 90/2 |
2026/08/27 01:20 |
llm |
expert |
0m
Model:
Tokens:
input: 62431
output: 67
thoughts: 0
|
| 91/2 |
2026/08/27 01:20 |
tool |
git-log |
0m
Args: map[CodeRegexp: Count:10 MessageRegexps:[btrfs_insert_delayed_dir_index reserve] PathPrefix: Since: SourcePath: SymbolName:]
Results: map[Output:d246331b78cb btrfs: don't free qgroup space unless specified
3bae13e9d42e btrfs: do not BUG_ON() on failure to reserve metadata for delayed item
]
|
| 92/2 |
2026/08/27 01:21 |
llm |
expert |
0m
Model:
Tokens:
input: 71715
output: 25
thoughts: 17
Thoughts: Okay, here's my interpretation of that thought as a first-person expert in my field, starting with the bolded header:
**Diving into Commit 3bae13e9d42e**
Right, let's get down to it. My initial thought is to zero in on commit `3bae13e9d42e`. This is where I'm going to start digging. There's something in that commit that warrants a closer look, a specific change, a potential issue, or maybe I'm just curious about how something was implemented. It's a key piece of the puzzle I'm working on right now, and I need to understand it fully to move forward. This commit represents a point in time, a snapshot of the code, and a specific set of modifications that have the potential to reveal a lot. Let's see what secrets it holds.
|
| 93/2 |
2026/08/27 01:21 |
tool |
git-show |
0m
Args:
Results: map[Output:commit 3bae13e9d42eae0fea7afd6a9aea6b4fe81770ad
Author: Filipe Manana <fdmanana@suse.com>
Date: Tue May 31 16:06:41 2022 +0100
btrfs: do not BUG_ON() on failure to reserve metadata for delayed item
At btrfs_insert_delayed_dir_index(), we don't expect the metadata
reservation for the delayed dir index item insertion to fail, because the
caller is supposed to have reserved 1 unit of metadata space for that.
All callers are able to deal with an error in case that happens, so there
is no need for something so drastic as a BUG_ON() in case of failure.
Instead just emit a warning, so that's easily noticed during development
(fstests in particular), and return the error to the caller.
Reviewed-by: Nikolay Borisov <nborisov@suse.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
diff --git a/fs/btrfs/delayed-inode.c b/fs/btrfs/delayed-inode.c
index d9be90ac7c3a..6019c35de8de 100644
--- a/fs/btrfs/delayed-inode.c
+++ b/fs/btrfs/delayed-inode.c
@@ -1386,10 +1386,13 @@ int btrfs_insert_delayed_dir_index(struct btrfs_trans_handle *trans,
ret = btrfs_delayed_item_reserve_metadata(trans, dir->root, delayed_item);
/*
- * we have reserved enough space when we start a new transaction,
- * so reserving metadata failure is impossible
+ * Space was reserved for a dir index item insertion when we started the
+ * transaction, so getting a failure here should be impossible.
*/
- BUG_ON(ret);
+ if (WARN_ON(ret)) {
+ btrfs_release_delayed_item(delayed_item);
+ goto release_node;
+ }
mutex_lock(&delayed_node->mutex);
ret = __btrfs_add_delayed_insertion_item(delayed_node, delayed_item);
]
|
| 94/2 |
2026/08/27 01:21 |
llm |
expert |
0m
Model:
Tokens:
input: 63418
output: 40
thoughts: 0
|
| 95/2 |
2026/08/27 01:21 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/btrfs/inode.c Name:btrfs_add_link]
Results: map[SourceCode:6889: /*
6890: * utility function to add 'inode' into 'parent_inode' with
6891: * a give name and a given sequence number.
6892: * if 'add_backref' is true, also insert a backref from the
6893: * inode to the parent directory.
6894: */
6895: int btrfs_add_link(struct btrfs_trans_handle *trans,
6896: struct btrfs_inode *parent_inode, struct btrfs_inode *inode,
6897: const struct fscrypt_str *name, bool add_backref, u64 index)
6898: {
6899: int ret = 0;
6900: struct btrfs_key key;
6901: struct btrfs_root *root = parent_inode->root;
6902: u64 ino = btrfs_ino(inode);
6903: u64 parent_ino = btrfs_ino(parent_inode);
6904:
6905: if (unlikely(ino == BTRFS_FIRST_FREE_OBJECTID)) {
6906: memcpy(&key, &inode->root->root_key, sizeof(key));
6907: } else {
6908: key.objectid = ino;
6909: key.type = BTRFS_INODE_ITEM_KEY;
6910: key.offset = 0;
6911: }
6912:
6913: if (unlikely(ino == BTRFS_FIRST_FREE_OBJECTID)) {
6914: ret = btrfs_add_root_ref(trans, key.objectid,
6915: btrfs_root_id(root), parent_ino,
6916: index, name);
6917: } else if (add_backref) {
6918: ret = btrfs_insert_inode_ref(trans, root, name,
6919: ino, parent_ino, index);
6920: }
6921:
6922: /* Nothing to clean up yet */
6923: if (ret)
6924: return ret;
6925:
6926: ret = btrfs_insert_dir_item(trans, name, parent_inode, &key,
6927: btrfs_inode_type(inode), index);
6928: if (ret == -EEXIST || ret == -EOVERFLOW)
6929: goto fail_dir_item;
6930: else if (unlikely(ret)) {
6931: btrfs_abort_transaction(trans, ret);
6932: return ret;
6933: }
6934:
6935: btrfs_i_size_write(parent_inode, parent_inode->vfs_inode.i_size +
6936: name->len * 2);
6937: inode_inc_iversion(&parent_inode->vfs_inode);
6938: update_time_after_link_or_unlink(parent_inode);
6939:
6940: ret = btrfs_update_inode(trans, parent_inode);
6941: if (ret)
6942: btrfs_abort_transaction(trans, ret);
6943: return ret;
6944:
6945: fail_dir_item:
6946: if (unlikely(ino == BTRFS_FIRST_FREE_OBJECTID)) {
6947: u64 local_index;
6948: int ret2;
6949:
6950: ret2 = btrfs_del_root_ref(trans, key.objectid, btrfs_root_id(root),
6951: parent_ino, &local_index, name);
6952: if (ret2)
6953: btrfs_abort_transaction(trans, ret2);
6954: } else if (add_backref) {
6955: int ret2;
6956:
6957: ret2 = btrfs_del_inode_ref(trans, root, name, ino, parent_ino, NULL);
6958: if (ret2)
6959: btrfs_abort_transaction(trans, ret2);
6960: }
6961:
6962: /* Return the original error code */
6963: return ret;
6964: }
SourceFile:fs/btrfs/inode.c]
|
| 96/2 |
2026/08/27 01:21 |
llm |
expert |
0m
Model:
Tokens:
input: 73893
output: 66
thoughts: 1747
Thoughts: **Analyzing a Btrfs ENOSPC Warning**
Okay, so I'm looking at this warning that's triggered by `btrfs_abort_transaction` after `btrfs_insert_dir_item` returned an error. I need to figure out the root cause. My first thought is: `btrfs_add_link` calls `btrfs_insert_dir_item`, and if that fails with `-ENOSPC`, the transaction gets aborted.
My mind immediately starts tracing the code path. I see that `btrfs_create_new_inode` *does* call `btrfs_add_link`, but with `add_backref = false`, so `btrfs_insert_inode_ref` isn't called. So the problem *must* originate from `btrfs_insert_dir_item`. And, ultimately, that `-ENOSPC` probably comes from `btrfs_cow_block` returning the error. This suggests an insufficient transaction reservation.
I now need to understand why the reservation was insufficient. I'm recalling that `btrfs_new_inode_prepare` reserves 4 items for a normal file creation: inode ref, dir item, dir index, and the parent inode. But wait, `btrfs_insert_empty_items` in `btrfs_create_new_inode` handles the inode item and inode ref. That takes two item reservations. Then, we have potential space consumption from `btrfs_inode_inherit_props` (xattrs), `btrfs_init_inode_security` (ACLs, SELinux), and the two items for the dir item and dir index. Finally, `btrfs_update_inode` to update the parent inode requires 1 more item.
So, the total should be 2 (inode/ref) + xattrs + ACLs + SELinux + 2 (dir/index) + 1 (parent inode).
Let me check the `btrfs_new_inode_prepare` again to see what it reserves. It reserves 1 (inode) + 1 (compression) + 1 (default ACL) + 1 (access ACL) + 1 (LSM) + 4 (inode ref, dir item, dir index, parent inode). Total = 9 items.
Could it be that a leaf split during one of the insertions consumed more space than reserved? If the tree is highly fragmented or there are multiple splits, yes. But, `btrfs_calc_insert_metadata_size` *should* account for leaf splits up to the root, so it should be enough space. Unless there is a bug in `btrfs_calc_insert_metadata_size` or something else is eating space.
I'm starting to think about filesystem corruption. Could `btrfs_insert_dir_item` be returning `-EUCLEAN` because of a corrupted filesystem? If syzbot is fuzzing, a previous test might have damaged the filesystem, or it could be mounting a corrupted image. However, the report indicates `Not tainted syzkaller #0 PREEMPT(full)`. So it's an ENOSPC bug.
Either way, it's a transaction abort. This is a Denial of Service. Is it exploitable? No, just a transaction abort. Can it be triggered by an unprivileged user? If it's an ENOSPC bug, then yes. If it's corrupted filesystem, then it's mounting malicious image. I'll mark it as Denial of Service, and potentially Unprivileged and Malicious Filesystem Trigger.
Let's double-check about memory corruption. `btrfs_abort_transaction` simply sets the filesystem read-only and prints a warning. No memory corruption. So, no, it's not exploitable.
My summary:
* **Exploitable: No.** (transaction abort, read-only filesystem)
* **Denial of Service: Yes.** (filesystem becomes read-only)
* **Accessible From Unprivileged Processes: Yes.** (if ENOSPC)
* **Accessible From User Namespaces: Yes.**
* **VM Guest Trigger: Yes.**
* **VM Host Trigger in The Confidential Computing Context: No.**
* **Ethernet Network Trigger: No.**
* **Other Remote Trigger: No.**
* **Peripheral Trigger: No.**
* **Malicious Filesystem Trigger: Yes.** (if EUCLEAN)
Wait, let me make sure it isn't any other errors. What about `-EEXIST`, `-EOVERFLOW`, `-ENOMEM`, `-EIO`, or `-EROFS`? It's not `-EEXIST`, or `-EOVERFLOW` because
|
| 97/2 |
2026/08/27 01:21 |
tool |
set-results |
0m
Args: map[DenialOfService:true Exploitable:false FilesystemTrigger:true NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:true UserNamespace:true VMGuestTrigger:true VMHostTrigger:false]
Results: map[DenialOfService:true Exploitable:false FilesystemTrigger:true NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:true UserNamespace:true VMGuestTrigger:true VMHostTrigger:false]
|
| 98/2 |
2026/08/27 01:21 |
llm |
expert |
0m
Model:
Tokens:
input: 65881
output: 576
thoughts: 1309
Thoughts: **Analysis of the `btrfs_add_link` Warning**
Alright, let's break this down. My initial observation is that we have a `WARNING` in `btrfs_add_link` at `fs/btrfs/inode.c:6931`. That line is the `btrfs_abort_transaction(trans, ret);` call. The warning itself is triggered by the `WARN_ON` macro within `btrfs_abort_transaction` because the error code, `ret`, is not one of the standard "safe to ignore" errors like `-EIO`, `-EROFS`, or `-ENOMEM`.
The likely culprit for `ret` is an `-ENOSPC` or similar error from `btrfs_insert_dir_item`. This function calls a chain of other functions to insert a directory entry: `insert_with_overflow` -> `btrfs_insert_empty_item` -> `btrfs_insert_empty_items` -> `btrfs_search_slot` -> `setup_items_for_insert` (or `btrfs_insert_delayed_dir_index`). The last is especially interesting, because the `btrfs_insert_delayed_dir_index` can itself return `-ENOSPC`.
Looking deeper, `btrfs_insert_delayed_dir_index` might return `-ENOSPC` if the space reservation fails within `btrfs_delayed_item_reserve_metadata` which in turn calls `btrfs_block_rsv_migrate`. `btrfs_block_rsv_migrate` failing means the transaction block reservation (`trans->block_rsv`) doesn't have enough space.
Now, where does that transaction block reservation come from? It's reserved during `btrfs_new_inode_prepare`, which allocates space for potentially multiple items. For a non-orphan inode, it reserves space for 4 items: inode ref, dir item, dir index, and the update of the parent inode item.
However, `btrfs_create_new_inode` *itself* only inserts the inode item and inode ref (2 items) via `btrfs_insert_empty_items`. Then `btrfs_add_link` calls `btrfs_insert_dir_item`, which takes care of inserting the dir item and then calls `btrfs_insert_delayed_dir_index` which reserves space for the dir index. That's a total of 4.
The question then becomes: why would the reservation fail? Maybe `btrfs_new_inode_prepare` *underestimates* the space needed, or perhaps `btrfs_insert_delayed_dir_index` needs more space than initially allocated? And, could it be, in the overflow path, that when `btrfs_insert_dir_item` calls `insert_with_overflow` that returns `-EEXIST` (and subsequently calls `btrfs_extend_item`) that there is then not enough space.
Ultimately, this `btrfs_abort_transaction` is the standard Btrfs mechanism for handling unexpected errors within a transaction. It aborts the transaction, and sets the filesystem to read-only. From a user perspective, this looks like a Denial of Service, which is the immediate impact.
So, in terms of exploitability, there's no direct memory corruption or code execution here. The bug report points exactly to `btrfs_abort_transaction(trans, ret)`, confirming this is the root cause. This is a denial of service (making the filesystem read-only), and an unprivileged user can trigger this condition by creating a file. There's no privileged escalation. This happens because Btrfs is behaving as designed, by handling errors in a safe but abrupt manner.
Therefore, my summary is:
* **Exploitable: No.**
* **DoS: Yes.**
* **Unprivileged: Yes.**
* **User namespaces: Yes.**
* **Filesystem trigger: Yes (can be triggered on a mounted btrfs).**
* **VM Guest: Yes.**
|
| 99/1 |
2026/08/27 01:21 |
action |
format-explanation |
0m
Results: map[Explanation:**Analysis of the Bug Report**
**1. Bug Mechanism & Impact:**
The bug report indicates a `WARNING` at `fs/btrfs/inode.c` inside
`btrfs_add_link()`. The warning is triggered by the `btrfs_abort_transaction()`
macro.
In Btrfs, `btrfs_abort_transaction()` is called when an unexpected,
unrecoverable error occurs during an active transaction (e.g., failing to insert
a directory item or a delayed directory index due to `-ENOSPC` or other errors
when space was supposedly already reserved).
When `btrfs_abort_transaction()` is invoked, it safely halts the current
transaction, prints a stack trace (via
`WARN_ON(btrfs_abort_should_print_stack(__error))`), and flips the filesystem
into a read-only state to prevent any potential metadata corruption.
Because the filesystem gracefully handles the error by aborting the transaction
and transitioning to read-only mode, there is no memory corruption,
out-of-bounds access, or use-after-free involved. Therefore, this is not a
privilege escalation or information leak vulnerability. However, because it
forces the filesystem into a read-only state, it results in a local **Denial of
Service (DoS)**.
**2. Reachability:**
The stack trace shows the bug is triggered via the standard `openat()` system
call path (`do_sys_openat2` -> `do_file_open` -> `path_openat` -> `lookup_open`
-> `btrfs_create_common` -> `btrfs_create_new_inode` -> `btrfs_add_link`).
Creating a new file or directory is a fundamental filesystem operation that does
not require any special capabilities. Thus, any unprivileged local user (or a
process inside a user namespace) with write access to a Btrfs mount can
potentially trigger this bug if they can manipulate the filesystem into the
edge-case state that causes the transaction to fail (e.g., specific ENOSPC
conditions, hash collisions, or quota limits).
**3. Trigger Contexts:**
* **Local Unprivileged Users / User Namespaces:** Yes, it is accessible through
standard file creation syscalls.
* **VM Guests:** Yes, a user inside a VM guest backed by a Btrfs filesystem can
trigger this.
* **Malicious Filesystem:** Yes, a specially crafted Btrfs image could be
designed to reliably trigger this transaction abort upon the first file creation
attempt after being mounted.
* **Remote / Network / Peripheral:** No, this is strictly tied to local
filesystem operations and does not involve network packets or peripheral device
parsing.]
|