ext4: Support case-insensitive file name lookups
[linux-2.6-microblaze.git] / fs / ext4 / namei.c
index 980166a..e917830 100644 (file)
@@ -35,6 +35,7 @@
 #include <linux/buffer_head.h>
 #include <linux/bio.h>
 #include <linux/iversion.h>
+#include <linux/unicode.h>
 #include "ext4.h"
 #include "ext4_jbd2.h"
 
@@ -629,7 +630,7 @@ static struct stats dx_show_leaf(struct inode *dir,
                                }
                                if (!fscrypt_has_encryption_key(dir)) {
                                        /* Directory is not encrypted */
-                                       ext4fs_dirhash(de->name,
+                                       ext4fs_dirhash(dir, de->name,
                                                de->name_len, &h);
                                        printk("%*.s:(U)%x.%u ", len,
                                               name, h.hash,
@@ -662,8 +663,8 @@ static struct stats dx_show_leaf(struct inode *dir,
                                                name = fname_crypto_str.name;
                                                len = fname_crypto_str.len;
                                        }
-                                       ext4fs_dirhash(de->name, de->name_len,
-                                                      &h);
+                                       ext4fs_dirhash(dir, de->name,
+                                                      de->name_len, &h);
                                        printk("%*.s:(E)%x.%u ", len, name,
                                               h.hash, (unsigned) ((char *) de
                                                                   - base));
@@ -673,7 +674,7 @@ static struct stats dx_show_leaf(struct inode *dir,
 #else
                                int len = de->name_len;
                                char *name = de->name;
-                               ext4fs_dirhash(de->name, de->name_len, &h);
+                               ext4fs_dirhash(dir, de->name, de->name_len, &h);
                                printk("%*.s:%x.%u ", len, name, h.hash,
                                       (unsigned) ((char *) de - base));
 #endif
@@ -762,7 +763,7 @@ dx_probe(struct ext4_filename *fname, struct inode *dir,
                hinfo->hash_version += EXT4_SB(dir->i_sb)->s_hash_unsigned;
        hinfo->seed = EXT4_SB(dir->i_sb)->s_hash_seed;
        if (fname && fname_name(fname))
-               ext4fs_dirhash(fname_name(fname), fname_len(fname), hinfo);
+               ext4fs_dirhash(dir, fname_name(fname), fname_len(fname), hinfo);
        hash = hinfo->hash;
 
        if (root->info.unused_flags & 1) {
@@ -1008,7 +1009,7 @@ static int htree_dirblock_to_tree(struct file *dir_file,
                        /* silently ignore the rest of the block */
                        break;
                }
-               ext4fs_dirhash(de->name, de->name_len, hinfo);
+               ext4fs_dirhash(dir, de->name, de->name_len, hinfo);
                if ((hinfo->hash < start_hash) ||
                    ((hinfo->hash == start_hash) &&
                     (hinfo->minor_hash < start_minor_hash)))
@@ -1197,7 +1198,7 @@ static int dx_make_map(struct inode *dir, struct ext4_dir_entry_2 *de,
 
        while ((char *) de < base + blocksize) {
                if (de->name_len && de->inode) {
-                       ext4fs_dirhash(de->name, de->name_len, &h);
+                       ext4fs_dirhash(dir, de->name, de->name_len, &h);
                        map_tail--;
                        map_tail->hash = h.hash;
                        map_tail->offs = ((char *) de - base)>>2;
@@ -1252,15 +1253,52 @@ static void dx_insert_block(struct dx_frame *frame, u32 hash, ext4_lblk_t block)
        dx_set_count(entries, count + 1);
 }
 
+#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 ext4_ci_compare(const struct inode *parent, const struct qstr *name,
+                   const struct qstr *entry)
+{
+       const struct ext4_sb_info *sbi = EXT4_SB(parent->i_sb);
+       const struct unicode_map *um = sbi->s_encoding;
+       int ret;
+
+       ret = utf8_strncasecmp(um, name, entry);
+       if (ret < 0) {
+               /* Handle invalid character sequence as either an error
+                * or as an opaque byte sequence.
+                */
+               if (ext4_has_strict_mode(sbi))
+                       return -EINVAL;
+
+               if (name->len != entry->len)
+                       return 1;
+
+               return !!memcmp(name->name, entry->name, name->len);
+       }
+
+       return ret;
+}
+#endif
+
 /*
  * Test whether a directory entry matches the filename being searched for.
  *
  * Return: %true if the directory entry matches, otherwise %false.
  */
-static inline bool ext4_match(const struct ext4_filename *fname,
+static inline bool ext4_match(const struct inode *parent,
+                             const struct ext4_filename *fname,
                              const struct ext4_dir_entry_2 *de)
 {
        struct fscrypt_name f;
+#ifdef CONFIG_UNICODE
+       const struct qstr entry = {.name = de->name, .len = de->name_len};
+#endif
 
        if (!de->inode)
                return false;
@@ -1270,6 +1308,12 @@ static inline bool ext4_match(const struct ext4_filename *fname,
 #ifdef CONFIG_FS_ENCRYPTION
        f.crypto_buf = fname->crypto_buf;
 #endif
+
+#ifdef CONFIG_UNICODE
+       if (EXT4_SB(parent->i_sb)->s_encoding && IS_CASEFOLDED(parent))
+               return (ext4_ci_compare(parent, fname->usr_fname, &entry) == 0);
+#endif
+
        return fscrypt_match_name(&f, de->name, de->name_len);
 }
 
@@ -1290,7 +1334,7 @@ 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)) {
+                   ext4_match(dir, fname, de)) {
                        /* found a match - just to be sure, do
                         * a full check */
                        if (ext4_check_dir_entry(dir, NULL, de, bh, bh->b_data,
@@ -1588,6 +1632,17 @@ static struct dentry *ext4_lookup(struct inode *dir, struct dentry *dentry, unsi
                        return ERR_PTR(-EPERM);
                }
        }
+
+#ifdef CONFIG_UNICODE
+       if (!inode && IS_CASEFOLDED(dir)) {
+               /* Eventually we want to call d_add_ci(dentry, NULL)
+                * for negative dentries in the encoding case as
+                * well.  For now, prevent the negative dentry
+                * from being cached.
+                */
+               return NULL;
+       }
+#endif
        return d_splice_alias(inode, dentry);
 }
 
@@ -1798,7 +1853,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))
+               if (ext4_match(dir, fname, de))
                        return -EEXIST;
                nlen = EXT4_DIR_REC_LEN(de->name_len);
                rlen = ext4_rec_len_from_disk(de->rec_len, buf_size);
@@ -1983,7 +2038,7 @@ static int make_indexed_dir(handle_t *handle, struct ext4_filename *fname,
        if (fname->hinfo.hash_version <= DX_HASH_TEA)
                fname->hinfo.hash_version += EXT4_SB(dir->i_sb)->s_hash_unsigned;
        fname->hinfo.seed = EXT4_SB(dir->i_sb)->s_hash_seed;
-       ext4fs_dirhash(fname_name(fname), fname_len(fname), &fname->hinfo);
+       ext4fs_dirhash(dir, fname_name(fname), fname_len(fname), &fname->hinfo);
 
        memset(frames, 0, sizeof(frames));
        frame = frames;
@@ -2036,6 +2091,7 @@ static int ext4_add_entry(handle_t *handle, struct dentry *dentry,
        struct ext4_dir_entry_2 *de;
        struct ext4_dir_entry_tail *t;
        struct super_block *sb;
+       struct ext4_sb_info *sbi;
        struct ext4_filename fname;
        int     retval;
        int     dx_fallback=0;
@@ -2047,10 +2103,17 @@ static int ext4_add_entry(handle_t *handle, struct dentry *dentry,
                csum_size = sizeof(struct ext4_dir_entry_tail);
 
        sb = dir->i_sb;
+       sbi = EXT4_SB(sb);
        blocksize = sb->s_blocksize;
        if (!dentry->d_name.len)
                return -EINVAL;
 
+#ifdef CONFIG_UNICODE
+       if (ext4_has_strict_mode(sbi) && IS_CASEFOLDED(dir) &&
+           utf8_validate(sbi->s_encoding, &dentry->d_name))
+               return -EINVAL;
+#endif
+
        retval = ext4_fname_setup_filename(dir, &dentry->d_name, 0, &fname);
        if (retval)
                return retval;
@@ -2975,6 +3038,17 @@ static int ext4_rmdir(struct inode *dir, struct dentry *dentry)
        ext4_update_dx_flag(dir);
        ext4_mark_inode_dirty(handle, dir);
 
+#ifdef CONFIG_UNICODE
+       /* VFS negative dentries are incompatible with Encoding and
+        * Case-insensitiveness. Eventually we'll want avoid
+        * invalidating the dentries here, alongside with returning the
+        * negative dentries at ext4_lookup(), when it is better
+        * supported by the VFS for the CI case.
+        */
+       if (IS_CASEFOLDED(dir))
+               d_invalidate(dentry);
+#endif
+
 end_rmdir:
        brelse(bh);
        if (handle)
@@ -3044,6 +3118,17 @@ static int ext4_unlink(struct inode *dir, struct dentry *dentry)
        inode->i_ctime = current_time(inode);
        ext4_mark_inode_dirty(handle, inode);
 
+#ifdef CONFIG_UNICODE
+       /* VFS negative dentries are incompatible with Encoding and
+        * Case-insensitiveness. Eventually we'll want avoid
+        * invalidating the dentries here, alongside with returning the
+        * negative dentries at ext4_lookup(), when it is  better
+        * supported by the VFS for the CI case.
+        */
+       if (IS_CASEFOLDED(dir))
+               d_invalidate(dentry);
+#endif
+
 end_unlink:
        brelse(bh);
        if (handle)