public inbox for linux-kernel@vger.kernel.org
 help / color / mirror / Atom feed
From: Oleg Nesterov <oleg@redhat.com>
To: Steven Rostedt <rostedt@goodmis.org>,
	Masami Hiramatsu <masami.hiramatsu.pt@hitachi.com>,
	Linus Torvalds <torvalds@linux-foundation.org>,
	Al Viro <viro@zeniv.linux.org.uk>,
	Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Alexander Z Lam <azl@google.com>,
	Arnaldo Carvalho de Melo <acme@ghostprotocols.net>,
	David Sharp <dhsharp@google.com>,
	Frederic Weisbecker <fweisbec@gmail.com>,
	Ingo Molnar <mingo@redhat.com>,
	Peter Zijlstra <a.p.zijlstra@chello.nl>,
	Srikar Dronamraju <srikar@linux.vnet.ibm.com>,
	Vaibhav Nagarnaik <vnagarnaik@google.com>,
	"zhangwei(Jovi)" <jovi.zhangwei@huawei.com>,
	linux-kernel@vger.kernel.org
Subject: PATCH? debugfs_remove_recursive() must not rely on list_empty(d_subdirs)
Date: Thu, 25 Jul 2013 21:27:42 +0200	[thread overview]
Message-ID: <20130725192742.GA14060@redhat.com> (raw)
In-Reply-To: <20130724184640.GA21322@redhat.com>

Hello.

debugfs_remove_recursive() looks wrong to me. The patch below is
probably wrong too and it was only slightly tested, but could you
please confirm my understanding?

First of all, the usage of simple_release_fs() doesn't look correct
but please ignore, the patch doesn't try to fix this.

The problem is, debugfs_remove_recursive() wrongly (I think) assumes
that a) !list_empty(d_subdirs) means that this dir should be removed,
and b) if d_subdirs does not becomes empty after __debugfs_remove()
debugfs_remove_recursive() gives up and doesn't even try to remove
other entries.

I think this is wrong, and at least we need the additional
debugfs_positive() check before list_empty() or just simple_empty()
instead.

Test-case. Suppose we have

	dir1/
		dir2/
			file2
		file1

somewhere in debugfs.

Suppose that someone opens dir1/dir2/file2 and keeps it opened.

Now. debugfs_remove_recursive(dir1/dir2) succeeds, and dir1/di2 goes
away.

But debugfs_remove_recursive(dir1) silently fails and doesn't remove
this directory. Because it tries to delete (the already deleted)
dir1/dir2/file2 again and then fails due to "Avoid infinite loop"
logic.

The patch seems to fix the problem. Note also that the new code
tries to remove as much as possible, iow it does not give up if
__debugfs_remove() fails for any reason. However I am not sure
we want this, perhaps it should simply abort and return the err.

To simplify the review, this is how it looks with the patch applied:

	void debugfs_remove_recursive(struct dentry *dentry)
	{
		struct dentry *child, *next, *parent;

		if (IS_ERR_OR_NULL(dentry))
			return;

		parent = dentry->d_parent;
		if (!parent || !parent->d_inode)
			return;

		parent = dentry;
	 down:
		mutex_lock(&parent->d_inode->i_mutex);
		child = list_first_entry(&parent->d_subdirs, struct dentry, d_u.d_child);

		while (&child->d_u.d_child != &parent->d_subdirs) {
			next = list_next_entry(child, d_u.d_child);

			if (!debugfs_positive(child))
				goto next;

			/* XXX: simple_empty(child) instead ? */
			if (!list_empty(&child->d_subdirs)) {
				mutex_unlock(&parent->d_inode->i_mutex);
				parent = child;
				goto down;
			}
	 up:
			__debugfs_remove(child, parent);
	 next:
			child = next;
		}

		mutex_unlock(&parent->d_inode->i_mutex);
		if (parent != dentry) {
			child = parent;
			next = list_next_entry(child, d_u.d_child);
			parent = parent->d_parent;
			mutex_lock(&parent->d_inode->i_mutex);
			goto up;
		}

		parent = dentry->d_parent;
		mutex_lock(&parent->d_inode->i_mutex);
		__debugfs_remove(dentry, parent);
		mutex_unlock(&parent->d_inode->i_mutex);
		simple_release_fs(&debugfs_mount, &debugfs_mount_count);
	}

Oleg.
---
 debugfs/inode.c |   69 +++++++++++++++++++++-----------------------------------
 1 file changed, 26 insertions(+), 43 deletions(-)

--- x/fs/debugfs/inode.c	2013-07-25 19:08:08.000000000 +0200
+++ x/fs/debugfs/inode.c	2013-07-25 21:18:26.000000000 +0200
@@ -532,6 +532,9 @@ void debugfs_remove(struct dentry *dentr
 }
 EXPORT_SYMBOL_GPL(debugfs_remove);
 
