Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit cbdf5a24 authored by David Woodhouse's avatar David Woodhouse Committed by Chris Mason
Browse files

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: default avatarDavid Woodhouse <David.Woodhouse@intel.com>
Signed-off-by: default avatarChris Mason <chris.mason@oracle.com>
parent be6e8dc0
Loading
Loading
Loading
Loading
+102 −2
Original line number Diff line number Diff line
@@ -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 @@ 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;
@@ -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,