|
@@ -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);
|
|
|
- spin_unlock(&dcache_lock);
|
|
|
- security_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 @@ already_unhashed:
|
|
|
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);
|