| 0/0 |
2026/08/09 23:43 |
flow |
assessment-security |
42m
Results: map[DenialOfService:true Explanation:Based on the analysis of the crash report and the kernel source code, here is
the assessment of the bug:
**Bug Analysis:**
The crash is a `WARNING` triggered by `btrfs_abort_should_print_stack()` which
is called from the `btrfs_abort_transaction()` macro in `fs/btrfs/inode.c`
inside the `__cow_file_range_inline()` function.
Looking at the source code for `__cow_file_range_inline()`:
```c
ret = btrfs_drop_extents(trans, root, inode, &drop_args);
if (unlikely(ret)) {
btrfs_abort_transaction(trans, ret);
goto out;
}
```
The transaction is aborted because `btrfs_drop_extents()` returned an error. By
examining the register dump in the crash report, specifically `R13:
00000000ffffffe4` (which corresponds to `-28` or `-ENOSPC`), we can deduce that
`btrfs_drop_extents()` failed due to a lack of metadata space (`-ENOSPC`).
When `btrfs_drop_extents()` attempts to modify the extent tree (e.g., splitting
an extent) and the pre-reserved metadata space (`inode->block_rsv`) is
insufficient or the global block reserve is depleted, it returns `-ENOSPC`.
Because the tree might be left in an inconsistent state if the operation fails
halfway, BTRFS must abort the transaction.
**Security Impact:**
* **Exploitable:** No. This is a standard BTRFS transaction abort mechanism. It
does not cause memory corruption, use-after-free, out-of-bounds access, or
information leaks. It safely halts the transaction to prevent filesystem
corruption.
* **Denial of Service:** Yes. When a BTRFS transaction is aborted, the
filesystem is immediately forced into a read-only state to protect data
integrity. If this happens on the root filesystem or a critical mount, it
results in a Denial of Service.
* **Unprivileged / User Namespace:** Yes. An unprivileged user with write access
to a BTRFS filesystem can potentially trigger this by filling up the
filesystem's metadata space and performing specific write operations (e.g.,
writing small inline extents over existing preallocated extents) that exhaust
the block reserves.
* **Filesystem Trigger:** Yes. This can also be triggered by mounting a
maliciously crafted BTRFS image where the extent tree is artificially deep or
corrupted, causing `btrfs_drop_extents()` to require more metadata space than
what is normally reserved, leading to an immediate `-ENOSPC` and transaction
abort. Exploitable:false FilesystemTrigger:true NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:true UserNamespace:true VMGuestTrigger:false VMHostTrigger:false]
|
| 1/1 |
2026/08/09 23:43 |
action |
syz-repro-to-c-repro |
0m
Results:
|
| 2/1 |
2026/08/09 23:43 |
action |
kernel-checkouter |
0m
Results: map[KernelSrc:/app/workdir/cache/src/372d345ff176cc24833eddc5c254ca2d46535c5e]
|
| 3/1 |
2026/08/09 23:43 |
action |
kernel-builder |
16m
Results: map[KernelObj:/app/workdir/cache/build/0237f2911535a44d7bfef0d95880f551979505e0]
|
| 4/1 |
2026/08/10 00:00 |
action |
codesearch-prepare |
12m
Results: map[Index:codesearch-index]
|
| 5/1 |
2026/08/10 00:12 |
agent |
expert |
13m
Model:
Results: map[DenialOfService:true Exploitable:false FilesystemTrigger:true NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:true UserNamespace:true VMGuestTrigger:false 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:
x_tables: ip_tables: owner match: used from hooks PREROUTING, but only valid from OUTPUT/POSTROUTING
------------[ cut here ]------------
btrfs_abort_should_print_stack(__error)
WARNING: fs/btrfs/inode.c:641 at __cow_file_range_inline+0xef0/0x1000 fs/btrfs/inode.c:641, CPU#0: syz.0.0/5319
Modules linked in:
CPU: 0 UID: 0 PID: 5319 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:__cow_file_range_inline+0xef0/0x1000 fs/btrfs/inode.c:641
Code: e2 74 39 41 83 fd fb 74 3a 41 83 fd f4 75 3b e8 86 04 b3 fd eb 3d e8 7f 04 b3 fd eb 10 e8 78 04 b3 fd eb 09 e8 71 04 b3 fd 90 <0f> 0b 90 44 89 e9 f7 d9 4c 8b 7c 24 18 e9 d4 fd ff ff e8 59 04 b3
RSP: 0018:ffffc9000eb5ec80 EFLAGS: 00010283
RAX: ffffffff84140e6f RBX: 1ffff92001d6bda0 RCX: 0000000000100000
RDX: ffffc90020001000 RSI: 00000000000010b8 RDI: 00000000000010b9
RBP: ffffc9000eb5ee70 R08: ffff888000ef4a80 R09: 0000000000000003
R10: 00000000fffffffb R11: 0000000000000002 R12: dffffc0000000000
R13: 00000000ffffffe4 R14: ffff888052a24001 R15: ffff888013211d10
FS: 00007fed310f66c0(0000) GS:ffff88808c549000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 000020000000c2c0 CR3: 0000000012e06000 CR4: 0000000000352ef0
Call Trace:
<TASK>
run_delalloc_inline fs/btrfs/inode.c:2351 [inline]
btrfs_run_delalloc_range+0x1a84/0x1fd0 fs/btrfs/inode.c:2407
writepage_delalloc+0x10a4/0x1c60 fs/btrfs/extent_io.c:1671
extent_writepage fs/btrfs/extent_io.c:2043 [inline]
extent_write_cache_pages fs/btrfs/extent_io.c:2706 [inline]
btrfs_writepages+0x1667/0x28b0 fs/btrfs/extent_io.c:2838
do_writepages+0x338/0x560 mm/page-writeback.c:2571
filemap_writeback mm/filemap.c:387 [inline]
filemap_fdatawrite_range+0x1ef/0x2f0 mm/filemap.c:412
btrfs_fdatawrite_range+0x54/0xf0 fs/btrfs/file.c:3884
btrfs_direct_write+0x69d/0xc20 fs/btrfs/direct-io.c:1038
btrfs_do_write_iter+0x325/0x790 fs/btrfs/file.c:1449
do_iter_readv_writev+0x612/0x8c0 fs/read_write.c:-1
vfs_writev+0x343/0x990 fs/read_write.c:1058
do_writev+0x154/0x2e0 fs/read_write.c:1104
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7fed3019e0d9
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:00007fed310f5fe8 EFLAGS: 00000246 ORIG_RAX: 0000000000000014
RAX: ffffffffffffffda RBX: 00007fed30426090 RCX: 00007fed3019e0d9
RDX: 0000000000000001 RSI: 0000200000000040 RDI: 0000000000000005
RBP: 00007fed30235024 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
R13: 00007fed30426128 R14: 00007fed30426090 R15: 00007ffcf827f4e8
</TASK>
Reply: Based on the analysis of the crash report and the kernel source code, here is the assessment of the bug:
**Bug Analysis:**
The crash is a `WARNING` triggered by `btrfs_abort_should_print_stack()` which is called from the `btrfs_abort_transaction()` macro in `fs/btrfs/inode.c` inside the `__cow_file_range_inline()` function.
Looking at the source code for `__cow_file_range_inline()`:
```c
ret = btrfs_drop_extents(trans, root, inode, &drop_args);
if (unlikely(ret)) {
btrfs_abort_transaction(trans, ret);
goto out;
}
```
The transaction is aborted because `btrfs_drop_extents()` returned an error. By examining the register dump in the crash report, specifically `R13: 00000000ffffffe4` (which corresponds to `-28` or `-ENOSPC`), we can deduce that `btrfs_drop_extents()` failed due to a lack of metadata space (`-ENOSPC`).
When `btrfs_drop_extents()` attempts to modify the extent tree (e.g., splitting an extent) and the pre-reserved metadata space (`inode->block_rsv`) is insufficient or the global block reserve is depleted, it returns `-ENOSPC`. Because the tree might be left in an inconsistent state if the operation fails halfway, BTRFS must abort the transaction.
**Security Impact:**
* **Exploitable:** No. This is a standard BTRFS transaction abort mechanism. It does not cause memory corruption, use-after-free, out-of-bounds access, or information leaks. It safely halts the transaction to prevent filesystem corruption.
* **Denial of Service:** Yes. When a BTRFS transaction is aborted, the filesystem is immediately forced into a read-only state to protect data integrity. If this happens on the root filesystem or a critical mount, it results in a Denial of Service.
* **Unprivileged / User Namespace:** Yes. An unprivileged user with write access to a BTRFS filesystem can potentially trigger this by filling up the filesystem's metadata space and performing specific write operations (e.g., writing small inline extents over existing preallocated extents) that exhaust the block reserves.
* **Filesystem Trigger:** Yes. This can also be triggered by mounting a maliciously crafted BTRFS image where the extent tree is artificially deep or corrupted, causing `btrfs_drop_extents()` to require more metadata space than what is normally reserved, leading to an immediate `-ENOSPC` and transaction abort.
|
| 6/2 |
2026/08/10 00:12 |
llm |
expert |
0m
Model:
Tokens:
input: 7745
output: 38
thoughts: 99
Thoughts: Okay, here's my take on those thoughts, framed as an expert's internal monologue:
**Investigating a Btrfs Issue**
Right, a syzkaller report just came in. The kernel's complaining about something in Btrfs, specifically a warning related to `__cow_file_range_inline` in `fs/btrfs/inode.c`. Line 641, huh? That's a pretty specific pointer, let's dive right in. Time to pull up that code and see what's happening at that point in the CoW file range operation. Hopefully, this is something straightforward, but syzkaller reports can be tricky, so I need to be ready for anything.
|
| 7/2 |
2026/08/10 00:12 |
tool |
read-file |
0m
Args: map[File:fs/btrfs/inode.c FirstLine:600 LineCount:100]
Results: map[Contents: 600: *
601: * Return 0 if the inlined extent is created successfully.
602: * Return <0 for critical error, and should be considered as an writeback error.
603: * Return >0 if can not create an inlined extent (mostly due to lack of meta space).
604: */
605: static noinline int __cow_file_range_inline(struct btrfs_inode *inode,
606: u64 size, size_t compressed_size,
607: int compress_type,
608: struct folio *compressed_folio,
609: bool update_i_size)
610: {
611: struct btrfs_drop_extents_args drop_args = { 0 };
612: struct btrfs_root *root = inode->root;
613: struct btrfs_fs_info *fs_info = root->fs_info;
614: struct btrfs_trans_handle *trans = NULL;
615: u64 data_len = (compressed_size ?: size);
616: int ret;
617: struct btrfs_path *path;
618:
619: path = btrfs_alloc_path();
620: if (!path) {
621: ret = -ENOMEM;
622: goto out;
623: }
624:
625: trans = btrfs_join_transaction(root);
626: if (IS_ERR(trans)) {
627: ret = PTR_ERR(trans);
628: trans = NULL;
629: goto out;
630: }
631: trans->block_rsv = &inode->block_rsv;
632:
633: drop_args.path = path;
634: drop_args.start = 0;
635: drop_args.end = fs_info->sectorsize;
636: drop_args.drop_cache = true;
637: drop_args.replace_extent = true;
638: drop_args.extent_item_size = btrfs_file_extent_calc_inline_size(data_len);
639: ret = btrfs_drop_extents(trans, root, inode, &drop_args);
640: if (unlikely(ret)) {
641: btrfs_abort_transaction(trans, ret);
642: goto out;
643: }
644:
645: ret = insert_inline_extent(trans, path, inode, drop_args.extent_inserted,
646: size, compressed_size, compress_type,
647: compressed_folio, update_i_size);
648: if (unlikely(ret && ret != -ENOSPC)) {
649: btrfs_abort_transaction(trans, ret);
650: goto out;
651: } else if (ret == -ENOSPC) {
652: ret = 1;
653: goto out;
654: }
655:
656: btrfs_update_inode_bytes(inode, size, drop_args.bytes_found);
657: ret = btrfs_update_inode(trans, inode);
658: if (unlikely(ret && ret != -ENOSPC)) {
659: btrfs_abort_transaction(trans, ret);
660: goto out;
661: } else if (ret == -ENOSPC) {
662: ret = 1;
663: goto out;
664: }
665:
666: btrfs_set_inode_full_sync(inode);
667: out:
668: /*
669: * Don't forget to free the reserved space, as for inlined extent
670: * it won't count as data extent, free them directly here.
671: * And at reserve time, it's always aligned to sector size, so
672: * just free one sector here.
673: *
674: * If we fallback to non-inline (ret == 1) due to -ENOSPC, then we need
675: * to keep the data reservation.
676: */
677: if (ret <= 0)
678: btrfs_qgroup_free_data(inode, NULL, 0, fs_info->sectorsize, NULL);
679: btrfs_free_path(path);
680: if (trans)
681: btrfs_end_transaction(trans);
682: return ret;
683: }
684:
685: struct async_extent {
686: u64 start;
687: u64 ram_size;
688: struct compressed_bio *cb;
689: struct list_head list;
690: };
691:
692: struct async_chunk {
693: struct btrfs_inode *inode;
694: struct folio *locked_folio;
695: u64 start;
696: u64 end;
697: blk_opf_t write_flags;
698: struct list_head extents;
699: struct cgroup_subsys_state *blkcg_css;
]
|
| 8/2 |
2026/08/10 00:12 |
llm |
expert |
0m
Model:
Tokens:
input: 9592
output: 39
thoughts: 528
Thoughts: **Analyzing a BTRFS Transaction Abort**
Okay, so I'm looking at this crash, and the warning is originating from `btrfs_abort_transaction` within `__cow_file_range_inline`. This is a standard BTRFS transaction abort, which immediately flags `btrfs_drop_extents` returning an error as the likely culprit. This is a filesystem trigger, which is interesting. The question immediately becomes, can unprivileged users cause this? The answer is likely yes, either through a maliciously crafted filesystem image, or a bug within BTRFS that causes `btrfs_drop_extents` to fail under normal circumstances.
Hmm, the bug report specifies `fs/btrfs/inode.c:641 at __cow_file_range_inline`. This looks familiar. The line number suggests it might be a normal user issue on a healthy filesystem, but usually this kind of abort is from metadata corruption, pointing towards a malicious image.
Hold on, the bug report's title, "x_tables: ip_tables: owner match..." is completely irrelevant! Ah, right, that's just syzkaller grabbing a line from elsewhere in the log. Okay, back to the crash: it's a `btrfs_abort_should_print_stack(__error)` in `__cow_file_range_inline`.
Now, can `btrfs_drop_extents` fail with an `-ENOSPC` or similar error, even on a healthy filesystem? If this is happening on a corrupted filesystem, it's a Malicious Filesystem Trigger. However, I need to know if it can be triggered by a normal user on a healthy filesystem. To figure this out, I'm going to search for recent commits related to both `__cow_file_range_inline` and `btrfs_drop_extents` to identify any potential regression or bug fixes. This should help to narrow down the possible causes.
|
| 9/2 |
2026/08/10 00:13 |
tool |
git-log |
10m
Error:
Args: map[CodeRegexp:btrfs_drop_extents.*__cow_file_range_inline Count:10]
Results:
|
| 10/2 |
2026/08/10 00:23 |
llm |
expert |
0m
Model:
Tokens:
input: 10615
output: 44
thoughts: 16
Thoughts: Here's my thought process, summarized as you requested:
**Navigating the Btrfs Inode Landscape**
Okay, so I need to dive into the heart of Btrfs inode handling. Given the likely area of interest, the logical first place to look is within the `fs/btrfs` directory, specifically within the `inode.c` file. This is where I'd expect to find the core functions and data structures that manage how inodes are represented and manipulated within the Btrfs filesystem. I anticipate finding definitions for the `btrfs_inode` structure itself, as well as functions for allocating, freeing, initializing, and updating inode metadata. Furthermore, I expect to find implementations related to disk space management for inodes and their associated attributes. My initial search should focus on exploring the structure definitions and related accessor functions within this file. From there, depending on the specific problem I'm trying to address, I can branch out to other relevant files and functions.
|
| 11/2 |
2026/08/10 00:23 |
tool |
git-log |
0m
Error:
Args: map[Count:10 PathPrefix:fs/btrfs/inode.c SymbolName:__cow_file_range_inline]
Results:
|
| 12/2 |
2026/08/10 00:23 |
llm |
expert |
0m
Model:
Tokens:
input: 10900
output: 44
thoughts: 12
Thoughts: Okay, here's my interpretation of that thought process, tailored for an expert audience, and presented as if it were my own internal monologue:
**Connecting Source and Symbol**
Right, let me see... *Ah,* so this `SourcePath` β I'm making the connection, finally β it's referring to the location, the originating file path for a given `SymbolName`. So the link between the source code location and the symbol itself is established here. That particular association, it's pretty crucial for debugging, refactoring, code navigation, and things of that sort, like getting the definition of something or understanding where it's used. This is probably related to the indexing or symbol table generation process, I'd bet. Got it. This is a pretty fundamental relationship, but seeing it explicitly articulated helps refine my mental model of how the system works. Makes sense.
|
| 13/2 |
2026/08/10 00:23 |
tool |
git-log |
0m
Args: map[Count:10 SourcePath:fs/btrfs/inode.c SymbolName:__cow_file_range_inline]
Results: map[Output:6d0f25cdd8e3 btrfs: update stale comment in __cow_file_range_inline()
c1c050f92d8f btrfs: fix reservation leak in some error paths when inserting inline extent
f8da41de0bff btrfs: do not free data reservation in fallback from inline due to -ENOSPC
a929904cf73b btrfs: add unlikely annotations to branches leading to transaction abort
0bb067ca64e3 btrfs: fix the qgroup data free range for inline data extents
e469da5d8414 btrfs: drop unused parameter offset from __cow_file_range_inline()
cd241a8f554c btrfs: move can_cow_file_range_inline() outside of the extent lock
0586d0a89e77 btrfs: move extent bit and page cleanup into cow_file_range_inline
6eecfa22403e btrfs: push all inline logic into cow_file_range
ae0d22a7fcb6 btrfs: migrate insert_inline_extent() to folio interfaces
]
|
| 14/2 |
2026/08/10 00:23 |
llm |
expert |
0m
Model:
Tokens:
input: 11432
output: 27
thoughts: 29
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Analyzing Commits: A Quick Sanity Check**
Alright, let's take a look at these two commits, `c1c050f92d8f` and `f8da41de0bff`. My initial thought is just a gut check. Given the context of what I've been working on, these commit hashes are pinging something in the back of my head. I need to quickly verify my initial assumptions about them. Are they related? What do they touch? Are there any obvious red flags based on recent activity? Need to know if this requires a deep dive or if its a quick confirmation of a known quantity.
|
| 15/2 |
2026/08/10 00:23 |
tool |
git-show |
0m
Args:
Results: map[Output:commit c1c050f92d8f6aac4e17f7f2230160794fceef0c
Author: Filipe Manana <fdmanana@suse.com>
Date: Fri Dec 12 17:18:25 2025 +0000
btrfs: fix reservation leak in some error paths when inserting inline extent
If we fail to allocate a path or join a transaction, we return from
__cow_file_range_inline() without freeing the reserved qgroup data,
resulting in a leak. Fix this by ensuring we call btrfs_qgroup_free_data()
in such cases.
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/inode.c b/fs/btrfs/inode.c
index c4c370b6aae9..03337fa7a61c 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -617,19 +617,22 @@ static noinline int __cow_file_range_inline(struct btrfs_inode *inode,
struct btrfs_drop_extents_args drop_args = { 0 };
struct btrfs_root *root = inode->root;
struct btrfs_fs_info *fs_info = root->fs_info;
- struct btrfs_trans_handle *trans;
+ struct btrfs_trans_handle *trans = NULL;
u64 data_len = (compressed_size ?: size);
int ret;
struct btrfs_path *path;
path = btrfs_alloc_path();
- if (!path)
- return -ENOMEM;
+ if (!path) {
+ ret = -ENOMEM;
+ goto out;
+ }
trans = btrfs_join_transaction(root);
if (IS_ERR(trans)) {
- btrfs_free_path(path);
- return PTR_ERR(trans);
+ ret = PTR_ERR(trans);
+ trans = NULL;
+ goto out;
}
trans->block_rsv = &inode->block_rsv;
@@ -680,7 +683,8 @@ static noinline int __cow_file_range_inline(struct btrfs_inode *inode,
if (ret <= 0)
btrfs_qgroup_free_data(inode, NULL, 0, fs_info->sectorsize, NULL);
btrfs_free_path(path);
- btrfs_end_transaction(trans);
+ if (trans)
+ btrfs_end_transaction(trans);
return ret;
}
]
|
| 16/2 |
2026/08/10 00:23 |
llm |
expert |
0m
Model:
Tokens:
input: 12428
output: 23
thoughts: 14
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience and written in the first person:
**Analyzing the Hash `f8da41de0bff`**
Alright, let's dive into this hash: `f8da41de0bff`. Immediately, I need to consider a few things. Is this a common hashing algorithm like MD5, SHA-1, SHA-256, or something custom? The length suggests either MD5 or a very truncated SHA-1/SHA-256, but the specifics require a lookup. My intuition leans toward MD5 given its prevalence in certain contexts.
If it *is* MD5, I have an immediate mental framework to work from, considering its vulnerabilities and potential collisions. If itβs something else, I need to quickly identify the algorithm and its properties β the expected length of the input, the level of security it offers, and whether any public vulnerabilities are known. Knowing the context of this hash is crucial; is this a file checksum, a password hash, a session identifier, or something else entirely? The environment will heavily influence my next steps. Are there known databases with this hash value? Do I have access to any dictionaries or rainbow tables?
Based on the structure, is it hex encoded? If it's not and the encoding is something else, this will change my entire approach. I need to keep in mind possible collision attacks. The more context I have, the better. Let's start with a quick online lookup to confirm the hash type and see if any related information can be gleaned. The goal is to quickly ascertain the likely algorithm, any relevant attack vectors, and any initial context that might be available to guide a more detailed investigation.
|
| 17/2 |
2026/08/10 00:23 |
tool |
git-show |
0m
Args:
Results: map[Output:commit f8da41de0bff9eb1d774a7253da0c9f637c4470a
Author: Filipe Manana <fdmanana@suse.com>
Date: Fri Dec 12 17:10:10 2025 +0000
btrfs: do not free data reservation in fallback from inline due to -ENOSPC
If we fail to create an inline extent due to -ENOSPC, we will attempt to
go through the normal COW path, reserve an extent, create an ordered
extent, etc. However we were always freeing the reserved qgroup data,
which is wrong since we will use data. Fix this by freeing the reserved
qgroup data in __cow_file_range_inline() only if we are not doing the
fallback (ret is <= 0).
Reviewed-by: Qu Wenruo <wqu@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/inode.c b/fs/btrfs/inode.c
index 6633b3dc9314..c4c370b6aae9 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -673,8 +673,12 @@ static noinline int __cow_file_range_inline(struct btrfs_inode *inode,
* it won't count as data extent, free them directly here.
* And at reserve time, it's always aligned to page size, so
* just free one page here.
+ *
+ * If we fallback to non-inline (ret == 1) due to -ENOSPC, then we need
+ * to keep the data reservation.
*/
- btrfs_qgroup_free_data(inode, NULL, 0, fs_info->sectorsize, NULL);
+ if (ret <= 0)
+ btrfs_qgroup_free_data(inode, NULL, 0, fs_info->sectorsize, NULL);
btrfs_free_path(path);
btrfs_end_transaction(trans);
return ret;
]
|
| 18/2 |
2026/08/10 00:23 |
llm |
expert |
0m
Model:
Tokens:
input: 13446
output: 34
thoughts: 693
Thoughts: **Analyzing a Btrfs Transaction Abort Warning**
Okay, so the bug report is flagging a WARNING at `btrfs_abort_transaction(trans, ret);` specifically within `__cow_file_range_inline`. Let's break this down. From what I see in the code snippets, the warning can be triggered after calls to `btrfs_drop_extents`, `insert_inline_extent`, and `btrfs_update_inode`. All these calls are encapsulated within error checking blocks that lead to transaction abortion. But is this an exploitable condition?
The key here is that a warning within `btrfs_abort_transaction` simply signals that a transaction was aborted. It's not necessarily a fatal error, just an indication that something went wrong. This `btrfs_abort_transaction` call is, of course, a macro, which expands to `__btrfs_abort_transaction`.
Looking at the macro's definition, it looks like the warning is conditionally printed, but it's *always* calling `__btrfs_abort_transaction` to clean up the transaction. The warning itself, `WARN(1, KERN_DEBUG "BTRFS: Transaction aborted (error %d)\n", (errno));` is triggered only if the error isn't -EIO or -EROFS. So, it's printing that we aborted a transaction for a reason other than an IO or read-only filesystem error. I note the use of `test_and_set_bit` indicates that only the *first* transaction abort will trigger the warning.
I think the important thing to note here is the warning about `btrfs_abort_should_print_stack(__error)` within `__cow_file_range_inline`. Let me quickly check the current kernel's definition of `btrfs_abort_transaction` to confirm my understanding of this. I need to make sure I am analyzing this from the correct perspective.
|
| 19/2 |
2026/08/10 00:23 |
tool |
grepper |
0m
Args: map[Expression:define btrfs_abort_transaction PathPrefix:fs/btrfs/]
Results: map[Output:fs/btrfs/transaction.h=260=do { \
--
fs/btrfs/transaction.h-273- */
fs/btrfs/transaction.h:274:#define btrfs_abort_transaction(trans, error) \
fs/btrfs/transaction.h-275-do { \
]
|
| 20/2 |
2026/08/10 00:23 |
llm |
expert |
0m
Model:
Tokens:
input: 14693
output: 37
thoughts: 22
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Diving into Transaction Handling**
Okay, let's zero in on `fs/btrfs/transaction.h`, specifically around line 274. I need to refresh my memory on how Btrfs handles transactions at a fundamental level. I'm guessing that line 274 is likely dealing with a key data structure or function related to commit groups or the transaction descriptor itself. Perhaps it's defining the flags or fields within the transaction struct that dictates rollback behaviour, commit order, or even the locking scheme employed. It could also involve definitions for tracking extent modifications within a transaction, or maybe even related to the metadata updates. I'll need to see if it's related to the commit cycle, and if there are any subtle hints about performance optimizations in that section that are worth investigating. Time to dig in and see what nuances the code reveals.
|
| 21/2 |
2026/08/10 00:23 |
tool |
read-file |
0m
Args: map[File:fs/btrfs/transaction.h FirstLine:260 LineCount:40]
Results: map[Contents: 260: do { \
261: (void)sizeof(char[-!(__builtin_constant_p(error) ? (error) < 0 : 1)]); \
262: if (IS_ENABLED(CONFIG_BTRFS_DEBUG)) { \
263: if (sizeof(char[(error) < 0]) != 1) \
264: DEBUG_WARN("error >= 0 passed to btrfs_abort_transaction()"); \
265: } \
266: } while(0)
267:
268: /*
269: * Call btrfs_abort_transaction() as early as possible when an error condition
270: * is detected, that way the exact stack trace is reported for some errors.
271: *
272: * Error number must be negative as it encodes wheather it's the first abort.
273: */
274: #define btrfs_abort_transaction(trans, error) \
275: do { \
276: int __error = (error); \
277: \
278: VERIFY_NEGATIVE_ERROR(error); \
279: /* Report first abort since mount */ \
280: if (!test_and_set_bit(BTRFS_FS_STATE_TRANS_ABORTED, \
281: &((trans)->fs_info->fs_state))) { \
282: WARN_ON(btrfs_abort_should_print_stack(__error)); \
283: __error = -__error; \
284: } \
285: __btrfs_abort_transaction((trans), __func__, \
286: __LINE__, __error); \
287: } while (0)
288:
289: int btrfs_end_transaction(struct btrfs_trans_handle *trans);
290: struct btrfs_trans_handle *btrfs_start_transaction(struct btrfs_root *root,
291: unsigned int num_items);
292: struct btrfs_trans_handle *btrfs_start_transaction_fallback_global_rsv(
293: struct btrfs_root *root,
294: unsigned int num_items);
295: struct btrfs_trans_handle *btrfs_join_transaction(struct btrfs_root *root);
296: struct btrfs_trans_handle *btrfs_join_transaction_spacecache(struct btrfs_root *root);
297: struct btrfs_trans_handle *btrfs_join_transaction_nostart(struct btrfs_root *root);
298: struct btrfs_trans_handle *btrfs_attach_transaction(struct btrfs_root *root);
299: struct btrfs_trans_handle *btrfs_attach_transaction_barrier(
]
|
| 22/2 |
2026/08/10 00:23 |
llm |
expert |
0m
Model:
Tokens:
input: 15767
output: 40
thoughts: 859
Thoughts: **Analyzing a BTRFS Transaction Abort Warning**
Okay, so `WARN_ON(btrfs_abort_should_print_stack(__error))` is the key here β that's what's generating the warning. This just means a BTRFS transaction was aborted, which, in BTRFS-speak, translates to the filesystem encountering an error and subsequently remounting read-only to safeguard against data corruption.
The immediate question is whether this constitutes a bug. Syzkaller, the fuzzer, flags *any* warning as a potential bug, so yes, according to its definition. But is it *exploitable*? I'd say no. It's simply a transaction abort that results in a read-only filesystem, essentially a Denial of Service.
Can a regular user trigger this? Now, that's where it gets interesting. If a standard user can trigger a transaction abort on a perfectly healthy filesystem, that's definitely a DoS. However, if it requires a corrupted filesystem as a prerequisite, we're looking at a "Malicious Filesystem Trigger" type of issue, which is still bad, but in a different way.
I need to see if there are any known bugs where `__cow_file_range_inline` has been known to abort transactions on a healthy filesystem.
Aha! Syzkaller is the one triggering it. Syzkaller is often used to mount a corrupted filesystem image, which makes me think that that is the case here as well. I need to figure out if Syzkaller is mounting a corrupted filesystem image in this particular instance.
The bug report doesn't include the exact reproducer, but we *do* have the call trace. Let's see... looking at the call trace:
```
run_delalloc_inline fs/btrfs/inode.c:2351 [inline]
btrfs_run_delalloc_range+0x1a84/0x1fd0 fs/btrfs/inode.c:2407
writepage_delalloc+0x10a4/0x1c60 fs/btrfs/extent_io.c:1671
extent_writepage fs/btrfs/extent_io.c:2043 [inline]
extent_write_cache_pages fs/btrfs/extent_io.c:2706 [inline]
btrfs_writepages+0x1667/0x28b0 fs/btrfs/extent_io.c:2838
do_writepages+0x338/0x560 mm/page-writeback.c:2571
filemap_writeback mm/filemap.c:387 [inline]
filemap_fdatawrite_range+0x1ef/0x2f0 mm/filemap.c:412
btrfs_fdatawrite_range+0x54/0xf0 fs/btrfs/file.c:3884
btrfs_direct_write+0x69d/0xc20 fs/btrfs/direct-io.c:1038
btrfs_do_write_iter+0x325/0x790 fs/btrfs/file.c:1449
do_iter_readv_writev+0x612/0x8c0 fs/read_write.c:-1
vfs_writev+0x343/0x990 fs/read_write.c:1058
do_writev+0x154/0x2e0 fs/read_write.c:1104
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
```
This looks like a direct write that ultimately falls back to a buffered write. Essentially, it causes writeback. The critical thing is that `btrfs_direct_write` calls `btrfs_fdatawrite_range`. I need to review the `btrfs_direct_write` function in `fs/btrfs/direct-io.c` to understand exactly what's happening.
|
| 23/2 |
2026/08/10 00:23 |
tool |
read-file |
0m
Args: map[File:fs/btrfs/direct-io.c FirstLine:1000 LineCount:50]
Results: map[Contents:1000: fault_in_iov_iter_readable(from, left);
1001: prev_left = left;
1002: goto again;
1003: }
1004: }
1005:
1006: btrfs_inode_unlock(BTRFS_I(inode), ilock_flags);
1007:
1008: /*
1009: * If 'ret' is -ENOTBLK or we have not written all data, then it means
1010: * we must fallback to buffered IO.
1011: */
1012: if ((ret < 0 && ret != -ENOTBLK) || !iov_iter_count(from))
1013: goto out;
1014:
1015: buffered:
1016: /*
1017: * If we are in a NOWAIT context, then return -EAGAIN to signal the caller
1018: * it must retry the operation in a context where blocking is acceptable,
1019: * because even if we end up not blocking during the buffered IO attempt
1020: * below, we will block when flushing and waiting for the IO.
1021: */
1022: if (iocb->ki_flags & IOCB_NOWAIT) {
1023: ret = -EAGAIN;
1024: goto out;
1025: }
1026:
1027: pos = iocb->ki_pos;
1028: written_buffered = btrfs_buffered_write(iocb, from);
1029: if (written_buffered < 0) {
1030: ret = written_buffered;
1031: goto out;
1032: }
1033: /*
1034: * Ensure all data is persisted. We want the next direct IO read to be
1035: * able to read what was just written.
1036: */
1037: endbyte = pos + written_buffered - 1;
1038: ret = btrfs_fdatawrite_range(BTRFS_I(inode), pos, endbyte);
1039: if (ret)
1040: goto out;
1041: ret = filemap_fdatawait_range(inode->i_mapping, pos, endbyte);
1042: if (ret)
1043: goto out;
1044: written += written_buffered;
1045: iocb->ki_pos = pos + written_buffered;
1046: invalidate_mapping_pages(file->f_mapping, pos >> PAGE_SHIFT,
1047: endbyte >> PAGE_SHIFT);
1048: out:
1049: return ret < 0 ? ret : written;
]
|
| 24/2 |
2026/08/10 00:23 |
llm |
expert |
0m
Model:
Tokens:
input: 18411
output: 41
thoughts: 212
Thoughts: **Analyzing Potential Btrfs Write Path Vulnerability**
Okay, so `btrfs_direct_write` apparently isn't truly direct all the time; it falls back to buffered writes, which then kicks off `btrfs_fdatawrite_range`. That function then sets in motion a cascade of writeback operations, hitting `btrfs_writepages`, which then triggers `extent_write_cache_pages`, and then eventually leads to a sequence of calls: `extent_writepage`, `writepage_delalloc`, `btrfs_run_delalloc_range`, `run_delalloc_inline`, all the way down to `__cow_file_range_inline`.
In that innermost function, `__cow_file_range_inline`, things get interesting. We're seeing calls to `btrfs_drop_extents`, `insert_inline_extent`, and `btrfs_update_inode`. My concern is this: if *any* of those calls fail with an error *other* than `-ENOSPC`, it causes the entire transaction to abort. This seems like a potential point of failure.
The question is, can a normal user on a healthy filesystem somehow trigger one of these error paths? It seems unlikely, but it's worth checking out, especially since the code path is indirect. I need to dig into `btrfs_drop_extents` specifically to understand what errors it can actually return. I need to understand this to have any chance of determining whether this behavior poses a realistic security threat or just represents robust error handling.
|
| 25/2 |
2026/08/10 00:23 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/btrfs/inode.c Name:btrfs_drop_extents]
Results: map[SourceCode: 106: /*
107: * this is very complex, but the basic idea is to drop all extents
108: * in the range start - end. hint_block is filled in with a block number
109: * that would be a good hint to the block allocator for this file.
110: *
111: * If an extent intersects the range but is not entirely inside the range
112: * it is either truncated or split. Anything entirely inside the range
113: * is deleted from the tree.
114: *
115: * Note: the VFS' inode number of bytes is not updated, it's up to the caller
116: * to deal with that. We set the field 'bytes_found' of the arguments structure
117: * with the number of allocated bytes found in the target range, so that the
118: * caller can update the inode's number of bytes in an atomic way when
119: * replacing extents in a range to avoid races with stat(2).
120: */
121: int btrfs_drop_extents(struct btrfs_trans_handle *trans,
122: struct btrfs_root *root, struct btrfs_inode *inode,
123: struct btrfs_drop_extents_args *args)
124: {
125: struct btrfs_fs_info *fs_info = root->fs_info;
126: struct extent_buffer *leaf;
127: struct btrfs_file_extent_item *fi;
128: struct btrfs_key key;
129: struct btrfs_key new_key;
130: u64 ino = btrfs_ino(inode);
131: u64 search_start = args->start;
132: u64 disk_bytenr = 0;
133: u64 num_bytes = 0;
134: u64 extent_offset = 0;
135: u64 extent_end = 0;
136: u64 last_end = args->start;
137: int del_nr = 0;
138: int del_slot = 0;
139: int extent_type;
140: int recow;
141: int ret;
142: int modify_tree = -1;
143: int update_refs;
144: bool found = false;
145: struct btrfs_path *path = args->path;
146:
147: args->bytes_found = 0;
148: args->extent_inserted = false;
149:
150: /* Must always have a path if ->replace_extent is true */
151: ASSERT(!(args->replace_extent && !args->path));
152:
153: if (!path) {
154: path = btrfs_alloc_path();
155: if (!path) {
156: ret = -ENOMEM;
157: goto out;
158: }
159: }
160:
161: if (args->drop_cache)
162: btrfs_drop_extent_map_range(inode, args->start, args->end - 1, false);
163:
164: if (data_race(args->start >= inode->disk_i_size) && !args->replace_extent)
165: modify_tree = 0;
166:
167: update_refs = (btrfs_root_id(root) != BTRFS_TREE_LOG_OBJECTID);
168: while (1) {
169: recow = 0;
170: ret = btrfs_lookup_file_extent(trans, root, path, ino,
171: search_start, modify_tree);
172: if (ret < 0)
173: break;
174: if (ret > 0 && path->slots[0] > 0 && search_start == args->start) {
175: leaf = path->nodes[0];
176: btrfs_item_key_to_cpu(leaf, &key, path->slots[0] - 1);
177: if (key.objectid == ino &&
178: key.type == BTRFS_EXTENT_DATA_KEY)
179: path->slots[0]--;
180: }
181: ret = 0;
182: next_slot:
183: leaf = path->nodes[0];
184: if (path->slots[0] >= btrfs_header_nritems(leaf)) {
185: if (WARN_ON(del_nr > 0)) {
186: btrfs_print_leaf(leaf);
187: ret = -EINVAL;
188: break;
189: }
190: ret = btrfs_next_leaf(root, path);
191: if (ret < 0)
192: break;
193: if (ret > 0) {
194: ret = 0;
195: break;
196: }
197: leaf = path->nodes[0];
198: recow = 1;
199: }
200:
201: btrfs_item_key_to_cpu(leaf, &key, path->slots[0]);
202:
203: if (key.objectid > ino)
204: break;
205: if (WARN_ON_ONCE(key.objectid < ino) ||
206: key.type < BTRFS_EXTENT_DATA_KEY) {
207: ASSERT(del_nr == 0);
208: path->slots[0]++;
209: goto next_slot;
210: }
211: if (key.type > BTRFS_EXTENT_DATA_KEY || key.offset >= args->end)
212: break;
213:
214: fi = btrfs_item_ptr(leaf, path->slots[0],
215: struct btrfs_file_extent_item);
216: extent_type = btrfs_file_extent_type(leaf, fi);
217:
218: if (extent_type == BTRFS_FILE_EXTENT_REG ||
219: extent_type == BTRFS_FILE_EXTENT_PREALLOC) {
220: disk_bytenr = btrfs_file_extent_disk_bytenr(leaf, fi);
221: num_bytes = btrfs_file_extent_disk_num_bytes(leaf, fi);
222: extent_offset = btrfs_file_extent_offset(leaf, fi);
223: extent_end = key.offset +
224: btrfs_file_extent_num_bytes(leaf, fi);
225: } else if (extent_type == BTRFS_FILE_EXTENT_INLINE) {
226: extent_end = key.offset +
227: btrfs_file_extent_ram_bytes(leaf, fi);
228: } else {
229: /* can't happen */
230: BUG();
231: }
232:
233: /*
234: * Don't skip extent items representing 0 byte lengths. They
235: * used to be created (bug) if while punching holes we hit
236: * -ENOSPC condition. So if we find one here, just ensure we
237: * delete it, otherwise we would insert a new file extent item
238: * with the same key (offset) as that 0 bytes length file
239: * extent item in the call to setup_items_for_insert() later
240: * in this function.
241: */
242: if (extent_end == key.offset && extent_end >= search_start) {
243: last_end = extent_end;
244: goto delete_extent_item;
245: }
246:
247: if (extent_end <= search_start) {
248: path->slots[0]++;
249: goto next_slot;
250: }
251:
252: found = true;
253: search_start = max(key.offset, args->start);
254: if (recow || !modify_tree) {
255: modify_tree = -1;
256: btrfs_release_path(path);
257: continue;
258: }
259:
260: /*
261: * | - range to drop - |
262: * | -------- extent -------- |
263: */
264: if (args->start > key.offset && args->end < extent_end) {
265: if (WARN_ON(del_nr > 0)) {
266: btrfs_print_leaf(leaf);
267: ret = -EINVAL;
268: break;
269: }
270: if (extent_type == BTRFS_FILE_EXTENT_INLINE) {
271: ret = -EOPNOTSUPP;
272: break;
273: }
274:
275: memcpy(&new_key, &key, sizeof(new_key));
276: new_key.offset = args->start;
277: ret = btrfs_duplicate_item(trans, root, path,
278: &new_key);
279: if (ret == -EAGAIN) {
280: btrfs_release_path(path);
281: continue;
282: }
283: if (ret < 0)
284: break;
285:
286: leaf = path->nodes[0];
287: fi = btrfs_item_ptr(leaf, path->slots[0] - 1,
288: struct btrfs_file_extent_item);
289: btrfs_set_file_extent_num_bytes(leaf, fi,
290: args->start - key.offset);
291:
292: fi = btrfs_item_ptr(leaf, path->slots[0],
293: struct btrfs_file_extent_item);
294:
295: extent_offset += args->start - key.offset;
296: btrfs_set_file_extent_offset(leaf, fi, extent_offset);
297: btrfs_set_file_extent_num_bytes(leaf, fi,
298: extent_end - args->start);
299:
300: if (update_refs && disk_bytenr > 0) {
301: struct btrfs_ref ref = {
302: .action = BTRFS_ADD_DELAYED_REF,
303: .bytenr = disk_bytenr,
304: .num_bytes = num_bytes,
305: .parent = 0,
306: .owning_root = btrfs_root_id(root),
307: .ref_root = btrfs_root_id(root),
308: };
309: btrfs_init_data_ref(&ref, new_key.objectid,
310: args->start - extent_offset,
311: 0, false);
312: ret = btrfs_inc_extent_ref(trans, &ref);
313: if (unlikely(ret)) {
314: btrfs_abort_transaction(trans, ret);
315: break;
316: }
317: }
318: key.offset = args->start;
319: }
320: /*
321: * From here on out we will have actually dropped something, so
322: * last_end can be updated.
323: */
324: last_end = extent_end;
325:
326: /*
327: * | ---- range to drop ----- |
328: * | -------- extent -------- |
329: */
330: if (args->start <= key.offset && args->end < extent_end) {
331: if (extent_type == BTRFS_FILE_EXTENT_INLINE) {
332: ret = -EOPNOTSUPP;
333: break;
334: }
335:
336: memcpy(&new_key, &key, sizeof(new_key));
337: new_key.offset = args->end;
338: btrfs_set_item_key_safe(trans, path, &new_key);
339:
340: extent_offset += args->end - key.offset;
341: btrfs_set_file_extent_offset(leaf, fi, extent_offset);
342: btrfs_set_file_extent_num_bytes(leaf, fi,
343: extent_end - args->end);
344: if (update_refs && disk_bytenr > 0)
345: args->bytes_found += args->end - key.offset;
346: break;
347: }
348:
349: search_start = extent_end;
350: /*
351: * | ---- range to drop ----- |
352: * | -------- extent -------- |
353: */
354: if (args->start > key.offset && args->end >= extent_end) {
355: if (WARN_ON(del_nr > 0)) {
356: btrfs_print_leaf(leaf);
357: ret = -EINVAL;
358: break;
359: }
360: if (extent_type == BTRFS_FILE_EXTENT_INLINE) {
361: ret = -EOPNOTSUPP;
362: break;
363: }
364:
365: btrfs_set_file_extent_num_bytes(leaf, fi,
366: args->start - key.offset);
367: if (update_refs && disk_bytenr > 0)
368: args->bytes_found += extent_end - args->start;
369: if (args->end == extent_end)
370: break;
371:
372: path->slots[0]++;
373: goto next_slot;
374: }
375:
376: /*
377: * | ---- range to drop ----- |
378: * | ------ extent ------ |
379: */
380: if (args->start <= key.offset && args->end >= extent_end) {
381: delete_extent_item:
382: if (del_nr == 0) {
383: del_slot = path->slots[0];
384: del_nr = 1;
385: } else {
386: if (WARN_ON(del_slot + del_nr != path->slots[0])) {
387: btrfs_print_leaf(leaf);
388: ret = -EINVAL;
389: break;
390: }
391: del_nr++;
392: }
393:
394: if (update_refs &&
395: extent_type == BTRFS_FILE_EXTENT_INLINE) {
396: args->bytes_found += extent_end - key.offset;
397: extent_end = ALIGN(extent_end,
398: fs_info->sectorsize);
399: } else if (update_refs && disk_bytenr > 0) {
400: struct btrfs_ref ref = {
401: .action = BTRFS_DROP_DELAYED_REF,
402: .bytenr = disk_bytenr,
403: .num_bytes = num_bytes,
404: .parent = 0,
405: .owning_root = btrfs_root_id(root),
406: .ref_root = btrfs_root_id(root),
407: };
408: btrfs_init_data_ref(&ref, key.objectid,
409: key.offset - extent_offset,
410: 0, false);
411: ret = btrfs_free_extent(trans, &ref);
412: if (unlikely(ret)) {
413: btrfs_abort_transaction(trans, ret);
414: break;
415: }
416: args->bytes_found += extent_end - key.offset;
417: }
418:
419: if (args->end == extent_end)
420: break;
421:
422: if (path->slots[0] + 1 < btrfs_header_nritems(leaf)) {
423: path->slots[0]++;
424: goto next_slot;
425: }
426:
427: ret = btrfs_del_items(trans, root, path, del_slot,
428: del_nr);
429: if (unlikely(ret)) {
430: btrfs_abort_transaction(trans, ret);
431: break;
432: }
433:
434: del_nr = 0;
435: del_slot = 0;
436:
437: btrfs_release_path(path);
438: continue;
439: }
440:
441: BUG();
442: }
443:
444: if (!ret && del_nr > 0) {
445: /*
446: * Set path->slots[0] to first slot, so that after the delete
447: * if items are move off from our leaf to its immediate left or
448: * right neighbor leafs, we end up with a correct and adjusted
449: * path->slots[0] for our insertion (if args->replace_extent).
450: */
451: path->slots[0] = del_slot;
452: ret = btrfs_del_items(trans, root, path, del_slot, del_nr);
453: if (ret)
454: btrfs_abort_transaction(trans, ret);
455: }
456:
457: leaf = path->nodes[0];
458: /*
459: * If btrfs_del_items() was called, it might have deleted a leaf, in
460: * which case it unlocked our path, so check path->locks[0] matches a
461: * write lock.
462: */
463: if (!ret && args->replace_extent &&
464: path->locks[0] == BTRFS_WRITE_LOCK &&
465: btrfs_leaf_free_space(leaf) >=
466: sizeof(struct btrfs_item) + args->extent_item_size) {
467:
468: key.objectid = ino;
469: key.type = BTRFS_EXTENT_DATA_KEY;
470: key.offset = args->start;
471: if (!del_nr && path->slots[0] < btrfs_header_nritems(leaf)) {
472: struct btrfs_key slot_key;
473:
474: btrfs_item_key_to_cpu(leaf, &slot_key, path->slots[0]);
475: if (btrfs_comp_cpu_keys(&key, &slot_key) > 0)
476: path->slots[0]++;
477: }
478: btrfs_setup_item_for_insert(trans, root, path, &key,
479: args->extent_item_size);
480: args->extent_inserted = true;
481: }
482:
483: if (!args->path)
484: btrfs_free_path(path);
485: else if (!args->extent_inserted)
486: btrfs_release_path(path);
487: out:
488: args->drop_end = found ? min(args->end, last_end) : args->end;
489:
490: return ret;
491: }
SourceFile:fs/btrfs/file.c]
|
| 26/2 |
2026/08/10 00:23 |
llm |
expert |
0m
Model:
Tokens:
input: 25033
output: 40
thoughts: 1583
Thoughts: **Analyzing `btrfs_drop_extents` and its Interaction with Inline Extents**
Okay, let's break this down. `btrfs_drop_extents` can return a few errors: `-ENOMEM`, `-EINVAL`, `-EOPNOTSUPP`, and errors from its internal calls. The `-EOPNOTSUPP` is triggered by partially dropping an inline extent, but let's check the context where `btrfs_drop_extents` is being called: specifically inside `__cow_file_range_inline`.
In this case, `btrfs_drop_extents` gets called with `start = 0` and `end = fs_info->sectorsize`. Since the inline extent *starts* at 0 and its length is not greater than `sectorsize`, it should *not* hit the `-EOPNOTSUPP` case, right? It should be handled by `delete_extent_item`, and it is *not* a partial drop. Further, if the inline extent is larger than `sectorsize`, that would be an impossible state, indicating corruption.
Now, `-EINVAL` is triggered by a `WARN_ON(del_nr > 0)` or an unexpected `path->slots[0]`. That case seems unreachable here. `args->start` is 0, which means the condition `args->start > key.offset` will always be false, as `key.offset` cannot be negative.
The remaining errors are from calls like `btrfs_inc_extent_ref` and `btrfs_free_extent`, where we could encounter `-ENOMEM` or `-ENOSPC`. If we get `-ENOSPC`, `btrfs_drop_extents` returns `-ENOSPC` and inside `__cow_file_range_inline`, there's `btrfs_abort_transaction` when `btrfs_drop_extents` returns ANY error code, *including* `-ENOSPC`. So, is it even possible for `btrfs_drop_extents` to return `-ENOSPC`?
Yes, absolutely. If `btrfs_inc_extent_ref` or `btrfs_free_extent` need to allocate a new tree block, it could fail due to a full filesystem. But, hold on a second! `btrfs_drop_extents` *is* called with a transaction handle. The transaction, in theory, *should* have reserved enough space. However, it looks like, here in `__cow_file_range_inline`, the transaction is joined: `trans = btrfs_join_transaction(root);` meaning it *doesn't* reserve space! It simply grabs an existing transaction. Then it sets `trans->block_rsv = &inode->block_rsv;`. If the `inode->block_rsv` *doesn't* have enough space, we can hit `-ENOSPC` in `btrfs_drop_extents`.
Here is the kicker: `__cow_file_range_inline` handles `-ENOSPC` for *other* operations, like `insert_inline_extent` or `btrfs_update_inode`. But *it doesn't* handle `-ENOSPC` from `btrfs_drop_extents` at all. Meaning that `-ENOSPC` will cause an abort!
So, can `btrfs_drop_extents` *actually* return `-ENOSPC` in this scenario? Definitely. It could need to modify the B-tree when `inode->block_rsv` is exhausted. With `start = 0` and `end = fs_info->sectorsize`, if there's a large extent already starting at 0 (e.g., from 0 to 1MB), `btrfs_drop_extents` would have to split it. We'd have to insert a new extent item, potentially requiring a tree split, which means more space. If `inode->block_rsv` is empty, it'll fail. But how could a large extent even start at 0, if the purpose is the copy on write inline? The existing inline content needs to be checked.
|
| 27/2 |
2026/08/10 00:24 |
tool |
codesearch-definition-source |
0m
Error:
Args: map[ContextFile:fs/btrfs/inode.c Name:cow_file_range_inline]
Results: map[SourceCode: SourceFile:]
|
| 28/2 |
2026/08/10 00:24 |
llm |
expert |
0m
Model:
Tokens:
input: 27595
output: 39
thoughts: 23
Thoughts: Here's my thought process, summarized as you requested:
**Searching for Key Optimization Techniques**
Okay, let's dive in. I need to refresh my memory on some specific optimization techniques that could be relevant here. I'm thinking about how the system handles file ranges, and I seem to recall some potential inlining strategies that were discussed. Let's start with a focused search. My initial instinct is to look for instances of the terms `cow_file_range_inline` or `run_delalloc_inline`. Those sound like the kinds of functions or concepts that might be central to the issue I'm currently investigating. Hopefully, by searching for these specific terms, I'll be able to quickly zero in on the relevant code or documentation and get a handle on what needs to be understood or refactored.
|
| 29/2 |
2026/08/10 00:24 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/btrfs/inode.c Name:run_delalloc_inline]
Results: map[SourceCode:2302: /*
2303: * Return 0 if an inlined extent is created successfully.
2304: * Return <0 if critical error happened.
2305: * Return >0 if an inline extent can not be created.
2306: */
2307: static int run_delalloc_inline(struct btrfs_inode *inode, struct folio *locked_folio)
2308: {
2309: struct btrfs_fs_info *fs_info = inode->root->fs_info;
2310: struct compressed_bio *cb = NULL;
2311: struct extent_state *cached = NULL;
2312: const u64 i_size = i_size_read(&inode->vfs_inode);
2313: const u32 blocksize = fs_info->sectorsize;
2314: int compress_type = fs_info->compress_type;
2315: int compress_level = fs_info->compress_level;
2316: u32 compressed_size = 0;
2317: int ret;
2318:
2319: ASSERT(folio_pos(locked_folio) == 0);
2320:
2321: if (btrfs_inode_can_compress(inode) &&
2322: inode_need_compress(inode, 0, blocksize, true)) {
2323: if (inode->defrag_compress > 0 &&
2324: inode->defrag_compress < BTRFS_NR_COMPRESS_TYPES) {
2325: compress_type = inode->defrag_compress;
2326: compress_level = inode->defrag_compress_level;
2327: } else if (inode->prop_compress) {
2328: compress_type = inode->prop_compress;
2329: }
2330: cb = btrfs_compress_bio(inode, 0, blocksize, compress_type, compress_level, 0);
2331: if (IS_ERR(cb)) {
2332: cb = NULL;
2333: /* Just fall back to non-compressed case. */
2334: } else {
2335: compressed_size = cb->bbio.bio.bi_iter.bi_size;
2336: }
2337: }
2338: if (!can_cow_file_range_inline(inode, 0, i_size, compressed_size)) {
2339: if (cb)
2340: cleanup_compressed_bio(cb);
2341: return 1;
2342: }
2343:
2344: btrfs_lock_extent(&inode->io_tree, 0, blocksize - 1, &cached);
2345: if (cb) {
2346: ret = __cow_file_range_inline(inode, i_size, compressed_size, compress_type,
2347: bio_first_folio_all(&cb->bbio.bio), false);
2348: cleanup_compressed_bio(cb);
2349: cb = NULL;
2350: } else {
2351: ret = __cow_file_range_inline(inode, i_size, 0, BTRFS_COMPRESS_NONE,
2352: NULL, false);
2353: }
2354: /*
2355: * We failed to insert inline extent due to lack of meta space.
2356: * Just unlock the extent io range and fallback to regular COW/NOCOW path.
2357: */
2358: if (ret > 0) {
2359: btrfs_unlock_extent(&inode->io_tree, 0, blocksize - 1, &cached);
2360: return ret;
2361: }
2362:
2363: /*
2364: * In the successful case (ret == 0 here), btrfs_run_delalloc_range()
2365: * will return 1.
2366: *
2367: * Quite a bit further up the callstack in extent_writepage(), ret == 1
2368: * is treated as a short circuited success and does not unlock the folio,
2369: * so we must do it here.
2370: *
2371: * For failure case, the @locked_folio does get unlocked by
2372: * btrfs_folio_end_lock_bitmap(), so we must *not* unlock it here.
2373: *
2374: * So if ret == 0, we let extent_clear_unlock_delalloc() to unlock the
2375: * folio by passing NULL as @locked_folio.
2376: * Otherwise pass @locked_folio as usual.
2377: */
2378: if (ret == 0)
2379: locked_folio = NULL;
2380: extent_clear_unlock_delalloc(inode, 0, blocksize - 1, locked_folio, &cached,
2381: EXTENT_DELALLOC | EXTENT_DELALLOC_NEW | EXTENT_DEFRAG |
2382: EXTENT_DO_ACCOUNTING | EXTENT_LOCKED,
2383: PAGE_UNLOCK | PAGE_START_WRITEBACK | PAGE_END_WRITEBACK);
2384: return ret;
2385: }
SourceFile:fs/btrfs/inode.c]
|
| 30/2 |
2026/08/10 00:24 |
llm |
expert |
0m
Model:
Tokens:
input: 29323
output: 42
thoughts: 17
Thoughts: Okay, here's my interpretation of that thought process, tailored for an expert audience, and written in the first person:
**Inline Range File Considerations**
Right, `can_cow_file_range_inline`. This is a crucial check. We're asking, can we efficiently handle a file range operation with inlining? This isn't just a simple yes/no. We need to consider a cascade of factors. The first thing I'm thinking is about the underlying storage. Is it even *suitable* for inlining? We have to evaluate whether the file system and hardware are amenable to it.
Then I am considering the COW (Copy-on-Write) aspect, obviously. Inlining implies less overhead, but does it make sense to put that overhead on the performance? Are we dealing with something that would benefit more from in-place updates, or might we save time from writing a copy? What is the frequency of write? Is it likely the data will be modified, or is it mostly read?
I have to factor in the size of the range. Small ranges? Potentially a slam dunk for inlining. But gargantuan ranges? Inlining might be a terrible idea, resulting in significant overhead and performance impact. Also, I am thinking about how `can_cow_file_range_inline` is implemented. Is it a quick lookup in some sort of metadata, or are we potentially performing more expensive checks? The more complex the checks, the more we have to think about making sure that the gain of inlining is higher than the loss of the lookup. Finally, I'm already anticipating the implementation.
|
| 31/2 |
2026/08/10 00:24 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/btrfs/inode.c Name:can_cow_file_range_inline]
Results: map[SourceCode: 543: static bool can_cow_file_range_inline(struct btrfs_inode *inode,
544: u64 offset, u64 size,
545: size_t compressed_size)
546: {
547: struct btrfs_fs_info *fs_info = inode->root->fs_info;
548: u64 data_len = (compressed_size ?: size);
549:
550: /* Inline extents must start at offset 0. */
551: if (offset != 0)
552: return false;
553:
554: /*
555: * Even for bs > ps cases, cow_file_range_inline() can only accept a
556: * single folio.
557: *
558: * This can be problematic and cause access beyond page boundary if a
559: * page sized folio is passed into that function.
560: * And encoded write is doing exactly that.
561: * So here limits the inlined extent size to PAGE_SIZE.
562: */
563: if (size > PAGE_SIZE || compressed_size > PAGE_SIZE)
564: return false;
565:
566: /* Inline extents are limited to sectorsize. */
567: if (size > fs_info->sectorsize)
568: return false;
569:
570: /* We do not allow a non-compressed extent to be as large as block size. */
571: if (data_len >= fs_info->sectorsize)
572: return false;
573:
574: /* We cannot exceed the maximum inline data size. */
575: if (data_len > BTRFS_MAX_INLINE_DATA_SIZE(fs_info))
576: return false;
577:
578: /* We cannot exceed the user specified max_inline size. */
579: if (data_len > fs_info->max_inline)
580: return false;
581:
582: /* Inline extents must be the entirety of the file. */
583: if (size < i_size_read(&inode->vfs_inode))
584: return false;
585:
586: /* Encrypted file cannot be inlined. */
587: if (IS_ENCRYPTED(&inode->vfs_inode))
588: return false;
589:
590: return true;
591: }
SourceFile:fs/btrfs/inode.c]
|
| 32/2 |
2026/08/10 00:24 |
llm |
expert |
0m
Model:
Tokens:
input: 30477
output: 46
thoughts: 684
Thoughts: **Analyzing the Inlining Logic**
Okay, let's break this down. It seems the function `can_cow_file_range_inline` is a crucial check, specifically, it compares `size`, which comes from `run_delalloc_inline`, against the file's current size as read by `i_size_read(&inode->vfs_inode)`. At first glance, it appears to immediately return false, because the size passed in is the current size.
However, a closer look reveals a potential flaw. `run_delalloc_inline` passes `i_size_read(&inode->vfs_inode)` (the current file size) as `size`. So, the comparison `size < i_size_read` can't return true, but what if the file has existing extents that are larger than `sectorsize`? `can_cow_file_range_inline` doesn't actually check that. It seems to only verify that the size being inlined is less than or equal to `sectorsize`.
So, here's the scenario: if the file size is, for instance, truncated to be less than or equal to `sectorsize`, the inlining check, `can_cow_file_range_inline`, permits the inlining process. Subsequently, `__cow_file_range_inline` calls `btrfs_drop_extents` with a start of 0 and an end of `sectorsize`. This is where it gets interesting.
Ideally, if the file was truncated, the extents *beyond* the new file size should have been dropped already. But what if, for some reason (pre-allocated extents, or a previous truncation failure), extents still exist beyond `sectorsize`? `btrfs_drop_extents` is called, and it's designed to *drop* or split extents within that range (0 to `sectorsize`). If we have a big extent from 0 to 1MB, this function splits the extent, creating potentially new tree blocks, and therefore it requires space for those allocations.
This is where the problem arises. If `inode->block_rsv` (the inode's block reservation) doesn't have enough space to allocate these new tree blocks, we get an `-ENOSPC` error, and the entire transaction aborts! Essentially, the inlining attempt fails, which is probably not what we want.
Now, can we have a situation where a file has an extent from 0 to 1MB, *and* its `i_size` is less than or equal to `sectorsize`? Yes, consider using `fallocate` to preallocate a 1MB region. Then, with an initial `i_size` of 0, we write a 10-byte file at offset 0. Now `i_size` becomes 10 (or whatever), and `can_cow_file_range_inline` returns true.
`__cow_file_range_inline` is then called, and `btrfs_drop_extents(0, sectorsize)` tries to drop extents from 0 to `sectorsize`, which in this example, splits the 1MB preallocated extent. This requires allocating space, and the question is, is there enough space reserved by `inode->block_rsv`? When we write 10 bytes, space *was* reserved for that write. The reservation is for 1 sector of data, because it's sector aligned, so that seems ok. However, is that enough to split an extent and allocate the required tree blocks? This is where we need to check the function `btrfs_delalloc_reserve_metadata` for further information on metadata reservation. It looks like it is not.
|
| 33/2 |
2026/08/10 00:24 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/btrfs/delalloc-space.c Name:btrfs_delalloc_reserve_metadata]
Results: map[SourceCode: 317: int btrfs_delalloc_reserve_metadata(struct btrfs_inode *inode, u64 num_bytes,
318: u64 disk_num_bytes, bool noflush)
319: {
320: struct btrfs_root *root = inode->root;
321: struct btrfs_fs_info *fs_info = root->fs_info;
322: struct btrfs_block_rsv *block_rsv = &inode->block_rsv;
323: u64 meta_reserve, qgroup_reserve;
324: unsigned nr_extents;
325: enum btrfs_reserve_flush_enum flush = BTRFS_RESERVE_FLUSH_ALL;
326: int ret = 0;
327:
328: /*
329: * If we are a free space inode we need to not flush since we will be in
330: * the middle of a transaction commit. We also don't need the delalloc
331: * mutex since we won't race with anybody. We need this mostly to make
332: * lockdep shut its filthy mouth.
333: *
334: * If we have a transaction open (can happen if we call truncate_block
335: * from truncate), then we need FLUSH_LIMIT so we don't deadlock.
336: */
337: if (noflush || btrfs_is_free_space_inode(inode)) {
338: flush = BTRFS_RESERVE_NO_FLUSH;
339: } else {
340: if (current->journal_info)
341: flush = BTRFS_RESERVE_FLUSH_LIMIT;
342: }
343:
344: num_bytes = ALIGN(num_bytes, fs_info->sectorsize);
345: disk_num_bytes = ALIGN(disk_num_bytes, fs_info->sectorsize);
346:
347: /*
348: * We always want to do it this way, every other way is wrong and ends
349: * in tears. Pre-reserving the amount we are going to add will always
350: * be the right way, because otherwise if we have enough parallelism we
351: * could end up with thousands of inodes all holding little bits of
352: * reservations they were able to make previously and the only way to
353: * reclaim that space is to ENOSPC out the operations and clear
354: * everything out and try again, which is bad. This way we just
355: * over-reserve slightly, and clean up the mess when we are done.
356: */
357: calc_inode_reservations(inode, num_bytes, disk_num_bytes,
358: &meta_reserve, &qgroup_reserve);
359: ret = btrfs_qgroup_reserve_meta_prealloc(root, qgroup_reserve, true,
360: noflush);
361: if (ret)
362: return ret;
363: ret = btrfs_reserve_metadata_bytes(block_rsv->space_info, meta_reserve,
364: flush);
365: if (ret) {
366: btrfs_qgroup_free_meta_prealloc(root, qgroup_reserve);
367: return ret;
368: }
369:
370: /*
371: * Now we need to update our outstanding extents and csum bytes _first_
372: * and then add the reservation to the block_rsv. This keeps us from
373: * racing with an ordered completion or some such that would think it
374: * needs to free the reservation we just made.
375: */
376: nr_extents = count_max_extents(fs_info, num_bytes);
377: spin_lock(&inode->lock);
378: btrfs_mod_outstanding_extents(inode, nr_extents);
379: if (!(inode->flags & BTRFS_INODE_NODATASUM))
380: inode->csum_bytes += disk_num_bytes;
381: btrfs_calculate_inode_block_rsv_size(fs_info, inode);
382: spin_unlock(&inode->lock);
383:
384: /* Now we can safely add our space to our block rsv */
385: btrfs_block_rsv_add_bytes(block_rsv, meta_reserve, false);
386: trace_btrfs_space_reservation(root->fs_info, "delalloc",
387: btrfs_ino(inode), meta_reserve, 1);
388:
389: spin_lock(&block_rsv->lock);
390: block_rsv->qgroup_rsv_reserved += qgroup_reserve;
391: spin_unlock(&block_rsv->lock);
392:
393: return 0;
394: }
SourceFile:fs/btrfs/delalloc-space.c]
|
| 34/2 |
2026/08/10 00:24 |
llm |
expert |
0m
Model:
Tokens:
input: 33471
output: 37
thoughts: 458
Thoughts: **Analyzing a Btrfs ENOSPC Error**
Okay, so I'm trying to figure out why this Btrfs operation is failing with an out of space error, specifically `-ENOSPC`. My initial thought was that the `calc_inode_reservations` function calculates the right reservation size. It accounts for `nr_extents` items and checksums. If I'm writing 10 bytes, then I only need to reserve space for one extent insertion. I figured that even if `btrfs_drop_extents` needs to split an extent, that would still only need one extent item insertion. Even if it's a replacement (delete old, insert new) or a split (modify old, insert new), it's still just one insertion, right?
So, the reservation *should* be sufficient. `inode->block_rsv` *should* have enough space. Why am I getting `-ENOSPC`? Maybe the problem isn't the reservation calculation itself.
Let's dig deeper. The crash dump, from syzkaller, it specifically points to `WARNING: fs/btrfs/inode.c:641 at __cow_file_range_inline`. Line 641... okay:
```c
ret = btrfs_drop_extents(trans, root, inode, &drop_args);
if (unlikely(ret)) {
btrfs_abort_transaction(trans, ret);
goto out;
}
```
Aha! `btrfs_drop_extents` *itself* is returning an error. So it's not a reservation problem, it's an error from the core extent dropping logic. What error is it? Let's check the registers.
`R10: 00000000fffffffb R11: 0000000000000002 R12: dffffc0000000000`. Wait a minute, `R10` is `fffffffb`. In a signed context, that's `-5`. `-EIO`. So the error is I/O related, not space-related! Okay, I need to investigate *why* `btrfs_drop_extents` is failing with I/O in this specific scenario. I need to check the assembly.
|
| 35/2 |
2026/08/10 00:24 |
tool |
grepper |
0m
Args: map[Expression:__cow_file_range_inline PathPrefix:fs/btrfs/inode.c]
Results: map[Output:fs/btrfs/inode.c=543=static bool can_cow_file_range_inline(struct btrfs_inode *inode,
--
fs/btrfs/inode.c-604- */
fs/btrfs/inode.c:605:static noinline int __cow_file_range_inline(struct btrfs_inode *inode,
fs/btrfs/inode.c-606- u64 size, size_t compressed_size,
--
fs/btrfs/inode.c=2307=static int run_delalloc_inline(struct btrfs_inode *inode, struct folio *locked_folio)
--
fs/btrfs/inode.c-2345- if (cb) {
fs/btrfs/inode.c:2346: ret = __cow_file_range_inline(inode, i_size, compressed_size, compress_type,
fs/btrfs/inode.c-2347- bio_first_folio_all(&cb->bbio.bio), false);
--
fs/btrfs/inode.c-2350- } else {
fs/btrfs/inode.c:2351: ret = __cow_file_range_inline(inode, i_size, 0, BTRFS_COMPRESS_NONE,
fs/btrfs/inode.c-2352- NULL, false);
--
fs/btrfs/inode.c=9889=ssize_t btrfs_do_encoded_write(struct kiocb *iocb, struct iov_iter *from,
--
fs/btrfs/inode.c-10064- can_cow_file_range_inline(inode, start, encoded->len, orig_count)) {
fs/btrfs/inode.c:10065: ret = __cow_file_range_inline(inode, encoded->len,
fs/btrfs/inode.c-10066- orig_count, compression,
]
|
| 36/2 |
2026/08/10 00:24 |
llm |
expert |
0m
Model:
Tokens:
input: 35006
output: 36
thoughts: 2844
Thoughts: **Analyzing a Btrfs Transaction Abort: A Deep Dive into -ENOSPC**
Okay, so I'm looking at this assembly code, and it seems like we've got a Btrfs transaction abort happening. The core of the problem seems to be an `-ENOSPC` error being returned, specifically by `btrfs_drop_extents`. The assembly clearly shows a comparison against `0xffffffe4`, which corresponds to `-ENOSPC` and a subsequent abort triggered when `btrfs_drop_extents` fails and returns the error. `r13d` appears to hold the error code.
`btrfs_abort_transaction` is then called, which is a big red flag in Btrfs because it often means we're going read-only. It's calling `btrfs_abort_should_print_stack`, which, in turn, checks if the error is `-EIO`, `-EROFS`, or `-ENOMEM`. It doesn't print the stack on this case, which is expected.
I know `btrfs_drop_extents` is involved in deleting and/or modifying the B-tree, and the issue is likely rooted in running out of space. Initially, it seems counterintuitive because in `__cow_file_range_inline`, if we run out of space, why not fallback to non-inline? But the problem is this: `btrfs_drop_extents` is a tree-modifying operation, and if it fails mid-flight, we're left with an inconsistent tree.
I start thinking about the space reservation and it seems `inode->block_rsv` is the culprit. We know the reservation should have been done but might not be enough. This is related to `btrfs_delalloc_reserve_metadata` but also `btrfs_duplicate_item` and `btrfs_search_slot`. `btrfs_duplicate_item` modifies the subvolume tree and uses `trans->block_rsv`, not `inode->block_rsv`. It makes the situation more complex.
My thought is, it looks like `btrfs_delalloc_reserve_metadata` may not be reserving enough space, or something is stealing the reserved space, or there's a problem in the reservation logic itself when the tree is very deep. It appears a bug in BTRFS is that `btrfs_drop_extents` may return `-ENOSPC` when it tries to split an extent and doesn't have enough space in the block reserve.
Since we are doing a `drop_args.replace_extent = true` this means we have to drop and *then* insert a new extent item, which, if it fails because of `-ENOSPC`, would be a significant bug related to BTRFS's reservation mechanism. If this happened, then we can have a DoS condition.
This makes me consider if an unprivileged user can trigger this. If so, they can likely cause a DoS by crafting a specific write sequence on a nearly full filesystem. This could be due to a bug, a malicious trigger, or a corrupted filesystem. If the filesystem tree is corrupted, COW might require more space, causing `-ENOSPC`. So, I'll need to check if Syzkaller is exploiting a corrupted filesystem and if this particular situation is known.
|
| 37/2 |
2026/08/10 00:24 |
tool |
grepper |
0m
Error:
Args: map[Expression:btrfs: fix transaction abort in __cow_file_range_inline PathPrefix:]
Results:
|
| 38/2 |
2026/08/10 00:24 |
llm |
expert |
0m
Model:
Tokens:
input: 38629
output: 54
thoughts: 23
Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience:
**Investigating Potential Resource Exhaustion in `__cow_file_range_inline`**
Alright, time to dig in. My initial thought process is leading me to hypothesize a potential resource exhaustion issue related to the `__cow_file_range_inline` function, and I'm also suspecting a connection to the `ENOSPC` error code. It's a hunch based on past experiences and the symptoms I'm seeing, which suggest a possible inability to allocate necessary space or resources at a low level in the kernel. My approach will be a targeted investigation. I'm going to leverage `git log` to pinpoint any recent modifications or commits that touch upon both `__cow_file_range_inline` and `ENOSPC`. This dual search will help me narrow the scope considerably. I'm hoping to find changes related to copy-on-write functionality and disk space management, specifically any adjustments around how file ranges are handled when dealing with potential out-of-space conditions. This will hopefully lead me to an understanding, allowing me to start evaluating the root cause of the issue.
|
| 39/2 |
2026/08/10 00:24 |
tool |
git-log |
0m
Error:
Args: map[CodeRegexp:ENOSPC Count:10 PathPrefix:fs/btrfs/inode.c SymbolName:__cow_file_range_inline]
Results:
|
| 40/2 |
2026/08/10 00:24 |
llm |
expert |
0m
Model:
Tokens:
input: 38975
output: 54
thoughts: 0
|
| 41/2 |
2026/08/10 00:25 |
tool |
git-log |
0m
Args: map[CodeRegexp:ENOSPC Count:10 SourcePath:fs/btrfs/inode.c SymbolName:__cow_file_range_inline]
Results: map[Output:6d0f25cdd8e3 btrfs: update stale comment in __cow_file_range_inline()
c1c050f92d8f btrfs: fix reservation leak in some error paths when inserting inline extent
f8da41de0bff btrfs: do not free data reservation in fallback from inline due to -ENOSPC
a929904cf73b btrfs: add unlikely annotations to branches leading to transaction abort
0bb067ca64e3 btrfs: fix the qgroup data free range for inline data extents
e469da5d8414 btrfs: drop unused parameter offset from __cow_file_range_inline()
cd241a8f554c btrfs: move can_cow_file_range_inline() outside of the extent lock
0586d0a89e77 btrfs: move extent bit and page cleanup into cow_file_range_inline
6eecfa22403e btrfs: push all inline logic into cow_file_range
ae0d22a7fcb6 btrfs: migrate insert_inline_extent() to folio interfaces
]
|
| 42/2 |
2026/08/10 00:25 |
llm |
expert |
0m
Model:
Tokens:
input: 39322
output: 46
thoughts: 28
Thoughts: Here's my thought process, as requested:
**Investigating a Potential Btrfs Extent Drop Issue**
Alright, let's dive in. My immediate thought is, "I need to track down that specific bug fix for the `-ENOSPC` return in `btrfs_drop_extents`." This seems like a critical issue, probably related to some form of space management problem or metadata corruption, so I want to understand the root cause and the solution implemented. I'm thinking I'll start by checking the commit history for relevant changes. I need to see exactly how this was addressed in the past, any potential regression, if this behavior exists currently, and what might've been the circumstances around it. I'm going to look for keyword combinations like "btrfs", "drop_extents", "ENOSPC", "out of space", or anything related to allocation failures. The specifics of the commit descriptions and the code diffs will be key to understanding the full picture. My goal is to quickly pinpoint the specific commit(s) responsible for fixing this issue and understand the changes implemented.
|
| 43/2 |
2026/08/10 00:25 |
tool |
git-log |
0m
Args: map[CodeRegexp:btrfs_drop_extents.*ENOSPC Count:10 PathPrefix:fs/btrfs/]
Results:
|
| 44/2 |
2026/08/10 00:25 |
llm |
expert |
0m
Model:
Tokens:
input: 39643
output: 60
thoughts: 25
Thoughts: Here's my thought process, as if I were considering this search:
**Diving into Btrfs Transaction Aborts**
Okay, so I'm thinking about how transaction aborts in Btrfs might play out within the `__cow_file_range_inline` function. This suggests we're dealing with a specific path related to copy-on-write operations, which makes sense. I need to understand the potential interactions there. I'm imagining situations where a transaction might need to be aborted during this inline COW process, perhaps due to resource constraints, errors, or conflict detection. My initial thought is that `btrfs_abort_transaction` likely gets called if these conditions are met. I'll need to explore that code path to understand precisely why and how. Time to dive into the code... I'll start by searching for the function call directly.
|
| 45/2 |
2026/08/10 00:25 |
tool |
git-log |
0m
Args: map[CodeRegexp:btrfs_abort_transaction.*ret Count:10 SourcePath:fs/btrfs/inode.c SymbolName:__cow_file_range_inline]
Results: map[Output:6d0f25cdd8e3 btrfs: update stale comment in __cow_file_range_inline()
c1c050f92d8f btrfs: fix reservation leak in some error paths when inserting inline extent
f8da41de0bff btrfs: do not free data reservation in fallback from inline due to -ENOSPC
a929904cf73b btrfs: add unlikely annotations to branches leading to transaction abort
0bb067ca64e3 btrfs: fix the qgroup data free range for inline data extents
e469da5d8414 btrfs: drop unused parameter offset from __cow_file_range_inline()
cd241a8f554c btrfs: move can_cow_file_range_inline() outside of the extent lock
0586d0a89e77 btrfs: move extent bit and page cleanup into cow_file_range_inline
6eecfa22403e btrfs: push all inline logic into cow_file_range
ae0d22a7fcb6 btrfs: migrate insert_inline_extent() to folio interfaces
]
|
| 46/2 |
2026/08/10 00:25 |
llm |
expert |
0m
Model:
Tokens:
input: 40204
output: 26
thoughts: 17
Thoughts: Okay, here's my interpretation of that thought process, tailored for an expert audience, and written in the first person:
**Analyzing a Specific Hash**
Alright, let's dive into this. The initial impulse is to examine `a929904cf73b`. My immediate thought is, what's the context here? Is this a file hash, a commit hash, a cryptographic key fingerprint, or something else entirely? The brevity of the identifier suggests it's likely a relatively specific piece of data. My brain is already starting to cross-reference this with my mental database of recently encountered hashes, projects, and potential areas of concern. Is there any existing tooling or known vulnerabilities associated with this particular hexadecimal sequence, or any pattern within it? I'm already anticipating the next steps: a `grep` for this string in logs, a lookup against known threat intelligence feeds, perhaps a quick hash algorithm identification if I don't already know it from its length. Time to start the investigation.
|
| 47/2 |
2026/08/10 00:25 |
tool |
git-show |
0m
Args:
Results: map[Output:
Full output is too long, showing 1000 out of 2091 lines.
[commit a929904cf73b650f49cc60941e6e618240096fcb
Author: David Sterba <dsterba@suse.com>
Date: Wed Sep 17 19:53:56 2025 +0200
btrfs: add unlikely annotations to branches leading to transaction abort
The unlikely() annotation is a static prediction hint that compiler may
use to reorder code out of hot path. We use it elsewhere (namely
tree-checker.c) for error branches that almost never happen.
Transaction abort is one such error, the btrfs_abort_transaction()
inlines code to check the state and print a warning, this ought to be
out of the hot path.
The most common pattern is when transaction abort is called after
checking a return value and the control flow leads to a quick return.
In other cases it may not be necessary to add unlikely() e.g. when the
function returns anyway or the control flow is not changed noticeably.
Reviewed-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
diff --git a/fs/btrfs/block-group.c b/fs/btrfs/block-group.c
index 6f7974060a1a..4330f5ba02dd 100644
--- a/fs/btrfs/block-group.c
+++ b/fs/btrfs/block-group.c
@@ -3248,7 +3248,7 @@ static int cache_save_setup(struct btrfs_block_group *block_group,
*/
BTRFS_I(inode)->generation = 0;
ret = btrfs_update_inode(trans, BTRFS_I(inode));
- if (ret) {
+ if (unlikely(ret)) {
/*
* So theoretically we could recover from this, simply set the
* super cache generation to 0 so we know to invalidate the
@@ -3995,7 +3995,7 @@ static struct btrfs_block_group *do_chunk_alloc(struct btrfs_trans_handle *trans
struct btrfs_space_info *sys_space_info;
sys_space_info = btrfs_find_space_info(trans->fs_info, sys_flags);
- if (!sys_space_info) {
+ if (unlikely(!sys_space_info)) {
ret = -EINVAL;
btrfs_abort_transaction(trans, ret);
goto out;
@@ -4009,17 +4009,17 @@ static struct btrfs_block_group *do_chunk_alloc(struct btrfs_trans_handle *trans
}
ret = btrfs_chunk_alloc_add_chunk_item(trans, sys_bg);
- if (ret) {
+ if (unlikely(ret)) {
btrfs_abort_transaction(trans, ret);
goto out;
}
ret = btrfs_chunk_alloc_add_chunk_item(trans, bg);
- if (ret) {
+ if (unlikely(ret)) {
btrfs_abort_transaction(trans, ret);
goto out;
}
- } else if (ret) {
+ } else if (unlikely(ret)) {
btrfs_abort_transaction(trans, ret);
goto out;
}
diff --git a/fs/btrfs/ctree.c b/fs/btrfs/ctree.c
index 71256ed909f0..561658aca018 100644
--- a/fs/btrfs/ctree.c
+++ b/fs/btrfs/ctree.c
@@ -293,11 +293,11 @@ int btrfs_copy_root(struct btrfs_trans_handle *trans,
if (new_root_objectid == BTRFS_TREE_RELOC_OBJECTID) {
ret = btrfs_inc_ref(trans, root, cow, 1);
- if (ret)
+ if (unlikely(ret))
btrfs_abort_transaction(trans, ret);
} else {
ret = btrfs_inc_ref(trans, root, cow, 0);
- if (ret)
+ if (unlikely(ret))
btrfs_abort_transaction(trans, ret);
}
if (ret) {
@@ -536,14 +536,14 @@ int btrfs_force_cow_block(struct btrfs_trans_handle *trans,
write_extent_buffer_fsid(cow, fs_info->fs_devices->metadata_uuid);
ret = update_ref_for_cow(trans, root, buf, cow, &last_ref);
- if (ret) {
+ if (unlikely(ret)) {
btrfs_abort_transaction(trans, ret);
goto error_unlock_cow;
}
if (test_bit(BTRFS_ROOT_SHAREABLE, &root->state)) {
ret = btrfs_reloc_cow_block(trans, root, buf, cow);
- if (ret) {
+ if (unlikely(ret)) {
btrfs_abort_transaction(trans, ret);
goto error_unlock_cow;
}
@@ -556,7 +556,7 @@ int btrfs_force_cow_block(struct btrfs_trans_handle *trans,
parent_start = buf->start;
ret = btrfs_tree_mod_log_insert_root(root->node, cow, true);
- if (ret < 0) {
+ if (unlikely(ret < 0)) {
btrfs_abort_transaction(trans, ret);
goto error_unlock_cow;
}
@@ -567,7 +567,7 @@ int btrfs_force_cow_block(struct btrfs_trans_handle *trans,
parent_start, last_ref);
free_extent_buffer(buf);
add_root_to_dirty_list(root);
- if (ret < 0) {
+ if (unlikely(ret < 0)) {
btrfs_abort_transaction(trans, ret);
goto error_unlock_cow;
}
@@ -575,7 +575,7 @@ int btrfs_force_cow_block(struct btrfs_trans_handle *trans,
WARN_ON(trans->transid != btrfs_header_generation(parent));
ret = btrfs_tree_mod_log_insert_key(parent, parent_slot,
BTRFS_MOD_LOG_KEY_REPLACE);
- if (ret) {
+ if (unlikely(ret)) {
btrfs_abort_transaction(trans, ret);
goto error_unlock_cow;
}
@@ -586,14 +586,14 @@ int btrfs_force_cow_block(struct btrfs_trans_handle *trans,
btrfs_mark_buffer_dirty(trans, parent);
if (last_ref) {
ret = btrfs_tree_mod_log_free_eb(buf);
- if (ret) {
+ if (unlikely(ret)) {
btrfs_abort_transaction(trans, ret);
goto error_unlock_cow;
}
}
ret = btrfs_free_tree_block(trans, btrfs_root_id(root), buf,
parent_start, last_ref);
- if (ret < 0) {
+ if (unlikely(ret < 0)) {
btrfs_abort_transaction(trans, ret);
goto error_unlock_cow;
}
@@ -922,7 +922,7 @@ static noinline int balance_level(struct btrfs_trans_handle *trans,
}
ret = btrfs_tree_mod_log_insert_root(root->node, child, true);
- if (ret < 0) {
+ if (unlikely(ret < 0)) {
btrfs_tree_unlock(child);
free_extent_buffer(child);
btrfs_abort_transaction(trans, ret);
@@ -944,7 +944,7 @@ static noinline int balance_level(struct btrfs_trans_handle *trans,
ret = btrfs_free_tree_block(trans, btrfs_root_id(root), mid, 0, 1);
/* once for the root ptr */
free_extent_buffer_stale(mid);
- if (ret < 0) {
+ if (unlikely(ret < 0)) {
btrfs_abort_transaction(trans, ret);
goto out;
}
@@ -1019,7 +1019,7 @@ static noinline int balance_level(struct btrfs_trans_handle *trans,
right, 0, 1);
free_extent_buffer_stale(right);
right = NULL;
- if (ret < 0) {
+ if (unlikely(ret < 0)) {
btrfs_abort_transaction(trans, ret);
goto out;
}
@@ -1028,7 +1028,7 @@ static noinline int balance_level(struct btrfs_trans_handle *trans,
btrfs_node_key(right, &right_key, 0);
ret = btrfs_tree_mod_log_insert_key(parent, pslot + 1,
BTRFS_MOD_LOG_KEY_REPLACE);
- if (ret < 0) {
+ if (unlikely(ret < 0)) {
btrfs_abort_transaction(trans, ret);
goto out;
}
@@ -1080,7 +1080,7 @@ static noinline int balance_level(struct btrfs_trans_handle *trans,
ret = btrfs_free_tree_block(trans, btrfs_root_id(root), mid, 0, 1);
free_extent_buffer_stale(mid);
mid = NULL;
- if (ret < 0) {
+ if (unlikely(ret < 0)) {
btrfs_abort_transaction(trans, ret);
goto out;
}
@@ -1090,7 +1090,7 @@ static noinline int balance_level(struct btrfs_trans_handle *trans,
btrfs_node_key(mid, &mid_key, 0);
ret = btrfs_tree_mod_log_insert_key(parent, pslot,
BTRFS_MOD_LOG_KEY_REPLACE);
- if (ret < 0) {
+ if (unlikely(ret < 0)) {
btrfs_abort_transaction(trans, ret);
goto out;
}
@@ -1195,7 +1195,7 @@ static noinline int push_nodes_for_insert(struct btrfs_trans_handle *trans,
btrfs_node_key(mid, &disk_key, 0);
ret = btrfs_tree_mod_log_insert_key(parent, pslot,
BTRFS_MOD_LOG_KEY_REPLACE);
- if (ret < 0) {
+ if (unlikely(ret < 0)) {
btrfs_tree_unlock(left);
free_extent_buffer(left);
btrfs_abort_transaction(trans, ret);
@@ -1255,7 +1255,7 @@ static noinline int push_nodes_for_insert(struct btrfs_trans_handle *trans,
btrfs_node_key(right, &disk_key, 0);
ret = btrfs_tree_mod_log_insert_key(parent, pslot + 1,
BTRFS_MOD_LOG_KEY_REPLACE);
- if (ret < 0) {
+ if (unlikely(ret < 0)) {
btrfs_tree_unlock(right);
free_extent_buffer(right);
btrfs_abort_transaction(trans, ret);
@@ -2737,7 +2737,7 @@ static int push_node_left(struct btrfs_trans_handle *trans,
return ret;
}
ret = btrfs_tree_mod_log_eb_copy(dst, src, dst_nritems, 0, push_items);
- if (ret) {
+ if (unlikely(ret)) {
btrfs_abort_transaction(trans, ret);
return ret;
}
@@ -2822,7 +2822,7 @@ static int balance_node_right(struct btrfs_trans_handle *trans,
ret = btrfs_tree_mod_log_eb_copy(dst, src, 0, src_nritems - push_items,
push_items);
- if (ret) {
+ if (unlikely(ret)) {
btrfs_abort_transaction(trans, ret);
return ret;
}
@@ -2892,7 +2892,7 @@ static noinline int insert_new_root(struct btrfs_trans_handle *trans,
btrfs_clear_buffer_dirty(trans, c);
ret2 = btrfs_free_tree_block(trans, btrfs_root_id(root), c, 0, 1);
- if (ret2 < 0)
+ if (unlikely(ret2 < 0))
btrfs_abort_transaction(trans, ret2);
btrfs_tree_unlock(c);
free_extent_buffer(c);
@@ -2937,7 +2937,7 @@ static int insert_ptr(struct btrfs_trans_handle *trans,
if (level) {
ret = btrfs_tree_mod_log_insert_move(lower, slot + 1,
slot, nritems - slot);
- if (ret < 0) {
+ if (unlikely(ret < 0)) {
btrfs_abort_transaction(trans, ret);
return ret;
}
@@ -2950,7 +2950,7 @@ static int insert_ptr(struct btrfs_trans_handle *trans,
if (level) {
ret = btrfs_tree_mod_log_insert_key(lower, slot,
BTRFS_MOD_LOG_KEY_ADD);
- if (ret < 0) {
+ if (unlikely(ret < 0)) {
btrfs_abort_transaction(trans, ret);
return ret;
}
@@ -3026,7 +3026,7 @@ static noinline int split_node(struct btrfs_trans_handle *trans,
ASSERT(btrfs_header_level(c) == level);
ret = btrfs_tree_mod_log_eb_copy(split, c, 0, mid, c_nritems - mid);
- if (ret) {
+ if (unlikely(ret)) {
btrfs_tree_unlock(split);
free_extent_buffer(split);
btrfs_abort_transaction(trans, ret);
@@ -4383,7 +4383,7 @@ int btrfs_del_ptr(struct btrfs_trans_handle *trans, struct btrfs_root *root,
if (level) {
ret = btrfs_tree_mod_log_insert_move(parent, slot,
slot + 1, nritems - slot - 1);
- if (ret < 0) {
+ if (unlikely(ret < 0)) {
btrfs_abort_transaction(trans, ret);
return ret;
}
@@ -4396,7 +4396,7 @@ int btrfs_del_ptr(struct btrfs_trans_handle *trans, struct btrfs_root *root,
} else if (level) {
ret = btrfs_tree_mod_log_insert_key(parent, slot,
BTRFS_MOD_LOG_KEY_REMOVE);
- if (ret < 0) {
+ if (unlikely(ret < 0)) {
btrfs_abort_transaction(trans, ret);
return ret;
}
diff --git a/fs/btrfs/delayed-inode.c b/fs/btrfs/delayed-inode.c
index 81577a0c601f..41e37f7f67cc 100644
--- a/fs/btrfs/delayed-inode.c
+++ b/fs/btrfs/delayed-inode.c
@@ -1038,7 +1038,7 @@ static int __btrfs_update_delayed_inode(struct btrfs_trans_handle *trans,
* transaction, because we could leave the inode with the
* improper counts behind.
*/
- if (ret != -ENOENT)
+ if (unlikely(ret != -ENOENT))
btrfs_abort_transaction(trans, ret);
goto out;
}
@@ -1066,7 +1066,7 @@ static int __btrfs_update_delayed_inode(struct btrfs_trans_handle *trans,
btrfs_release_path(path);
ret = btrfs_search_slot(trans, root, &key, path, -1, 1);
- if (ret < 0) {
+ if (unlikely(ret < 0)) {
btrfs_abort_transaction(trans, ret);
goto err_out;
}
@@ -1175,7 +1175,7 @@ static int __btrfs_run_delayed_items(struct btrfs_trans_handle *trans, int nr)
while (curr_node && (!count || nr--)) {
ret = __btrfs_commit_inode_delayed_items(trans, path,
curr_node);
- if (ret) {
+ if (unlikely(ret)) {
btrfs_abort_transaction(trans, ret);
break;
}
diff --git a/fs/btrfs/extent-tree.c b/fs/btrfs/extent-tree.c
index 6a258bd8693e..dc4ca98c3780 100644
--- a/fs/btrfs/extent-tree.c
+++ b/fs/btrfs/extent-tree.c
@@ -2157,7 +2157,7 @@ int btrfs_run_delayed_refs(struct btrfs_trans_handle *trans, u64 min_bytes)
delayed_refs->run_delayed_start = find_middle(&delayed_refs->root);
#endif
ret = __btrfs_run_delayed_refs(trans, min_bytes);
- if (ret < 0) {
+ if (unlikely(ret < 0)) {
btrfs_abort_transaction(trans, ret);
return ret;
}
@@ -2982,26 +2982,26 @@ static int do_free_extent_accounting(struct btrfs_trans_handle *trans,
csum_root = btrfs_csum_root(trans->fs_info, bytenr);
ret = btrfs_del_csums(trans, csum_root, bytenr, num_bytes);
- if (ret) {
+ if (unlikely(ret)) {
btrfs_abort_transaction(trans, ret);
return ret;
}
ret = btrfs_delete_raid_extent(trans, bytenr, num_bytes);
- if (ret) {
+ if (unlikely(ret)) {
btrfs_abort_transaction(trans, ret);
return ret;
}
}
ret = btrfs_record_squota_delta(trans->fs_info, delta);
- if (ret) {
+ if (unlikely(ret)) {
btrfs_abort_transaction(trans, ret);
return ret;
}
ret = btrfs_add_to_free_space_tree(trans, bytenr, num_bytes);
- if (ret) {
+ if (unlikely(ret)) {
btrfs_abort_transaction(trans, ret);
return ret;
}
@@ -3115,7 +3115,7 @@ static int __btrfs_free_extent(struct btrfs_trans_handle *trans,
is_data = owner_objectid >= BTRFS_FIRST_FREE_OBJECTID;
- if (!is_data && refs_to_drop != 1) {
+ if (unlikely(!is_data && refs_to_drop != 1)) {
btrfs_crit(info,
"invalid refs_to_drop, dropping more than 1 refs for tree block %llu refs_to_drop %u",
node->bytenr, refs_to_drop);
@@ -3172,7 +3172,7 @@ static int __btrfs_free_extent(struct btrfs_trans_handle *trans,
/* Must be SHARED_* item, remove the backref first */
ret = remove_extent_backref(trans, extent_root, path,
NULL, refs_to_drop, is_data);
- if (ret) {
+ if (unlikely(ret)) {
btrfs_abort_transaction(trans, ret);
goto out;
}
@@ -3221,7 +3221,7 @@ static int __btrfs_free_extent(struct btrfs_trans_handle *trans,
"umm, got %d back from search, was looking for %llu, slot %d",
ret, bytenr, path->slots[0]);
}
- if (ret < 0) {
+ if (unlikely(ret < 0)) {
btrfs_abort_transaction(trans, ret);
goto out;
}
@@ -3298,7 +3298,7 @@ static int __btrfs_free_extent(struct btrfs_trans_handle *trans,
if (found_extent) {
ret = remove_extent_backref(trans, extent_root, path,
iref, refs_to_drop, is_data);
- if (ret) {
+ if (unlikely(ret)) {
btrfs_abort_transaction(trans, ret);
goto out;
}
@@ -3363,7 +3363,7 @@ static int __btrfs_free_extent(struct btrfs_trans_handle *trans,
ret = btrfs_del_items(trans, extent_root, path, path->slots[0],
num_to_del);
- if (ret) {
+ if (unlikely(ret)) {
btrfs_abort_transaction(trans, ret);
goto out;
}
@@ -5472,17 +5472,17 @@ static noinline int walk_down_proc(struct btrfs_trans_handle *trans,
if (!(wc->flags[level] & flag)) {
ASSERT(path->locks[level]);
ret = btrfs_inc_ref(trans, root, eb, 1);
- if (ret) {
+ if (unlikely(ret)) {
btrfs_abort_transaction(trans, ret);
return ret;
}
ret = btrfs_dec_ref(trans, root, eb, 0);
- if (ret) {
+ if (unlikely(ret)) {
btrfs_abort_transaction(trans, ret);
return ret;
}
ret = btrfs_set_disk_extent_flags(trans, eb, flag);
- if (ret) {
+ if (unlikely(ret)) {
btrfs_abort_transaction(trans, ret);
return ret;
}
@@ -5885,7 +5885,7 @@ static noinline int walk_up_proc(struct btrfs_trans_handle *trans,
}
} else {
ret = btrfs_dec_ref(trans, root, eb, 0);
- if (ret) {
+ if (unlikely(ret)) {
btrfs_abort_transaction(trans, ret);
return ret;
}
@@ -6180,13 +6180,13 @@ int btrfs_drop_snapshot(struct btrfs_root *root, bool update_ref, bool for_reloc
while (1) {
ret = walk_down_tree(trans, root, path, wc);
- if (ret < 0) {
+ if (unlikely(ret < 0)) {
btrfs_abort_transaction(trans, ret);
break;
}
ret = walk_up_tree(trans, root, path, wc, BTRFS_MAX_LEVEL);
- if (ret < 0) {
+ if (unlikely(ret < 0)) {
btrfs_abort_transaction(trans, ret);
break;
}
@@ -6213,7 +6213,7 @@ int btrfs_drop_snapshot(struct btrfs_root *root, bool update_ref, bool for_reloc
ret = btrfs_update_root(trans, tree_root,
&root->root_key,
root_item);
- if (ret) {
+ if (unlikely(ret)) {
btrfs_abort_transaction(trans, ret);
goto out_end_trans;
}
@@ -6249,7 +6249,7 @@ int btrfs_drop_snapshot(struct btrfs_root *root, bool update_ref, bool for_reloc
goto out_end_trans;
ret = btrfs_del_root(trans, &root->root_key);
- if (ret) {
+ if (unlikely(ret)) {
btrfs_abort_transaction(trans, ret);
goto out_end_trans;
}
@@ -6257,7 +6257,7 @@ int btrfs_drop_snapshot(struct btrfs_root *root, bool update_ref, bool for_reloc
if (!is_reloc_root) {
ret = btrfs_find_root(tree_root, &root->root_key, path,
NULL, NULL);
- if (ret < 0) {
+ if (unlikely(ret < 0)) {
btrfs_abort_transaction(trans, ret);
goto out_end_trans;
} else if (ret > 0) {
diff --git a/fs/btrfs/file-item.c b/fs/btrfs/file-item.c
index 7906aea75ee4..a42e6d54e7cd 100644
--- a/fs/btrfs/file-item.c
+++ b/fs/btrfs/file-item.c
@@ -1011,7 +1011,7 @@ int btrfs_del_csums(struct btrfs_trans_handle *trans,
* item changed size or key
*/
ret = btrfs_split_item(trans, root, path, &key, offset);
- if (ret && ret != -EAGAIN) {
+ if (unlikely(ret && ret != -EAGAIN)) {
btrfs_abort_transaction(trans, ret);
break;
}
diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c
index e7765578349b..7efd1f8a1912 100644
--- a/fs/btrfs/file.c
+++ b/fs/btrfs/file.c
@@ -327,7 +327,7 @@ int btrfs_drop_extents(struct btrfs_trans_handle *trans,
args->start - extent_offset,
0, false);
ret = btrfs_inc_extent_ref(trans, &ref);
- if (ret) {
+ if (unlikely(ret)) {
btrfs_abort_transaction(trans, ret);
break;
}
@@ -426,7 +426,7 @@ int btrfs_drop_extents(struct btrfs_trans_handle *trans,
key.offset - extent_offset,
0, false);
ret = btrfs_free_extent(trans, &ref);
- if (ret) {
+ if (unlikely(ret)) {
btrfs_abort_transaction(trans, ret);
break;
}
@@ -443,7 +443,7 @@ int btrfs_drop_extents(struct btrfs_trans_handle *trans,
ret = btrfs_del_items(trans, root, path, del_slot,
del_nr);
- if (ret) {
+ if (unlikely(ret)) {
btrfs_abort_transaction(trans, ret);
break;
}
@@ -587,21 +587,20 @@ int btrfs_mark_extent_written(struct btrfs_trans_handle *trans,
leaf = path->nodes[0];
btrfs_item_key_to_cpu(leaf, &key, path->slots[0]);
- if (key.objectid != ino ||
- key.type != BTRFS_EXTENT_DATA_KEY) {
+ if (unlikely(key.objectid != ino || key.type != BTRFS_EXTENT_DATA_KEY)) {
ret = -EINVAL;
btrfs_abort_transaction(trans, ret);
goto out;
}
fi = btrfs_item_ptr(leaf, path->slots[0],
struct btrfs_file_extent_item);
- if (btrfs_file_extent_type(leaf, fi) != BTRFS_FILE_EXTENT_PREALLOC) {
+ if (unlikely(btrfs_file_extent_type(leaf, fi) != BTRFS_FILE_EXTENT_PREALLOC)) {
ret = -EINVAL;
btrfs_abort_transaction(trans, ret);
goto out;
}
extent_end = key.offset + btrfs_file_extent_num_bytes(leaf, fi);
- if (key.offset > start || extent_end < end) {
+ if (unlikely(key.offset > start || extent_end < end)) {
ret = -EINVAL;
btrfs_abort_transaction(trans, ret);
goto out;
@@ -676,7 +675,7 @@ int btrfs_mark_extent_written(struct btrfs_trans_handle *trans,
btrfs_release_path(path);
goto again;
}
- if (ret < 0) {
+ if (unlikely(ret < 0)) {
btrfs_abort_transaction(trans, ret);
goto out;
}
@@ -704,7 +703,7 @@ int btrfs_mark_extent_written(struct btrfs_trans_handle *trans,
ref.ref_root = btrfs_root_id(root);
btrfs_init_data_ref(&ref, ino, orig_offset, 0, false);
ret = btrfs_inc_extent_ref(trans, &ref);
- if (ret) {
+ if (unlikely(ret)) {
btrfs_abort_transaction(trans, ret);
goto out;
}
@@ -712,7 +711,7 @@ int btrfs_mark_extent_written(struct btrfs_trans_handle *trans,
if (split == start) {
key.offset = start;
} else {
- if (start != key.offset) {
+ if (unlikely(start != key.offset)) {
ret = -EINVAL;
btrfs_abort_transaction(trans, ret);
goto out;
@@ -744,7 +743,7 @@ int btrfs_mark_extent_written(struct btrfs_trans_handle *trans,
del_slot = path->slots[0] + 1;
del_nr++;
ret = btrfs_free_extent(trans, &ref);
- if (ret) {
+ if (unlikely(ret)) {
btrfs_abort_transaction(trans, ret);
goto out;
}
@@ -762,7 +761,7 @@ int btrfs_mark_extent_written(struct btrfs_trans_handle *trans,
del_slot = path->slots[0];
del_nr++;
ret = btrfs_free_extent(trans, &ref);
- if (ret) {
+ if (unlikely(ret)) {
btrfs_abort_transaction(trans, ret);
goto out;
}
@@ -783,7 +782,7 @@ int btrfs_mark_extent_written(struct btrfs_trans_handle *trans,
extent_end - key.offset);
ret = btrfs_del_items(trans, root, path, del_slot, del_nr);
- if (ret < 0) {
+ if (unlikely(ret < 0)) {
btrfs_abort_transaction(trans, ret);
goto out;
}
@@ -2460,9 +2459,9 @@ int btrfs_replace_file_extents(struct btrfs_inode *inode,
* got EOPNOTSUPP via prealloc then we messed up and
* need to abort.
*/
- if (ret &&
- (ret != -EOPNOTSUPP ||
- (extent_info && extent_info->is_new_extent)))
+ if (unlikely(ret &&
+ (ret != -EOPNOTSUPP ||
+ (extent_info && extent_info->is_new_extent))))
btrfs_abort_transaction(trans, ret);
break;
}
@@ -2473,7 +2472,7 @@ int btrfs_replace_file_extents(struct btrfs_inode *inode,
cur_offset < ino_size) {
ret = fill_holes(trans, inode, path, cur_offset,
drop_args.drop_end);
- if (ret) {
+ if (unlikely(ret)) {
/*
* If we failed then we didn't insert our hole
* entries for the area we dropped, so now the
@@ -2493,7 +2492,7 @@ int btrfs_replace_file_extents(struct btrfs_inode *inode,
ret = btrfs_inode_clear_file_extent_range(inode,
cur_offset,
drop_args.drop_end - cur_offset);
- if (ret) {
+ if (unlikely(ret)) {
/*
* We couldn't clear our area, so we could
* presumably adjust up and corrupt the fs, so
@@ -2512,7 +2511,7 @@ int btrfs_replace_file_extents(struct btrfs_inode *inode,
ret = btrfs_insert_replace_extent(trans, inode, path,
extent_info, replace_len,
drop_args.bytes_found);
- if (ret) {
+ if (unlikely(ret)) {
btrfs_abort_transaction(trans, ret);
break;
}
@@ -2607,7 +2606,7 @@ int btrfs_replace_file_extents(struct btrfs_inode *inode,
cur_offset < drop_args.drop_end) {
ret = fill_holes(trans, inode, path, cur_offset,
drop_args.drop_end);
- if (ret) {
+ if (unlikely(ret)) {
/* Same comment as above. */
btrfs_abort_transaction(trans, ret);
goto out_trans;
@@ -2616,7 +2615,7 @@ int btrfs_replace_file_extents(struct btrfs_inode *inode,
/* See the comment in the loop above for the reasoning here. */
ret = btrfs_inode_clear_file_extent_range(inode, cur_offset,
drop_args.drop_end - cur_offset);
- if (ret) {
+ if (unlikely(ret)) {
btrfs_abort_transaction(trans, ret);
goto out_trans;
}
@@ -2626,7 +2625,7 @@ int btrfs_replace_file_extents(struct btrfs_inode *inode,
ret = btrfs_insert_replace_extent(trans, inode, path,
extent_info, extent_info->data_len,
drop_args.bytes_found);
- if (ret) {
+ if (unlikely(ret)) {
btrfs_abort_transaction(trans, ret);
goto out_trans;
}
diff --git a/fs/btrfs/free-space-cache.c b/fs/btrfs/free-space-cache.c
index c2730740d928..ab873bd67192 100644
--- a/fs/btrfs/free-space-cache.c
+++ b/fs/btrfs/free-space-cache.c
@@ -4142,7 +4142,7 @@ int btrfs_set_free_space_cache_v1_active(struct btrfs_fs_info *fs_info, bool act
if (!active) {
set_bit(BTRFS_FS_CLEANUP_SPACE_CACHE_V1, &fs_info->flags);
ret = cleanup_free_space_cache_v1(fs_info, trans);
- if (ret) {
+ if (unlikely(ret)) {
btrfs_abort_transaction(trans, ret);
btrfs_end_transaction(trans);
goto out;
diff --git a/fs/btrfs/free-space-tree.c b/fs/btrfs/free-space-tree.c
index 2fee9f3b60d4..dad0b492a663 100644
--- a/fs/btrfs/free-space-tree.c
+++ b/fs/btrfs/free-space-tree.c
@@ -218,7 +218,7 @@ int btrfs_convert_free_space_to_bitmaps(struct btrfs_trans_handle *trans,
bitmap_size = free_space_bitmap_size(fs_info, block_group->length);
bitmap = alloc_bitmap(bitmap_size);
- if (!bitmap) {
+ if (unlikely(!bitmap)) {
ret = -ENOMEM;
btrfs_abort_transaction(trans, ret);
goto out;
@@ -233,7 +233,7 @@ int btrfs_convert_free_space_to_bitmaps(struct btrfs_trans_handle *trans,
while (!done) {
ret = btrfs_search_prev_slot(trans, root, &key, path, -1, 1);
- if (ret) {
+ if (unlikely(ret)) {
btrfs_abort_transaction(trans, ret);
goto out;
}
@@ -271,7 +271,7 @@ int btrfs_convert_free_space_to_bitmaps(struct btrfs_trans_handle *trans,
}
ret = btrfs_del_items(trans, root, path, path->slots[0], nr);
- if (ret) {
+ if (unlikely(ret)) {
btrfs_abort_transaction(trans, ret);
goto out;
}
@@ -320,7 +320,7 @@ int btrfs_convert_free_space_to_bitmaps(struct btrfs_trans_handle *trans,
ret = btrfs_insert_empty_item(trans, root, path, &key,
data_size);
- if (ret) {
+ if (unlikely(ret)) {
btrfs_abort_transaction(trans, ret);
goto out;
}
@@ -361,7 +361,7 @@ int btrfs_convert_free_space_to_extents(struct btrfs_trans_handle *trans,
bitmap_size = free_space_bitmap_size(fs_info, block_group->length);
bitmap = alloc_bitmap(bitmap_size);
- if (!bitmap) {
+ if (unlikely(!bitmap)) {
ret = -ENOMEM;
btrfs_abort_transaction(trans, ret);
goto out;
@@ -376,7 +376,7 @@ int btrfs_convert_free_space_to_extents(struct btrfs_trans_handle *trans,
while (!done) {
ret = btrfs_search_prev_slot(trans, root, &key, path, -1, 1);
- if (ret) {
+ if (unlikely(ret)) {
btrfs_abort_transaction(trans, ret);
goto out;
}
@@ -420,7 +420,7 @@ int btrfs_convert_free_space_to_extents(struct btrfs_trans_handle *trans,
}
ret = btrfs_del_items(trans, root, path, path->slots[0], nr);
- if (ret) {
+ if (unlikely(ret)) {
btrfs_abort_transaction(trans, ret);
goto out;
}
@@ -454,7 +454,7 @@ int btrfs_convert_free_space_to_extents(struct btrfs_trans_handle *trans,
key.offset = (end_bit - start_bit) * fs_info->sectorsize;
ret = btrfs_insert_empty_item(trans, root, path, &key, 0);
- if (ret) {
+ if (unlikely(ret)) {
btrfs_abort_transaction(trans, ret);
goto out;
}
@@ -848,14 +848,14 @@ int btrfs_remove_from_free_space_tree(struct btrfs_trans_handle *trans,
return 0;
path = btrfs_alloc_path();
- if (!path) {
+ if (unlikely(!path)) {
ret = -ENOMEM;
btrfs_abort_transaction(trans, ret);
goto out;
}
block_group = btrfs_lookup_block_group(trans->fs_info, start);
- if (!block_group) {
+ if (unlikely(!block_group)) {
DEBUG_WARN("no block group found for start=%llu", start);
ret = -ENOENT;
btrfs_abort_transaction(trans, ret);
@@ -1030,14 +1030,14 @@ int btrfs_add_to_free_space_tree(struct btrfs_trans_handle *trans,
return 0;
path = btrfs_alloc_path();
- if (!path) {
+ if (unlikely(!path)) {
ret = -ENOMEM;
btrfs_abort_transaction(trans, ret);
goto out;
}
block_group = btrfs_lookup_block_group(trans->fs_info, start);
- if (!block_group) {
+ if (unlikely(!block_group)) {
DEBUG_WARN("no block group found for start=%llu", start);
ret = -ENOENT;
btrfs_abort_transaction(trans, ret);
@@ -1185,7 +1185,7 @@ int btrfs_create_free_space_tree(struct btrfs_fs_info *fs_info)
goto out_clear;
}
ret = btrfs_global_root_insert(free_space_root);
- if (ret) {
+ if (unlikely(ret)) {
btrfs_put_root(free_space_root);
btrfs_abort_transaction(trans, ret);
btrfs_end_transaction(trans);
@@ -1197,7 +1197,7 @@ int btrfs_create_free_space_tree(struct btrfs_fs_info *fs_info)
block_group = rb_entry(node, struct btrfs_block_group,
cache_node);
ret = populate_free_space_tree(trans, block_group);
- if (ret) {
+ if (unlikely(ret)) {
btrfs_abort_transaction(trans, ret);
btrfs_end_transaction(trans);
goto out_clear;
@@ -1290,14 +1290,14 @@ int btrfs_delete_free_space_tree(struct btrfs_fs_info *fs_info)
btrfs_clear_fs_compat_ro(fs_info, FREE_SPACE_TREE_VALID);
ret = clear_free_space_tree(trans, free_space_root);
- if (ret) {
+ if (unlikely(ret)) {
btrfs_abort_transaction(trans, ret);
btrfs_end_transaction(trans);
return ret;
}
ret = btrfs_del_root(trans, &free_space_root->root_key);
- if (ret) {
+ if (unlikely(ret)) {
btrfs_abort_transaction(trans, ret);
btrfs_end_transaction(trans);
return ret;
@@ -1315,7 +1315,7 @@ int btrfs_delete_free_space_tree(struct btrfs_fs_info *fs_info)
ret = btrfs_free_tree_block(trans, btrfs_root_id(free_space_root),
free_space_root->node, 0, 1);
btrfs_put_root(free_space_root);
- if (ret < 0) {
+ if (unlikely(ret < 0)) {
btrfs_abort_transaction(trans, ret);
btrfs_end_transaction(trans);
return ret;
@@ -1344,7 +1344,7 @@ int btrfs_rebuild_free_space_tree(struct btrfs_fs_info *fs_info)
set_bit(BTRFS_FS_FREE_SPACE_TREE_UNTRUSTED, &fs_info->flags);
ret = clear_free_space_tree(trans, free_space_root);
- if (ret) {
+ if (unlikely(ret)) {
btrfs_abort_transaction(trans, ret);
btrfs_end_transaction(trans);
return ret;
@@ -1362,7 +1362,7 @@ int btrfs_rebuild_free_space_tree(struct btrfs_fs_info *fs_info)
goto next;
ret = populate_free_space_tree(trans, block_group);
- if (ret) {
+ if (unlikely(ret)) {
btrfs_abort_transaction(trans, ret);
btrfs_end_transaction(trans);
return ret;
@@ -1422,7 +1422,7 @@ static int __add_block_group_free_space(struct btrfs_trans_handle *trans,
if (!path) {
path = btrfs_alloc_path();
- if (!path) {
+ if (unlikely(!path)) {
btrfs_abort_transaction(trans, -ENOMEM);
return -ENOMEM;
}
@@ -1430,7 +1430,7 @@ static int __add_block_group_free_space(struct btrfs_trans_handle *trans,
}
ret = add_new_free_space_info(trans, block_group, path);
- if (ret) {
+ if (unlikely(ret)) {
btrfs_abort_transaction(trans, ret);
goto out;
}
@@ -1481,7 +1481,7 @@ int btrfs_remove_block_group_free_space(struct btrfs_trans_handle *trans,
}
path = btrfs_alloc_path();
- if (!path) {
+ if (unlikely(!path)) {
ret = -ENOMEM;
btrfs_abort_transaction(trans, ret);
goto out;
@@ -1496,7 +1496,7 @@ int btrfs_remove_block_group_free_space(struct btrfs_trans_handle *trans,
while (!done) {
ret = btrfs_search_prev_slot(trans, root, &key, path, -1, 1);
- if (ret) {
+ if (unlikely(ret)) {
btrfs_abort_transaction(trans, ret);
goto out;
}
@@ -1527,7 +1527,7 @@ int btrfs_remove_block_group_free_space(struct btrfs_trans_handle *trans,
}
ret = btrfs_del_items(trans, root, path, path->slots[0], nr);
- if (ret) {
+ if (unlikely(ret)) {
btrfs_abort_transaction(trans, ret);
goto out;
}
diff --git a/fs/btrfs/inode-item.c b/fs/btrfs/inode-item.c
index f06cf701ae5a..1bd73b80f9fa 100644
--- a/fs/btrfs/inode-item.c
+++ b/fs/btrfs/inode-item.c
@@ -137,7 +137,7 @@ static int btrfs_del_inode_extref(struct btrfs_trans_handle *trans,
*/
extref = btrfs_find_name_in_ext_backref(path->nodes[0], path->slots[0],
ref_objectid, name);
- if (!extref) {
+ if (unlikely(!extref)) {
btrfs_abort_transaction(trans, -ENOENT);
return -ENOENT;
}
@@ -627,7 +627,7 @@ int btrfs_truncate_inode_items(struct btrfs_trans_handle *trans,
if (control->clear_extent_range) {
ret = btrfs_inode_clear_file_extent_range(control->inode,
clear_start, clear_len);
- if (ret) {
+ if (unlikely(ret)) {
btrfs_abort_transaction(trans, ret);
break;
}
@@ -666,7 +666,7 @@ int btrfs_truncate_inode_items(struct btrfs_trans_handle *trans,
btrfs_init_data_ref(&ref, control->ino, extent_offset,
btrfs_root_id(root), false);
ret = btrfs_free_extent(trans, &ref);
- if (ret) {
+ if (unlikely(ret)) {
btrfs_abort_transaction(trans, ret);
break;
}
@@ -684,7 +684,7 @@ int btrfs_truncate_inode_items(struct btrfs_trans_handle *trans,
ret = btrfs_del_items(trans, root, path,
pending_del_slot,
pending_del_nr);
- if (ret) {
+ if (unlikely(ret)) {
btrfs_abort_transaction(trans, ret);
break;
}
@@ -720,7 +720,7 @@ int btrfs_truncate_inode_items(struct btrfs_trans_handle *trans,
int ret2;
ret2 = btrfs_del_items(trans, root, path, pending_del_slot, pending_del_nr);
- if (ret2) {
+ if (unlikely(ret2)) {
btrfs_abort_transaction(trans, ret2);
ret = ret2;
}
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index 6403312505c4..02cb081697fe 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -634,7 +634,7 @@ static noinline int __cow_file_range_inline(struct btrfs_inode *inode,
drop_args.replace_extent = true;
drop_args.extent_item_size = btrfs_file_extent_calc_inline_size(data_len);
ret = btrfs_drop_extents(trans, root, inode, &drop_args);
- if (ret) {
+ if (unlikely(ret)) {
btrfs_abort_transaction(trans, ret);
goto out;
}
@@ -642,7 +642,7 @@ static noinline int __cow_file_range_inline(struct btrfs_inode *inode,
ret = insert_inline_extent(trans, path, inode, drop_args.extent_inserted,
size, compressed_size, compress_type,
compressed_folio, update_i_size);
- if (ret && ret != -ENOSPC) {
+ if (unlikely(ret && ret != -ENOSPC)) {
btrfs_abort_transaction(trans, ret);
goto out;
} else if (ret == -ENOSPC) {
@@ -652,7 +652,7 @@ static noinline int __cow_file_range_inline(struct btrfs_inode *inode,
btrfs_update_inode_bytes(inode, size, drop_args.bytes_found);
ret = btrfs_update_inode(trans, inode);
- if (ret && ret != -ENOSPC) {
+ if (unlikely(ret && ret != -ENOSPC)) {
btrfs_abort_transaction(trans, ret);
goto out;
} else if (ret == -ENOSPC) {
@@ -3150,7 +3150,7 @@ int btrfs_finish_one_ordered(struct btrfs_ordered_extent *ordered_extent)
trans->block_rsv = &inode->block_rsv;
ret = btrfs_insert_raid_extent(trans, ordered_extent);
- if (ret) {
+ if (unlikely(ret)) {
btrfs_abort_transaction(trans, ret);
goto out;
}
@@ -3158,7 +3158,7 @@ int btrfs_finish_one_ordered(struct btrfs_ordered_extent *ordered_extent)
if (test_bit(BTRFS_ORDERED_NOCOW, &ordered_extent->flags)) {
/* Logic error */
ASSERT(list_empty(&ordered_extent->list));
- if (!list_empty(&ordered_extent->list)) {
+ if (unlikely(!list_empty(&ordered_extent->list))) {
ret = -EINVAL;
btrfs_abort_transaction(trans, ret);
goto out;
@@ -3166,7 +3166,7 @@ int btrfs_finish_one_ordered(struct btrfs_ordered_extent *ordered_extent)
]
]
|
| 48/2 |
2026/08/10 00:25 |
llm |
expert |
0m
Model:
Tokens:
input: 55350
output: 66
thoughts: 1422
Thoughts: **Analysis of BTRFS Transaction Abort due to ENOSPC**
Okay, so `a929904cf73b` added some annotations about an interesting scenario. Essentially, if `btrfs_drop_extents` returns `-ENOSPC`, the BTRFS transaction aborts. This sounds familiar; syzkaller probably hits this one frequently. The question is, is this exploitable?
Well, it's not a memory corruption bug. It's a transaction abort, so the filesystem goes read-only. Thatβs a Denial of Service. Can an unprivileged user trigger this? Yes, if they can get this error on a healthy filesystem. And they can. If the filesystem is near full, and they perform a write operation that triggers `__cow_file_range_inline`, and then `btrfs_drop_extents` fails due to `-ENOSPC`, we have the problem.
It seems like BTRFS has a known limitation here: `btrfs_delalloc_reserve_metadata` might succeed, but the COW itself might fail later. So we are dealing with a known ENOSPC handling problem. It's a DoS, accessible to unprivileged processes, and any user in any namespace can write to files. Weβre not talking about a malicious filesystem *necessarily*; a near-full one could be enough, or a specific operation sequence.
Now, if syzkaller hit this, it *could* mean a corrupted filesystem (very deep tree, corrupted extents), which means `btrfs_drop_extents` needing more space than reserved. If that's the case, it becomes a "malicious filesystem trigger". However, syzkaller also hits ENOSPC on healthy filesystems. We could use the syzkaller reproducer, but we do not have it.
The error results in a `WARNING` within `btrfs_abort_transaction`.
So, my classification is:
* Exploitable: No (transaction abort only)
* Denial of Service: Yes (filesystem goes read-only)
* Unprivileged: Yes
* User Namespace: Yes
* VM Guest/Host/Network/Remote/Peripheral Trigger: No
* Filesystem Trigger: Yes (either a malicious one, or a nearly-full one.)
I need to quickly verify a point. I need to make sure the value of `-ENOSPC` is the real cause.
Looking at the assembly, the code checks for `-5` and `-12`. I know `-EROFS` is `-30` (`0xffffffe2`). Why is there no comparison for `-EROFS`? Probably compiler optimization because `btrfs_drop_extents` doesn't return `-EROFS`. The error code is indeed `-ENOSPC`, which is `-28`. `btrfs_drop_extents` returned `-ENOSPC` and that's why `__cow_file_range_inline` aborted the transaction.
Why `-ENOSPC`? Because itβs running out of space when trying to modify the tree, as expected in a classic ENOSPC issue. And it's a DoS because the filesystem is read-only. Anyone can trigger this on a full filesystem.
So, in summary:
* Exploitable: False
* Denial of Service: True
* Unprivileged: True
* UserNamespace: True
* FilesystemTrigger: True (or malicious)
* Network/Remote/Peripheral/VM: False
Finally, I need to check for memory corruption. It's just a `WARN_ON` inside `btrfs_abort_transaction`. No memory corruption. So the transaction is aborted, the filesystem goes read-only.
Can they mount a filesystem? No, not without `CAP_SYS_ADMIN` in the initial namespace. But they *can* write to an already mounted one. A full filesystem could cause a DoS to the whole BTRFS. If itβs the root filesystem, this is a system crash. Definitely a valid DoS.
So, the final `set-results`:
* Exploitable: False
* Denial of Service: True
* Unprivileged: True
* User Namespace: True
* VM Guest/Host/Network/Remote/Peripheral: False
* FilesystemTrigger: True
I'm ready to call `set-results`.
|
| 49/2 |
2026/08/10 00:25 |
tool |
set-results |
0m
Args: map[DenialOfService:true Exploitable:false FilesystemTrigger:true NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:true UserNamespace:true VMGuestTrigger:false VMHostTrigger:false]
Results: map[DenialOfService:true Exploitable:false FilesystemTrigger:true NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:true UserNamespace:true VMGuestTrigger:false VMHostTrigger:false]
|
| 50/2 |
2026/08/10 00:25 |
llm |
expert |
0m
Model:
Tokens:
input: 57859
output: 593
thoughts: 272
Thoughts: **My Analysis of the BTRFS ENOSPC Bug**
Okay, the analysis is complete, time to formulate the final response. Based on the data, here's what I've figured out. This crash is triggered by a `WARNING` in `btrfs_abort_transaction` within `__cow_file_range_inline`. The root cause is an aborted transaction, and that abortion is a direct result of `btrfs_drop_extents` returning an error.
Looking at the register values from the crash dump, specifically `R13: 00000000ffffffe4`, it's clear the error `btrfs_drop_extents` reported was `-ENOSPC` (which is -28). This indicates the BTRFS filesystem ran out of metadata space while it was trying to drop extents associated with an inline file range. Classic metadata exhaustion.
The consequence of this transaction abort is the filesystem going read-only. This is a standard behavior of BTRFS to prevent corruption in the face of such a critical failure, and it effectively causes a Denial of Service.
The concerning part is that any unprivileged user with write access to the filesystem could potentially trigger this by filling up the metadata space. It's an unprivileged Denial of Service, meaning no privilege escalation is possible. At least, the analysis indicates it does not directly lead to memory corruption or information leaks.
This could also be triggered by a maliciously crafted BTRFS filesystem image. A particularly clever attacker could create corrupted metadata or extent trees designed to exhaust block reserves during the `btrfs_drop_extents` call.
|
| 51/1 |
2026/08/10 00:26 |
action |
format-explanation |
0m
Results: map[Explanation:Based on the analysis of the crash report and the kernel source code, here is
the assessment of the bug:
**Bug Analysis:**
The crash is a `WARNING` triggered by `btrfs_abort_should_print_stack()` which
is called from the `btrfs_abort_transaction()` macro in `fs/btrfs/inode.c`
inside the `__cow_file_range_inline()` function.
Looking at the source code for `__cow_file_range_inline()`:
```c
ret = btrfs_drop_extents(trans, root, inode, &drop_args);
if (unlikely(ret)) {
btrfs_abort_transaction(trans, ret);
goto out;
}
```
The transaction is aborted because `btrfs_drop_extents()` returned an error. By
examining the register dump in the crash report, specifically `R13:
00000000ffffffe4` (which corresponds to `-28` or `-ENOSPC`), we can deduce that
`btrfs_drop_extents()` failed due to a lack of metadata space (`-ENOSPC`).
When `btrfs_drop_extents()` attempts to modify the extent tree (e.g., splitting
an extent) and the pre-reserved metadata space (`inode->block_rsv`) is
insufficient or the global block reserve is depleted, it returns `-ENOSPC`.
Because the tree might be left in an inconsistent state if the operation fails
halfway, BTRFS must abort the transaction.
**Security Impact:**
* **Exploitable:** No. This is a standard BTRFS transaction abort mechanism. It
does not cause memory corruption, use-after-free, out-of-bounds access, or
information leaks. It safely halts the transaction to prevent filesystem
corruption.
* **Denial of Service:** Yes. When a BTRFS transaction is aborted, the
filesystem is immediately forced into a read-only state to protect data
integrity. If this happens on the root filesystem or a critical mount, it
results in a Denial of Service.
* **Unprivileged / User Namespace:** Yes. An unprivileged user with write access
to a BTRFS filesystem can potentially trigger this by filling up the
filesystem's metadata space and performing specific write operations (e.g.,
writing small inline extents over existing preallocated extents) that exhaust
the block reserves.
* **Filesystem Trigger:** Yes. This can also be triggered by mounting a
maliciously crafted BTRFS image where the extent tree is artificially deep or
corrupted, causing `btrfs_drop_extents()` to require more metadata space than
what is normally reserved, leading to an immediate `-ENOSPC` and transaction
abort.]
|