fuse2 + sample

usage:

linux/samples/fuse2/loraw -2 -p -t ~/mnt/fuse/

options:

 -d: debug
 -s: single threaded
 -b: FUSE_DEV_IOC_CLONE (v1)
 -p: use ioctl for device I/O (v2)
 -m: use "map read" transferring offset into file instead of actual data
 -1: use regular fuse
 -2: use experimental fuse2
 -t: use shared memory instead of threads

Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
diff --git a/fs/Kconfig b/fs/Kconfig
index bfb1c60..27e98bd 100644
--- a/fs/Kconfig
+++ b/fs/Kconfig
@@ -118,6 +118,7 @@
 
 source "fs/autofs/Kconfig"
 source "fs/fuse/Kconfig"
+source "fs/fuse2/Kconfig"
 source "fs/overlayfs/Kconfig"
 
 menu "Caches"
diff --git a/fs/Makefile b/fs/Makefile
index d60089f..f259c3d 100644
--- a/fs/Makefile
+++ b/fs/Makefile
@@ -108,6 +108,7 @@
 obj-$(CONFIG_AUTOFS_FS)		+= autofs/
 obj-$(CONFIG_ADFS_FS)		+= adfs/
 obj-$(CONFIG_FUSE_FS)		+= fuse/
+obj-$(CONFIG_FUSE2_CORE)	+= fuse2/
 obj-$(CONFIG_OVERLAY_FS)	+= overlayfs/
 obj-$(CONFIG_ORANGEFS_FS)       += orangefs/
 obj-$(CONFIG_UDF_FS)		+= udf/
