|
@@ -1469,23 +1469,21 @@ static void switch_names(struct dentry *dentry, struct dentry *target)
|
|
|
* deleted it.
|
|
|
*/
|
|
|
|
|
|
-/**
|
|
|
- * d_move - move a dentry
|
|
|
+/*
|
|
|
+ * d_move_locked - move a dentry
|
|
|
* @dentry: entry to move
|
|
|
* @target: new dentry
|
|
|
*
|
|
|
* Update the dcache to reflect the move of a file name. Negative
|
|
|
* dcache entries should not be moved in this way.
|
|
|
*/
|
|
|
-
|
|
|
-void d_move(struct dentry * dentry, struct dentry * target)
|
|
|
+static void d_move_locked(struct dentry * dentry, struct dentry * target)
|
|
|
{
|
|
|
struct hlist_head *list;
|
|
|
|
|
|
if (!dentry->d_inode)
|
|
|
printk(KERN_WARNING "VFS: moving negative dcache entry\n");
|
|
|
|
|
|
- spin_lock(&dcache_lock);
|
|
|
write_seqlock(&rename_lock);
|
|
|
/*
|
|
|
* XXXX: do we really need to take target->d_lock?
|
|
@@ -1536,9 +1534,83 @@ already_unhashed:
|
|
|
fsnotify_d_move(dentry);
|
|
|
spin_unlock(&dentry->d_lock);
|
|
|
write_sequnlock(&rename_lock);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * d_move - move a dentry
|
|
|
+ * @dentry: entry to move
|
|
|
+ * @target: new dentry
|
|
|
+ *
|
|
|
+ * Update the dcache to reflect the move of a file name. Negative
|
|
|
+ * dcache entries should not be moved in this way.
|
|
|
+ */
|
|
|
+
|
|
|
+void d_move(struct dentry * dentry, struct dentry * target)
|
|
|
+{
|
|
|
+ spin_lock(&dcache_lock);
|
|
|
+ d_move_locked(dentry, target);
|
|
|
spin_unlock(&dcache_lock);
|
|
|
}
|
|
|
|
|
|
+/*
|
|
|
+ * Helper that returns 1 if p1 is a parent of p2, else 0
|
|
|
+ */
|
|
|
+static int d_isparent(struct dentry *p1, struct dentry *p2)
|
|
|
+{
|
|
|
+ struct dentry *p;
|
|
|
+
|
|
|
+ for (p = p2; p->d_parent != p; p = p->d_parent) {
|
|
|
+ if (p->d_parent == p1)
|
|
|
+ return 1;
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * This helper attempts to cope with remotely renamed directories
|
|
|
+ *
|
|
|
+ * It assumes that the caller is already holding
|
|
|
+ * dentry->d_parent->d_inode->i_mutex and the dcache_lock
|
|
|
+ *
|
|
|
+ * Note: If ever the locking in lock_rename() changes, then please
|
|
|
+ * remember to update this too...
|
|
|
+ *
|
|
|
+ * On return, dcache_lock will have been unlocked.
|
|
|
+ */
|
|
|
+static struct dentry *__d_unalias(struct dentry *dentry, struct dentry *alias)
|
|
|
+{
|
|
|
+ struct mutex *m1 = NULL, *m2 = NULL;
|
|
|
+ struct dentry *ret;
|
|
|
+
|
|
|
+ /* If alias and dentry share a parent, then no extra locks required */
|
|
|
+ if (alias->d_parent == dentry->d_parent)
|
|
|
+ goto out_unalias;
|
|
|
+
|
|
|
+ /* Check for loops */
|
|
|
+ ret = ERR_PTR(-ELOOP);
|
|
|
+ if (d_isparent(alias, dentry))
|
|
|
+ goto out_err;
|
|
|
+
|
|
|
+ /* See lock_rename() */
|
|
|
+ ret = ERR_PTR(-EBUSY);
|
|
|
+ if (!mutex_trylock(&dentry->d_sb->s_vfs_rename_mutex))
|
|
|
+ goto out_err;
|
|
|
+ m1 = &dentry->d_sb->s_vfs_rename_mutex;
|
|
|
+ if (!mutex_trylock(&alias->d_parent->d_inode->i_mutex))
|
|
|
+ goto out_err;
|
|
|
+ m2 = &alias->d_parent->d_inode->i_mutex;
|
|
|
+out_unalias:
|
|
|
+ d_move_locked(alias, dentry);
|
|
|
+ ret = alias;
|
|
|
+out_err:
|
|
|
+ spin_unlock(&dcache_lock);
|
|
|
+ if (m2)
|
|
|
+ mutex_unlock(m2);
|
|
|
+ if (m1)
|
|
|
+ mutex_unlock(m1);
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
/*
|
|
|
* Prepare an anonymous dentry for life in the superblock's dentry tree as a
|
|
|
* named dentry in place of the dentry to be replaced.
|
|
@@ -1581,7 +1653,7 @@ static void __d_materialise_dentry(struct dentry *dentry, struct dentry *anon)
|
|
|
*/
|
|
|
struct dentry *d_materialise_unique(struct dentry *dentry, struct inode *inode)
|
|
|
{
|
|
|
- struct dentry *alias, *actual;
|
|
|
+ struct dentry *actual;
|
|
|
|
|
|
BUG_ON(!d_unhashed(dentry));
|
|
|
|
|
@@ -1593,26 +1665,27 @@ struct dentry *d_materialise_unique(struct dentry *dentry, struct inode *inode)
|
|
|
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);
|
|
|
+ if (S_ISDIR(inode->i_mode)) {
|
|
|
+ struct dentry *alias;
|
|
|
+
|
|
|
+ /* Does an aliased dentry already exist? */
|
|
|
+ alias = __d_find_alias(inode, 0);
|
|
|
+ if (alias) {
|
|
|
+ actual = alias;
|
|
|
+ /* Is this an anonymous mountpoint that we could splice
|
|
|
+ * into our tree? */
|
|
|
+ if (IS_ROOT(alias)) {
|
|
|
+ spin_lock(&alias->d_lock);
|
|
|
+ __d_materialise_dentry(dentry, alias);
|
|
|
+ __d_drop(alias);
|
|
|
+ goto found;
|
|
|
+ }
|
|
|
+ /* Nope, but we must(!) avoid directory aliasing */
|
|
|
+ actual = __d_unalias(dentry, alias);
|
|
|
+ if (IS_ERR(actual))
|
|
|
+ dput(alias);
|
|
|
+ goto out_nolock;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/* Add a unique reference */
|
|
@@ -1628,7 +1701,7 @@ found:
|
|
|
_d_rehash(actual);
|
|
|
spin_unlock(&actual->d_lock);
|
|
|
spin_unlock(&dcache_lock);
|
|
|
-
|
|
|
+out_nolock:
|
|
|
if (actual == dentry) {
|
|
|
security_d_instantiate(dentry, inode);
|
|
|
return NULL;
|
|
@@ -1637,16 +1710,6 @@ found:
|
|
|
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();
|