diff -uNrp linux-2.5.70/fs/nfs/dir.c linux-2.5.70-silly/fs/nfs/dir.c --- linux-2.5.70/fs/nfs/dir.c Sun Jun 1 23:30:30 2003 +++ linux-2.5.70-silly/fs/nfs/dir.c Mon Jun 9 05:38:44 2003 @@ -894,8 +894,10 @@ static int nfs_rmdir(struct inode *dir, lock_kernel(); nfs_zap_caches(dir); error = NFS_PROTO(dir)->rmdir(dir, &dentry->d_name); - if (!error) + if (!error) { + nfs_async_unlink_dequeue(dentry); dentry->d_inode->i_nlink = 0; + } unlock_kernel(); return error; @@ -925,8 +927,14 @@ dentry->d_parent->d_name.name, dentry->d * We don't allow a dentry to be silly-renamed twice. */ error = -EBUSY; - if (dentry->d_flags & DCACHE_NFSFS_RENAMED) + if (dentry->d_flags & DCACHE_NFSFS_RENAMED) { +#ifdef NFS_PARANOIA + printk("NFS: silly-rename %s/%s with DCACHE_NFSFS_RENAMED " + "already set?\n", + dentry->d_parent->d_name.name, dentry->d_name.name); +#endif goto out; + } sprintf(silly, ".nfs%*.*lx", i_inosize, i_inosize, dentry->d_inode->i_ino); @@ -959,7 +967,6 @@ dentry->d_parent->d_name.name, dentry->d nfs_renew_times(dentry); d_move(dentry, sdentry); error = nfs_async_unlink(dentry); - /* If we return 0 we don't unlink */ } dput(sdentry); out: @@ -1000,10 +1007,13 @@ out: return error; } -/* We do silly rename. In case sillyrename() returns -EBUSY, the inode - * belongs to an active ".nfs..." file and we return -EBUSY. +/* + * We do silly rename if the dentry says there are active users. + * Otherwise we unlink. * - * If sillyrename() returns 0, we do nothing, otherwise we unlink. + * Note that the dentry->d_count may lie; once a file is sillyrenamed + * it is unhashed and so a future lookup repopulates the dcache and the + * new dentry doesn't reflect the previous dentry->d_count. */ static int nfs_unlink(struct inode *dir, struct dentry *dentry) { diff -uNrp linux-2.5.70/fs/nfs/unlink.c linux-2.5.70-silly/fs/nfs/unlink.c --- linux-2.5.70/fs/nfs/unlink.c Sun Jun 1 23:28:55 2003 +++ linux-2.5.70-silly/fs/nfs/unlink.c Mon Jun 9 05:40:56 2003 @@ -21,6 +21,7 @@ struct nfs_unlinkdata { struct rpc_task task; struct rpc_cred *cred; unsigned int count; + int noaction; /* really remove? */ }; static struct nfs_unlinkdata *nfs_deletes; @@ -98,7 +99,7 @@ nfs_async_unlink_init(struct rpc_task *t }; int status = -ENOENT; - if (!data->name.len) + if (!data->name.len || data->noaction) goto out_err; status = NFS_PROTO(dir->d_inode)->unlink_setup(&msg, dir, &data->name); @@ -150,8 +151,7 @@ nfs_async_unlink_release(struct rpc_task /** * nfs_async_unlink - asynchronous unlinking of a file - * @dir: directory in which the file resides. - * @name: name of the file to unlink. + * @dentry: dentry to unlink */ int nfs_async_unlink(struct dentry *dentry) @@ -167,7 +167,7 @@ nfs_async_unlink(struct dentry *dentry) goto out; memset(data, 0, sizeof(*data)); - data->dir = dget(dir); + data->dir = dget(dir); /* dput() in nfs_async_unlink_done() */ data->dentry = dentry; data->next = nfs_deletes; @@ -190,7 +190,7 @@ nfs_async_unlink(struct dentry *dentry) } /** - * nfs_complete_remove - Initialize completion of the sillydelete + * nfs_complete_unlink - Initialize completion of the sillydelete * @dentry: dentry to delete * * Since we're most likely to be called by dentry_iput(), we @@ -202,16 +202,46 @@ nfs_complete_unlink(struct dentry *dentr { struct nfs_unlinkdata *data; - for(data = nfs_deletes; data != NULL; data = data->next) { + for (data = nfs_deletes; data != NULL; data = data->next) { if (dentry == data->dentry) break; } if (!data) return; - data->count++; nfs_copy_dname(dentry, data); dentry->d_flags &= ~DCACHE_NFSFS_RENAMED; if (data->task.tk_rpcwait == &nfs_delete_queue) rpc_wake_up_task(&data->task); - nfs_put_unlinkdata(data); } + +/** + * nfs_async_unlink_dequeue - Remove previously queued async unlinks + * @dir: parent directory being rmdir'd + * + * This function removes all dentrys from nfs_delete_queue whose parent + * is @dir. + * + * When a file is sillydeleted, the dentry gets unhashed and placed on + * nfs_delete_queue for a future [async] unlink (when d_count goes to 0). + * Since the sillyfile can still be found, it may be unlinked independently, + * in which case nfs_delete_queue will have stale data. When the parent + * dir goes away, data->dir being stale is a problem. This is 1 of 2 reasons + * this is only called from rmdir. The other is for performance; we can + * either dequeue on every unlink, or just on rmdir's. + * + * Just calling this on rmdir isn't ideal though, since we can incur a lot + * of extra NFS remove's if sillyrenamed files are themselves unlinked, and + * the parent dirs stick around. rmdir seems more likely though; generally, + * sillyrenamed files won't be accessed other than for 'rm -rf' operations. + */ +void +nfs_async_unlink_dequeue(struct dentry *dir) +{ + struct nfs_unlinkdata *data; + + /* Don't actually try to do anything when d_count goes to 0. */ + for (data = nfs_deletes; data != NULL; data = data->next) + if (data->dir == dir) + data->noaction = 1; +} + diff -uNrp linux-2.5.70/include/linux/nfs_fs.h linux-2.5.70-silly/include/linux/nfs_fs.h --- linux-2.5.70/include/linux/nfs_fs.h Sun Jun 1 23:29:17 2003 +++ linux-2.5.70-silly/include/linux/nfs_fs.h Mon Jun 9 00:02:53 2003 @@ -294,6 +294,7 @@ extern int nfs_lock(struct file *, int, */ extern int nfs_async_unlink(struct dentry *); extern void nfs_complete_unlink(struct dentry *); +extern void nfs_async_unlink_dequeue(struct dentry *); /* * linux/fs/nfs/write.c