diff --git a/fs/fuse2/Kconfig b/fs/fuse2/Kconfig
new file mode 100644
index 0000000..0b726c1
--- /dev/null
+++ b/fs/fuse2/Kconfig
@@ -0,0 +1,7 @@
+config FUSE2_CORE
+	tristate
+	select FS_POSIX_ACL
+
+config FUSE2_FS
+	tristate "Experimental new fuse driver"
+	select FUSE2_CORE
diff --git a/fs/fuse2/Makefile b/fs/fuse2/Makefile
new file mode 100644
index 0000000..9d805b1
--- /dev/null
+++ b/fs/fuse2/Makefile
@@ -0,0 +1,9 @@
+#
+# Makefile for the FUSE filesystem.
+#
+
+obj-$(CONFIG_FUSE2_CORE) += fuse2_core.o
+obj-$(CONFIG_FUSE2_FS) += fuse2.o
+
+fuse2-y := fudev.o
+fuse2_core-y := request.o dir.o file.o inode.o xattr.o acl.o readdir.o map.o
diff --git a/fs/fuse2/acl.c b/fs/fuse2/acl.c
new file mode 100644
index 0000000..e237f8c
--- /dev/null
+++ b/fs/fuse2/acl.c
@@ -0,0 +1,99 @@
+/*
+ * FUSE: Filesystem in Userspace
+ * Copyright (C) 2016 Canonical Ltd. <seth.forshee@canonical.com>
+ *
+ * This program can be distributed under the terms of the GNU GPL.
+ * See the file COPYING.
+ */
+
+#include "fuse_i.h"
+
+#include <linux/posix_acl.h>
+#include <linux/posix_acl_xattr.h>
+
+struct posix_acl *fuse2_get_acl(struct inode *inode, int type)
+{
+	struct fuse_conn *fc = get_fuse_conn(inode);
+	int size;
+	const char *name;
+	void *value = NULL;
+	struct posix_acl *acl;
+
+	if (!fc->posix_acl || fc->no_getxattr)
+		return NULL;
+
+	if (type == ACL_TYPE_ACCESS)
+		name = XATTR_NAME_POSIX_ACL_ACCESS;
+	else if (type == ACL_TYPE_DEFAULT)
+		name = XATTR_NAME_POSIX_ACL_DEFAULT;
+	else
+		return ERR_PTR(-EOPNOTSUPP);
+
+	value = kmalloc(PAGE_SIZE, GFP_KERNEL);
+	if (!value)
+		return ERR_PTR(-ENOMEM);
+	size = fuse2_getxattr(inode, name, value, PAGE_SIZE);
+	if (size > 0)
+		acl = posix_acl_from_xattr(fc->user_ns, value, size);
+	else if ((size == 0) || (size == -ENODATA) ||
+		 (size == -EOPNOTSUPP && fc->no_getxattr))
+		acl = NULL;
+	else if (size == -ERANGE)
+		acl = ERR_PTR(-E2BIG);
+	else
+		acl = ERR_PTR(size);
+
+	kfree(value);
+	forget_cached_acl(inode, type);
+
+	return acl;
+}
+
+int fuse2_set_acl(struct inode *inode, struct posix_acl *acl, int type)
+{
+	struct fuse_conn *fc = get_fuse_conn(inode);
+	const char *name;
+	int ret;
+
+	if (!fc->posix_acl || fc->no_setxattr)
+		return -EOPNOTSUPP;
+
+	if (type == ACL_TYPE_ACCESS)
+		name = XATTR_NAME_POSIX_ACL_ACCESS;
+	else if (type == ACL_TYPE_DEFAULT)
+		name = XATTR_NAME_POSIX_ACL_DEFAULT;
+	else
+		return -EINVAL;
+
+	if (acl) {
+		/*
+		 * Fuse userspace is responsible for updating access
+		 * permissions in the inode, if needed. fuse2_setxattr
+		 * invalidates the inode attributes, which will force
+		 * them to be refreshed the next time they are used,
+		 * and it also updates i_ctime.
+		 */
+		size_t size = posix_acl_xattr_size(acl->a_count);
+		void *value;
+
+		if (size > PAGE_SIZE)
+			return -E2BIG;
+
+		value = kmalloc(size, GFP_KERNEL);
+		if (!value)
+			return -ENOMEM;
+
+		ret = posix_acl_to_xattr(fc->user_ns, acl, value, size);
+		if (ret < 0) {
+			kfree(value);
+			return ret;
+		}
+
+		ret = fuse2_setxattr(inode, name, value, size, 0);
+		kfree(value);
+	} else {
+		ret = fuse2_removexattr(inode, name);
+	}
+
+	return ret;
+}
diff --git a/fs/fuse2/dev.h b/fs/fuse2/dev.h
new file mode 100644
index 0000000..487d72c7
--- /dev/null
+++ b/fs/fuse2/dev.h
@@ -0,0 +1,157 @@
+/*
+  FUSE: Filesystem in Userspace
+  Copyright (C) 2001-2019  Miklos Szeredi <miklos@szeredi.hu>
+
+  This program can be distributed under the terms of the GNU GPL.
+  See the file COPYING.
+*/
+
+#include <linux/fuse.h>
+
+#include <linux/fs.h>
+#include <linux/list.h>
+#include <linux/wait.h>
+#include <linux/refcount.h>
+#include <linux/mm_types.h>
+
+/*
+ * Request flags
+ *
+ * FR_ISREPLY:		set if the request has reply
+ * FR_FORCE:		force sending of the request
+ * FR_PENDING:		request is not yet in userspace
+ * FR_SENT:		request sent to userspace
+ * FR_FINISHED:		request is finished
+ * FR_ZEROTAIL:		zero tail of returned data
+ */
+enum fuse_req_flag {
+	FR_ISREPLY,
+	FR_FORCE,
+	FR_PENDING,
+	FR_SENT,
+	FR_FINISHED,
+	FR_ZEROTAIL,
+	FR_KILLABLE,
+};
+
+/* FUSE page descriptor */
+struct fuse_page_desc {
+	unsigned int length;
+	unsigned int offset;
+};
+
+/* Number of page pointers embedded in fuse_req */
+#define FUSE_REQ_INLINE_PAGES 1
+
+#define FUSE_REQ_INLINE_DATA 196
+
+struct fuse_req {
+	/* This can be on either pending or processing lists */
+	struct list_head list;
+
+	/* refcount */
+	refcount_t count;
+
+	/* Request flags, updated with test/set/clear_bit() */
+	unsigned long flags;
+
+	union {
+		/* The request header */
+		struct fuse_in_header inh;
+
+		/* The reply header */
+		struct fuse_out_header outh;
+
+		/* Inline data */
+		char inlinedata[FUSE_REQ_INLINE_DATA];
+	};
+
+	/* length of inline in data */
+	unsigned short inline_inlen;
+
+	/* length of inline out data */
+	unsigned short inline_outlen;
+
+	/* mandatory out len */
+	unsigned int mand_outlen;
+
+	/* max out len */
+	unsigned int max_outlen;
+
+	/* size of the 'pages' array */
+	unsigned short max_pages;
+
+	/* number of pages in vector */
+	unsigned short num_pages;
+
+	/* Used to wake up the task waiting for completion of request*/
+	wait_queue_head_t waitq;
+
+	/* page vector */
+	struct page **pages;
+
+	/* page-descriptor vector */
+	struct fuse_page_desc *page_descs;
+
+	/* inline page vector */
+	struct page *inline_pages[FUSE_REQ_INLINE_PAGES];
+
+	/* inline page-descriptor vector */
+	struct fuse_page_desc inline_page_descs[FUSE_REQ_INLINE_PAGES];
+};
+
+/* One forget request */
+struct fuse_forget {
+	struct fuse_forget_one forget_one;
+	struct list_head list;
+};
+
+/** One input argument of a request */
+struct fuse_in_arg {
+	unsigned size;
+	const void *value;
+};
+
+/** One output argument of a request */
+struct fuse_arg {
+	unsigned size;
+	void *value;
+};
+
+struct fuse_args {
+	bool force:1;
+	bool killable:1;
+	struct {
+		struct {
+			uint32_t opcode;
+			uint64_t nodeid;
+		} h;
+		unsigned numargs;
+		struct fuse_in_arg args[3];
+
+	} in;
+	struct {
+		unsigned argvar:1;
+		unsigned numargs;
+		struct fuse_arg args[2];
+	} out;
+};
+
+struct fuse_dev_operations {
+	void (*put)(void *dev);
+	void (*abort)(void *dev);
+	int (*send)(void *dev, struct fuse_req *req);
+	ssize_t (*simple_send)(void *dev, struct fuse_args *args, uid_t uid,
+			       gid_t gid, pid_t pid);
+	void (*forget)(void *dev, struct fuse_forget *forget);
+};
+
+struct dentry *fuse_mount_common(struct file_system_type *fs_type,
+				 int flags, void *opts,
+				 const struct fuse_dev_operations *dev_ops,
+				 void *dev_priv);
+void fuse_kill_sb(struct super_block *sb);
+void fuse2_get_request(struct fuse_req *req);
+void fuse2_put_request(struct fuse_req *req);
+int fuse2_map_open(struct super_block *sb, struct file *file);
+int fuse2_map_close(struct super_block *sb, unsigned long mapfd);
diff --git a/fs/fuse2/dir.c b/fs/fuse2/dir.c
new file mode 100644
index 0000000..7b0f13a
--- /dev/null
+++ b/fs/fuse2/dir.c
@@ -0,0 +1,993 @@
+/*
+  FUSE: Filesystem in Userspace
+  Copyright (C) 2001-2008  Miklos Szeredi <miklos@szeredi.hu>
+
+  This program can be distributed under the terms of the GNU GPL.
+  See the file COPYING.
+*/
+
+#include "fuse_i.h"
+
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/namei.h>
+#include <linux/user_namespace.h>
+
+static void fuse_lookup_init(struct fuse_args *args, u64 nodeid,
+			     const struct qstr *name,
+			     struct fuse_entry_out *outarg)
+{
+	memset(outarg, 0, sizeof(struct fuse_entry_out));
+	args->in.h.opcode = FUSE_LOOKUP;
+	args->in.h.nodeid = nodeid;
+	args->in.numargs = 1;
+	args->in.args[0].size = name->len + 1;
+	args->in.args[0].value = name->name;
+	args->out.numargs = 1;
+	args->out.args[0].size = sizeof(struct fuse_entry_out);
+	args->out.args[0].value = outarg;
+}
+
+/*
+ * Check whether the dentry is still valid
+ *
+ * If the entry validity timeout has expired and the dentry is
+ * positive, try to redo the lookup.  If the lookup results in a
+ * different inode, then let the VFS invalidate the dentry and redo
+ * the lookup once more.  If the lookup results in the same inode,
+ * then refresh the attributes, timeouts and mark the dentry valid.
+ */
+static int fuse_dentry_revalidate(struct dentry *entry, unsigned int flags)
+{
+	struct inode *inode;
+	struct dentry *parent;
+	struct fuse_conn *fc;
+	struct fuse_inode *fi;
+	struct fuse_entry_out outarg;
+	FUSE_ARGS(args);
+	int ret;
+
+	inode = d_inode_rcu(entry);
+	if (inode && is_bad_inode(inode))
+		goto invalid;
+
+
+	/* For negative dentries, always do a fresh lookup */
+	if (!inode)
+		goto invalid;
+
+	ret = -ECHILD;
+	if (flags & LOOKUP_RCU)
+		goto out;
+
+	fc = get_fuse_conn(inode);
+
+	parent = dget_parent(entry);
+	fuse_lookup_init(&args, get_node_id(d_inode(parent)),
+			 &entry->d_name, &outarg);
+	ret = fuse2_simple_request(fc, &args);
+	dput(parent);
+	/* Zero nodeid is same as -ENOENT */
+	if (!ret && !outarg.nodeid)
+		ret = -ENOENT;
+	if (!ret) {
+		fi = get_fuse_inode(inode);
+		if (outarg.nodeid != get_node_id(inode)) {
+			fuse2_force_forget(fc, outarg.nodeid);
+			goto invalid;
+		}
+		spin_lock(&fi->lock);
+		fi->nlookup++;
+		spin_unlock(&fi->lock);
+	}
+	if (ret == -ENOMEM)
+		goto out;
+	if (ret || (outarg.attr.mode ^ inode->i_mode) & S_IFMT)
+		goto invalid;
+
+	fuse2_change_attributes(inode, &outarg.attr);
+	ret = 1;
+out:
+	return ret;
+
+invalid:
+	ret = 0;
+	goto out;
+}
+
+const struct dentry_operations fuse2_dentry_operations = {
+	.d_revalidate	= fuse_dentry_revalidate,
+//	.d_delete	= always_delete_dentry,
+};
+
+static int fuse_valid_type(int m)
+{
+	return S_ISREG(m) || S_ISDIR(m) || S_ISLNK(m) || S_ISCHR(m) ||
+		S_ISBLK(m) || S_ISFIFO(m) || S_ISSOCK(m);
+}
+
+static int fuse_lookup_name(struct super_block *sb, u64 nodeid,
+			    const struct qstr *name,
+			    struct fuse_entry_out *outarg, struct inode **inode)
+{
+	struct fuse_conn *fc = get_fuse_conn_super(sb);
+	FUSE_ARGS(args);
+	int err;
+
+	*inode = NULL;
+	if (name->len > FUSE_NAME_MAX)
+		return -ENAMETOOLONG;
+
+	fuse_lookup_init(&args, nodeid, name, outarg);
+	err = fuse2_simple_request(fc, &args);
+	/* Zero nodeid is same as -ENOENT, but with valid timeout */
+	if (err || !outarg->nodeid)
+		return err;
+
+	if (!outarg->nodeid || !fuse_valid_type(outarg->attr.mode))
+		return -EIO;
+
+	*inode = fuse2_iget(sb, outarg->nodeid, outarg->generation,
+			   &outarg->attr);
+	if (!*inode) {
+		fuse2_force_forget(fc, outarg->nodeid);
+		return -ENOMEM;
+	}
+
+	return 0;
+}
+
+static struct dentry *fuse_lookup(struct inode *dir, struct dentry *entry,
+				  unsigned int flags)
+{
+	int err;
+	struct fuse_entry_out outarg;
+	struct inode *inode;
+	bool outarg_valid = true;
+
+	err = fuse_lookup_name(dir->i_sb, get_node_id(dir), &entry->d_name,
+			       &outarg, &inode);
+	if (err == -ENOENT) {
+		outarg_valid = false;
+		err = 0;
+	}
+	if (err)
+		goto out_err;
+
+	err = -EIO;
+	if (inode && get_node_id(inode) == FUSE_ROOT_ID)
+		goto out_iput;
+
+	return d_splice_alias(inode, entry);
+
+ out_iput:
+	iput(inode);
+ out_err:
+	return ERR_PTR(err);
+}
+
+/*
+ * Atomic create+open operation
+ *
+ * If the filesystem doesn't support this, then fall back to separate
+ * 'mknod' + 'open' requests.
+ */
+static int fuse_create_open(struct inode *dir, struct dentry *entry,
+			    struct file *file, unsigned flags,
+			    umode_t mode)
+{
+	int err;
+	struct inode *inode;
+	struct fuse_conn *fc = get_fuse_conn(dir);
+	FUSE_ARGS(args);
+	struct fuse_create_in inarg;
+	struct fuse_open_out outopen;
+	struct fuse_entry_out outentry;
+	struct fuse_inode *fi;
+	struct fuse_file *ff;
+
+	/* Userspace expects S_IFREG in create mode */
+	BUG_ON((mode & S_IFMT) != S_IFREG);
+
+	ff = fuse2_file_alloc(fc);
+	if (!ff)
+		return -ENOMEM;
+
+	mode &= ~current_umask();
+
+	flags &= ~O_NOCTTY;
+	memset(&inarg, 0, sizeof(inarg));
+	memset(&outentry, 0, sizeof(outentry));
+	inarg.flags = flags;
+	inarg.mode = mode;
+	inarg.umask = current_umask();
+	args.in.h.opcode = FUSE_CREATE;
+	args.in.h.nodeid = get_node_id(dir);
+	args.in.numargs = 2;
+	args.in.args[0].size = sizeof(inarg);
+	args.in.args[0].value = &inarg;
+	args.in.args[1].size = entry->d_name.len + 1;
+	args.in.args[1].value = entry->d_name.name;
+	args.out.numargs = 2;
+	args.out.args[0].size = sizeof(outentry);
+	args.out.args[0].value = &outentry;
+	args.out.args[1].size = sizeof(outopen);
+	args.out.args[1].value = &outopen;
+	err = fuse2_simple_request(fc, &args);
+	if (err)
+		goto out_free_ff;
+
+	err = -EIO;
+	if (!S_ISREG(outentry.attr.mode) || invalid_nodeid(outentry.nodeid))
+		goto out_free_ff;
+
+	ff->fh = outopen.fh;
+	ff->nodeid = outentry.nodeid;
+	ff->open_flags = outopen.open_flags;
+	inode = fuse2_iget(dir->i_sb, outentry.nodeid, outentry.generation,
+			  &outentry.attr);
+	if (!inode) {
+		flags &= ~(O_CREAT | O_EXCL | O_TRUNC);
+		fuse2_release_common(fc, ff, flags, NULL, false);
+		fuse2_force_forget(fc, outentry.nodeid);
+		return -ENOMEM;
+	}
+	d_instantiate(entry, inode);
+	err = finish_open(file, entry, generic_file_open);
+	if (err) {
+		fi = get_fuse_inode(inode);
+		fuse2_release_common(fc, ff, flags, NULL, false);
+	} else {
+		file->private_data = ff;
+		fuse2_finish_open(inode, file);
+	}
+	return err;
+
+out_free_ff:
+	fuse2_file_free(ff);
+	return err;
+}
+
+static int fuse_mknod(struct inode *, struct dentry *, umode_t, dev_t);
+static int fuse_atomic_open(struct inode *dir, struct dentry *entry,
+			    struct file *file, unsigned flags,
+			    umode_t mode)
+{
+	int err;
+	struct fuse_conn *fc = get_fuse_conn(dir);
+	struct dentry *res = NULL;
+
+	if (d_in_lookup(entry)) {
+		res = fuse_lookup(dir, entry, 0);
+		if (IS_ERR(res))
+			return PTR_ERR(res);
+
+		if (res)
+			entry = res;
+	}
+
+	if (!(flags & O_CREAT) || d_really_is_positive(entry))
+		goto no_open;
+
+	/* Only creates */
+	file->f_mode |= FMODE_CREATED;
+
+	if (fc->no_create)
+		goto mknod;
+
+	err = fuse_create_open(dir, entry, file, flags, mode);
+	if (err == -ENOSYS) {
+		fc->no_create = 1;
+		goto mknod;
+	}
+out_dput:
+	dput(res);
+	return err;
+
+mknod:
+	err = fuse_mknod(dir, entry, mode, 0);
+	if (err)
+		goto out_dput;
+no_open:
+	return finish_no_open(file, res);
+}
+
+/*
+ * Code shared between mknod, mkdir, symlink and link
+ */
+static int create_new_entry(struct fuse_conn *fc, struct fuse_args *args,
+			    struct inode *dir, struct dentry *entry,
+			    umode_t mode)
+{
+	struct fuse_entry_out outarg;
+	struct inode *inode;
+	struct dentry *d;
+	int err;
+
+	memset(&outarg, 0, sizeof(outarg));
+	args->in.h.nodeid = get_node_id(dir);
+	args->out.numargs = 1;
+	args->out.args[0].size = sizeof(outarg);
+	args->out.args[0].value = &outarg;
+
+	err = fuse2_simple_request(fc, args);
+	if (err)
+		return err;
+
+	if (invalid_nodeid(outarg.nodeid))
+		return -EIO;
+
+	if ((outarg.attr.mode ^ mode) & S_IFMT)
+		return -EIO;
+
+	inode = fuse2_iget(dir->i_sb, outarg.nodeid, outarg.generation,
+			  &outarg.attr);
+	if (!inode) {
+		fuse2_force_forget(fc, outarg.nodeid);
+		return -ENOMEM;
+	}
+
+	d_drop(entry);
+	d = d_splice_alias(inode, entry);
+	if (IS_ERR(d))
+		return PTR_ERR(d);
+
+	dput(d);
+
+	return 0;
+}
+
+static int fuse_mknod(struct inode *dir, struct dentry *entry, umode_t mode,
+		      dev_t rdev)
+{
+	struct fuse_mknod_in inarg;
+	struct fuse_conn *fc = get_fuse_conn(dir);
+	FUSE_ARGS(args);
+
+	mode &= ~current_umask();
+
+	memset(&inarg, 0, sizeof(inarg));
+	inarg.mode = mode;
+	inarg.rdev = new_encode_dev(rdev);
+	inarg.umask = current_umask();
+	args.in.h.opcode = FUSE_MKNOD;
+	args.in.numargs = 2;
+	args.in.args[0].size = sizeof(inarg);
+	args.in.args[0].value = &inarg;
+	args.in.args[1].size = entry->d_name.len + 1;
+	args.in.args[1].value = entry->d_name.name;
+	return create_new_entry(fc, &args, dir, entry, mode);
+}
+
+static int fuse_create(struct inode *dir, struct dentry *entry, umode_t mode,
+		       bool excl)
+{
+	return fuse_mknod(dir, entry, mode, 0);
+}
+
+static int fuse_mkdir(struct inode *dir, struct dentry *entry, umode_t mode)
+{
+	struct fuse_mkdir_in inarg;
+	struct fuse_conn *fc = get_fuse_conn(dir);
+	FUSE_ARGS(args);
+
+	mode &= ~current_umask();
+
+	memset(&inarg, 0, sizeof(inarg));
+	inarg.mode = mode;
+	inarg.umask = current_umask();
+	args.in.h.opcode = FUSE_MKDIR;
+	args.in.numargs = 2;
+	args.in.args[0].size = sizeof(inarg);
+	args.in.args[0].value = &inarg;
+	args.in.args[1].size = entry->d_name.len + 1;
+	args.in.args[1].value = entry->d_name.name;
+	return create_new_entry(fc, &args, dir, entry, S_IFDIR);
+}
+
+static int fuse_symlink(struct inode *dir, struct dentry *entry,
+			const char *link)
+{
+	struct fuse_conn *fc = get_fuse_conn(dir);
+	unsigned len = strlen(link) + 1;
+	FUSE_ARGS(args);
+
+	args.in.h.opcode = FUSE_SYMLINK;
+	args.in.numargs = 2;
+	args.in.args[0].size = entry->d_name.len + 1;
+	args.in.args[0].value = entry->d_name.name;
+	args.in.args[1].size = len;
+	args.in.args[1].value = link;
+	return create_new_entry(fc, &args, dir, entry, S_IFLNK);
+}
+
+static int fuse_unlink(struct inode *dir, struct dentry *entry)
+{
+	int err;
+	struct fuse_conn *fc = get_fuse_conn(dir);
+	FUSE_ARGS(args);
+
+	args.in.h.opcode = FUSE_UNLINK;
+	args.in.h.nodeid = get_node_id(dir);
+	args.in.numargs = 1;
+	args.in.args[0].size = entry->d_name.len + 1;
+	args.in.args[0].value = entry->d_name.name;
+	err = fuse2_simple_request(fc, &args);
+	if (!err) {
+		struct inode *inode = d_inode(entry);
+		struct fuse_inode *fi = get_fuse_inode(inode);
+
+		spin_lock(&fi->lock);
+		/*
+		 * If i_nlink == 0 then unlink doesn't make sense, yet this can
+		 * happen if userspace filesystem is careless.  It would be
+		 * difficult to enforce correct nlink usage so just ignore this
+		 * condition here
+		 */
+		if (inode->i_nlink > 0)
+			drop_nlink(inode);
+		spin_unlock(&fi->lock);
+	}
+	return err;
+}
+
+static int fuse_rmdir(struct inode *dir, struct dentry *entry)
+{
+	int err;
+	struct fuse_conn *fc = get_fuse_conn(dir);
+	FUSE_ARGS(args);
+
+	args.in.h.opcode = FUSE_RMDIR;
+	args.in.h.nodeid = get_node_id(dir);
+	args.in.numargs = 1;
+	args.in.args[0].size = entry->d_name.len + 1;
+	args.in.args[0].value = entry->d_name.name;
+	err = fuse2_simple_request(fc, &args);
+	if (!err)
+		clear_nlink(d_inode(entry));
+
+	return err;
+}
+
+static int fuse_rename_common(struct inode *olddir, struct dentry *oldent,
+			      struct inode *newdir, struct dentry *newent,
+			      unsigned int flags, int opcode, size_t argsize)
+{
+	struct fuse_rename2_in inarg;
+	struct fuse_conn *fc = get_fuse_conn(olddir);
+	FUSE_ARGS(args);
+
+	memset(&inarg, 0, argsize);
+	inarg.newdir = get_node_id(newdir);
+	inarg.flags = flags;
+	args.in.h.opcode = opcode;
+	args.in.h.nodeid = get_node_id(olddir);
+	args.in.numargs = 3;
+	args.in.args[0].size = argsize;
+	args.in.args[0].value = &inarg;
+	args.in.args[1].size = oldent->d_name.len + 1;
+	args.in.args[1].value = oldent->d_name.name;
+	args.in.args[2].size = newent->d_name.len + 1;
+	args.in.args[2].value = newent->d_name.name;
+
+	return fuse2_simple_request(fc, &args);
+}
+
+static int fuse_rename2(struct inode *olddir, struct dentry *oldent,
+			struct inode *newdir, struct dentry *newent,
+			unsigned int flags)
+{
+	struct fuse_conn *fc = get_fuse_conn(olddir);
+	int err;
+
+	if (flags & ~(RENAME_NOREPLACE | RENAME_EXCHANGE))
+		return -EINVAL;
+
+	if (flags) {
+		if (fc->no_rename2)
+			return -EINVAL;
+
+		err = fuse_rename_common(olddir, oldent, newdir, newent, flags,
+					 FUSE_RENAME2,
+					 sizeof(struct fuse_rename2_in));
+		if (err == -ENOSYS) {
+			fc->no_rename2 = 1;
+			err = -EINVAL;
+		}
+	} else {
+		err = fuse_rename_common(olddir, oldent, newdir, newent, 0,
+					 FUSE_RENAME,
+					 sizeof(struct fuse_rename_in));
+	}
+
+	return err;
+}
+
+static int fuse_link(struct dentry *entry, struct inode *newdir,
+		     struct dentry *newent)
+{
+	int err;
+	struct fuse_link_in inarg;
+	struct inode *inode = d_inode(entry);
+	struct fuse_conn *fc = get_fuse_conn(inode);
+	FUSE_ARGS(args);
+
+	memset(&inarg, 0, sizeof(inarg));
+	inarg.oldnodeid = get_node_id(inode);
+	args.in.h.opcode = FUSE_LINK;
+	args.in.numargs = 2;
+	args.in.args[0].size = sizeof(inarg);
+	args.in.args[0].value = &inarg;
+	args.in.args[1].size = newent->d_name.len + 1;
+	args.in.args[1].value = newent->d_name.name;
+	err = create_new_entry(fc, &args, newdir, newent, inode->i_mode);
+	if (!err) {
+		struct fuse_inode *fi = get_fuse_inode(inode);
+
+		spin_lock(&fi->lock);
+		inc_nlink(inode);
+		spin_unlock(&fi->lock);
+	}
+	return err;
+}
+
+static void fuse_fillattr(struct inode *inode, struct fuse_attr *attr,
+			  struct kstat *stat)
+{
+	unsigned int blkbits;
+	struct fuse_conn *fc = get_fuse_conn(inode);
+
+	stat->dev = inode->i_sb->s_dev;
+	stat->ino = attr->ino;
+	stat->mode = (inode->i_mode & S_IFMT) | (attr->mode & 07777);
+	stat->nlink = attr->nlink;
+	stat->uid = make_kuid(fc->user_ns, attr->uid);
+	stat->gid = make_kgid(fc->user_ns, attr->gid);
+	stat->rdev = inode->i_rdev;
+	stat->atime.tv_sec = attr->atime;
+	stat->atime.tv_nsec = attr->atimensec;
+	stat->mtime.tv_sec = attr->mtime;
+	stat->mtime.tv_nsec = attr->mtimensec;
+	stat->ctime.tv_sec = attr->ctime;
+	stat->ctime.tv_nsec = attr->ctimensec;
+	stat->size = attr->size;
+	stat->blocks = attr->blocks;
+
+	if (attr->blksize != 0)
+		blkbits = ilog2(attr->blksize);
+	else
+		blkbits = inode->i_sb->s_blocksize_bits;
+
+	stat->blksize = 1 << blkbits;
+}
+
+static int fuse_do_getattr(struct inode *inode, struct kstat *stat,
+			   struct file *file)
+{
+	int err;
+	struct fuse_getattr_in inarg;
+	struct fuse_attr_out outarg;
+	struct fuse_conn *fc = get_fuse_conn(inode);
+	FUSE_ARGS(args);
+
+
+	memset(&inarg, 0, sizeof(inarg));
+	memset(&outarg, 0, sizeof(outarg));
+	/* Directories have separate file-handle space */
+	if (file && S_ISREG(inode->i_mode)) {
+		struct fuse_file *ff = file->private_data;
+
+		inarg.getattr_flags |= FUSE_GETATTR_FH;
+		inarg.fh = ff->fh;
+	}
+	args.in.h.opcode = FUSE_GETATTR;
+	args.in.h.nodeid = get_node_id(inode);
+	args.in.numargs = 1;
+	args.in.args[0].size = sizeof(inarg);
+	args.in.args[0].value = &inarg;
+	args.out.numargs = 1;
+	args.out.args[0].size = sizeof(outarg);
+	args.out.args[0].value = &outarg;
+	err = fuse2_simple_request(fc, &args);
+	if (!err) {
+		if ((inode->i_mode ^ outarg.attr.mode) & S_IFMT) {
+			make_bad_inode(inode);
+			err = -EIO;
+		} else {
+			fuse2_change_attributes(inode, &outarg.attr);
+			if (stat)
+				fuse_fillattr(inode, &outarg.attr, stat);
+		}
+	}
+	return err;
+}
+
+int fuse2_update_attributes(struct inode *inode, struct file *file)
+{
+	return fuse_do_getattr(inode, NULL, file);
+}
+
+/*
+ * Calling into a user-controlled filesystem gives the filesystem
+ * daemon ptrace-like capabilities over the current process.  This
+ * means, that the filesystem daemon is able to record the exact
+ * filesystem operations performed, and can also control the behavior
+ * of the requester process in otherwise impossible ways.  For example
+ * it can delay the operation for arbitrary length of time allowing
+ * DoS against the requester.
+ *
+ * For this reason only those processes can call into the filesystem,
+ * for which the owner of the mount has ptrace privilege.  This
+ * excludes processes started by other users, suid or sgid processes.
+ */
+int fuse2_allow_current_process(struct fuse_conn *fc)
+{
+	return current_in_userns(fc->user_ns);
+}
+
+static int fuse_permission(struct inode *inode, int mask)
+{
+	struct fuse_conn *fc = get_fuse_conn(inode);
+	int err;
+
+	if (mask & MAY_NOT_BLOCK)
+		return -ECHILD;
+
+	if (!fuse2_allow_current_process(fc))
+		return -EACCES;
+
+	err = fuse_do_getattr(inode, NULL, NULL);
+	if (err)
+		return err;
+
+	return generic_permission(inode, mask);
+}
+
+static int fuse_readlink_page(struct inode *inode, struct page *page)
+{
+	struct fuse_conn *fc = get_fuse_conn(inode);
+	struct fuse_req *req;
+	unsigned int count = PAGE_SIZE - 1;
+	int err;
+
+	req = fuse2_get_req(fc, 1);
+	if (IS_ERR(req))
+		return PTR_ERR(req);
+
+	req->inline_inlen = sizeof(struct fuse_in_header);
+
+	req->mand_outlen = req->inline_outlen = sizeof(struct fuse_out_header);
+	req->max_outlen = req->mand_outlen + count;
+
+	req->num_pages = 1;
+	get_page(req->pages[0] = page);
+	req->page_descs[0].length = count;
+	req->inh.opcode = FUSE_READLINK;
+	req->inh.nodeid = get_node_id(inode);
+	req->inh.len = req->inline_inlen;
+
+	__set_bit(FR_ZEROTAIL, &req->flags);
+	err = fuse2_request_send(fc, req);
+	if (!err) {
+		char *link = page_address(page);
+		size_t len = req->outh.len - req->mand_outlen;
+
+		WARN_ON(len >= PAGE_SIZE);
+		link[len] = '\0';
+	}
+
+	fuse2_put_request(req);
+
+	return err;
+}
+
+static const char *fuse_get_link(struct dentry *dentry, struct inode *inode,
+				 struct delayed_call *callback)
+{
+	struct page *page;
+	int err;
+
+	err = -EIO;
+	if (is_bad_inode(inode))
+		goto out_err;
+
+	err = -ECHILD;
+	if (!dentry)
+		goto out_err;
+
+	page = alloc_page(GFP_KERNEL);
+	err = -ENOMEM;
+	if (!page)
+		goto out_err;
+
+	err = fuse_readlink_page(inode, page);
+	if (err) {
+		put_page(page);
+		goto out_err;
+	}
+
+	set_delayed_call(callback, page_put_link, page);
+
+	return page_address(page);
+
+out_err:
+	return ERR_PTR(err);
+}
+
+static int fuse_dir_open(struct inode *inode, struct file *file)
+{
+	return fuse2_open_common(inode, file, true);
+}
+
+static int fuse_dir_release(struct inode *inode, struct file *file)
+{
+	struct fuse_conn *fc = get_fuse_conn(inode);
+	struct fuse_file *ff = file->private_data;
+
+	fuse2_release_common(fc, ff, file->f_flags, (fl_owner_t) file, true);
+
+	return 0;
+}
+
+static int fuse_dir_fsync(struct file *file, loff_t start, loff_t end,
+			  int datasync)
+{
+	struct inode *inode = file->f_mapping->host;
+	struct fuse_conn *fc = get_fuse_conn(inode);
+	int err;
+
+	if (is_bad_inode(inode))
+		return -EIO;
+
+	if (fc->no_fsyncdir)
+		return 0;
+
+	inode_lock(inode);
+	err = fuse2_fsync_common(file, start, end, datasync, FUSE_FSYNCDIR);
+	if (err == -ENOSYS) {
+		fc->no_fsyncdir = 1;
+		err = 0;
+	}
+	inode_unlock(inode);
+
+	return err;
+}
+
+static bool update_mtime(unsigned ivalid)
+{
+	/* Always update if mtime is explicitly set  */
+	if (ivalid & ATTR_MTIME_SET)
+		return true;
+
+	/* If it's an open(O_TRUNC) or an ftruncate(), don't update */
+	if ((ivalid & ATTR_SIZE) && (ivalid & (ATTR_OPEN | ATTR_FILE)))
+		return false;
+
+	/* In all other cases update */
+	return true;
+}
+
+static void iattr_to_fattr(struct fuse_conn *fc, struct iattr *iattr,
+			   struct fuse_setattr_in *arg)
+{
+	unsigned ivalid = iattr->ia_valid;
+
+	if (ivalid & ATTR_MODE)
+		arg->valid |= FATTR_MODE,   arg->mode = iattr->ia_mode;
+	if (ivalid & ATTR_UID)
+		arg->valid |= FATTR_UID,    arg->uid = from_kuid(fc->user_ns, iattr->ia_uid);
+	if (ivalid & ATTR_GID)
+		arg->valid |= FATTR_GID,    arg->gid = from_kgid(fc->user_ns, iattr->ia_gid);
+	if (ivalid & ATTR_SIZE)
+		arg->valid |= FATTR_SIZE,   arg->size = iattr->ia_size;
+	if (ivalid & ATTR_ATIME) {
+		arg->valid |= FATTR_ATIME;
+		arg->atime = iattr->ia_atime.tv_sec;
+		arg->atimensec = iattr->ia_atime.tv_nsec;
+		if (!(ivalid & ATTR_ATIME_SET))
+			arg->valid |= FATTR_ATIME_NOW;
+	}
+	if ((ivalid & ATTR_MTIME) && update_mtime(ivalid)) {
+		arg->valid |= FATTR_MTIME;
+		arg->mtime = iattr->ia_mtime.tv_sec;
+		arg->mtimensec = iattr->ia_mtime.tv_nsec;
+		if (!(ivalid & ATTR_MTIME_SET))
+			arg->valid |= FATTR_MTIME_NOW;
+	}
+}
+
+static void fuse_setattr_fill(struct fuse_conn *fc, struct fuse_args *args,
+			      struct inode *inode,
+			      struct fuse_setattr_in *inarg_p,
+			      struct fuse_attr_out *outarg_p)
+{
+	args->in.h.opcode = FUSE_SETATTR;
+	args->in.h.nodeid = get_node_id(inode);
+	args->in.numargs = 1;
+	args->in.args[0].size = sizeof(*inarg_p);
+	args->in.args[0].value = inarg_p;
+	args->out.numargs = 1;
+	args->out.args[0].size = sizeof(*outarg_p);
+	args->out.args[0].value = outarg_p;
+}
+
+/*
+ * Set attributes, and at the same time refresh them.
+ *
+ * Truncation is slightly complicated, because the 'truncate' request
+ * may fail, in which case we don't want to touch the mapping.
+ * vmtruncate() doesn't allow for this case, so do the rlimit checking
+ * and the actual truncation by hand.
+ */
+static int fuse_do_setattr(struct dentry *dentry, struct iattr *attr,
+			   struct file *file)
+{
+	struct inode *inode = d_inode(dentry);
+	struct fuse_conn *fc = get_fuse_conn(inode);
+	struct fuse_inode *fi = get_fuse_inode(inode);
+	FUSE_ARGS(args);
+	struct fuse_setattr_in inarg;
+	struct fuse_attr_out outarg;
+	bool is_truncate = false;
+	loff_t oldsize;
+	int err;
+
+	err = setattr_prepare(dentry, attr);
+	if (err)
+		return err;
+
+	if (attr->ia_valid & ATTR_OPEN) {
+		/* This is coming from open(..., ... | O_TRUNC); */
+		WARN_ON(!(attr->ia_valid & ATTR_SIZE));
+		WARN_ON(attr->ia_size != 0);
+
+		/*
+		 * No need to send request to userspace, since actual
+		 * truncation has already been done by OPEN.  But still
+		 * need to truncate page cache.
+		 */
+		truncate_pagecache(inode, 0);
+		return 0;
+	}
+
+	if (attr->ia_valid & ATTR_SIZE) {
+		if (WARN_ON(!S_ISREG(inode->i_mode)))
+			return -EIO;
+		is_truncate = true;
+	}
+
+	memset(&inarg, 0, sizeof(inarg));
+	memset(&outarg, 0, sizeof(outarg));
+	iattr_to_fattr(fc, attr, &inarg);
+	if (file) {
+		struct fuse_file *ff = file->private_data;
+		inarg.valid |= FATTR_FH;
+		inarg.fh = ff->fh;
+	}
+	if (attr->ia_valid & ATTR_SIZE) {
+		/* For mandatory locking in truncate */
+		inarg.valid |= FATTR_LOCKOWNER;
+		inarg.lock_owner = fuse2_lock_owner_id(fc, current->files);
+	}
+	fuse_setattr_fill(fc, &args, inode, &inarg, &outarg);
+	err = fuse2_simple_request(fc, &args);
+	if (err)
+		goto error;
+
+	if ((inode->i_mode ^ outarg.attr.mode) & S_IFMT) {
+		make_bad_inode(inode);
+		err = -EIO;
+		goto error;
+	}
+
+	spin_lock(&fi->lock);
+	fuse2_change_attributes_common(inode, &outarg.attr);
+	oldsize = inode->i_size;
+	i_size_write(inode, outarg.attr.size);
+	spin_unlock(&fi->lock);
+
+	/*
+	 * Only call invalidate_inode_pages2() after removing
+	 * FUSE_NOWRITE, otherwise fuse_launder_page() would deadlock.
+	 */
+	if (S_ISREG(inode->i_mode) && oldsize != outarg.attr.size) {
+		truncate_pagecache(inode, outarg.attr.size);
+		invalidate_inode_pages2(inode->i_mapping);
+	}
+
+	return 0;
+
+error:
+	return err;
+}
+
+static int fuse_setattr(struct dentry *entry, struct iattr *attr)
+{
+	struct inode *inode = d_inode(entry);
+	struct file *file = (attr->ia_valid & ATTR_FILE) ? attr->ia_file : NULL;
+
+	if (!fuse2_allow_current_process(get_fuse_conn(inode)))
+		return -EACCES;
+
+	if (attr->ia_valid & (ATTR_KILL_SUID | ATTR_KILL_SGID)) {
+		attr->ia_valid &= ~(ATTR_KILL_SUID | ATTR_KILL_SGID |
+				    ATTR_MODE);
+	}
+	if (!attr->ia_valid)
+		return 0;
+
+	return fuse_do_setattr(entry, attr, file);
+}
+
+static int fuse_getattr(const struct path *path, struct kstat *stat,
+			u32 request_mask, unsigned int flags)
+{
+	struct inode *inode = d_inode(path->dentry);
+	struct fuse_conn *fc = get_fuse_conn(inode);
+
+	if (!fuse2_allow_current_process(fc))
+		return -EACCES;
+
+	return fuse_do_getattr(inode, stat, NULL);
+}
+
+static const struct inode_operations fuse_dir_inode_operations = {
+	.lookup		= fuse_lookup,
+	.mkdir		= fuse_mkdir,
+	.symlink	= fuse_symlink,
+	.unlink		= fuse_unlink,
+	.rmdir		= fuse_rmdir,
+	.rename		= fuse_rename2,
+	.link		= fuse_link,
+	.setattr	= fuse_setattr,
+	.create		= fuse_create,
+	.atomic_open	= fuse_atomic_open,
+	.mknod		= fuse_mknod,
+	.permission	= fuse_permission,
+	.getattr	= fuse_getattr,
+	.listxattr	= fuse2_listxattr,
+	.get_acl	= fuse2_get_acl,
+	.set_acl	= fuse2_set_acl,
+};
+
+static const struct file_operations fuse_dir_operations = {
+	.llseek		= generic_file_llseek,
+	.read		= generic_read_dir,
+	.iterate_shared	= fuse2_readdir,
+	.open		= fuse_dir_open,
+	.release	= fuse_dir_release,
+	.fsync		= fuse_dir_fsync,
+};
+
+static const struct inode_operations fuse_common_inode_operations = {
+	.setattr	= fuse_setattr,
+	.permission	= fuse_permission,
+	.getattr	= fuse_getattr,
+	.listxattr	= fuse2_listxattr,
+	.get_acl	= fuse2_get_acl,
+	.set_acl	= fuse2_set_acl,
+};
+
+static const struct inode_operations fuse_symlink_inode_operations = {
+	.setattr	= fuse_setattr,
+	.get_link	= fuse_get_link,
+	.getattr	= fuse_getattr,
+	.listxattr	= fuse2_listxattr,
+};
+
+void fuse2_init_common(struct inode *inode)
+{
+	inode->i_op = &fuse_common_inode_operations;
+}
+
+void fuse2_init_dir(struct inode *inode)
+{
+	inode->i_op = &fuse_dir_inode_operations;
+	inode->i_fop = &fuse_dir_operations;
+}
+
+void fuse2_init_symlink(struct inode *inode)
+{
+	inode->i_op = &fuse_symlink_inode_operations;
+	inode_nohighmem(inode);
+}
diff --git a/fs/fuse2/file.c b/fs/fuse2/file.c
new file mode 100644
index 0000000..9324262
--- /dev/null
+++ b/fs/fuse2/file.c
@@ -0,0 +1,991 @@
+/*
+  FUSE: Filesystem in Userspace
+  Copyright (C) 2001-2008  Miklos Szeredi <miklos@szeredi.hu>
+
+  This program can be distributed under the terms of the GNU GPL.
+  See the file COPYING.
+*/
+
+#include "fuse_i.h"
+
+#include <linux/pagemap.h>
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/sched/signal.h>
+#include <linux/module.h>
+#include <linux/compat.h>
+#include <linux/swap.h>
+#include <linux/falloc.h>
+#include <linux/uio.h>
+
+static int fuse_send_open(struct fuse_conn *fc, u64 nodeid, struct file *file,
+			  int opcode, struct fuse_open_out *outargp)
+{
+	struct fuse_open_in inarg;
+	FUSE_ARGS(args);
+
+	memset(&inarg, 0, sizeof(inarg));
+	inarg.flags = file->f_flags & ~(O_CREAT | O_EXCL | O_NOCTTY);
+	args.in.h.opcode = opcode;
+	args.in.h.nodeid = nodeid;
+	args.in.numargs = 1;
+	args.in.args[0].size = sizeof(inarg);
+	args.in.args[0].value = &inarg;
+	args.out.numargs = 1;
+	args.out.args[0].size = sizeof(*outargp);
+	args.out.args[0].value = outargp;
+
+	return fuse2_simple_request(fc, &args);
+}
+
+struct fuse_file *fuse2_file_alloc(struct fuse_conn *fc)
+{
+	struct fuse_file *ff;
+
+	ff = kzalloc(sizeof(struct fuse_file), GFP_KERNEL);
+	if (unlikely(!ff))
+		return NULL;
+
+	ff->fc = fc;
+	ff->kh = atomic64_inc_return(&fc->khctr);
+
+	return ff;
+}
+
+void fuse2_file_free(struct fuse_file *ff)
+{
+	kfree(ff);
+}
+
+static int fuse_do_open(struct fuse_conn *fc, u64 nodeid, struct file *file,
+		 bool isdir)
+{
+	struct fuse_file *ff;
+	int opcode = isdir ? FUSE_OPENDIR : FUSE_OPEN;
+	struct fuse_open_out outarg;
+	int err;
+
+	ff = fuse2_file_alloc(fc);
+	if (!ff)
+		return -ENOMEM;
+
+	err = fuse_send_open(fc, nodeid, file, opcode, &outarg);
+	if (err) {
+		fuse2_file_free(ff);
+		return err;
+	}
+	ff->open_flags = outarg.open_flags;
+	ff->fh = outarg.fh;
+	ff->nodeid = nodeid;
+	file->private_data = ff;
+
+	return 0;
+}
+
+void fuse2_finish_open(struct inode *inode, struct file *file)
+{
+	struct fuse_inode *fi = get_fuse_inode(inode);
+
+	if (file->f_flags & O_TRUNC) {
+		spin_lock(&fi->lock);
+		i_size_write(inode, 0);
+		spin_unlock(&fi->lock);
+	}
+}
+
+int fuse2_open_common(struct inode *inode, struct file *file, bool isdir)
+{
+	struct fuse_conn *fc = get_fuse_conn(inode);
+	int err;
+
+	err = generic_file_open(inode, file);
+	if (err)
+		return err;
+
+	err = fuse_do_open(fc, get_node_id(inode), file, isdir);
+	if (!err)
+		fuse2_finish_open(inode, file);
+
+	return err;
+}
+
+void fuse2_release_common(struct fuse_conn *fc, struct fuse_file *ff,
+			 int flags, fl_owner_t id, bool isdir)
+{
+	struct fuse_release_in inarg;
+	FUSE_ARGS(args);
+
+	memset(&inarg, 0, sizeof(inarg));
+	inarg.fh = ff->fh;
+	inarg.flags = flags;
+	args.force = true;
+	args.in.h.opcode = isdir ? FUSE_RELEASEDIR : FUSE_RELEASE;
+	args.in.h.nodeid = ff->nodeid;
+	args.in.numargs = 1;
+	args.in.args[0].size = sizeof(inarg);
+	args.in.args[0].value = &inarg;
+
+	if (ff->flock) {
+		inarg.release_flags |= FUSE_RELEASE_FLOCK_UNLOCK;
+		inarg.lock_owner = fuse2_lock_owner_id(ff->fc, id);
+	}
+	fuse2_simple_request(fc, &args);
+
+	kfree(ff);
+}
+
+static int fuse_open(struct inode *inode, struct file *file)
+{
+	return fuse2_open_common(inode, file, false);
+}
+
+static int fuse_release(struct inode *inode, struct file *file)
+{
+	struct fuse_conn *fc = get_fuse_conn(inode);
+	struct fuse_file *ff = file->private_data;
+
+	fuse2_release_common(fc, ff, file->f_flags, (fl_owner_t) file, false);
+
+	/* return value is ignored by VFS */
+	return 0;
+}
+
+/*
+ * Scramble the ID space with XTEA, so that the value of the files_struct
+ * pointer is not exposed to userspace.
+ */
+u64 fuse2_lock_owner_id(struct fuse_conn *fc, fl_owner_t id)
+{
+	u32 *k = fc->scramble_key;
+	u64 v = (unsigned long) id;
+	u32 v0 = v;
+	u32 v1 = v >> 32;
+	u32 sum = 0;
+	int i;
+
+	for (i = 0; i < 32; i++) {
+		v0 += ((v1 << 4 ^ v1 >> 5) + v1) ^ (sum + k[sum & 3]);
+		sum += 0x9E3779B9;
+		v1 += ((v0 << 4 ^ v0 >> 5) + v0) ^ (sum + k[sum>>11 & 3]);
+	}
+
+	return (u64) v0 + ((u64) v1 << 32);
+}
+
+static int fuse_flush(struct file *file, fl_owner_t id)
+{
+	struct inode *inode = file_inode(file);
+	struct fuse_conn *fc = get_fuse_conn(inode);
+	struct fuse_file *ff = file->private_data;
+	struct fuse_flush_in inarg;
+	FUSE_ARGS(args);
+	int err;
+
+	if (is_bad_inode(inode))
+		return -EIO;
+
+	if (fc->no_flush)
+		return 0;
+
+	err = filemap_check_errors(file->f_mapping);
+	if (err)
+		return err;
+
+	memset(&inarg, 0, sizeof(inarg));
+	inarg.fh = ff->fh;
+	inarg.lock_owner = fuse2_lock_owner_id(fc, id);
+	args.force = true;
+	args.in.h.opcode = FUSE_FLUSH;
+	args.in.h.nodeid = get_node_id(inode);
+	args.in.numargs = 1;
+	args.in.args[0].size = sizeof(inarg);
+	args.in.args[0].value = &inarg;
+	err = fuse2_simple_request(fc, &args);
+	if (err == -ENOSYS) {
+		fc->no_flush = 1;
+		err = 0;
+	}
+	return err;
+}
+
+int fuse2_fsync_common(struct file *file, loff_t start, loff_t end,
+		      int datasync, int opcode)
+{
+	struct inode *inode = file->f_mapping->host;
+	struct fuse_conn *fc = get_fuse_conn(inode);
+	struct fuse_file *ff = file->private_data;
+	FUSE_ARGS(args);
+	struct fuse_fsync_in inarg;
+
+	memset(&inarg, 0, sizeof(inarg));
+	inarg.fh = ff->fh;
+	inarg.fsync_flags = datasync ? 1 : 0;
+	args.in.h.opcode = opcode;
+	args.in.h.nodeid = get_node_id(inode);
+	args.in.numargs = 1;
+	args.in.args[0].size = sizeof(inarg);
+	args.in.args[0].value = &inarg;
+	return fuse2_simple_request(fc, &args);
+}
+
+static int fuse_fsync(struct file *file, loff_t start, loff_t end,
+		      int datasync)
+{
+	struct inode *inode = file->f_mapping->host;
+	struct fuse_conn *fc = get_fuse_conn(inode);
+	int err;
+
+	if (is_bad_inode(inode))
+		return -EIO;
+
+	if (fc->no_fsync)
+		return 0;
+
+	inode_lock(inode);
+	err = fuse2_fsync_common(file, start, end, datasync, FUSE_FSYNC);
+	if (err == -ENOSYS) {
+		fc->no_fsync = 1;
+		err = 0;
+	}
+	inode_unlock(inode);
+
+	return err;
+}
+
+void fuse2_read_fill(struct fuse_req *req, struct fuse_read_in **p,
+		    struct file *file, loff_t pos, size_t count, int opcode)
+{
+	struct fuse_file *ff = file->private_data;
+	struct fuse_read_in *inarg;
+
+	req->inline_inlen = sizeof(struct fuse_in_header) + sizeof(*inarg);
+	BUILD_BUG_ON(req->inline_inlen > FUSE_REQ_INLINE_DATA);
+
+	req->mand_outlen = req->inline_outlen = sizeof(struct fuse_out_header);
+	req->max_outlen = req->mand_outlen + count;
+
+	inarg = (void *) req->inlinedata + sizeof(struct fuse_in_header);
+	inarg->fh = ff->fh;
+	inarg->offset = pos;
+	inarg->size = count;
+	inarg->flags = file->f_flags;
+	if (p)
+		*p = inarg;
+
+	req->inh.opcode = opcode;
+	req->inh.nodeid = ff->nodeid;
+	req->inh.len = req->inline_inlen;
+}
+
+static ssize_t fuse_send_read(struct fuse_req *req, struct kiocb *iocb,
+			      loff_t pos, size_t count)
+{
+	struct file *file = iocb->ki_filp;
+	struct fuse_file *ff = file->private_data;
+	struct fuse_conn *fc = ff->fc;
+	ssize_t res;
+
+	fuse2_read_fill(req, NULL, iocb->ki_filp, pos, count, FUSE_READ);
+	res = fuse2_request_send(fc, req);
+
+	return res ? res : req->outh.len - req->mand_outlen;
+}
+
+static int fuse_do_readpage(struct file *file, struct page *page)
+{
+	struct inode *inode = page->mapping->host;
+	struct fuse_conn *fc = get_fuse_conn(inode);
+	struct fuse_req *req;
+	loff_t pos = page_offset(page);
+	size_t count = PAGE_SIZE;
+	int err;
+
+	req = fuse2_get_req(fc, 1);
+	if (IS_ERR(req))
+		return PTR_ERR(req);
+
+	req->num_pages = 1;
+	get_page(req->pages[0] = page);
+	req->page_descs[0].length = count;
+	__set_bit(FR_ZEROTAIL, &req->flags);
+	fuse2_read_fill(req, NULL, file, pos, count, FUSE_READ);
+	err = fuse2_request_send(fc, req);
+
+	if (!err)
+		SetPageUptodate(page);
+
+	fuse2_put_request(req);
+
+	return err;
+}
+
+static int fuse_readpage(struct file *file, struct page *page)
+{
+	struct inode *inode = page->mapping->host;
+	int err;
+
+	err = -EIO;
+	if (is_bad_inode(inode))
+		goto out;
+
+	err = fuse_do_readpage(file, page);
+ out:
+	unlock_page(page);
+	return err;
+}
+
+static ssize_t fuse_send_write(struct fuse_req *req, struct kiocb *iocb,
+			       loff_t pos, size_t count)
+{
+	struct file *file = iocb->ki_filp;
+	struct fuse_file *ff = file->private_data;
+	struct fuse_conn *fc = ff->fc;
+	struct fuse_write_in *inarg;
+	struct fuse_write_out *outarg;
+	ssize_t res;
+
+	req->inline_inlen = sizeof(struct fuse_in_header) + sizeof(*inarg);
+	BUILD_BUG_ON(req->inline_inlen > FUSE_REQ_INLINE_DATA);
+
+	req->inline_outlen = sizeof(struct fuse_out_header) + sizeof(*outarg);
+	BUILD_BUG_ON(req->inline_outlen > FUSE_REQ_INLINE_DATA);
+	req->max_outlen = req->mand_outlen = req->inline_outlen;
+
+	inarg = (void *) req->inlinedata + sizeof(struct fuse_in_header); 
+	inarg->fh = ff->fh;
+	inarg->offset = pos;
+	inarg->size = count;
+	inarg->flags = file->f_flags;
+	if (iocb->ki_flags & IOCB_DSYNC)
+		inarg->flags |= O_DSYNC;
+	if (iocb->ki_flags & IOCB_SYNC)
+		inarg->flags |= O_SYNC;
+	if (!capable(CAP_FSETID))
+		inarg->write_flags |= FUSE_WRITE_KILL_PRIV;
+	req->inh.opcode = FUSE_WRITE;
+	req->inh.nodeid = ff->nodeid;
+	req->inh.len = req->inline_inlen + count;
+	res = fuse2_request_send(fc, req);
+	outarg = (void *) req->inlinedata + sizeof(struct fuse_out_header);
+
+	return res ? res : outarg->size;
+}
+
+static void fuse_write_update_size(struct inode *inode, loff_t pos)
+{
+	struct fuse_inode *fi = get_fuse_inode(inode);
+
+	spin_lock(&fi->lock);
+	if (pos > inode->i_size)
+		i_size_write(inode, pos);
+	spin_unlock(&fi->lock);
+}
+
+static inline void fuse_page_descs_length_init(struct fuse_req *req,
+		unsigned index, unsigned nr_pages)
+{
+	int i;
+
+	for (i = index; i < index + nr_pages; i++)
+		req->page_descs[i].length = PAGE_SIZE -
+			req->page_descs[i].offset;
+}
+
+static int fuse_get_user_pages(struct fuse_req *req, struct iov_iter *ii,
+			       size_t *nbytesp, int write)
+{
+	size_t nbytes = 0;  /* # bytes already packed in req */
+	ssize_t ret = 0;
+
+	while (nbytes < *nbytesp && req->num_pages < req->max_pages) {
+		unsigned npages;
+		size_t start;
+		ret = iov_iter_get_pages(ii, &req->pages[req->num_pages],
+					*nbytesp - nbytes,
+					req->max_pages - req->num_pages,
+					&start);
+		if (ret < 0)
+			break;
+
+		iov_iter_advance(ii, ret);
+		nbytes += ret;
+
+		ret += start;
+		npages = (ret + PAGE_SIZE - 1) / PAGE_SIZE;
+
+		req->page_descs[req->num_pages].offset = start;
+		fuse_page_descs_length_init(req, req->num_pages, npages);
+
+		req->num_pages += npages;
+		req->page_descs[req->num_pages - 1].length -=
+			(PAGE_SIZE - ret) & (PAGE_SIZE - 1);
+	}
+
+	*nbytesp = nbytes;
+
+	return ret < 0 ? ret : 0;
+}
+
+/* If set, it is WRITE; otherwise - READ */
+#define FUSE_DIO_WRITE (1 << 0)
+
+static ssize_t fuse_direct_io(struct kiocb *iocb, struct iov_iter *iter,
+			      int flags)
+{
+	int write = flags & FUSE_DIO_WRITE;
+	struct file *file = iocb->ki_filp;
+	struct fuse_file *ff = file->private_data;
+	struct fuse_conn *fc = ff->fc;
+	loff_t pos = iocb->ki_pos;
+	size_t count = iov_iter_count(iter);
+	ssize_t res = 0;
+	struct fuse_req *req;
+	bool should_dirty = !write && iter_is_iovec(iter);
+	unsigned int i;
+	int err = 0;
+
+	req = fuse2_get_req(fc, iov_iter_npages(iter, fc->max_pages));
+	if (IS_ERR(req))
+		return PTR_ERR(req);
+
+	while (count) {
+		ssize_t nres;
+		size_t nbytes = count;
+		err = fuse_get_user_pages(req, iter, &nbytes, write);
+		if (err && !nbytes)
+			break;
+
+		if (write)
+			nres = fuse_send_write(req, iocb, pos, nbytes);
+		else
+			nres = fuse_send_read(req, iocb, pos, nbytes);
+
+		if (should_dirty) {
+			for (i = 0; i < req->num_pages; i++)
+				set_page_dirty_lock(req->pages[i]);
+		}
+		if (nres < 0) {
+			err = nres;
+			break;
+		} else if (nres > nbytes) {
+			res = 0;
+			err = -EIO;
+			break;
+		}
+		count -= nres;
+		res += nres;
+		pos += nres;
+		if (nres != nbytes)
+			break;
+		if (count) {
+			fuse2_put_request(req);
+			req = fuse2_get_req(fc, iov_iter_npages(iter, fc->max_pages));
+			if (IS_ERR(req))
+				break;
+		}
+	}
+	if (!IS_ERR(req))
+		fuse2_put_request(req);
+
+	if (res > 0) {
+		iocb->ki_pos = pos;
+		return res;
+	}
+
+	return err;
+}
+
+static ssize_t fuse_read_buf(struct file *file, loff_t pos,
+			     void *buf, size_t len)
+{
+	struct fuse_conn *fc = get_fuse_conn(file_inode(file));
+	struct fuse_file *ff = file->private_data;
+	struct fuse_read_in inarg = {
+		.fh = ff->fh,
+		.offset = pos,
+		.size = len,
+		.flags = file->f_flags,
+	};
+	FUSE_ARGS(args);
+
+	args.in.h.opcode = FUSE_READ;
+	args.in.h.nodeid = ff->nodeid;
+	args.in.numargs = 1;
+	args.in.args[0].size = sizeof(inarg);
+	args.in.args[0].value = &inarg;
+	args.out.argvar = 1;
+	args.out.numargs = 1;
+	args.out.args[0].size = len;
+	args.out.args[0].value = buf;
+
+	return fuse2_simple_request(fc, &args);
+}
+
+struct fuse_kvec_ctx {
+	struct file *file;
+	size_t res;
+	loff_t pos;
+};
+
+static int fuse_read_kvec(struct kvec *vec, void *_ctx)
+{
+	struct fuse_kvec_ctx *ctx = _ctx;
+	ssize_t res;
+
+	while (vec->iov_len) {
+		size_t len = min(vec->iov_len, PAGE_SIZE);
+
+		res = fuse_read_buf(ctx->file, ctx->pos, vec->iov_base, len);
+		if (res <= 0)
+			return res;
+
+		ctx->res += res;
+		ctx->pos += res;
+
+		if (res < len)
+			break;
+
+		vec->iov_len -= res;
+		vec->iov_base += res;
+	}
+
+	return 0;
+}
+
+static int fuse_send_map(struct kiocb *iocb, size_t count,
+			 struct fuse_map_out *outarg)
+{
+	struct file *file = iocb->ki_filp;
+	struct fuse_conn *fc = get_fuse_conn(file_inode(file));
+	struct fuse_file *ff = file->private_data;
+	struct fuse_read_in inarg = {
+		.fh = ff->fh,
+		.offset = iocb->ki_pos,
+		.size = count,
+		.flags = file->f_flags,
+	};
+	FUSE_ARGS(args);
+
+	args.in.h.opcode = FUSE_MAP;
+	args.in.h.nodeid = ff->nodeid;
+	args.in.numargs = 1;
+	args.in.args[0].size = sizeof(inarg);
+	args.in.args[0].value = &inarg;
+	args.out.numargs = 1;
+	args.out.args[0].size = sizeof(*outarg);
+	args.out.args[0].value = outarg;
+
+	return fuse2_simple_request(fc, &args);
+}
+
+static ssize_t fuse_file_map_iter(struct kiocb *iocb, struct iov_iter *to)
+{
+	struct fuse_conn *fc = get_fuse_conn(file_inode(iocb->ki_filp));
+	struct fuse_map_out outarg;
+	struct file *mapfile;
+	ssize_t res, total = 0;
+	size_t count;
+	loff_t pos;
+
+	while ((count = iov_iter_count(to))) {
+		res = fuse_send_map(iocb, count, &outarg);
+		if (res || !outarg.size)
+			break;
+
+		res = -EBADF;
+		mapfile = fuse2_map_get(fc, outarg.mapfd);
+		if (!mapfile)
+			break;
+
+		iov_iter_truncate(to, outarg.size);
+		pos = outarg.offset;
+		res = vfs_iter_read(mapfile, to, &pos, /* FIXME */ 0);
+		fput(mapfile);
+		if (res < 0)
+			break;
+		iov_iter_reexpand(to, count - res);
+		if (res == 0)
+			break;
+
+		total += res;
+		iocb->ki_pos += res;
+	}
+
+	return total ?: res;
+}
+
+static ssize_t fuse_file_read_iter(struct kiocb *iocb, struct iov_iter *to)
+{
+	struct file *file = iocb->ki_filp;
+	struct fuse_file *ff = file->private_data;
+	struct inode *inode = file_inode(file);
+
+	if (is_bad_inode(inode))
+		return -EIO;
+
+	if (ff->open_flags & FOPEN_MAP)
+		return fuse_file_map_iter(iocb, to);
+
+	if (iov_iter_is_kvec(to)) {
+		struct fuse_kvec_ctx ctx = {
+			.file = file,
+			.pos = iocb->ki_pos
+		};
+		size_t len = iov_iter_count(to);
+		int err;
+
+		err = iov_iter_for_each_range(to, len, fuse_read_kvec, &ctx);
+		iocb->ki_pos = ctx.pos;
+
+		if (ctx.res > 0)
+			return ctx.res;
+
+		return err;
+	}
+
+	return fuse_direct_io(iocb, to, 0);
+}
+
+static ssize_t fuse_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
+{
+	struct file *file = iocb->ki_filp;
+	struct inode *inode = file_inode(file);
+	ssize_t res;
+
+	if (is_bad_inode(inode))
+		return -EIO;
+
+	/* Don't allow parallel writes to the same file */
+	inode_lock(inode);
+	res = generic_write_checks(iocb, from);
+	if (res > 0)
+		res = fuse_direct_io(iocb, from, FUSE_DIO_WRITE);
+	if (res > 0)
+		fuse_write_update_size(inode, iocb->ki_pos);
+	inode_unlock(inode);
+
+	return res;
+}
+
+static int fuse_file_mmap(struct file *file, struct vm_area_struct *vma)
+{
+	/* Can't provide the coherency needed for MAP_SHARED */
+	if (vma->vm_flags & VM_MAYSHARE)
+		return -ENODEV;
+
+	invalidate_inode_pages2(file->f_mapping);
+
+	return generic_file_mmap(file, vma);
+}
+
+static int convert_fuse_file_lock(struct fuse_conn *fc,
+				  const struct fuse_file_lock *ffl,
+				  struct file_lock *fl)
+{
+	switch (ffl->type) {
+	case F_UNLCK:
+		break;
+
+	case F_RDLCK:
+	case F_WRLCK:
+		if (ffl->start > OFFSET_MAX || ffl->end > OFFSET_MAX ||
+		    ffl->end < ffl->start)
+			return -EIO;
+
+		fl->fl_start = ffl->start;
+		fl->fl_end = ffl->end;
+
+		/*
+		 * Convert pid into init's pid namespace.  The locks API will
+		 * translate it into the caller's pid namespace.
+		 */
+		rcu_read_lock();
+		fl->fl_pid = pid_nr_ns(find_pid_ns(ffl->pid, fc->pid_ns), &init_pid_ns);
+		rcu_read_unlock();
+		break;
+
+	default:
+		return -EIO;
+	}
+	fl->fl_type = ffl->type;
+	return 0;
+}
+
+static void fuse_lk_fill(struct fuse_args *args, struct file *file,
+			 const struct file_lock *fl, int opcode, pid_t pid,
+			 int flock, struct fuse_lk_in *inarg)
+{
+	struct inode *inode = file_inode(file);
+	struct fuse_conn *fc = get_fuse_conn(inode);
+	struct fuse_file *ff = file->private_data;
+
+	memset(inarg, 0, sizeof(*inarg));
+	inarg->fh = ff->fh;
+	inarg->owner = fuse2_lock_owner_id(fc, fl->fl_owner);
+	inarg->lk.start = fl->fl_start;
+	inarg->lk.end = fl->fl_end;
+	inarg->lk.type = fl->fl_type;
+	inarg->lk.pid = pid;
+	if (flock)
+		inarg->lk_flags |= FUSE_LK_FLOCK;
+	args->in.h.opcode = opcode;
+	args->in.h.nodeid = get_node_id(inode);
+	args->in.numargs = 1;
+	args->in.args[0].size = sizeof(*inarg);
+	args->in.args[0].value = inarg;
+}
+
+static int fuse_getlk(struct file *file, struct file_lock *fl)
+{
+	struct inode *inode = file_inode(file);
+	struct fuse_conn *fc = get_fuse_conn(inode);
+	FUSE_ARGS(args);
+	struct fuse_lk_in inarg;
+	struct fuse_lk_out outarg;
+	int err;
+
+	fuse_lk_fill(&args, file, fl, FUSE_GETLK, 0, 0, &inarg);
+	args.out.numargs = 1;
+	args.out.args[0].size = sizeof(outarg);
+	args.out.args[0].value = &outarg;
+	err = fuse2_simple_request(fc, &args);
+	if (!err)
+		err = convert_fuse_file_lock(fc, &outarg.lk, fl);
+
+	return err;
+}
+
+static int fuse_setlk(struct file *file, struct file_lock *fl, int flock)
+{
+	struct inode *inode = file_inode(file);
+	struct fuse_conn *fc = get_fuse_conn(inode);
+	FUSE_ARGS(args);
+	struct fuse_lk_in inarg;
+	int opcode = (fl->fl_flags & FL_SLEEP) ? FUSE_SETLKW : FUSE_SETLK;
+	struct pid *pid = fl->fl_type != F_UNLCK ? task_tgid(current) : NULL;
+	pid_t pid_nr = pid_nr_ns(pid, fc->pid_ns);
+	int err;
+
+	if (fl->fl_lmops && fl->fl_lmops->lm_grant) {
+		/* NLM needs asynchronous locks, which we don't support yet */
+		return -ENOLCK;
+	}
+
+	/* Unlock on close is handled by the flush method */
+	if ((fl->fl_flags & FL_CLOSE_POSIX) == FL_CLOSE_POSIX)
+		return 0;
+
+	fuse_lk_fill(&args, file, fl, opcode, pid_nr, flock, &inarg);
+	err = fuse2_simple_request(fc, &args);
+
+	/* locking is restartable */
+	if (err == -EINTR)
+		err = -ERESTARTSYS;
+
+	return err;
+}
+
+static int fuse_file_lock(struct file *file, int cmd, struct file_lock *fl)
+{
+	struct inode *inode = file_inode(file);
+	struct fuse_conn *fc = get_fuse_conn(inode);
+	int err;
+
+	if (cmd == F_CANCELLK) {
+		err = 0;
+	} else if (cmd == F_GETLK) {
+		if (fc->no_lock) {
+			posix_test_lock(file, fl);
+			err = 0;
+		} else
+			err = fuse_getlk(file, fl);
+	} else {
+		if (fc->no_lock)
+			err = posix_lock_file(file, fl, NULL);
+		else
+			err = fuse_setlk(file, fl, 0);
+	}
+	return err;
+}
+
+static int fuse_file_flock(struct file *file, int cmd, struct file_lock *fl)
+{
+	struct inode *inode = file_inode(file);
+	struct fuse_conn *fc = get_fuse_conn(inode);
+	int err;
+
+	if (fc->no_flock) {
+		err = locks_lock_file_wait(file, fl);
+	} else {
+		struct fuse_file *ff = file->private_data;
+
+		/* emulate flock with POSIX locks */
+		ff->flock = true;
+		err = fuse_setlk(file, fl, 1);
+	}
+
+	return err;
+}
+
+static loff_t fuse_lseek(struct file *file, loff_t offset, int whence)
+{
+	struct inode *inode = file->f_mapping->host;
+	struct fuse_conn *fc = get_fuse_conn(inode);
+	struct fuse_file *ff = file->private_data;
+	FUSE_ARGS(args);
+	struct fuse_lseek_in inarg = {
+		.fh = ff->fh,
+		.offset = offset,
+		.whence = whence
+	};
+	struct fuse_lseek_out outarg;
+	int err;
+
+	if (fc->no_lseek)
+		goto fallback;
+
+	args.in.h.opcode = FUSE_LSEEK;
+	args.in.h.nodeid = ff->nodeid;
+	args.in.numargs = 1;
+	args.in.args[0].size = sizeof(inarg);
+	args.in.args[0].value = &inarg;
+	args.out.numargs = 1;
+	args.out.args[0].size = sizeof(outarg);
+	args.out.args[0].value = &outarg;
+	err = fuse2_simple_request(fc, &args);
+	if (err) {
+		if (err == -ENOSYS) {
+			fc->no_lseek = 1;
+			goto fallback;
+		}
+		return err;
+	}
+
+	return vfs_setpos(file, outarg.offset, inode->i_sb->s_maxbytes);
+
+fallback:
+	err = fuse2_update_attributes(inode, file);
+	if (!err)
+		return generic_file_llseek(file, offset, whence);
+	else
+		return err;
+}
+
+static loff_t fuse_file_llseek(struct file *file, loff_t offset, int whence)
+{
+	loff_t retval;
+	struct inode *inode = file_inode(file);
+
+	switch (whence) {
+	case SEEK_SET:
+	case SEEK_CUR:
+		 /* No i_mutex protection necessary for SEEK_CUR and SEEK_SET */
+		retval = generic_file_llseek(file, offset, whence);
+		break;
+	case SEEK_END:
+		inode_lock(inode);
+		retval = fuse2_update_attributes(inode, file);
+		if (!retval)
+			retval = generic_file_llseek(file, offset, whence);
+		inode_unlock(inode);
+		break;
+	case SEEK_HOLE:
+	case SEEK_DATA:
+		inode_lock(inode);
+		retval = fuse_lseek(file, offset, whence);
+		inode_unlock(inode);
+		break;
+	default:
+		retval = -EINVAL;
+	}
+
+	return retval;
+}
+
+static inline loff_t fuse_round_up(struct fuse_conn *fc, loff_t off)
+{
+	return round_up(off, fc->max_pages << PAGE_SHIFT);
+}
+
+static long fuse_file_fallocate(struct file *file, int mode, loff_t offset,
+				loff_t length)
+{
+	struct fuse_file *ff = file->private_data;
+	struct inode *inode = file_inode(file);
+	struct fuse_conn *fc = ff->fc;
+	FUSE_ARGS(args);
+	struct fuse_fallocate_in inarg = {
+		.fh = ff->fh,
+		.offset = offset,
+		.length = length,
+		.mode = mode
+	};
+	int err;
+	bool lock_inode = !(mode & FALLOC_FL_KEEP_SIZE) ||
+			   (mode & FALLOC_FL_PUNCH_HOLE);
+
+	if (mode & ~(FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE))
+		return -EOPNOTSUPP;
+
+	if (fc->no_fallocate)
+		return -EOPNOTSUPP;
+
+	if (lock_inode) {
+		inode_lock(inode);
+		if (mode & FALLOC_FL_PUNCH_HOLE) {
+			loff_t endbyte = offset + length - 1;
+			err = filemap_write_and_wait_range(inode->i_mapping,
+							   offset, endbyte);
+			if (err)
+				goto out;
+		}
+	}
+
+	args.in.h.opcode = FUSE_FALLOCATE;
+	args.in.h.nodeid = ff->nodeid;
+	args.in.numargs = 1;
+	args.in.args[0].size = sizeof(inarg);
+	args.in.args[0].value = &inarg;
+	err = fuse2_simple_request(fc, &args);
+	if (err == -ENOSYS) {
+		fc->no_fallocate = 1;
+		err = -EOPNOTSUPP;
+	}
+	if (err)
+		goto out;
+
+	if (mode & FALLOC_FL_PUNCH_HOLE)
+		truncate_pagecache_range(inode, offset, offset + length - 1);
+out:
+	if (lock_inode)
+		inode_unlock(inode);
+
+	return err;
+}
+
+static const struct file_operations fuse_file_operations = {
+	.llseek		= fuse_file_llseek,
+	.read_iter	= fuse_file_read_iter,
+	.write_iter	= fuse_file_write_iter,
+	.mmap		= fuse_file_mmap,
+	.open		= fuse_open,
+	.flush		= fuse_flush,
+	.release	= fuse_release,
+	.fsync		= fuse_fsync,
+	.lock		= fuse_file_lock,
+	.flock		= fuse_file_flock,
+	.splice_read	= generic_file_splice_read,
+	.splice_write	= iter_file_splice_write,
+	.fallocate	= fuse_file_fallocate,
+};
+
+static const struct address_space_operations fuse_file_aops  = {
+	.readpage	= fuse_readpage,
+};
+
+void fuse2_init_file_inode(struct inode *inode)
+{
+	inode->i_fop = &fuse_file_operations;
+	inode->i_data.a_ops = &fuse_file_aops;
+}
diff --git a/fs/fuse2/fudev.c b/fs/fuse2/fudev.c
new file mode 100644
index 0000000..bbf95583
--- /dev/null
+++ b/fs/fuse2/fudev.c
@@ -0,0 +1,1475 @@
+/*
+  FUSE: Filesystem in Userspace
+  Copyright (C) 2001-2008  Miklos Szeredi <miklos@szeredi.hu>
+
+  This program can be distributed under the terms of the GNU GPL.
+  See the file COPYING.
+*/
+
+#include "dev.h"
+
+#include <linux/module.h>
+#include <linux/uio.h>
+#include <linux/miscdevice.h>
+#include <linux/highmem.h>
+#include <linux/file.h>
+#include <linux/poll.h>
+#include <linux/cred.h>
+#include <linux/parser.h>
+
+MODULE_AUTHOR("Miklos Szeredi <miklos@szeredi.hu>");
+MODULE_DESCRIPTION("Filesystem in Userspace");
+MODULE_LICENSE("GPL");
+
+MODULE_ALIAS_MISCDEV(FUSE_MINOR);
+MODULE_ALIAS("devname:fuse");
+
+/* Ordinary requests have even IDs, while interrupts IDs are odd */
+#define FUSE_INT_REQ_BIT 1
+#define FUSE_REQ_ID_STEP (2 * (nr_cpumask_bits + 1))
+
+static DEFINE_MUTEX(fudev_mutex);
+
+struct fuse_iqueue {
+	/* Connection established */
+	unsigned connected;
+
+	/* Readers of the connection are waiting on this */
+	wait_queue_head_t waitq;
+
+	/* The next unique request id */
+	u64 reqctr;
+
+	/* The list of pending requests */
+	struct list_head pending;
+
+	/* Queue of pending forgets */
+	struct list_head forgets;
+
+	/* Batching of FORGET requests (positive indicates FORGET batch) */
+	int forget_batch;
+};
+
+#define FUSE_PQ_HASH_BITS 8
+#define FUSE_PQ_HASH_SIZE (1 << FUSE_PQ_HASH_BITS)
+
+struct fuse_pqueue {
+	/* Connection established */
+	unsigned connected;
+
+	/* Lock protecting accessess to  members of this structure */
+	spinlock_t lock;
+
+	/* Hash table of requests being processed */
+	struct list_head *processing;
+};
+
+/*
+ * Fuse device instance
+ */
+struct fudev {
+	/* refcount */
+	refcount_t count;
+
+	/* Processing queue */
+	struct fuse_pqueue pq;
+
+	/* Input queue */
+	struct fuse_iqueue iq;
+
+	/* Per-cpu mapped buffers */
+	struct fudev_aux * __percpu *aux_devs;
+
+	/* Our super block */
+	struct super_block *sb;
+};
+
+struct fudev_aux {
+	/* Fuse device this is bound to or NULL */
+	struct fudev *dev;
+
+	/* Request (if exists) */
+	struct fuse_req *req;
+
+	/* Memory mapped input buffer */
+	void *ibuf;
+
+	/* Size of input buffer */
+	size_t ibufsize;
+
+	/* Memory mapped output buffer */
+	void *obuf;
+
+	/* Size of output buffer */
+	size_t obufsize;
+
+	unsigned int cpu;
+
+	u64 reqctr;
+
+	int reserved;
+
+	int ready;
+
+	int done;
+
+	wait_queue_head_t waitq;
+
+};
+
+static const struct file_operations fudev_operations;
+static const struct file_operations fudev_aux_operations;
+
+static struct fudev *fudev(struct file *file)
+{
+	BUG_ON(file->f_op != &fudev_operations);
+	return file->private_data;
+}
+
+static struct fudev_aux *fudev_aux(struct file *file)
+{
+	BUG_ON(file->f_op != &fudev_aux_operations);
+	return file->private_data;
+}
+
+static u64 fuse_get_unique(struct fuse_iqueue *fiq)
+{
+	fiq->reqctr += FUSE_REQ_ID_STEP;
+	return fiq->reqctr;
+}
+
+static unsigned int fuse_req_hash(u64 unique)
+{
+	return hash_long(unique & ~FUSE_INT_REQ_BIT, FUSE_PQ_HASH_BITS);
+}
+
+static int fuse_read_pages(struct iov_iter *to, struct fuse_req *req,
+			   unsigned int nbytes)
+{
+	unsigned int i;
+
+	for (i = 0; i < req->num_pages && nbytes; i++) {
+		struct page *page = req->pages[i];
+		unsigned int offset = req->page_descs[i].offset;
+		unsigned int count = min(nbytes, req->page_descs[i].length);
+
+		if (copy_page_to_iter(page, offset, count, to) != count)
+			return -EFAULT;
+
+		nbytes -= count;
+	}
+	return 0;
+}
+
+static int fuse_read_one(struct iov_iter *iter, void *val, unsigned size)
+{
+	return _copy_to_iter(val, size, iter) == size ? 0 : -EFAULT;
+}
+
+static bool forget_pending(struct fuse_iqueue *fiq)
+{
+	return !list_empty(&fiq->forgets);
+}
+
+static bool request_pending(struct fuse_iqueue *fiq)
+{
+	return !list_empty(&fiq->pending) || forget_pending(fiq);
+}
+
+static void fuse_free_forgets(struct list_head *head)
+{
+	struct fuse_forget *forget, *next;
+
+	list_for_each_entry_safe(forget, next, head, list) {
+		list_del(&forget->list);
+		kfree(forget);
+	}
+}
+static ssize_t fuse_read_forget(struct fuse_iqueue *fiq, struct iov_iter *to)
+__releases(fiq->waitq.lock)
+{
+	int err;
+	const size_t one_len = sizeof(struct fuse_forget_one);
+	unsigned int count, max_forgets;
+	struct fuse_forget *forget;
+	size_t nbytes = iov_iter_count(to);
+	struct list_head head, *last;
+	struct fuse_batch_forget_in arg = { .count = 0 };
+	struct fuse_in_header ih = {
+		.opcode = FUSE_BATCH_FORGET,
+		.unique = fuse_get_unique(fiq),
+		.len = sizeof(ih) + sizeof(arg),
+	};
+
+	if (nbytes < ih.len) {
+		spin_unlock(&fiq->waitq.lock);
+		return -EINVAL;
+	}
+
+	max_forgets = (nbytes - ih.len) / one_len;
+
+	for (count = 0, last = &fiq->forgets;
+	     count < max_forgets && last->next != &fiq->forgets;
+	     count++, last = last->next);
+
+	list_cut_position(&head, &fiq->forgets, last);
+	spin_unlock(&fiq->waitq.lock);
+
+	arg.count = count;
+	ih.len += count * one_len;
+	err = fuse_read_one(to, &ih, sizeof(ih));
+	if (!err)
+		err = fuse_read_one(to, &arg, sizeof(arg));
+
+	if (!err) {
+		list_for_each_entry(forget, &head, list) {
+			err = fuse_read_one(to, &forget->forget_one, one_len);
+			if (err)
+				break;
+		}
+	}
+	fuse_free_forgets(&head);
+
+	if (err)
+		return err;
+
+	return ih.len;
+}
+
+/*
+ * This function is called when a request is finished.  Either a reply
+ * has arrived or it was aborted (and not yet sent) or some error
+ * occurred during communication with userspace, or the device file
+ * was closed.  The requester thread is woken up (if still waiting),
+ * the 'end' callback is called if given, else the reference to the
+ * request is released
+ */
+static void request_end(struct fuse_req *req)
+{
+	if (!test_and_set_bit(FR_FINISHED, &req->flags)) {
+		/* Wake up waiter sleeping in request_wait_answer() */
+		wake_up(&req->waitq);
+	}
+}
+
+static void fuse_io_end(struct fuse_pqueue *fpq, struct fuse_req *req, int err)
+{
+	if (err) {
+		spin_lock(&fpq->lock);
+		if (fpq->connected)
+			req->outh.error = -EIO;
+		spin_unlock(&fpq->lock);
+	}
+	request_end(req);
+
+	spin_lock(&fpq->lock);
+	list_del_init(&req->list);
+	spin_unlock(&fpq->lock);
+
+	fuse2_put_request(req);
+}
+
+
+/* Look up request on processing list by unique ID */
+static struct fuse_req *request_find(struct fuse_pqueue *fpq, u64 unique)
+{
+	unsigned int hash = fuse_req_hash(unique);
+	struct fuse_req *req;
+
+	list_for_each_entry(req, &fpq->processing[hash], list) {
+		if (req->inh.unique == unique)
+			return req;
+	}
+	return NULL;
+}
+
+static int copy_out_args(struct iov_iter *from, struct fuse_req *req)
+{
+	bool zeroing = test_bit(FR_ZEROTAIL, &req->flags);
+	unsigned int nbytes = iov_iter_count(from);
+	const unsigned int hlen = sizeof(struct fuse_out_header);
+	unsigned int thislen = req->inline_outlen - hlen;
+	void *arg = req->inlinedata + hlen;
+	unsigned int i;
+	size_t ret;
+
+	if (req->outh.error)
+		return nbytes > 0 ? -EINVAL : 0;
+
+	if (nbytes < req->mand_outlen - hlen || nbytes > req->max_outlen - hlen)
+		return -EINVAL;
+
+	thislen = min(thislen, nbytes);
+
+	ret = _copy_from_iter(arg, thislen, from);
+	if (ret != thislen)
+		return -EFAULT;
+
+	nbytes -= thislen;
+	for (i = 0; i < req->num_pages; i++) {
+		unsigned offset = req->page_descs[i].offset;
+		unsigned count = min(nbytes, req->page_descs[i].length);
+		struct page *page = req->pages[i];
+
+		if (zeroing && count < PAGE_SIZE)
+			clear_highpage(page);
+
+		if (!count) {
+			if (!zeroing)
+				break;
+
+			flush_dcache_page(page);
+			continue;
+		}
+		ret = copy_page_from_iter(page, offset, count, from);
+		if (ret != count)
+			return -EFAULT;
+
+		flush_dcache_page(page);
+		nbytes -= count;
+	}
+	WARN_ON(nbytes);
+
+	return 0;
+}
+
+/*
+ * Read a single request into the userspace filesystem's buffer.  This
+ * function waits until a request is available, then removes it from
+ * the pending list and copies request data to userspace buffer.  If
+ * no reply is needed (FORGET) or request has been aborted or there
+ * was an error during the copying then it's finished by calling
+ * request_end().  Otherwise add it to the processing list, and set
+ * the 'sent' flag.
+ */
+static ssize_t fudev_read(struct kiocb *iocb, struct iov_iter *to)
+{
+	ssize_t err;
+	struct file *file = iocb->ki_filp;
+	struct fudev *fud = fudev(file);
+	struct fuse_iqueue *fiq = &fud->iq;
+	struct fuse_pqueue *fpq = &fud->pq;
+	struct fuse_req *req;
+	unsigned int reqsize, hash;
+
+restart:
+	spin_lock(&fiq->waitq.lock);
+	err = -EAGAIN;
+	if ((file->f_flags & O_NONBLOCK) && fiq->connected &&
+	    !request_pending(fiq))
+		goto err_unlock;
+
+	err = wait_event_interruptible_exclusive_locked(fiq->waitq,
+				!fiq->connected || request_pending(fiq));
+	if (err)
+		goto err_unlock;
+
+	if (!fiq->connected) {
+		err = -ENODEV;
+		goto err_unlock;
+	}
+
+	if (forget_pending(fiq)) {
+		if (list_empty(&fiq->pending) || fiq->forget_batch-- > 0)
+			return fuse_read_forget(fiq, to);
+
+		if (fiq->forget_batch <= -8)
+			fiq->forget_batch = 16;
+	}
+
+	req = list_entry(fiq->pending.next, struct fuse_req, list);
+	clear_bit(FR_PENDING, &req->flags);
+
+	hash = fuse_req_hash(req->inh.unique);
+	spin_lock(&fpq->lock);
+	list_move_tail(&req->list, &fpq->processing[hash]);
+	spin_unlock(&fpq->lock);
+
+	spin_unlock(&fiq->waitq.lock);
+
+	reqsize = req->inh.len;
+
+	/* If request is too large, reply with an error and restart the read */
+	if (iov_iter_count(to) < reqsize) {
+		req->outh.error = -EIO;
+		/* SETXATTR is special, since it may contain too large data */
+		if (req->inh.opcode == FUSE_SETXATTR)
+			req->outh.error = -E2BIG;
+		request_end(req);
+		fuse2_put_request(req);
+		goto restart;
+	}
+
+
+	err = fuse_read_one(to, req->inlinedata, req->inline_inlen);
+	if (!err && reqsize > req->inline_inlen)
+		err = fuse_read_pages(to, req, reqsize - req->inline_inlen);
+
+	if (!err && test_bit(FR_ISREPLY, &req->flags)) {
+		set_bit(FR_SENT, &req->flags);
+		return reqsize;
+	}
+
+	fuse_io_end(fpq, req, err);
+	if (err)
+		return err;
+	return reqsize;
+
+err_unlock:
+	spin_unlock(&fiq->waitq.lock);
+	return err;
+}
+
+/*
+ * Write a single reply to a request.  First the header is copied from
+ * the write buffer.  The request is then searched on the processing
+ * list by the unique ID found in the header.  If found, then remove
+ * it from the list and copy the rest of the buffer to the request.
+ * The request is finished by calling request_end()
+ */
+static ssize_t fudev_write(struct kiocb *iocb, struct iov_iter *from)
+{
+	int err;
+	unsigned int nbytes = iov_iter_count(from);
+	struct fudev *fud = fudev(iocb->ki_filp);
+	struct fuse_pqueue *fpq = &fud->pq;
+	struct fuse_req *req;
+	struct fuse_out_header oh;
+
+	if (nbytes < sizeof(struct fuse_out_header))
+		return -EINVAL;
+
+	if (_copy_from_iter(&oh, sizeof(oh), from) != sizeof(oh))
+		return -EFAULT;
+
+	if (oh.len != nbytes)
+		return -EINVAL;
+
+	/*
+	 * Zero oh.unique indicates unsolicited notification message
+	 * and error contains notification code.
+	 */
+	if (!oh.unique)
+		return -EINVAL;
+
+	if (oh.error <= -1000 || oh.error > 0)
+		return -EINVAL;
+
+	spin_lock(&fpq->lock);
+	req = request_find(fpq, oh.unique & ~FUSE_INT_REQ_BIT);
+	if (!req || !test_bit(FR_SENT, &req->flags)) {
+		spin_unlock(&fpq->lock);
+		return -ENOENT;
+	}
+	clear_bit(FR_SENT, &req->flags);
+	req->outh = oh;
+	spin_unlock(&fpq->lock);
+
+	err = copy_out_args(from, req);
+
+	fuse_io_end(fpq, req, err);
+	if (err)
+		return err;
+	return nbytes;
+}
+
+static void fuse_pqueue_init(struct fuse_pqueue *fpq)
+{
+	unsigned int i;
+
+	spin_lock_init(&fpq->lock);
+	for (i = 0; i < FUSE_PQ_HASH_SIZE; i++)
+		INIT_LIST_HEAD(&fpq->processing[i]);
+	fpq->connected = 1;
+}
+
+static void fuse_iqueue_init(struct fuse_iqueue *fiq)
+{
+	init_waitqueue_head(&fiq->waitq);
+	INIT_LIST_HEAD(&fiq->pending);
+	INIT_LIST_HEAD(&fiq->forgets);
+	fiq->connected = 1;
+}
+
+static void fudev_put(void *priv)
+{
+	struct fudev *fud = priv;
+
+	if (refcount_dec_and_test(&fud->count)) {
+		kfree(fud->pq.processing);
+		kfree(fud);
+	}
+}
+
+static int fudev_open(struct inode *inode, struct file *file)
+{
+	struct fudev *fud;
+	struct list_head *pq;
+
+	fud = kzalloc(sizeof(struct fudev), GFP_KERNEL);
+	if (!fud)
+		goto nomem;
+
+	pq = kcalloc(FUSE_PQ_HASH_SIZE, sizeof(struct list_head), GFP_KERNEL);
+	if (!pq)
+		goto nomem;
+
+	refcount_set(&fud->count, 1);
+	fud->pq.processing = pq;
+	fuse_pqueue_init(&fud->pq);
+	fuse_iqueue_init(&fud->iq);
+
+	file->private_data = fud;
+
+	return 0;
+
+nomem:
+	kfree(fud);
+	return -ENOMEM;
+}
+
+static __poll_t fudev_poll(struct file *file, poll_table *wait)
+{
+	__poll_t mask = EPOLLOUT | EPOLLWRNORM;
+	struct fudev *fud = fudev(file);
+	struct fuse_iqueue *fiq = &fud->iq;
+
+	poll_wait(file, &fiq->waitq, wait);
+
+	spin_lock(&fiq->waitq.lock);
+	if (!fiq->connected)
+		mask = EPOLLERR;
+	else if (request_pending(fiq))
+		mask |= EPOLLIN | EPOLLRDNORM;
+	spin_unlock(&fiq->waitq.lock);
+
+	return mask;
+}
+
+/* Abort all requests on the given list (pending or processing) */
+static void end_requests(struct list_head *head, spinlock_t *lock)
+{
+	struct fuse_req *req;
+
+	while (!list_empty(head)) {
+		req = list_entry(head->next, struct fuse_req, list);
+		list_del_init(&req->list);
+		req->outh.error = -ECONNABORTED;
+		clear_bit(FR_PENDING, &req->flags);
+		fuse2_get_request(req);
+		spin_unlock(lock);
+		request_end(req);
+		fuse2_put_request(req);
+		spin_lock(lock);
+	}
+}
+
+static void fudev_abort(void *priv)
+{
+	struct fudev *fud = priv;
+	struct fuse_iqueue *fiq;
+	struct fuse_pqueue *fpq;
+	unsigned int i;
+
+	fpq = &fud->pq;
+	spin_lock(&fpq->lock);
+	fpq->connected = 0;
+	for (i = 0; i < FUSE_PQ_HASH_SIZE; i++)
+		end_requests(&fpq->processing[i], &fpq->lock);
+	spin_unlock(&fpq->lock);
+
+	fiq = &fud->iq;
+	spin_lock(&fiq->waitq.lock);
+	fiq->connected = 0;
+
+	end_requests(&fiq->pending, &fiq->waitq.lock);
+	fuse_free_forgets(&fiq->forgets);
+	wake_up_all_locked(&fiq->waitq);
+	spin_unlock(&fiq->waitq.lock);
+}
+
+static int fudev_release(struct inode *inode, struct file *file)
+{
+	struct fudev *fud = fudev(file);
+
+	fudev_abort(fud);
+	fudev_put(fud);
+
+	return 0;
+}
+
+static long fudev_map_ioctl(struct fudev *fud, unsigned int cmd,
+			    unsigned long arg)
+{
+	struct file *file;
+
+	switch (cmd) {
+	case FUSE2_DEV_IOC_MAP_OPEN:
+		file = fget(arg);
+		if (!file)
+			return -EBADF;
+		return fuse2_map_open(fud->sb, file);
+
+	case FUSE2_DEV_IOC_MAP_CLOSE:
+		return fuse2_map_close(fud->sb, arg);
+
+	default:
+		return -ENOTTY;
+	}
+
+}
+
+static long fudev_ioctl(struct file *file, unsigned int cmd,
+			unsigned long arg)
+{
+	return fudev_map_ioctl(fudev(file), cmd, arg);
+}
+
+static const struct file_operations fudev_operations = {
+	.owner		= THIS_MODULE,
+	.open		= fudev_open,
+	.llseek		= no_llseek,
+	.read_iter	= fudev_read,
+	.write_iter	= fudev_write,
+	.poll		= fudev_poll,
+	.release	= fudev_release,
+	.unlocked_ioctl	= fudev_ioctl,
+};
+
+
+static int fudev_aux_open(struct inode *inode, struct file *file)
+{
+	struct fudev_aux *fux;
+
+	fux = kzalloc(sizeof(struct fudev_aux), GFP_KERNEL);
+	if (!fux)
+		return -ENOMEM;
+
+	init_waitqueue_head(&fux->waitq);
+	file->private_data = fux;
+
+	return 0;
+}
+
+static long fudev_aux_bind(struct file *file, unsigned long arg)
+{
+	int err;
+	int oldfd;
+	struct file *old;
+	struct fudev *fud;
+	struct fudev_aux *fux = fudev_aux(file);
+	const struct cpumask *cpumask = current->cpus_ptr;
+
+	if (get_user(oldfd, (__u32 __user *) arg) != 0)
+		return -EFAULT;
+
+	old = fget(oldfd);
+	if (!old)
+		return -EINVAL;
+
+	err = -EINVAL;
+	if (old->f_op != &fudev_operations ||
+	    old->f_cred->user_ns != file->f_cred->user_ns)
+		goto out_put_old;
+
+	fud = fudev(old);
+
+	mutex_lock(&fudev_mutex);
+	err = -EINVAL;
+	if (fux->dev)
+		goto out_unlock;
+
+	if (cpumask_weight(cpumask) == 1) {
+		unsigned int cpu;
+
+		if (!fud->aux_devs) {
+			fud->aux_devs = alloc_percpu(struct fudev_aux *);
+			err = -ENOMEM;
+			if (!fud->aux_devs)
+				goto out_unlock;
+		}
+		cpu = get_cpu();
+		fux->cpu = cpu;
+		fux->reqctr = cpu + 1;
+		pr_info("...binding to cpu %u\n", cpu);
+		WARN_ON(cpu != cpumask_first(cpumask));
+		*per_cpu_ptr(fud->aux_devs, cpu) = fux;
+		put_cpu();
+	}
+
+	refcount_inc(&fud->count);
+	fux->dev = fud;
+	err = 0;
+
+out_unlock:
+	mutex_unlock(&fudev_mutex);
+out_put_old:
+	fput(old);
+
+	return err;
+}
+
+static inline void fudev_barrier(void)
+{
+	/* FIXME: ??? */
+	barrier();
+}
+
+static void fudev_req_done(struct fudev_aux *fux)
+{
+	fux->ready = 0;
+	fudev_barrier();
+	fux->done = 1;
+	wake_up(&fux->waitq);
+}
+
+static long fudev_aux_proc(struct file *file, unsigned int cmd)
+{
+	struct fudev_aux *fux = fudev_aux(file);
+	int ret;
+
+	if (cpumask_weight(current->cpus_ptr) != 1 ||
+	    fux->cpu != smp_processor_id())
+		return -EINVAL;
+
+	if (cmd == FUSE2_DEV_IOC_PROC) {
+		fudev_req_done(fux);
+	}
+	ret = wait_event_interruptible(fux->waitq, fux->ready);
+	if (ret) {
+		/* Must report success of write! */
+		if (cmd == FUSE2_DEV_IOC_PROC)
+			return 0;
+		return ret;
+	}
+
+	return ((struct fuse_in_header *) fux->ibuf)->len;
+}
+
+static long fudev_aux_ioctl(struct file *file, unsigned int cmd,
+			    unsigned long arg)
+{
+	switch (cmd) {
+	case FUSE2_DEV_IOC_BIND:
+		return fudev_aux_bind(file, arg);
+
+	case FUSE2_DEV_IOC_PROC:
+	case FUSE2_DEV_IOC_READ:
+		return fudev_aux_proc(file, cmd);
+
+	case FUSE2_DEV_IOC_MAP_OPEN:
+	case FUSE2_DEV_IOC_MAP_CLOSE:
+		return fudev_map_ioctl(fudev_aux(file)->dev, cmd, arg);
+
+	default:
+		return -ENOTTY;
+	}
+}
+
+static int fudev_aux_mmap(struct file *file, struct vm_area_struct *vma)
+{
+	struct fudev_aux *fux = fudev_aux(file);
+	unsigned long length = vma->vm_end - vma->vm_start;
+	void **bufp;
+	size_t *bufsizep;
+ 	unsigned int off;
+	int err;
+
+	if (length != 0x2000 ||
+	    (vma->vm_pgoff != (FUSE2_MMAP_INBUF_OFFSET >> PAGE_SHIFT) &&
+	     vma->vm_pgoff != (FUSE2_MMAP_OUTBUF_OFFSET >> PAGE_SHIFT)))
+		return -EINVAL;
+
+	if (vma->vm_pgoff == (FUSE2_MMAP_INBUF_OFFSET >> PAGE_SHIFT)) {
+		bufp = &fux->ibuf;
+		bufsizep = &fux->ibufsize;
+	} else {
+		bufp = &fux->obuf;
+		bufsizep = &fux->obufsize;
+	}
+
+	if (!*bufp) {
+		*bufp = (void *)
+			__get_free_pages(GFP_KERNEL | __GFP_COMP |__GFP_ZERO,
+					 get_order(length));
+		if (!*bufp)
+			return -ENOMEM;
+		*bufsizep = length;
+	}
+
+	for (off = 0; off < length; off += PAGE_SIZE) {
+		struct page *page = virt_to_page(*bufp + off);
+
+		err = vm_insert_page(vma, vma->vm_start + off, page);
+		if (err)
+			return err;
+	}
+
+	vma->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP;
+
+	return 0;
+}
+
+static void fudev_aux_remove(void *info)
+{
+	struct fudev_aux *fux = info;
+	struct fudev *fud = fux->dev;
+
+	*per_cpu_ptr(fud->aux_devs, fux->cpu) = NULL;
+}
+
+static int fudev_aux_release(struct inode *inode, struct file *file)
+{
+	struct fudev_aux *fux = fudev_aux(file);
+	struct fudev *fud = fux->dev;
+	int err;
+
+	if (fud) {
+		/* FIXME: synchronize with fudev_simple_send() */
+		err = smp_call_function_single(fux->cpu, fudev_aux_remove,
+					       fux, true);
+		if (err)
+			fudev_aux_remove(fux);
+
+		fudev_put(fud);
+	}
+
+	free_pages((unsigned long) fux->ibuf, get_order(fux->ibufsize));
+	free_pages((unsigned long) fux->obuf, get_order(fux->obufsize));
+
+	kfree(fux);
+
+	return 0;
+}
+
+static ssize_t fudev_aux_read(struct kiocb *iocb, struct iov_iter *to)
+{
+	struct file *file = iocb->ki_filp;
+	struct fudev_aux *fux = fudev_aux(file);
+	struct fuse_req *req;
+	unsigned int reqsize;
+	int err;
+
+	if (cpumask_weight(current->cpus_ptr) != 1 ||
+	    fux->cpu != smp_processor_id())
+		return -EINVAL;
+
+restart:
+	err = wait_event_interruptible(fux->waitq, fux->ready);
+	if (err)
+		return err;
+
+	req = READ_ONCE(fux->req);
+	if (!req)
+		return -EIO;
+
+	reqsize = req->inh.len;
+
+	/* If request is too large, reply with an error and restart the read */
+	if (iov_iter_count(to) < reqsize) {
+		req->outh.error = -EIO;
+		/* SETXATTR is special, since it may contain too large data */
+		if (req->inh.opcode == FUSE_SETXATTR)
+			req->outh.error = -E2BIG;
+		fudev_req_done(fux);
+		goto restart;
+	}
+
+	err = fuse_read_one(to, req->inlinedata, req->inline_inlen);
+	if (!err && reqsize > req->inline_inlen)
+		err = fuse_read_pages(to, req, reqsize - req->inline_inlen);
+
+	if (err) {
+		req->outh.error = -EIO;
+		fudev_req_done(fux);
+		return err;
+	}
+
+	return reqsize;
+}
+
+static ssize_t fudev_aux_write(struct kiocb *iocb, struct iov_iter *from)
+{
+	struct file *file = iocb->ki_filp;
+	struct fudev_aux *fux = fudev_aux(file);
+	struct fuse_req *req;
+	unsigned int nbytes = iov_iter_count(from);
+	struct fuse_out_header oh;
+	int err;
+
+	if (cpumask_weight(current->cpus_ptr) != 1 ||
+	    fux->cpu != smp_processor_id())
+		return -EINVAL;
+
+	if (nbytes < sizeof(struct fuse_out_header))
+		return -EINVAL;
+
+	if (_copy_from_iter(&oh, sizeof(oh), from) != sizeof(oh))
+		return -EFAULT;
+
+	if (oh.len != nbytes)
+		return -EINVAL;
+
+	/*
+	 * Zero oh.unique indicates unsolicited notification message
+	 * and error contains notification code.
+	 */
+	if (!oh.unique)
+		return -EINVAL;
+
+	if (oh.error <= -1000 || oh.error > 0)
+		return -EINVAL;
+
+
+	req = READ_ONCE(fux->req);
+	if (!req)
+		return -EIO;
+
+	if (req->inh.unique != oh.unique)
+		return -EIO;
+
+	req->outh = oh;
+
+	err = copy_out_args(from, req);
+	if (err) {
+		req->outh.error = -EIO;
+		fudev_req_done(fux);
+		return err;
+	}
+
+	fudev_req_done(fux);
+
+	return nbytes;
+
+}
+
+static const struct file_operations fudev_aux_operations = {
+	.owner		= THIS_MODULE,
+	.open		= fudev_aux_open,
+	.llseek		= no_llseek,
+	.read_iter	= fudev_aux_read,
+	.write_iter	= fudev_aux_write,
+	.release	= fudev_aux_release,
+	.unlocked_ioctl	= fudev_aux_ioctl,
+	.mmap		= fudev_aux_mmap,
+};
+
+static struct miscdevice fuse_miscdevice = {
+	.minor = MISC_DYNAMIC_MINOR,
+	.name  = "fuse2",
+	.fops = &fudev_operations,
+};
+
+static struct miscdevice fuse_aux_miscdevice = {
+	.minor = MISC_DYNAMIC_MINOR,
+	.name  = "fuse2-aux",
+	.fops = &fudev_aux_operations,
+};
+
+static int fudev_queue_req(struct fuse_iqueue *fiq, struct fuse_req *req)
+{
+	int err;
+
+	spin_lock(&fiq->waitq.lock);
+
+	err = -ENOTCONN;
+	if (!fiq->connected)
+		goto unlock;
+
+	req->inh.unique = fuse_get_unique(fiq);
+	list_add_tail(&req->list, &fiq->pending);
+	wake_up_locked(&fiq->waitq);
+	/*
+	 * acquire extra reference, since request is still needed after
+	 * request_end()
+	 */
+	refcount_inc(&req->count);
+	err = 0;
+
+unlock:
+	spin_unlock(&fiq->waitq.lock);
+
+	return err;
+}
+
+static int fudev_wait_req(struct fuse_iqueue *fiq, struct fuse_req *req)
+{
+	int err;
+
+	if (!test_bit(FR_FORCE, &req->flags)) {
+		/* Only fatal signals may interrupt this */
+		err = wait_event_killable(req->waitq,
+					test_bit(FR_FINISHED, &req->flags));
+		if (!err)
+			return req->outh.error;
+
+		spin_lock(&fiq->waitq.lock);
+		/* Request is not yet in userspace, bail out */
+		if (test_bit(FR_PENDING, &req->flags)) {
+			list_del(&req->list);
+			spin_unlock(&fiq->waitq.lock);
+			fuse2_put_request(req);
+			return -EINTR;
+		}
+		spin_unlock(&fiq->waitq.lock);
+		if (test_bit(FR_KILLABLE, &req->flags))
+			return -EINTR;
+	}
+
+	/*
+	 * Either request is already in userspace, or it was forced.
+	 * Wait it out.
+	 */
+	wait_event(req->waitq, test_bit(FR_FINISHED, &req->flags));
+
+	return req->outh.error;
+}
+
+static void fudev_simple_copy_to_user(struct fudev_aux *fux,
+				      struct fuse_args *args,
+				      unsigned int inlen, u64 unique, uid_t uid,
+				      gid_t gid, pid_t pid)
+{
+	struct fuse_in_header *inh = fux->ibuf;
+	void *ptr = inh + 1;
+	struct fuse_in_arg *a;
+	unsigned int i;
+
+	inh->len = inlen;
+	inh->opcode = args->in.h.opcode;
+	inh->unique = unique;
+	inh->nodeid = args->in.h.nodeid;
+	inh->uid = uid;
+	inh->gid = gid;
+	inh->pid = pid;
+	inh->padding = 0;
+
+	ptr = inh + 1;
+
+	for (i = 0; i < args->in.numargs; i++) {
+		a = &args->in.args[i];
+		memcpy(ptr, a->value, a->size);
+		ptr += a->size;
+	}
+}
+
+static ssize_t fudev_simple_copy_from_user(struct fudev_aux *fux,
+					   struct fuse_args *args,
+					   unsigned int outlen, u64 unique)
+{
+	struct fuse_out_header *outh = fux->obuf;
+	void *ptr = outh + 1;
+	struct fuse_arg *a;
+	unsigned int i;
+	size_t rem;
+
+	if (outh->unique != unique)
+		return -EIO;
+
+	rem = outh->len;
+	if (rem > outlen)
+		return -EIO;
+
+	rem -= sizeof(*outh);
+
+	if (outh->error)
+		return outh->error;
+
+	for (i = 0; i < args->out.numargs; i++) {
+		a = &args->out.args[i];
+		/* FIXME: truncate to rem? */
+		if (a->value)
+			memcpy(a->value, ptr, a->size);
+		ptr += a->size;
+		rem -= a->size;
+	}
+	if (args->out.argvar)
+		return outh->len - sizeof(*outh);
+
+	return 0;
+}
+
+static int fudev_simple_wake_and_wait(struct fudev_aux *fux,
+				      struct fuse_args *args)
+{
+	wake_up(&fux->waitq);
+
+	/* FIXME: make aux dev requests killable */
+#if 0
+	if (!args->force && args->killable)
+		return wait_event_killable(fux->waitq, fux->done);
+#endif
+
+	wait_event(fux->waitq, fux->done);
+	return 0;
+}
+
+static unsigned len_args(unsigned numargs, struct fuse_arg *args)
+{
+	unsigned nbytes = 0;
+	unsigned i;
+
+	for (i = 0; i < numargs; i++)
+		nbytes += args[i].size;
+
+	return nbytes;
+}
+
+static ssize_t fudev_simple_send(void *priv, struct fuse_args *args, uid_t uid,
+				 gid_t gid, pid_t pid)
+{
+	unsigned int cpu;
+	struct fudev *fud = priv;
+	struct fudev_aux *fux;
+	unsigned int inlen = sizeof(struct fuse_in_header) +
+		len_args(args->in.numargs, (struct fuse_arg *) args->in.args);
+	unsigned int outlen = sizeof(struct fuse_out_header) +
+		len_args(args->out.numargs, args->out.args);
+	ssize_t ret;
+	u64 unique;
+	cpumask_t orig_mask;
+
+	cpu = get_cpu();
+	fux = *per_cpu_ptr(fud->aux_devs, cpu);
+	if (!fux || fux->reserved ||
+	    inlen > fux->ibufsize || outlen > fux->obufsize) {
+		put_cpu();
+		return -EPROBE_DEFER;
+	}
+	fux->reserved = 1;
+	fudev_barrier();
+	cpumask_copy(&orig_mask, current->cpus_ptr);
+	cpumask_copy(&current->cpus_mask, cpumask_of(cpu));
+	put_cpu();
+
+	unique = fux->reqctr;
+	fux->reqctr += FUSE_REQ_ID_STEP;
+
+	fudev_simple_copy_to_user(fux, args, inlen, unique, uid, gid, pid);
+	fux->done = 0;
+	fudev_barrier();
+	fux->ready = 1;
+
+	ret = fudev_simple_wake_and_wait(fux, args);
+	if (!ret)
+		ret = fudev_simple_copy_from_user(fux, args, outlen, unique);
+
+	fudev_barrier();
+	fux->reserved = 0;
+	cpumask_copy(&current->cpus_mask, &orig_mask);
+
+	return ret;
+}
+
+static int fudev_send_slow(struct fudev *fud, struct fuse_req *req)
+{
+	int err;
+
+	err = fudev_queue_req(&fud->iq, req);
+	if (!err)
+		err = fudev_wait_req(&fud->iq, req);
+
+	return err;
+}
+
+static int fudev_send_fast(struct fudev *fud, struct fuse_req *req)
+{
+ 	unsigned int cpu;
+	struct fudev_aux *fux;
+	ssize_t ret;
+	u64 unique;
+	cpumask_t orig_mask;
+	void *ptr, *addr;
+	struct fuse_out_header *outh;
+	struct fuse_page_desc *pd;
+	unsigned int i, thislen, rem;
+
+	cpu = get_cpu();
+	fux = *per_cpu_ptr(fud->aux_devs, cpu);
+	if (!fux || fux->reserved) {
+		put_cpu();
+		return -EPROBE_DEFER;
+	}
+
+	if (!fux->ibuf) {
+		fux->reserved = 1;
+		fudev_barrier();
+		req->inh.unique = unique = fux->reqctr;
+		fux->reqctr += FUSE_REQ_ID_STEP;
+		fux->req = req;
+		fux->done = 0;
+		fudev_barrier();
+		fux->ready = 1;
+
+		put_cpu();
+		ret = fudev_simple_wake_and_wait(fux, NULL);
+		fudev_barrier();
+		fux->reserved = 0;
+
+		return ret;
+	}
+	if (req->inh.len > fux->ibufsize || req->max_outlen > fux->obufsize) {
+		put_cpu();
+		return -EPROBE_DEFER;
+	}
+
+	fux->reserved = 1;
+	fudev_barrier();
+	cpumask_copy(&orig_mask, current->cpus_ptr);
+	cpumask_copy(&current->cpus_mask, cpumask_of(cpu));
+	put_cpu();
+
+	req->inh.unique = unique = fux->reqctr;
+	fux->reqctr += FUSE_REQ_ID_STEP;
+
+	ptr = fux->ibuf;
+	memcpy(ptr, req->inlinedata, req->inline_inlen);
+	ptr += req->inline_inlen;
+	for (i = 0; i < req->num_pages; i++) {
+		pd = &req->page_descs[i];
+		addr = kmap_atomic(req->pages[i]);
+		memcpy(ptr, addr + pd->offset, pd->length);
+		kunmap_atomic(addr);
+		ptr += pd->length;
+	}
+
+	fux->done = 0;
+	fudev_barrier();
+	fux->ready = 1;
+
+	ret = fudev_simple_wake_and_wait(fux, NULL);
+	if (!ret) {
+		outh = ptr = fux->obuf;
+
+		if (outh->unique != unique || outh->len < sizeof(*outh)) {
+			ret = -EIO;
+			goto out;
+		}
+		if (outh->error) {
+			ret = outh->error;
+			goto out;
+		}
+		rem = outh->len;
+		if (rem < req->mand_outlen || rem > req->max_outlen) {
+			ret = -EIO;
+			goto out;
+		}
+
+		memcpy(req->inlinedata, ptr, req->inline_outlen);
+		ptr += req->inline_outlen;
+		rem -= req->inline_outlen;
+
+		for (i = 0; rem && i < req->num_pages; i++) {
+			pd = &req->page_descs[i];
+			thislen = min(pd->length, rem);
+			addr = kmap_atomic(req->pages[i]);
+			memcpy(addr + pd->offset, ptr, thislen);
+			kunmap_atomic(addr);
+			ptr += thislen;
+			rem -= thislen;
+		}
+	}
+
+out:
+	fudev_barrier();
+	fux->reserved = 0;
+	cpumask_copy(&current->cpus_mask, &orig_mask);
+
+	return ret;
+}
+
+static int fudev_send(void *priv, struct fuse_req *req)
+{
+	int ret;
+
+	ret = fudev_send_fast(priv, req);
+	if (ret == -EPROBE_DEFER)
+		ret = fudev_send_slow(priv, req);
+
+	return ret;
+}
+
+static void fudev_forget(void *priv, struct fuse_forget *forget)
+{
+	struct fudev *fud = priv;
+	struct fuse_iqueue *fiq = &fud->iq;
+
+	spin_lock(&fiq->waitq.lock);
+	if (fiq->connected) {
+		list_add_tail(&forget->list, &fiq->forgets);
+		wake_up_locked(&fiq->waitq);
+	} else {
+		kfree(forget);
+	}
+	spin_unlock(&fiq->waitq.lock);
+}
+
+static const struct fuse_dev_operations fudev_ops = {
+	.put = fudev_put,
+	.abort = fudev_abort,
+	.send = fudev_send,
+	.simple_send = fudev_simple_send,
+	.forget = fudev_forget,
+};
+
+static struct fudev *fudev_get(int fd)
+{
+	struct fudev *fud;
+	struct file *file;
+	int err = -EINVAL;
+
+	file = fget(fd);
+	if (!file)
+		goto out_err;
+
+	if (file->f_op != &fudev_operations)
+		goto fput;
+
+	fud = fudev(file);
+	/*
+	 * Require mount to happen from the same user namespace which
+	 * opened /dev/fuse to prevent potential attacks.
+	 */
+	if (file->f_cred->user_ns != current_user_ns())
+		goto abort;
+
+	refcount_inc(&fud->count);
+	fput(file);
+
+	return fud;
+
+abort:
+	fudev_abort(fud);
+fput:
+	fput(file);
+out_err:
+	return ERR_PTR(err);
+}
+
+struct fuse_mount_data {
+	int fd;
+};
+
+enum {
+	OPT_FD,
+	OPT_ROOTMODE,
+	OPT_ERR
+};
+
+static const match_table_t tokens = {
+	{OPT_FD,			"fd=%u"},
+	{OPT_ROOTMODE,			"rootmode=%o"},
+	{OPT_ERR,			NULL}
+};
+
+static int parse_fuse_opt(char *opt, struct fuse_mount_data *d)
+{
+	char *p, *t = opt, *e = opt;
+	bool fd_present = false;
+
+	memset(d, 0, sizeof(struct fuse_mount_data));
+
+	while ((p = strsep(&opt, ",")) != NULL) {
+		int token;
+		int value;
+		size_t len;
+		substring_t args[MAX_OPT_ARGS];
+		if (!*p)
+			continue;
+
+		token = match_token(p, tokens, args);
+		switch (token) {
+		case OPT_FD:
+			if (match_int(&args[0], &value))
+				return 0;
+			d->fd = value;
+			fd_present = true;
+			break;
+
+		case OPT_ROOTMODE:
+			pr_info("fuse: legacy mount mode not supported\n");
+			return 0;
+
+		default:
+			len = strlen(p);
+			memmove(t, p, len);
+			*(e = t + len) = ',';
+			t = e + 1;
+		}
+	}
+
+	if (!fd_present)
+		return 0;
+
+	*e = '\0';
+
+	return 1;
+}
+
+static struct dentry *fudev_mount(struct file_system_type *fs_type,
+		       int flags, const char *dev_name, void *opts)
+{
+	struct fuse_mount_data d;
+	struct fudev *fud;
+	struct dentry *root;
+
+	if (!parse_fuse_opt(opts, &d))
+		return ERR_PTR(-EINVAL);
+
+	fud = fudev_get(d.fd);
+	if (IS_ERR(fud))
+		return ERR_CAST(fud);
+
+	root = fuse_mount_common(fs_type, flags, opts, &fudev_ops, fud);
+	if (!IS_ERR(root))
+		fud->sb = root->d_sb;
+
+	return root;
+}
+
+static struct file_system_type fuse_fs_type = {
+	.owner		= THIS_MODULE,
+	.name		= "fuse2",
+	.fs_flags	= FS_HAS_SUBTYPE | FS_USERNS_MOUNT,
+	.mount		= fudev_mount,
+	.kill_sb	= fuse_kill_sb,
+};
+MODULE_ALIAS_FS("fuse");
+
+static int __init fudev_init(void)
+{
+	int err;
+
+	err = register_filesystem(&fuse_fs_type);
+	if (err)
+		goto out;
+
+	err = misc_register(&fuse_miscdevice);
+	if (err)
+		goto unreg_fs;
+
+	err = misc_register(&fuse_aux_miscdevice);
+	if (err)
+		goto unreg_misc;
+
+	pr_info("fuse2 device initialized\n");
+
+	return 0;
+
+unreg_misc:
+	misc_deregister(&fuse_miscdevice);
+unreg_fs:
+	unregister_filesystem(&fuse_fs_type);
+out:
+	return err;
+
+}
+module_init(fudev_init);
+
+static void fudev_cleanup(void)
+{
+	unregister_filesystem(&fuse_fs_type);
+	misc_deregister(&fuse_miscdevice);
+	misc_deregister(&fuse_aux_miscdevice);
+}
+module_exit(fudev_cleanup);
diff --git a/fs/fuse2/fuse_i.h b/fs/fuse2/fuse_i.h
new file mode 100644
index 0000000..03dbbd2
--- /dev/null
+++ b/fs/fuse2/fuse_i.h
@@ -0,0 +1,321 @@
+/*
+  FUSE: Filesystem in Userspace
+  Copyright (C) 2001-2008  Miklos Szeredi <miklos@szeredi.hu>
+
+  This program can be distributed under the terms of the GNU GPL.
+  See the file COPYING.
+*/
+
+#ifndef _FS_FUSE_I_H
+#define _FS_FUSE_I_H
+
+#include "dev.h"
+
+/** Default max number of pages that can be used in a single read request */
+#define FUSE_DEFAULT_MAX_PAGES_PER_REQ 32
+
+/** Maximum of max_pages received in init_out */
+#define FUSE_MAX_MAX_PAGES 256
+
+/** Bias for fi->writectr, meaning new writepages must not be sent */
+#define FUSE_NOWRITE INT_MIN
+
+/** It could be as large as PATH_MAX, but would that have any uses? */
+#define FUSE_NAME_MAX 1024
+
+/** Number of dentries for each connection in the control filesystem */
+#define FUSE_CTL_NUM_DENTRIES 5
+
+/** FUSE inode */
+struct fuse_inode {
+	/** Inode data */
+	struct inode inode;
+
+	/** Unique ID, which identifies the inode between userspace
+	 * and kernel */
+	u64 nodeid;
+
+	/** Number of lookups on this inode */
+	u64 nlookup;
+
+	/** The request used for sending the FORGET message */
+	struct fuse_forget *forget;
+
+	/** Miscellaneous bits describing inode state */
+	unsigned long state;
+
+	/** Lock to protect write related fields */
+	spinlock_t lock;
+};
+
+struct fuse_conn;
+
+/** FUSE specific file data */
+struct fuse_file {
+	/** Fuse connection for this file */
+	struct fuse_conn *fc;
+
+	/** Kernel file handle guaranteed to be unique */
+	u64 kh;
+
+	/** File handle used by userspace */
+	u64 fh;
+
+	/** Node id of this file */
+	u64 nodeid;
+
+	/** FOPEN_* flags returned by open */
+	u32 open_flags;
+
+	/** Has flock been performed on this file? */
+	bool flock:1;
+};
+
+#define FUSE_ARGS(args) struct fuse_args args = {}
+
+/**
+ * A Fuse connection.
+ *
+ * This structure is created, when the filesystem is mounted, and is
+ * destroyed, when the client device is closed and the filesystem is
+ * unmounted.
+ */
+struct fuse_conn {
+	/** Refcount */
+	refcount_t count;
+
+	struct rcu_head rcu;
+
+	/** The pid namespace for this mount */
+	struct pid_namespace *pid_ns;
+
+	/** The user namespace for this mount */
+	struct user_namespace *user_ns;
+
+	/** Maxmum number of pages that can be used in a single request */
+	unsigned int max_pages;
+
+	/** The next unique kernel file handle */
+	atomic64_t khctr;
+
+	/*
+	 * The following bitfields are only for optimization purposes
+	 * and hence races in setting them will not cause malfunction
+	 */
+
+	/** Is fsync not implemented by fs? */
+	unsigned no_fsync:1;
+
+	/** Is fsyncdir not implemented by fs? */
+	unsigned no_fsyncdir:1;
+
+	/** Is flush not implemented by fs? */
+	unsigned no_flush:1;
+
+	/** Is setxattr not implemented by fs? */
+	unsigned no_setxattr:1;
+
+	/** Is getxattr not implemented by fs? */
+	unsigned no_getxattr:1;
+
+	/** Is listxattr not implemented by fs? */
+	unsigned no_listxattr:1;
+
+	/** Is removexattr not implemented by fs? */
+	unsigned no_removexattr:1;
+
+	/** Are posix file locking primitives not implemented by fs? */
+	unsigned no_lock:1;
+
+	/** Is create not implemented by fs? */
+	unsigned no_create:1;
+
+	/** Are BSD file locking primitives not implemented by fs? */
+	unsigned no_flock:1;
+
+	/** Is fallocate not implemented by fs? */
+	unsigned no_fallocate:1;
+
+	/** Is rename with flags implemented by fs? */
+	unsigned no_rename2:1;
+
+	/** Is lseek not implemented by fs? */
+	unsigned no_lseek:1;
+
+	/** Does the filesystem support posix acls? */
+	unsigned posix_acl:1;
+
+	/** Negotiated minor version */
+	unsigned minor;
+
+	/** Key for lock owner ID scrambling */
+	u32 scramble_key[4];
+
+	/* fuse device operations */
+	const struct fuse_dev_operations *dev_ops;
+
+	/* device data */
+	void *dev_priv;
+
+	/* Map file */
+	struct file *mapfile;
+};
+
+static inline struct fuse_conn *get_fuse_conn_super(struct super_block *sb)
+{
+	return sb->s_fs_info;
+}
+
+static inline struct fuse_conn *get_fuse_conn(struct inode *inode)
+{
+	return get_fuse_conn_super(inode->i_sb);
+}
+
+static inline struct fuse_inode *get_fuse_inode(struct inode *inode)
+{
+	return container_of(inode, struct fuse_inode, inode);
+}
+
+static inline u64 get_node_id(struct inode *inode)
+{
+	return get_fuse_inode(inode)->nodeid;
+}
+
+static inline int invalid_nodeid(u64 nodeid)
+{
+	return !nodeid || nodeid == FUSE_ROOT_ID;
+}
+
+extern const struct dentry_operations fuse2_dentry_operations;
+
+/**
+ * Inode to nodeid comparison.
+ */
+int fuse2_inode_eq(struct inode *inode, void *_nodeidp);
+
+/**
+ * Get a filled in inode
+ */
+struct inode *fuse2_iget(struct super_block *sb, u64 nodeid,
+			int generation, struct fuse_attr *attr);
+
+/**
+ * Initialize READ or READDIR request
+ */
+void fuse2_read_fill(struct fuse_req *req, struct fuse_read_in **inarg,
+		    struct file *file, loff_t pos, size_t count, int opcode);
+
+/**
+ * Send OPEN or OPENDIR request
+ */
+int fuse2_open_common(struct inode *inode, struct file *file, bool isdir);
+
+struct fuse_file *fuse2_file_alloc(struct fuse_conn *fc);
+void fuse2_file_free(struct fuse_file *ff);
+void fuse2_finish_open(struct inode *inode, struct file *file);
+
+/**
+ * Send RELEASE or RELEASEDIR request
+ */
+void fuse2_release_common(struct fuse_conn *fc, struct fuse_file *ff,
+			 int flags, fl_owner_t id, bool isdir);
+
+/**
+ * Send FSYNC or FSYNCDIR request
+ */
+int fuse2_fsync_common(struct file *file, loff_t start, loff_t end,
+		      int datasync, int opcode);
+
+/**
+ * Notify poll wakeup
+ */
+int fuse_notify_poll_wakeup(struct fuse_conn *fc,
+			    struct fuse_notify_poll_wakeup_out *outarg);
+
+/**
+ * Initialize file operations on a regular file
+ */
+void fuse2_init_file_inode(struct inode *inode);
+
+/**
+ * Initialize inode operations on regular files and special files
+ */
+void fuse2_init_common(struct inode *inode);
+
+/**
+ * Initialize inode and file operations on a directory
+ */
+void fuse2_init_dir(struct inode *inode);
+
+/**
+ * Initialize inode operations on a symlink
+ */
+void fuse2_init_symlink(struct inode *inode);
+
+/**
+ * Change attributes of an inode
+ */
+void fuse2_change_attributes(struct inode *inode, struct fuse_attr *attr);
+
+void fuse2_change_attributes_common(struct inode *inode, struct fuse_attr *attr);
+
+int  fuse_req_cache_init(void);
+void fuse_req_cache_cleanup(void);
+
+/**
+ * Get a request, may fail with -ENOMEM,
+ * caller should specify # elements in req->pages[] explicitly
+ */
+struct fuse_req *fuse2_get_req(struct fuse_conn *fc, unsigned npages);
+
+struct fuse_forget *fuse2_alloc_forget(void);
+
+/*
+ * Send a request (synchronous)
+ */
+int fuse2_request_send(struct fuse_conn *fc, struct fuse_req *req);
+
+/**
+ * Simple request sending that does request allocation and freeing
+ */
+ssize_t fuse2_simple_request(struct fuse_conn *fc, struct fuse_args *args);
+
+/*
+ * Send FORGET command
+ */
+void fuse2_queue_forget(struct fuse_conn *fc, struct fuse_forget *forget,
+		       u64 nodeid, u64 nlookup);
+
+void fuse2_force_forget(struct fuse_conn *fc, u64 nodeid);
+
+/**
+ * Is current process allowed to perform filesystem operation?
+ */
+int fuse2_allow_current_process(struct fuse_conn *fc);
+
+u64 fuse2_lock_owner_id(struct fuse_conn *fc, fl_owner_t id);
+
+int fuse2_update_attributes(struct inode *inode, struct file *file);
+
+int fuse2_setxattr(struct inode *inode, const char *name, const void *value,
+		  size_t size, int flags);
+ssize_t fuse2_getxattr(struct inode *inode, const char *name, void *value,
+		      size_t size);
+ssize_t fuse2_listxattr(struct dentry *entry, char *list, size_t size);
+int fuse2_removexattr(struct inode *inode, const char *name);
+extern const struct xattr_handler *fuse2_xattr_handlers[];
+extern const struct xattr_handler *fuse2_acl_xattr_handlers[];
+extern const struct xattr_handler *fuse2_no_acl_xattr_handlers[];
+
+struct posix_acl;
+struct posix_acl *fuse2_get_acl(struct inode *inode, int type);
+int fuse2_set_acl(struct inode *inode, struct posix_acl *acl, int type);
+
+
+/* readdir.c */
+int fuse2_readdir(struct file *file, struct dir_context *ctx);
+
+/* map.c */
+struct file *fuse2_map_get(struct fuse_conn *fc, u64 mapfd);
+
+#endif /* _FS_FUSE_I_H */
diff --git a/fs/fuse2/inode.c b/fs/fuse2/inode.c
new file mode 100644
index 0000000..1060f07
--- /dev/null
+++ b/fs/fuse2/inode.c
@@ -0,0 +1,635 @@
+/*
+  FUSE: Filesystem in Userspace
+  Copyright (C) 2001-2008  Miklos Szeredi <miklos@szeredi.hu>
+
+  This program can be distributed under the terms of the GNU GPL.
+  See the file COPYING.
+*/
+
+#include "fuse_i.h"
+
+#include <linux/module.h>
+#include <linux/statfs.h>
+#include <linux/random.h>
+#include <linux/backing-dev.h>
+#include <linux/pid_namespace.h>
+
+MODULE_AUTHOR("Miklos Szeredi <miklos@szeredi.hu>");
+MODULE_DESCRIPTION("Filesystem in Userspace");
+MODULE_LICENSE("GPL");
+
+static struct kmem_cache *fuse_inode_cachep;
+
+#define FUSE_SUPER_MAGIC 0x65735546
+
+#define FUSE_DEFAULT_BLKSIZE 512
+
+static struct inode *fuse_alloc_inode(struct super_block *sb)
+{
+	struct fuse_inode *fi;
+
+	fi = kmem_cache_alloc(fuse_inode_cachep, GFP_KERNEL);
+	if (!fi)
+		return NULL;
+
+	fi->nodeid = 0;
+	fi->nlookup = 0;
+	fi->state = 0;
+	spin_lock_init(&fi->lock);
+	fi->forget = fuse2_alloc_forget();
+	if (!fi->forget) {
+		kmem_cache_free(fuse_inode_cachep, fi);
+		return NULL;
+	}
+
+	return &fi->inode;
+}
+
+static void fuse_i_callback(struct rcu_head *head)
+{
+	struct inode *inode = container_of(head, struct inode, i_rcu);
+	kmem_cache_free(fuse_inode_cachep, inode);
+}
+
+static void fuse_destroy_inode(struct inode *inode)
+{
+	struct fuse_inode *fi = get_fuse_inode(inode);
+
+	kfree(fi->forget);
+	call_rcu(&inode->i_rcu, fuse_i_callback);
+}
+
+static void fuse_evict_inode(struct inode *inode)
+{
+	truncate_inode_pages_final(&inode->i_data);
+	clear_inode(inode);
+	if (inode->i_sb->s_flags & SB_ACTIVE) {
+		struct fuse_conn *fc = get_fuse_conn(inode);
+		struct fuse_inode *fi = get_fuse_inode(inode);
+		fuse2_queue_forget(fc, fi->forget, fi->nodeid, fi->nlookup);
+		fi->forget = NULL;
+	}
+}
+
+static int fuse_remount_fs(struct super_block *sb, int *flags, char *data)
+{
+	sync_filesystem(sb);
+	if (*flags & SB_MANDLOCK)
+		return -EINVAL;
+
+	return 0;
+}
+
+/*
+ * ino_t is 32-bits on 32-bit arch. We have to squash the 64-bit value down
+ * so that it will fit.
+ */
+static ino_t fuse_squash_ino(u64 ino64)
+{
+	ino_t ino = (ino_t) ino64;
+	if (sizeof(ino_t) < sizeof(u64))
+		ino ^= ino64 >> (sizeof(u64) - sizeof(ino_t)) * 8;
+	return ino;
+}
+
+void fuse2_change_attributes_common(struct inode *inode, struct fuse_attr *attr)
+{
+	struct fuse_conn *fc = get_fuse_conn(inode);
+	struct fuse_inode *fi = get_fuse_inode(inode);
+
+	lockdep_assert_held(&fi->lock);
+
+	inode->i_ino     = fuse_squash_ino(attr->ino);
+	inode->i_mode    = (inode->i_mode & S_IFMT) | (attr->mode & 07777);
+	set_nlink(inode, attr->nlink);
+	inode->i_uid     = make_kuid(fc->user_ns, attr->uid);
+	inode->i_gid     = make_kgid(fc->user_ns, attr->gid);
+	inode->i_blocks  = attr->blocks;
+	inode->i_atime.tv_sec   = attr->atime;
+	inode->i_atime.tv_nsec  = attr->atimensec;
+	inode->i_mtime.tv_sec   = attr->mtime;
+	inode->i_mtime.tv_nsec  = attr->mtimensec;
+	inode->i_ctime.tv_sec   = attr->ctime;
+	inode->i_ctime.tv_nsec  = attr->ctimensec;
+
+	if (attr->blksize != 0)
+		inode->i_blkbits = ilog2(attr->blksize);
+	else
+		inode->i_blkbits = inode->i_sb->s_blocksize_bits;
+}
+
+void fuse2_change_attributes(struct inode *inode, struct fuse_attr *attr)
+{
+	struct fuse_inode *fi = get_fuse_inode(inode);
+	struct fuse_conn *fc = get_fuse_conn(inode);
+	loff_t oldsize;
+	struct timespec64 old_mtime;
+
+	/* before taking spinlock, check if anything has changed */
+	if (i_size_read(inode) == attr->size &&
+	    inode->i_ino == fuse_squash_ino(attr->ino) &&
+	    (inode->i_mode & 07777) == (attr->mode & 07777) &&
+	    inode->i_nlink == attr->nlink &&
+	    uid_eq(inode->i_uid, make_kuid(fc->user_ns, attr->uid)) &&
+	    gid_eq(inode->i_gid, make_kgid(fc->user_ns, attr->gid)) &&
+	    inode->i_blocks == attr->blocks &&
+	    inode->i_atime.tv_sec == attr->atime &&
+	    inode->i_atime.tv_nsec == attr->atimensec &&
+	    inode->i_mtime.tv_sec == attr->mtime &&
+	    inode->i_mtime.tv_nsec == attr->mtimensec &&
+	    inode->i_ctime.tv_sec == attr->ctime &&
+	    inode->i_ctime.tv_nsec == attr->ctimensec &&
+	    ((attr->blksize != 0 && inode->i_blkbits == ilog2(attr->blksize)) ||
+	     (attr->blksize == 0 && inode->i_blkbits == inode->i_sb->s_blocksize_bits)))
+		return;
+
+	spin_lock(&fi->lock);
+	old_mtime = inode->i_mtime;
+	fuse2_change_attributes_common(inode, attr);
+
+	oldsize = inode->i_size;
+	i_size_write(inode, attr->size);
+	spin_unlock(&fi->lock);
+
+	if (S_ISREG(inode->i_mode)) {
+		if (oldsize != attr->size) {
+			truncate_pagecache(inode, attr->size);
+			invalidate_inode_pages2(inode->i_mapping);
+		}
+	}
+}
+
+static void fuse_init_inode(struct inode *inode, struct fuse_attr *attr)
+{
+	inode->i_mode = attr->mode & S_IFMT;
+	inode->i_size = attr->size;
+	inode->i_mtime.tv_sec  = attr->mtime;
+	inode->i_mtime.tv_nsec = attr->mtimensec;
+	inode->i_ctime.tv_sec  = attr->ctime;
+	inode->i_ctime.tv_nsec = attr->ctimensec;
+	if (S_ISREG(inode->i_mode)) {
+		fuse2_init_common(inode);
+		fuse2_init_file_inode(inode);
+	} else if (S_ISDIR(inode->i_mode))
+		fuse2_init_dir(inode);
+	else if (S_ISLNK(inode->i_mode))
+		fuse2_init_symlink(inode);
+	else if (S_ISCHR(inode->i_mode) || S_ISBLK(inode->i_mode) ||
+		 S_ISFIFO(inode->i_mode) || S_ISSOCK(inode->i_mode)) {
+		fuse2_init_common(inode);
+		init_special_inode(inode, inode->i_mode,
+				   new_decode_dev(attr->rdev));
+	} else
+		BUG();
+}
+
+int fuse2_inode_eq(struct inode *inode, void *_nodeidp)
+{
+	u64 nodeid = *(u64 *) _nodeidp;
+	if (get_node_id(inode) == nodeid)
+		return 1;
+	else
+		return 0;
+}
+
+static int fuse_inode_set(struct inode *inode, void *_nodeidp)
+{
+	u64 nodeid = *(u64 *) _nodeidp;
+	get_fuse_inode(inode)->nodeid = nodeid;
+	return 0;
+}
+
+struct inode *fuse2_iget(struct super_block *sb, u64 nodeid,
+			int generation, struct fuse_attr *attr)
+{
+	struct inode *inode;
+	struct fuse_inode *fi;
+
+ retry:
+	inode = iget5_locked(sb, nodeid, fuse2_inode_eq, fuse_inode_set, &nodeid);
+	if (!inode)
+		return NULL;
+
+	if ((inode->i_state & I_NEW)) {
+		inode->i_flags |= S_NOATIME;
+		inode->i_flags |= S_NOCMTIME;
+		inode->i_generation = generation;
+		fuse_init_inode(inode, attr);
+		unlock_new_inode(inode);
+	} else if ((inode->i_mode ^ attr->mode) & S_IFMT) {
+		/* Inode has changed type, any I/O on the old should fail */
+		make_bad_inode(inode);
+		iput(inode);
+		goto retry;
+	}
+
+	fi = get_fuse_inode(inode);
+	spin_lock(&fi->lock);
+	fi->nlookup++;
+	spin_unlock(&fi->lock);
+	fuse2_change_attributes(inode, attr);
+
+	return inode;
+}
+
+static void fuse_umount_begin(struct super_block *sb)
+{
+	struct fuse_conn *fc = get_fuse_conn_super(sb);
+
+	fc->dev_ops->abort(fc->dev_priv);
+}
+
+static void fuse_conn_put(struct fuse_conn *fc)
+{
+	if (refcount_dec_and_test(&fc->count)) {
+		put_pid_ns(fc->pid_ns);
+		put_user_ns(fc->user_ns);
+		fc->dev_ops->put(fc->dev_priv);
+		kfree_rcu(fc, rcu);
+	}
+}
+
+static void fuse_abort_and_put(struct fuse_conn *fc)
+{
+	fc->dev_ops->abort(fc->dev_priv);
+	fuse_conn_put(fc);
+}
+
+static void fuse_put_super(struct super_block *sb)
+{
+	struct fuse_conn *fc = get_fuse_conn_super(sb);
+	FUSE_ARGS(args);
+
+	args.force = true;
+	args.in.h.opcode = FUSE_DESTROY;
+	fuse2_simple_request(fc, &args);
+
+	fuse_abort_and_put(fc);
+}
+
+static void convert_fuse_statfs(struct kstatfs *stbuf, struct fuse_kstatfs *attr)
+{
+	stbuf->f_type    = FUSE_SUPER_MAGIC;
+	stbuf->f_bsize   = attr->bsize;
+	stbuf->f_frsize  = attr->frsize;
+	stbuf->f_blocks  = attr->blocks;
+	stbuf->f_bfree   = attr->bfree;
+	stbuf->f_bavail  = attr->bavail;
+	stbuf->f_files   = attr->files;
+	stbuf->f_ffree   = attr->ffree;
+	stbuf->f_namelen = attr->namelen;
+	/* fsid is left zero */
+}
+
+static int fuse_statfs(struct dentry *dentry, struct kstatfs *buf)
+{
+	struct super_block *sb = dentry->d_sb;
+	struct fuse_conn *fc = get_fuse_conn_super(sb);
+	FUSE_ARGS(args);
+	struct fuse_statfs_out outarg;
+	int err;
+
+	if (!fuse2_allow_current_process(fc)) {
+		buf->f_type = FUSE_SUPER_MAGIC;
+		return 0;
+	}
+
+	memset(&outarg, 0, sizeof(outarg));
+	args.in.h.opcode = FUSE_STATFS;
+	args.in.h.nodeid = get_node_id(d_inode(dentry));
+	args.out.numargs = 1;
+	args.out.args[0].size = sizeof(outarg);
+	args.out.args[0].value = &outarg;
+	err = fuse2_simple_request(fc, &args);
+	if (!err)
+		convert_fuse_statfs(buf, &outarg.st);
+	return err;
+}
+
+static int fuse_show_options(struct seq_file *m, struct dentry *root)
+{
+	return 0;
+}
+
+static struct fuse_conn *fuse_conn_alloc(void)
+{
+	struct fuse_conn *fc;
+
+	fc = kzalloc(sizeof(struct fuse_conn), GFP_KERNEL);
+	if (!fc)
+		goto out;
+
+	refcount_set(&fc->count, 1);
+	atomic64_set(&fc->khctr, 0);
+	get_random_bytes(&fc->scramble_key, sizeof(fc->scramble_key));
+	fc->pid_ns = get_pid_ns(task_active_pid_ns(current));
+	fc->user_ns = get_user_ns(current_user_ns());
+	fc->max_pages = FUSE_DEFAULT_MAX_PAGES_PER_REQ;
+
+out:
+	return fc;
+}
+
+static struct fuse_conn *fuse_conn_get(struct fuse_conn *fc)
+{
+	refcount_inc(&fc->count);
+	return fc;
+}
+
+static struct inode *fuse_get_root_inode(struct super_block *sb, unsigned mode)
+{
+	struct fuse_attr attr;
+	memset(&attr, 0, sizeof(attr));
+
+	attr.mode = mode;
+	attr.ino = FUSE_ROOT_ID;
+	attr.nlink = 1;
+	return fuse2_iget(sb, 1, 0, &attr);
+}
+
+static const struct super_operations fuse_super_operations = {
+	.alloc_inode    = fuse_alloc_inode,
+	.destroy_inode  = fuse_destroy_inode,
+	.evict_inode	= fuse_evict_inode,
+	.drop_inode	= generic_delete_inode,
+	.remount_fs	= fuse_remount_fs,
+	.put_super	= fuse_put_super,
+	.umount_begin	= fuse_umount_begin,
+	.statfs		= fuse_statfs,
+	.show_options	= fuse_show_options,
+};
+
+static int process_init_reply(struct fuse_conn *fc, struct super_block *sb,
+			      struct fuse_init_out *arg)
+{
+	unsigned long ra_pages;
+
+	if (arg->major != FUSE_KERNEL_VERSION || arg->minor < 23)
+		return -EINVAL;
+
+	ra_pages = arg->max_readahead / PAGE_SIZE;
+	if (!(arg->flags & FUSE_POSIX_LOCKS))
+		fc->no_lock = 1;
+	if (!(arg->flags & FUSE_FLOCK_LOCKS))
+		fc->no_flock = 1;
+	if (!(arg->flags & FUSE_ATOMIC_O_TRUNC)) {
+		pr_info("fuse: must support FUSE_ATOMIC_O_TRUNC\n");
+		return -EINVAL;
+	}
+	if (!(arg->flags & FUSE_BIG_WRITES)) {
+		pr_info("fuse: must support FUSE_BIG_WRITES\n");
+		return -EINVAL;
+	}
+	if (!(arg->flags & FUSE_PARALLEL_DIROPS)) {
+		pr_info("fuse: must support FUSE_PARALLEL_DIROPS\n");
+		return -EINVAL;
+	}
+	if (!(arg->flags & FUSE_HANDLE_KILLPRIV)) {
+		pr_info("fuse: must support FUSE_HANDLE_KILLPRIV\n");
+		return -EINVAL;
+	}
+	if (arg->time_gran && arg->time_gran <= 1000000000)
+		sb->s_time_gran = arg->time_gran;
+	if ((arg->flags & FUSE_POSIX_ACL)) {
+		fc->posix_acl = 1;
+		sb->s_xattr = fuse2_acl_xattr_handlers;
+	}
+	if (arg->flags & FUSE_MAX_PAGES) {
+		fc->max_pages =
+			min_t(unsigned int, FUSE_MAX_MAX_PAGES,
+			      max_t(unsigned int, arg->max_pages, 1));
+	}
+	return 0;
+}
+
+static int fuse_send_init(struct fuse_conn *fc, struct super_block *sb,
+			  char *opts)
+{
+	struct fuse_init_in inarg;
+	struct fuse_init_out outarg;
+	FUSE_ARGS(args);
+	int err;
+
+	memset(&inarg, 0, sizeof(inarg));
+	inarg.major = FUSE_KERNEL_VERSION;
+	inarg.minor = FUSE_KERNEL_MINOR_VERSION;
+	inarg.max_readahead = sb->s_bdi->ra_pages * PAGE_SIZE;
+	inarg.flags = FUSE_POSIX_LOCKS | FUSE_ATOMIC_O_TRUNC |
+		FUSE_BIG_WRITES | FUSE_PARALLEL_DIROPS | FUSE_FLOCK_LOCKS |
+		FUSE_HANDLE_KILLPRIV | FUSE_POSIX_ACL |
+		FUSE_MAX_PAGES;
+
+	args.killable = true;
+	args.in.h.opcode = FUSE_INIT;
+	args.in.numargs = 1;
+	args.in.args[0].size = sizeof(inarg);
+	args.in.args[0].value = &inarg;
+	if (opts) {
+		args.in.numargs++;
+		args.in.args[1].size = strlen(opts) + 1;
+		args.in.args[1].value = opts;
+	}
+	args.out.numargs = 1;
+	args.out.args[0].size = sizeof(outarg);
+	args.out.args[0].value = &outarg;
+	err = fuse2_simple_request(fc, &args);
+	if (!err)
+		err = process_init_reply(fc, sb, &outarg);
+
+	return err;
+}
+
+static int fuse_fill_super(struct super_block *sb, char *opts)
+{
+	struct fuse_conn *fc = get_fuse_conn_super(sb);
+	struct inode *root;
+	int err;
+
+	if (sb->s_flags & SB_MANDLOCK)
+		return -EINVAL;
+
+	sb->s_flags &= ~(SB_NOSEC | SB_I_VERSION);
+	sb->s_blocksize = PAGE_SIZE;
+	sb->s_blocksize_bits = PAGE_SHIFT;
+	sb->s_magic = FUSE_SUPER_MAGIC;
+	sb->s_op = &fuse_super_operations;
+	sb->s_xattr = fuse2_xattr_handlers;
+	sb->s_maxbytes = MAX_LFS_FILESIZE;
+	sb->s_time_gran = 1;
+	sb->s_iflags |= SB_I_IMA_UNVERIFIABLE_SIGNATURE;
+	if (sb->s_user_ns != &init_user_ns)
+		sb->s_iflags |= SB_I_UNTRUSTED_MOUNTER;
+
+	/*
+	 * If we are not in the initial user namespace posix
+	 * acls must be translated.
+	 */
+	if (sb->s_user_ns != &init_user_ns)
+		sb->s_xattr = fuse2_no_acl_xattr_handlers;
+
+	sb->s_flags |= SB_POSIXACL;
+
+	err = fuse_send_init(fc, sb, opts);
+	if (err)
+		return err;
+
+	root = fuse_get_root_inode(sb, S_IFDIR | 0);
+	sb->s_root = d_make_root(root);
+	if (!sb->s_root)
+		return -ENOMEM;
+
+	/* Root dentry doesn't have .d_revalidate */
+	sb->s_d_op = &fuse2_dentry_operations;
+
+	return 0;
+}
+
+static int fuse_set_super(struct super_block *s, void *data)
+{
+	int err;
+
+	err = get_anon_bdev(&s->s_dev);
+	if (!err)
+		s->s_fs_info = fuse_conn_get(data);
+
+	return err;
+}
+
+static int fuse_test_super(struct super_block *s, void *data)
+{
+	struct fuse_conn *fc = data;
+
+	return fc->dev_priv == get_fuse_conn_super(s)->dev_priv;
+}
+
+struct dentry *fuse_mount_common(struct file_system_type *fs_type,
+				 int flags, void *opts,
+				 const struct fuse_dev_operations *dev_ops,
+				 void *dev_priv)
+{
+	struct super_block *s;
+	struct fuse_conn *fc;
+	int err;
+
+	fc = fuse_conn_alloc();
+	if (!fc) {
+		dev_ops->abort(dev_priv);
+		dev_ops->put(dev_priv);
+		return ERR_PTR(-ENOMEM);
+	}
+	fc->dev_ops = dev_ops;
+	fc->dev_priv = dev_priv;
+
+	s = sget(fs_type, fuse_test_super, fuse_set_super, flags, fc);
+	err = PTR_ERR(s);
+	if (IS_ERR(s))
+		goto abort;
+
+	err = -EIO;
+	if (WARN_ON(fc->user_ns != s->s_user_ns))
+		goto deactivate;
+
+	if (s->s_root) {
+		err = -EBUSY;
+		if ((flags ^ s->s_flags) & SB_RDONLY)
+			goto deactivate;
+	} else {
+		err = fuse_fill_super(s, opts);
+		if (err)
+			goto deactivate;
+
+		s->s_flags |= SB_ACTIVE;
+	}
+	fuse_conn_put(fc);
+
+	return dget(s->s_root);
+
+deactivate:
+	deactivate_locked_super(s);
+abort:
+	fuse_abort_and_put(fc);
+
+	return ERR_PTR(err);
+}
+EXPORT_SYMBOL(fuse_mount_common);
+
+void fuse_kill_sb(struct super_block *sb)
+{
+	struct fuse_conn *fc = get_fuse_conn_super(sb);
+
+	if (!sb->s_root) {
+		/*
+		 * Setup of the sb didn't complete, ->put_super() won't be
+		 * called.
+		 */
+		sb->s_fs_info = NULL;
+		fuse_abort_and_put(fc);
+	}
+	kill_anon_super(sb);
+}
+EXPORT_SYMBOL(fuse_kill_sb);
+
+
+static void fuse_inode_init_once(void *foo)
+{
+	struct inode *inode = foo;
+
+	inode_init_once(inode);
+}
+
+static int __init fuse_fs_init(void)
+{
+	fuse_inode_cachep = kmem_cache_create("fuse2_inode",
+			sizeof(struct fuse_inode), 0,
+			SLAB_HWCACHE_ALIGN|SLAB_ACCOUNT|SLAB_RECLAIM_ACCOUNT,
+			fuse_inode_init_once);
+
+	if (!fuse_inode_cachep)
+		return -ENOMEM;
+
+	return 0;
+}
+
+static void fuse_fs_cleanup(void)
+{
+	/*
+	 * Make sure all delayed rcu free inodes are flushed before we
+	 * destroy cache.
+	 */
+	rcu_barrier();
+	kmem_cache_destroy(fuse_inode_cachep);
+}
+
+static int __init fuse_init(void)
+{
+	int res;
+
+	res = fuse_fs_init();
+	if (res)
+		goto err;
+
+	res = fuse_req_cache_init();
+	if (res)
+		goto err_fs_cleanup;
+
+	pr_info("fuse2 core initialized (API version %i.%i)\n",
+		FUSE_KERNEL_VERSION, FUSE_KERNEL_MINOR_VERSION);
+
+	return 0;
+
+err_fs_cleanup:
+	fuse_fs_cleanup();
+err:
+	return res;
+}
+
+static void __exit fuse_exit(void)
+{
+	printk(KERN_DEBUG "fuse exit\n");
+
+	fuse_req_cache_cleanup();
+	fuse_fs_cleanup();
+}
+
+module_init(fuse_init);
+module_exit(fuse_exit);
diff --git a/fs/fuse2/map.c b/fs/fuse2/map.c
new file mode 100644
index 0000000..184e210
--- /dev/null
+++ b/fs/fuse2/map.c
@@ -0,0 +1,60 @@
+/*
+  FUSE: Filesystem in Userspace
+  Copyright (C) 2001-2008  Miklos Szeredi <miklos@szeredi.hu>
+
+  This program can be distributed under the terms of the GNU GPL.
+  See the file COPYING.
+*/
+
+#include "fuse_i.h"
+
+#include <linux/file.h>
+#include <linux/idr.h>
+
+static DEFINE_SPINLOCK(fuse2_map_lock);
+static DEFINE_IDR(fuse2_map);
+
+int fuse2_map_open(struct super_block *sb, struct file *file)
+{
+	int res;
+
+	idr_preload(GFP_KERNEL);
+	spin_lock(&fuse2_map_lock);
+	res = idr_alloc(&fuse2_map, file, 0, 0, GFP_ATOMIC);
+	spin_unlock(&fuse2_map_lock);
+	idr_preload_end();
+	if (res)
+		fput(file);
+
+	return res;
+}
+EXPORT_SYMBOL(fuse2_map_open);
+
+int fuse2_map_close(struct super_block *sb, unsigned long mapfd)
+{
+	struct file *file;
+
+	spin_lock(&fuse2_map_lock);
+	file = idr_remove(&fuse2_map, mapfd);
+	spin_unlock(&fuse2_map_lock);
+
+	if (!file)
+		return -EBADF;
+
+	fput(file);
+	return 0;
+}
+EXPORT_SYMBOL(fuse2_map_close);
+
+struct file *fuse2_map_get(struct fuse_conn *fc, u64 mapfd)
+{
+	struct file *file;
+
+	rcu_read_lock();
+	file = idr_find(&fuse2_map, mapfd);
+	if (file)
+		get_file(file);
+	rcu_read_unlock();
+
+	return file;
+}
diff --git a/fs/fuse2/readdir.c b/fs/fuse2/readdir.c
new file mode 100644
index 0000000..1e9b2f1
--- /dev/null
+++ b/fs/fuse2/readdir.c
@@ -0,0 +1,89 @@
+/*
+  FUSE: Filesystem in Userspace
+  Copyright (C) 2001-2018  Miklos Szeredi <miklos@szeredi.hu>
+
+  This program can be distributed under the terms of the GNU GPL.
+  See the file COPYING.
+*/
+
+
+#include "fuse_i.h"
+#include <linux/iversion.h>
+#include <linux/posix_acl.h>
+#include <linux/pagemap.h>
+#include <linux/highmem.h>
+
+static bool fuse_emit(struct file *file, struct dir_context *ctx,
+		      struct fuse_dirent *dirent)
+{
+	return dir_emit(ctx, dirent->name, dirent->namelen, dirent->ino,
+			dirent->type);
+}
+
+static int parse_dirfile(char *buf, size_t nbytes, struct file *file,
+			 struct dir_context *ctx)
+{
+	while (nbytes >= FUSE_NAME_OFFSET) {
+		struct fuse_dirent *dirent = (struct fuse_dirent *) buf;
+		size_t reclen = FUSE_DIRENT_SIZE(dirent);
+		if (!dirent->namelen || dirent->namelen > FUSE_NAME_MAX)
+			return -EIO;
+		if (reclen > nbytes)
+			break;
+		if (memchr(dirent->name, '/', dirent->namelen) != NULL)
+			return -EIO;
+
+		if (!fuse_emit(file, ctx, dirent))
+			break;
+
+		buf += reclen;
+		nbytes -= reclen;
+		ctx->pos = dirent->off;
+	}
+
+	return 0;
+}
+
+static int fuse2_readdir_uncached(struct file *file, struct dir_context *ctx)
+{
+	int err;
+	size_t nbytes;
+	struct page *page;
+	struct inode *inode = file_inode(file);
+	struct fuse_conn *fc = get_fuse_conn(inode);
+	struct fuse_req *req;
+	struct fuse_read_in *inarg;
+
+	req = fuse2_get_req(fc, 1);
+	if (IS_ERR(req))
+		return PTR_ERR(req);
+
+	page = alloc_page(GFP_KERNEL);
+	if (!page) {
+		fuse2_put_request(req);
+		return -ENOMEM;
+	}
+
+	req->num_pages = 1;
+	req->pages[0] = page;
+	req->page_descs[0].length = PAGE_SIZE;
+	fuse2_read_fill(req, &inarg, file, ctx->pos, PAGE_SIZE, FUSE_READDIR);
+	err = fuse2_request_send(fc, req);
+	nbytes = req->outh.len - req->mand_outlen;
+	if (!err && nbytes)
+		err = parse_dirfile(page_address(page), nbytes, file, ctx);
+
+	fuse2_put_request(req);
+
+	return err;
+}
+
+int fuse2_readdir(struct file *file, struct dir_context *ctx)
+{
+	struct inode *inode = file_inode(file);
+
+	if (is_bad_inode(inode))
+		return -EIO;
+
+	return fuse2_readdir_uncached(file, ctx);
+}
diff --git a/fs/fuse2/request.c b/fs/fuse2/request.c
new file mode 100644
index 0000000..3665ddc
--- /dev/null
+++ b/fs/fuse2/request.c
@@ -0,0 +1,303 @@
+/*
+  FUSE: Filesystem in Userspace
+  Copyright (C) 2001-2019  Miklos Szeredi <miklos@szeredi.hu>
+
+  This program can be distributed under the terms of the GNU GPL.
+  See the file COPYING.
+*/
+
+#include "fuse_i.h"
+
+#include <linux/mm.h>
+#include <linux/uio.h>
+#include <linux/cred.h>
+
+static struct kmem_cache *fuse_req_cachep;
+
+int fuse2_request_send(struct fuse_conn *fc, struct fuse_req *req)
+{
+	return fc->dev_ops->send(fc->dev_priv, req);
+}
+
+static void fuse_request_init(struct fuse_req *req, struct page **pages,
+			      struct fuse_page_desc *page_descs,
+			      unsigned npages)
+{
+	INIT_LIST_HEAD(&req->list);
+	init_waitqueue_head(&req->waitq);
+	refcount_set(&req->count, 1);
+	req->pages = pages;
+	req->page_descs = page_descs;
+	req->max_pages = npages;
+	__set_bit(FR_PENDING, &req->flags);
+	__set_bit(FR_ISREPLY, &req->flags);
+}
+
+static struct page **fuse_req_pages_alloc(unsigned int npages, gfp_t flags,
+					  struct fuse_page_desc **desc)
+{
+	struct page **pages;
+
+	pages = kzalloc(npages * (sizeof(struct page *) +
+				  sizeof(struct fuse_page_desc)), flags);
+	*desc = (void *) pages + npages * sizeof(struct page *);
+
+	return pages;
+}
+
+static struct fuse_req *fuse_request_alloc(unsigned npages, gfp_t flags)
+{
+	struct fuse_req *req = kmem_cache_zalloc(fuse_req_cachep, flags);
+	if (req) {
+		struct page **pages = NULL;
+		struct fuse_page_desc *page_descs = NULL;
+
+		WARN_ON(npages > FUSE_MAX_MAX_PAGES);
+		if (npages > FUSE_REQ_INLINE_PAGES) {
+			pages = fuse_req_pages_alloc(npages, flags,
+						     &page_descs);
+			if (!pages) {
+				kmem_cache_free(fuse_req_cachep, req);
+				return NULL;
+			}
+		} else if (npages) {
+			pages = req->inline_pages;
+			page_descs = req->inline_page_descs;
+		}
+
+		fuse_request_init(req, pages, page_descs, npages);
+	}
+	return req;
+}
+
+static void fuse_request_free(struct fuse_req *req)
+{
+	unsigned int i;
+
+	for (i = 0; i < req->num_pages; i++)
+		put_page(req->pages[i]);
+
+	if (req->pages != req->inline_pages)
+		kfree(req->pages);
+	kmem_cache_free(fuse_req_cachep, req);
+}
+
+static struct fuse_req *fuse2_get_req_gfp(struct fuse_conn *fc, unsigned npages,
+					 gfp_t flags)
+{
+	struct fuse_req *req;
+	int err;
+
+	req = fuse_request_alloc(npages, flags);
+	err = -ENOMEM;
+	if (!req)
+		goto out;
+
+	req->inh.uid = from_kuid(fc->user_ns, current_fsuid());
+	req->inh.gid = from_kgid(fc->user_ns, current_fsgid());
+	req->inh.pid = pid_nr_ns(task_pid(current), fc->pid_ns);
+
+	if (unlikely(req->inh.uid == ((uid_t)-1) ||
+		     req->inh.gid == ((gid_t)-1))) {
+		fuse2_put_request(req);
+		return ERR_PTR(-EOVERFLOW);
+	}
+	return req;
+
+ out:
+	return ERR_PTR(err);
+}
+
+struct fuse_req *fuse2_get_req(struct fuse_conn *fc, unsigned npages)
+{
+	return fuse2_get_req_gfp(fc, npages, GFP_KERNEL);
+}
+
+struct fuse_forget *fuse2_alloc_forget(void)
+{
+	return kzalloc(sizeof(struct fuse_forget), GFP_KERNEL);
+}
+
+void fuse2_get_request(struct fuse_req *req)
+{
+	refcount_inc(&req->count);
+}
+EXPORT_SYMBOL(fuse2_get_request);
+
+void fuse2_put_request(struct fuse_req *req)
+{
+	if (refcount_dec_and_test(&req->count))
+		fuse_request_free(req);
+}
+EXPORT_SYMBOL(fuse2_put_request);
+
+static unsigned len_args(unsigned numargs, struct fuse_arg *args)
+{
+	unsigned nbytes = 0;
+	unsigned i;
+
+	for (i = 0; i < numargs; i++)
+		nbytes += args[i].size;
+
+	return nbytes;
+}
+
+static int fuse_req_pages_fill(struct fuse_req *req, gfp_t flags)
+{
+	unsigned int i;
+
+	for (i = 0; i < req->max_pages; i++) {
+		struct page *page = alloc_page(flags);
+		if (!page)
+			return -ENOMEM;
+
+		req->pages[i] = page;
+		req->page_descs[i].length = PAGE_SIZE;
+		req->num_pages++;
+	}
+	return 0;
+}
+
+#define FUSE_SIMPLE_MAX_PAGES 17
+
+static ssize_t fuse2_simple_send(struct fuse_conn *fc, struct fuse_args *args)
+{
+	struct fuse_req *req;
+	ssize_t ret;
+	gfp_t flags = GFP_KERNEL;
+	unsigned int inlen = sizeof(struct fuse_in_header) +
+		len_args(args->in.numargs, (struct fuse_arg *) args->in.args);
+	unsigned int outlen = sizeof(struct fuse_out_header) +
+		len_args(args->out.numargs, args->out.args);
+	unsigned int maxlen = maxlen = max(inlen, outlen);
+	unsigned int npages = 0;
+	struct kvec vec[FUSE_SIMPLE_MAX_PAGES + 1];
+	struct iov_iter iter;
+	unsigned int i;
+
+	if (maxlen > FUSE_REQ_INLINE_DATA) {
+		npages = DIV_ROUND_UP(maxlen - FUSE_REQ_INLINE_DATA, PAGE_SIZE);
+		if (WARN_ON(npages > FUSE_SIMPLE_MAX_PAGES))
+			return -EIO;
+	}
+
+	if (args->force)
+		flags |= __GFP_NOFAIL;
+
+	req = fuse2_get_req_gfp(fc, npages, flags);
+	if (IS_ERR(req))
+		return PTR_ERR(req);
+
+	vec[0].iov_base = req->inlinedata;
+	vec[0].iov_len = FUSE_REQ_INLINE_DATA;
+	if (npages) {
+		ret = fuse_req_pages_fill(req, flags);
+		if (ret)
+			goto out_put;
+
+		for (i = 0; i < npages; i++) {
+			vec[i + 1].iov_base = page_address(req->pages[i]);
+			vec[i + 1].iov_len = req->page_descs[i].length;
+		}
+	}
+
+	iov_iter_kvec(&iter, READ, vec, npages + 1, inlen);
+	iov_iter_advance(&iter, sizeof(struct fuse_in_header));
+	for (i = 0; i < args->in.numargs; i++) {
+		struct fuse_in_arg *a = &args->in.args[i];
+
+		ret = _copy_to_iter(a->value, a->size, &iter);
+		WARN_ON(ret != a->size);
+	}
+	req->inline_inlen = min_t(unsigned int, inlen, FUSE_REQ_INLINE_DATA);
+	req->inh.opcode = args->in.h.opcode;
+	req->inh.nodeid = args->in.h.nodeid;
+
+	req->inline_outlen = min_t(unsigned int, outlen, FUSE_REQ_INLINE_DATA);
+	req->max_outlen = req->mand_outlen = outlen;
+	if (args->out.argvar)
+		req->mand_outlen = sizeof(struct fuse_out_header);
+
+	if (args->force)
+		__set_bit(FR_FORCE, &req->flags);
+	if (args->killable)
+		__set_bit(FR_KILLABLE, &req->flags);
+	req->inh.len = inlen;
+	ret = fuse2_request_send(fc, req);
+	if (ret)
+		goto out_put;
+
+	iov_iter_kvec(&iter, WRITE, vec, npages + 1, req->outh.len);
+	iov_iter_advance(&iter, sizeof(struct fuse_out_header));
+	for (i = 0; i < args->out.numargs; i++) {
+		struct fuse_arg *a = &args->out.args[i];
+
+		if (a->value)
+			_copy_from_iter(a->value, a->size, &iter);
+		else
+			iov_iter_advance(&iter, a->size);
+	}
+	if (args->out.argvar)
+		ret = req->outh.len - req->mand_outlen;
+
+out_put:
+	fuse2_put_request(req);
+
+	return ret;
+}
+
+ssize_t fuse2_simple_request(struct fuse_conn *fc, struct fuse_args *args)
+{
+	ssize_t res;
+	uid_t uid = from_kuid(fc->user_ns, current_fsuid());
+	gid_t gid = from_kgid(fc->user_ns, current_fsgid());
+	pid_t pid = pid_nr_ns(task_pid(current), fc->pid_ns);
+
+	if (unlikely(uid == ((uid_t)-1) || gid == ((gid_t)-1)))
+		return -EOVERFLOW;
+
+	if (WARN_ON(args->out.argvar && args->out.numargs != 1))
+		return -EIO;
+
+	if (fc->dev_ops->simple_send) {
+			res = fc->dev_ops->simple_send(fc->dev_priv, args,
+					       uid, gid, pid);
+		if (res != -EPROBE_DEFER)
+			return res;
+	}
+
+	return fuse2_simple_send(fc, args);
+}
+
+void fuse2_queue_forget(struct fuse_conn *fc, struct fuse_forget *forget,
+		       u64 nodeid, u64 nlookup)
+{
+	forget->forget_one.nodeid = nodeid;
+	forget->forget_one.nlookup = nlookup;
+
+	fc->dev_ops->forget(fc->dev_priv, forget);
+}
+
+void fuse2_force_forget(struct fuse_conn *fc, u64 nodeid)
+{
+	struct fuse_forget *forget;
+
+	forget = kzalloc(sizeof(struct fuse_forget), GFP_KERNEL | __GFP_NOFAIL);
+	fuse2_queue_forget(fc, forget, nodeid, 1);
+}
+
+int __init fuse_req_cache_init(void)
+{
+	fuse_req_cachep = kmem_cache_create("fuse2_request",
+					    sizeof(struct fuse_req),
+					    0, 0, NULL);
+	if (!fuse_req_cachep)
+		return -ENOMEM;
+
+	return 0;
+}
+
+void fuse_req_cache_cleanup(void)
+{
+	kmem_cache_destroy(fuse_req_cachep);
+}
diff --git a/fs/fuse2/xattr.c b/fs/fuse2/xattr.c
new file mode 100644
index 0000000..47710c9
--- /dev/null
+++ b/fs/fuse2/xattr.c
@@ -0,0 +1,246 @@
+/*
+ * FUSE: Filesystem in Userspace
+ * Copyright (C) 2001-2016  Miklos Szeredi <miklos@szeredi.hu>
+ *
+ * This program can be distributed under the terms of the GNU GPL.
+ * See the file COPYING.
+ */
+
+#include "fuse_i.h"
+
+#include <linux/xattr.h>
+#include <linux/posix_acl_xattr.h>
+
+int fuse2_setxattr(struct inode *inode, const char *name, const void *value,
+		  size_t size, int flags)
+{
+	struct fuse_conn *fc = get_fuse_conn(inode);
+	FUSE_ARGS(args);
+	struct fuse_setxattr_in inarg;
+	int err;
+
+	if (fc->no_setxattr)
+		return -EOPNOTSUPP;
+
+	memset(&inarg, 0, sizeof(inarg));
+	inarg.size = size;
+	inarg.flags = flags;
+	args.in.h.opcode = FUSE_SETXATTR;
+	args.in.h.nodeid = get_node_id(inode);
+	args.in.numargs = 3;
+	args.in.args[0].size = sizeof(inarg);
+	args.in.args[0].value = &inarg;
+	args.in.args[1].size = strlen(name) + 1;
+	args.in.args[1].value = name;
+	args.in.args[2].size = size;
+	args.in.args[2].value = value;
+	err = fuse2_simple_request(fc, &args);
+	if (err == -ENOSYS) {
+		fc->no_setxattr = 1;
+		err = -EOPNOTSUPP;
+	}
+	return err;
+}
+
+ssize_t fuse2_getxattr(struct inode *inode, const char *name, void *value,
+		      size_t size)
+{
+	struct fuse_conn *fc = get_fuse_conn(inode);
+	FUSE_ARGS(args);
+	struct fuse_getxattr_in inarg;
+	struct fuse_getxattr_out outarg;
+	ssize_t ret;
+
+	if (fc->no_getxattr)
+		return -EOPNOTSUPP;
+
+	memset(&inarg, 0, sizeof(inarg));
+	inarg.size = size;
+	args.in.h.opcode = FUSE_GETXATTR;
+	args.in.h.nodeid = get_node_id(inode);
+	args.in.numargs = 2;
+	args.in.args[0].size = sizeof(inarg);
+	args.in.args[0].value = &inarg;
+	args.in.args[1].size = strlen(name) + 1;
+	args.in.args[1].value = name;
+	/* This is really two different operations rolled into one */
+	args.out.numargs = 1;
+	if (size) {
+		args.out.argvar = 1;
+		args.out.args[0].size = size;
+		args.out.args[0].value = value;
+	} else {
+		args.out.args[0].size = sizeof(outarg);
+		args.out.args[0].value = &outarg;
+	}
+	ret = fuse2_simple_request(fc, &args);
+	if (!ret && !size)
+		ret = min_t(ssize_t, outarg.size, XATTR_SIZE_MAX);
+	if (ret == -ENOSYS) {
+		fc->no_getxattr = 1;
+		ret = -EOPNOTSUPP;
+	}
+	return ret;
+}
+
+static int fuse_verify_xattr_list(char *list, size_t size)
+{
+	size_t origsize = size;
+
+	while (size) {
+		size_t thislen = strnlen(list, size);
+
+		if (!thislen || thislen == size)
+			return -EIO;
+
+		size -= thislen + 1;
+		list += thislen + 1;
+	}
+
+	return origsize;
+}
+
+ssize_t fuse2_listxattr(struct dentry *entry, char *list, size_t size)
+{
+	struct inode *inode = d_inode(entry);
+	struct fuse_conn *fc = get_fuse_conn(inode);
+	FUSE_ARGS(args);
+	struct fuse_getxattr_in inarg;
+	struct fuse_getxattr_out outarg;
+	ssize_t ret;
+
+	if (!fuse2_allow_current_process(fc))
+		return -EACCES;
+
+	if (fc->no_listxattr)
+		return -EOPNOTSUPP;
+
+	memset(&inarg, 0, sizeof(inarg));
+	inarg.size = size;
+	args.in.h.opcode = FUSE_LISTXATTR;
+	args.in.h.nodeid = get_node_id(inode);
+	args.in.numargs = 1;
+	args.in.args[0].size = sizeof(inarg);
+	args.in.args[0].value = &inarg;
+	/* This is really two different operations rolled into one */
+	args.out.numargs = 1;
+	if (size) {
+		args.out.argvar = 1;
+		args.out.args[0].size = size;
+		args.out.args[0].value = list;
+	} else {
+		args.out.args[0].size = sizeof(outarg);
+		args.out.args[0].value = &outarg;
+	}
+	ret = fuse2_simple_request(fc, &args);
+	if (!ret && !size)
+		ret = min_t(ssize_t, outarg.size, XATTR_LIST_MAX);
+	if (ret > 0 && size)
+		ret = fuse_verify_xattr_list(list, ret);
+	if (ret == -ENOSYS) {
+		fc->no_listxattr = 1;
+		ret = -EOPNOTSUPP;
+	}
+	return ret;
+}
+
+int fuse2_removexattr(struct inode *inode, const char *name)
+{
+	struct fuse_conn *fc = get_fuse_conn(inode);
+	FUSE_ARGS(args);
+	int err;
+
+	if (fc->no_removexattr)
+		return -EOPNOTSUPP;
+
+	args.in.h.opcode = FUSE_REMOVEXATTR;
+	args.in.h.nodeid = get_node_id(inode);
+	args.in.numargs = 1;
+	args.in.args[0].size = strlen(name) + 1;
+	args.in.args[0].value = name;
+	err = fuse2_simple_request(fc, &args);
+	if (err == -ENOSYS) {
+		fc->no_removexattr = 1;
+		err = -EOPNOTSUPP;
+	}
+	return err;
+}
+
+static int fuse_xattr_get(const struct xattr_handler *handler,
+			 struct dentry *dentry, struct inode *inode,
+			 const char *name, void *value, size_t size)
+{
+	return fuse2_getxattr(inode, name, value, size);
+}
+
+static int fuse_xattr_set(const struct xattr_handler *handler,
+			  struct dentry *dentry, struct inode *inode,
+			  const char *name, const void *value, size_t size,
+			  int flags)
+{
+	if (!value)
+		return fuse2_removexattr(inode, name);
+
+	return fuse2_setxattr(inode, name, value, size, flags);
+}
+
+static bool no_xattr_list(struct dentry *dentry)
+{
+	return false;
+}
+
+static int no_xattr_get(const struct xattr_handler *handler,
+			struct dentry *dentry, struct inode *inode,
+			const char *name, void *value, size_t size)
+{
+	return -EOPNOTSUPP;
+}
+
+static int no_xattr_set(const struct xattr_handler *handler,
+			struct dentry *dentry, struct inode *nodee,
+			const char *name, const void *value,
+			size_t size, int flags)
+{
+	return -EOPNOTSUPP;
+}
+
+static const struct xattr_handler fuse_xattr_handler = {
+	.prefix = "",
+	.get    = fuse_xattr_get,
+	.set    = fuse_xattr_set,
+};
+
+const struct xattr_handler *fuse2_xattr_handlers[] = {
+	&fuse_xattr_handler,
+	NULL
+};
+
+const struct xattr_handler *fuse2_acl_xattr_handlers[] = {
+	&posix_acl_access_xattr_handler,
+	&posix_acl_default_xattr_handler,
+	&fuse_xattr_handler,
+	NULL
+};
+
+static const struct xattr_handler fuse_no_acl_access_xattr_handler = {
+	.name  = XATTR_NAME_POSIX_ACL_ACCESS,
+	.flags = ACL_TYPE_ACCESS,
+	.list  = no_xattr_list,
+	.get   = no_xattr_get,
+	.set   = no_xattr_set,
+};
+
+static const struct xattr_handler fuse_no_acl_default_xattr_handler = {
+	.name  = XATTR_NAME_POSIX_ACL_DEFAULT,
+	.flags = ACL_TYPE_ACCESS,
+	.list  = no_xattr_list,
+	.get   = no_xattr_get,
+	.set   = no_xattr_set,
+};
+
+const struct xattr_handler *fuse2_no_acl_xattr_handlers[] = {
+	&fuse_no_acl_access_xattr_handler,
+	&fuse_no_acl_default_xattr_handler,
+	&fuse_xattr_handler,
+	NULL
+};
diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h
index 2971d29..2c290d2 100644
--- a/include/uapi/linux/fuse.h
+++ b/include/uapi/linux/fuse.h
@@ -244,6 +244,7 @@ struct fuse_file_lock {
 #define FOPEN_NONSEEKABLE	(1 << 2)
 #define FOPEN_CACHE_DIR		(1 << 3)
 #define FOPEN_STREAM		(1 << 4)
+#define FOPEN_MAP		(1 << 5)
 
 /**
  * INIT request/reply flags
@@ -422,6 +423,7 @@ enum fuse_opcode {
 	FUSE_RENAME2		= 45,
 	FUSE_LSEEK		= 46,
 	FUSE_COPY_FILE_RANGE	= 47,
+	FUSE_MAP		= 50,
 
 	/* CUSE specific operations */
 	CUSE_INIT		= 4096,
@@ -571,6 +573,12 @@ struct fuse_read_in {
 	uint32_t	padding;
 };
 
+struct fuse_map_out {
+	uint64_t	mapfd;
+	uint64_t	offset;
+	uint64_t	size;
+};
+
 #define FUSE_COMPAT_WRITE_IN_SIZE 24
 
 struct fuse_write_in {
@@ -823,6 +831,14 @@ struct fuse_notify_retrieve_in {
 
 /* Device ioctls: */
 #define FUSE_DEV_IOC_CLONE	_IOR(229, 0, uint32_t)
+#define FUSE2_DEV_IOC_BIND	_IOW(229, 1, uint32_t)
+#define FUSE2_DEV_IOC_PROC	_IO(229, 2)
+#define FUSE2_DEV_IOC_READ	_IO(229, 3)
+#define FUSE2_DEV_IOC_MAP_OPEN	_IO(229, 4)
+#define FUSE2_DEV_IOC_MAP_CLOSE	_IO(230, 5)
+
+#define FUSE2_MMAP_INBUF_OFFSET		0x00000000UL
+#define FUSE2_MMAP_OUTBUF_OFFSET	0x80000000UL
 
 struct fuse_lseek_in {
 	uint64_t	fh;
diff --git a/samples/Kconfig b/samples/Kconfig
index c8dacb4..a4ea071 100644
--- a/samples/Kconfig
+++ b/samples/Kconfig
@@ -169,4 +169,8 @@
 	  as mount API and statx().  Note that this is restricted to the x86
 	  arch whilst it accesses system calls that aren't yet in all arches.
 
+config SAMPLE_FUSE2
+       bool "Build example server for fuse2"
+       select HEADERS_INSTALL
+
 endif # SAMPLES
diff --git a/samples/Makefile b/samples/Makefile
index 7d6e4ca..2d6034d 100644
--- a/samples/Makefile
+++ b/samples/Makefile
@@ -20,3 +20,4 @@
 obj-$(CONFIG_VIDEO_PCI_SKELETON)	+= v4l/
 obj-y					+= vfio-mdev/
 subdir-$(CONFIG_SAMPLE_VFS)		+= vfs
+subdir-$(CONFIG_SAMPLE_FUSE2)		+= fuse2/
diff --git a/samples/fuse2/.gitignore b/samples/fuse2/.gitignore
new file mode 100644
index 0000000..9b64f2d
--- /dev/null
+++ b/samples/fuse2/.gitignore
@@ -0,0 +1 @@
+loraw
diff --git a/samples/fuse2/Makefile b/samples/fuse2/Makefile
new file mode 100644
index 0000000..fe8bf58
--- /dev/null
+++ b/samples/fuse2/Makefile
@@ -0,0 +1,4 @@
+hostprogs-y := loraw
+always := $(hostprogs-y)
+KBUILD_HOSTCFLAGS += -I$(objtree)/usr/include
+KBUILD_HOSTLDLIBS += -pthread
diff --git a/samples/fuse2/loraw.c b/samples/fuse2/loraw.c
new file mode 100644
index 0000000..8984d44
--- /dev/null
+++ b/samples/fuse2/loraw.c
@@ -0,0 +1,1155 @@
+#define _GNU_SOURCE
+#define LO_NOTHREAD 1
+
+#include <linux/fuse.h>
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include <string.h>
+#include <limits.h>
+#include <dirent.h>
+#include <assert.h>
+#include <errno.h>
+#include <err.h>
+#include <inttypes.h>
+#include <sched.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/mount.h>
+#include <sys/mman.h>
+#include <pthread.h>
+#include <semaphore.h>
+
+struct lo_inode {
+	struct lo_inode *next; /* protected by lo->mutex */
+	struct lo_inode *prev; /* protected by lo->mutex */
+	int fd;
+	dev_t dev;
+	uint64_t refcount; /* protected by lo->mutex */
+	struct fuse_attr attr;
+};
+
+struct lo_file {
+	union {
+		struct lo_file *next;
+		struct {
+			int fd;
+			int mapfd;
+		};
+		struct {
+			DIR *dp;
+			struct dirent *entry;
+			off_t offset;
+		};
+	};
+};
+
+struct lo_config {
+	int debug;
+	int version;
+	int single;
+	int bind;
+	int proc;
+	int map;
+	uint64_t timeout;
+	const char *source;
+	int nothread;
+};
+
+struct lo_data {
+	pthread_mutex_t mutex;
+#ifdef LO_NOTHREAD
+#define LO_INODE_MAX 65536
+	struct lo_inode inodes[LO_INODE_MAX];
+	struct lo_inode *free_inodes;
+#define LO_FILE_MAX 65536
+	struct lo_file files[LO_FILE_MAX];
+	struct lo_file *free_files;
+	sem_t sem;
+#endif
+	struct lo_config c;
+	struct lo_inode root;
+};
+
+struct lo_chan {
+	struct lo_data *lo;
+	enum { FUDEV_V1, FUDEV_V2, FUDEV_AUX } type;
+	int fd;
+	int filled;
+	int mapped;
+	void *inbuf;
+	void *outbuf;
+	size_t len;
+	size_t bufsize;
+};
+
+
+#ifdef LO_NOTHREAD
+static inline int lo_nothread(struct lo_data *lo)
+{
+	return lo->c.nothread;
+}
+
+static inline void lo_mutex_init_nt(struct lo_data *lo)
+{
+	sem_init(&lo->sem, 1, 1);
+}
+
+static inline void lo_mutex_lock_nt(struct lo_data *lo)
+{
+	sem_wait(&lo->sem);
+}
+
+static inline void lo_mutex_unlock_nt(struct lo_data *lo)
+{
+	sem_post(&lo->sem);
+}
+
+static inline struct lo_inode *lo_alloc_inode_nt(struct lo_data *lo)
+{
+	struct lo_inode *inode;
+
+	lo_mutex_lock_nt(lo);
+	inode = lo->free_inodes;
+	if (inode)
+		lo->free_inodes = inode->next;
+	lo_mutex_unlock_nt(lo);
+
+	memset(inode, 0, sizeof(*inode));
+
+	return inode;
+}
+
+static inline struct lo_file *lo_alloc_file_nt(struct lo_data *lo)
+{
+	struct lo_file *lf;
+
+	lo_mutex_lock_nt(lo);
+	lf = lo->free_files;
+	if (lf)
+		lo->free_files = lf->next;
+	lo_mutex_unlock_nt(lo);
+
+	memset(lf, 0, sizeof(*lf));
+
+	return lf;
+
+}
+
+static inline void lo_free_inode_locked_nt(struct lo_data *lo,
+					   struct lo_inode *inode)
+{
+	inode->next = lo->free_inodes;
+	lo->free_inodes = inode;
+}
+
+static inline void lo_free_file_locked_nt(struct lo_data *lo,
+					  struct lo_file *lf)
+{
+	lf->next = lo->free_files;
+	lo->free_files = lf;
+}
+
+static inline void lo_free_inode_nt(struct lo_data *lo, struct lo_inode *inode)
+{
+	lo_mutex_lock_nt(lo);
+	lo_free_inode_locked_nt(lo, inode);
+	lo_mutex_unlock_nt(lo);
+}
+
+static inline void lo_free_file_nt(struct lo_data *lo, struct lo_file *lf)
+{
+	lo_mutex_lock_nt(lo);
+	lo_free_file_locked_nt(lo, lf);
+	lo_mutex_unlock_nt(lo);
+}
+
+static inline struct lo_data *lo_alloc_lo_nt(void)
+{
+	struct lo_data *lo;
+	unsigned int i;
+
+	lo = mmap(NULL, sizeof(struct lo_data), PROT_READ | PROT_WRITE,
+		   MAP_SHARED | MAP_ANONYMOUS, -1, 0);
+	if (lo == MAP_FAILED) {
+		warn("mmap(NULL, %zu, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0)", sizeof(struct lo_data));
+		return NULL;
+	}
+
+	for (i = 0; i < LO_INODE_MAX; i++)
+		lo_free_inode_locked_nt(lo, &lo->inodes[i]);
+	for (i = 0; i < LO_FILE_MAX; i++)
+		lo_free_file_locked_nt(lo, &lo->files[i]);
+
+	return lo;
+}
+#else
+
+#define lo_nothread(lo) ((void) lo, 0)
+#define lo_alloc_inode_nt(lo) NULL
+#define lo_alloc_file_nt(lo) NULL
+#define lo_free_inode_nt(lo, inode) abort()
+#define lo_free_file_nt(lo, lf) abort()
+#define lo_alloc_lo_nt() NULL
+#define lo_mutex_init_nt(lo) abort()
+#define lo_mutex_lock_nt(lo) abort()
+#define lo_mutex_unlock_nt(lo) abort()
+
+#endif
+
+static void lo_mutex_init(struct lo_data *lo)
+{
+	if (!lo_nothread(lo))
+		pthread_mutex_init(&lo->mutex, NULL);
+	else
+		lo_mutex_init_nt(lo);
+}
+
+static void lo_mutex_lock(struct lo_data *lo)
+{
+	if (!lo_nothread(lo))
+		pthread_mutex_lock(&lo->mutex);
+	else
+		lo_mutex_lock_nt(lo);
+}
+
+static void lo_mutex_unlock(struct lo_data *lo)
+{
+	if (!lo_nothread(lo))
+		pthread_mutex_unlock(&lo->mutex);
+	else
+		lo_mutex_unlock_nt(lo);
+}
+
+static struct lo_inode *lo_alloc_inode(struct lo_data *lo)
+{
+	if (!lo_nothread(lo))
+		return calloc(1, sizeof(struct lo_inode));
+	else
+		return lo_alloc_inode_nt(lo);
+}
+
+static struct lo_file *lo_alloc_file(struct lo_data *lo)
+{
+	if (!lo_nothread(lo))
+		return calloc(1, sizeof(struct lo_file));
+	else
+		return lo_alloc_file_nt(lo);
+}
+
+static void lo_free_inode(struct lo_data *lo, struct lo_inode *inode)
+{
+	if (!lo_nothread(lo))
+		free(inode);
+	else
+		lo_free_inode_nt(lo, inode);
+}
+
+static void lo_free_file(struct lo_data *lo, struct lo_file *lf)
+{
+	if (!lo_nothread(lo))
+		free(lf);
+	else
+		lo_free_file_nt(lo, lf);
+}
+
+static struct lo_inode *lo_inode(struct lo_data *lo, uint64_t ino)
+{
+	if (ino == FUSE_ROOT_ID)
+		return &lo->root;
+	else
+		return (struct lo_inode *) (uintptr_t) ino;
+}
+
+static int lo_debug(struct lo_chan *lc)
+{
+	return lc->lo->c.debug;
+}
+
+static void lo_reply(struct lo_chan *lc, int error, size_t argsize)
+{
+
+	struct fuse_in_header *inh = lc->inbuf;
+	struct fuse_out_header *outh = lc->outbuf;
+	int res;
+
+	outh->len = sizeof(struct fuse_out_header) + argsize;
+	outh->error = -error;
+	outh->unique = inh->unique;
+
+	if (lo_debug(lc)) {
+		fprintf(stderr,
+			"unique: %"PRIu64", opcode: %i, nodeid: %"PRIu64", insize: %zu\n",
+			inh->unique, inh->opcode, inh->nodeid, lc->len);
+
+		fprintf(stderr, "   error: %i, outsize: %u\n",
+			error, outh->len);
+	}
+
+	if (lc->mapped) {
+		res = ioctl(lc->fd, FUSE2_DEV_IOC_PROC, 0);
+		if (res == -1)
+			err(1, "writing/reading fuse device");
+
+		if (res > 0) {
+			lc->len = res;
+			lc->filled = 1;
+		}
+	} else {
+		res = write(lc->fd, lc->outbuf, outh->len);
+		if (res == -1)
+			err(1, "writing fuse device");
+	}
+}
+
+static void *lo_out_arg(struct lo_chan *lc)
+{
+	return ((struct fuse_out_header *) lc->outbuf) + 1;
+}
+
+static void lo_convert_stat(const struct stat *stat, struct fuse_attr *attr)
+{
+	memset(attr, 0, sizeof(*attr));
+
+	attr->ino	= stat->st_ino;
+	attr->mode	= stat->st_mode;
+	attr->nlink	= stat->st_nlink;
+	attr->uid	= stat->st_uid;
+	attr->gid	= stat->st_gid;
+	attr->rdev	= stat->st_rdev;
+	attr->size	= stat->st_size;
+	attr->blksize	= stat->st_blksize;
+	attr->blocks	= stat->st_blocks;
+	attr->atime	= stat->st_atime;
+	attr->mtime	= stat->st_mtime;
+	attr->ctime	= stat->st_ctime;
+	attr->atimensec	= stat->st_atim.tv_nsec;
+	attr->mtimensec	= stat->st_mtim.tv_nsec;
+	attr->ctimensec	= stat->st_ctim.tv_nsec;
+}
+
+static void lo_getattr(struct lo_chan *lc, struct fuse_in_header *inh,
+		       struct fuse_getattr_in *inarg)
+{
+	struct lo_data *lo = lc->lo;
+	struct fuse_attr_out *outarg = lo_out_arg(lc);
+
+	(void) inarg;
+
+	if (lo_debug(lc))
+		fprintf(stderr, "lo_getattr(ino=%"PRIu64")\n", inh->nodeid);
+
+	outarg->attr_valid = lo->c.timeout;
+	outarg->attr_valid_nsec = 0;
+	outarg->dummy = 0;
+	outarg->attr = lo_inode(lo, inh->nodeid)->attr;
+	lo_reply(lc, 0, sizeof(*outarg));
+}
+
+static struct lo_inode *lo_find(struct lo_data *lo, struct stat *st)
+{
+	struct lo_inode *p;
+	struct lo_inode *ret = NULL;
+
+	lo_mutex_lock(lo);
+	for (p = lo->root.next; p != &lo->root; p = p->next) {
+		if (p->attr.ino == st->st_ino && p->dev == st->st_dev) {
+			assert(p->refcount > 0);
+			ret = p;
+			ret->refcount++;
+			break;
+		}
+	}
+	lo_mutex_unlock(lo);
+	return ret;
+}
+
+static void lo_lookup(struct lo_chan *lc, struct fuse_in_header *inh,
+		      char *name)
+{
+	struct lo_data *lo = lc->lo;
+	struct lo_inode *inode, *parent = lo_inode(lo, inh->nodeid);
+	struct fuse_entry_out *outarg = lo_out_arg(lc);
+	struct stat stat;
+	int newfd;
+	int res;
+	int saverr;
+
+	if (lo_debug(lc)) {
+		fprintf(stderr, "lo_lookup(parent=%"PRIu64", name=%s)\n",
+			inh->nodeid, name);
+	}
+
+	newfd = openat(parent->fd, name, O_PATH | O_NOFOLLOW);
+	if (newfd == -1)
+		goto out_err;
+
+	res = fstatat(newfd, "", &stat, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+	if (res == -1)
+		goto out_err;
+
+
+	inode = lo_find(lo, &stat);
+	if (inode) {
+		close(newfd);
+		newfd = -1;
+	} else {
+		struct lo_inode *prev, *next;
+
+		saverr = ENOMEM;
+		inode = lo_alloc_inode(lo);
+		if (!inode)
+			goto out_err;
+
+		inode->refcount = 1;
+		inode->fd = newfd;
+		inode->dev = stat.st_dev;
+		lo_convert_stat(&stat, &inode->attr);
+
+		lo_mutex_lock(lo);
+		prev = &lo->root;
+		next = prev->next;
+		next->prev = inode;
+		inode->next = next;
+		inode->prev = prev;
+		prev->next = inode;
+		lo_mutex_unlock(lo);
+	}
+	memset(outarg, 0, sizeof(*outarg));
+	outarg->nodeid = (uintptr_t) inode;
+	outarg->entry_valid = lo->c.timeout;
+	outarg->attr_valid = lo->c.timeout;
+	outarg->attr = inode->attr;
+
+	if (lo_debug(lc)) {
+		fprintf(stderr, "  %"PRIu64"/%s -> %"PRIu64"\n",
+			inh->nodeid, name, outarg->nodeid);
+	}
+
+	lo_reply(lc, 0, sizeof(*outarg));
+	return;
+
+out_err:
+	saverr = errno;
+	if (newfd != -1)
+		close(newfd);
+	lo_reply(lc, saverr, 0);
+}
+
+static void lo_open(struct lo_chan *lc, struct fuse_in_header *inh,
+		    struct fuse_open_in *inarg)
+{
+	struct lo_data *lo = lc->lo;
+	struct lo_inode *inode = lo_inode(lo, inh->nodeid);
+	struct fuse_open_out *outarg = lo_out_arg(lc);
+	struct lo_file *lf;
+	char buf[64];
+	int fd;
+
+	sprintf(buf, "/proc/self/fd/%i", inode->fd);
+	fd = open(buf, inarg->flags & ~O_NOFOLLOW);
+	if (fd == -1) {
+		lo_reply(lc, errno, 0);
+		return;
+	}
+
+	lf = lo_alloc_file(lo);
+	if (lf == NULL)
+		errx(1, "malloc failed");
+
+	memset(outarg, 0, sizeof(*outarg));
+	outarg->fh = (uintptr_t) lf;
+	outarg->open_flags = FOPEN_DIRECT_IO;
+
+	lf->fd = fd;
+	if (lo->c.map) {
+		lf->mapfd = ioctl(lc->fd, FUSE2_DEV_IOC_MAP_OPEN, fd);
+		if (lf->mapfd == -1)
+			warn("FUSE2_DEV_IOC_MAP_OPEN");
+		else
+			outarg->open_flags |= FOPEN_MAP;
+	}
+
+	lo_reply(lc, 0, sizeof(*outarg));
+}
+
+static struct lo_file *lo_file(uint64_t fh)
+{
+	return (void *) (uintptr_t) fh;
+}
+
+static void lo_release(struct lo_chan *lc, struct fuse_in_header *inh,
+		       struct fuse_release_in *inarg)
+{
+	struct lo_file *lf = lo_file(inarg->fh);
+
+	(void) inh;
+
+	close(lf->fd);
+	if (lf->mapfd != -1)
+		ioctl(lc->fd, FUSE2_DEV_IOC_MAP_CLOSE, lf->mapfd);
+
+	lo_free_file(lc->lo, lf);
+	lo_reply(lc, 0, 0);
+}
+
+static void lo_read(struct lo_chan *lc, struct fuse_in_header *inh,
+		    struct fuse_read_in *inarg)
+{
+	char *outarg = lo_out_arg(lc);
+	struct lo_file *lf = lo_file(inarg->fh);
+	ssize_t res;
+
+	(void) inh;
+
+	if (inarg->size > lc->bufsize - (outarg - (char *) lc->outbuf)) {
+		lo_reply(lc, EOVERFLOW, 0);
+		return;
+	}
+
+	res = pread(lf->fd, outarg, inarg->size, inarg->offset);
+	if (res == -1) {
+		lo_reply(lc, errno, 0);
+		return;
+	}
+
+	lo_reply(lc, 0, res);
+}
+
+static void lo_map(struct lo_chan *lc, struct fuse_in_header *inh,
+		    struct fuse_read_in *inarg)
+{
+	struct lo_file *lf = lo_file(inarg->fh);
+	struct fuse_map_out *outarg = lo_out_arg(lc);
+
+	(void) inh;
+
+	if (lo_debug(lc))
+		fprintf(stderr, "lo_map(offset=%"PRIu64", size=%u)\n",
+			inarg->offset, inarg->size);
+
+	outarg->mapfd = lf->mapfd;
+	outarg->offset = inarg->offset;
+	outarg->size = inarg->size;
+
+	lo_reply(lc, 0, sizeof(*outarg));
+}
+
+
+static void lo_opendir(struct lo_chan *lc, struct fuse_in_header *inh,
+		       struct fuse_open_in *inarg)
+{
+	struct lo_data *lo = lc->lo;
+	struct lo_inode *inode = lo_inode(lo, inh->nodeid);
+	struct fuse_open_out *outarg = lo_out_arg(lc);
+	struct lo_file *lf;
+	int fd;
+	DIR *dp;
+
+	(void) inarg;
+
+	fd = openat(inode->fd, ".", O_RDONLY);
+	if (fd == -1) {
+		lo_reply(lc, errno, 0);
+		return;
+	}
+
+	dp = fdopendir(fd);
+	if (dp == NULL) {
+		int saverr = errno;
+
+		close(fd);
+		lo_reply(lc, saverr, 0);
+		return;
+	}
+
+	lf = lo_alloc_file(lo);
+	if (lf == NULL)
+		errx(1, "malloc failed");
+
+	memset(outarg, 0, sizeof(*outarg));
+	outarg->fh = (uintptr_t) lf;
+	outarg->open_flags = 0;
+
+	lf->dp = dp;
+
+	lo_reply(lc, 0, sizeof(*outarg));
+}
+
+static void lo_releasedir(struct lo_chan *lc, struct fuse_in_header *inh,
+		       struct fuse_release_in *inarg)
+{
+	struct lo_file *lf = lo_file(inarg->fh);
+
+	(void) inh;
+
+	closedir(lf->dp);
+
+	lo_free_file(lc->lo, lf);
+	lo_reply(lc, 0, 0);
+}
+
+static void lo_readdir(struct lo_chan *lc, struct fuse_in_header *inh,
+		       struct fuse_read_in *inarg)
+{
+	void *p = lo_out_arg(lc);
+	struct lo_file *lf = lo_file(inarg->fh);
+	size_t rem = inarg->size;
+	int err = 0;
+	const char *name;
+	size_t namelen, entlen, entlen_padded;
+	struct fuse_dirent *dirent;
+	off_t nextoff;
+
+	(void) inh;
+
+	if (inarg->size > lc->bufsize - (p - lc->outbuf)) {
+		lo_reply(lc, EOVERFLOW, 0);
+		return;
+	}
+
+	if ((off_t) inarg->offset != lf->offset) {
+		seekdir(lf->dp, inarg->offset);
+		lf->entry = NULL;
+		lf->offset = inarg->offset;
+	}
+	while (1) {
+		if (!lf->entry) {
+			errno = 0;
+			lf->entry = readdir(lf->dp);
+			if (!lf->entry) {
+				if (errno) {
+					err = errno;
+					break;
+				} else {
+					break; 
+				}
+			}
+		}
+		nextoff = lf->entry->d_off;
+		name = lf->entry->d_name;
+		namelen = strlen(name);
+		entlen = FUSE_NAME_OFFSET + namelen;
+		entlen_padded = FUSE_DIRENT_ALIGN(entlen);
+		if (entlen_padded > rem)
+			break;
+
+		dirent = (struct fuse_dirent *) p;
+
+		dirent->ino = lf->entry->d_ino;
+		dirent->off = nextoff;
+		dirent->namelen = namelen;
+		dirent->type = lf->entry->d_type;
+		memcpy(dirent->name, name, namelen);
+		memset(dirent->name + namelen, 0, entlen_padded - entlen);
+
+		p += entlen_padded;
+		rem -= entlen_padded;
+
+		lf->entry = NULL;
+		lf->offset = nextoff;
+	}
+
+	if (err && rem == inarg->size)
+		lo_reply(lc, err, 0);
+	else
+		lo_reply(lc, 0, inarg->size - rem);
+}
+
+static void unref_inode(struct lo_data *lo, struct lo_inode *inode, uint64_t n)
+{
+	if (!inode)
+		return;
+
+	lo_mutex_lock(lo);
+	assert(inode->refcount >= n);
+	inode->refcount -= n;
+	if (!inode->refcount) {
+		struct lo_inode *prev, *next;
+
+		prev = inode->prev;
+		next = inode->next;
+		next->prev = prev;
+		prev->next = next;
+		lo_mutex_unlock(lo);
+
+		close(inode->fd);
+		lo_free_inode(lo, inode);
+	} else {
+		lo_mutex_unlock(lo);
+	}
+}
+
+static void lo_forget_one(struct lo_data *lo, uint64_t nodeid,
+			  uint64_t nlookup)
+{
+	struct lo_inode *inode = lo_inode(lo, nodeid);
+
+	if (lo->c.debug) {
+		fprintf(stderr, "  forget %"PRIu64" %"PRIu64" -%"PRIu64"\n",
+			nodeid, inode->refcount, nlookup);
+	}
+
+	unref_inode(lo, inode, nlookup);
+
+}
+
+static void lo_forget(struct lo_data *lo, struct fuse_in_header *inh,
+		      struct fuse_forget_in *inarg)
+{
+	lo_forget_one(lo, inh->nodeid, inarg->nlookup);
+}
+
+static void lo_batch_forget(struct lo_data *lo, struct fuse_in_header *inh,
+			    struct fuse_batch_forget_in *inarg)
+{
+	struct fuse_forget_one *param = (void *) (inarg + 1);
+	unsigned int i;
+
+	(void) inh;
+
+	for (i = 0; i < inarg->count; i++)
+		lo_forget_one(lo, param[i].nodeid, param[i].nlookup);
+}
+
+static void lo_init(struct lo_chan *lc, struct fuse_in_header *inh,
+		    struct fuse_init_in *inarg)
+{
+	struct fuse_init_out *outarg = lo_out_arg(lc);
+
+	(void) inh;
+
+	memset(outarg, 0, sizeof(*outarg));
+	outarg->flags = inarg->flags &
+		(FUSE_ATOMIC_O_TRUNC | FUSE_BIG_WRITES | FUSE_PARALLEL_DIROPS |
+		 FUSE_HANDLE_KILLPRIV);
+	outarg->major = FUSE_KERNEL_VERSION;
+	outarg->minor = FUSE_KERNEL_MINOR_VERSION;
+
+	lo_reply(lc, 0, sizeof(*outarg));
+}
+
+static ssize_t lo_getreq(struct lo_chan *lc)
+{
+	ssize_t res;
+
+	if (lc->mapped)
+		res = ioctl(lc->fd, FUSE2_DEV_IOC_READ, 0);
+	else
+		res = read(lc->fd, lc->inbuf, lc->bufsize);
+	if (res != -1)
+		lc->len = res;
+
+	return res;
+}
+
+static void lo_process(struct lo_chan *lc)
+{
+	int res;
+	struct fuse_in_header *inh = lc->inbuf;
+	void *arg = inh + 1;
+
+	if (!lc->filled) {
+		res = lo_getreq(lc);
+		if (res == -1)
+			err(1, "reading from device");
+	}
+
+	lc->filled = 0;
+
+	if (lc->len < sizeof(*inh))
+		errx(1, "short read from fuse device");
+
+	switch (inh->opcode) {
+	case FUSE_INIT:
+		lo_init(lc, inh, arg);
+		break;
+
+	case FUSE_LOOKUP:
+		lo_lookup(lc, inh, arg);
+		break;
+
+	case FUSE_GETATTR:
+		lo_getattr(lc, inh, arg);
+		break;
+
+	case FUSE_OPEN:
+		lo_open(lc, inh, arg);
+		break;
+
+	case FUSE_RELEASE:
+		lo_release(lc, inh, arg);
+		break;
+
+	case FUSE_READ:
+		lo_read(lc, inh, arg);
+		break;
+
+	case FUSE_MAP:
+		lo_map(lc, inh, arg);
+		break;
+
+	case FUSE_OPENDIR:
+		lo_opendir(lc, inh, arg);
+		break;
+
+	case FUSE_RELEASEDIR:
+		lo_releasedir(lc, inh, arg);
+		break;
+
+	case FUSE_READDIR:
+		lo_readdir(lc, inh, arg);
+		break;
+
+	case FUSE_FORGET:
+		lo_forget(lc->lo, inh, arg);
+		break;
+
+	case FUSE_BATCH_FORGET:
+		lo_batch_forget(lc->lo, inh, arg);
+		break;
+
+	default:
+		lo_reply(lc, ENOSYS, 0);
+	}
+
+#if 0
+	{
+		static int slow_ctr, fast_ctr;
+		int *cp;
+
+		cp = lc->type == FUDEV_AUX ? &fast_ctr : &slow_ctr;
+		if (__atomic_add_fetch(cp, 1, __ATOMIC_SEQ_CST) % 1000000 == 0)
+			fprintf(stderr, "slow: %9i fast: %9i total: %9i\r",
+				slow_ctr, fast_ctr, slow_ctr + fast_ctr);
+	}
+#endif
+}
+
+static void lo_alloc_bufs(struct lo_chan *lc)
+{
+	int res;
+
+	res = posix_memalign(&lc->inbuf, 0x1000, lc->bufsize);
+	if (res)
+		errx(1, "allocating aligned buffer: %s", strerror(res));
+
+	res = posix_memalign(&lc->outbuf, 0x1000, lc->bufsize);
+	if (res)
+		errx(1, "allocating aligned buffer: %s", strerror(res));
+}
+
+static void lo_map_bufs(struct lo_chan *lc)
+{
+	lc->inbuf = mmap(NULL, lc->bufsize, PROT_READ, MAP_SHARED, lc->fd,
+			 FUSE2_MMAP_INBUF_OFFSET);
+	if (lc->inbuf == MAP_FAILED)
+		err(1, "mmap of inbuf failed");
+
+	lc->outbuf = mmap(NULL, lc->bufsize, PROT_WRITE, MAP_SHARED, lc->fd,
+			  FUSE2_MMAP_OUTBUF_OFFSET);
+	if (lc->outbuf == MAP_FAILED)
+		err(1, "mmap of outbuf failed");
+
+	lc->mapped = 1;
+}
+
+static void lo_open_aux(struct lo_chan *lc, int devfd)
+{
+	int res;
+
+	lc->type = FUDEV_AUX;
+	lc->fd = open("/dev/fuse2-aux", O_RDWR);
+	if (lc->fd == -1)
+		err(1, "failed to open /dev/fuse2-aux");
+
+	res = ioctl(lc->fd, FUSE2_DEV_IOC_BIND, &devfd);
+	if (res == -1)
+		err(1, "failed to bind aux device");
+
+	if (lc->lo->c.proc)
+		lo_map_bufs(lc);
+	else
+		lo_alloc_bufs(lc);
+}
+
+struct lo_thread_data {
+	struct lo_chan *def_chan;
+	int cpu;
+};
+
+static void lo_start_aux(struct lo_thread_data *ltd)
+{
+	struct lo_chan lc = {
+		.lo = ltd->def_chan->lo,
+		.bufsize = lc.lo->c.proc ? 0x2000 : ltd->def_chan->bufsize,
+	};
+	cpu_set_t set;
+	int res;
+
+	CPU_ZERO(&set);
+	CPU_SET(ltd->cpu, &set);
+
+	res = sched_setaffinity(0, sizeof(set), &set);
+	if (res == -1)
+		err(1, "sched_getaffinity() to cpu %i", ltd->cpu);
+
+	lo_open_aux(&lc, ltd->def_chan->fd);
+
+	while (1)
+		lo_process(&lc);
+}
+
+static void lo_start_v1(struct lo_thread_data *ltd)
+{
+	int res;
+	int devfd = ltd->def_chan->fd;
+	struct lo_chan lc = {
+		.type = FUDEV_V1,
+		.lo = ltd->def_chan->lo,
+		.bufsize = ltd->def_chan->bufsize,
+	};
+
+	if (lc.lo->c.bind) {
+		lc.fd = open("/dev/fuse", O_RDWR);
+		if (lc.fd == -1)
+			err(1, "/dev/fuse");
+
+		res = ioctl(lc.fd, FUSE_DEV_IOC_CLONE, &devfd);
+		if (res == -1)
+			err(1, "FUSE_DEV_IOC_CLONE");
+
+	} else {
+		lc.fd = devfd;
+	}
+
+	lo_alloc_bufs(&lc);
+
+
+	while (1)
+		lo_process(&lc);
+}
+static void *lo_start_one(void *data)
+{
+	struct lo_thread_data *ltd = data;
+
+	if (ltd->def_chan->type == FUDEV_V2)
+		lo_start_aux(ltd);
+	else
+		lo_start_v1(ltd);
+
+	return NULL;
+}
+
+static int lo_start_one_nt(void *data)
+{
+	lo_start_one(data);
+	return 0;
+}
+
+static void lo_start_threads(struct lo_chan *def_chan)
+{
+	int i, n, res;
+	cpu_set_t set;
+	struct lo_thread_data *ltd;
+
+	res = sched_getaffinity(0, sizeof(set), &set);
+	if (res == -1)
+		err(1, "sched_getaffinity()");
+
+	n = CPU_COUNT(&set);
+	for (i = 0; n && i < CPU_SETSIZE; i++) {
+		if (CPU_ISSET(i, &set)) {
+			ltd = malloc(sizeof(*ltd));
+			if (ltd == NULL)
+				errx(1, "malloc failed");
+
+			ltd->def_chan = def_chan;
+			ltd->cpu = i;
+			if (!lo_nothread(def_chan->lo)) {
+				pthread_t id;
+
+				res = pthread_create(&id, NULL, lo_start_one, ltd);
+				if (res != 0) {
+					errno = res;
+					err(1, "pthread_create");
+				}
+			} else {
+				void *stack, *top;
+				size_t stack_size = 1048576;
+
+				stack = malloc(stack_size);
+				if (stack == NULL)
+					errx(1, "failed to allocate child stack");
+				top = stack + stack_size;
+
+				res = clone(lo_start_one_nt, top, CLONE_FILES, ltd);
+				if (res == -1)
+					err(1, "clone");
+			}
+
+			n--;
+		}
+	}
+}
+
+static void lo_usage(char *argv[])
+{
+	errx(1, "usage: %s [-d] [-s] [-b] [-m] [-1] [-2] mountpoint", argv[0]);
+}
+
+int main(int argc, char *argv[])
+{
+	struct lo_data *lo;
+	struct lo_config c = {};
+	struct lo_chan def_chan = {
+		.bufsize = 0x21000,
+	};
+	char *devname;
+	int res, pid;
+	int status;
+	struct stat stat;
+	int ctr;
+	const char *mnt = NULL;
+
+	if (argc < 2)
+		lo_usage(argv);
+
+	c.source = "/";
+	for (ctr = 1; ctr < argc; ctr++) {
+		char *arg = argv[ctr];
+
+		if (arg[0] == '-') {
+			switch (arg[1]) {
+			case 'd':
+				c.debug = 1;
+				break;
+			case 's':
+				c.single = 1;
+				break;
+			case 'b':
+				c.bind = 1;
+				break;
+			case 'p':
+				c.proc = 1;
+				break;
+			case 'm':
+				c.map = 1;
+				break;
+			case '1':
+				c.version = 1;
+				break;
+			case '2':
+				c.version = 2;
+				break;
+#ifdef LO_NOTHREAD
+			case 't':
+				c.nothread = 1;
+				break;
+#endif
+			default:
+				lo_usage(argv);
+			}
+
+		} else if (!mnt) {
+			mnt = arg;
+		} else {
+			lo_usage(argv);
+		}
+	}
+
+	if (c.nothread)
+		lo = lo_alloc_lo_nt();
+	else
+		lo = calloc(1, sizeof(struct lo_data));
+	if (lo == NULL)
+		errx(1, "failed to allocate memory");
+
+	lo->c = c;
+	lo_mutex_init(lo);
+
+	/* Don't mask creation mode, kernel already did that */
+	umask(0);
+
+	lo->root.next = lo->root.prev = &lo->root;
+	lo->root.refcount = 2;
+
+	lo->root.fd = open(lo->c.source, O_PATH);
+	if (lo->root.fd == -1)
+		err(1, "open(\"%s\", O_PATH)", lo->c.source);
+
+	res = fstatat(lo->root.fd, "", &stat,
+		      AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+	if (res == -1)
+		err(1, "statting root");
+
+	lo_convert_stat(&stat, &lo->root.attr);
+
+
+	def_chan.lo = lo;
+	def_chan.fd = -1;
+	if (lo->c.version != 1) {
+		def_chan.type = FUDEV_V2;
+		devname = "/dev/fuse2";
+		def_chan.fd = open(devname, O_RDWR);
+	}
+	if (lo->c.version != 2 && def_chan.fd == -1) {
+		def_chan.type = FUDEV_V1;
+		devname = "/dev/fuse";
+		def_chan.fd = open(devname, O_RDWR);
+	}
+	if (def_chan.fd == -1)
+		err(1, "opening %s", devname);
+
+	lo_alloc_bufs(&def_chan);
+
+	pid = fork();
+	if (pid == -1)
+		err(1, "fork");
+
+	if (pid == 0) {
+		char opts[128];
+
+		if (def_chan.type == FUDEV_V2) {
+			snprintf(opts, sizeof(opts), "fd=%i", def_chan.fd);
+			res = mount("loraw", mnt, "fuse2.loraw", 0, opts);
+		} else {
+			snprintf(opts, sizeof(opts),
+				 "fd=%i,rootmode=40000,user_id=0,group_id=0",
+				 def_chan.fd);
+
+			res = mount("loraw", mnt, "fuse.loraw", 0, opts);
+		}
+		if (res == -1)
+			exit(1);
+
+		exit(0);
+	}
+	res = lo_getreq(&def_chan);
+	if (res > 0) {
+		struct fuse_in_header *inh = def_chan.inbuf;
+
+		if (def_chan.len < sizeof(*inh))
+			errx(1, "short read from fuse device");
+		if (inh->opcode != FUSE_INIT)
+			errx(1, "FUSE_INIT expected");
+		lo_init(&def_chan, inh, (void *) (inh + 1));
+	}
+	res = waitpid(pid, &status, 0);
+	if (res == -1)
+		err(1, "waitpid failed for mount process");
+	if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
+		errx(1, "mount failed");
+
+	if (!lo->c.single)
+		lo_start_threads(&def_chan);
+
+	while (1)
+		lo_process(&def_chan);
+
+	return 0;
+}