Implement generic caseless lookup based on the mount flags using the charset library. Signed-off-by: Gabriel Krisman Bertazi <krisman@xxxxxxxxxxxxxxx> --- fs/ext4/namei.c | 47 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c index 3373017e9315..ad7330b82740 100644 --- a/fs/ext4/namei.c +++ b/fs/ext4/namei.c @@ -1259,7 +1259,8 @@ static void dx_insert_block(struct dx_frame *frame, u32 hash, ext4_lblk_t block) * * Return: %true if the directory entry matches, otherwise %false. */ -static inline bool ext4_match(const struct ext4_filename *fname, +static inline bool ext4_match(struct nls_table *charset, + const struct ext4_filename *fname, const struct ext4_dir_entry_2 *de, bool casefold) { @@ -1273,7 +1274,8 @@ static inline bool ext4_match(const struct ext4_filename *fname, #ifdef CONFIG_EXT4_FS_ENCRYPTION f.crypto_buf = fname->crypto_buf; #endif - return fscrypt_match_name(&f, de->name, de->name_len); + return fscrypt_charset_match_name(charset, &f, de->name, + de->name_len, casefold); } /* @@ -1287,6 +1289,7 @@ int ext4_search_dir(struct buffer_head *bh, char *search_buf, int buf_size, struct ext4_dir_entry_2 * de; char * dlimit; int de_len; + struct super_block *sb = dir->i_sb; de = (struct ext4_dir_entry_2 *)search_buf; dlimit = search_buf + buf_size; @@ -1294,7 +1297,9 @@ int ext4_search_dir(struct buffer_head *bh, char *search_buf, int buf_size, /* this code is executed quadratically often */ /* do minimal checking `by hand' */ if ((char *) de + de->name_len <= dlimit && - ext4_match(fname, de, flags & LOOKUP_CASEFOLD)) { + ext4_match(EXT4_SB(sb)->encoding, fname, de, + flags & LOOKUP_CASEFOLD)) { + /* found a match - just to be sure, do * a full check */ if (ext4_check_dir_entry(dir, NULL, de, bh, bh->b_data, @@ -1395,11 +1400,13 @@ static struct buffer_head * ext4_find_entry (struct inode *dir, if (is_dx(dir)) { ret = ext4_dx_find_entry(dir, &fname, res_dir, flags); /* - * On success, or if the error was file not found, - * return. Otherwise, fall back to doing a search the - * old fashioned way. + * On success, or if the file cannot be found, return. + * If an error occurred, or if the file was not found + * but we can do case-insensitive lookups, fall back to + * the linear search. */ - if (!IS_ERR(ret) || PTR_ERR(ret) != ERR_BAD_DX_DIR) + if ((!IS_ERR(ret) && (ret || !(flags & LOOKUP_CASEFOLD))) || + (IS_ERR(ret) && PTR_ERR(ret) != ERR_BAD_DX_DIR)) goto cleanup_and_exit; dxtrace(printk(KERN_DEBUG "ext4_find_entry: dx failed, " "falling back\n")); @@ -1592,6 +1599,29 @@ static struct dentry *ext4_lookup(struct inode *dir, struct dentry *dentry, unsi return ERR_PTR(-EPERM); } } + + if (flags & LOOKUP_CASEFOLD) { + struct dentry *new; + struct qstr ciname; + char *name; + + if (!de) + return d_add_ci_negative_dentry(dentry); + + name = kmalloc((sizeof(char) * de->name_len) + 1, + GFP_NOFS); + if (!name) + return ERR_PTR(-ENOMEM); + + memcpy(name, de->name, de->name_len); + name[de->name_len] = '\0'; + ciname.len = de->name_len; + ciname.name = name; + new = d_add_ci(dentry, inode, &ciname); + kfree(name); + return new; + } + return d_splice_alias(inode, dentry); } @@ -1795,6 +1825,7 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode, int nlen, rlen; unsigned int offset = 0; char *top; + struct super_block *sb = dir->i_sb; de = (struct ext4_dir_entry_2 *)buf; top = buf + buf_size - reclen; @@ -1802,7 +1833,7 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode, if (ext4_check_dir_entry(dir, NULL, de, bh, buf, buf_size, offset)) return -EFSCORRUPTED; - if (ext4_match(fname, de, false)) + if (ext4_match(EXT4_SB(sb)->encoding, fname, de, false)) return -EEXIST; nlen = EXT4_DIR_REC_LEN(de->name_len); rlen = ext4_rec_len_from_disk(de->rec_len, buf_size); -- 2.17.0