diff options
Diffstat (limited to 'fs/f2fs/dir.c')
-rw-r--r-- | fs/f2fs/dir.c | 184 |
1 files changed, 175 insertions, 9 deletions
diff --git a/fs/f2fs/dir.c b/fs/f2fs/dir.c index 85a1528f319f..4033778bcbbf 100644 --- a/fs/f2fs/dir.c +++ b/fs/f2fs/dir.c @@ -8,6 +8,7 @@ #include <linux/fs.h> #include <linux/f2fs_fs.h> #include <linux/sched/signal.h> +#include <linux/unicode.h> #include "f2fs.h" #include "node.h" #include "acl.h" @@ -81,7 +82,8 @@ static unsigned long dir_block_index(unsigned int level, return bidx; } -static struct f2fs_dir_entry *find_in_block(struct page *dentry_page, +static struct f2fs_dir_entry *find_in_block(struct inode *dir, + struct page *dentry_page, struct fscrypt_name *fname, f2fs_hash_t namehash, int *max_slots, @@ -93,7 +95,7 @@ static struct f2fs_dir_entry *find_in_block(struct page *dentry_page, dentry_blk = (struct f2fs_dentry_block *)page_address(dentry_page); - make_dentry_ptr_block(NULL, &d, dentry_blk); + make_dentry_ptr_block(dir, &d, dentry_blk); de = f2fs_find_target_dentry(fname, namehash, max_slots, &d); if (de) *res_page = dentry_page; @@ -101,14 +103,116 @@ static struct f2fs_dir_entry *find_in_block(struct page *dentry_page, return de; } +#ifdef CONFIG_UNICODE +/* + * Test whether a case-insensitive directory entry matches the filename + * being searched for. + * + * Returns: 0 if the directory entry matches, more than 0 if it + * doesn't match or less than zero on error. + */ +int f2fs_ci_compare(const struct inode *parent, const struct qstr *name, + const struct qstr *entry, bool quick) +{ + const struct f2fs_sb_info *sbi = F2FS_SB(parent->i_sb); + const struct unicode_map *um = sbi->s_encoding; + int ret; + + if (quick) + ret = utf8_strncasecmp_folded(um, name, entry); + else + ret = utf8_strncasecmp(um, name, entry); + + if (ret < 0) { + /* Handle invalid character sequence as either an error + * or as an opaque byte sequence. + */ + if (f2fs_has_strict_mode(sbi)) + return -EINVAL; + + if (name->len != entry->len) + return 1; + + return !!memcmp(name->name, entry->name, name->len); + } + + return ret; +} + +static void f2fs_fname_setup_ci_filename(struct inode *dir, + const struct qstr *iname, + struct fscrypt_str *cf_name) +{ + struct f2fs_sb_info *sbi = F2FS_I_SB(dir); + + if (!IS_CASEFOLDED(dir)) { + cf_name->name = NULL; + return; + } + + cf_name->name = f2fs_kmalloc(sbi, F2FS_NAME_LEN, GFP_NOFS); + if (!cf_name->name) + return; + + cf_name->len = utf8_casefold(sbi->s_encoding, + iname, cf_name->name, + F2FS_NAME_LEN); + if ((int)cf_name->len <= 0) { + kvfree(cf_name->name); + cf_name->name = NULL; + } +} +#endif + +static inline bool f2fs_match_name(struct f2fs_dentry_ptr *d, + struct f2fs_dir_entry *de, + struct fscrypt_name *fname, + struct fscrypt_str *cf_str, + unsigned long bit_pos, + f2fs_hash_t namehash) +{ +#ifdef CONFIG_UNICODE + struct inode *parent = d->inode; + struct f2fs_sb_info *sbi = F2FS_I_SB(parent); + struct qstr entry; +#endif + + if (de->hash_code != namehash) + return false; + +#ifdef CONFIG_UNICODE + entry.name = d->filename[bit_pos]; + entry.len = de->name_len; + + if (sbi->s_encoding && IS_CASEFOLDED(parent)) { + if (cf_str->name) { + struct qstr cf = {.name = cf_str->name, + .len = cf_str->len}; + return !f2fs_ci_compare(parent, &cf, &entry, true); + } + return !f2fs_ci_compare(parent, fname->usr_fname, &entry, + false); + } +#endif + if (fscrypt_match_name(fname, d->filename[bit_pos], + le16_to_cpu(de->name_len))) + return true; + return false; +} + struct f2fs_dir_entry *f2fs_find_target_dentry(struct fscrypt_name *fname, f2fs_hash_t namehash, int *max_slots, struct f2fs_dentry_ptr *d) { struct f2fs_dir_entry *de; + struct fscrypt_str cf_str = { .name = NULL, .len = 0 }; unsigned long bit_pos = 0; int max_len = 0; +#ifdef CONFIG_UNICODE + f2fs_fname_setup_ci_filename(d->inode, fname->usr_fname, &cf_str); +#endif + if (max_slots) *max_slots = 0; while (bit_pos < d->max) { @@ -125,9 +229,7 @@ struct f2fs_dir_entry *f2fs_find_target_dentry(struct fscrypt_name *fname, continue; } - if (de->hash_code == namehash && - fscrypt_match_name(fname, d->filename[bit_pos], - le16_to_cpu(de->name_len))) + if (f2fs_match_name(d, de, fname, &cf_str, bit_pos, namehash)) goto found; if (max_slots && max_len > *max_slots) @@ -141,6 +243,10 @@ struct f2fs_dir_entry *f2fs_find_target_dentry(struct fscrypt_name *fname, found: if (max_slots && max_len > *max_slots) *max_slots = max_len; + +#ifdef CONFIG_UNICODE + kvfree(cf_str.name); +#endif return de; } @@ -157,7 +263,7 @@ static struct f2fs_dir_entry *find_in_level(struct inode *dir, struct f2fs_dir_entry *de = NULL; bool room = false; int max_slots; - f2fs_hash_t namehash = f2fs_dentry_hash(&name, fname); + f2fs_hash_t namehash = f2fs_dentry_hash(dir, &name, fname); nbucket = dir_buckets(level, F2FS_I(dir)->i_dir_level); nblock = bucket_blocks(level); @@ -179,8 +285,8 @@ static struct f2fs_dir_entry *find_in_level(struct inode *dir, } } - de = find_in_block(dentry_page, fname, namehash, &max_slots, - res_page); + de = find_in_block(dir, dentry_page, fname, namehash, + &max_slots, res_page); if (de) break; @@ -250,6 +356,14 @@ struct f2fs_dir_entry *f2fs_find_entry(struct inode *dir, struct fscrypt_name fname; int err; +#ifdef CONFIG_UNICODE + if (f2fs_has_strict_mode(F2FS_I_SB(dir)) && IS_CASEFOLDED(dir) && + utf8_validate(F2FS_I_SB(dir)->s_encoding, child)) { + *res_page = ERR_PTR(-EINVAL); + return NULL; + } +#endif + err = fscrypt_setup_filename(dir, child, 1, &fname); if (err) { if (err == -ENOENT) @@ -504,7 +618,7 @@ int f2fs_add_regular_entry(struct inode *dir, const struct qstr *new_name, level = 0; slots = GET_DENTRY_SLOTS(new_name->len); - dentry_hash = f2fs_dentry_hash(new_name, NULL); + dentry_hash = f2fs_dentry_hash(dir, new_name, NULL); current_depth = F2FS_I(dir)->i_current_depth; if (F2FS_I(dir)->chash == dentry_hash) { @@ -568,6 +682,11 @@ add_dentry: if (inode) { f2fs_i_pino_write(inode, dir->i_ino); + + /* synchronize inode page's data from inode cache */ + if (is_inode_flag_set(inode, FI_NEW_INODE)) + f2fs_update_inode(inode, page); + f2fs_put_page(page, 1); } @@ -943,3 +1062,50 @@ const struct file_operations f2fs_dir_operations = { .compat_ioctl = f2fs_compat_ioctl, #endif }; + +#ifdef CONFIG_UNICODE +static int f2fs_d_compare(const struct dentry *dentry, unsigned int len, + const char *str, const struct qstr *name) +{ + struct qstr qstr = {.name = str, .len = len }; + + if (!IS_CASEFOLDED(dentry->d_parent->d_inode)) { + if (len != name->len) + return -1; + return memcmp(str, name, len); + } + + return f2fs_ci_compare(dentry->d_parent->d_inode, name, &qstr, false); +} + +static int f2fs_d_hash(const struct dentry *dentry, struct qstr *str) +{ + struct f2fs_sb_info *sbi = F2FS_SB(dentry->d_sb); + const struct unicode_map *um = sbi->s_encoding; + unsigned char *norm; + int len, ret = 0; + + if (!IS_CASEFOLDED(dentry->d_inode)) + return 0; + + norm = f2fs_kmalloc(sbi, PATH_MAX, GFP_ATOMIC); + if (!norm) + return -ENOMEM; + + len = utf8_casefold(um, str, norm, PATH_MAX); + if (len < 0) { + if (f2fs_has_strict_mode(sbi)) + ret = -EINVAL; + goto out; + } + str->hash = full_name_hash(dentry, norm, len); +out: + kvfree(norm); + return ret; +} + +const struct dentry_operations f2fs_dentry_ops = { + .d_hash = f2fs_d_hash, + .d_compare = f2fs_d_compare, +}; +#endif |