mirror of
https://github.com/torvalds/linux.git
synced 2026-10-25 08:16:03 +01:00
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:
parent
17c7d109d8
commit
33d76519f0
51
fs/dcache.c
51
fs/dcache.c
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 *);
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user