2019-07-31 18:57:31 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2018-07-26 15:21:50 +03:00
/*
* Copyright ( C ) 2017 - 2018 HUAWEI , Inc .
* http : //www.huawei.com/
* Created by Gao Xiang < gaoxiang25 @ huawei . com >
*/
2018-07-26 15:21:52 +03:00
# include "xattr.h"
2018-07-26 15:21:50 +03:00
2018-07-26 15:21:55 +03:00
# include <trace/events/erofs.h>
2019-02-01 15:16:31 +03:00
struct erofs_qstr {
const unsigned char * name ;
const unsigned char * end ;
} ;
/* based on the end of qn is accurate and it must have the trailing '\0' */
2019-09-04 05:09:05 +03:00
static inline int erofs_dirnamecmp ( const struct erofs_qstr * qn ,
const struct erofs_qstr * qd ,
unsigned int * matched )
2018-07-26 15:21:50 +03:00
{
2019-02-01 15:16:31 +03:00
unsigned int i = * matched ;
/*
* on - disk error , let ' s only BUG_ON in the debugging mode .
* otherwise , it will return 1 to just skip the invalid name
* and go on ( in consideration of the lookup performance ) .
*/
DBG_BUGON ( qd - > name > qd - > end ) ;
/* qd could not have trailing '\0' */
/* However it is absolutely safe if < qd->end */
while ( qd - > name + i < qd - > end & & qd - > name [ i ] ! = ' \0 ' ) {
if ( qn - > name [ i ] ! = qd - > name [ i ] ) {
* matched = i ;
return qn - > name [ i ] > qd - > name [ i ] ? 1 : - 1 ;
2018-07-26 15:21:50 +03:00
}
2019-02-01 15:16:31 +03:00
+ + i ;
2018-07-26 15:21:50 +03:00
}
2019-02-01 15:16:31 +03:00
* matched = i ;
/* See comments in __d_alloc on the terminating NUL character */
return qn - > name [ i ] = = ' \0 ' ? 0 : 1 ;
2018-07-26 15:21:50 +03:00
}
2019-02-01 15:16:31 +03:00
# define nameoff_from_disk(off, sz) (le16_to_cpu(off) & ((sz) - 1))
static struct erofs_dirent * find_target_dirent ( struct erofs_qstr * name ,
u8 * data ,
unsigned int dirblksize ,
const int ndirents )
2018-07-26 15:21:50 +03:00
{
2019-02-01 15:16:31 +03:00
int head , back ;
2018-09-10 22:41:14 +03:00
unsigned int startprfx , endprfx ;
2018-07-26 15:21:50 +03:00
struct erofs_dirent * const de = ( struct erofs_dirent * ) data ;
2019-02-01 15:16:31 +03:00
/* since the 1st dirent has been evaluated previously */
head = 1 ;
2018-07-26 15:21:50 +03:00
back = ndirents - 1 ;
startprfx = endprfx = 0 ;
while ( head < = back ) {
2019-02-01 15:16:31 +03:00
const int mid = head + ( back - head ) / 2 ;
const int nameoff = nameoff_from_disk ( de [ mid ] . nameoff ,
dirblksize ) ;
2018-09-10 22:41:14 +03:00
unsigned int matched = min ( startprfx , endprfx ) ;
2019-02-01 15:16:31 +03:00
struct erofs_qstr dname = {
. name = data + nameoff ,
2019-08-29 19:38:27 +03:00
. end = mid > = ndirents - 1 ?
2019-02-01 15:16:31 +03:00
data + dirblksize :
data + nameoff_from_disk ( de [ mid + 1 ] . nameoff ,
dirblksize )
} ;
2018-07-26 15:21:50 +03:00
/* string comparison without already matched prefix */
2019-09-04 05:09:05 +03:00
int ret = erofs_dirnamecmp ( name , & dname , & matched ) ;
2018-07-26 15:21:50 +03:00
2019-08-29 19:38:27 +03:00
if ( ! ret ) {
2018-07-26 15:21:50 +03:00
return de + mid ;
2019-02-01 15:16:31 +03:00
} else if ( ret > 0 ) {
2018-07-26 15:21:50 +03:00
head = mid + 1 ;
startprfx = matched ;
2019-02-01 15:16:31 +03:00
} else {
2018-07-26 15:21:50 +03:00
back = mid - 1 ;
endprfx = matched ;
}
}
return ERR_PTR ( - ENOENT ) ;
}
2019-02-01 15:16:31 +03:00
static struct page * find_target_block_classic ( struct inode * dir ,
struct erofs_qstr * name ,
int * _ndirents )
2018-07-26 15:21:50 +03:00
{
2018-09-10 22:41:14 +03:00
unsigned int startprfx , endprfx ;
2019-02-01 15:16:31 +03:00
int head , back ;
2018-07-26 15:21:50 +03:00
struct address_space * const mapping = dir - > i_mapping ;
struct page * candidate = ERR_PTR ( - ENOENT ) ;
startprfx = endprfx = 0 ;
head = 0 ;
2019-09-04 05:09:05 +03:00
back = erofs_inode_datablocks ( dir ) - 1 ;
2018-07-26 15:21:50 +03:00
while ( head < = back ) {
2019-02-01 15:16:31 +03:00
const int mid = head + ( back - head ) / 2 ;
2018-07-26 15:21:50 +03:00
struct page * page = read_mapping_page ( mapping , mid , NULL ) ;
2019-02-01 15:16:31 +03:00
if ( ! IS_ERR ( page ) ) {
2018-07-26 15:21:50 +03:00
struct erofs_dirent * de = kmap_atomic ( page ) ;
2019-02-01 15:16:31 +03:00
const int nameoff = nameoff_from_disk ( de - > nameoff ,
EROFS_BLKSIZ ) ;
const int ndirents = nameoff / sizeof ( * de ) ;
int diff ;
unsigned int matched ;
struct erofs_qstr dname ;
2018-07-26 15:21:50 +03:00
2019-08-29 19:38:27 +03:00
if ( ! ndirents ) {
2019-02-01 15:16:31 +03:00
kunmap_atomic ( de ) ;
put_page ( page ) ;
2019-09-04 05:09:09 +03:00
erofs_err ( dir - > i_sb ,
" corrupted dir block %d @ nid %llu " ,
mid , EROFS_I ( dir ) - > nid ) ;
2019-08-14 13:37:03 +03:00
DBG_BUGON ( 1 ) ;
page = ERR_PTR ( - EFSCORRUPTED ) ;
2019-02-01 15:16:31 +03:00
goto out ;
}
2018-07-26 15:21:50 +03:00
matched = min ( startprfx , endprfx ) ;
dname . name = ( u8 * ) de + nameoff ;
2019-02-01 15:16:31 +03:00
if ( ndirents = = 1 )
dname . end = ( u8 * ) de + EROFS_BLKSIZ ;
else
dname . end = ( u8 * ) de +
nameoff_from_disk ( de [ 1 ] . nameoff ,
EROFS_BLKSIZ ) ;
2018-07-26 15:21:50 +03:00
/* string comparison without already matched prefix */
2019-09-04 05:09:05 +03:00
diff = erofs_dirnamecmp ( name , & dname , & matched ) ;
2018-07-26 15:21:50 +03:00
kunmap_atomic ( de ) ;
2019-08-29 19:38:27 +03:00
if ( ! diff ) {
2019-02-01 15:16:31 +03:00
* _ndirents = 0 ;
goto out ;
2018-07-26 15:21:50 +03:00
} else if ( diff > 0 ) {
head = mid + 1 ;
startprfx = matched ;
2019-02-12 06:24:22 +03:00
if ( ! IS_ERR ( candidate ) )
2018-07-26 15:21:50 +03:00
put_page ( candidate ) ;
candidate = page ;
2019-02-01 15:16:31 +03:00
* _ndirents = ndirents ;
2018-07-26 15:21:50 +03:00
} else {
put_page ( page ) ;
back = mid - 1 ;
endprfx = matched ;
}
2019-02-01 15:16:31 +03:00
continue ;
2018-07-26 15:21:50 +03:00
}
2019-02-01 15:16:31 +03:00
out : /* free if the candidate is valid */
if ( ! IS_ERR ( candidate ) )
put_page ( candidate ) ;
return page ;
2018-07-26 15:21:50 +03:00
}
return candidate ;
}
int erofs_namei ( struct inode * dir ,
2019-02-01 15:16:31 +03:00
struct qstr * name ,
erofs_nid_t * nid , unsigned int * d_type )
2018-07-26 15:21:50 +03:00
{
2019-02-01 15:16:31 +03:00
int ndirents ;
2018-07-26 15:21:50 +03:00
struct page * page ;
2019-02-01 15:16:31 +03:00
void * data ;
2018-07-26 15:21:50 +03:00
struct erofs_dirent * de ;
2019-02-01 15:16:31 +03:00
struct erofs_qstr qn ;
2018-07-26 15:21:50 +03:00
2019-08-29 19:38:27 +03:00
if ( ! dir - > i_size )
2018-07-26 15:21:50 +03:00
return - ENOENT ;
2019-02-01 15:16:31 +03:00
qn . name = name - > name ;
qn . end = name - > name + name - > len ;
ndirents = 0 ;
page = find_target_block_classic ( dir , & qn , & ndirents ) ;
2018-07-26 15:21:50 +03:00
2019-02-12 06:24:22 +03:00
if ( IS_ERR ( page ) )
2018-07-26 15:21:50 +03:00
return PTR_ERR ( page ) ;
data = kmap_atomic ( page ) ;
/* the target page has been mapped */
2019-02-01 15:16:31 +03:00
if ( ndirents )
de = find_target_dirent ( & qn , data , EROFS_BLKSIZ , ndirents ) ;
else
de = ( struct erofs_dirent * ) data ;
2018-07-26 15:21:50 +03:00
2019-02-12 06:24:22 +03:00
if ( ! IS_ERR ( de ) ) {
2018-07-26 15:21:50 +03:00
* nid = le64_to_cpu ( de - > nid ) ;
* d_type = de - > file_type ;
}
kunmap_atomic ( data ) ;
put_page ( page ) ;
2018-07-30 04:51:01 +03:00
return PTR_ERR_OR_ZERO ( de ) ;
2018-07-26 15:21:50 +03:00
}
/* NOTE: i_mutex is already held by vfs */
static struct dentry * erofs_lookup ( struct inode * dir ,
2019-03-19 02:58:41 +03:00
struct dentry * dentry ,
unsigned int flags )
2018-07-26 15:21:50 +03:00
{
int err ;
erofs_nid_t nid ;
2018-09-10 22:41:14 +03:00
unsigned int d_type ;
2018-07-26 15:21:50 +03:00
struct inode * inode ;
DBG_BUGON ( ! d_really_is_negative ( dentry ) ) ;
/* dentry must be unhashed in lookup, no need to worry about */
DBG_BUGON ( ! d_unhashed ( dentry ) ) ;
2018-07-26 15:21:55 +03:00
trace_erofs_lookup ( dir , dentry , flags ) ;
2018-07-26 15:21:50 +03:00
/* file name exceeds fs limit */
2019-08-29 19:38:27 +03:00
if ( dentry - > d_name . len > EROFS_NAME_LEN )
2018-07-26 15:21:50 +03:00
return ERR_PTR ( - ENAMETOOLONG ) ;
/* false uninitialized warnings on gcc 4.8.x */
err = erofs_namei ( dir , & dentry - > d_name , & nid , & d_type ) ;
if ( err = = - ENOENT ) {
/* negative dentry */
inode = NULL ;
2019-08-29 19:38:27 +03:00
} else if ( err ) {
2018-10-10 23:37:39 +03:00
inode = ERR_PTR ( err ) ;
} else {
2019-09-04 05:09:09 +03:00
erofs_dbg ( " %s, %s (nid %llu) found, d_type %u " , __func__ ,
dentry - > d_name . name , nid , d_type ) ;
2019-08-16 10:11:42 +03:00
inode = erofs_iget ( dir - > i_sb , nid , d_type = = FT_DIR ) ;
2018-10-10 23:37:39 +03:00
}
2018-07-26 15:21:50 +03:00
return d_splice_alias ( inode , dentry ) ;
}
const struct inode_operations erofs_dir_iops = {
. lookup = erofs_lookup ,
2019-05-28 06:19:42 +03:00
. getattr = erofs_getattr ,
2018-07-26 15:21:52 +03:00
# ifdef CONFIG_EROFS_FS_XATTR
. listxattr = erofs_listxattr ,
# endif
2019-01-29 11:35:20 +03:00
. get_acl = erofs_get_acl ,
2018-07-26 15:21:50 +03:00
} ;