mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2025-01-01 02:36:02 +00:00
7c4e39f9d2
At btrfs_ref_tree_mod() after we successfully inserted the new ref entry
(local variable 'ref') into the respective block entry's rbtree (local
variable 'be'), if we find an unexpected action of BTRFS_DROP_DELAYED_REF,
we error out and free the ref entry without removing it from the block
entry's rbtree. Then in the error path of btrfs_ref_tree_mod() we call
btrfs_free_ref_cache(), which iterates over all block entries and then
calls free_block_entry() for each one, and there we will trigger a
use-after-free when we are called against the block entry to which we
added the freed ref entry to its rbtree, since the rbtree still points
to the block entry, as we didn't remove it from the rbtree before freeing
it in the error path at btrfs_ref_tree_mod(). Fix this by removing the
new ref entry from the rbtree before freeing it.
Syzbot report this with the following stack traces:
BTRFS error (device loop0 state EA): Ref action 2, root 5, ref_root 0, parent 8564736, owner 0, offset 0, num_refs 18446744073709551615
__btrfs_mod_ref+0x7dd/0xac0 fs/btrfs/extent-tree.c:2523
update_ref_for_cow+0x9cd/0x11f0 fs/btrfs/ctree.c:512
btrfs_force_cow_block+0x9f6/0x1da0 fs/btrfs/ctree.c:594
btrfs_cow_block+0x35e/0xa40 fs/btrfs/ctree.c:754
btrfs_search_slot+0xbdd/0x30d0 fs/btrfs/ctree.c:2116
btrfs_insert_empty_items+0x9c/0x1a0 fs/btrfs/ctree.c:4314
btrfs_insert_empty_item fs/btrfs/ctree.h:669 [inline]
btrfs_insert_orphan_item+0x1f1/0x320 fs/btrfs/orphan.c:23
btrfs_orphan_add+0x6d/0x1a0 fs/btrfs/inode.c:3482
btrfs_unlink+0x267/0x350 fs/btrfs/inode.c:4293
vfs_unlink+0x365/0x650 fs/namei.c:4469
do_unlinkat+0x4ae/0x830 fs/namei.c:4533
__do_sys_unlinkat fs/namei.c:4576 [inline]
__se_sys_unlinkat fs/namei.c:4569 [inline]
__x64_sys_unlinkat+0xcc/0xf0 fs/namei.c:4569
do_syscall_x64 arch/x86/entry/common.c:52 [inline]
do_syscall_64+0xf3/0x230 arch/x86/entry/common.c:83
entry_SYSCALL_64_after_hwframe+0x77/0x7f
BTRFS error (device loop0 state EA): Ref action 1, root 5, ref_root 5, parent 0, owner 260, offset 0, num_refs 1
__btrfs_mod_ref+0x76b/0xac0 fs/btrfs/extent-tree.c:2521
update_ref_for_cow+0x96a/0x11f0
btrfs_force_cow_block+0x9f6/0x1da0 fs/btrfs/ctree.c:594
btrfs_cow_block+0x35e/0xa40 fs/btrfs/ctree.c:754
btrfs_search_slot+0xbdd/0x30d0 fs/btrfs/ctree.c:2116
btrfs_lookup_inode+0xdc/0x480 fs/btrfs/inode-item.c:411
__btrfs_update_delayed_inode+0x1e7/0xb90 fs/btrfs/delayed-inode.c:1030
btrfs_update_delayed_inode fs/btrfs/delayed-inode.c:1114 [inline]
__btrfs_commit_inode_delayed_items+0x2318/0x24a0 fs/btrfs/delayed-inode.c:1137
__btrfs_run_delayed_items+0x213/0x490 fs/btrfs/delayed-inode.c:1171
btrfs_commit_transaction+0x8a8/0x3740 fs/btrfs/transaction.c:2313
prepare_to_relocate+0x3c4/0x4c0 fs/btrfs/relocation.c:3586
relocate_block_group+0x16c/0xd40 fs/btrfs/relocation.c:3611
btrfs_relocate_block_group+0x77d/0xd90 fs/btrfs/relocation.c:4081
btrfs_relocate_chunk+0x12c/0x3b0 fs/btrfs/volumes.c:3377
__btrfs_balance+0x1b0f/0x26b0 fs/btrfs/volumes.c:4161
btrfs_balance+0xbdc/0x10c0 fs/btrfs/volumes.c:4538
BTRFS error (device loop0 state EA): Ref action 2, root 5, ref_root 0, parent 8564736, owner 0, offset 0, num_refs 18446744073709551615
__btrfs_mod_ref+0x7dd/0xac0 fs/btrfs/extent-tree.c:2523
update_ref_for_cow+0x9cd/0x11f0 fs/btrfs/ctree.c:512
btrfs_force_cow_block+0x9f6/0x1da0 fs/btrfs/ctree.c:594
btrfs_cow_block+0x35e/0xa40 fs/btrfs/ctree.c:754
btrfs_search_slot+0xbdd/0x30d0 fs/btrfs/ctree.c:2116
btrfs_lookup_inode+0xdc/0x480 fs/btrfs/inode-item.c:411
__btrfs_update_delayed_inode+0x1e7/0xb90 fs/btrfs/delayed-inode.c:1030
btrfs_update_delayed_inode fs/btrfs/delayed-inode.c:1114 [inline]
__btrfs_commit_inode_delayed_items+0x2318/0x24a0 fs/btrfs/delayed-inode.c:1137
__btrfs_run_delayed_items+0x213/0x490 fs/btrfs/delayed-inode.c:1171
btrfs_commit_transaction+0x8a8/0x3740 fs/btrfs/transaction.c:2313
prepare_to_relocate+0x3c4/0x4c0 fs/btrfs/relocation.c:3586
relocate_block_group+0x16c/0xd40 fs/btrfs/relocation.c:3611
btrfs_relocate_block_group+0x77d/0xd90 fs/btrfs/relocation.c:4081
btrfs_relocate_chunk+0x12c/0x3b0 fs/btrfs/volumes.c:3377
__btrfs_balance+0x1b0f/0x26b0 fs/btrfs/volumes.c:4161
btrfs_balance+0xbdc/0x10c0 fs/btrfs/volumes.c:4538
==================================================================
BUG: KASAN: slab-use-after-free in rb_first+0x69/0x70 lib/rbtree.c:473
Read of size 8 at addr ffff888042d1af38 by task syz.0.0/5329
CPU: 0 UID: 0 PID: 5329 Comm: syz.0.0 Not tainted 6.12.0-rc7-syzkaller #0
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2~bpo12+1 04/01/2014
Call Trace:
<TASK>
__dump_stack lib/dump_stack.c:94 [inline]
dump_stack_lvl+0x241/0x360 lib/dump_stack.c:120
print_address_description mm/kasan/report.c:377 [inline]
print_report+0x169/0x550 mm/kasan/report.c:488
kasan_report+0x143/0x180 mm/kasan/report.c:601
rb_first+0x69/0x70 lib/rbtree.c:473
free_block_entry+0x78/0x230 fs/btrfs/ref-verify.c:248
btrfs_free_ref_cache+0xa3/0x100 fs/btrfs/ref-verify.c:917
btrfs_ref_tree_mod+0x139f/0x15e0 fs/btrfs/ref-verify.c:898
btrfs_free_extent+0x33c/0x380 fs/btrfs/extent-tree.c:3544
__btrfs_mod_ref+0x7dd/0xac0 fs/btrfs/extent-tree.c:2523
update_ref_for_cow+0x9cd/0x11f0 fs/btrfs/ctree.c:512
btrfs_force_cow_block+0x9f6/0x1da0 fs/btrfs/ctree.c:594
btrfs_cow_block+0x35e/0xa40 fs/btrfs/ctree.c:754
btrfs_search_slot+0xbdd/0x30d0 fs/btrfs/ctree.c:2116
btrfs_lookup_inode+0xdc/0x480 fs/btrfs/inode-item.c:411
__btrfs_update_delayed_inode+0x1e7/0xb90 fs/btrfs/delayed-inode.c:1030
btrfs_update_delayed_inode fs/btrfs/delayed-inode.c:1114 [inline]
__btrfs_commit_inode_delayed_items+0x2318/0x24a0 fs/btrfs/delayed-inode.c:1137
__btrfs_run_delayed_items+0x213/0x490 fs/btrfs/delayed-inode.c:1171
btrfs_commit_transaction+0x8a8/0x3740 fs/btrfs/transaction.c:2313
prepare_to_relocate+0x3c4/0x4c0 fs/btrfs/relocation.c:3586
relocate_block_group+0x16c/0xd40 fs/btrfs/relocation.c:3611
btrfs_relocate_block_group+0x77d/0xd90 fs/btrfs/relocation.c:4081
btrfs_relocate_chunk+0x12c/0x3b0 fs/btrfs/volumes.c:3377
__btrfs_balance+0x1b0f/0x26b0 fs/btrfs/volumes.c:4161
btrfs_balance+0xbdc/0x10c0 fs/btrfs/volumes.c:4538
btrfs_ioctl_balance+0x493/0x7c0 fs/btrfs/ioctl.c:3673
vfs_ioctl fs/ioctl.c:51 [inline]
__do_sys_ioctl fs/ioctl.c:907 [inline]
__se_sys_ioctl+0xf9/0x170 fs/ioctl.c:893
do_syscall_x64 arch/x86/entry/common.c:52 [inline]
do_syscall_64+0xf3/0x230 arch/x86/entry/common.c:83
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f996df7e719
RSP: 002b:00007f996ede7038 EFLAGS: 00000246 ORIG_RAX: 0000000000000010
RAX: ffffffffffffffda RBX: 00007f996e135f80 RCX: 00007f996df7e719
RDX: 0000000020000180 RSI: 00000000c4009420 RDI: 0000000000000004
RBP: 00007f996dff139e R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
R13: 0000000000000000 R14: 00007f996e135f80 R15: 00007fff79f32e68
</TASK>
Allocated by task 5329:
kasan_save_stack mm/kasan/common.c:47 [inline]
kasan_save_track+0x3f/0x80 mm/kasan/common.c:68
poison_kmalloc_redzone mm/kasan/common.c:377 [inline]
__kasan_kmalloc+0x98/0xb0 mm/kasan/common.c:394
kasan_kmalloc include/linux/kasan.h:257 [inline]
__kmalloc_cache_noprof+0x19c/0x2c0 mm/slub.c:4295
kmalloc_noprof include/linux/slab.h:878 [inline]
kzalloc_noprof include/linux/slab.h:1014 [inline]
btrfs_ref_tree_mod+0x264/0x15e0 fs/btrfs/ref-verify.c:701
btrfs_free_extent+0x33c/0x380 fs/btrfs/extent-tree.c:3544
__btrfs_mod_ref+0x7dd/0xac0 fs/btrfs/extent-tree.c:2523
update_ref_for_cow+0x9cd/0x11f0 fs/btrfs/ctree.c:512
btrfs_force_cow_block+0x9f6/0x1da0 fs/btrfs/ctree.c:594
btrfs_cow_block+0x35e/0xa40 fs/btrfs/ctree.c:754
btrfs_search_slot+0xbdd/0x30d0 fs/btrfs/ctree.c:2116
btrfs_lookup_inode+0xdc/0x480 fs/btrfs/inode-item.c:411
__btrfs_update_delayed_inode+0x1e7/0xb90 fs/btrfs/delayed-inode.c:1030
btrfs_update_delayed_inode fs/btrfs/delayed-inode.c:1114 [inline]
__btrfs_commit_inode_delayed_items+0x2318/0x24a0 fs/btrfs/delayed-inode.c:1137
__btrfs_run_delayed_items+0x213/0x490 fs/btrfs/delayed-inode.c:1171
btrfs_commit_transaction+0x8a8/0x3740 fs/btrfs/transaction.c:2313
prepare_to_relocate+0x3c4/0x4c0 fs/btrfs/relocation.c:3586
relocate_block_group+0x16c/0xd40 fs/btrfs/relocation.c:3611
btrfs_relocate_block_group+0x77d/0xd90 fs/btrfs/relocation.c:4081
btrfs_relocate_chunk+0x12c/0x3b0 fs/btrfs/volumes.c:3377
__btrfs_balance+0x1b0f/0x26b0 fs/btrfs/volumes.c:4161
btrfs_balance+0xbdc/0x10c0 fs/btrfs/volumes.c:4538
btrfs_ioctl_balance+0x493/0x7c0 fs/btrfs/ioctl.c:3673
vfs_ioctl fs/ioctl.c:51 [inline]
__do_sys_ioctl fs/ioctl.c:907 [inline]
__se_sys_ioctl+0xf9/0x170 fs/ioctl.c:893
do_syscall_x64 arch/x86/entry/common.c:52 [inline]
do_syscall_64+0xf3/0x230 arch/x86/entry/common.c:83
entry_SYSCALL_64_after_hwframe+0x77/0x7f
Freed by task 5329:
kasan_save_stack mm/kasan/common.c:47 [inline]
kasan_save_track+0x3f/0x80 mm/kasan/common.c:68
kasan_save_free_info+0x40/0x50 mm/kasan/generic.c:579
poison_slab_object mm/kasan/common.c:247 [inline]
__kasan_slab_free+0x59/0x70 mm/kasan/common.c:264
kasan_slab_free include/linux/kasan.h:230 [inline]
slab_free_hook mm/slub.c:2342 [inline]
slab_free mm/slub.c:4579 [inline]
kfree+0x1a0/0x440 mm/slub.c:4727
btrfs_ref_tree_mod+0x136c/0x15e0
btrfs_free_extent+0x33c/0x380 fs/btrfs/extent-tree.c:3544
__btrfs_mod_ref+0x7dd/0xac0 fs/btrfs/extent-tree.c:2523
update_ref_for_cow+0x9cd/0x11f0 fs/btrfs/ctree.c:512
btrfs_force_cow_block+0x9f6/0x1da0 fs/btrfs/ctree.c:594
btrfs_cow_block+0x35e/0xa40 fs/btrfs/ctree.c:754
btrfs_search_slot+0xbdd/0x30d0 fs/btrfs/ctree.c:2116
btrfs_lookup_inode+0xdc/0x480 fs/btrfs/inode-item.c:411
__btrfs_update_delayed_inode+0x1e7/0xb90 fs/btrfs/delayed-inode.c:1030
btrfs_update_delayed_inode fs/btrfs/delayed-inode.c:1114 [inline]
__btrfs_commit_inode_delayed_items+0x2318/0x24a0 fs/btrfs/delayed-inode.c:1137
__btrfs_run_delayed_items+0x213/0x490 fs/btrfs/delayed-inode.c:1171
btrfs_commit_transaction+0x8a8/0x3740 fs/btrfs/transaction.c:2313
prepare_to_relocate+0x3c4/0x4c0 fs/btrfs/relocation.c:3586
relocate_block_group+0x16c/0xd40 fs/btrfs/relocation.c:3611
btrfs_relocate_block_group+0x77d/0xd90 fs/btrfs/relocation.c:4081
btrfs_relocate_chunk+0x12c/0x3b0 fs/btrfs/volumes.c:3377
__btrfs_balance+0x1b0f/0x26b0 fs/btrfs/volumes.c:4161
btrfs_balance+0xbdc/0x10c0 fs/btrfs/volumes.c:4538
btrfs_ioctl_balance+0x493/0x7c0 fs/btrfs/ioctl.c:3673
vfs_ioctl fs/ioctl.c:51 [inline]
__do_sys_ioctl fs/ioctl.c:907 [inline]
__se_sys_ioctl+0xf9/0x170 fs/ioctl.c:893
do_syscall_x64 arch/x86/entry/common.c:52 [inline]
do_syscall_64+0xf3/0x230 arch/x86/entry/common.c:83
entry_SYSCALL_64_after_hwframe+0x77/0x7f
The buggy address belongs to the object at ffff888042d1af00
which belongs to the cache kmalloc-64 of size 64
The buggy address is located 56 bytes inside of
freed 64-byte region [ffff888042d1af00, ffff888042d1af40)
The buggy address belongs to the physical page:
page: refcount:1 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x42d1a
anon flags: 0x4fff00000000000(node=1|zone=1|lastcpupid=0x7ff)
page_type: f5(slab)
raw: 04fff00000000000 ffff88801ac418c0 0000000000000000 dead000000000001
raw: 0000000000000000 0000000000200020 00000001f5000000 0000000000000000
page dumped because: kasan: bad access detected
page_owner tracks the page as allocated
page last allocated via order 0, migratetype Unmovable, gfp_mask 0x52c40(GFP_NOFS|__GFP_NOWARN|__GFP_NORETRY|__GFP_COMP), pid 5055, tgid 5055 (dhcpcd-run-hook), ts 40377240074, free_ts 40376848335
set_page_owner include/linux/page_owner.h:32 [inline]
post_alloc_hook+0x1f3/0x230 mm/page_alloc.c:1541
prep_new_page mm/page_alloc.c:1549 [inline]
get_page_from_freelist+0x3649/0x3790 mm/page_alloc.c:3459
__alloc_pages_noprof+0x292/0x710 mm/page_alloc.c:4735
alloc_pages_mpol_noprof+0x3e8/0x680 mm/mempolicy.c:2265
alloc_slab_page+0x6a/0x140 mm/slub.c:2412
allocate_slab+0x5a/0x2f0 mm/slub.c:2578
new_slab mm/slub.c:2631 [inline]
___slab_alloc+0xcd1/0x14b0 mm/slub.c:3818
__slab_alloc+0x58/0xa0 mm/slub.c:3908
__slab_alloc_node mm/slub.c:3961 [inline]
slab_alloc_node mm/slub.c:4122 [inline]
__do_kmalloc_node mm/slub.c:4263 [inline]
__kmalloc_noprof+0x25a/0x400 mm/slub.c:4276
kmalloc_noprof include/linux/slab.h:882 [inline]
kzalloc_noprof include/linux/slab.h:1014 [inline]
tomoyo_encode2 security/tomoyo/realpath.c:45 [inline]
tomoyo_encode+0x26f/0x540 security/tomoyo/realpath.c:80
tomoyo_realpath_from_path+0x59e/0x5e0 security/tomoyo/realpath.c:283
tomoyo_get_realpath security/tomoyo/file.c:151 [inline]
tomoyo_check_open_permission+0x255/0x500 security/tomoyo/file.c:771
security_file_open+0x777/0x990 security/security.c:3109
do_dentry_open+0x369/0x1460 fs/open.c:945
vfs_open+0x3e/0x330 fs/open.c:1088
do_open fs/namei.c:3774 [inline]
path_openat+0x2c84/0x3590 fs/namei.c:3933
page last free pid 5055 tgid 5055 stack trace:
reset_page_owner include/linux/page_owner.h:25 [inline]
free_pages_prepare mm/page_alloc.c:1112 [inline]
free_unref_page+0xcfb/0xf20 mm/page_alloc.c:2642
free_pipe_info+0x300/0x390 fs/pipe.c:860
put_pipe_info fs/pipe.c:719 [inline]
pipe_release+0x245/0x320 fs/pipe.c:742
__fput+0x23f/0x880 fs/file_table.c:431
__do_sys_close fs/open.c:1567 [inline]
__se_sys_close fs/open.c:1552 [inline]
__x64_sys_close+0x7f/0x110 fs/open.c:1552
do_syscall_x64 arch/x86/entry/common.c:52 [inline]
do_syscall_64+0xf3/0x230 arch/x86/entry/common.c:83
entry_SYSCALL_64_after_hwframe+0x77/0x7f
Memory state around the buggy address:
ffff888042d1ae00: fa fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc
ffff888042d1ae80: 00 00 00 00 00 fc fc fc fc fc fc fc fc fc fc fc
>ffff888042d1af00: fa fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc
^
ffff888042d1af80: 00 00 00 00 00 00 fc fc fc fc fc fc fc fc fc fc
ffff888042d1b000: 00 00 00 00 00 fc fc 00 00 00 00 00 fc fc 00 00
Reported-by: syzbot+7325f164162e200000c1@syzkaller.appspotmail.com
Link: https://lore.kernel.org/linux-btrfs/673723eb.050a0220.1324f8.00a8.GAE@google.com/T/#u
Fixes: fd708b81d9
("Btrfs: add a extent ref verify tool")
CC: stable@vger.kernel.org # 4.19+
Reviewed-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
1038 lines
25 KiB
C
1038 lines
25 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (C) 2014 Facebook. All rights reserved.
|
|
*/
|
|
|
|
#include <linux/sched.h>
|
|
#include <linux/stacktrace.h>
|
|
#include "messages.h"
|
|
#include "ctree.h"
|
|
#include "disk-io.h"
|
|
#include "locking.h"
|
|
#include "delayed-ref.h"
|
|
#include "ref-verify.h"
|
|
#include "fs.h"
|
|
#include "accessors.h"
|
|
|
|
/*
|
|
* Used to keep track the roots and number of refs each root has for a given
|
|
* bytenr. This just tracks the number of direct references, no shared
|
|
* references.
|
|
*/
|
|
struct root_entry {
|
|
u64 root_objectid;
|
|
u64 num_refs;
|
|
struct rb_node node;
|
|
};
|
|
|
|
/*
|
|
* These are meant to represent what should exist in the extent tree, these can
|
|
* be used to verify the extent tree is consistent as these should all match
|
|
* what the extent tree says.
|
|
*/
|
|
struct ref_entry {
|
|
u64 root_objectid;
|
|
u64 parent;
|
|
u64 owner;
|
|
u64 offset;
|
|
u64 num_refs;
|
|
struct rb_node node;
|
|
};
|
|
|
|
#define MAX_TRACE 16
|
|
|
|
/*
|
|
* Whenever we add/remove a reference we record the action. The action maps
|
|
* back to the delayed ref action. We hold the ref we are changing in the
|
|
* action so we can account for the history properly, and we record the root we
|
|
* were called with since it could be different from ref_root. We also store
|
|
* stack traces because that's how I roll.
|
|
*/
|
|
struct ref_action {
|
|
int action;
|
|
u64 root;
|
|
struct ref_entry ref;
|
|
struct list_head list;
|
|
unsigned long trace[MAX_TRACE];
|
|
unsigned int trace_len;
|
|
};
|
|
|
|
/*
|
|
* One of these for every block we reference, it holds the roots and references
|
|
* to it as well as all of the ref actions that have occurred to it. We never
|
|
* free it until we unmount the file system in order to make sure re-allocations
|
|
* are happening properly.
|
|
*/
|
|
struct block_entry {
|
|
u64 bytenr;
|
|
u64 len;
|
|
u64 num_refs;
|
|
int metadata;
|
|
int from_disk;
|
|
struct rb_root roots;
|
|
struct rb_root refs;
|
|
struct rb_node node;
|
|
struct list_head actions;
|
|
};
|
|
|
|
static struct block_entry *insert_block_entry(struct rb_root *root,
|
|
struct block_entry *be)
|
|
{
|
|
struct rb_node **p = &root->rb_node;
|
|
struct rb_node *parent_node = NULL;
|
|
struct block_entry *entry;
|
|
|
|
while (*p) {
|
|
parent_node = *p;
|
|
entry = rb_entry(parent_node, struct block_entry, node);
|
|
if (entry->bytenr > be->bytenr)
|
|
p = &(*p)->rb_left;
|
|
else if (entry->bytenr < be->bytenr)
|
|
p = &(*p)->rb_right;
|
|
else
|
|
return entry;
|
|
}
|
|
|
|
rb_link_node(&be->node, parent_node, p);
|
|
rb_insert_color(&be->node, root);
|
|
return NULL;
|
|
}
|
|
|
|
static struct block_entry *lookup_block_entry(struct rb_root *root, u64 bytenr)
|
|
{
|
|
struct rb_node *n;
|
|
struct block_entry *entry = NULL;
|
|
|
|
n = root->rb_node;
|
|
while (n) {
|
|
entry = rb_entry(n, struct block_entry, node);
|
|
if (entry->bytenr < bytenr)
|
|
n = n->rb_right;
|
|
else if (entry->bytenr > bytenr)
|
|
n = n->rb_left;
|
|
else
|
|
return entry;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static struct root_entry *insert_root_entry(struct rb_root *root,
|
|
struct root_entry *re)
|
|
{
|
|
struct rb_node **p = &root->rb_node;
|
|
struct rb_node *parent_node = NULL;
|
|
struct root_entry *entry;
|
|
|
|
while (*p) {
|
|
parent_node = *p;
|
|
entry = rb_entry(parent_node, struct root_entry, node);
|
|
if (entry->root_objectid > re->root_objectid)
|
|
p = &(*p)->rb_left;
|
|
else if (entry->root_objectid < re->root_objectid)
|
|
p = &(*p)->rb_right;
|
|
else
|
|
return entry;
|
|
}
|
|
|
|
rb_link_node(&re->node, parent_node, p);
|
|
rb_insert_color(&re->node, root);
|
|
return NULL;
|
|
|
|
}
|
|
|
|
static int comp_refs(struct ref_entry *ref1, struct ref_entry *ref2)
|
|
{
|
|
if (ref1->root_objectid < ref2->root_objectid)
|
|
return -1;
|
|
if (ref1->root_objectid > ref2->root_objectid)
|
|
return 1;
|
|
if (ref1->parent < ref2->parent)
|
|
return -1;
|
|
if (ref1->parent > ref2->parent)
|
|
return 1;
|
|
if (ref1->owner < ref2->owner)
|
|
return -1;
|
|
if (ref1->owner > ref2->owner)
|
|
return 1;
|
|
if (ref1->offset < ref2->offset)
|
|
return -1;
|
|
if (ref1->offset > ref2->offset)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static struct ref_entry *insert_ref_entry(struct rb_root *root,
|
|
struct ref_entry *ref)
|
|
{
|
|
struct rb_node **p = &root->rb_node;
|
|
struct rb_node *parent_node = NULL;
|
|
struct ref_entry *entry;
|
|
int cmp;
|
|
|
|
while (*p) {
|
|
parent_node = *p;
|
|
entry = rb_entry(parent_node, struct ref_entry, node);
|
|
cmp = comp_refs(entry, ref);
|
|
if (cmp > 0)
|
|
p = &(*p)->rb_left;
|
|
else if (cmp < 0)
|
|
p = &(*p)->rb_right;
|
|
else
|
|
return entry;
|
|
}
|
|
|
|
rb_link_node(&ref->node, parent_node, p);
|
|
rb_insert_color(&ref->node, root);
|
|
return NULL;
|
|
|
|
}
|
|
|
|
static struct root_entry *lookup_root_entry(struct rb_root *root, u64 objectid)
|
|
{
|
|
struct rb_node *n;
|
|
struct root_entry *entry = NULL;
|
|
|
|
n = root->rb_node;
|
|
while (n) {
|
|
entry = rb_entry(n, struct root_entry, node);
|
|
if (entry->root_objectid < objectid)
|
|
n = n->rb_right;
|
|
else if (entry->root_objectid > objectid)
|
|
n = n->rb_left;
|
|
else
|
|
return entry;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
#ifdef CONFIG_STACKTRACE
|
|
static void __save_stack_trace(struct ref_action *ra)
|
|
{
|
|
ra->trace_len = stack_trace_save(ra->trace, MAX_TRACE, 2);
|
|
}
|
|
|
|
static void __print_stack_trace(struct btrfs_fs_info *fs_info,
|
|
struct ref_action *ra)
|
|
{
|
|
if (ra->trace_len == 0) {
|
|
btrfs_err(fs_info, " ref-verify: no stacktrace");
|
|
return;
|
|
}
|
|
stack_trace_print(ra->trace, ra->trace_len, 2);
|
|
}
|
|
#else
|
|
static inline void __save_stack_trace(struct ref_action *ra)
|
|
{
|
|
}
|
|
|
|
static inline void __print_stack_trace(struct btrfs_fs_info *fs_info,
|
|
struct ref_action *ra)
|
|
{
|
|
btrfs_err(fs_info, " ref-verify: no stacktrace support");
|
|
}
|
|
#endif
|
|
|
|
static void free_block_entry(struct block_entry *be)
|
|
{
|
|
struct root_entry *re;
|
|
struct ref_entry *ref;
|
|
struct ref_action *ra;
|
|
struct rb_node *n;
|
|
|
|
while ((n = rb_first(&be->roots))) {
|
|
re = rb_entry(n, struct root_entry, node);
|
|
rb_erase(&re->node, &be->roots);
|
|
kfree(re);
|
|
}
|
|
|
|
while((n = rb_first(&be->refs))) {
|
|
ref = rb_entry(n, struct ref_entry, node);
|
|
rb_erase(&ref->node, &be->refs);
|
|
kfree(ref);
|
|
}
|
|
|
|
while (!list_empty(&be->actions)) {
|
|
ra = list_first_entry(&be->actions, struct ref_action,
|
|
list);
|
|
list_del(&ra->list);
|
|
kfree(ra);
|
|
}
|
|
kfree(be);
|
|
}
|
|
|
|
static struct block_entry *add_block_entry(struct btrfs_fs_info *fs_info,
|
|
u64 bytenr, u64 len,
|
|
u64 root_objectid)
|
|
{
|
|
struct block_entry *be = NULL, *exist;
|
|
struct root_entry *re = NULL;
|
|
|
|
re = kzalloc(sizeof(struct root_entry), GFP_NOFS);
|
|
be = kzalloc(sizeof(struct block_entry), GFP_NOFS);
|
|
if (!be || !re) {
|
|
kfree(re);
|
|
kfree(be);
|
|
return ERR_PTR(-ENOMEM);
|
|
}
|
|
be->bytenr = bytenr;
|
|
be->len = len;
|
|
|
|
re->root_objectid = root_objectid;
|
|
re->num_refs = 0;
|
|
|
|
spin_lock(&fs_info->ref_verify_lock);
|
|
exist = insert_block_entry(&fs_info->block_tree, be);
|
|
if (exist) {
|
|
if (root_objectid) {
|
|
struct root_entry *exist_re;
|
|
|
|
exist_re = insert_root_entry(&exist->roots, re);
|
|
if (exist_re)
|
|
kfree(re);
|
|
} else {
|
|
kfree(re);
|
|
}
|
|
kfree(be);
|
|
return exist;
|
|
}
|
|
|
|
be->num_refs = 0;
|
|
be->metadata = 0;
|
|
be->from_disk = 0;
|
|
be->roots = RB_ROOT;
|
|
be->refs = RB_ROOT;
|
|
INIT_LIST_HEAD(&be->actions);
|
|
if (root_objectid)
|
|
insert_root_entry(&be->roots, re);
|
|
else
|
|
kfree(re);
|
|
return be;
|
|
}
|
|
|
|
static int add_tree_block(struct btrfs_fs_info *fs_info, u64 ref_root,
|
|
u64 parent, u64 bytenr, int level)
|
|
{
|
|
struct block_entry *be;
|
|
struct root_entry *re;
|
|
struct ref_entry *ref = NULL, *exist;
|
|
|
|
ref = kmalloc(sizeof(struct ref_entry), GFP_NOFS);
|
|
if (!ref)
|
|
return -ENOMEM;
|
|
|
|
if (parent)
|
|
ref->root_objectid = 0;
|
|
else
|
|
ref->root_objectid = ref_root;
|
|
ref->parent = parent;
|
|
ref->owner = level;
|
|
ref->offset = 0;
|
|
ref->num_refs = 1;
|
|
|
|
be = add_block_entry(fs_info, bytenr, fs_info->nodesize, ref_root);
|
|
if (IS_ERR(be)) {
|
|
kfree(ref);
|
|
return PTR_ERR(be);
|
|
}
|
|
be->num_refs++;
|
|
be->from_disk = 1;
|
|
be->metadata = 1;
|
|
|
|
if (!parent) {
|
|
ASSERT(ref_root);
|
|
re = lookup_root_entry(&be->roots, ref_root);
|
|
ASSERT(re);
|
|
re->num_refs++;
|
|
}
|
|
exist = insert_ref_entry(&be->refs, ref);
|
|
if (exist) {
|
|
exist->num_refs++;
|
|
kfree(ref);
|
|
}
|
|
spin_unlock(&fs_info->ref_verify_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int add_shared_data_ref(struct btrfs_fs_info *fs_info,
|
|
u64 parent, u32 num_refs, u64 bytenr,
|
|
u64 num_bytes)
|
|
{
|
|
struct block_entry *be;
|
|
struct ref_entry *ref;
|
|
|
|
ref = kzalloc(sizeof(struct ref_entry), GFP_NOFS);
|
|
if (!ref)
|
|
return -ENOMEM;
|
|
be = add_block_entry(fs_info, bytenr, num_bytes, 0);
|
|
if (IS_ERR(be)) {
|
|
kfree(ref);
|
|
return PTR_ERR(be);
|
|
}
|
|
be->num_refs += num_refs;
|
|
|
|
ref->parent = parent;
|
|
ref->num_refs = num_refs;
|
|
if (insert_ref_entry(&be->refs, ref)) {
|
|
spin_unlock(&fs_info->ref_verify_lock);
|
|
btrfs_err(fs_info, "existing shared ref when reading from disk?");
|
|
kfree(ref);
|
|
return -EINVAL;
|
|
}
|
|
spin_unlock(&fs_info->ref_verify_lock);
|
|
return 0;
|
|
}
|
|
|
|
static int add_extent_data_ref(struct btrfs_fs_info *fs_info,
|
|
struct extent_buffer *leaf,
|
|
struct btrfs_extent_data_ref *dref,
|
|
u64 bytenr, u64 num_bytes)
|
|
{
|
|
struct block_entry *be;
|
|
struct ref_entry *ref;
|
|
struct root_entry *re;
|
|
u64 ref_root = btrfs_extent_data_ref_root(leaf, dref);
|
|
u64 owner = btrfs_extent_data_ref_objectid(leaf, dref);
|
|
u64 offset = btrfs_extent_data_ref_offset(leaf, dref);
|
|
u32 num_refs = btrfs_extent_data_ref_count(leaf, dref);
|
|
|
|
ref = kzalloc(sizeof(struct ref_entry), GFP_NOFS);
|
|
if (!ref)
|
|
return -ENOMEM;
|
|
be = add_block_entry(fs_info, bytenr, num_bytes, ref_root);
|
|
if (IS_ERR(be)) {
|
|
kfree(ref);
|
|
return PTR_ERR(be);
|
|
}
|
|
be->num_refs += num_refs;
|
|
|
|
ref->parent = 0;
|
|
ref->owner = owner;
|
|
ref->root_objectid = ref_root;
|
|
ref->offset = offset;
|
|
ref->num_refs = num_refs;
|
|
if (insert_ref_entry(&be->refs, ref)) {
|
|
spin_unlock(&fs_info->ref_verify_lock);
|
|
btrfs_err(fs_info, "existing ref when reading from disk?");
|
|
kfree(ref);
|
|
return -EINVAL;
|
|
}
|
|
|
|
re = lookup_root_entry(&be->roots, ref_root);
|
|
if (!re) {
|
|
spin_unlock(&fs_info->ref_verify_lock);
|
|
btrfs_err(fs_info, "missing root in new block entry?");
|
|
return -EINVAL;
|
|
}
|
|
re->num_refs += num_refs;
|
|
spin_unlock(&fs_info->ref_verify_lock);
|
|
return 0;
|
|
}
|
|
|
|
static int process_extent_item(struct btrfs_fs_info *fs_info,
|
|
struct btrfs_path *path, struct btrfs_key *key,
|
|
int slot, int *tree_block_level)
|
|
{
|
|
struct btrfs_extent_item *ei;
|
|
struct btrfs_extent_inline_ref *iref;
|
|
struct btrfs_extent_data_ref *dref;
|
|
struct btrfs_shared_data_ref *sref;
|
|
struct extent_buffer *leaf = path->nodes[0];
|
|
u32 item_size = btrfs_item_size(leaf, slot);
|
|
unsigned long end, ptr;
|
|
u64 offset, flags, count;
|
|
int type;
|
|
int ret = 0;
|
|
|
|
ei = btrfs_item_ptr(leaf, slot, struct btrfs_extent_item);
|
|
flags = btrfs_extent_flags(leaf, ei);
|
|
|
|
if ((key->type == BTRFS_EXTENT_ITEM_KEY) &&
|
|
flags & BTRFS_EXTENT_FLAG_TREE_BLOCK) {
|
|
struct btrfs_tree_block_info *info;
|
|
|
|
info = (struct btrfs_tree_block_info *)(ei + 1);
|
|
*tree_block_level = btrfs_tree_block_level(leaf, info);
|
|
iref = (struct btrfs_extent_inline_ref *)(info + 1);
|
|
} else {
|
|
if (key->type == BTRFS_METADATA_ITEM_KEY)
|
|
*tree_block_level = key->offset;
|
|
iref = (struct btrfs_extent_inline_ref *)(ei + 1);
|
|
}
|
|
|
|
ptr = (unsigned long)iref;
|
|
end = (unsigned long)ei + item_size;
|
|
while (ptr < end) {
|
|
iref = (struct btrfs_extent_inline_ref *)ptr;
|
|
type = btrfs_extent_inline_ref_type(leaf, iref);
|
|
offset = btrfs_extent_inline_ref_offset(leaf, iref);
|
|
switch (type) {
|
|
case BTRFS_TREE_BLOCK_REF_KEY:
|
|
ret = add_tree_block(fs_info, offset, 0, key->objectid,
|
|
*tree_block_level);
|
|
break;
|
|
case BTRFS_SHARED_BLOCK_REF_KEY:
|
|
ret = add_tree_block(fs_info, 0, offset, key->objectid,
|
|
*tree_block_level);
|
|
break;
|
|
case BTRFS_EXTENT_DATA_REF_KEY:
|
|
dref = (struct btrfs_extent_data_ref *)(&iref->offset);
|
|
ret = add_extent_data_ref(fs_info, leaf, dref,
|
|
key->objectid, key->offset);
|
|
break;
|
|
case BTRFS_SHARED_DATA_REF_KEY:
|
|
sref = (struct btrfs_shared_data_ref *)(iref + 1);
|
|
count = btrfs_shared_data_ref_count(leaf, sref);
|
|
ret = add_shared_data_ref(fs_info, offset, count,
|
|
key->objectid, key->offset);
|
|
break;
|
|
case BTRFS_EXTENT_OWNER_REF_KEY:
|
|
if (!btrfs_fs_incompat(fs_info, SIMPLE_QUOTA)) {
|
|
btrfs_err(fs_info,
|
|
"found extent owner ref without simple quotas enabled");
|
|
ret = -EINVAL;
|
|
}
|
|
break;
|
|
default:
|
|
btrfs_err(fs_info, "invalid key type in iref");
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
if (ret)
|
|
break;
|
|
ptr += btrfs_extent_inline_ref_size(type);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int process_leaf(struct btrfs_root *root,
|
|
struct btrfs_path *path, u64 *bytenr, u64 *num_bytes,
|
|
int *tree_block_level)
|
|
{
|
|
struct btrfs_fs_info *fs_info = root->fs_info;
|
|
struct extent_buffer *leaf = path->nodes[0];
|
|
struct btrfs_extent_data_ref *dref;
|
|
struct btrfs_shared_data_ref *sref;
|
|
u32 count;
|
|
int i = 0, ret = 0;
|
|
struct btrfs_key key;
|
|
int nritems = btrfs_header_nritems(leaf);
|
|
|
|
for (i = 0; i < nritems; i++) {
|
|
btrfs_item_key_to_cpu(leaf, &key, i);
|
|
switch (key.type) {
|
|
case BTRFS_EXTENT_ITEM_KEY:
|
|
*num_bytes = key.offset;
|
|
fallthrough;
|
|
case BTRFS_METADATA_ITEM_KEY:
|
|
*bytenr = key.objectid;
|
|
ret = process_extent_item(fs_info, path, &key, i,
|
|
tree_block_level);
|
|
break;
|
|
case BTRFS_TREE_BLOCK_REF_KEY:
|
|
ret = add_tree_block(fs_info, key.offset, 0,
|
|
key.objectid, *tree_block_level);
|
|
break;
|
|
case BTRFS_SHARED_BLOCK_REF_KEY:
|
|
ret = add_tree_block(fs_info, 0, key.offset,
|
|
key.objectid, *tree_block_level);
|
|
break;
|
|
case BTRFS_EXTENT_DATA_REF_KEY:
|
|
dref = btrfs_item_ptr(leaf, i,
|
|
struct btrfs_extent_data_ref);
|
|
ret = add_extent_data_ref(fs_info, leaf, dref, *bytenr,
|
|
*num_bytes);
|
|
break;
|
|
case BTRFS_SHARED_DATA_REF_KEY:
|
|
sref = btrfs_item_ptr(leaf, i,
|
|
struct btrfs_shared_data_ref);
|
|
count = btrfs_shared_data_ref_count(leaf, sref);
|
|
ret = add_shared_data_ref(fs_info, key.offset, count,
|
|
*bytenr, *num_bytes);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (ret)
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/* Walk down to the leaf from the given level */
|
|
static int walk_down_tree(struct btrfs_root *root, struct btrfs_path *path,
|
|
int level, u64 *bytenr, u64 *num_bytes,
|
|
int *tree_block_level)
|
|
{
|
|
struct extent_buffer *eb;
|
|
int ret = 0;
|
|
|
|
while (level >= 0) {
|
|
if (level) {
|
|
eb = btrfs_read_node_slot(path->nodes[level],
|
|
path->slots[level]);
|
|
if (IS_ERR(eb))
|
|
return PTR_ERR(eb);
|
|
btrfs_tree_read_lock(eb);
|
|
path->nodes[level-1] = eb;
|
|
path->slots[level-1] = 0;
|
|
path->locks[level-1] = BTRFS_READ_LOCK;
|
|
} else {
|
|
ret = process_leaf(root, path, bytenr, num_bytes,
|
|
tree_block_level);
|
|
if (ret)
|
|
break;
|
|
}
|
|
level--;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/* Walk up to the next node that needs to be processed */
|
|
static int walk_up_tree(struct btrfs_path *path, int *level)
|
|
{
|
|
int l;
|
|
|
|
for (l = 0; l < BTRFS_MAX_LEVEL; l++) {
|
|
if (!path->nodes[l])
|
|
continue;
|
|
if (l) {
|
|
path->slots[l]++;
|
|
if (path->slots[l] <
|
|
btrfs_header_nritems(path->nodes[l])) {
|
|
*level = l;
|
|
return 0;
|
|
}
|
|
}
|
|
btrfs_tree_unlock_rw(path->nodes[l], path->locks[l]);
|
|
free_extent_buffer(path->nodes[l]);
|
|
path->nodes[l] = NULL;
|
|
path->slots[l] = 0;
|
|
path->locks[l] = 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void dump_ref_action(struct btrfs_fs_info *fs_info,
|
|
struct ref_action *ra)
|
|
{
|
|
btrfs_err(fs_info,
|
|
" Ref action %d, root %llu, ref_root %llu, parent %llu, owner %llu, offset %llu, num_refs %llu",
|
|
ra->action, ra->root, ra->ref.root_objectid, ra->ref.parent,
|
|
ra->ref.owner, ra->ref.offset, ra->ref.num_refs);
|
|
__print_stack_trace(fs_info, ra);
|
|
}
|
|
|
|
/*
|
|
* Dumps all the information from the block entry to printk, it's going to be
|
|
* awesome.
|
|
*/
|
|
static void dump_block_entry(struct btrfs_fs_info *fs_info,
|
|
struct block_entry *be)
|
|
{
|
|
struct ref_entry *ref;
|
|
struct root_entry *re;
|
|
struct ref_action *ra;
|
|
struct rb_node *n;
|
|
|
|
btrfs_err(fs_info,
|
|
"dumping block entry [%llu %llu], num_refs %llu, metadata %d, from disk %d",
|
|
be->bytenr, be->len, be->num_refs, be->metadata,
|
|
be->from_disk);
|
|
|
|
for (n = rb_first(&be->refs); n; n = rb_next(n)) {
|
|
ref = rb_entry(n, struct ref_entry, node);
|
|
btrfs_err(fs_info,
|
|
" ref root %llu, parent %llu, owner %llu, offset %llu, num_refs %llu",
|
|
ref->root_objectid, ref->parent, ref->owner,
|
|
ref->offset, ref->num_refs);
|
|
}
|
|
|
|
for (n = rb_first(&be->roots); n; n = rb_next(n)) {
|
|
re = rb_entry(n, struct root_entry, node);
|
|
btrfs_err(fs_info, " root entry %llu, num_refs %llu",
|
|
re->root_objectid, re->num_refs);
|
|
}
|
|
|
|
list_for_each_entry(ra, &be->actions, list)
|
|
dump_ref_action(fs_info, ra);
|
|
}
|
|
|
|
/*
|
|
* Called when we modify a ref for a bytenr.
|
|
*
|
|
* This will add an action item to the given bytenr and do sanity checks to make
|
|
* sure we haven't messed something up. If we are making a new allocation and
|
|
* this block entry has history we will delete all previous actions as long as
|
|
* our sanity checks pass as they are no longer needed.
|
|
*/
|
|
int btrfs_ref_tree_mod(struct btrfs_fs_info *fs_info,
|
|
struct btrfs_ref *generic_ref)
|
|
{
|
|
struct ref_entry *ref = NULL, *exist;
|
|
struct ref_action *ra = NULL;
|
|
struct block_entry *be = NULL;
|
|
struct root_entry *re = NULL;
|
|
int action = generic_ref->action;
|
|
int ret = 0;
|
|
bool metadata;
|
|
u64 bytenr = generic_ref->bytenr;
|
|
u64 num_bytes = generic_ref->num_bytes;
|
|
u64 parent = generic_ref->parent;
|
|
u64 ref_root = 0;
|
|
u64 owner = 0;
|
|
u64 offset = 0;
|
|
|
|
if (!btrfs_test_opt(fs_info, REF_VERIFY))
|
|
return 0;
|
|
|
|
if (generic_ref->type == BTRFS_REF_METADATA) {
|
|
if (!parent)
|
|
ref_root = generic_ref->ref_root;
|
|
owner = generic_ref->tree_ref.level;
|
|
} else if (!parent) {
|
|
ref_root = generic_ref->ref_root;
|
|
owner = generic_ref->data_ref.objectid;
|
|
offset = generic_ref->data_ref.offset;
|
|
}
|
|
metadata = owner < BTRFS_FIRST_FREE_OBJECTID;
|
|
|
|
ref = kzalloc(sizeof(struct ref_entry), GFP_NOFS);
|
|
ra = kmalloc(sizeof(struct ref_action), GFP_NOFS);
|
|
if (!ra || !ref) {
|
|
kfree(ref);
|
|
kfree(ra);
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
ref->parent = parent;
|
|
ref->owner = owner;
|
|
ref->root_objectid = ref_root;
|
|
ref->offset = offset;
|
|
ref->num_refs = (action == BTRFS_DROP_DELAYED_REF) ? -1 : 1;
|
|
|
|
memcpy(&ra->ref, ref, sizeof(struct ref_entry));
|
|
/*
|
|
* Save the extra info from the delayed ref in the ref action to make it
|
|
* easier to figure out what is happening. The real ref's we add to the
|
|
* ref tree need to reflect what we save on disk so it matches any
|
|
* on-disk refs we pre-loaded.
|
|
*/
|
|
ra->ref.owner = owner;
|
|
ra->ref.offset = offset;
|
|
ra->ref.root_objectid = ref_root;
|
|
__save_stack_trace(ra);
|
|
|
|
INIT_LIST_HEAD(&ra->list);
|
|
ra->action = action;
|
|
ra->root = generic_ref->real_root;
|
|
|
|
/*
|
|
* This is an allocation, preallocate the block_entry in case we haven't
|
|
* used it before.
|
|
*/
|
|
ret = -EINVAL;
|
|
if (action == BTRFS_ADD_DELAYED_EXTENT) {
|
|
/*
|
|
* For subvol_create we'll just pass in whatever the parent root
|
|
* is and the new root objectid, so let's not treat the passed
|
|
* in root as if it really has a ref for this bytenr.
|
|
*/
|
|
be = add_block_entry(fs_info, bytenr, num_bytes, ref_root);
|
|
if (IS_ERR(be)) {
|
|
kfree(ref);
|
|
kfree(ra);
|
|
ret = PTR_ERR(be);
|
|
goto out;
|
|
}
|
|
be->num_refs++;
|
|
if (metadata)
|
|
be->metadata = 1;
|
|
|
|
if (be->num_refs != 1) {
|
|
btrfs_err(fs_info,
|
|
"re-allocated a block that still has references to it!");
|
|
dump_block_entry(fs_info, be);
|
|
dump_ref_action(fs_info, ra);
|
|
kfree(ref);
|
|
kfree(ra);
|
|
goto out_unlock;
|
|
}
|
|
|
|
while (!list_empty(&be->actions)) {
|
|
struct ref_action *tmp;
|
|
|
|
tmp = list_first_entry(&be->actions, struct ref_action,
|
|
list);
|
|
list_del(&tmp->list);
|
|
kfree(tmp);
|
|
}
|
|
} else {
|
|
struct root_entry *tmp;
|
|
|
|
if (!parent) {
|
|
re = kmalloc(sizeof(struct root_entry), GFP_NOFS);
|
|
if (!re) {
|
|
kfree(ref);
|
|
kfree(ra);
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
/*
|
|
* This is the root that is modifying us, so it's the
|
|
* one we want to lookup below when we modify the
|
|
* re->num_refs.
|
|
*/
|
|
ref_root = generic_ref->real_root;
|
|
re->root_objectid = generic_ref->real_root;
|
|
re->num_refs = 0;
|
|
}
|
|
|
|
spin_lock(&fs_info->ref_verify_lock);
|
|
be = lookup_block_entry(&fs_info->block_tree, bytenr);
|
|
if (!be) {
|
|
btrfs_err(fs_info,
|
|
"trying to do action %d to bytenr %llu num_bytes %llu but there is no existing entry!",
|
|
action, bytenr, num_bytes);
|
|
dump_ref_action(fs_info, ra);
|
|
kfree(ref);
|
|
kfree(ra);
|
|
kfree(re);
|
|
goto out_unlock;
|
|
} else if (be->num_refs == 0) {
|
|
btrfs_err(fs_info,
|
|
"trying to do action %d for a bytenr that has 0 total references",
|
|
action);
|
|
dump_block_entry(fs_info, be);
|
|
dump_ref_action(fs_info, ra);
|
|
kfree(ref);
|
|
kfree(ra);
|
|
kfree(re);
|
|
goto out_unlock;
|
|
}
|
|
|
|
if (!parent) {
|
|
tmp = insert_root_entry(&be->roots, re);
|
|
if (tmp) {
|
|
kfree(re);
|
|
re = tmp;
|
|
}
|
|
}
|
|
}
|
|
|
|
exist = insert_ref_entry(&be->refs, ref);
|
|
if (exist) {
|
|
if (action == BTRFS_DROP_DELAYED_REF) {
|
|
if (exist->num_refs == 0) {
|
|
btrfs_err(fs_info,
|
|
"dropping a ref for a existing root that doesn't have a ref on the block");
|
|
dump_block_entry(fs_info, be);
|
|
dump_ref_action(fs_info, ra);
|
|
kfree(ref);
|
|
kfree(ra);
|
|
goto out_unlock;
|
|
}
|
|
exist->num_refs--;
|
|
if (exist->num_refs == 0) {
|
|
rb_erase(&exist->node, &be->refs);
|
|
kfree(exist);
|
|
}
|
|
} else if (!be->metadata) {
|
|
exist->num_refs++;
|
|
} else {
|
|
btrfs_err(fs_info,
|
|
"attempting to add another ref for an existing ref on a tree block");
|
|
dump_block_entry(fs_info, be);
|
|
dump_ref_action(fs_info, ra);
|
|
kfree(ref);
|
|
kfree(ra);
|
|
goto out_unlock;
|
|
}
|
|
kfree(ref);
|
|
} else {
|
|
if (action == BTRFS_DROP_DELAYED_REF) {
|
|
btrfs_err(fs_info,
|
|
"dropping a ref for a root that doesn't have a ref on the block");
|
|
dump_block_entry(fs_info, be);
|
|
dump_ref_action(fs_info, ra);
|
|
rb_erase(&ref->node, &be->refs);
|
|
kfree(ref);
|
|
kfree(ra);
|
|
goto out_unlock;
|
|
}
|
|
}
|
|
|
|
if (!parent && !re) {
|
|
re = lookup_root_entry(&be->roots, ref_root);
|
|
if (!re) {
|
|
/*
|
|
* This shouldn't happen because we will add our re
|
|
* above when we lookup the be with !parent, but just in
|
|
* case catch this case so we don't panic because I
|
|
* didn't think of some other corner case.
|
|
*/
|
|
btrfs_err(fs_info, "failed to find root %llu for %llu",
|
|
generic_ref->real_root, be->bytenr);
|
|
dump_block_entry(fs_info, be);
|
|
dump_ref_action(fs_info, ra);
|
|
kfree(ra);
|
|
goto out_unlock;
|
|
}
|
|
}
|
|
if (action == BTRFS_DROP_DELAYED_REF) {
|
|
if (re)
|
|
re->num_refs--;
|
|
be->num_refs--;
|
|
} else if (action == BTRFS_ADD_DELAYED_REF) {
|
|
be->num_refs++;
|
|
if (re)
|
|
re->num_refs++;
|
|
}
|
|
list_add_tail(&ra->list, &be->actions);
|
|
ret = 0;
|
|
out_unlock:
|
|
spin_unlock(&fs_info->ref_verify_lock);
|
|
out:
|
|
if (ret) {
|
|
btrfs_free_ref_cache(fs_info);
|
|
btrfs_clear_opt(fs_info->mount_opt, REF_VERIFY);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/* Free up the ref cache */
|
|
void btrfs_free_ref_cache(struct btrfs_fs_info *fs_info)
|
|
{
|
|
struct block_entry *be;
|
|
struct rb_node *n;
|
|
|
|
if (!btrfs_test_opt(fs_info, REF_VERIFY))
|
|
return;
|
|
|
|
spin_lock(&fs_info->ref_verify_lock);
|
|
while ((n = rb_first(&fs_info->block_tree))) {
|
|
be = rb_entry(n, struct block_entry, node);
|
|
rb_erase(&be->node, &fs_info->block_tree);
|
|
free_block_entry(be);
|
|
cond_resched_lock(&fs_info->ref_verify_lock);
|
|
}
|
|
spin_unlock(&fs_info->ref_verify_lock);
|
|
}
|
|
|
|
void btrfs_free_ref_tree_range(struct btrfs_fs_info *fs_info, u64 start,
|
|
u64 len)
|
|
{
|
|
struct block_entry *be = NULL, *entry;
|
|
struct rb_node *n;
|
|
|
|
if (!btrfs_test_opt(fs_info, REF_VERIFY))
|
|
return;
|
|
|
|
spin_lock(&fs_info->ref_verify_lock);
|
|
n = fs_info->block_tree.rb_node;
|
|
while (n) {
|
|
entry = rb_entry(n, struct block_entry, node);
|
|
if (entry->bytenr < start) {
|
|
n = n->rb_right;
|
|
} else if (entry->bytenr > start) {
|
|
n = n->rb_left;
|
|
} else {
|
|
be = entry;
|
|
break;
|
|
}
|
|
/* We want to get as close to start as possible */
|
|
if (be == NULL ||
|
|
(entry->bytenr < start && be->bytenr > start) ||
|
|
(entry->bytenr < start && entry->bytenr > be->bytenr))
|
|
be = entry;
|
|
}
|
|
|
|
/*
|
|
* Could have an empty block group, maybe have something to check for
|
|
* this case to verify we were actually empty?
|
|
*/
|
|
if (!be) {
|
|
spin_unlock(&fs_info->ref_verify_lock);
|
|
return;
|
|
}
|
|
|
|
n = &be->node;
|
|
while (n) {
|
|
be = rb_entry(n, struct block_entry, node);
|
|
n = rb_next(n);
|
|
if (be->bytenr < start && be->bytenr + be->len > start) {
|
|
btrfs_err(fs_info,
|
|
"block entry overlaps a block group [%llu,%llu]!",
|
|
start, len);
|
|
dump_block_entry(fs_info, be);
|
|
continue;
|
|
}
|
|
if (be->bytenr < start)
|
|
continue;
|
|
if (be->bytenr >= start + len)
|
|
break;
|
|
if (be->bytenr + be->len > start + len) {
|
|
btrfs_err(fs_info,
|
|
"block entry overlaps a block group [%llu,%llu]!",
|
|
start, len);
|
|
dump_block_entry(fs_info, be);
|
|
}
|
|
rb_erase(&be->node, &fs_info->block_tree);
|
|
free_block_entry(be);
|
|
}
|
|
spin_unlock(&fs_info->ref_verify_lock);
|
|
}
|
|
|
|
/* Walk down all roots and build the ref tree, meant to be called at mount */
|
|
int btrfs_build_ref_tree(struct btrfs_fs_info *fs_info)
|
|
{
|
|
struct btrfs_root *extent_root;
|
|
struct btrfs_path *path;
|
|
struct extent_buffer *eb;
|
|
int tree_block_level = 0;
|
|
u64 bytenr = 0, num_bytes = 0;
|
|
int ret, level;
|
|
|
|
if (!btrfs_test_opt(fs_info, REF_VERIFY))
|
|
return 0;
|
|
|
|
path = btrfs_alloc_path();
|
|
if (!path)
|
|
return -ENOMEM;
|
|
|
|
extent_root = btrfs_extent_root(fs_info, 0);
|
|
eb = btrfs_read_lock_root_node(extent_root);
|
|
level = btrfs_header_level(eb);
|
|
path->nodes[level] = eb;
|
|
path->slots[level] = 0;
|
|
path->locks[level] = BTRFS_READ_LOCK;
|
|
|
|
while (1) {
|
|
/*
|
|
* We have to keep track of the bytenr/num_bytes we last hit
|
|
* because we could have run out of space for an inline ref, and
|
|
* would have had to added a ref key item which may appear on a
|
|
* different leaf from the original extent item.
|
|
*/
|
|
ret = walk_down_tree(extent_root, path, level,
|
|
&bytenr, &num_bytes, &tree_block_level);
|
|
if (ret)
|
|
break;
|
|
ret = walk_up_tree(path, &level);
|
|
if (ret < 0)
|
|
break;
|
|
if (ret > 0) {
|
|
ret = 0;
|
|
break;
|
|
}
|
|
}
|
|
if (ret) {
|
|
btrfs_free_ref_cache(fs_info);
|
|
btrfs_clear_opt(fs_info->mount_opt, REF_VERIFY);
|
|
}
|
|
btrfs_free_path(path);
|
|
return ret;
|
|
}
|