diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2024-06-16 13:06:56 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2024-06-19 12:35:57 -0700 |
commit | ba848a77c90800cb686a5c8cf725e9bdfdcccfc2 (patch) | |
tree | 6c196ff73b114e4b57d0fd95c7d3c3eca36e862d /fs/namei.c | |
parent | 631e1a710c0489e083d4f1276f6de787a5cf08fb (diff) | |
download | linux-ba848a77c90800cb686a5c8cf725e9bdfdcccfc2.tar.gz linux-ba848a77c90800cb686a5c8cf725e9bdfdcccfc2.tar.bz2 linux-ba848a77c90800cb686a5c8cf725e9bdfdcccfc2.zip |
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.c | 68 |
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) |