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 ;
2014-04-04 01:50:35 +04:00
int __init hfsplus_create_attr_tree_cache ( void )
2013-02-28 05:03:01 +04:00
{
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 ) {
hfsplus: correct usage of HFSPLUS_ATTR_MAX_STRLEN for non-English attributes
HFSPLUS_ATTR_MAX_STRLEN (=127) is the limit of attribute names for the
number of unicode character (UTF-16BE) storable in the HFS+ file system.
Almost all the current usage of it is wrong, in relation to NLS to
on-disk conversion.
Except for one use calling hfsplus_asc2uni (which should stay the same)
and its uses in calling hfsplus_uni2asc (which was corrected in the
earlier patch in this series concerning usage of hfsplus_uni2asc), all
the other uses are of the forms:
- char buffer[size]
- bound check: "if (namespace_adjusted_input_length > size) return failure;"
Conversion between on-disk unicode representation and NLS char strings
(in whichever direction) always needs to accommodate the worst-case NLS
conversion, so all char buffers of that size need to have a
NLS_MAX_CHARSET_SIZE x .
The bound checks are all wrong, since they compare nls_length derived
from strlen() to a unicode length limit.
It turns out that all the bound-checks do is to protect
hfsplus_asc2uni(), which can fail if the input is too large.
There is only one usage of it as far as attributes are concerned, in
hfsplus_attr_build_key(). It is in turn used by hfsplus_find_attr(),
hfsplus_create_attr(), hfsplus_delete_attr(). Thus making sure that
errors from hfsplus_asc2uni() is caught in hfsplus_attr_build_key() and
propagated is sufficient to replace all the bound checks.
Unpropagated errors from hfsplus_asc2uni() in the file catalog code was
addressed recently in an independent patch "hfsplus: fix longname
handling" by Sougata Santra.
Before this patch, trying to set a 55 CJK character (in a UTF-8 locale,
> 127/3=42) attribute plus user prefix fails with:
$ setfattr -n user.`cat testing-string` -v `cat testing-string` \
testing-string
setfattr: testing-string: Operation not supported
and retrieving a stored long attributes is particular ugly(!):
find /mnt/* -type f -exec getfattr -d {} \;
getfattr: /mnt/testing-string: Input/output error
with console log:
[268008.389781] hfsplus: unicode conversion failed
After the patch, both of the above works.
FYI, the test attribute string is prepared with:
echo -e -n \
"\xe9\x80\x99\xe6\x98\xaf\xe4\xb8\x80\xe5\x80\x8b\xe9\x9d\x9e\xe5" \
"\xb8\xb8\xe6\xbc\xab\xe9\x95\xb7\xe8\x80\x8c\xe6\xa5\xb5\xe5\x85" \
"\xb6\xe4\xb9\x8f\xe5\x91\xb3\xe5\x92\x8c\xe7\x9b\xb8\xe7\x95\xb6" \
"\xe7\x84\xa1\xe8\xb6\xa3\xe3\x80\x81\xe4\xbb\xa5\xe5\x8f\x8a\xe7" \
"\x84\xa1\xe7\x94\xa8\xe7\x9a\x84\xe3\x80\x81\xe5\x86\x8d\xe5\x8a" \
"\xa0\xe4\xb8\x8a\xe6\xaf\xab\xe7\x84\xa1\xe6\x84\x8f\xe7\xbe\xa9" \
"\xe7\x9a\x84\xe6\x93\xb4\xe5\xb1\x95\xe5\xb1\xac\xe6\x80\xa7\xef" \
"\xbc\x8c\xe8\x80\x8c\xe5\x85\xb6\xe5\x94\xaf\xe4\xb8\x80\xe5\x89" \
"\xb5\xe5\xbb\xba\xe7\x9b\xae\xe7\x9a\x84\xe5\x83\x85\xe6\x98\xaf" \
"\xe7\x82\xba\xe4\xba\x86\xe6\xb8\xac\xe8\xa9\xa6\xe4\xbd\x9c\xe7" \
"\x94\xa8\xe3\x80\x82" | tr -d ' '
(= "pointlessly long attribute for testing", elaborate Chinese in
UTF-8 enoding).
However, it is not possible to set double the size (110 + 5 is still
under 127) in a UTF-8 locale:
$setfattr -n user.`cat testing-string testing-string` -v \
`cat testing-string testing-string` testing-string
setfattr: testing-string: Numerical result out of range
110 CJK char in UTF-8 is 330 bytes - the generic get/set attribute
system call code in linux/fs/xattr.c imposes a 255 byte limit. One can
use a combination of iconv to encode content, changing terminal locale
for viewing, and an nls=cp932/cp936/cp949/cp950 mount option to fully
use 127-unicode attribute in a double-byte locale.
Also, as an additional information, it is possible to (mis-)use unicode
half-width/full-width forms (U+FFxx) to write attributes which looks
like english but not actually ascii.
Thanks Anton Altaparmakov for reviewing the earlier ideas behind this
change.
[akpm@linux-foundation.org: fix build]
[akpm@linux-foundation.org: fix build]
Signed-off-by: Hin-Tak Leung <htl10@users.sourceforge.net>
Cc: Anton Altaparmakov <anton@tuxera.com>
Cc: Vyacheslav Dubeyko <slava@dubeyko.com>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Cc: Christoph Hellwig <hch@infradead.org>
Cc: Sougata Santra <sougata@tuxera.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2014-06-07 01:36:22 +04:00
int res = hfsplus_asc2uni ( sb ,
2013-02-28 05:03:01 +04:00
( struct hfsplus_unistr * ) & key - > attr . key_name ,
hfsplus: correct usage of HFSPLUS_ATTR_MAX_STRLEN for non-English attributes
HFSPLUS_ATTR_MAX_STRLEN (=127) is the limit of attribute names for the
number of unicode character (UTF-16BE) storable in the HFS+ file system.
Almost all the current usage of it is wrong, in relation to NLS to
on-disk conversion.
Except for one use calling hfsplus_asc2uni (which should stay the same)
and its uses in calling hfsplus_uni2asc (which was corrected in the
earlier patch in this series concerning usage of hfsplus_uni2asc), all
the other uses are of the forms:
- char buffer[size]
- bound check: "if (namespace_adjusted_input_length > size) return failure;"
Conversion between on-disk unicode representation and NLS char strings
(in whichever direction) always needs to accommodate the worst-case NLS
conversion, so all char buffers of that size need to have a
NLS_MAX_CHARSET_SIZE x .
The bound checks are all wrong, since they compare nls_length derived
from strlen() to a unicode length limit.
It turns out that all the bound-checks do is to protect
hfsplus_asc2uni(), which can fail if the input is too large.
There is only one usage of it as far as attributes are concerned, in
hfsplus_attr_build_key(). It is in turn used by hfsplus_find_attr(),
hfsplus_create_attr(), hfsplus_delete_attr(). Thus making sure that
errors from hfsplus_asc2uni() is caught in hfsplus_attr_build_key() and
propagated is sufficient to replace all the bound checks.
Unpropagated errors from hfsplus_asc2uni() in the file catalog code was
addressed recently in an independent patch "hfsplus: fix longname
handling" by Sougata Santra.
Before this patch, trying to set a 55 CJK character (in a UTF-8 locale,
> 127/3=42) attribute plus user prefix fails with:
$ setfattr -n user.`cat testing-string` -v `cat testing-string` \
testing-string
setfattr: testing-string: Operation not supported
and retrieving a stored long attributes is particular ugly(!):
find /mnt/* -type f -exec getfattr -d {} \;
getfattr: /mnt/testing-string: Input/output error
with console log:
[268008.389781] hfsplus: unicode conversion failed
After the patch, both of the above works.
FYI, the test attribute string is prepared with:
echo -e -n \
"\xe9\x80\x99\xe6\x98\xaf\xe4\xb8\x80\xe5\x80\x8b\xe9\x9d\x9e\xe5" \
"\xb8\xb8\xe6\xbc\xab\xe9\x95\xb7\xe8\x80\x8c\xe6\xa5\xb5\xe5\x85" \
"\xb6\xe4\xb9\x8f\xe5\x91\xb3\xe5\x92\x8c\xe7\x9b\xb8\xe7\x95\xb6" \
"\xe7\x84\xa1\xe8\xb6\xa3\xe3\x80\x81\xe4\xbb\xa5\xe5\x8f\x8a\xe7" \
"\x84\xa1\xe7\x94\xa8\xe7\x9a\x84\xe3\x80\x81\xe5\x86\x8d\xe5\x8a" \
"\xa0\xe4\xb8\x8a\xe6\xaf\xab\xe7\x84\xa1\xe6\x84\x8f\xe7\xbe\xa9" \
"\xe7\x9a\x84\xe6\x93\xb4\xe5\xb1\x95\xe5\xb1\xac\xe6\x80\xa7\xef" \
"\xbc\x8c\xe8\x80\x8c\xe5\x85\xb6\xe5\x94\xaf\xe4\xb8\x80\xe5\x89" \
"\xb5\xe5\xbb\xba\xe7\x9b\xae\xe7\x9a\x84\xe5\x83\x85\xe6\x98\xaf" \
"\xe7\x82\xba\xe4\xba\x86\xe6\xb8\xac\xe8\xa9\xa6\xe4\xbd\x9c\xe7" \
"\x94\xa8\xe3\x80\x82" | tr -d ' '
(= "pointlessly long attribute for testing", elaborate Chinese in
UTF-8 enoding).
However, it is not possible to set double the size (110 + 5 is still
under 127) in a UTF-8 locale:
$setfattr -n user.`cat testing-string testing-string` -v \
`cat testing-string testing-string` testing-string
setfattr: testing-string: Numerical result out of range
110 CJK char in UTF-8 is 330 bytes - the generic get/set attribute
system call code in linux/fs/xattr.c imposes a 255 byte limit. One can
use a combination of iconv to encode content, changing terminal locale
for viewing, and an nls=cp932/cp936/cp949/cp950 mount option to fully
use 127-unicode attribute in a double-byte locale.
Also, as an additional information, it is possible to (mis-)use unicode
half-width/full-width forms (U+FFxx) to write attributes which looks
like english but not actually ascii.
Thanks Anton Altaparmakov for reviewing the earlier ideas behind this
change.
[akpm@linux-foundation.org: fix build]
[akpm@linux-foundation.org: fix build]
Signed-off-by: Hin-Tak Leung <htl10@users.sourceforge.net>
Cc: Anton Altaparmakov <anton@tuxera.com>
Cc: Vyacheslav Dubeyko <slava@dubeyko.com>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Cc: Christoph Hellwig <hch@infradead.org>
Cc: Sougata Santra <sougata@tuxera.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2014-06-07 01:36:22 +04:00
HFSPLUS_ATTR_MAX_STRLEN , name , strlen ( name ) ) ;
if ( res )
return res ;
2013-02-28 05:03:01 +04:00
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 ;
}
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 ;
}