2013-02-28 05:03:01 +04:00
/*
* linux / fs / hfsplus / attributes . c
*
* Vyacheslav Dubeyko < slava @ dubeyko . com >
*
* Handling of records in attributes tree
*/
# include "hfsplus_fs.h"
# include "hfsplus_raw.h"
static struct kmem_cache * hfsplus_attr_tree_cachep ;
int hfsplus_create_attr_tree_cache ( void )
{
if ( hfsplus_attr_tree_cachep )
return - EEXIST ;
hfsplus_attr_tree_cachep =
kmem_cache_create ( " hfsplus_attr_cache " ,
sizeof ( hfsplus_attr_entry ) , 0 ,
SLAB_HWCACHE_ALIGN , NULL ) ;
if ( ! hfsplus_attr_tree_cachep )
return - ENOMEM ;
return 0 ;
}
void hfsplus_destroy_attr_tree_cache ( void )
{
kmem_cache_destroy ( hfsplus_attr_tree_cachep ) ;
}
int hfsplus_attr_bin_cmp_key ( const hfsplus_btree_key * k1 ,
const hfsplus_btree_key * k2 )
{
__be32 k1_cnid , k2_cnid ;
k1_cnid = k1 - > attr . cnid ;
k2_cnid = k2 - > attr . cnid ;
if ( k1_cnid ! = k2_cnid )
return be32_to_cpu ( k1_cnid ) < be32_to_cpu ( k2_cnid ) ? - 1 : 1 ;
return hfsplus_strcmp (
( const struct hfsplus_unistr * ) & k1 - > attr . key_name ,
( const struct hfsplus_unistr * ) & k2 - > attr . key_name ) ;
}
int hfsplus_attr_build_key ( struct super_block * sb , hfsplus_btree_key * key ,
u32 cnid , const char * name )
{
int len ;
memset ( key , 0 , sizeof ( struct hfsplus_attr_key ) ) ;
key - > attr . cnid = cpu_to_be32 ( cnid ) ;
if ( name ) {
len = strlen ( name ) ;
if ( len > HFSPLUS_ATTR_MAX_STRLEN ) {
2013-05-01 02:27:55 +04:00
pr_err ( " invalid xattr name's length \n " ) ;
2013-02-28 05:03:01 +04:00
return - EINVAL ;
}
hfsplus_asc2uni ( sb ,
( struct hfsplus_unistr * ) & key - > attr . key_name ,
HFSPLUS_ATTR_MAX_STRLEN , name , len ) ;
len = be16_to_cpu ( key - > attr . key_name . length ) ;
} else {
key - > attr . key_name . length = 0 ;
len = 0 ;
}
/* The length of the key, as stored in key_len field, does not include
* the size of the key_len field itself .
* So , offsetof ( hfsplus_attr_key , key_name ) is a trick because
* it takes into consideration key_len field ( __be16 ) of
* hfsplus_attr_key structure instead of length field ( __be16 ) of
* hfsplus_attr_unistr structure .
*/
key - > key_len =
cpu_to_be16 ( offsetof ( struct hfsplus_attr_key , key_name ) +
2 * len ) ;
return 0 ;
}
void hfsplus_attr_build_key_uni ( hfsplus_btree_key * key ,
u32 cnid ,
struct hfsplus_attr_unistr * name )
{
int ustrlen ;
memset ( key , 0 , sizeof ( struct hfsplus_attr_key ) ) ;
ustrlen = be16_to_cpu ( name - > length ) ;
key - > attr . cnid = cpu_to_be32 ( cnid ) ;
key - > attr . key_name . length = cpu_to_be16 ( ustrlen ) ;
ustrlen * = 2 ;
memcpy ( key - > attr . key_name . unicode , name - > unicode , ustrlen ) ;
/* The length of the key, as stored in key_len field, does not include
* the size of the key_len field itself .
* So , offsetof ( hfsplus_attr_key , key_name ) is a trick because
* it takes into consideration key_len field ( __be16 ) of
* hfsplus_attr_key structure instead of length field ( __be16 ) of
* hfsplus_attr_unistr structure .
*/
key - > key_len =
cpu_to_be16 ( offsetof ( struct hfsplus_attr_key , key_name ) +
ustrlen ) ;
}
hfsplus_attr_entry * hfsplus_alloc_attr_entry ( void )
{
return kmem_cache_alloc ( hfsplus_attr_tree_cachep , GFP_KERNEL ) ;
}
void hfsplus_destroy_attr_entry ( hfsplus_attr_entry * entry )
{
if ( entry )
kmem_cache_free ( hfsplus_attr_tree_cachep , entry ) ;
}
# define HFSPLUS_INVALID_ATTR_RECORD -1
static int hfsplus_attr_build_record ( hfsplus_attr_entry * entry , int record_type ,
u32 cnid , const void * value , size_t size )
{
if ( record_type = = HFSPLUS_ATTR_FORK_DATA ) {
/*
* Mac OS X supports only inline data attributes .
* Do nothing
*/
memset ( entry , 0 , sizeof ( * entry ) ) ;
return sizeof ( struct hfsplus_attr_fork_data ) ;
} else if ( record_type = = HFSPLUS_ATTR_EXTENTS ) {
/*
* Mac OS X supports only inline data attributes .
* Do nothing .
*/
memset ( entry , 0 , sizeof ( * entry ) ) ;
return sizeof ( struct hfsplus_attr_extents ) ;
} else if ( record_type = = HFSPLUS_ATTR_INLINE_DATA ) {
u16 len ;
memset ( entry , 0 , sizeof ( struct hfsplus_attr_inline_data ) ) ;
entry - > inline_data . record_type = cpu_to_be32 ( record_type ) ;
if ( size < = HFSPLUS_MAX_INLINE_DATA_SIZE )
len = size ;
else
return HFSPLUS_INVALID_ATTR_RECORD ;
entry - > inline_data . length = cpu_to_be16 ( len ) ;
memcpy ( entry - > inline_data . raw_bytes , value , len ) ;
/*
* Align len on two - byte boundary .
* It needs to add pad byte if we have odd len .
*/
len = round_up ( len , 2 ) ;
return offsetof ( struct hfsplus_attr_inline_data , raw_bytes ) +
len ;
} else /* invalid input */
memset ( entry , 0 , sizeof ( * entry ) ) ;
return HFSPLUS_INVALID_ATTR_RECORD ;
}
int hfsplus_find_attr ( struct super_block * sb , u32 cnid ,
const char * name , struct hfs_find_data * fd )
{
int err = 0 ;
2013-05-01 02:27:54 +04:00
hfs_dbg ( ATTR_MOD , " find_attr: %s,%d \n " , name ? name : NULL , cnid ) ;
2013-02-28 05:03:01 +04:00
if ( ! HFSPLUS_SB ( sb ) - > attr_tree ) {
2013-05-01 02:27:55 +04:00
pr_err ( " attributes file doesn't exist \n " ) ;
2013-02-28 05:03:01 +04:00
return - EINVAL ;
}
if ( name ) {
err = hfsplus_attr_build_key ( sb , fd - > search_key , cnid , name ) ;
if ( err )
goto failed_find_attr ;
err = hfs_brec_find ( fd , hfs_find_rec_by_key ) ;
if ( err )
goto failed_find_attr ;
} else {
err = hfsplus_attr_build_key ( sb , fd - > search_key , cnid , NULL ) ;
if ( err )
goto failed_find_attr ;
err = hfs_brec_find ( fd , hfs_find_1st_rec_by_cnid ) ;
if ( err )
goto failed_find_attr ;
}
failed_find_attr :
return err ;
}
int hfsplus_attr_exists ( struct inode * inode , const char * name )
{
int err = 0 ;
struct super_block * sb = inode - > i_sb ;
struct hfs_find_data fd ;
if ( ! HFSPLUS_SB ( sb ) - > attr_tree )
return 0 ;
err = hfs_find_init ( HFSPLUS_SB ( sb ) - > attr_tree , & fd ) ;
if ( err )
return 0 ;
err = hfsplus_find_attr ( sb , inode - > i_ino , name , & fd ) ;
if ( err )
goto attr_not_found ;
hfs_find_exit ( & fd ) ;
return 1 ;
attr_not_found :
hfs_find_exit ( & fd ) ;
return 0 ;
}
int hfsplus_create_attr ( struct inode * inode ,
const char * name ,
const void * value , size_t size )
{
struct super_block * sb = inode - > i_sb ;
struct hfs_find_data fd ;
hfsplus_attr_entry * entry_ptr ;
int entry_size ;
int err ;
2013-05-01 02:27:54 +04:00
hfs_dbg ( ATTR_MOD , " create_attr: %s,%ld \n " ,
2013-02-28 05:03:01 +04:00
name ? name : NULL , inode - > i_ino ) ;
if ( ! HFSPLUS_SB ( sb ) - > attr_tree ) {
2013-05-01 02:27:55 +04:00
pr_err ( " attributes file doesn't exist \n " ) ;
2013-02-28 05:03:01 +04:00
return - EINVAL ;
}
entry_ptr = hfsplus_alloc_attr_entry ( ) ;
if ( ! entry_ptr )
return - ENOMEM ;
err = hfs_find_init ( HFSPLUS_SB ( sb ) - > attr_tree , & fd ) ;
if ( err )
goto failed_init_create_attr ;
if ( name ) {
err = hfsplus_attr_build_key ( sb , fd . search_key ,
inode - > i_ino , name ) ;
if ( err )
goto failed_create_attr ;
} else {
err = - EINVAL ;
goto failed_create_attr ;
}
/* Mac OS X supports only inline data attributes. */
entry_size = hfsplus_attr_build_record ( entry_ptr ,
HFSPLUS_ATTR_INLINE_DATA ,
inode - > i_ino ,
value , size ) ;
if ( entry_size = = HFSPLUS_INVALID_ATTR_RECORD ) {
err = - EINVAL ;
goto failed_create_attr ;
}
err = hfs_brec_find ( & fd , hfs_find_rec_by_key ) ;
if ( err ! = - ENOENT ) {
if ( ! err )
err = - EEXIST ;
goto failed_create_attr ;
}
err = hfs_brec_insert ( & fd , entry_ptr , entry_size ) ;
if ( err )
goto failed_create_attr ;
hfsplus_mark_inode_dirty ( inode , HFSPLUS_I_ATTR_DIRTY ) ;
failed_create_attr :
hfs_find_exit ( & fd ) ;
failed_init_create_attr :
hfsplus_destroy_attr_entry ( entry_ptr ) ;
return err ;
}
static int __hfsplus_delete_attr ( struct inode * inode , u32 cnid ,
struct hfs_find_data * fd )
{
int err = 0 ;
__be32 found_cnid , record_type ;
hfs_bnode_read ( fd - > bnode , & found_cnid ,
fd - > keyoffset +
offsetof ( struct hfsplus_attr_key , cnid ) ,
sizeof ( __be32 ) ) ;
if ( cnid ! = be32_to_cpu ( found_cnid ) )
return - ENOENT ;
hfs_bnode_read ( fd - > bnode , & record_type ,
fd - > entryoffset , sizeof ( record_type ) ) ;
switch ( be32_to_cpu ( record_type ) ) {
case HFSPLUS_ATTR_INLINE_DATA :
/* All is OK. Do nothing. */
break ;
case HFSPLUS_ATTR_FORK_DATA :
case HFSPLUS_ATTR_EXTENTS :
2013-05-01 02:27:55 +04:00
pr_err ( " only inline data xattr are supported \n " ) ;
2013-02-28 05:03:01 +04:00
return - EOPNOTSUPP ;
default :
2013-05-01 02:27:55 +04:00
pr_err ( " invalid extended attribute record \n " ) ;
2013-02-28 05:03:01 +04:00
return - ENOENT ;
}
err = hfs_brec_remove ( fd ) ;
if ( err )
return err ;
hfsplus_mark_inode_dirty ( inode , HFSPLUS_I_ATTR_DIRTY ) ;
return err ;
}
int hfsplus_delete_attr ( struct inode * inode , const char * name )
{
int err = 0 ;
struct super_block * sb = inode - > i_sb ;
struct hfs_find_data fd ;
2013-05-01 02:27:54 +04:00
hfs_dbg ( ATTR_MOD , " delete_attr: %s,%ld \n " ,
2013-02-28 05:03:01 +04:00
name ? name : NULL , inode - > i_ino ) ;
if ( ! HFSPLUS_SB ( sb ) - > attr_tree ) {
2013-05-01 02:27:55 +04:00
pr_err ( " attributes file doesn't exist \n " ) ;
2013-02-28 05:03:01 +04:00
return - EINVAL ;
}
err = hfs_find_init ( HFSPLUS_SB ( sb ) - > attr_tree , & fd ) ;
if ( err )
return err ;
if ( name ) {
err = hfsplus_attr_build_key ( sb , fd . search_key ,
inode - > i_ino , name ) ;
if ( err )
goto out ;
} else {
2013-05-01 02:27:55 +04:00
pr_err ( " invalid extended attribute name \n " ) ;
2013-02-28 05:03:01 +04:00
err = - EINVAL ;
goto out ;
}
err = hfs_brec_find ( & fd , hfs_find_rec_by_key ) ;
if ( err )
goto out ;
err = __hfsplus_delete_attr ( inode , inode - > i_ino , & fd ) ;
if ( err )
goto out ;
out :
hfs_find_exit ( & fd ) ;
return err ;
}
int hfsplus_delete_all_attrs ( struct inode * dir , u32 cnid )
{
int err = 0 ;
struct hfs_find_data fd ;
2013-05-01 02:27:54 +04:00
hfs_dbg ( ATTR_MOD , " delete_all_attrs: %d \n " , cnid ) ;
2013-02-28 05:03:01 +04:00
if ( ! HFSPLUS_SB ( dir - > i_sb ) - > attr_tree ) {
2013-05-01 02:27:55 +04:00
pr_err ( " attributes file doesn't exist \n " ) ;
2013-02-28 05:03:01 +04:00
return - EINVAL ;
}
err = hfs_find_init ( HFSPLUS_SB ( dir - > i_sb ) - > attr_tree , & fd ) ;
if ( err )
return err ;
for ( ; ; ) {
err = hfsplus_find_attr ( dir - > i_sb , cnid , NULL , & fd ) ;
if ( err ) {
if ( err ! = - ENOENT )
2013-05-01 02:27:55 +04:00
pr_err ( " xattr search failed \n " ) ;
2013-02-28 05:03:01 +04:00
goto end_delete_all ;
}
err = __hfsplus_delete_attr ( dir , cnid , & fd ) ;
if ( err )
goto end_delete_all ;
}
end_delete_all :
hfs_find_exit ( & fd ) ;
return err ;
}