Skip to content

Commit

Permalink
Btrfs: Implement our own copy of the nfsd readdir hack, for older ker…
Browse files Browse the repository at this point in the history
…nels

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>
  • Loading branch information
David Woodhouse authored and Chris Mason committed Sep 25, 2008
1 parent be6e8dc commit cbdf5a2
Showing 1 changed file with 102 additions and 2 deletions.
104 changes: 102 additions & 2 deletions fs/btrfs/inode.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -2064,6 +2065,101 @@ static int btrfs_readdir(struct file *filp, void *dirent, filldir_t filldir)
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;
Expand Down Expand Up @@ -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,
Expand Down

0 comments on commit cbdf5a2

Please sign in to comment.