2008-07-28 23:32:19 +04:00
/*
* Copyright ( C ) 2008 Oracle . All rights reserved .
*
* This program is free software ; you can redistribute it and / or
* modify it under the terms of the GNU General Public
* License v2 as published by the Free Software Foundation .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the GNU
* General Public License for more details .
*
* You should have received a copy of the GNU General Public
* License along with this program ; if not , write to the
* Free Software Foundation , Inc . , 59 Temple Place - Suite 330 ,
* Boston , MA 021110 - 1307 , USA .
*/
# include <linux/sched.h>
2009-02-04 17:27:02 +03:00
# include <linux/sort.h>
2008-07-28 23:32:19 +04:00
# include "ctree.h"
# include "ref-cache.h"
# include "transaction.h"
2008-09-29 23:18:18 +04:00
/*
* leaf refs are used to cache the information about which extents
* a given leaf has references on . This allows us to process that leaf
* in btrfs_drop_snapshot without needing to read it back from disk .
*/
/*
* kmalloc a leaf reference struct and update the counters for the
* total ref cache size
*/
2008-07-31 00:29:20 +04:00
struct btrfs_leaf_ref * btrfs_alloc_leaf_ref ( struct btrfs_root * root ,
int nr_extents )
2008-07-28 23:32:19 +04:00
{
struct btrfs_leaf_ref * ref ;
2008-07-31 00:29:20 +04:00
size_t size = btrfs_leaf_ref_size ( nr_extents ) ;
2008-07-28 23:32:19 +04:00
2008-07-31 00:29:20 +04:00
ref = kmalloc ( size , GFP_NOFS ) ;
2008-07-28 23:32:19 +04:00
if ( ref ) {
2008-07-31 00:29:20 +04:00
spin_lock ( & root - > fs_info - > ref_cache_lock ) ;
root - > fs_info - > total_ref_cache_size + = size ;
spin_unlock ( & root - > fs_info - > ref_cache_lock ) ;
2008-07-28 23:32:19 +04:00
memset ( ref , 0 , sizeof ( * ref ) ) ;
atomic_set ( & ref - > usage , 1 ) ;
2008-07-28 23:32:51 +04:00
INIT_LIST_HEAD ( & ref - > list ) ;
2008-07-28 23:32:19 +04:00
}
return ref ;
}
2008-09-29 23:18:18 +04:00
/*
* free a leaf reference struct and update the counters for the
* total ref cache size
*/
2008-07-31 00:29:20 +04:00
void btrfs_free_leaf_ref ( struct btrfs_root * root , struct btrfs_leaf_ref * ref )
2008-07-28 23:32:19 +04:00
{
if ( ! ref )
return ;
WARN_ON ( atomic_read ( & ref - > usage ) = = 0 ) ;
if ( atomic_dec_and_test ( & ref - > usage ) ) {
2008-07-31 00:29:20 +04:00
size_t size = btrfs_leaf_ref_size ( ref - > nritems ) ;
2008-07-28 23:32:19 +04:00
BUG_ON ( ref - > in_tree ) ;
kfree ( ref ) ;
2008-07-31 00:29:20 +04:00
spin_lock ( & root - > fs_info - > ref_cache_lock ) ;
root - > fs_info - > total_ref_cache_size - = size ;
spin_unlock ( & root - > fs_info - > ref_cache_lock ) ;
2008-07-28 23:32:19 +04:00
}
}
2008-07-28 23:32:51 +04:00
static struct rb_node * tree_insert ( struct rb_root * root , u64 bytenr ,
2008-07-28 23:32:19 +04:00
struct rb_node * node )
{
2009-01-06 05:25:51 +03:00
struct rb_node * * p = & root - > rb_node ;
struct rb_node * parent = NULL ;
2008-07-28 23:32:19 +04:00
struct btrfs_leaf_ref * entry ;
2009-01-06 05:25:51 +03:00
while ( * p ) {
2008-07-28 23:32:19 +04:00
parent = * p ;
entry = rb_entry ( parent , struct btrfs_leaf_ref , rb_node ) ;
2008-07-28 23:32:51 +04:00
if ( bytenr < entry - > bytenr )
2008-07-28 23:32:19 +04:00
p = & ( * p ) - > rb_left ;
2008-07-28 23:32:51 +04:00
else if ( bytenr > entry - > bytenr )
2008-07-28 23:32:19 +04:00
p = & ( * p ) - > rb_right ;
else
return parent ;
}
2008-07-31 00:29:20 +04:00
2008-07-28 23:32:19 +04:00
entry = rb_entry ( node , struct btrfs_leaf_ref , rb_node ) ;
rb_link_node ( node , parent , p ) ;
rb_insert_color ( node , root ) ;
return NULL ;
}
2008-07-28 23:32:51 +04:00
static struct rb_node * tree_search ( struct rb_root * root , u64 bytenr )
2008-07-28 23:32:19 +04:00
{
2009-01-06 05:25:51 +03:00
struct rb_node * n = root - > rb_node ;
2008-07-28 23:32:19 +04:00
struct btrfs_leaf_ref * entry ;
2009-01-06 05:25:51 +03:00
while ( n ) {
2008-07-28 23:32:19 +04:00
entry = rb_entry ( n , struct btrfs_leaf_ref , rb_node ) ;
WARN_ON ( ! entry - > in_tree ) ;
2008-07-28 23:32:51 +04:00
if ( bytenr < entry - > bytenr )
2008-07-28 23:32:19 +04:00
n = n - > rb_left ;
2008-07-28 23:32:51 +04:00
else if ( bytenr > entry - > bytenr )
2008-07-28 23:32:19 +04:00
n = n - > rb_right ;
else
return n ;
}
return NULL ;
}
2008-09-26 18:04:53 +04:00
int btrfs_remove_leaf_refs ( struct btrfs_root * root , u64 max_root_gen ,
int shared )
2008-07-28 23:32:19 +04:00
{
struct btrfs_leaf_ref * ref = NULL ;
struct btrfs_leaf_ref_tree * tree = root - > ref_tree ;
2008-09-26 18:04:53 +04:00
if ( shared )
tree = & root - > fs_info - > shared_ref_tree ;
2008-07-28 23:32:19 +04:00
if ( ! tree )
return 0 ;
spin_lock ( & tree - > lock ) ;
2009-01-06 05:25:51 +03:00
while ( ! list_empty ( & tree - > list ) ) {
2008-07-31 00:29:20 +04:00
ref = list_entry ( tree - > list . next , struct btrfs_leaf_ref , list ) ;
2008-09-26 18:04:53 +04:00
BUG_ON ( ref - > tree ! = tree ) ;
2008-07-31 00:29:20 +04:00
if ( ref - > root_gen > max_root_gen )
break ;
2008-09-26 18:04:53 +04:00
if ( ! xchg ( & ref - > in_tree , 0 ) ) {
cond_resched_lock ( & tree - > lock ) ;
continue ;
}
2008-07-31 00:29:20 +04:00
2008-07-28 23:32:19 +04:00
rb_erase ( & ref - > rb_node , & tree - > root ) ;
2008-07-28 23:32:51 +04:00
list_del_init ( & ref - > list ) ;
2008-07-28 23:32:19 +04:00
spin_unlock ( & tree - > lock ) ;
2008-07-31 00:29:20 +04:00
btrfs_free_leaf_ref ( root , ref ) ;
2008-07-28 23:32:19 +04:00
cond_resched ( ) ;
spin_lock ( & tree - > lock ) ;
}
spin_unlock ( & tree - > lock ) ;
return 0 ;
}
2008-09-29 23:18:18 +04:00
/*
* find the leaf ref for a given extent . This returns the ref struct with
* a usage reference incremented
*/
2008-07-28 23:32:19 +04:00
struct btrfs_leaf_ref * btrfs_lookup_leaf_ref ( struct btrfs_root * root ,
2008-07-28 23:32:51 +04:00
u64 bytenr )
2008-07-28 23:32:19 +04:00
{
struct rb_node * rb ;
struct btrfs_leaf_ref * ref = NULL ;
struct btrfs_leaf_ref_tree * tree = root - > ref_tree ;
2008-09-26 18:04:53 +04:00
again :
if ( tree ) {
spin_lock ( & tree - > lock ) ;
rb = tree_search ( & tree - > root , bytenr ) ;
if ( rb )
ref = rb_entry ( rb , struct btrfs_leaf_ref , rb_node ) ;
if ( ref )
atomic_inc ( & ref - > usage ) ;
spin_unlock ( & tree - > lock ) ;
if ( ref )
return ref ;
}
if ( tree ! = & root - > fs_info - > shared_ref_tree ) {
tree = & root - > fs_info - > shared_ref_tree ;
goto again ;
}
return NULL ;
2008-07-28 23:32:19 +04:00
}
2008-09-29 23:18:18 +04:00
/*
* add a fully filled in leaf ref struct
* remove all the refs older than a given root generation
*/
2008-09-26 18:04:53 +04:00
int btrfs_add_leaf_ref ( struct btrfs_root * root , struct btrfs_leaf_ref * ref ,
int shared )
2008-07-28 23:32:19 +04:00
{
int ret = 0 ;
struct rb_node * rb ;
struct btrfs_leaf_ref_tree * tree = root - > ref_tree ;
2008-09-26 18:04:53 +04:00
if ( shared )
tree = & root - > fs_info - > shared_ref_tree ;
2008-07-28 23:32:19 +04:00
spin_lock ( & tree - > lock ) ;
2008-07-28 23:32:51 +04:00
rb = tree_insert ( & tree - > root , ref - > bytenr , & ref - > rb_node ) ;
2008-07-28 23:32:19 +04:00
if ( rb ) {
ret = - EEXIST ;
} else {
atomic_inc ( & ref - > usage ) ;
2008-09-26 18:04:53 +04:00
ref - > tree = tree ;
ref - > in_tree = 1 ;
2008-07-28 23:32:51 +04:00
list_add_tail ( & ref - > list , & tree - > list ) ;
2008-07-28 23:32:19 +04:00
}
spin_unlock ( & tree - > lock ) ;
return ret ;
}
2008-09-29 23:18:18 +04:00
/*
* remove a single leaf ref from the tree . This drops the ref held by the tree
* only
*/
2008-07-28 23:32:19 +04:00
int btrfs_remove_leaf_ref ( struct btrfs_root * root , struct btrfs_leaf_ref * ref )
{
2008-09-26 18:04:53 +04:00
struct btrfs_leaf_ref_tree * tree ;
if ( ! xchg ( & ref - > in_tree , 0 ) )
return 0 ;
2008-07-28 23:32:19 +04:00
2008-09-26 18:04:53 +04:00
tree = ref - > tree ;
2008-07-28 23:32:19 +04:00
spin_lock ( & tree - > lock ) ;
rb_erase ( & ref - > rb_node , & tree - > root ) ;
2008-07-28 23:32:51 +04:00
list_del_init ( & ref - > list ) ;
2008-07-28 23:32:19 +04:00
spin_unlock ( & tree - > lock ) ;
2008-07-31 00:29:20 +04:00
btrfs_free_leaf_ref ( root , ref ) ;
2008-07-28 23:32:19 +04:00
return 0 ;
}