fs/ntfs3: Add option "nocase"
authorKonstantin Komarov <almaz.alexandrovich@paragon-software.com>
Fri, 23 Sep 2022 09:42:18 +0000 (12:42 +0300)
committerKonstantin Komarov <almaz.alexandrovich@paragon-software.com>
Fri, 30 Sep 2022 14:39:47 +0000 (17:39 +0300)
This commit adds mount option and additional functions.

Signed-off-by: Konstantin Komarov <almaz.alexandrovich@paragon-software.com>
fs/ntfs3/index.c
fs/ntfs3/namei.c
fs/ntfs3/ntfs_fs.h
fs/ntfs3/super.c
fs/ntfs3/upcase.c

index 440328147e7e39f011a0ee7b11bbfaf7f93b4d45..613036f9c6e6696a072008131104c2926b55fb7a 100644 (file)
@@ -47,7 +47,7 @@ static int cmp_fnames(const void *key1, size_t l1, const void *key2, size_t l2,
        if (l2 < fsize2)
                return -1;
 
-       both_case = f2->type != FILE_NAME_DOS /*&& !sbi->options.nocase*/;
+       both_case = f2->type != FILE_NAME_DOS && !sbi->options->nocase;
        if (!l1) {
                const struct le_str *s2 = (struct le_str *)&f2->name_len;
 
index bc22cc321a74bba0110a0030386af6396e9e63e9..315763eb05ff2874be42017555aad018f9919263 100644 (file)
@@ -7,6 +7,7 @@
 
 #include <linux/fs.h>
 #include <linux/nls.h>
+#include <linux/ctype.h>
 
 #include "debug.h"
 #include "ntfs.h"
@@ -355,6 +356,138 @@ struct dentry *ntfs3_get_parent(struct dentry *child)
        return ERR_PTR(-ENOENT);
 }
 
+/*
+ * dentry_operations::d_hash
+ */
+static int ntfs_d_hash(const struct dentry *dentry, struct qstr *name)
+{
+       struct ntfs_sb_info *sbi;
+       const char *n = name->name;
+       unsigned int len = name->len;
+       unsigned long hash;
+       struct cpu_str *uni;
+       unsigned int c;
+       int err;
+
+       /* First try fast implementation. */
+       hash = init_name_hash(dentry);
+
+       for (;;) {
+               if (!len--) {
+                       name->hash = end_name_hash(hash);
+                       return 0;
+               }
+
+               c = *n++;
+               if (c >= 0x80)
+                       break;
+
+               hash = partial_name_hash(toupper(c), hash);
+       }
+
+       /*
+        * Try slow way with current upcase table
+        */
+       uni = __getname();
+       if (!uni)
+               return -ENOMEM;
+
+       sbi = dentry->d_sb->s_fs_info;
+
+       err = ntfs_nls_to_utf16(sbi, name->name, name->len, uni, NTFS_NAME_LEN,
+                               UTF16_HOST_ENDIAN);
+       if (err < 0)
+               goto out;
+
+       if (!err) {
+               err = -EINVAL;
+               goto out;
+       }
+
+       hash = ntfs_names_hash(uni->name, uni->len, sbi->upcase,
+                              init_name_hash(dentry));
+       name->hash = end_name_hash(hash);
+       err = 0;
+
+out:
+       __putname(uni);
+       return err;
+}
+
+/*
+ * dentry_operations::d_compare
+ */
+static int ntfs_d_compare(const struct dentry *dentry, unsigned int len1,
+                         const char *str, const struct qstr *name)
+{
+       struct ntfs_sb_info *sbi;
+       int ret;
+       const char *n1 = str;
+       const char *n2 = name->name;
+       unsigned int len2 = name->len;
+       unsigned int lm = min(len1, len2);
+       unsigned char c1, c2;
+       struct cpu_str *uni1, *uni2;
+
+       /* First try fast implementation. */
+       for (;;) {
+               if (!lm--) {
+                       ret = len1 == len2 ? 0 : 1;
+                       goto out;
+               }
+
+               if ((c1 = *n1++) == (c2 = *n2++))
+                       continue;
+
+               if (c1 >= 0x80 || c2 >= 0x80)
+                       break;
+
+               if (toupper(c1) != toupper(c2)) {
+                       ret = 1;
+                       goto out;
+               }
+       }
+
+       /*
+        * Try slow way with current upcase table
+        */
+       sbi = dentry->d_sb->s_fs_info;
+       uni1 = __getname();
+       if (!uni1)
+               return -ENOMEM;
+
+       ret = ntfs_nls_to_utf16(sbi, str, len1, uni1, NTFS_NAME_LEN,
+                               UTF16_HOST_ENDIAN);
+       if (ret < 0)
+               goto out;
+
+       if (!ret) {
+               ret = -EINVAL;
+               goto out;
+       }
+
+       uni2 = Add2Ptr(uni1, 2048);
+
+       ret = ntfs_nls_to_utf16(sbi, name->name, name->len, uni2, NTFS_NAME_LEN,
+                               UTF16_HOST_ENDIAN);
+       if (ret < 0)
+               goto out;
+
+       if (!ret) {
+               ret = -EINVAL;
+               goto out;
+       }
+
+       ret = !ntfs_cmp_names(uni1->name, uni1->len, uni2->name, uni2->len,
+                             sbi->upcase, false)
+                     ? 0
+                     : 1;
+
+out:
+       __putname(uni1);
+       return ret;
+}
+
 // clang-format off
 const struct inode_operations ntfs_dir_inode_operations = {
        .lookup         = ntfs_lookup,
@@ -382,4 +515,10 @@ const struct inode_operations ntfs_special_inode_operations = {
        .get_acl        = ntfs_get_acl,
        .set_acl        = ntfs_set_acl,
 };
+
+const struct dentry_operations ntfs_dentry_ops = {
+       .d_hash         = ntfs_d_hash,
+       .d_compare      = ntfs_d_compare,
+};
+
 // clang-format on
index cd680ada50abf594e3b3fc1728a78731b54770ff..6c1c7ef3b2d6abb62f118644b5e836e1d1136064 100644 (file)
@@ -101,6 +101,7 @@ struct ntfs_mount_options {
        unsigned force : 1; /* RW mount dirty volume. */
        unsigned noacsrules : 1; /* Exclude acs rules. */
        unsigned prealloc : 1; /* Preallocate space when file is growing. */
+       unsigned nocase : 1; /* case insensitive. */
 };
 
 /* Special value to unpack and deallocate. */
@@ -721,6 +722,7 @@ struct dentry *ntfs3_get_parent(struct dentry *child);
 
 extern const struct inode_operations ntfs_dir_inode_operations;
 extern const struct inode_operations ntfs_special_inode_operations;
+extern const struct dentry_operations ntfs_dentry_ops;
 
 /* Globals from record.c */
 int mi_get(struct ntfs_sb_info *sbi, CLST rno, struct mft_inode **mi);
@@ -840,6 +842,8 @@ int ntfs_cmp_names(const __le16 *s1, size_t l1, const __le16 *s2, size_t l2,
                   const u16 *upcase, bool bothcase);
 int ntfs_cmp_names_cpu(const struct cpu_str *uni1, const struct le_str *uni2,
                       const u16 *upcase, bool bothcase);
+unsigned long ntfs_names_hash(const u16 *name, size_t len, const u16 *upcase,
+                             unsigned long hash);
 
 /* globals from xattr.c */
 #ifdef CONFIG_NTFS3_FS_POSIX_ACL
index 87d9eabf9847691689252d0a4b92a4358a1178b0..d72a27abf1c837f9be47ebd4e7ee538a674d6c33 100644 (file)
@@ -253,6 +253,7 @@ enum Opt {
        Opt_iocharset,
        Opt_prealloc,
        Opt_noacsrules,
+       Opt_nocase,
        Opt_err,
 };
 
@@ -272,6 +273,7 @@ static const struct fs_parameter_spec ntfs_fs_parameters[] = {
        fsparam_flag_no("showmeta",             Opt_showmeta),
        fsparam_flag_no("prealloc",             Opt_prealloc),
        fsparam_flag_no("acsrules",             Opt_noacsrules),
+       fsparam_flag_no("nocase",               Opt_nocase),
        fsparam_string("iocharset",             Opt_iocharset),
        {}
 };
@@ -383,6 +385,9 @@ static int ntfs_fs_parse_param(struct fs_context *fc,
        case Opt_noacsrules:
                opts->noacsrules = result.negated ? 1 : 0;
                break;
+       case Opt_nocase:
+               opts->nocase = result.negated ? 1 : 0;
+               break;
        default:
                /* Should not be here unless we forget add case. */
                return -EINVAL;
@@ -936,6 +941,7 @@ static int ntfs_fill_super(struct super_block *sb, struct fs_context *fc)
        sb->s_export_op = &ntfs_export_ops;
        sb->s_time_gran = NTFS_TIME_GRAN; // 100 nsec
        sb->s_xattr = ntfs_xattr_handlers;
+       sb->s_d_op = sbi->options->nocase ? &ntfs_dentry_ops : NULL;
 
        sbi->options->nls = ntfs_load_nls(sbi->options->nls_name);
        if (IS_ERR(sbi->options->nls)) {
index b5e8256fd710d5263b2be07b92df14cb8eea3c35..7681eefacb4b42dbdf28e436841148f8a6f04012 100644 (file)
@@ -102,3 +102,15 @@ case_insentive:
        diff2 = l1 - l2;
        return diff2 ? diff2 : diff1;
 }
+
+/* Helper function for ntfs_d_hash. */
+unsigned long ntfs_names_hash(const u16 *name, size_t len, const u16 *upcase,
+                             unsigned long hash)
+{
+       while (len--) {
+               unsigned int c = upcase_unicode_char(upcase, *name++);
+               hash = partial_name_hash(c, hash);
+       }
+
+       return hash;
+}