patch-2.1.64 linux/fs/nfs/dir.c

Next file: linux/fs/nfs/inode.c
Previous file: linux/fs/ncpfs/sock.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.1.63/linux/fs/nfs/dir.c linux/fs/nfs/dir.c
@@ -31,10 +31,6 @@
 
 #define NFS_MAX_AGE 10*HZ	/* max age for dentry validation */
 
-#ifndef shrink_dcache_parent
-#define shrink_dcache_parent(dentry) shrink_dcache_sb((dentry)->d_sb)
-#endif
-
 /* needed by smbfs as well ... move to dcache? */
 extern void nfs_renew_times(struct dentry *);
 
@@ -59,7 +55,7 @@
 
 static int nfs_safe_remove(struct dentry *);
 
-static int nfs_dir_open(struct inode * inode, struct file * file);
+static int nfs_dir_open(struct inode *, struct file *);
 static ssize_t nfs_dir_read(struct file *, char *, size_t, loff_t *);
 static int nfs_readdir(struct file *, void *, filldir_t);
 static int nfs_lookup(struct inode *, struct dentry *);
@@ -435,6 +431,21 @@
 		if (age > NFS_MAX_AGE)
 			d_drop(dentry);
 	}
+
+#ifdef NFS_PARANOIA
+	/*
+	 * Sanity check: if the dentry has been unhashed and the
+	 * inode still has users, we could have problems ...
+	 */
+	if (list_empty(&dentry->d_hash) && dentry->d_inode) {
+		struct inode *inode = dentry->d_inode;
+		if (inode->i_count > 1) {
+printk("nfs_dentry_delete: %s/%s: ino=%ld, count=%d, nlink=%d\n",
+dentry->d_parent->d_name.name, dentry->d_name.name,
+inode->i_ino, inode->i_count, inode->i_nlink);
+		}
+	}
+#endif
 }
 
 static struct dentry_operations nfs_dentry_operations = {
@@ -785,7 +796,7 @@
 {
 	struct inode *dir = dentry->d_parent->d_inode;
 	struct inode *inode = dentry->d_inode;
-	int error;
+	int error, rehash = 0;
 		
 	error = -EBUSY;
 	if (inode) {
@@ -813,8 +824,22 @@
 #endif
 		goto out;
 	}
+	/*
+	 * Unhash the dentry while we remove the file ...
+	 */
+	if (!list_empty(&dentry->d_hash)) {
+		d_drop(dentry);
+		rehash = 1;
+	}
 	error = nfs_proc_remove(NFS_SERVER(dir),
 					NFS_FH(dir), dentry->d_name.name);
+	/*
+	 * ... then restore the hashed state.  This ensures that the
+	 * dentry can't become busy after having its file deleted.
+	 */
+	if (rehash) {
+		d_add(dentry, inode);
+	}
 #ifdef NFS_PARANOIA
 if (dentry->d_count > 1)
 printk("nfs_safe_remove: %s/%s busy after delete?? d_count=%d\n",
@@ -827,6 +852,7 @@
 		nfs_invalidate_dircache(dir);
 		if (inode && inode->i_nlink)
 			inode->i_nlink --;
+		d_delete(dentry);
 	}
 out:
 	return error;
@@ -858,7 +884,6 @@
 		error = nfs_safe_remove(dentry);
 		if (!error) {
 			nfs_renew_times(dentry);
-			d_delete(dentry);
 		}
 	}
 out:
@@ -966,8 +991,9 @@
 static int nfs_rename(struct inode *old_dir, struct dentry *old_dentry,
 		      struct inode *new_dir, struct dentry *new_dentry)
 {
-	struct inode *inode = old_dentry->d_inode;
-	int update = 1, error;
+	struct inode *old_inode = old_dentry->d_inode;
+	struct inode *new_inode = new_dentry->d_inode;
+	int error, rehash = 0, update = 1;
 
 #ifdef NFS_DEBUG_VERBOSE
 printk("nfs_rename: old %s/%s, count=%d, new %s/%s, count=%d\n",
@@ -988,6 +1014,24 @@
 	if (old_dentry->d_name.len > NFS_MAXNAMLEN ||
 	    new_dentry->d_name.len > NFS_MAXNAMLEN)
 		goto out;
+
+	/*
+	 * First check whether the target is busy ... we can't
+	 * safely do _any_ rename if the target is in use.
+	 */
+	if (new_dentry->d_count > 1) {
+		if (new_inode && S_ISDIR(new_inode->i_mode))
+			shrink_dcache_parent(new_dentry);
+	}
+	error = -EBUSY;
+	if (new_dentry->d_count > 1) {
+#ifdef NFS_PARANOIA
+printk("nfs_rename: target %s/%s busy, d_count=%d\n",
+new_dentry->d_parent->d_name.name,new_dentry->d_name.name,new_dentry->d_count);
+#endif
+		goto out;
+	}
+
 	/*
 	 * Check for within-directory rename ... no complications.
 	 */
@@ -996,15 +1040,14 @@
 	/*
 	 * Cross-directory move ... check whether it's a file.
 	 */
-	error = -EBUSY;
-	if (S_ISREG(inode->i_mode)) {
-		if (NFS_WRITEBACK(inode)) {
+	if (S_ISREG(old_inode->i_mode)) {
+		if (NFS_WRITEBACK(old_inode)) {
 #ifdef NFS_PARANOIA
 printk("nfs_rename: %s/%s has pending writes\n",
 old_dentry->d_parent->d_name.name, old_dentry->d_name.name);
 #endif
-			nfs_flush_dirty_pages(inode, 0, 0, 0);
-			if (NFS_WRITEBACK(inode)) {
+			nfs_flush_dirty_pages(old_inode, 0, 0, 0);
+			if (NFS_WRITEBACK(old_inode)) {
 #ifdef NFS_PARANOIA
 printk("nfs_rename: %s/%s has pending writes after flush\n",
 old_dentry->d_parent->d_name.name, old_dentry->d_name.name);
@@ -1030,7 +1073,6 @@
 #endif
 		goto out;
 	}
-
 	if (new_dentry->d_count > 1) {
 #ifdef NFS_PARANOIA
 printk("nfs_rename: new dentry %s/%s busy, d_count=%d\n",
@@ -1040,13 +1082,28 @@
 	}
 
 	d_drop(old_dentry);
-	d_drop(new_dentry);
 	update = 0;
 
 do_rename:
+	/*
+	 * We must prevent any new references to the target while
+	 * the rename is in progress, so we unhash the dentry.
+	 */
+	if (!list_empty(&new_dentry->d_hash)) {
+		d_drop(new_dentry);
+		rehash = 1;
+	}
 	error = nfs_proc_rename(NFS_SERVER(old_dir),
 				NFS_FH(old_dir), old_dentry->d_name.name,
 				NFS_FH(new_dir), new_dentry->d_name.name);
+	if (rehash) {
+		d_add(new_dentry, new_inode);
+	}
+#ifdef NFS_PARANOIA
+if (new_dentry->d_count > 1)
+printk("nfs_rename: %s/%s busy after rename, d_count=%d\n",
+new_dentry->d_parent->d_name.name,new_dentry->d_name.name,new_dentry->d_count);
+#endif
 	if (!error) {
 		nfs_invalidate_dircache(new_dir);
 		nfs_invalidate_dircache(old_dir);

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