libfs: Attempt exact-match comparison first during casefolded lookup
authorGabriel Krisman Bertazi <krisman@suse.de>
Wed, 24 Jan 2024 18:13:40 +0000 (15:13 -0300)
committerGabriel Krisman Bertazi <krisman@suse.de>
Tue, 27 Feb 2024 21:55:25 +0000 (16:55 -0500)
Casefolded comparisons are (obviously) way more costly than a simple
memcmp.  Try the case-sensitive comparison first, falling-back to the
case-insensitive lookup only when needed.  This allows any exact-match
lookup to complete without having to walk the utf8 trie.

Note that, for strict mode, generic_ci_d_compare used to reject an
invalid UTF-8 string, which would now be considered valid if it
exact-matches the disk-name.  But, if that is the case, the filesystem
is corrupt.  More than that, it really doesn't matter in practice,
because the name-under-lookup will have already been rejected by
generic_ci_d_hash and we won't even get here.

The memcmp is safe under RCU because we are operating on str/len instead
of dentry->d_name directly, and the caller guarantees their consistency
between each other in __d_lookup_rcu_op_compare.

Link: https://lore.kernel.org/r/87ttn2sip7.fsf_-_@mailhost.krisman.be
Suggested-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Gabriel Krisman Bertazi <krisman@suse.de>
fs/libfs.c

index eec6031b0155442eab924ce946c2295c711105d2..306a0510b7dc253d4b0b5b1510dd537440460822 100644 (file)
@@ -1704,16 +1704,28 @@ bool is_empty_dir_inode(struct inode *inode)
 static int generic_ci_d_compare(const struct dentry *dentry, unsigned int len,
                                const char *str, const struct qstr *name)
 {
-       const struct dentry *parent = READ_ONCE(dentry->d_parent);
-       const struct inode *dir = READ_ONCE(parent->d_inode);
-       const struct super_block *sb = dentry->d_sb;
-       const struct unicode_map *um = sb->s_encoding;
-       struct qstr qstr = QSTR_INIT(str, len);
+       const struct dentry *parent;
+       const struct inode *dir;
        char strbuf[DNAME_INLINE_LEN];
-       int ret;
+       struct qstr qstr;
+
+       /*
+        * Attempt a case-sensitive match first. It is cheaper and
+        * should cover most lookups, including all the sane
+        * applications that expect a case-sensitive filesystem.
+        *
+        * This comparison is safe under RCU because the caller
+        * guarantees the consistency between str and len. See
+        * __d_lookup_rcu_op_compare() for details.
+        */
+       if (len == name->len && !memcmp(str, name->name, len))
+               return 0;
 
+       parent = READ_ONCE(dentry->d_parent);
+       dir = READ_ONCE(parent->d_inode);
        if (!dir || !IS_CASEFOLDED(dir))
-               goto fallback;
+               return 1;
+
        /*
         * If the dentry name is stored in-line, then it may be concurrently
         * modified by a rename.  If this happens, the VFS will eventually retry
@@ -1724,20 +1736,14 @@ static int generic_ci_d_compare(const struct dentry *dentry, unsigned int len,
        if (len <= DNAME_INLINE_LEN - 1) {
                memcpy(strbuf, str, len);
                strbuf[len] = 0;
-               qstr.name = strbuf;
+               str = strbuf;
                /* prevent compiler from optimizing out the temporary buffer */
                barrier();
        }
-       ret = utf8_strncasecmp(um, name, &qstr);
-       if (ret >= 0)
-               return ret;
+       qstr.len = len;
+       qstr.name = str;
 
-       if (sb_has_strict_encoding(sb))
-               return -EINVAL;
-fallback:
-       if (len != name->len)
-               return 1;
-       return !!memcmp(str, name->name, len);
+       return utf8_strncasecmp(dentry->d_sb->s_encoding, name, &qstr);
 }
 
 /**