Btrfs: Implement our own copy of the nfsd readdir hack, for older kernels
Date: Wed, 6 Aug 2008 19:42:33 +0100
Signed-off-by: David Woodhouse <David.Woodhouse@intel.com>
Signed-off-by: Chris Mason <chris.mason@oracle.com>
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index 266ca55..0da1ae4 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -1915,7 +1915,8 @@
DT_UNKNOWN, DT_REG, DT_DIR, DT_CHR, DT_BLK, DT_FIFO, DT_SOCK, DT_LNK
};
-static int btrfs_readdir(struct file *filp, void *dirent, filldir_t filldir)
+static int btrfs_real_readdir(struct file *filp, void *dirent,
+ filldir_t filldir)
{
struct inode *inode = filp->f_dentry->d_inode;
struct btrfs_root *root = BTRFS_I(inode)->root;
@@ -2064,6 +2065,101 @@
return ret;
}
+/* Kernels earlier than 2.6.28 still have the NFS deadlock where nfsd
+ will call the file system's ->lookup() method from within its
+ filldir callback, which in turn was called from the file system's
+ ->readdir() method. And will deadlock for many file systems. */
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28)
+
+struct nfshack_dirent {
+ u64 ino;
+ loff_t offset;
+ int namlen;
+ unsigned int d_type;
+ char name[];
+};
+
+struct nfshack_readdir {
+ char *dirent;
+ size_t used;
+};
+
+
+
+static int btrfs_nfshack_filldir(void *__buf, const char *name, int namlen,
+ loff_t offset, u64 ino, unsigned int d_type)
+{
+ struct nfshack_readdir *buf = __buf;
+ struct nfshack_dirent *de = (void *)(buf->dirent + buf->used);
+ unsigned int reclen;
+
+ reclen = ALIGN(sizeof(struct nfshack_dirent) + namlen, sizeof(u64));
+ if (buf->used + reclen > PAGE_SIZE)
+ return -EINVAL;
+
+ de->namlen = namlen;
+ de->offset = offset;
+ de->ino = ino;
+ de->d_type = d_type;
+ memcpy(de->name, name, namlen);
+ buf->used += reclen;
+
+ return 0;
+}
+
+static int btrfs_nfshack_readdir(struct file *file, void *dirent,
+ filldir_t filldir)
+{
+ struct nfshack_readdir buf;
+ struct nfshack_dirent *de;
+ int err;
+ int size;
+ loff_t offset;
+
+ buf.dirent = (void *)__get_free_page(GFP_KERNEL);
+ if (!buf.dirent)
+ return -ENOMEM;
+
+ offset = file->f_pos;
+
+ while (1) {
+ unsigned int reclen;
+
+ buf.used = 0;
+
+ err = btrfs_real_readdir(file, &buf, btrfs_nfshack_filldir);
+ if (err)
+ break;
+
+ size = buf.used;
+
+ if (!size)
+ break;
+
+ de = (struct nfshack_dirent *)buf.dirent;
+ while (size > 0) {
+ offset = de->offset;
+
+ if (filldir(dirent, de->name, de->namlen, de->offset,
+ de->ino, de->d_type))
+ goto done;
+ offset = file->f_pos;
+
+ reclen = ALIGN(sizeof(*de) + de->namlen,
+ sizeof(u64));
+ size -= reclen;
+ de = (struct nfshack_dirent *)((char *)de + reclen);
+ }
+ }
+
+ done:
+ free_page((unsigned long)buf.dirent);
+ file->f_pos = offset;
+
+ return err;
+}
+#endif
+
int btrfs_write_inode(struct inode *inode, int wait)
{
struct btrfs_root *root = BTRFS_I(inode)->root;
@@ -3623,7 +3719,11 @@
static struct file_operations btrfs_dir_file_operations = {
.llseek = generic_file_llseek,
.read = generic_read_dir,
- .readdir = btrfs_readdir,
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28)
+ .readdir = btrfs_nfshack_readdir,
+#else /* NFSd readdir/lookup deadlock is fixed */
+ .readdir = btrfs_real_readdir,
+#endif
.unlocked_ioctl = btrfs_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = btrfs_ioctl,