diff --git a/fs/ntfs3/index.c b/fs/ntfs3/index.c index 440328147e7e..613036f9c6e6 100644 --- a/fs/ntfs3/index.c +++ b/fs/ntfs3/index.c @@ -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; diff --git a/fs/ntfs3/namei.c b/fs/ntfs3/namei.c index bc22cc321a74..315763eb05ff 100644 --- a/fs/ntfs3/namei.c +++ b/fs/ntfs3/namei.c @@ -7,6 +7,7 @@ #include #include +#include #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 diff --git a/fs/ntfs3/ntfs_fs.h b/fs/ntfs3/ntfs_fs.h index cd680ada50ab..6c1c7ef3b2d6 100644 --- a/fs/ntfs3/ntfs_fs.h +++ b/fs/ntfs3/ntfs_fs.h @@ -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 diff --git a/fs/ntfs3/super.c b/fs/ntfs3/super.c index 87d9eabf9847..d72a27abf1c8 100644 --- a/fs/ntfs3/super.c +++ b/fs/ntfs3/super.c @@ -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)) { diff --git a/fs/ntfs3/upcase.c b/fs/ntfs3/upcase.c index b5e8256fd710..7681eefacb4b 100644 --- a/fs/ntfs3/upcase.c +++ b/fs/ntfs3/upcase.c @@ -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; +}