vfs: add try_take_dentry_name_snapshot()

take_dentry_name_snapshot() requires the caller to keep the dentry
alive. The external name of a dead dentry has a zero refcount that
never changes, so atomic_inc_not_zero() fails on every retry and the
function never returns.

A caller in RCU walk holds no reference on the dentry. Add a variant
that fails instead of retrying when it cannot get a reference on the
external name, so such a caller can return -ECHILD.

Reviewed-by: Christian Brauner (Amutable) <brauner@kernel.org>
Signed-off-by: Amir Goldstein <amir73il@gmail.com>
This commit is contained in:
Amir Goldstein 2026-10-16 13:21:34 +02:00
parent 65bf000962
commit 15c6bd5cf1
2 changed files with 27 additions and 3 deletions

View File

@ -358,7 +358,8 @@ static inline int dname_external(const struct dentry *dentry)
return dentry->d_name.name != dentry->d_shortname.string;
}
void take_dentry_name_snapshot(struct name_snapshot *name, struct dentry *dentry)
static bool __take_dentry_name_snapshot(struct name_snapshot *name,
struct dentry *dentry, bool retry)
{
unsigned seq;
const unsigned char *s;
@ -375,8 +376,12 @@ void take_dentry_name_snapshot(struct name_snapshot *name, struct dentry *dentry
struct external_name *p;
p = container_of(s, struct external_name, name[0]);
// get a valid reference
if (unlikely(!atomic_inc_not_zero(&p->count)))
goto retry;
if (unlikely(!atomic_inc_not_zero(&p->count))) {
if (retry)
goto retry;
rcu_read_unlock();
return false;
}
name->name.name = s;
}
if (read_seqcount_retry(&dentry->d_seq, seq)) {
@ -384,9 +389,27 @@ void take_dentry_name_snapshot(struct name_snapshot *name, struct dentry *dentry
goto retry;
}
rcu_read_unlock();
return true;
}
void take_dentry_name_snapshot(struct name_snapshot *name, struct dentry *dentry)
{
__take_dentry_name_snapshot(name, dentry, true);
}
EXPORT_SYMBOL(take_dentry_name_snapshot);
/*
* For a caller that holds no reference on @dentry, e.g. in RCU walk.
* The external name of a dead dentry has a zero refcount forever, so
* take_dentry_name_snapshot() would retry without end. Fail instead.
*/
bool try_take_dentry_name_snapshot(struct name_snapshot *name,
struct dentry *dentry)
{
return __take_dentry_name_snapshot(name, dentry, false);
}
EXPORT_SYMBOL(try_take_dentry_name_snapshot);
void release_dentry_name_snapshot(struct name_snapshot *name)
{
if (unlikely(name->name.name != name->inline_name.string)) {

View File

@ -629,6 +629,7 @@ struct name_snapshot {
union shortname_store inline_name;
};
void take_dentry_name_snapshot(struct name_snapshot *, struct dentry *);
bool try_take_dentry_name_snapshot(struct name_snapshot *, struct dentry *);
void release_dentry_name_snapshot(struct name_snapshot *);
static inline struct dentry *d_first_child(const struct dentry *dentry)