patch-2.2.0-pre8 linux/fs/namei.c

Next file: linux/fs/nfs/dir.c
Previous file: linux/fs/isofs/namei.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.2.0-pre7/linux/fs/namei.c linux/fs/namei.c
@@ -561,6 +561,20 @@
 }
 
 /*
+ * We need to do a check-parent every time
+ * after we have locked the parent - to verify
+ * that the parent is still our parent and
+ * that we are still hashed onto it..
+ *
+ * This is requied in case two processes race
+ * on removing (or moving) the same entry: the
+ * parent lock will serialize them, but the
+ * other process will be too late..
+ */
+#define check_parent(dir, dentry) \
+	((dir) == (dentry)->d_parent && !list_empty(&dentry->d_hash))
+
+/*
  * Locking the parent is needed to:
  *  - serialize directory operations
  *  - make sure the parent doesn't change from
@@ -577,17 +591,41 @@
 	struct dentry *dir = dget(dentry->d_parent);
 
 	down(&dir->d_inode->i_sem);
+	return dir;
+}
 
-	/* Un-hashed or moved?  Punt if so.. */
-	if (dir != dentry->d_parent || list_empty(&dentry->d_hash)) {
-		if (dir != dentry) {
-			unlock_dir(dir);
-			dir = ERR_PTR(-ENOENT);
+/*
+ * Whee.. Deadlock country. Happily there are only two VFS
+ * operations that do this..
+ */
+static inline void double_lock(struct dentry *d1, struct dentry *d2)
+{
+	struct semaphore *s1 = &d1->d_inode->i_sem;
+	struct semaphore *s2 = &d2->d_inode->i_sem;
+
+	if (s1 != s2) {
+		if ((unsigned long) s1 < (unsigned long) s2) {
+			struct semaphore *tmp = s2;
+			s2 = s1; s1 = tmp;
 		}
+		down(s1);
 	}
-	return dir;
+	down(s2);
 }
 
+static inline void double_unlock(struct dentry *d1, struct dentry *d2)
+{
+	struct semaphore *s1 = &d1->d_inode->i_sem;
+	struct semaphore *s2 = &d2->d_inode->i_sem;
+
+	up(s1);
+	if (s1 != s2)
+		up(s2);
+	dput(d1);
+	dput(d2);
+}
+
+
 /* 
  * Special case: O_CREAT|O_EXCL implies O_NOFOLLOW for security
  * reasons.
@@ -645,9 +683,20 @@
 			goto exit;
 
 		dir = lock_parent(dentry);
-		error = PTR_ERR(dir);
-		if (IS_ERR(dir))
+		if (!check_parent(dir, dentry)) {
+			/*
+			 * Really nasty race happened. What's the 
+			 * right error code? We had a dentry, but
+			 * before we could use it it was removed
+			 * by somebody else. We could just re-try
+			 * everything, I guess.
+			 *
+			 * ENOENT is definitely wrong.
+			 */
+			error = -ENOENT;
+			unlock_dir(dir);
 			goto exit;
+		}
 
 		/*
 		 * Somebody might have created the file while we
@@ -760,9 +809,9 @@
 		return dentry;
 
 	dir = lock_parent(dentry);
-	retval = dir;
-	if (IS_ERR(dir))
-		goto exit;
+	error = -ENOENT;
+	if (!check_parent(dir, dentry))
+		goto exit_lock;
 
 	error = may_create(dir->d_inode, dentry);
 	if (error)
@@ -779,7 +828,6 @@
 	if (!error)
 		retval = dget(dentry);
 	unlock_dir(dir);
-exit:
 	dput(dentry);
 	return retval;
 }
@@ -835,9 +883,9 @@
 		goto exit;
 
 	dir = lock_parent(dentry);
-	error = PTR_ERR(dir);
-	if (IS_ERR(dir))
-		goto exit_dput;
+	error = -ENOENT;
+	if (!check_parent(dir, dentry))
+		goto exit_lock;
 
 	error = may_create(dir->d_inode, dentry);
 	if (error)
@@ -853,7 +901,6 @@
 
 exit_lock:
 	unlock_dir(dir);
-exit_dput:
 	dput(dentry);
 exit:
 	return error;
@@ -875,39 +922,6 @@
 	return error;
 }
 
-/*
- * Whee.. Deadlock country. Happily there are only two VFS
- * operations that do this..
- */
-static inline void double_lock(struct dentry *d1, struct dentry *d2)
-{
-	struct semaphore *s1 = &d1->d_inode->i_sem;
-	struct semaphore *s2 = &d2->d_inode->i_sem;
-
-	if (s1 != s2) {
-		if ((unsigned long) s1 < (unsigned long) s2) {
-			struct semaphore *tmp = s2;
-			s2 = s1; s1 = tmp;
-		}
-		down(s1);
-	}
-	down(s2);
-}
-
-static inline void double_unlock(struct dentry *d1, struct dentry *d2)
-{
-	struct semaphore *s1 = &d1->d_inode->i_sem;
-	struct semaphore *s2 = &d2->d_inode->i_sem;
-
-	up(s1);
-	if (s1 != s2)
-		up(s2);
-	dput(d1);
-	dput(d2);
-}
-
-
-
 int vfs_rmdir(struct inode *dir, struct dentry *dentry)
 {
 	int error;
@@ -976,7 +990,9 @@
 	dentry->d_count++;
 	double_lock(dir, dentry);
 
-	error = vfs_rmdir(dir->d_inode, dentry);
+	error = -ENOENT;
+	if (check_parent(dir, dentry))
+		error = vfs_rmdir(dir->d_inode, dentry);
 
 	double_unlock(dentry, dir);
 exit_dput:
@@ -1032,14 +1048,11 @@
 		goto exit;
 
 	dir = lock_parent(dentry);
-	error = PTR_ERR(dir);
-	if (IS_ERR(dir))
-		goto exit_dput;
-
-	error = vfs_unlink(dir->d_inode, dentry);
+	error = -ENOENT;
+	if (check_parent(dir, dentry))
+		error = vfs_unlink(dir->d_inode, dentry);
 
         unlock_dir(dir);
-exit_dput:
 	dput(dentry);
 exit:
 	return error;
@@ -1074,9 +1087,9 @@
 		goto exit;
 
 	dir = lock_parent(dentry);
-	error = PTR_ERR(dir);
-	if (IS_ERR(dir))
-		goto exit_dput;
+	error = -ENOENT;
+	if (!check_parent(dir, dentry))
+		goto exit_lock;
 
 	error = may_create(dir->d_inode, dentry);
 	if (error)
@@ -1091,7 +1104,6 @@
 
 exit_lock:
 	unlock_dir(dir);
-exit_dput:
 	dput(dentry);
 exit:
 	return error;
@@ -1145,9 +1157,9 @@
 		goto exit_old;
 
 	dir = lock_parent(new_dentry);
-	error = PTR_ERR(dir);
-	if (IS_ERR(dir))
-		goto exit_new;
+	error = -ENOENT;
+	if (!check_parent(dir, new_dentry))
+		goto exit_lock;
 
 	error = -ENOENT;
 	inode = old_dentry->d_inode;
@@ -1178,7 +1190,6 @@
 
 exit_lock:
 	unlock_dir(dir);
-exit_new:
 	dput(new_dentry);
 exit_old:
 	dput(old_dentry);
@@ -1272,8 +1283,10 @@
 
 	double_lock(new_dir, old_dir);
 
-	error = vfs_rename(old_dir->d_inode, old_dentry,
-			   new_dir->d_inode, new_dentry);
+	error = -ENOENT;
+	if (check_parent(old_dir, old_dentry) && check_parent(new_dir, new_dentry))
+		error = vfs_rename(old_dir->d_inode, old_dentry,
+				   new_dir->d_inode, new_dentry);
 
 	double_unlock(new_dir, old_dir);
 	dput(new_dentry);

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov