link_path_walk(): simplify stack handling

We use nd->stack to store two things: pinning down the symlinks
we are resolving and resuming the name traversal when a nested
symlink is finished.

Currently, nd->depth is used to keep track of both.  It's 0 when
we call link_path_walk() for the first time (for the pathname
itself) and 1 on all subsequent calls (for trailing symlinks,
if any).  That's fine, as far as pinning symlinks goes - when
handling a trailing symlink, the string we are interpreting
is the body of symlink pinned down in nd->stack[0].  It's
rather inconvenient with respect to handling nested symlinks,
though - when we run out of a string we are currently interpreting,
we need to decide whether it's a nested symlink (in which case
we need to pick the string saved back when we started to interpret
that nested symlink and resume its traversal) or not (in which
case we are done with link_path_walk()).

Current solution is a bit of a kludge - in handling of trailing symlink
(in lookup_last() and open_last_lookups() we clear nd->stack[0].name.
That allows link_path_walk() to use the following rules when
running out of a string to interpret:
	* if nd->depth is zero, we are at the end of pathname itself.
	* if nd->depth is positive, check the saved string; for
nested symlink it will be non-NULL, for trailing symlink - NULL.

It works, but it's rather non-obvious.  Note that we have two sets:
the set of symlinks currently being traversed and the set of postponed
pathname tails.  The former is stored in nd->stack[0..nd->depth-1].link
and it's valid throught the pathname resolution; the latter is valid only
during an individual call of link_path_walk() and it occupies
nd->stack[0..nd->depth-1].name for the first call of link_path_walk() and
nd->stack[1..nd->depth-1].name for subsequent ones.  The kludge is basically
a way to recognize the second set becoming empty.

The things get simpler if we keep track of the second set's size
explicitly and always store it in nd->stack[0..depth-1].name.
We access the second set only inside link_path_walk(), so its
size can live in a local variable; that way the check becomes
trivial without the need of that kludge.

Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
This commit is contained in:
Al Viro 2020-02-23 22:04:15 -05:00
parent b1a8197240
commit d8d4611a4f

View File

@ -2120,6 +2120,7 @@ static inline u64 hash_name(const void *salt, const char *name)
*/ */
static int link_path_walk(const char *name, struct nameidata *nd) static int link_path_walk(const char *name, struct nameidata *nd)
{ {
int depth = 0; // depth <= nd->depth
int err; int err;
nd->last_type = LAST_ROOT; nd->last_type = LAST_ROOT;
@ -2182,14 +2183,11 @@ static int link_path_walk(const char *name, struct nameidata *nd)
} while (unlikely(*name == '/')); } while (unlikely(*name == '/'));
if (unlikely(!*name)) { if (unlikely(!*name)) {
OK: OK:
/* pathname body, done */ /* pathname or trailing symlink, done */
if (!nd->depth) if (!depth)
return 0;
name = nd->stack[nd->depth - 1].name;
/* trailing symlink, done */
if (!name)
return 0; return 0;
/* last component of nested symlink */ /* last component of nested symlink */
name = nd->stack[--depth].name;
link = walk_component(nd, 0); link = walk_component(nd, 0);
} else { } else {
/* not the last component */ /* not the last component */
@ -2199,7 +2197,7 @@ static int link_path_walk(const char *name, struct nameidata *nd)
if (IS_ERR(link)) if (IS_ERR(link))
return PTR_ERR(link); return PTR_ERR(link);
/* a symlink to follow */ /* a symlink to follow */
nd->stack[nd->depth - 1].name = name; nd->stack[depth++].name = name;
name = link; name = link;
continue; continue;
} }
@ -2324,7 +2322,6 @@ static inline const char *lookup_last(struct nameidata *nd)
link = walk_component(nd, WALK_TRAILING); link = walk_component(nd, WALK_TRAILING);
if (link) { if (link) {
nd->flags |= LOOKUP_PARENT; nd->flags |= LOOKUP_PARENT;
nd->stack[0].name = NULL;
} }
return link; return link;
} }
@ -3280,7 +3277,6 @@ static const char *do_last(struct nameidata *nd,
if (unlikely(res)) { if (unlikely(res)) {
nd->flags |= LOOKUP_PARENT; nd->flags |= LOOKUP_PARENT;
nd->flags &= ~(LOOKUP_OPEN|LOOKUP_CREATE|LOOKUP_EXCL); nd->flags &= ~(LOOKUP_OPEN|LOOKUP_CREATE|LOOKUP_EXCL);
nd->stack[0].name = NULL;
return res; return res;
} }