Commit 770bfad8 authored by David Howells's avatar David Howells Committed by Trond Myklebust
Browse files

NFS: Add dentry materialisation op



The attached patch adds a new directory cache management function that prepares
a disconnected anonymous function to be connected into the dentry tree. The
anonymous dentry is transferred the name and parentage from another dentry.

The following changes were made in [try #2]:

 (*) d_materialise_dentry() now switches the parentage of the two nodes around
     correctly when one or other of them is self-referential.

The following changes were made in [try #7]:

 (*) d_instantiate_unique() has had the interior part split out as function
     __d_instantiate_unique(). Callers of this latter function must be holding
     the appropriate locks.

 (*) _d_rehash() has been added as a wrapper around __d_rehash() to call it
     with the most obvious hash list (the one from the name). d_rehash() now
     calls _d_rehash().

 (*) d_materialise_dentry() is now __d_materialise_dentry() and is static.

 (*) d_materialise_unique() added to perform the combination of d_find_alias(),
     d_materialise_dentry() and d_add_unique() that the NFS client was doing
     twice, all within a single dcache_lock critical section. This reduces the
     number of times two different spinlocks were being accessed.

The following further changes were made:

 (*) Add the dentries onto their parents d_subdirs lists.

Signed-Off-By: default avatarDavid Howells <dhowells@redhat.com>
Signed-off-by: default avatarTrond Myklebust <Trond.Myklebust@netapp.com>
parent 979df72e
Loading
Loading
Loading
Loading
+150 −14
Original line number Diff line number Diff line
@@ -828,17 +828,19 @@ void d_instantiate(struct dentry *entry, struct inode * inode)
 * (or otherwise set) by the caller to indicate that it is now
 * in use by the dcache.
 */
struct dentry *d_instantiate_unique(struct dentry *entry, struct inode *inode)
static struct dentry *__d_instantiate_unique(struct dentry *entry,
					     struct inode *inode)
{
	struct dentry *alias;
	int len = entry->d_name.len;
	const char *name = entry->d_name.name;
	unsigned int hash = entry->d_name.hash;

	BUG_ON(!list_empty(&entry->d_alias));
	spin_lock(&dcache_lock);
	if (!inode)
		goto do_negative;
	if (!inode) {
		entry->d_inode = NULL;
		return NULL;
	}

	list_for_each_entry(alias, &inode->i_dentry, d_alias) {
		struct qstr *qstr = &alias->d_name;

@@ -851,19 +853,35 @@ struct dentry *d_instantiate_unique(struct dentry *entry, struct inode *inode)
		if (memcmp(qstr->name, name, len))
			continue;
		dget_locked(alias);
		spin_unlock(&dcache_lock);
		BUG_ON(!d_unhashed(alias));
		iput(inode);
		return alias;
	}

	list_add(&entry->d_alias, &inode->i_dentry);
do_negative:
	entry->d_inode = inode;
	fsnotify_d_instantiate(entry, inode);
	return NULL;
}

struct dentry *d_instantiate_unique(struct dentry *entry, struct inode *inode)
{
	struct dentry *result;

	BUG_ON(!list_empty(&entry->d_alias));

	spin_lock(&dcache_lock);
	result = __d_instantiate_unique(entry, inode);
	spin_unlock(&dcache_lock);

	if (!result) {
		security_d_instantiate(entry, inode);
		return NULL;
	}

	BUG_ON(!d_unhashed(result));
	iput(inode);
	return result;
}

EXPORT_SYMBOL(d_instantiate_unique);

/**
@@ -1235,6 +1253,11 @@ static void __d_rehash(struct dentry * entry, struct hlist_head *list)
 	hlist_add_head_rcu(&entry->d_hash, list);
}

static void _d_rehash(struct dentry * entry)
{
	__d_rehash(entry, d_hash(entry->d_parent, entry->d_name.hash));
}

/**
 * d_rehash	- add an entry back to the hash
 * @entry: dentry to add to the hash
@@ -1244,11 +1267,9 @@ static void __d_rehash(struct dentry * entry, struct hlist_head *list)
 
void d_rehash(struct dentry * entry)
{
	struct hlist_head *list = d_hash(entry->d_parent, entry->d_name.hash);

	spin_lock(&dcache_lock);
	spin_lock(&entry->d_lock);
	__d_rehash(entry, list);
	_d_rehash(entry);
	spin_unlock(&entry->d_lock);
	spin_unlock(&dcache_lock);
}
@@ -1386,6 +1407,120 @@ void d_move(struct dentry * dentry, struct dentry * target)
	spin_unlock(&dcache_lock);
}

/*
 * Prepare an anonymous dentry for life in the superblock's dentry tree as a
 * named dentry in place of the dentry to be replaced.
 */
static void __d_materialise_dentry(struct dentry *dentry, struct dentry *anon)
{
	struct dentry *dparent, *aparent;

	switch_names(dentry, anon);
	do_switch(dentry->d_name.len, anon->d_name.len);
	do_switch(dentry->d_name.hash, anon->d_name.hash);

	dparent = dentry->d_parent;
	aparent = anon->d_parent;

	dentry->d_parent = (aparent == anon) ? dentry : aparent;
	list_del(&dentry->d_u.d_child);
	if (!IS_ROOT(dentry))
		list_add(&dentry->d_u.d_child, &dentry->d_parent->d_subdirs);
	else
		INIT_LIST_HEAD(&dentry->d_u.d_child);

	anon->d_parent = (dparent == dentry) ? anon : dparent;
	list_del(&anon->d_u.d_child);
	if (!IS_ROOT(anon))
		list_add(&anon->d_u.d_child, &anon->d_parent->d_subdirs);
	else
		INIT_LIST_HEAD(&anon->d_u.d_child);

	anon->d_flags &= ~DCACHE_DISCONNECTED;
}

/**
 * d_materialise_unique - introduce an inode into the tree
 * @dentry: candidate dentry
 * @inode: inode to bind to the dentry, to which aliases may be attached
 *
 * Introduces an dentry into the tree, substituting an extant disconnected
 * root directory alias in its place if there is one
 */
struct dentry *d_materialise_unique(struct dentry *dentry, struct inode *inode)
{
	struct dentry *alias, *actual;

	BUG_ON(!d_unhashed(dentry));

	spin_lock(&dcache_lock);

	if (!inode) {
		actual = dentry;
		dentry->d_inode = NULL;
		goto found_lock;
	}

	/* See if a disconnected directory already exists as an anonymous root
	 * that we should splice into the tree instead */
	if (S_ISDIR(inode->i_mode) && (alias = __d_find_alias(inode, 1))) {
		spin_lock(&alias->d_lock);

		/* Is this a mountpoint that we could splice into our tree? */
		if (IS_ROOT(alias))
			goto connect_mountpoint;

		if (alias->d_name.len == dentry->d_name.len &&
		    alias->d_parent == dentry->d_parent &&
		    memcmp(alias->d_name.name,
			   dentry->d_name.name,
			   dentry->d_name.len) == 0)
			goto replace_with_alias;

		spin_unlock(&alias->d_lock);

		/* Doh! Seem to be aliasing directories for some reason... */
		dput(alias);
	}

	/* Add a unique reference */
	actual = __d_instantiate_unique(dentry, inode);
	if (!actual)
		actual = dentry;
	else if (unlikely(!d_unhashed(actual)))
		goto shouldnt_be_hashed;

found_lock:
	spin_lock(&actual->d_lock);
found:
	_d_rehash(actual);
	spin_unlock(&actual->d_lock);
	spin_unlock(&dcache_lock);

	if (actual == dentry) {
		security_d_instantiate(dentry, inode);
		return NULL;
	}

	iput(inode);
	return actual;

	/* Convert the anonymous/root alias into an ordinary dentry */
connect_mountpoint:
	__d_materialise_dentry(dentry, alias);

	/* Replace the candidate dentry with the alias in the tree */
replace_with_alias:
	__d_drop(alias);
	actual = alias;
	goto found;

shouldnt_be_hashed:
	spin_unlock(&dcache_lock);
	BUG();
	goto shouldnt_be_hashed;
}

/**
 * d_path - return the path of a dentry
 * @dentry: dentry to report
@@ -1784,6 +1919,7 @@ EXPORT_SYMBOL(d_instantiate);
EXPORT_SYMBOL(d_invalidate);
EXPORT_SYMBOL(d_lookup);
EXPORT_SYMBOL(d_move);
EXPORT_SYMBOL_GPL(d_materialise_unique);
EXPORT_SYMBOL(d_path);
EXPORT_SYMBOL(d_prune_aliases);
EXPORT_SYMBOL(d_rehash);
+1 −0
Original line number Diff line number Diff line
@@ -221,6 +221,7 @@ static inline int dname_external(struct dentry *dentry)
 */
extern void d_instantiate(struct dentry *, struct inode *);
extern struct dentry * d_instantiate_unique(struct dentry *, struct inode *);
extern struct dentry * d_materialise_unique(struct dentry *, struct inode *);
extern void d_delete(struct dentry *);

/* allocate/de-allocate */