2007-02-26 18:40:21 +03:00
# include <stdio.h>
# include <stdlib.h>
# include "kerncompat.h"
# include "radix-tree.h"
# include "ctree.h"
# include "disk-io.h"
# include "print-tree.h"
2007-03-07 19:50:24 +03:00
static int find_free_extent ( struct ctree_root * orig_root , u64 num_blocks ,
2007-03-12 23:22:34 +03:00
u64 search_start , u64 search_end ,
struct btrfs_key * ins ) ;
2007-03-07 19:50:24 +03:00
static int finish_current_insert ( struct ctree_root * extent_root ) ;
static int run_pending ( struct ctree_root * extent_root ) ;
2007-02-26 18:40:21 +03:00
/*
* pending extents are blocks that we ' re trying to allocate in the extent
* map while trying to grow the map because of other allocations . To avoid
* recursing , they are tagged in the radix tree and cleaned up after
* other allocations are done . The pending tag is also used in the same
* manner for deletes .
*/
2007-03-07 19:50:24 +03:00
# define CTREE_EXTENT_PENDING_DEL 0
2007-02-26 18:40:21 +03:00
2007-03-03 00:08:05 +03:00
static int inc_block_ref ( struct ctree_root * root , u64 blocknr )
{
struct ctree_path path ;
int ret ;
2007-03-12 23:22:34 +03:00
struct btrfs_key key ;
2007-03-03 00:08:05 +03:00
struct leaf * l ;
struct extent_item * item ;
2007-03-12 23:22:34 +03:00
struct btrfs_key ins ;
2007-03-07 19:50:24 +03:00
find_free_extent ( root - > extent_root , 0 , 0 , ( u64 ) - 1 , & ins ) ;
2007-03-03 00:08:05 +03:00
init_path ( & path ) ;
key . objectid = blocknr ;
key . flags = 0 ;
key . offset = 1 ;
ret = search_slot ( root - > extent_root , & key , & path , 0 , 1 ) ;
2007-03-07 04:08:01 +03:00
if ( ret ! = 0 )
BUG ( ) ;
2007-03-03 00:08:05 +03:00
BUG_ON ( ret ! = 0 ) ;
l = & path . nodes [ 0 ] - > leaf ;
2007-03-13 03:12:07 +03:00
item = ( struct extent_item * ) ( l - > data + btrfs_item_offset ( l - > items +
path . slots [ 0 ] ) ) ;
2007-03-03 00:08:05 +03:00
item - > refs + + ;
2007-03-07 04:08:01 +03:00
2007-03-03 00:08:05 +03:00
BUG_ON ( list_empty ( & path . nodes [ 0 ] - > dirty ) ) ;
release_path ( root - > extent_root , & path ) ;
2007-03-07 19:50:24 +03:00
finish_current_insert ( root - > extent_root ) ;
run_pending ( root - > extent_root ) ;
2007-03-03 00:08:05 +03:00
return 0 ;
}
2007-03-10 14:35:47 +03:00
static int lookup_block_ref ( struct ctree_root * root , u64 blocknr , u32 * refs )
2007-03-07 04:08:01 +03:00
{
struct ctree_path path ;
int ret ;
2007-03-12 23:22:34 +03:00
struct btrfs_key key ;
2007-03-07 04:08:01 +03:00
struct leaf * l ;
struct extent_item * item ;
init_path ( & path ) ;
key . objectid = blocknr ;
key . flags = 0 ;
key . offset = 1 ;
ret = search_slot ( root - > extent_root , & key , & path , 0 , 0 ) ;
if ( ret ! = 0 )
BUG ( ) ;
l = & path . nodes [ 0 ] - > leaf ;
item = ( struct extent_item * ) ( l - > data +
2007-03-13 03:12:07 +03:00
btrfs_item_offset ( l - > items +
path . slots [ 0 ] ) ) ;
2007-03-07 04:08:01 +03:00
* refs = item - > refs ;
release_path ( root - > extent_root , & path ) ;
return 0 ;
}
2007-03-03 00:08:05 +03:00
int btrfs_inc_ref ( struct ctree_root * root , struct tree_buffer * buf )
{
u64 blocknr ;
int i ;
2007-03-07 04:08:01 +03:00
if ( root = = root - > extent_root )
return 0 ;
2007-03-12 19:01:18 +03:00
if ( btrfs_is_leaf ( & buf - > node ) )
2007-03-07 04:08:01 +03:00
return 0 ;
2007-03-12 19:01:18 +03:00
for ( i = 0 ; i < btrfs_header_nritems ( & buf - > node . header ) ; i + + ) {
2007-03-03 00:08:05 +03:00
blocknr = buf - > node . blockptrs [ i ] ;
inc_block_ref ( root , blocknr ) ;
}
return 0 ;
}
2007-03-07 04:08:01 +03:00
int btrfs_finish_extent_commit ( struct ctree_root * root )
{
struct ctree_root * extent_root = root - > extent_root ;
unsigned long gang [ 8 ] ;
int ret ;
int i ;
while ( 1 ) {
ret = radix_tree_gang_lookup ( & extent_root - > pinned_radix ,
( void * * ) gang , 0 ,
ARRAY_SIZE ( gang ) ) ;
if ( ! ret )
break ;
2007-03-08 00:15:30 +03:00
for ( i = 0 ; i < ret ; i + + ) {
2007-03-07 04:08:01 +03:00
radix_tree_delete ( & extent_root - > pinned_radix , gang [ i ] ) ;
2007-03-08 00:15:30 +03:00
}
2007-03-07 04:08:01 +03:00
}
2007-03-08 00:15:30 +03:00
extent_root - > last_insert . objectid = 0 ;
extent_root - > last_insert . offset = 0 ;
2007-03-07 04:08:01 +03:00
return 0 ;
}
2007-03-07 19:50:24 +03:00
static int finish_current_insert ( struct ctree_root * extent_root )
{
2007-03-12 23:22:34 +03:00
struct btrfs_key ins ;
2007-03-07 19:50:24 +03:00
struct extent_item extent_item ;
int i ;
int ret ;
extent_item . refs = 1 ;
2007-03-12 19:01:18 +03:00
extent_item . owner =
btrfs_header_parentid ( & extent_root - > node - > node . header ) ;
2007-03-07 19:50:24 +03:00
ins . offset = 1 ;
ins . flags = 0 ;
for ( i = 0 ; i < extent_root - > current_insert . flags ; i + + ) {
ins . objectid = extent_root - > current_insert . objectid + i ;
ret = insert_item ( extent_root , & ins , & extent_item ,
sizeof ( extent_item ) ) ;
BUG_ON ( ret ) ;
}
extent_root - > current_insert . offset = 0 ;
return 0 ;
}
2007-02-26 18:40:21 +03:00
/*
2007-03-07 04:08:01 +03:00
* remove an extent from the root , returns 0 on success
2007-02-26 18:40:21 +03:00
*/
2007-03-07 04:08:01 +03:00
int __free_extent ( struct ctree_root * root , u64 blocknr , u64 num_blocks )
{
struct ctree_path path ;
2007-03-12 23:22:34 +03:00
struct btrfs_key key ;
2007-03-07 04:08:01 +03:00
struct ctree_root * extent_root = root - > extent_root ;
int ret ;
2007-03-13 03:12:07 +03:00
struct btrfs_item * item ;
2007-03-07 04:08:01 +03:00
struct extent_item * ei ;
2007-03-12 23:22:34 +03:00
struct btrfs_key ins ;
2007-03-07 19:50:24 +03:00
2007-03-07 04:08:01 +03:00
key . objectid = blocknr ;
key . flags = 0 ;
key . offset = num_blocks ;
2007-03-07 19:50:24 +03:00
find_free_extent ( root , 0 , 0 , ( u64 ) - 1 , & ins ) ;
2007-03-07 04:08:01 +03:00
init_path ( & path ) ;
ret = search_slot ( extent_root , & key , & path , - 1 , 1 ) ;
if ( ret ) {
printf ( " failed to find %Lu \n " , key . objectid ) ;
print_tree ( extent_root , extent_root - > node ) ;
printf ( " failed to find %Lu \n " , key . objectid ) ;
BUG ( ) ;
}
item = path . nodes [ 0 ] - > leaf . items + path . slots [ 0 ] ;
2007-03-13 03:12:07 +03:00
ei = ( struct extent_item * ) ( path . nodes [ 0 ] - > leaf . data +
btrfs_item_offset ( item ) ) ;
2007-03-07 04:08:01 +03:00
BUG_ON ( ei - > refs = = 0 ) ;
ei - > refs - - ;
if ( ei - > refs = = 0 ) {
if ( root = = extent_root ) {
int err ;
radix_tree_preload ( GFP_KERNEL ) ;
err = radix_tree_insert ( & extent_root - > pinned_radix ,
blocknr , ( void * ) blocknr ) ;
BUG_ON ( err ) ;
radix_tree_preload_end ( ) ;
}
ret = del_item ( extent_root , & path ) ;
2007-03-08 00:15:30 +03:00
if ( root ! = extent_root & &
extent_root - > last_insert . objectid < blocknr )
extent_root - > last_insert . objectid = blocknr ;
2007-03-07 04:08:01 +03:00
if ( ret )
BUG ( ) ;
}
release_path ( extent_root , & path ) ;
2007-03-07 19:50:24 +03:00
finish_current_insert ( extent_root ) ;
2007-03-07 04:08:01 +03:00
return ret ;
}
/*
* find all the blocks marked as pending in the radix tree and remove
* them from the extent map
*/
static int del_pending_extents ( struct ctree_root * extent_root )
{
int ret ;
struct tree_buffer * gang [ 4 ] ;
int i ;
while ( 1 ) {
ret = radix_tree_gang_lookup_tag ( & extent_root - > cache_radix ,
( void * * ) gang , 0 ,
ARRAY_SIZE ( gang ) ,
CTREE_EXTENT_PENDING_DEL ) ;
if ( ! ret )
break ;
for ( i = 0 ; i < ret ; i + + ) {
ret = __free_extent ( extent_root , gang [ i ] - > blocknr , 1 ) ;
2007-02-26 18:40:21 +03:00
radix_tree_tag_clear ( & extent_root - > cache_radix ,
gang [ i ] - > blocknr ,
2007-03-07 04:08:01 +03:00
CTREE_EXTENT_PENDING_DEL ) ;
2007-02-26 18:40:21 +03:00
tree_block_release ( extent_root , gang [ i ] ) ;
}
}
return 0 ;
}
2007-03-07 04:08:01 +03:00
static int run_pending ( struct ctree_root * extent_root )
{
while ( radix_tree_tagged ( & extent_root - > cache_radix ,
2007-03-07 19:50:24 +03:00
CTREE_EXTENT_PENDING_DEL ) )
2007-03-07 04:08:01 +03:00
del_pending_extents ( extent_root ) ;
return 0 ;
}
2007-02-26 18:40:21 +03:00
/*
* remove an extent from the root , returns 0 on success
*/
int free_extent ( struct ctree_root * root , u64 blocknr , u64 num_blocks )
{
2007-03-12 23:22:34 +03:00
struct btrfs_key key ;
2007-02-26 18:40:21 +03:00
struct ctree_root * extent_root = root - > extent_root ;
struct tree_buffer * t ;
int pending_ret ;
int ret ;
2007-03-07 04:08:01 +03:00
2007-02-26 18:40:21 +03:00
if ( root = = extent_root ) {
2007-03-07 04:08:01 +03:00
t = find_tree_block ( root , blocknr ) ;
2007-03-07 19:50:24 +03:00
radix_tree_tag_set ( & root - > cache_radix , blocknr ,
2007-03-07 04:08:01 +03:00
CTREE_EXTENT_PENDING_DEL ) ;
2007-02-26 18:40:21 +03:00
return 0 ;
}
2007-03-07 04:08:01 +03:00
key . objectid = blocknr ;
key . flags = 0 ;
key . offset = num_blocks ;
ret = __free_extent ( root , blocknr , num_blocks ) ;
pending_ret = run_pending ( root - > extent_root ) ;
2007-02-26 18:40:21 +03:00
return ret ? ret : pending_ret ;
}
/*
* walks the btree of allocated extents and find a hole of a given size .
* The key ins is changed to record the hole :
* ins - > objectid = = block start
* ins - > flags = 0
* ins - > offset = = number of blocks
* Any available blocks before search_start are skipped .
*/
2007-03-01 00:46:22 +03:00
static int find_free_extent ( struct ctree_root * orig_root , u64 num_blocks ,
2007-03-12 23:22:34 +03:00
u64 search_start , u64 search_end ,
struct btrfs_key * ins )
2007-02-26 18:40:21 +03:00
{
struct ctree_path path ;
2007-03-12 23:22:34 +03:00
struct btrfs_key key ;
2007-02-26 18:40:21 +03:00
int ret ;
u64 hole_size = 0 ;
int slot = 0 ;
u64 last_block ;
2007-03-07 19:50:24 +03:00
u64 test_block ;
2007-02-26 18:40:21 +03:00
int start_found ;
struct leaf * l ;
struct ctree_root * root = orig_root - > extent_root ;
2007-03-08 00:15:30 +03:00
int total_needed = num_blocks ;
2007-02-26 18:40:21 +03:00
2007-03-12 19:01:18 +03:00
total_needed + = ( btrfs_header_level ( & root - > node - > node . header ) + 1 ) * 3 ;
2007-03-08 00:15:30 +03:00
if ( root - > last_insert . objectid > search_start )
search_start = root - > last_insert . objectid ;
2007-02-26 18:40:21 +03:00
check_failed :
init_path ( & path ) ;
ins - > objectid = search_start ;
ins - > offset = 0 ;
ins - > flags = 0 ;
start_found = 0 ;
2007-03-03 00:08:05 +03:00
ret = search_slot ( root , ins , & path , 0 , 0 ) ;
2007-03-01 00:46:22 +03:00
if ( ret < 0 )
goto error ;
2007-03-01 00:35:06 +03:00
2007-03-08 00:15:30 +03:00
if ( path . slots [ 0 ] > 0 )
path . slots [ 0 ] - - ;
2007-02-26 18:40:21 +03:00
while ( 1 ) {
l = & path . nodes [ 0 ] - > leaf ;
slot = path . slots [ 0 ] ;
2007-03-12 19:01:18 +03:00
if ( slot > = btrfs_header_nritems ( & l - > header ) ) {
2007-02-26 18:40:21 +03:00
ret = next_leaf ( root , & path ) ;
if ( ret = = 0 )
continue ;
2007-03-01 00:46:22 +03:00
if ( ret < 0 )
goto error ;
2007-02-26 18:40:21 +03:00
if ( ! start_found ) {
ins - > objectid = search_start ;
2007-03-07 19:50:24 +03:00
ins - > offset = ( u64 ) - 1 ;
2007-02-26 18:40:21 +03:00
start_found = 1 ;
goto check_pending ;
}
ins - > objectid = last_block > search_start ?
last_block : search_start ;
2007-03-07 19:50:24 +03:00
ins - > offset = ( u64 ) - 1 ;
2007-02-26 18:40:21 +03:00
goto check_pending ;
}
2007-03-12 23:22:34 +03:00
btrfs_disk_key_to_cpu ( & key , & l - > items [ slot ] . key ) ;
if ( key . objectid > = search_start ) {
2007-02-26 18:40:21 +03:00
if ( start_found ) {
2007-03-08 00:15:30 +03:00
if ( last_block < search_start )
last_block = search_start ;
2007-03-12 23:22:34 +03:00
hole_size = key . objectid - last_block ;
2007-03-07 19:50:24 +03:00
if ( hole_size > total_needed ) {
2007-02-26 18:40:21 +03:00
ins - > objectid = last_block ;
2007-03-07 19:50:24 +03:00
ins - > offset = hole_size ;
2007-02-26 18:40:21 +03:00
goto check_pending ;
}
2007-03-08 00:15:30 +03:00
}
2007-02-26 18:40:21 +03:00
}
2007-03-08 00:15:30 +03:00
start_found = 1 ;
2007-03-12 23:22:34 +03:00
last_block = key . objectid + key . offset ;
2007-02-26 18:40:21 +03:00
path . slots [ 0 ] + + ;
}
// FIXME -ENOSPC
check_pending :
/* we have to make sure we didn't find an extent that has already
* been allocated by the map tree or the original allocation
*/
release_path ( root , & path ) ;
BUG_ON ( ins - > objectid < search_start ) ;
2007-03-07 19:50:24 +03:00
for ( test_block = ins - > objectid ;
test_block < ins - > objectid + total_needed ; test_block + + ) {
if ( radix_tree_lookup ( & root - > pinned_radix , test_block ) ) {
search_start = test_block + 1 ;
2007-02-26 18:40:21 +03:00
goto check_failed ;
}
}
2007-03-07 19:50:24 +03:00
BUG_ON ( root - > current_insert . offset ) ;
2007-03-08 00:15:30 +03:00
root - > current_insert . offset = total_needed - num_blocks ;
2007-03-07 19:50:24 +03:00
root - > current_insert . objectid = ins - > objectid + num_blocks ;
root - > current_insert . flags = 0 ;
2007-03-08 00:15:30 +03:00
root - > last_insert . objectid = ins - > objectid ;
2007-03-07 19:50:24 +03:00
ins - > offset = num_blocks ;
2007-02-26 18:40:21 +03:00
return 0 ;
2007-03-01 00:46:22 +03:00
error :
release_path ( root , & path ) ;
return ret ;
2007-02-26 18:40:21 +03:00
}
/*
* finds a free extent and does all the dirty work required for allocation
* returns the key for the extent through ins , and a tree buffer for
* the first block of the extent through buf .
*
* returns 0 if everything worked , non - zero otherwise .
*/
int alloc_extent ( struct ctree_root * root , u64 num_blocks , u64 search_start ,
2007-03-12 23:22:34 +03:00
u64 search_end , u64 owner , struct btrfs_key * ins )
2007-02-26 18:40:21 +03:00
{
int ret ;
int pending_ret ;
2007-03-07 19:50:24 +03:00
struct ctree_root * extent_root = root - > extent_root ;
2007-02-26 18:40:21 +03:00
struct extent_item extent_item ;
2007-03-07 19:50:24 +03:00
2007-02-26 18:40:21 +03:00
extent_item . refs = 1 ;
extent_item . owner = owner ;
2007-03-07 19:50:24 +03:00
if ( root = = extent_root ) {
BUG_ON ( extent_root - > current_insert . offset = = 0 ) ;
BUG_ON ( num_blocks ! = 1 ) ;
BUG_ON ( extent_root - > current_insert . flags = =
extent_root - > current_insert . offset ) ;
ins - > offset = 1 ;
ins - > objectid = extent_root - > current_insert . objectid +
extent_root - > current_insert . flags + + ;
2007-02-26 18:40:21 +03:00
return 0 ;
}
2007-03-07 19:50:24 +03:00
ret = find_free_extent ( root , num_blocks , search_start ,
search_end , ins ) ;
if ( ret )
return ret ;
2007-02-26 18:40:21 +03:00
2007-03-07 19:50:24 +03:00
ret = insert_item ( extent_root , ins , & extent_item ,
sizeof ( extent_item ) ) ;
finish_current_insert ( extent_root ) ;
pending_ret = run_pending ( extent_root ) ;
if ( ret )
return ret ;
if ( pending_ret )
return pending_ret ;
return 0 ;
2007-02-26 18:40:21 +03:00
}
/*
* helper function to allocate a block for a given tree
* returns the tree buffer or NULL .
*/
struct tree_buffer * alloc_free_block ( struct ctree_root * root )
{
2007-03-12 23:22:34 +03:00
struct btrfs_key ins ;
2007-02-26 18:40:21 +03:00
int ret ;
2007-03-07 19:50:24 +03:00
struct tree_buffer * buf ;
2007-02-26 18:40:21 +03:00
ret = alloc_extent ( root , 1 , 0 , ( unsigned long ) - 1 ,
2007-03-12 19:01:18 +03:00
btrfs_header_parentid ( & root - > node - > node . header ) ,
2007-03-07 19:50:24 +03:00
& ins ) ;
2007-02-26 18:40:21 +03:00
if ( ret ) {
BUG ( ) ;
return NULL ;
}
2007-03-07 19:50:24 +03:00
buf = find_tree_block ( root , ins . objectid ) ;
dirty_tree_block ( root , buf ) ;
2007-02-26 18:40:21 +03:00
return buf ;
}
2007-03-07 04:08:01 +03:00
2007-03-10 14:35:47 +03:00
int walk_down_tree ( struct ctree_root * root , struct ctree_path * path , int * level )
{
struct tree_buffer * next ;
struct tree_buffer * cur ;
u64 blocknr ;
int ret ;
u32 refs ;
ret = lookup_block_ref ( root , path - > nodes [ * level ] - > blocknr , & refs ) ;
BUG_ON ( ret ) ;
if ( refs > 1 )
goto out ;
while ( * level > 0 ) {
cur = path - > nodes [ * level ] ;
2007-03-12 19:01:18 +03:00
if ( path - > slots [ * level ] > =
btrfs_header_nritems ( & cur - > node . header ) )
2007-03-10 14:35:47 +03:00
break ;
blocknr = cur - > node . blockptrs [ path - > slots [ * level ] ] ;
ret = lookup_block_ref ( root , blocknr , & refs ) ;
if ( refs ! = 1 | | * level = = 1 ) {
path - > slots [ * level ] + + ;
ret = free_extent ( root , blocknr , 1 ) ;
BUG_ON ( ret ) ;
continue ;
}
BUG_ON ( ret ) ;
next = read_tree_block ( root , blocknr ) ;
2007-03-12 16:03:27 +03:00
if ( path - > nodes [ * level - 1 ] )
2007-03-10 14:35:47 +03:00
tree_block_release ( root , path - > nodes [ * level - 1 ] ) ;
path - > nodes [ * level - 1 ] = next ;
2007-03-12 19:01:18 +03:00
* level = btrfs_header_level ( & next - > node . header ) ;
2007-03-10 14:35:47 +03:00
path - > slots [ * level ] = 0 ;
}
out :
ret = free_extent ( root , path - > nodes [ * level ] - > blocknr , 1 ) ;
2007-03-12 16:03:27 +03:00
tree_block_release ( root , path - > nodes [ * level ] ) ;
2007-03-10 14:35:47 +03:00
path - > nodes [ * level ] = NULL ;
* level + = 1 ;
BUG_ON ( ret ) ;
return 0 ;
}
int walk_up_tree ( struct ctree_root * root , struct ctree_path * path , int * level )
{
int i ;
int slot ;
int ret ;
for ( i = * level ; i < MAX_LEVEL - 1 & & path - > nodes [ i ] ; i + + ) {
slot = path - > slots [ i ] ;
2007-03-12 19:01:18 +03:00
if ( slot <
btrfs_header_nritems ( & path - > nodes [ i ] - > node . header ) - 1 ) {
2007-03-10 14:35:47 +03:00
path - > slots [ i ] + + ;
* level = i ;
return 0 ;
} else {
ret = free_extent ( root ,
path - > nodes [ * level ] - > blocknr , 1 ) ;
2007-03-12 16:03:27 +03:00
tree_block_release ( root , path - > nodes [ * level ] ) ;
path - > nodes [ * level ] = NULL ;
2007-03-10 14:35:47 +03:00
* level = i + 1 ;
BUG_ON ( ret ) ;
}
}
return 1 ;
}
int btrfs_drop_snapshot ( struct ctree_root * root , struct tree_buffer * snap )
{
int ret ;
int level ;
struct ctree_path path ;
int i ;
int orig_level ;
init_path ( & path ) ;
2007-03-12 19:01:18 +03:00
level = btrfs_header_level ( & snap - > node . header ) ;
2007-03-10 14:35:47 +03:00
orig_level = level ;
path . nodes [ level ] = snap ;
path . slots [ level ] = 0 ;
while ( 1 ) {
ret = walk_down_tree ( root , & path , & level ) ;
if ( ret > 0 )
break ;
ret = walk_up_tree ( root , & path , & level ) ;
if ( ret > 0 )
break ;
}
2007-03-12 16:03:27 +03:00
for ( i = 0 ; i < = orig_level ; i + + ) {
if ( path . nodes [ i ] ) {
2007-03-10 14:35:47 +03:00
tree_block_release ( root , path . nodes [ i ] ) ;
2007-03-12 16:03:27 +03:00
}
2007-03-10 14:35:47 +03:00
}
return 0 ;
}