+#define list_next_entry(pos, member) \
+	list_entry(pos->member.next, typeof(*pos), member)
+
 /**
  * debugfs_remove_recursive - recursively removes a directory
  * @dentry: a pointer to a the dentry of the directory to be removed.
@@ -546,8 +549,7 @@ EXPORT_SYMBOL_GPL(debugfs_remove);
  */
 void debugfs_remove_recursive(struct dentry *dentry)
 {
-	struct dentry *child;
-	struct dentry *parent;
+	struct dentry *child, *next, *parent;
 
 	if (IS_ERR_OR_NULL(dentry))
 		return;
@@ -557,54 +559,35 @@ void debugfs_remove_recursive(struct den
 		return;
 
 	parent = dentry;
+ down:
 	mutex_lock(&parent->d_inode->i_mutex);
+	child = list_first_entry(&parent->d_subdirs, struct dentry, d_u.d_child);
 
-	while (1) {
-		/*
-		 * When all dentries under "parent" has been removed,
-		 * walk up the tree until we reach our starting point.
-		 */
-		if (list_empty(&parent->d_subdirs)) {
-			mutex_unlock(&parent->d_inode->i_mutex);
-			if (parent == dentry)
-				break;
-			parent = parent->d_parent;
-			mutex_lock(&parent->d_inode->i_mutex);
-		}
-		child = list_entry(parent->d_subdirs.next, struct dentry,
-				d_u.d_child);
- next_sibling:
-
-		/*
-		 * If "child" isn't empty, walk down the tree and
-		 * remove all its descendants first.
-		 */
+	while (&child->d_u.d_child != &parent->d_subdirs) {
+		next = list_next_entry(child, d_u.d_child);
+
+		if (!debugfs_positive(child))
+			goto next;
+
+		/* XXX: simple_empty(child) instead ? */
 		if (!list_empty(&child->d_subdirs)) {
 			mutex_unlock(&parent->d_inode->i_mutex);
 			parent = child;
-			mutex_lock(&parent->d_inode->i_mutex);
-			continue;
+			goto down;
 		}
+ up:
 		__debugfs_remove(child, parent);
-		if (parent->d_subdirs.next == &child->d_u.d_child) {
-			/*
-			 * Try the next sibling.
-			 */
-			if (child->d_u.d_child.next != &parent->d_subdirs) {
-				child = list_entry(child->d_u.d_child.next,
-						   struct dentry,
-						   d_u.d_child);
-				goto next_sibling;
-			}
-
-			/*
-			 * Avoid infinite loop if we fail to remove
-			 * one dentry.
-			 */
-			mutex_unlock(&parent->d_inode->i_mutex);
-			break;
-		}
-		simple_release_fs(&debugfs_mount, &debugfs_mount_count);
+ next:
+ 		child = next;
+	}
+
+	mutex_unlock(&parent->d_inode->i_mutex);
+	if (parent != dentry) {
+		child = parent;
+		next = list_next_entry(child, d_u.d_child);
+		parent = parent->d_parent;
+		mutex_lock(&parent->d_inode->i_mutex);
+		goto up;
 	}
 
 	parent = dentry->d_parent;


  parent reply	other threads:[~2013-07-25 19:34 UTC|newest]

Thread overview: 30+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2013-07-23 20:58 [PATCH 0/6] tracing: open/delete fixes Oleg Nesterov
2013-07-23 20:59 ` [PATCH 1/6] tracing: Turn event/id->i_private into call->event.type Oleg Nesterov
2013-07-24 20:13   ` Steven Rostedt
2013-07-25 14:26     ` Oleg Nesterov
2013-07-23 20:59 ` [PATCH 2/6] tracing: Change event_enable/disable_read() to verify i_private != NULL Oleg Nesterov
2013-07-24 19:37   ` Steven Rostedt
2013-07-23 20:59 ` [PATCH 3/6] tracing: Change event_filter_read/write " Oleg Nesterov
2013-07-24 19:52   ` Steven Rostedt
2013-07-23 20:59 ` [PATCH 4/6] tracing: Change f_start() to take event_mutex and " Oleg Nesterov
2013-07-23 20:59 ` [PATCH 5/6] tracing: Introduce remove_event_file_dir() Oleg Nesterov
2013-07-24 20:01   ` Steven Rostedt
2013-07-23 20:59 ` [PATCH 6/6] tracing: Change remove_event_file_dir() to clear "d_subdirs"->i_private Oleg Nesterov
2013-07-24 20:08   ` Steven Rostedt
2013-07-25 14:18     ` Oleg Nesterov
2013-07-24 18:46 ` [PATCH 0/6] tracing: open/delete fixes Oleg Nesterov
2013-07-25 16:56   ` Oleg Nesterov
2013-07-25 19:27   ` Oleg Nesterov [this message]
2013-07-25 20:04     ` PATCH? debugfs_remove_recursive() must not rely on list_empty(d_subdirs) Oleg Nesterov
2013-07-25 23:43       ` Greg Kroah-Hartman
2013-07-26 15:11         ` [PATCH 0/1] debugfs: " Oleg Nesterov
2013-07-26 15:12           ` Oleg Nesterov
2013-07-26 15:14             ` Oleg Nesterov
2013-07-26 15:12           ` [PATCH 1/1] " Oleg Nesterov
2013-07-26 17:38             ` Greg Kroah-Hartman
2013-07-26 18:40               ` Steven Rostedt
2013-07-26 15:30           ` [PATCH 0/1] " Steven Rostedt
2013-07-26 16:28             ` Greg Kroah-Hartman
2013-07-26 17:38             ` Greg Kroah-Hartman
2013-07-26 10:24       ` Re: PATCH? " Masami Hiramatsu
2013-07-26 14:49         ` Oleg Nesterov

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20130725192742.GA14060@redhat.com \
    --to=oleg@redhat.com \
    --cc=a.p.zijlstra@chello.nl \
    --cc=acme@ghostprotocols.net \
    --cc=azl@google.com \
    --cc=dhsharp@google.com \
    --cc=fweisbec@gmail.com \
    --cc=gregkh@linuxfoundation.org \
    --cc=jovi.zhangwei@huawei.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=masami.hiramatsu.pt@hitachi.com \
    --cc=mingo@redhat.com \
    --cc=rostedt@goodmis.org \
    --cc=srikar@linux.vnet.ibm.com \
    --cc=torvalds@linux-foundation.org \
    --cc=viro@zeniv.linux.org.uk \
    --cc=vnagarnaik@google.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox