Blob Blame History Raw
From: NeilBrown <neilb@suse.com>
Subject: getcwd: Close race with d_move called by lustre
Patch-mainline: Not yet, under development
References: bsc#1052593

When lustre invalidates a dentry (e.g. do to a recalled lock) and then
revalidates it, ll_splice_alias() will call d_move() to move the old alias
to the name of a new one.
This will d_drop then d_rehash the old dentry, creating a small window
when the dentry in unhashed.
If getcwd is run at this time, it might incorrectly think that
the dentry really is unhashed, and so return ENOENT.

This is a bug in lustre (it shouldn't call d_move()) but we can work
around it in getcwd by  taking the d_lock to avoid the race.
First we test without the lock as the common case does not involve
any race.  If we find the the dentry appears to be unhashed, we take
the lock and check again.

Signed-off-by: Neil Brown <neilb@suse.com>

---
 fs/dcache.c |   19 ++++++++++++++++---
 1 file changed, 16 insertions(+), 3 deletions(-)

--- a/fs/dcache.c
+++ b/fs/dcache.c
@@ -3228,6 +3228,19 @@ char *d_absolute_path(const struct path
 	return res;
 }
 
+static inline bool d_unlinked_safe(struct dentry *de)
+{
+	bool ret;
+	ret = d_unlinked(de);
+	if (unlikely(ret)) {
+		/* Maybe racing with d_move called from lustre */
+		spin_lock(&de->d_lock);
+		ret = d_unlinked(de);
+		spin_unlock(&de->d_lock);
+	}
+	return ret;
+}
+
 /*
  * same as __d_path but appends "(deleted)" for unlinked files.
  */
@@ -3236,7 +3249,7 @@ static int path_with_deleted(const struc
 			     char **buf, int *buflen)
 {
 	prepend(buf, buflen, "\0", 1);
-	if (d_unlinked(path->dentry)) {
+	if (d_unlinked_safe(path->dentry)) {
 		int error = prepend(buf, buflen, " (deleted)", 10);
 		if (error)
 			return error;
@@ -3400,7 +3413,7 @@ char *dentry_path(struct dentry *dentry,
 	char *p = NULL;
 	char *retval;
 
-	if (d_unlinked(dentry)) {
+	if (d_unlinked_safe(dentry)) {
 		p = buf + buflen;
 		if (prepend(&p, &buflen, "//deleted", 10) != 0)
 			goto Elong;
@@ -3457,7 +3470,7 @@ SYSCALL_DEFINE2(getcwd, char __user *, b
 	get_fs_root_and_pwd_rcu(current->fs, &root, &pwd);
 
 	error = -ENOENT;
-	if (!d_unlinked(pwd.dentry)) {
+	if (!d_unlinked_safe(pwd.dentry)) {
 		unsigned long len;
 		char *cwd = page + PATH_MAX;
 		int buflen = PATH_MAX;