VFS: add d_duplicate()

Occasionally a single operation can require two sub-operations on the
same name, and it is important that a d_alloc_parallel() (once that can
be run unlocked) does not create another dentry with the same name
between the operations.

Two examples:
1/ rename where the target name (a positive dentry) needs to be
  "silly-renamed" to a temporary name so it will remain available on the
  server (NFS and AFS).  Here the same name needs to be the subject
  of one rename, and the target of another.
2/ rename where the subject needs to be replaced with a white-out
  (shmemfs).  Here the same name need to be the subject of a rename
  and the target of a mknod()

In both cases the original dentry is renamed to something else, and a
replacement is instantiated, possibly as the target of d_move(), possibly
by d_instantiate().

Currently d_alloc() is used to create the dentry and the exclusive lock
on the parent ensures no other dentry is created.  When
d_alloc_parallel() is moved out of the parent lock, this will no longer
be sufficient.  In particular if the original is renamed away before the
new is instantiated, there is a window where d_alloc_parallel() could
create another name.  "silly-rename" does work in this order.  shmemfs
whiteout doesn't open this hole but is essentially the same pattern and
should use the same approach.

The new d_duplicate() creates an in-lookup dentry with the same name as
the original dentry, which must be hashed.  There is no need to check if
an in-lookup dentry exists with the same name as d_alloc_parallel() will
never try add one while the hashed dentry exists.  Once the new
in-lookup is created, d_alloc_parallel() will find it and wait for it to
complete, then use it.

Signed-off-by: NeilBrown <neil@brown.name>
Link: https://patch.msgid.link/20260904215142.1060510-5-neilb@ownmail.net
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
This commit is contained in:
NeilBrown 2026-09-05 07:48:13 +10:00 committed by Christian Brauner
parent 17c7d109d8
commit 33d76519f0
No known key found for this signature in database
GPG Key ID: 91C61BC06578DCA2
2 changed files with 52 additions and 0 deletions

View File

@ -2000,6 +2000,57 @@ struct dentry *d_alloc(struct dentry * parent, const struct qstr *name)
}
EXPORT_SYMBOL(d_alloc);
/**
* d_duplicate - duplicate a dentry for combined atomic operation
* @dentry: the dentry to duplicate
*
* Some rename operations need to be combined with another operation
* inside the filesystem.
* 1/ A cluster filesystem when renaming to an in-use file might need to
* first "silly-rename" that target out of the way before the main rename
* 2/ A filesystem that supports white-out might want to create a whiteout
* in place of the file being moved.
*
* For this they need two dentries which temporarily have the same name,
* before one is renamed. d_duplicate() provides for this. Given a
* positive hashed dentry, it creates a second in-lookup dentry.
* Because the original dentry exists, no other thread will try to
* create an in-lookup dentry, so there can be no race in this create.
*
* The caller should d_move() the original to a new name, often via a
* rename request, and should call d_lookup_done() on the newly created
* dentry. If the new is instantiated then the old MUST either be moved
* or dropped.
*
* Parent must be locked.
*
* Returns: an in-lookup dentry, or -ENOMEM.
*/
struct dentry *d_duplicate(struct dentry *dentry)
{
unsigned int hash = dentry->d_name.hash;
struct dentry *parent = dentry->d_parent;
struct hlist_bl_head *b = in_lookup_hash(parent, hash);
struct dentry *new = __d_alloc(parent->d_sb, &dentry->d_name);
if (unlikely(!new))
return ERR_PTR(-ENOMEM);
new->d_flags |= DCACHE_PAR_LOOKUP;
spin_lock(&parent->d_lock);
new->d_parent = dget_dlock(parent);
hlist_add_head(&new->d_sib, &parent->d_children);
if (parent->d_flags & DCACHE_DISCONNECTED)
new->d_flags |= DCACHE_DISCONNECTED;
spin_unlock(&parent->d_lock);
hlist_bl_lock(b);
hlist_bl_add_head(&new->d_in_lookup_hash, b);
hlist_bl_unlock(b);
return new;
}
EXPORT_SYMBOL(d_duplicate);
struct dentry *d_alloc_anon(struct super_block *sb)
{
return __d_alloc(sb, NULL);

View File

@ -259,6 +259,7 @@ extern struct dentry * d_alloc_anon(struct super_block *);
extern struct dentry * d_alloc_parallel(struct dentry *, const struct qstr *);
extern struct dentry * d_alloc_trylock(struct dentry *, struct qstr *);
extern struct dentry * d_splice_alias(struct inode *, struct dentry *);
struct dentry *d_duplicate(struct dentry *dentry);
/* weird procfs mess; *NOT* exported */
extern struct dentry * d_splice_alias_ops(struct inode *, struct dentry *,
const struct dentry_operations *);