Merge branch 'stable-3.2' into pandora-3.2
[pandora-kernel.git] / fs / debugfs / inode.c
index d813d6f..0e0a061 100644 (file)
@@ -380,7 +380,7 @@ EXPORT_SYMBOL_GPL(debugfs_remove);
  */
 void debugfs_remove_recursive(struct dentry *dentry)
 {
-       struct dentry *child, *next, *parent;
+       struct dentry *child, *parent;
 
        if (!dentry)
                return;
@@ -392,31 +392,49 @@ void debugfs_remove_recursive(struct dentry *dentry)
        parent = dentry;
  down:
        mutex_lock(&parent->d_inode->i_mutex);
-       list_for_each_entry_safe(child, next, &parent->d_subdirs, d_u.d_child) {
+ loop:
+       /*
+        * The parent->d_subdirs is protected by the d_lock. Outside that
+        * lock, the child can be unlinked and set to be freed which can
+        * use the d_u.d_child as the rcu head and corrupt this list.
+        */
+       spin_lock(&parent->d_lock);
+       list_for_each_entry(child, &parent->d_subdirs, d_u.d_child) {
                if (!debugfs_positive(child))
                        continue;
 
                /* perhaps simple_empty(child) makes more sense */
                if (!list_empty(&child->d_subdirs)) {
+                       spin_unlock(&parent->d_lock);
                        mutex_unlock(&parent->d_inode->i_mutex);
                        parent = child;
                        goto down;
                }
- up:
+
+               spin_unlock(&parent->d_lock);
+
                if (!__debugfs_remove(child, parent))
                        simple_release_fs(&debugfs_mount, &debugfs_mount_count);
+
+               /*
+                * The parent->d_lock protects agaist child from unlinking
+                * from d_subdirs. When releasing the parent->d_lock we can
+                * no longer trust that the next pointer is valid.
+                * Restart the loop. We'll skip this one with the
+                * debugfs_positive() check.
+                */
+               goto loop;
        }
+       spin_unlock(&parent->d_lock);
 
        mutex_unlock(&parent->d_inode->i_mutex);
        child = parent;
        parent = parent->d_parent;
        mutex_lock(&parent->d_inode->i_mutex);
 
-       if (child != dentry) {
-               next = list_entry(child->d_u.d_child.next, struct dentry,
-                                       d_u.d_child);
-               goto up;
-       }
+       if (child != dentry)
+               /* go up */
+               goto loop;
 
        if (!__debugfs_remove(child, parent))
                simple_release_fs(&debugfs_mount, &debugfs_mount_count);