]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
vfs: link_path_walk: simplify name hash flow
authorLinus Torvalds <torvalds@linux-foundation.org>
Sun, 16 Jun 2024 00:55:00 +0000 (17:55 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Wed, 19 Jun 2024 19:35:56 +0000 (12:35 -0700)
This is one of those hot functions in path walking, and it's doing
things in just the wrong order that causes slightly unnecessary extra
work.

Move the name pointer update and the setting of 'nd->last' up a bit, so
that the (unlikely) filesystem-specific hashing can run on them in
place, instead of having to set up a copy on the stack and copy things
back and forth.

Because even when the hashing is not run, it causes the stack frame of
the function to be bigger to hold the unnecessary temporary copy.

This also means that we never then reference the full "hashlen" field
after calculating it, and can clarify the code with just using the
length part.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
fs/namei.c

index 37fb0a8aa09a05e93637c984e665158006a50ef8..f3c91d8ae140d35822c4a7b062affa2ce5d8af3f 100644 (file)
@@ -2266,7 +2266,7 @@ static int link_path_walk(const char *name, struct nameidata *nd)
        for(;;) {
                struct mnt_idmap *idmap;
                const char *link;
-               u64 hash_len;
+               unsigned int len;
                int type;
 
                idmap = mnt_idmap(nd->path.mnt);
@@ -2274,37 +2274,34 @@ static int link_path_walk(const char *name, struct nameidata *nd)
                if (err)
                        return err;
 
-               hash_len = hash_name(nd->path.dentry, name);
+               nd->last.name = name;
+               nd->last.hash_len = hash_name(nd->path.dentry, name);
+               len = hashlen_len(nd->last.hash_len);
+               name += len;
 
                type = LAST_NORM;
-               if (name[0] == '.') switch (hashlen_len(hash_len)) {
-                       case 2:
-                               if (name[1] == '.') {
+               /* We know len is at least 1, so compare against 2 */
+               if (len <= 2 && name[-1] == '.') {
+                       if (len == 2) {
+                               if (name[-2] == '.') {
                                        type = LAST_DOTDOT;
                                        nd->state |= ND_JUMPED;
                                }
-                               break;
-                       case 1:
+                       } else {
                                type = LAST_DOT;
+                       }
                }
+               nd->last_type = type;
                if (likely(type == LAST_NORM)) {
                        struct dentry *parent = nd->path.dentry;
                        nd->state &= ~ND_JUMPED;
                        if (unlikely(parent->d_flags & DCACHE_OP_HASH)) {
-                               struct qstr this = { { .hash_len = hash_len }, .name = name };
-                               err = parent->d_op->d_hash(parent, &this);
+                               err = parent->d_op->d_hash(parent, &nd->last);
                                if (err < 0)
                                        return err;
-                               hash_len = this.hash_len;
-                               name = this.name;
                        }
                }
 
-               nd->last.hash_len = hash_len;
-               nd->last.name = name;
-               nd->last_type = type;
-
-               name += hashlen_len(hash_len);
                if (!*name)
                        goto OK;
                /*