2007-06-12 17:07:21 +04:00
/*
* Copyright ( C ) 2007 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 .
*/
2007-03-16 02:03:33 +03:00
# include "ctree.h"
# include "disk-io.h"
2012-08-08 22:32:27 +04:00
# include "hash.h"
2007-03-16 23:20:31 +03:00
# include "transaction.h"
2010-08-06 21:21:20 +04:00
# include "print-tree.h"
2007-03-16 02:03:33 +03:00
2008-12-02 17:54:17 +03:00
static int find_name_in_backref ( struct btrfs_path * path , const char * name ,
2007-12-12 22:38:19 +03:00
int name_len , struct btrfs_inode_ref * * ref_ret )
{
struct extent_buffer * leaf ;
struct btrfs_inode_ref * ref ;
unsigned long ptr ;
unsigned long name_ptr ;
u32 item_size ;
u32 cur_offset = 0 ;
int len ;
leaf = path - > nodes [ 0 ] ;
item_size = btrfs_item_size_nr ( leaf , path - > slots [ 0 ] ) ;
ptr = btrfs_item_ptr_offset ( leaf , path - > slots [ 0 ] ) ;
while ( cur_offset < item_size ) {
ref = ( struct btrfs_inode_ref * ) ( ptr + cur_offset ) ;
len = btrfs_inode_ref_name_len ( leaf , ref ) ;
name_ptr = ( unsigned long ) ( ref + 1 ) ;
cur_offset + = len + sizeof ( * ref ) ;
if ( len ! = name_len )
continue ;
if ( memcmp_extent_buffer ( leaf , name , name_ptr , name_len ) = = 0 ) {
* ref_ret = ref ;
return 1 ;
}
}
return 0 ;
}
2012-08-08 22:32:27 +04:00
int btrfs_find_name_in_ext_backref ( struct btrfs_path * path , u64 ref_objectid ,
const char * name , int name_len ,
struct btrfs_inode_extref * * extref_ret )
{
struct extent_buffer * leaf ;
struct btrfs_inode_extref * extref ;
unsigned long ptr ;
unsigned long name_ptr ;
u32 item_size ;
u32 cur_offset = 0 ;
int ref_name_len ;
leaf = path - > nodes [ 0 ] ;
item_size = btrfs_item_size_nr ( leaf , path - > slots [ 0 ] ) ;
ptr = btrfs_item_ptr_offset ( leaf , path - > slots [ 0 ] ) ;
/*
* Search all extended backrefs in this item . We ' re only
* looking through any collisions so most of the time this is
* just going to compare against one buffer . If all is well ,
* we ' ll return success and the inode ref object .
*/
while ( cur_offset < item_size ) {
extref = ( struct btrfs_inode_extref * ) ( ptr + cur_offset ) ;
name_ptr = ( unsigned long ) ( & extref - > name ) ;
ref_name_len = btrfs_inode_extref_name_len ( leaf , extref ) ;
if ( ref_name_len = = name_len & &
btrfs_inode_extref_parent ( leaf , extref ) = = ref_objectid & &
( memcmp_extent_buffer ( leaf , name , name_ptr , name_len ) = = 0 ) ) {
if ( extref_ret )
* extref_ret = extref ;
return 1 ;
}
cur_offset + = ref_name_len + sizeof ( * extref ) ;
}
return 0 ;
}
static struct btrfs_inode_ref *
2010-05-16 18:48:46 +04:00
btrfs_lookup_inode_ref ( struct btrfs_trans_handle * trans ,
2012-08-08 22:32:27 +04:00
struct btrfs_root * root ,
struct btrfs_path * path ,
const char * name , int name_len ,
u64 inode_objectid , u64 ref_objectid , int ins_len ,
int cow )
2010-05-16 18:48:46 +04:00
{
2012-08-08 22:32:27 +04:00
int ret ;
2010-05-16 18:48:46 +04:00
struct btrfs_key key ;
struct btrfs_inode_ref * ref ;
key . objectid = inode_objectid ;
key . type = BTRFS_INODE_REF_KEY ;
key . offset = ref_objectid ;
ret = btrfs_search_slot ( trans , root , & key , path , ins_len , cow ) ;
if ( ret < 0 )
return ERR_PTR ( ret ) ;
if ( ret > 0 )
return NULL ;
if ( ! find_name_in_backref ( path , name , name_len , & ref ) )
return NULL ;
return ref ;
}
2012-08-08 22:32:27 +04:00
/* Returns NULL if no extref found */
struct btrfs_inode_extref *
btrfs_lookup_inode_extref ( struct btrfs_trans_handle * trans ,
struct btrfs_root * root ,
struct btrfs_path * path ,
const char * name , int name_len ,
u64 inode_objectid , u64 ref_objectid , int ins_len ,
int cow )
{
int ret ;
struct btrfs_key key ;
struct btrfs_inode_extref * extref ;
key . objectid = inode_objectid ;
key . type = BTRFS_INODE_EXTREF_KEY ;
key . offset = btrfs_extref_hash ( ref_objectid , name , name_len ) ;
ret = btrfs_search_slot ( trans , root , & key , path , ins_len , cow ) ;
if ( ret < 0 )
return ERR_PTR ( ret ) ;
if ( ret > 0 )
return NULL ;
if ( ! btrfs_find_name_in_ext_backref ( path , ref_objectid , name , name_len , & extref ) )
return NULL ;
return extref ;
}
int btrfs_get_inode_ref_index ( struct btrfs_trans_handle * trans ,
struct btrfs_root * root ,
struct btrfs_path * path ,
const char * name , int name_len ,
u64 inode_objectid , u64 ref_objectid , int mod ,
u64 * ret_index )
{
struct btrfs_inode_ref * ref ;
struct btrfs_inode_extref * extref ;
int ins_len = mod < 0 ? - 1 : 0 ;
int cow = mod ! = 0 ;
ref = btrfs_lookup_inode_ref ( trans , root , path , name , name_len ,
inode_objectid , ref_objectid , ins_len ,
cow ) ;
if ( IS_ERR ( ref ) )
return PTR_ERR ( ref ) ;
if ( ref ! = NULL ) {
* ret_index = btrfs_inode_ref_index ( path - > nodes [ 0 ] , ref ) ;
return 0 ;
}
btrfs_release_path ( path ) ;
extref = btrfs_lookup_inode_extref ( trans , root , path , name ,
name_len , inode_objectid ,
ref_objectid , ins_len , cow ) ;
if ( IS_ERR ( extref ) )
return PTR_ERR ( extref ) ;
if ( extref ) {
* ret_index = btrfs_inode_extref_index ( path - > nodes [ 0 ] , extref ) ;
return 0 ;
}
return - ENOENT ;
}
int btrfs_del_inode_extref ( struct btrfs_trans_handle * trans ,
2007-12-12 22:38:19 +03:00
struct btrfs_root * root ,
const char * name , int name_len ,
2008-07-24 20:12:38 +04:00
u64 inode_objectid , u64 ref_objectid , u64 * index )
2012-08-08 22:32:27 +04:00
{
struct btrfs_path * path ;
struct btrfs_key key ;
struct btrfs_inode_extref * extref ;
struct extent_buffer * leaf ;
int ret ;
int del_len = name_len + sizeof ( * extref ) ;
unsigned long ptr ;
unsigned long item_start ;
u32 item_size ;
key . objectid = inode_objectid ;
btrfs_set_key_type ( & key , BTRFS_INODE_EXTREF_KEY ) ;
key . offset = btrfs_extref_hash ( ref_objectid , name , name_len ) ;
path = btrfs_alloc_path ( ) ;
if ( ! path )
return - ENOMEM ;
path - > leave_spinning = 1 ;
ret = btrfs_search_slot ( trans , root , & key , path , - 1 , 1 ) ;
if ( ret > 0 )
ret = - ENOENT ;
if ( ret < 0 )
goto out ;
/*
* Sanity check - did we find the right item for this name ?
* This should always succeed so error here will make the FS
* readonly .
*/
if ( ! btrfs_find_name_in_ext_backref ( path , ref_objectid ,
name , name_len , & extref ) ) {
btrfs_std_error ( root - > fs_info , - ENOENT ) ;
ret = - EROFS ;
goto out ;
}
leaf = path - > nodes [ 0 ] ;
item_size = btrfs_item_size_nr ( leaf , path - > slots [ 0 ] ) ;
if ( index )
* index = btrfs_inode_extref_index ( leaf , extref ) ;
if ( del_len = = item_size ) {
/*
* Common case only one ref in the item , remove the
* whole item .
*/
ret = btrfs_del_item ( trans , root , path ) ;
goto out ;
}
ptr = ( unsigned long ) extref ;
item_start = btrfs_item_ptr_offset ( leaf , path - > slots [ 0 ] ) ;
memmove_extent_buffer ( leaf , ptr , ptr + del_len ,
item_size - ( ptr + del_len - item_start ) ) ;
btrfs_truncate_item ( trans , root , path , item_size - del_len , 1 ) ;
out :
btrfs_free_path ( path ) ;
return ret ;
}
int btrfs_del_inode_ref ( struct btrfs_trans_handle * trans ,
struct btrfs_root * root ,
const char * name , int name_len ,
u64 inode_objectid , u64 ref_objectid , u64 * index )
2007-12-12 22:38:19 +03:00
{
struct btrfs_path * path ;
struct btrfs_key key ;
struct btrfs_inode_ref * ref ;
struct extent_buffer * leaf ;
unsigned long ptr ;
unsigned long item_start ;
u32 item_size ;
u32 sub_item_len ;
int ret ;
2012-08-08 22:32:27 +04:00
int search_ext_refs = 0 ;
2007-12-12 22:38:19 +03:00
int del_len = name_len + sizeof ( * ref ) ;
key . objectid = inode_objectid ;
key . offset = ref_objectid ;
btrfs_set_key_type ( & key , BTRFS_INODE_REF_KEY ) ;
path = btrfs_alloc_path ( ) ;
if ( ! path )
return - ENOMEM ;
2009-03-13 18:00:37 +03:00
path - > leave_spinning = 1 ;
2007-12-12 22:38:19 +03:00
ret = btrfs_search_slot ( trans , root , & key , path , - 1 , 1 ) ;
if ( ret > 0 ) {
ret = - ENOENT ;
2012-08-08 22:32:27 +04:00
search_ext_refs = 1 ;
2007-12-12 22:38:19 +03:00
goto out ;
} else if ( ret < 0 ) {
goto out ;
}
if ( ! find_name_in_backref ( path , name , name_len , & ref ) ) {
ret = - ENOENT ;
2012-08-08 22:32:27 +04:00
search_ext_refs = 1 ;
2007-12-12 22:38:19 +03:00
goto out ;
}
leaf = path - > nodes [ 0 ] ;
item_size = btrfs_item_size_nr ( leaf , path - > slots [ 0 ] ) ;
2008-07-24 20:12:38 +04:00
if ( index )
* index = btrfs_inode_ref_index ( leaf , ref ) ;
2007-12-12 22:38:19 +03:00
if ( del_len = = item_size ) {
ret = btrfs_del_item ( trans , root , path ) ;
goto out ;
}
ptr = ( unsigned long ) ref ;
sub_item_len = name_len + sizeof ( * ref ) ;
item_start = btrfs_item_ptr_offset ( leaf , path - > slots [ 0 ] ) ;
memmove_extent_buffer ( leaf , ptr , ptr + sub_item_len ,
item_size - ( ptr + sub_item_len - item_start ) ) ;
2012-08-08 22:32:27 +04:00
btrfs_truncate_item ( trans , root , path , item_size - sub_item_len , 1 ) ;
out :
btrfs_free_path ( path ) ;
if ( search_ext_refs ) {
/*
* No refs were found , or we could not find the
* name in our ref array . Find and remove the extended
* inode ref then .
*/
return btrfs_del_inode_extref ( trans , root , name , name_len ,
inode_objectid , ref_objectid , index ) ;
}
return ret ;
}
/*
* btrfs_insert_inode_extref ( ) - Inserts an extended inode ref into a tree .
*
* The caller must have checked against BTRFS_LINK_MAX already .
*/
static int btrfs_insert_inode_extref ( struct btrfs_trans_handle * trans ,
struct btrfs_root * root ,
const char * name , int name_len ,
u64 inode_objectid , u64 ref_objectid , u64 index )
{
struct btrfs_inode_extref * extref ;
int ret ;
int ins_len = name_len + sizeof ( * extref ) ;
unsigned long ptr ;
struct btrfs_path * path ;
struct btrfs_key key ;
struct extent_buffer * leaf ;
struct btrfs_item * item ;
key . objectid = inode_objectid ;
key . type = BTRFS_INODE_EXTREF_KEY ;
key . offset = btrfs_extref_hash ( ref_objectid , name , name_len ) ;
path = btrfs_alloc_path ( ) ;
if ( ! path )
return - ENOMEM ;
path - > leave_spinning = 1 ;
ret = btrfs_insert_empty_item ( trans , root , path , & key ,
ins_len ) ;
if ( ret = = - EEXIST ) {
if ( btrfs_find_name_in_ext_backref ( path , ref_objectid ,
name , name_len , NULL ) )
goto out ;
btrfs_extend_item ( trans , root , path , ins_len ) ;
ret = 0 ;
}
if ( ret < 0 )
goto out ;
leaf = path - > nodes [ 0 ] ;
item = btrfs_item_nr ( leaf , path - > slots [ 0 ] ) ;
ptr = ( unsigned long ) btrfs_item_ptr ( leaf , path - > slots [ 0 ] , char ) ;
ptr + = btrfs_item_size ( leaf , item ) - ins_len ;
extref = ( struct btrfs_inode_extref * ) ptr ;
btrfs_set_inode_extref_name_len ( path - > nodes [ 0 ] , extref , name_len ) ;
btrfs_set_inode_extref_index ( path - > nodes [ 0 ] , extref , index ) ;
btrfs_set_inode_extref_parent ( path - > nodes [ 0 ] , extref , ref_objectid ) ;
ptr = ( unsigned long ) & extref - > name ;
write_extent_buffer ( path - > nodes [ 0 ] , name , ptr , name_len ) ;
btrfs_mark_buffer_dirty ( path - > nodes [ 0 ] ) ;
2007-12-12 22:38:19 +03:00
out :
btrfs_free_path ( path ) ;
return ret ;
}
2012-03-12 19:03:00 +04:00
/* Will return 0, -ENOMEM, -EMLINK, or -EEXIST or anything from the CoW path */
2007-12-12 22:38:19 +03:00
int btrfs_insert_inode_ref ( struct btrfs_trans_handle * trans ,
struct btrfs_root * root ,
const char * name , int name_len ,
2008-07-24 20:12:38 +04:00
u64 inode_objectid , u64 ref_objectid , u64 index )
2007-12-12 22:38:19 +03:00
{
struct btrfs_path * path ;
struct btrfs_key key ;
struct btrfs_inode_ref * ref ;
unsigned long ptr ;
int ret ;
int ins_len = name_len + sizeof ( * ref ) ;
key . objectid = inode_objectid ;
key . offset = ref_objectid ;
btrfs_set_key_type ( & key , BTRFS_INODE_REF_KEY ) ;
path = btrfs_alloc_path ( ) ;
if ( ! path )
return - ENOMEM ;
2009-03-13 18:00:37 +03:00
path - > leave_spinning = 1 ;
2007-12-12 22:38:19 +03:00
ret = btrfs_insert_empty_item ( trans , root , path , & key ,
ins_len ) ;
if ( ret = = - EEXIST ) {
u32 old_size ;
if ( find_name_in_backref ( path , name , name_len , & ref ) )
goto out ;
old_size = btrfs_item_size_nr ( path - > nodes [ 0 ] , path - > slots [ 0 ] ) ;
2012-03-01 17:56:26 +04:00
btrfs_extend_item ( trans , root , path , ins_len ) ;
2007-12-12 22:38:19 +03:00
ref = btrfs_item_ptr ( path - > nodes [ 0 ] , path - > slots [ 0 ] ,
struct btrfs_inode_ref ) ;
ref = ( struct btrfs_inode_ref * ) ( ( unsigned long ) ref + old_size ) ;
btrfs_set_inode_ref_name_len ( path - > nodes [ 0 ] , ref , name_len ) ;
2008-07-24 20:12:38 +04:00
btrfs_set_inode_ref_index ( path - > nodes [ 0 ] , ref , index ) ;
2007-12-12 22:38:19 +03:00
ptr = ( unsigned long ) ( ref + 1 ) ;
ret = 0 ;
} else if ( ret < 0 ) {
2009-09-24 17:17:31 +04:00
if ( ret = = - EOVERFLOW )
ret = - EMLINK ;
2007-12-12 22:38:19 +03:00
goto out ;
} else {
ref = btrfs_item_ptr ( path - > nodes [ 0 ] , path - > slots [ 0 ] ,
struct btrfs_inode_ref ) ;
btrfs_set_inode_ref_name_len ( path - > nodes [ 0 ] , ref , name_len ) ;
2008-07-24 20:12:38 +04:00
btrfs_set_inode_ref_index ( path - > nodes [ 0 ] , ref , index ) ;
2007-12-12 22:38:19 +03:00
ptr = ( unsigned long ) ( ref + 1 ) ;
}
write_extent_buffer ( path - > nodes [ 0 ] , name , ptr , name_len ) ;
btrfs_mark_buffer_dirty ( path - > nodes [ 0 ] ) ;
out :
btrfs_free_path ( path ) ;
2012-08-08 22:32:27 +04:00
if ( ret = = - EMLINK ) {
struct btrfs_super_block * disk_super = root - > fs_info - > super_copy ;
/* We ran out of space in the ref array. Need to
* add an extended ref . */
if ( btrfs_super_incompat_flags ( disk_super )
& BTRFS_FEATURE_INCOMPAT_EXTENDED_IREF )
ret = btrfs_insert_inode_extref ( trans , root , name ,
name_len ,
inode_objectid ,
ref_objectid , index ) ;
}
2007-12-12 22:38:19 +03:00
return ret ;
}
2007-10-16 00:14:19 +04:00
int btrfs_insert_empty_inode ( struct btrfs_trans_handle * trans ,
struct btrfs_root * root ,
struct btrfs_path * path , u64 objectid )
2007-03-16 02:03:33 +03:00
{
struct btrfs_key key ;
int ret ;
key . objectid = objectid ;
btrfs_set_key_type ( & key , BTRFS_INODE_ITEM_KEY ) ;
key . offset = 0 ;
2007-10-16 00:14:19 +04:00
ret = btrfs_insert_empty_item ( trans , root , path , & key ,
sizeof ( struct btrfs_inode_item ) ) ;
2007-03-16 02:03:33 +03:00
return ret ;
}
2007-03-16 23:20:31 +03:00
int btrfs_lookup_inode ( struct btrfs_trans_handle * trans , struct btrfs_root
2007-04-06 23:37:36 +04:00
* root , struct btrfs_path * path ,
struct btrfs_key * location , int mod )
2007-03-16 02:03:33 +03:00
{
int ins_len = mod < 0 ? - 1 : 0 ;
int cow = mod ! = 0 ;
2007-04-06 23:37:36 +04:00
int ret ;
int slot ;
2007-10-16 00:14:19 +04:00
struct extent_buffer * leaf ;
2007-04-06 23:37:36 +04:00
struct btrfs_key found_key ;
2007-03-16 02:03:33 +03:00
2007-04-06 23:37:36 +04:00
ret = btrfs_search_slot ( trans , root , location , path , ins_len , cow ) ;
if ( ret > 0 & & btrfs_key_type ( location ) = = BTRFS_ROOT_ITEM_KEY & &
location - > offset = = ( u64 ) - 1 & & path - > slots [ 0 ] ! = 0 ) {
slot = path - > slots [ 0 ] - 1 ;
2007-10-16 00:14:19 +04:00
leaf = path - > nodes [ 0 ] ;
btrfs_item_key_to_cpu ( leaf , & found_key , slot ) ;
2007-04-06 23:37:36 +04:00
if ( found_key . objectid = = location - > objectid & &
btrfs_key_type ( & found_key ) = = btrfs_key_type ( location ) ) {
path - > slots [ 0 ] - - ;
return 0 ;
}
}
return ret ;
2007-03-16 02:03:33 +03:00
}