patch-2.1.131 linux/fs/namei.c

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

diff -u --recursive --new-file v2.1.130/linux/fs/namei.c linux/fs/namei.c
@@ -834,6 +834,37 @@
 	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);
+}
+
 static inline int do_rmdir(const char * name)
 {
 	int error;
@@ -845,13 +876,20 @@
 	if (IS_ERR(dentry))
 		goto exit;
 
-	dir = lock_parent(dentry);
-	error = PTR_ERR(dir);
-	if (IS_ERR(dir))
-		goto exit_dput;
+	dir = dget(dentry->d_parent);
 
 	error = -ENOENT;
 	if (!dentry->d_inode)
+		goto exit;
+	/*
+	 * The dentry->d_count stuff confuses d_delete() enough to
+	 * not kill the inode from under us while it is locked. This
+	 * wouldn't be needed, except the dentry semaphore is really
+	 * in the inode, not in the dentry..
+	 */
+	dentry->d_count++;
+	double_lock(dir, dentry);
+	if (dentry->d_parent != dir)
 		goto exit_lock;
 
 	error = -EROFS;
@@ -880,15 +918,35 @@
 
 	DQUOT_INIT(dir->d_inode);
 
-	if (dentry->d_count > 1)
+	/*
+	 * We try to drop the dentry early: we should have
+	 * a usage count of 2 if we're the only user of this
+	 * dentry, and if that is true (possibly after pruning
+	 * the dcache), then we drop the dentry now.
+	 *
+	 * A low-level filesystem can, if it choses, legally
+	 * do a
+	 *
+	 *	if (!list_empty(&dentry->d_hash))
+	 *		return -EBUSY;
+	 *
+	 * if it cannot handle the case of removing a directory
+	 * that is still in use by something else..
+	 */
+	switch (dentry->d_count) {
+	default:
 		shrink_dcache_parent(dentry);
+		if (dentry->d_count != 2)
+			break;
+	case 2:
+		d_drop(dentry);
+	}
 
 	error = dir->d_inode->i_op->rmdir(dir->d_inode, dentry);
 
 exit_lock:
-        unlock_dir(dir);
-exit_dput:
-	dput(dentry);
+	dentry->d_count--;
+	double_unlock(dentry, dir);
 exit:
 	return error;
 }
@@ -943,13 +1001,16 @@
 		goto exit_lock;
 
 	/*
+	 * A directory can't be unlink'ed.
 	 * A file cannot be removed from an append-only directory.
 	 */
 	error = -EPERM;
+	if (S_ISDIR(dentry->d_inode->i_mode))
+		goto exit_lock;
+
 	if (IS_APPEND(dir->d_inode))
 		goto exit_lock;
 
-	error = -EPERM;
 	if (!dir->d_inode->i_op || !dir->d_inode->i_op->unlink)
 		goto exit_lock;
 
@@ -1142,37 +1203,6 @@
 	}
 	unlock_kernel();
 	return error;
-}
-
-/*
- * Whee.. Deadlock country. Happily there is only one VFS
- * operation that does 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);
 }
 
 static inline int do_rename(const char * oldname, const char * newname)

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