@@ -32,6 +32,7 @@
#include <linux/bit_spinlock.h>
#include <linux/rculist_bl.h>
#include <linux/list_lru.h>
+#include <linux/unicode.h>
#include "internal.h"
#include "mount.h"
@@ -247,7 +248,19 @@ static inline int dentry_cmp(const struct dentry *dentry, const unsigned char *c
* be no NUL in the ct/tcount data)
*/
const unsigned char *cs = READ_ONCE(dentry->d_name.name);
+#ifdef CONFIG_UNICODE
+ struct inode *parent = dentry->d_parent->d_inode;
+ if (unlikely(needs_casefold(parent))) {
+ const struct qstr n1 = QSTR_INIT(cs, tcount);
+ const struct qstr n2 = QSTR_INIT(ct, tcount);
+ int result = utf8_strncasecmp(dentry->d_sb->s_encoding,
+ &n1, &n2);
+
+ if (result >= 0 || sb_has_enc_strict_mode(dentry->d_sb))
+ return result;
+ }
+#endif
return dentry_string_cmp(cs, ct, tcount);
}
@@ -2406,7 +2419,22 @@ struct dentry *d_hash_and_lookup(struct dentry *dir, struct qstr *name)
* calculate the standard hash first, as the d_op->d_hash()
* routine may choose to leave the hash value unchanged.
*/
+#ifdef CONFIG_UNICODE
+ unsigned char *hname = NULL;
+ int hlen = name->len;
+
+ if (IS_CASEFOLDED(dir->d_inode)) {
+ hname = kmalloc(PATH_MAX, GFP_ATOMIC);
+ if (!hname)
+ return ERR_PTR(-ENOMEM);
+ hlen = utf8_casefold(dir->d_sb->s_encoding,
+ name, hname, PATH_MAX);
+ }
+ name->hash = full_name_hash(dir, hname ?: name->name, hlen);
+ kfree(hname);
+#else
name->hash = full_name_hash(dir, name->name, name->len);
+#endif
if (dir->d_flags & DCACHE_OP_HASH) {
int err = dir->d_op->d_hash(dir, name);
if (unlikely(err < 0))
@@ -39,6 +39,7 @@
#include <linux/bitops.h>
#include <linux/init_task.h>
#include <linux/uaccess.h>
+#include <linux/unicode.h>
#include "internal.h"
#include "mount.h"
@@ -2055,6 +2056,10 @@ static inline u64 hash_name(const void *salt, const char *name)
static int link_path_walk(const char *name, struct nameidata *nd)
{
int err;
+#ifdef CONFIG_UNICODE
+ char *hname = NULL;
+ int hlen = 0;
+#endif
if (IS_ERR(name))
return PTR_ERR(name);
@@ -2071,9 +2076,22 @@ static int link_path_walk(const char *name, struct nameidata *nd)
err = may_lookup(nd);
if (err)
return err;
-
+#ifdef CONFIG_UNICODE
+ if (needs_casefold(nd->path.dentry->d_inode)) {
+ struct qstr str = QSTR_INIT(name, PATH_MAX);
+
+ hname = kmalloc(PATH_MAX, GFP_ATOMIC);
+ if (!hname)
+ return -ENOMEM;
+ hlen = utf8_casefold(nd->path.dentry->d_sb->s_encoding,
+ &str, hname, PATH_MAX);
+ }
+ hash_len = hash_name(nd->path.dentry, hname ?: name);
+ kfree(hname);
+ hname = NULL;
+#else
hash_len = hash_name(nd->path.dentry, name);
-
+#endif
type = LAST_NORM;
if (name[0] == '.') switch (hashlen_len(hash_len)) {
case 2:
@@ -2445,9 +2463,26 @@ EXPORT_SYMBOL(vfs_path_lookup);
static int lookup_one_len_common(const char *name, struct dentry *base,
int len, struct qstr *this)
{
+#ifdef CONFIG_UNICODE
+ char *hname = NULL;
+ int hlen = len;
+
+ if (needs_casefold(base->d_inode)) {
+ struct qstr str = QSTR_INIT(name, len);
+
+ hname = kmalloc(PATH_MAX, GFP_ATOMIC);
+ if (!hname)
+ return -ENOMEM;
+ hlen = utf8_casefold(base->d_sb->s_encoding,
+ &str, hname, PATH_MAX);
+ }
+ this->hash = full_name_hash(base, hname ?: name, hlen);
+ kfree(hname);
+#else
+ this->hash = full_name_hash(base, name, len);
+#endif
this->name = name;
this->len = len;
- this->hash = full_name_hash(base, name, len);
if (!len)
return -EACCES;
@@ -1382,6 +1382,12 @@ extern int send_sigurg(struct fown_struct *fown);
#define SB_ACTIVE (1<<30)
#define SB_NOUSER (1<<31)
+/* These flags relate to encoding and casefolding */
+#define SB_ENC_STRICT_MODE_FL (1 << 0)
+
+#define sb_has_enc_strict_mode(sb) \
+ (sb->s_encoding_flags & SB_ENC_STRICT_MODE_FL)
+
/*
* Umount options
*/
@@ -1449,6 +1455,10 @@ struct super_block {
#endif
#ifdef CONFIG_FS_VERITY
const struct fsverity_operations *s_vop;
+#endif
+#ifdef CONFIG_UNICODE
+ struct unicode_map *s_encoding;
+ __u16 s_encoding_flags;
#endif
struct hlist_bl_head s_roots; /* alternate root dentries for NFS */
struct list_head s_mounts; /* list of mounts; _not_ for fs use */
@@ -4,6 +4,8 @@
#include <linux/init.h>
#include <linux/dcache.h>
+#include <linux/fscrypt.h>
+#include <linux/fs.h>
struct unicode_map {
const char *charset;
@@ -30,4 +32,16 @@ int utf8_casefold(const struct unicode_map *um, const struct qstr *str,
struct unicode_map *utf8_load(const char *version);
void utf8_unload(struct unicode_map *um);
+#ifdef CONFIG_UNICODE
+static inline bool needs_casefold(const struct inode *dir)
+{
+ return IS_CASEFOLDED(dir) && dir->i_sb->s_encoding &&
+ (!IS_ENCRYPTED(dir) || fscrypt_has_encryption_key(dir));
+}
+#else
+static inline bool needs_casefold(const struct inode *dir)
+{
+ return 0;
+}
+#endif
#endif /* _LINUX_UNICODE_H */
Ext4 and F2fs are both using casefolding, and they, along with any other filesystem that adds the feature, will be using identical dentry_ops. Additionally, those dentry ops interfere with the dentry_ops required for fscrypt once we add support for casefolding and encryption. Moving this into the vfs removes code duplication as well as the complication with encryption. Currently this is pretty close to just moving the existing f2fs/ext4 code up a level into the vfs, although there is a lot of room for improvement now. Signed-off-by: Daniel Rosenberg <drosen@google.com> --- fs/dcache.c | 28 ++++++++++++++++++++++++++++ fs/namei.c | 41 ++++++++++++++++++++++++++++++++++++++--- include/linux/fs.h | 10 ++++++++++ include/linux/unicode.h | 14 ++++++++++++++ 4 files changed, 90 insertions(+), 3 deletions(-)