Btrfs: Implement our own copy of the nfsd readdir hack, for older kernels
authorDavid Woodhouse <David.Woodhouse@intel.com>
Wed, 6 Aug 2008 18:42:33 +0000 (19:42 +0100)
committerChris Mason <chris.mason@oracle.com>
Thu, 25 Sep 2008 15:04:06 +0000 (11:04 -0400)
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>
fs/btrfs/inode.c

index 266ca55..0da1ae4 100644 (file)
@@ -1915,7 +1915,8 @@ static unsigned char btrfs_filetype_table[] = {
        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 @@ err:
        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 inode_operations btrfs_dir_ro_inode_operations = {
 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,