summaryrefslogtreecommitdiff
path: root/fs/namei.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2024-06-16 13:06:56 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2024-06-19 12:35:57 -0700
commitba848a77c90800cb686a5c8cf725e9bdfdcccfc2 (patch)
tree6c196ff73b114e4b57d0fd95c7d3c3eca36e862d /fs/namei.c
parent631e1a710c0489e083d4f1276f6de787a5cf08fb (diff)
vfs: link_path_walk: do '.' and '..' detection while hashing
Instead of loading the name again to detect '.' and '..', just use the fact that we already had the masked last word available when as we created the name hash. Which is exactly what we'd then test for. Dealing with big-endian word ordering needs a bit of care, particularly since we have the byte-at-a-time loop as a fallback that doesn't do BE word loads. But not a big deal. Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'fs/namei.c')
-rw-r--r--fs/namei.c68
1 files changed, 46 insertions, 22 deletions
diff --git a/fs/namei.c b/fs/namei.c
index 40929d3cdf76..928cbc856d5b 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -2163,9 +2163,11 @@ EXPORT_SYMBOL(hashlen_string);
/*
* Calculate the length and hash of the path component, and
- * return the "hash_len" as the result.
+ * return the length as the result.
*/
-static inline unsigned long hash_name(struct nameidata *nd, const char *name)
+static inline unsigned long hash_name(struct nameidata *nd,
+ const char *name,
+ unsigned long *lastword)
{
unsigned long a = 0, b, x = 0, y = (unsigned long)nd->path.dentry;
unsigned long adata, bdata, mask, len;
@@ -2185,7 +2187,9 @@ inside:
adata = prep_zero_mask(a, adata, &constants);
bdata = prep_zero_mask(b, bdata, &constants);
mask = create_zero_mask(adata | bdata);
- x ^= a & zero_bytemask(mask);
+ a &= zero_bytemask(mask);
+ *lastword = a;
+ x ^= a;
len += find_zero(mask);
nd->last.hash = fold_hash(x, y);
@@ -2193,6 +2197,15 @@ inside:
return len;
}
+/*
+ * Note that the 'last' word is always zero-masked, but
+ * was loaded as a possibly big-endian word.
+ */
+#ifdef __BIG_ENDIAN
+ #define LAST_WORD_IS_DOT (0x2eul << (BITS_PER_LONG-8))
+ #define LAST_WORD_IS_DOTDOT (0x2e2eul << (BITS_PER_LONG-16))
+#endif
+
#else /* !CONFIG_DCACHE_WORD_ACCESS: Slow, byte-at-a-time version */
/* Return the hash of a string of known length */
@@ -2225,17 +2238,19 @@ EXPORT_SYMBOL(hashlen_string);
* We know there's a real path component here of at least
* one character.
*/
-static inline unsigned long hash_name(struct nameidata *nd, const char *name)
+static inline unsigned long hash_name(struct nameidata *nd, const char *name, unsigned long *lastword)
{
unsigned long hash = init_name_hash(nd->path.dentry);
- unsigned long len = 0, c;
+ unsigned long len = 0, c, last = 0;
c = (unsigned char)*name;
do {
+ last = (last << 8) + c;
len++;
hash = partial_name_hash(c, hash);
c = (unsigned char)name[len];
} while (c && c != '/');
+ *lastword = last;
nd->last.hash = end_name_hash(hash);
nd->last.len = len;
return len;
@@ -2243,6 +2258,11 @@ static inline unsigned long hash_name(struct nameidata *nd, const char *name)
#endif
+#ifndef LAST_WORD_IS_DOT
+ #define LAST_WORD_IS_DOT 0x2e
+ #define LAST_WORD_IS_DOTDOT 0x2e2e
+#endif
+
/*
* Name resolution.
* This is the basic name resolution function, turning a pathname into
@@ -2271,8 +2291,8 @@ static int link_path_walk(const char *name, struct nameidata *nd)
for(;;) {
struct mnt_idmap *idmap;
const char *link;
+ unsigned long lastword;
unsigned int len;
- int type;
idmap = mnt_idmap(nd->path.mnt);
err = may_lookup(idmap, nd);
@@ -2280,25 +2300,29 @@ static int link_path_walk(const char *name, struct nameidata *nd)
return err;
nd->last.name = name;
- len = hash_name(nd, name);
+ len = hash_name(nd, name, &lastword);
name += len;
- type = LAST_NORM;
- /* 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;
- }
- } else {
- type = LAST_DOT;
- }
- }
- nd->last_type = type;
- if (likely(type == LAST_NORM)) {
- struct dentry *parent = nd->path.dentry;
+ switch(lastword) {
+ case LAST_WORD_IS_DOTDOT:
+ if (len != 2)
+ goto normal;
+ nd->last_type = LAST_DOTDOT;
+ nd->state |= ND_JUMPED;
+ break;
+
+ case LAST_WORD_IS_DOT:
+ if (len != 1)
+ goto normal;
+ nd->last_type = LAST_DOT;
+ break;
+
+ default:
+ normal:
+ nd->last_type = LAST_NORM;
nd->state &= ~ND_JUMPED;
+
+ struct dentry *parent = nd->path.dentry;
if (unlikely(parent->d_flags & DCACHE_OP_HASH)) {
err = parent->d_op->d_hash(parent, &nd->last);
if (err < 0)