ddf19c
From 18ef831cac81a6bd2336c73dda357d9d69f8fd25 Mon Sep 17 00:00:00 2001
ddf19c
From: "Dr. David Alan Gilbert" <dgilbert@redhat.com>
ddf19c
Date: Mon, 27 Jan 2020 19:00:43 +0100
ddf19c
Subject: [PATCH 012/116] virtiofsd: Add passthrough_ll
ddf19c
MIME-Version: 1.0
ddf19c
Content-Type: text/plain; charset=UTF-8
ddf19c
Content-Transfer-Encoding: 8bit
ddf19c
ddf19c
RH-Author: Dr. David Alan Gilbert <dgilbert@redhat.com>
ddf19c
Message-id: <20200127190227.40942-9-dgilbert@redhat.com>
ddf19c
Patchwork-id: 93462
ddf19c
O-Subject: [RHEL-AV-8.2 qemu-kvm PATCH 008/112] virtiofsd: Add passthrough_ll
ddf19c
Bugzilla: 1694164
ddf19c
RH-Acked-by: Philippe Mathieu-Daudé <philmd@redhat.com>
ddf19c
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
ddf19c
RH-Acked-by: Sergio Lopez Pascual <slp@redhat.com>
ddf19c
ddf19c
From: "Dr. David Alan Gilbert" <dgilbert@redhat.com>
ddf19c
ddf19c
passthrough_ll is one of the examples in the upstream fuse project
ddf19c
and is the main part of our daemon here.  It passes through requests
ddf19c
from fuse to the underlying filesystem, using syscalls as directly
ddf19c
as possible.
ddf19c
ddf19c
>From libfuse fuse-3.8.0
ddf19c
ddf19c
Signed-off-by: Dr. David Alan Gilbert <dgilbert@redhat.com>
ddf19c
  Fixed up 'GPL' to 'GPLv2' as per Dan's comments and consistent
ddf19c
  with the 'LICENSE' file in libfuse;  patch sent to libfuse to fix
ddf19c
  it upstream.
ddf19c
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
ddf19c
Signed-off-by: Dr. David Alan Gilbert <dgilbert@redhat.com>
ddf19c
(cherry picked from commit 7c6b66027241f41720240fc6ee1021cdbd975b2e)
ddf19c
ddf19c
Signed-off-by: Miroslav Rezanina <mrezanin@redhat.com>
ddf19c
---
ddf19c
 tools/virtiofsd/passthrough_ll.c | 1338 ++++++++++++++++++++++++++++++++++++++
ddf19c
 1 file changed, 1338 insertions(+)
ddf19c
 create mode 100644 tools/virtiofsd/passthrough_ll.c
ddf19c
ddf19c
diff --git a/tools/virtiofsd/passthrough_ll.c b/tools/virtiofsd/passthrough_ll.c
ddf19c
new file mode 100644
ddf19c
index 0000000..e1a6056
ddf19c
--- /dev/null
ddf19c
+++ b/tools/virtiofsd/passthrough_ll.c
ddf19c
@@ -0,0 +1,1338 @@
ddf19c
+/*
ddf19c
+  FUSE: Filesystem in Userspace
ddf19c
+  Copyright (C) 2001-2007  Miklos Szeredi <miklos@szeredi.hu>
ddf19c
+
ddf19c
+  This program can be distributed under the terms of the GNU GPLv2.
ddf19c
+  See the file COPYING.
ddf19c
+*/
ddf19c
+
ddf19c
+/** @file
ddf19c
+ *
ddf19c
+ * This file system mirrors the existing file system hierarchy of the
ddf19c
+ * system, starting at the root file system. This is implemented by
ddf19c
+ * just "passing through" all requests to the corresponding user-space
ddf19c
+ * libc functions. In contrast to passthrough.c and passthrough_fh.c,
ddf19c
+ * this implementation uses the low-level API. Its performance should
ddf19c
+ * be the least bad among the three, but many operations are not
ddf19c
+ * implemented. In particular, it is not possible to remove files (or
ddf19c
+ * directories) because the code necessary to defer actual removal
ddf19c
+ * until the file is not opened anymore would make the example much
ddf19c
+ * more complicated.
ddf19c
+ *
ddf19c
+ * When writeback caching is enabled (-o writeback mount option), it
ddf19c
+ * is only possible to write to files for which the mounting user has
ddf19c
+ * read permissions. This is because the writeback cache requires the
ddf19c
+ * kernel to be able to issue read requests for all files (which the
ddf19c
+ * passthrough filesystem cannot satisfy if it can't read the file in
ddf19c
+ * the underlying filesystem).
ddf19c
+ *
ddf19c
+ * Compile with:
ddf19c
+ *
ddf19c
+ *     gcc -Wall passthrough_ll.c `pkg-config fuse3 --cflags --libs` -o passthrough_ll
ddf19c
+ *
ddf19c
+ * ## Source code ##
ddf19c
+ * \include passthrough_ll.c
ddf19c
+ */
ddf19c
+
ddf19c
+#define _GNU_SOURCE
ddf19c
+#define FUSE_USE_VERSION 31
ddf19c
+
ddf19c
+#include "config.h"
ddf19c
+
ddf19c
+#include <fuse_lowlevel.h>
ddf19c
+#include <unistd.h>
ddf19c
+#include <stdlib.h>
ddf19c
+#include <stdio.h>
ddf19c
+#include <stddef.h>
ddf19c
+#include <stdbool.h>
ddf19c
+#include <string.h>
ddf19c
+#include <limits.h>
ddf19c
+#include <dirent.h>
ddf19c
+#include <assert.h>
ddf19c
+#include <errno.h>
ddf19c
+#include <inttypes.h>
ddf19c
+#include <pthread.h>
ddf19c
+#include <sys/file.h>
ddf19c
+#include <sys/xattr.h>
ddf19c
+
ddf19c
+#include "passthrough_helpers.h"
ddf19c
+
ddf19c
+/* We are re-using pointers to our `struct lo_inode` and `struct
ddf19c
+   lo_dirp` elements as inodes. This means that we must be able to
ddf19c
+   store uintptr_t values in a fuse_ino_t variable. The following
ddf19c
+   incantation checks this condition at compile time. */
ddf19c
+#if defined(__GNUC__) && (__GNUC__ > 4 || __GNUC__ == 4 && __GNUC_MINOR__ >= 6) && !defined __cplusplus
ddf19c
+_Static_assert(sizeof(fuse_ino_t) >= sizeof(uintptr_t),
ddf19c
+	       "fuse_ino_t too small to hold uintptr_t values!");
ddf19c
+#else
ddf19c
+struct _uintptr_to_must_hold_fuse_ino_t_dummy_struct \
ddf19c
+	{ unsigned _uintptr_to_must_hold_fuse_ino_t:
ddf19c
+			((sizeof(fuse_ino_t) >= sizeof(uintptr_t)) ? 1 : -1); };
ddf19c
+#endif
ddf19c
+
ddf19c
+struct lo_inode {
ddf19c
+	struct lo_inode *next; /* protected by lo->mutex */
ddf19c
+	struct lo_inode *prev; /* protected by lo->mutex */
ddf19c
+	int fd;
ddf19c
+	bool is_symlink;
ddf19c
+	ino_t ino;
ddf19c
+	dev_t dev;
ddf19c
+	uint64_t refcount; /* protected by lo->mutex */
ddf19c
+};
ddf19c
+
ddf19c
+enum {
ddf19c
+	CACHE_NEVER,
ddf19c
+	CACHE_NORMAL,
ddf19c
+	CACHE_ALWAYS,
ddf19c
+};
ddf19c
+
ddf19c
+struct lo_data {
ddf19c
+	pthread_mutex_t mutex;
ddf19c
+	int debug;
ddf19c
+	int writeback;
ddf19c
+	int flock;
ddf19c
+	int xattr;
ddf19c
+	const char *source;
ddf19c
+	double timeout;
ddf19c
+	int cache;
ddf19c
+	int timeout_set;
ddf19c
+	struct lo_inode root; /* protected by lo->mutex */
ddf19c
+};
ddf19c
+
ddf19c
+static const struct fuse_opt lo_opts[] = {
ddf19c
+	{ "writeback",
ddf19c
+	  offsetof(struct lo_data, writeback), 1 },
ddf19c
+	{ "no_writeback",
ddf19c
+	  offsetof(struct lo_data, writeback), 0 },
ddf19c
+	{ "source=%s",
ddf19c
+	  offsetof(struct lo_data, source), 0 },
ddf19c
+	{ "flock",
ddf19c
+	  offsetof(struct lo_data, flock), 1 },
ddf19c
+	{ "no_flock",
ddf19c
+	  offsetof(struct lo_data, flock), 0 },
ddf19c
+	{ "xattr",
ddf19c
+	  offsetof(struct lo_data, xattr), 1 },
ddf19c
+	{ "no_xattr",
ddf19c
+	  offsetof(struct lo_data, xattr), 0 },
ddf19c
+	{ "timeout=%lf",
ddf19c
+	  offsetof(struct lo_data, timeout), 0 },
ddf19c
+	{ "timeout=",
ddf19c
+	  offsetof(struct lo_data, timeout_set), 1 },
ddf19c
+	{ "cache=never",
ddf19c
+	  offsetof(struct lo_data, cache), CACHE_NEVER },
ddf19c
+	{ "cache=auto",
ddf19c
+	  offsetof(struct lo_data, cache), CACHE_NORMAL },
ddf19c
+	{ "cache=always",
ddf19c
+	  offsetof(struct lo_data, cache), CACHE_ALWAYS },
ddf19c
+
ddf19c
+	FUSE_OPT_END
ddf19c
+};
ddf19c
+
ddf19c
+static struct lo_data *lo_data(fuse_req_t req)
ddf19c
+{
ddf19c
+	return (struct lo_data *) fuse_req_userdata(req);
ddf19c
+}
ddf19c
+
ddf19c
+static struct lo_inode *lo_inode(fuse_req_t req, fuse_ino_t ino)
ddf19c
+{
ddf19c
+	if (ino == FUSE_ROOT_ID)
ddf19c
+		return &lo_data(req)->root;
ddf19c
+	else
ddf19c
+		return (struct lo_inode *) (uintptr_t) ino;
ddf19c
+}
ddf19c
+
ddf19c
+static int lo_fd(fuse_req_t req, fuse_ino_t ino)
ddf19c
+{
ddf19c
+	return lo_inode(req, ino)->fd;
ddf19c
+}
ddf19c
+
ddf19c
+static bool lo_debug(fuse_req_t req)
ddf19c
+{
ddf19c
+	return lo_data(req)->debug != 0;
ddf19c
+}
ddf19c
+
ddf19c
+static void lo_init(void *userdata,
ddf19c
+		    struct fuse_conn_info *conn)
ddf19c
+{
ddf19c
+	struct lo_data *lo = (struct lo_data*) userdata;
ddf19c
+
ddf19c
+	if(conn->capable & FUSE_CAP_EXPORT_SUPPORT)
ddf19c
+		conn->want |= FUSE_CAP_EXPORT_SUPPORT;
ddf19c
+
ddf19c
+	if (lo->writeback &&
ddf19c
+	    conn->capable & FUSE_CAP_WRITEBACK_CACHE) {
ddf19c
+		if (lo->debug)
ddf19c
+			fuse_log(FUSE_LOG_DEBUG, "lo_init: activating writeback\n");
ddf19c
+		conn->want |= FUSE_CAP_WRITEBACK_CACHE;
ddf19c
+	}
ddf19c
+	if (lo->flock && conn->capable & FUSE_CAP_FLOCK_LOCKS) {
ddf19c
+		if (lo->debug)
ddf19c
+			fuse_log(FUSE_LOG_DEBUG, "lo_init: activating flock locks\n");
ddf19c
+		conn->want |= FUSE_CAP_FLOCK_LOCKS;
ddf19c
+	}
ddf19c
+}
ddf19c
+
ddf19c
+static void lo_getattr(fuse_req_t req, fuse_ino_t ino,
ddf19c
+			     struct fuse_file_info *fi)
ddf19c
+{
ddf19c
+	int res;
ddf19c
+	struct stat buf;
ddf19c
+	struct lo_data *lo = lo_data(req);
ddf19c
+
ddf19c
+	(void) fi;
ddf19c
+
ddf19c
+	res = fstatat(lo_fd(req, ino), "", &buf, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
ddf19c
+	if (res == -1)
ddf19c
+		return (void) fuse_reply_err(req, errno);
ddf19c
+
ddf19c
+	fuse_reply_attr(req, &buf, lo->timeout);
ddf19c
+}
ddf19c
+
ddf19c
+static int utimensat_empty_nofollow(struct lo_inode *inode,
ddf19c
+				    const struct timespec *tv)
ddf19c
+{
ddf19c
+	int res;
ddf19c
+	char procname[64];
ddf19c
+
ddf19c
+	if (inode->is_symlink) {
ddf19c
+		res = utimensat(inode->fd, "", tv,
ddf19c
+				AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
ddf19c
+		if (res == -1 && errno == EINVAL) {
ddf19c
+			/* Sorry, no race free way to set times on symlink. */
ddf19c
+			errno = EPERM;
ddf19c
+		}
ddf19c
+		return res;
ddf19c
+	}
ddf19c
+	sprintf(procname, "/proc/self/fd/%i", inode->fd);
ddf19c
+
ddf19c
+	return utimensat(AT_FDCWD, procname, tv, 0);
ddf19c
+}
ddf19c
+
ddf19c
+static void lo_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr,
ddf19c
+		       int valid, struct fuse_file_info *fi)
ddf19c
+{
ddf19c
+	int saverr;
ddf19c
+	char procname[64];
ddf19c
+	struct lo_inode *inode = lo_inode(req, ino);
ddf19c
+	int ifd = inode->fd;
ddf19c
+	int res;
ddf19c
+
ddf19c
+	if (valid & FUSE_SET_ATTR_MODE) {
ddf19c
+		if (fi) {
ddf19c
+			res = fchmod(fi->fh, attr->st_mode);
ddf19c
+		} else {
ddf19c
+			sprintf(procname, "/proc/self/fd/%i", ifd);
ddf19c
+			res = chmod(procname, attr->st_mode);
ddf19c
+		}
ddf19c
+		if (res == -1)
ddf19c
+			goto out_err;
ddf19c
+	}
ddf19c
+	if (valid & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID)) {
ddf19c
+		uid_t uid = (valid & FUSE_SET_ATTR_UID) ?
ddf19c
+			attr->st_uid : (uid_t) -1;
ddf19c
+		gid_t gid = (valid & FUSE_SET_ATTR_GID) ?
ddf19c
+			attr->st_gid : (gid_t) -1;
ddf19c
+
ddf19c
+		res = fchownat(ifd, "", uid, gid,
ddf19c
+			       AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
ddf19c
+		if (res == -1)
ddf19c
+			goto out_err;
ddf19c
+	}
ddf19c
+	if (valid & FUSE_SET_ATTR_SIZE) {
ddf19c
+		if (fi) {
ddf19c
+			res = ftruncate(fi->fh, attr->st_size);
ddf19c
+		} else {
ddf19c
+			sprintf(procname, "/proc/self/fd/%i", ifd);
ddf19c
+			res = truncate(procname, attr->st_size);
ddf19c
+		}
ddf19c
+		if (res == -1)
ddf19c
+			goto out_err;
ddf19c
+	}
ddf19c
+	if (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) {
ddf19c
+		struct timespec tv[2];
ddf19c
+
ddf19c
+		tv[0].tv_sec = 0;
ddf19c
+		tv[1].tv_sec = 0;
ddf19c
+		tv[0].tv_nsec = UTIME_OMIT;
ddf19c
+		tv[1].tv_nsec = UTIME_OMIT;
ddf19c
+
ddf19c
+		if (valid & FUSE_SET_ATTR_ATIME_NOW)
ddf19c
+			tv[0].tv_nsec = UTIME_NOW;
ddf19c
+		else if (valid & FUSE_SET_ATTR_ATIME)
ddf19c
+			tv[0] = attr->st_atim;
ddf19c
+
ddf19c
+		if (valid & FUSE_SET_ATTR_MTIME_NOW)
ddf19c
+			tv[1].tv_nsec = UTIME_NOW;
ddf19c
+		else if (valid & FUSE_SET_ATTR_MTIME)
ddf19c
+			tv[1] = attr->st_mtim;
ddf19c
+
ddf19c
+		if (fi)
ddf19c
+			res = futimens(fi->fh, tv);
ddf19c
+		else
ddf19c
+			res = utimensat_empty_nofollow(inode, tv);
ddf19c
+		if (res == -1)
ddf19c
+			goto out_err;
ddf19c
+	}
ddf19c
+
ddf19c
+	return lo_getattr(req, ino, fi);
ddf19c
+
ddf19c
+out_err:
ddf19c
+	saverr = errno;
ddf19c
+	fuse_reply_err(req, saverr);
ddf19c
+}
ddf19c
+
ddf19c
+static struct lo_inode *lo_find(struct lo_data *lo, struct stat *st)
ddf19c
+{
ddf19c
+	struct lo_inode *p;
ddf19c
+	struct lo_inode *ret = NULL;
ddf19c
+
ddf19c
+	pthread_mutex_lock(&lo->mutex);
ddf19c
+	for (p = lo->root.next; p != &lo->root; p = p->next) {
ddf19c
+		if (p->ino == st->st_ino && p->dev == st->st_dev) {
ddf19c
+			assert(p->refcount > 0);
ddf19c
+			ret = p;
ddf19c
+			ret->refcount++;
ddf19c
+			break;
ddf19c
+		}
ddf19c
+	}
ddf19c
+	pthread_mutex_unlock(&lo->mutex);
ddf19c
+	return ret;
ddf19c
+}
ddf19c
+
ddf19c
+static int lo_do_lookup(fuse_req_t req, fuse_ino_t parent, const char *name,
ddf19c
+			 struct fuse_entry_param *e)
ddf19c
+{
ddf19c
+	int newfd;
ddf19c
+	int res;
ddf19c
+	int saverr;
ddf19c
+	struct lo_data *lo = lo_data(req);
ddf19c
+	struct lo_inode *inode;
ddf19c
+
ddf19c
+	memset(e, 0, sizeof(*e));
ddf19c
+	e->attr_timeout = lo->timeout;
ddf19c
+	e->entry_timeout = lo->timeout;
ddf19c
+
ddf19c
+	newfd = openat(lo_fd(req, parent), name, O_PATH | O_NOFOLLOW);
ddf19c
+	if (newfd == -1)
ddf19c
+		goto out_err;
ddf19c
+
ddf19c
+	res = fstatat(newfd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
ddf19c
+	if (res == -1)
ddf19c
+		goto out_err;
ddf19c
+
ddf19c
+	inode = lo_find(lo_data(req), &e->attr);
ddf19c
+	if (inode) {
ddf19c
+		close(newfd);
ddf19c
+		newfd = -1;
ddf19c
+	} else {
ddf19c
+		struct lo_inode *prev, *next;
ddf19c
+
ddf19c
+		saverr = ENOMEM;
ddf19c
+		inode = calloc(1, sizeof(struct lo_inode));
ddf19c
+		if (!inode)
ddf19c
+			goto out_err;
ddf19c
+
ddf19c
+		inode->is_symlink = S_ISLNK(e->attr.st_mode);
ddf19c
+		inode->refcount = 1;
ddf19c
+		inode->fd = newfd;
ddf19c
+		inode->ino = e->attr.st_ino;
ddf19c
+		inode->dev = e->attr.st_dev;
ddf19c
+
ddf19c
+		pthread_mutex_lock(&lo->mutex);
ddf19c
+		prev = &lo->root;
ddf19c
+		next = prev->next;
ddf19c
+		next->prev = inode;
ddf19c
+		inode->next = next;
ddf19c
+		inode->prev = prev;
ddf19c
+		prev->next = inode;
ddf19c
+		pthread_mutex_unlock(&lo->mutex);
ddf19c
+	}
ddf19c
+	e->ino = (uintptr_t) inode;
ddf19c
+
ddf19c
+	if (lo_debug(req))
ddf19c
+		fuse_log(FUSE_LOG_DEBUG, "  %lli/%s -> %lli\n",
ddf19c
+			(unsigned long long) parent, name, (unsigned long long) e->ino);
ddf19c
+
ddf19c
+	return 0;
ddf19c
+
ddf19c
+out_err:
ddf19c
+	saverr = errno;
ddf19c
+	if (newfd != -1)
ddf19c
+		close(newfd);
ddf19c
+	return saverr;
ddf19c
+}
ddf19c
+
ddf19c
+static void lo_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
ddf19c
+{
ddf19c
+	struct fuse_entry_param e;
ddf19c
+	int err;
ddf19c
+
ddf19c
+	if (lo_debug(req))
ddf19c
+		fuse_log(FUSE_LOG_DEBUG, "lo_lookup(parent=%" PRIu64 ", name=%s)\n",
ddf19c
+			parent, name);
ddf19c
+
ddf19c
+	err = lo_do_lookup(req, parent, name, &e);
ddf19c
+	if (err)
ddf19c
+		fuse_reply_err(req, err);
ddf19c
+	else
ddf19c
+		fuse_reply_entry(req, &e);
ddf19c
+}
ddf19c
+
ddf19c
+static void lo_mknod_symlink(fuse_req_t req, fuse_ino_t parent,
ddf19c
+			     const char *name, mode_t mode, dev_t rdev,
ddf19c
+			     const char *link)
ddf19c
+{
ddf19c
+	int res;
ddf19c
+	int saverr;
ddf19c
+	struct lo_inode *dir = lo_inode(req, parent);
ddf19c
+	struct fuse_entry_param e;
ddf19c
+
ddf19c
+	saverr = ENOMEM;
ddf19c
+
ddf19c
+	res = mknod_wrapper(dir->fd, name, link, mode, rdev);
ddf19c
+
ddf19c
+	saverr = errno;
ddf19c
+	if (res == -1)
ddf19c
+		goto out;
ddf19c
+
ddf19c
+	saverr = lo_do_lookup(req, parent, name, &e);
ddf19c
+	if (saverr)
ddf19c
+		goto out;
ddf19c
+
ddf19c
+	if (lo_debug(req))
ddf19c
+		fuse_log(FUSE_LOG_DEBUG, "  %lli/%s -> %lli\n",
ddf19c
+			(unsigned long long) parent, name, (unsigned long long) e.ino);
ddf19c
+
ddf19c
+	fuse_reply_entry(req, &e);
ddf19c
+	return;
ddf19c
+
ddf19c
+out:
ddf19c
+	fuse_reply_err(req, saverr);
ddf19c
+}
ddf19c
+
ddf19c
+static void lo_mknod(fuse_req_t req, fuse_ino_t parent,
ddf19c
+		     const char *name, mode_t mode, dev_t rdev)
ddf19c
+{
ddf19c
+	lo_mknod_symlink(req, parent, name, mode, rdev, NULL);
ddf19c
+}
ddf19c
+
ddf19c
+static void lo_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name,
ddf19c
+		     mode_t mode)
ddf19c
+{
ddf19c
+	lo_mknod_symlink(req, parent, name, S_IFDIR | mode, 0, NULL);
ddf19c
+}
ddf19c
+
ddf19c
+static void lo_symlink(fuse_req_t req, const char *link,
ddf19c
+		       fuse_ino_t parent, const char *name)
ddf19c
+{
ddf19c
+	lo_mknod_symlink(req, parent, name, S_IFLNK, 0, link);
ddf19c
+}
ddf19c
+
ddf19c
+static int linkat_empty_nofollow(struct lo_inode *inode, int dfd,
ddf19c
+				 const char *name)
ddf19c
+{
ddf19c
+	int res;
ddf19c
+	char procname[64];
ddf19c
+
ddf19c
+	if (inode->is_symlink) {
ddf19c
+		res = linkat(inode->fd, "", dfd, name, AT_EMPTY_PATH);
ddf19c
+		if (res == -1 && (errno == ENOENT || errno == EINVAL)) {
ddf19c
+			/* Sorry, no race free way to hard-link a symlink. */
ddf19c
+			errno = EPERM;
ddf19c
+		}
ddf19c
+		return res;
ddf19c
+	}
ddf19c
+
ddf19c
+	sprintf(procname, "/proc/self/fd/%i", inode->fd);
ddf19c
+
ddf19c
+	return linkat(AT_FDCWD, procname, dfd, name, AT_SYMLINK_FOLLOW);
ddf19c
+}
ddf19c
+
ddf19c
+static void lo_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t parent,
ddf19c
+		    const char *name)
ddf19c
+{
ddf19c
+	int res;
ddf19c
+	struct lo_data *lo = lo_data(req);
ddf19c
+	struct lo_inode *inode = lo_inode(req, ino);
ddf19c
+	struct fuse_entry_param e;
ddf19c
+	int saverr;
ddf19c
+
ddf19c
+	memset(&e, 0, sizeof(struct fuse_entry_param));
ddf19c
+	e.attr_timeout = lo->timeout;
ddf19c
+	e.entry_timeout = lo->timeout;
ddf19c
+
ddf19c
+	res = linkat_empty_nofollow(inode, lo_fd(req, parent), name);
ddf19c
+	if (res == -1)
ddf19c
+		goto out_err;
ddf19c
+
ddf19c
+	res = fstatat(inode->fd, "", &e.attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
ddf19c
+	if (res == -1)
ddf19c
+		goto out_err;
ddf19c
+
ddf19c
+	pthread_mutex_lock(&lo->mutex);
ddf19c
+	inode->refcount++;
ddf19c
+	pthread_mutex_unlock(&lo->mutex);
ddf19c
+	e.ino = (uintptr_t) inode;
ddf19c
+
ddf19c
+	if (lo_debug(req))
ddf19c
+		fuse_log(FUSE_LOG_DEBUG, "  %lli/%s -> %lli\n",
ddf19c
+			(unsigned long long) parent, name,
ddf19c
+			(unsigned long long) e.ino);
ddf19c
+
ddf19c
+	fuse_reply_entry(req, &e);
ddf19c
+	return;
ddf19c
+
ddf19c
+out_err:
ddf19c
+	saverr = errno;
ddf19c
+	fuse_reply_err(req, saverr);
ddf19c
+}
ddf19c
+
ddf19c
+static void lo_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name)
ddf19c
+{
ddf19c
+	int res;
ddf19c
+
ddf19c
+	res = unlinkat(lo_fd(req, parent), name, AT_REMOVEDIR);
ddf19c
+
ddf19c
+	fuse_reply_err(req, res == -1 ? errno : 0);
ddf19c
+}
ddf19c
+
ddf19c
+static void lo_rename(fuse_req_t req, fuse_ino_t parent, const char *name,
ddf19c
+		      fuse_ino_t newparent, const char *newname,
ddf19c
+		      unsigned int flags)
ddf19c
+{
ddf19c
+	int res;
ddf19c
+
ddf19c
+	if (flags) {
ddf19c
+		fuse_reply_err(req, EINVAL);
ddf19c
+		return;
ddf19c
+	}
ddf19c
+
ddf19c
+	res = renameat(lo_fd(req, parent), name,
ddf19c
+			lo_fd(req, newparent), newname);
ddf19c
+
ddf19c
+	fuse_reply_err(req, res == -1 ? errno : 0);
ddf19c
+}
ddf19c
+
ddf19c
+static void lo_unlink(fuse_req_t req, fuse_ino_t parent, const char *name)
ddf19c
+{
ddf19c
+	int res;
ddf19c
+
ddf19c
+	res = unlinkat(lo_fd(req, parent), name, 0);
ddf19c
+
ddf19c
+	fuse_reply_err(req, res == -1 ? errno : 0);
ddf19c
+}
ddf19c
+
ddf19c
+static void unref_inode(struct lo_data *lo, struct lo_inode *inode, uint64_t n)
ddf19c
+{
ddf19c
+	if (!inode)
ddf19c
+		return;
ddf19c
+
ddf19c
+	pthread_mutex_lock(&lo->mutex);
ddf19c
+	assert(inode->refcount >= n);
ddf19c
+	inode->refcount -= n;
ddf19c
+	if (!inode->refcount) {
ddf19c
+		struct lo_inode *prev, *next;
ddf19c
+
ddf19c
+		prev = inode->prev;
ddf19c
+		next = inode->next;
ddf19c
+		next->prev = prev;
ddf19c
+		prev->next = next;
ddf19c
+
ddf19c
+		pthread_mutex_unlock(&lo->mutex);
ddf19c
+		close(inode->fd);
ddf19c
+		free(inode);
ddf19c
+
ddf19c
+	} else {
ddf19c
+		pthread_mutex_unlock(&lo->mutex);
ddf19c
+	}
ddf19c
+}
ddf19c
+
ddf19c
+static void lo_forget_one(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
ddf19c
+{
ddf19c
+	struct lo_data *lo = lo_data(req);
ddf19c
+	struct lo_inode *inode = lo_inode(req, ino);
ddf19c
+
ddf19c
+	if (lo_debug(req)) {
ddf19c
+		fuse_log(FUSE_LOG_DEBUG, "  forget %lli %lli -%lli\n",
ddf19c
+			(unsigned long long) ino,
ddf19c
+			(unsigned long long) inode->refcount,
ddf19c
+			(unsigned long long) nlookup);
ddf19c
+	}
ddf19c
+
ddf19c
+	unref_inode(lo, inode, nlookup);
ddf19c
+}
ddf19c
+
ddf19c
+static void lo_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
ddf19c
+{
ddf19c
+	lo_forget_one(req, ino, nlookup);
ddf19c
+	fuse_reply_none(req);
ddf19c
+}
ddf19c
+
ddf19c
+static void lo_forget_multi(fuse_req_t req, size_t count,
ddf19c
+				struct fuse_forget_data *forgets)
ddf19c
+{
ddf19c
+	int i;
ddf19c
+
ddf19c
+	for (i = 0; i < count; i++)
ddf19c
+		lo_forget_one(req, forgets[i].ino, forgets[i].nlookup);
ddf19c
+	fuse_reply_none(req);
ddf19c
+}
ddf19c
+
ddf19c
+static void lo_readlink(fuse_req_t req, fuse_ino_t ino)
ddf19c
+{
ddf19c
+	char buf[PATH_MAX + 1];
ddf19c
+	int res;
ddf19c
+
ddf19c
+	res = readlinkat(lo_fd(req, ino), "", buf, sizeof(buf));
ddf19c
+	if (res == -1)
ddf19c
+		return (void) fuse_reply_err(req, errno);
ddf19c
+
ddf19c
+	if (res == sizeof(buf))
ddf19c
+		return (void) fuse_reply_err(req, ENAMETOOLONG);
ddf19c
+
ddf19c
+	buf[res] = '\0';
ddf19c
+
ddf19c
+	fuse_reply_readlink(req, buf);
ddf19c
+}
ddf19c
+
ddf19c
+struct lo_dirp {
ddf19c
+	DIR *dp;
ddf19c
+	struct dirent *entry;
ddf19c
+	off_t offset;
ddf19c
+};
ddf19c
+
ddf19c
+static struct lo_dirp *lo_dirp(struct fuse_file_info *fi)
ddf19c
+{
ddf19c
+	return (struct lo_dirp *) (uintptr_t) fi->fh;
ddf19c
+}
ddf19c
+
ddf19c
+static void lo_opendir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
ddf19c
+{
ddf19c
+	int error = ENOMEM;
ddf19c
+	struct lo_data *lo = lo_data(req);
ddf19c
+	struct lo_dirp *d;
ddf19c
+	int fd;
ddf19c
+
ddf19c
+	d = calloc(1, sizeof(struct lo_dirp));
ddf19c
+	if (d == NULL)
ddf19c
+		goto out_err;
ddf19c
+
ddf19c
+	fd = openat(lo_fd(req, ino), ".", O_RDONLY);
ddf19c
+	if (fd == -1)
ddf19c
+		goto out_errno;
ddf19c
+
ddf19c
+	d->dp = fdopendir(fd);
ddf19c
+	if (d->dp == NULL)
ddf19c
+		goto out_errno;
ddf19c
+
ddf19c
+	d->offset = 0;
ddf19c
+	d->entry = NULL;
ddf19c
+
ddf19c
+	fi->fh = (uintptr_t) d;
ddf19c
+	if (lo->cache == CACHE_ALWAYS)
ddf19c
+		fi->keep_cache = 1;
ddf19c
+	fuse_reply_open(req, fi);
ddf19c
+	return;
ddf19c
+
ddf19c
+out_errno:
ddf19c
+	error = errno;
ddf19c
+out_err:
ddf19c
+	if (d) {
ddf19c
+		if (fd != -1)
ddf19c
+			close(fd);
ddf19c
+		free(d);
ddf19c
+	}
ddf19c
+	fuse_reply_err(req, error);
ddf19c
+}
ddf19c
+
ddf19c
+static int is_dot_or_dotdot(const char *name)
ddf19c
+{
ddf19c
+	return name[0] == '.' && (name[1] == '\0' ||
ddf19c
+				  (name[1] == '.' && name[2] == '\0'));
ddf19c
+}
ddf19c
+
ddf19c
+static void lo_do_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
ddf19c
+			  off_t offset, struct fuse_file_info *fi, int plus)
ddf19c
+{
ddf19c
+	struct lo_dirp *d = lo_dirp(fi);
ddf19c
+	char *buf;
ddf19c
+	char *p;
ddf19c
+	size_t rem = size;
ddf19c
+	int err;
ddf19c
+
ddf19c
+	(void) ino;
ddf19c
+
ddf19c
+	buf = calloc(1, size);
ddf19c
+	if (!buf) {
ddf19c
+		err = ENOMEM;
ddf19c
+		goto error;
ddf19c
+	}
ddf19c
+	p = buf;
ddf19c
+
ddf19c
+	if (offset != d->offset) {
ddf19c
+		seekdir(d->dp, offset);
ddf19c
+		d->entry = NULL;
ddf19c
+		d->offset = offset;
ddf19c
+	}
ddf19c
+	while (1) {
ddf19c
+		size_t entsize;
ddf19c
+		off_t nextoff;
ddf19c
+		const char *name;
ddf19c
+
ddf19c
+		if (!d->entry) {
ddf19c
+			errno = 0;
ddf19c
+			d->entry = readdir(d->dp);
ddf19c
+			if (!d->entry) {
ddf19c
+				if (errno) {  // Error
ddf19c
+					err = errno;
ddf19c
+					goto error;
ddf19c
+				} else {  // End of stream
ddf19c
+					break; 
ddf19c
+				}
ddf19c
+			}
ddf19c
+		}
ddf19c
+		nextoff = d->entry->d_off;
ddf19c
+		name = d->entry->d_name;
ddf19c
+		fuse_ino_t entry_ino = 0;
ddf19c
+		if (plus) {
ddf19c
+			struct fuse_entry_param e;
ddf19c
+			if (is_dot_or_dotdot(name)) {
ddf19c
+				e = (struct fuse_entry_param) {
ddf19c
+					.attr.st_ino = d->entry->d_ino,
ddf19c
+					.attr.st_mode = d->entry->d_type << 12,
ddf19c
+				};
ddf19c
+			} else {
ddf19c
+				err = lo_do_lookup(req, ino, name, &e);
ddf19c
+				if (err)
ddf19c
+					goto error;
ddf19c
+				entry_ino = e.ino;
ddf19c
+			}
ddf19c
+
ddf19c
+			entsize = fuse_add_direntry_plus(req, p, rem, name,
ddf19c
+							 &e, nextoff);
ddf19c
+		} else {
ddf19c
+			struct stat st = {
ddf19c
+				.st_ino = d->entry->d_ino,
ddf19c
+				.st_mode = d->entry->d_type << 12,
ddf19c
+			};
ddf19c
+			entsize = fuse_add_direntry(req, p, rem, name,
ddf19c
+						    &st, nextoff);
ddf19c
+		}
ddf19c
+		if (entsize > rem) {
ddf19c
+			if (entry_ino != 0) 
ddf19c
+				lo_forget_one(req, entry_ino, 1);
ddf19c
+			break;
ddf19c
+		}
ddf19c
+		
ddf19c
+		p += entsize;
ddf19c
+		rem -= entsize;
ddf19c
+
ddf19c
+		d->entry = NULL;
ddf19c
+		d->offset = nextoff;
ddf19c
+	}
ddf19c
+
ddf19c
+    err = 0;
ddf19c
+error:
ddf19c
+    // If there's an error, we can only signal it if we haven't stored
ddf19c
+    // any entries yet - otherwise we'd end up with wrong lookup
ddf19c
+    // counts for the entries that are already in the buffer. So we
ddf19c
+    // return what we've collected until that point.
ddf19c
+    if (err && rem == size)
ddf19c
+	    fuse_reply_err(req, err);
ddf19c
+    else
ddf19c
+	    fuse_reply_buf(req, buf, size - rem);
ddf19c
+    free(buf);
ddf19c
+}
ddf19c
+
ddf19c
+static void lo_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
ddf19c
+		       off_t offset, struct fuse_file_info *fi)
ddf19c
+{
ddf19c
+	lo_do_readdir(req, ino, size, offset, fi, 0);
ddf19c
+}
ddf19c
+
ddf19c
+static void lo_readdirplus(fuse_req_t req, fuse_ino_t ino, size_t size,
ddf19c
+			   off_t offset, struct fuse_file_info *fi)
ddf19c
+{
ddf19c
+	lo_do_readdir(req, ino, size, offset, fi, 1);
ddf19c
+}
ddf19c
+
ddf19c
+static void lo_releasedir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
ddf19c
+{
ddf19c
+	struct lo_dirp *d = lo_dirp(fi);
ddf19c
+	(void) ino;
ddf19c
+	closedir(d->dp);
ddf19c
+	free(d);
ddf19c
+	fuse_reply_err(req, 0);
ddf19c
+}
ddf19c
+
ddf19c
+static void lo_create(fuse_req_t req, fuse_ino_t parent, const char *name,
ddf19c
+		      mode_t mode, struct fuse_file_info *fi)
ddf19c
+{
ddf19c
+	int fd;
ddf19c
+	struct lo_data *lo = lo_data(req);
ddf19c
+	struct fuse_entry_param e;
ddf19c
+	int err;
ddf19c
+
ddf19c
+	if (lo_debug(req))
ddf19c
+		fuse_log(FUSE_LOG_DEBUG, "lo_create(parent=%" PRIu64 ", name=%s)\n",
ddf19c
+			parent, name);
ddf19c
+
ddf19c
+	fd = openat(lo_fd(req, parent), name,
ddf19c
+		    (fi->flags | O_CREAT) & ~O_NOFOLLOW, mode);
ddf19c
+	if (fd == -1)
ddf19c
+		return (void) fuse_reply_err(req, errno);
ddf19c
+
ddf19c
+	fi->fh = fd;
ddf19c
+	if (lo->cache == CACHE_NEVER)
ddf19c
+		fi->direct_io = 1;
ddf19c
+	else if (lo->cache == CACHE_ALWAYS)
ddf19c
+		fi->keep_cache = 1;
ddf19c
+
ddf19c
+	err = lo_do_lookup(req, parent, name, &e);
ddf19c
+	if (err)
ddf19c
+		fuse_reply_err(req, err);
ddf19c
+	else
ddf19c
+		fuse_reply_create(req, &e, fi);
ddf19c
+}
ddf19c
+
ddf19c
+static void lo_fsyncdir(fuse_req_t req, fuse_ino_t ino, int datasync,
ddf19c
+			struct fuse_file_info *fi)
ddf19c
+{
ddf19c
+	int res;
ddf19c
+	int fd = dirfd(lo_dirp(fi)->dp);
ddf19c
+	(void) ino;
ddf19c
+	if (datasync)
ddf19c
+		res = fdatasync(fd);
ddf19c
+	else
ddf19c
+		res = fsync(fd);
ddf19c
+	fuse_reply_err(req, res == -1 ? errno : 0);
ddf19c
+}
ddf19c
+
ddf19c
+static void lo_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
ddf19c
+{
ddf19c
+	int fd;
ddf19c
+	char buf[64];
ddf19c
+	struct lo_data *lo = lo_data(req);
ddf19c
+
ddf19c
+	if (lo_debug(req))
ddf19c
+		fuse_log(FUSE_LOG_DEBUG, "lo_open(ino=%" PRIu64 ", flags=%d)\n",
ddf19c
+			ino, fi->flags);
ddf19c
+
ddf19c
+	/* With writeback cache, kernel may send read requests even
ddf19c
+	   when userspace opened write-only */
ddf19c
+	if (lo->writeback && (fi->flags & O_ACCMODE) == O_WRONLY) {
ddf19c
+		fi->flags &= ~O_ACCMODE;
ddf19c
+		fi->flags |= O_RDWR;
ddf19c
+	}
ddf19c
+
ddf19c
+	/* With writeback cache, O_APPEND is handled by the kernel.
ddf19c
+	   This breaks atomicity (since the file may change in the
ddf19c
+	   underlying filesystem, so that the kernel's idea of the
ddf19c
+	   end of the file isn't accurate anymore). In this example,
ddf19c
+	   we just accept that. A more rigorous filesystem may want
ddf19c
+	   to return an error here */
ddf19c
+	if (lo->writeback && (fi->flags & O_APPEND))
ddf19c
+		fi->flags &= ~O_APPEND;
ddf19c
+
ddf19c
+	sprintf(buf, "/proc/self/fd/%i", lo_fd(req, ino));
ddf19c
+	fd = open(buf, fi->flags & ~O_NOFOLLOW);
ddf19c
+	if (fd == -1)
ddf19c
+		return (void) fuse_reply_err(req, errno);
ddf19c
+
ddf19c
+	fi->fh = fd;
ddf19c
+	if (lo->cache == CACHE_NEVER)
ddf19c
+		fi->direct_io = 1;
ddf19c
+	else if (lo->cache == CACHE_ALWAYS)
ddf19c
+		fi->keep_cache = 1;
ddf19c
+	fuse_reply_open(req, fi);
ddf19c
+}
ddf19c
+
ddf19c
+static void lo_release(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
ddf19c
+{
ddf19c
+	(void) ino;
ddf19c
+
ddf19c
+	close(fi->fh);
ddf19c
+	fuse_reply_err(req, 0);
ddf19c
+}
ddf19c
+
ddf19c
+static void lo_flush(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
ddf19c
+{
ddf19c
+	int res;
ddf19c
+	(void) ino;
ddf19c
+	res = close(dup(fi->fh));
ddf19c
+	fuse_reply_err(req, res == -1 ? errno : 0);
ddf19c
+}
ddf19c
+
ddf19c
+static void lo_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
ddf19c
+		     struct fuse_file_info *fi)
ddf19c
+{
ddf19c
+	int res;
ddf19c
+	(void) ino;
ddf19c
+	if (datasync)
ddf19c
+		res = fdatasync(fi->fh);
ddf19c
+	else
ddf19c
+		res = fsync(fi->fh);
ddf19c
+	fuse_reply_err(req, res == -1 ? errno : 0);
ddf19c
+}
ddf19c
+
ddf19c
+static void lo_read(fuse_req_t req, fuse_ino_t ino, size_t size,
ddf19c
+		    off_t offset, struct fuse_file_info *fi)
ddf19c
+{
ddf19c
+	struct fuse_bufvec buf = FUSE_BUFVEC_INIT(size);
ddf19c
+
ddf19c
+	if (lo_debug(req))
ddf19c
+		fuse_log(FUSE_LOG_DEBUG, "lo_read(ino=%" PRIu64 ", size=%zd, "
ddf19c
+			"off=%lu)\n", ino, size, (unsigned long) offset);
ddf19c
+
ddf19c
+	buf.buf[0].flags = FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK;
ddf19c
+	buf.buf[0].fd = fi->fh;
ddf19c
+	buf.buf[0].pos = offset;
ddf19c
+
ddf19c
+	fuse_reply_data(req, &buf, FUSE_BUF_SPLICE_MOVE);
ddf19c
+}
ddf19c
+
ddf19c
+static void lo_write_buf(fuse_req_t req, fuse_ino_t ino,
ddf19c
+			 struct fuse_bufvec *in_buf, off_t off,
ddf19c
+			 struct fuse_file_info *fi)
ddf19c
+{
ddf19c
+	(void) ino;
ddf19c
+	ssize_t res;
ddf19c
+	struct fuse_bufvec out_buf = FUSE_BUFVEC_INIT(fuse_buf_size(in_buf));
ddf19c
+
ddf19c
+	out_buf.buf[0].flags = FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK;
ddf19c
+	out_buf.buf[0].fd = fi->fh;
ddf19c
+	out_buf.buf[0].pos = off;
ddf19c
+
ddf19c
+	if (lo_debug(req))
ddf19c
+		fuse_log(FUSE_LOG_DEBUG, "lo_write(ino=%" PRIu64 ", size=%zd, off=%lu)\n",
ddf19c
+			ino, out_buf.buf[0].size, (unsigned long) off);
ddf19c
+
ddf19c
+	res = fuse_buf_copy(&out_buf, in_buf, 0);
ddf19c
+	if(res < 0)
ddf19c
+		fuse_reply_err(req, -res);
ddf19c
+	else
ddf19c
+		fuse_reply_write(req, (size_t) res);
ddf19c
+}
ddf19c
+
ddf19c
+static void lo_statfs(fuse_req_t req, fuse_ino_t ino)
ddf19c
+{
ddf19c
+	int res;
ddf19c
+	struct statvfs stbuf;
ddf19c
+
ddf19c
+	res = fstatvfs(lo_fd(req, ino), &stbuf);
ddf19c
+	if (res == -1)
ddf19c
+		fuse_reply_err(req, errno);
ddf19c
+	else
ddf19c
+		fuse_reply_statfs(req, &stbuf);
ddf19c
+}
ddf19c
+
ddf19c
+static void lo_fallocate(fuse_req_t req, fuse_ino_t ino, int mode,
ddf19c
+			 off_t offset, off_t length, struct fuse_file_info *fi)
ddf19c
+{
ddf19c
+	int err = EOPNOTSUPP;
ddf19c
+	(void) ino;
ddf19c
+
ddf19c
+#ifdef HAVE_FALLOCATE
ddf19c
+	err = fallocate(fi->fh, mode, offset, length);
ddf19c
+	if (err < 0)
ddf19c
+		err = errno;
ddf19c
+
ddf19c
+#elif defined(HAVE_POSIX_FALLOCATE)
ddf19c
+	if (mode) {
ddf19c
+		fuse_reply_err(req, EOPNOTSUPP);
ddf19c
+		return;
ddf19c
+	}
ddf19c
+
ddf19c
+	err = posix_fallocate(fi->fh, offset, length);
ddf19c
+#endif
ddf19c
+
ddf19c
+	fuse_reply_err(req, err);
ddf19c
+}
ddf19c
+
ddf19c
+static void lo_flock(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi,
ddf19c
+		     int op)
ddf19c
+{
ddf19c
+	int res;
ddf19c
+	(void) ino;
ddf19c
+
ddf19c
+	res = flock(fi->fh, op);
ddf19c
+
ddf19c
+	fuse_reply_err(req, res == -1 ? errno : 0);
ddf19c
+}
ddf19c
+
ddf19c
+static void lo_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
ddf19c
+			size_t size)
ddf19c
+{
ddf19c
+	char *value = NULL;
ddf19c
+	char procname[64];
ddf19c
+	struct lo_inode *inode = lo_inode(req, ino);
ddf19c
+	ssize_t ret;
ddf19c
+	int saverr;
ddf19c
+
ddf19c
+	saverr = ENOSYS;
ddf19c
+	if (!lo_data(req)->xattr)
ddf19c
+		goto out;
ddf19c
+
ddf19c
+	if (lo_debug(req)) {
ddf19c
+		fuse_log(FUSE_LOG_DEBUG, "lo_getxattr(ino=%" PRIu64 ", name=%s size=%zd)\n",
ddf19c
+			ino, name, size);
ddf19c
+	}
ddf19c
+
ddf19c
+	if (inode->is_symlink) {
ddf19c
+		/* Sorry, no race free way to getxattr on symlink. */
ddf19c
+		saverr = EPERM;
ddf19c
+		goto out;
ddf19c
+	}
ddf19c
+
ddf19c
+	sprintf(procname, "/proc/self/fd/%i", inode->fd);
ddf19c
+
ddf19c
+	if (size) {
ddf19c
+		value = malloc(size);
ddf19c
+		if (!value)
ddf19c
+			goto out_err;
ddf19c
+
ddf19c
+		ret = getxattr(procname, name, value, size);
ddf19c
+		if (ret == -1)
ddf19c
+			goto out_err;
ddf19c
+		saverr = 0;
ddf19c
+		if (ret == 0)
ddf19c
+			goto out;
ddf19c
+
ddf19c
+		fuse_reply_buf(req, value, ret);
ddf19c
+	} else {
ddf19c
+		ret = getxattr(procname, name, NULL, 0);
ddf19c
+		if (ret == -1)
ddf19c
+			goto out_err;
ddf19c
+
ddf19c
+		fuse_reply_xattr(req, ret);
ddf19c
+	}
ddf19c
+out_free:
ddf19c
+	free(value);
ddf19c
+	return;
ddf19c
+
ddf19c
+out_err:
ddf19c
+	saverr = errno;
ddf19c
+out:
ddf19c
+	fuse_reply_err(req, saverr);
ddf19c
+	goto out_free;
ddf19c
+}
ddf19c
+
ddf19c
+static void lo_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size)
ddf19c
+{
ddf19c
+	char *value = NULL;
ddf19c
+	char procname[64];
ddf19c
+	struct lo_inode *inode = lo_inode(req, ino);
ddf19c
+	ssize_t ret;
ddf19c
+	int saverr;
ddf19c
+
ddf19c
+	saverr = ENOSYS;
ddf19c
+	if (!lo_data(req)->xattr)
ddf19c
+		goto out;
ddf19c
+
ddf19c
+	if (lo_debug(req)) {
ddf19c
+		fuse_log(FUSE_LOG_DEBUG, "lo_listxattr(ino=%" PRIu64 ", size=%zd)\n",
ddf19c
+			ino, size);
ddf19c
+	}
ddf19c
+
ddf19c
+	if (inode->is_symlink) {
ddf19c
+		/* Sorry, no race free way to listxattr on symlink. */
ddf19c
+		saverr = EPERM;
ddf19c
+		goto out;
ddf19c
+	}
ddf19c
+
ddf19c
+	sprintf(procname, "/proc/self/fd/%i", inode->fd);
ddf19c
+
ddf19c
+	if (size) {
ddf19c
+		value = malloc(size);
ddf19c
+		if (!value)
ddf19c
+			goto out_err;
ddf19c
+
ddf19c
+		ret = listxattr(procname, value, size);
ddf19c
+		if (ret == -1)
ddf19c
+			goto out_err;
ddf19c
+		saverr = 0;
ddf19c
+		if (ret == 0)
ddf19c
+			goto out;
ddf19c
+
ddf19c
+		fuse_reply_buf(req, value, ret);
ddf19c
+	} else {
ddf19c
+		ret = listxattr(procname, NULL, 0);
ddf19c
+		if (ret == -1)
ddf19c
+			goto out_err;
ddf19c
+
ddf19c
+		fuse_reply_xattr(req, ret);
ddf19c
+	}
ddf19c
+out_free:
ddf19c
+	free(value);
ddf19c
+	return;
ddf19c
+
ddf19c
+out_err:
ddf19c
+	saverr = errno;
ddf19c
+out:
ddf19c
+	fuse_reply_err(req, saverr);
ddf19c
+	goto out_free;
ddf19c
+}
ddf19c
+
ddf19c
+static void lo_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
ddf19c
+			const char *value, size_t size, int flags)
ddf19c
+{
ddf19c
+	char procname[64];
ddf19c
+	struct lo_inode *inode = lo_inode(req, ino);
ddf19c
+	ssize_t ret;
ddf19c
+	int saverr;
ddf19c
+
ddf19c
+	saverr = ENOSYS;
ddf19c
+	if (!lo_data(req)->xattr)
ddf19c
+		goto out;
ddf19c
+
ddf19c
+	if (lo_debug(req)) {
ddf19c
+		fuse_log(FUSE_LOG_DEBUG, "lo_setxattr(ino=%" PRIu64 ", name=%s value=%s size=%zd)\n",
ddf19c
+			ino, name, value, size);
ddf19c
+	}
ddf19c
+
ddf19c
+	if (inode->is_symlink) {
ddf19c
+		/* Sorry, no race free way to setxattr on symlink. */
ddf19c
+		saverr = EPERM;
ddf19c
+		goto out;
ddf19c
+	}
ddf19c
+
ddf19c
+	sprintf(procname, "/proc/self/fd/%i", inode->fd);
ddf19c
+
ddf19c
+	ret = setxattr(procname, name, value, size, flags);
ddf19c
+	saverr = ret == -1 ? errno : 0;
ddf19c
+
ddf19c
+out:
ddf19c
+	fuse_reply_err(req, saverr);
ddf19c
+}
ddf19c
+
ddf19c
+static void lo_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name)
ddf19c
+{
ddf19c
+	char procname[64];
ddf19c
+	struct lo_inode *inode = lo_inode(req, ino);
ddf19c
+	ssize_t ret;
ddf19c
+	int saverr;
ddf19c
+
ddf19c
+	saverr = ENOSYS;
ddf19c
+	if (!lo_data(req)->xattr)
ddf19c
+		goto out;
ddf19c
+
ddf19c
+	if (lo_debug(req)) {
ddf19c
+		fuse_log(FUSE_LOG_DEBUG, "lo_removexattr(ino=%" PRIu64 ", name=%s)\n",
ddf19c
+			ino, name);
ddf19c
+	}
ddf19c
+
ddf19c
+	if (inode->is_symlink) {
ddf19c
+		/* Sorry, no race free way to setxattr on symlink. */
ddf19c
+		saverr = EPERM;
ddf19c
+		goto out;
ddf19c
+	}
ddf19c
+
ddf19c
+	sprintf(procname, "/proc/self/fd/%i", inode->fd);
ddf19c
+
ddf19c
+	ret = removexattr(procname, name);
ddf19c
+	saverr = ret == -1 ? errno : 0;
ddf19c
+
ddf19c
+out:
ddf19c
+	fuse_reply_err(req, saverr);
ddf19c
+}
ddf19c
+
ddf19c
+#ifdef HAVE_COPY_FILE_RANGE
ddf19c
+static void lo_copy_file_range(fuse_req_t req, fuse_ino_t ino_in, off_t off_in,
ddf19c
+			       struct fuse_file_info *fi_in,
ddf19c
+			       fuse_ino_t ino_out, off_t off_out,
ddf19c
+			       struct fuse_file_info *fi_out, size_t len,
ddf19c
+			       int flags)
ddf19c
+{
ddf19c
+	ssize_t res;
ddf19c
+
ddf19c
+	if (lo_debug(req))
ddf19c
+		fuse_log(FUSE_LOG_DEBUG, "lo_copy_file_range(ino=%" PRIu64 "/fd=%lu, "
ddf19c
+				"off=%lu, ino=%" PRIu64 "/fd=%lu, "
ddf19c
+				"off=%lu, size=%zd, flags=0x%x)\n",
ddf19c
+			ino_in, fi_in->fh, off_in, ino_out, fi_out->fh, off_out,
ddf19c
+			len, flags);
ddf19c
+
ddf19c
+	res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len,
ddf19c
+			      flags);
ddf19c
+	if (res < 0)
ddf19c
+		fuse_reply_err(req, -errno);
ddf19c
+	else
ddf19c
+		fuse_reply_write(req, res);
ddf19c
+}
ddf19c
+#endif
ddf19c
+
ddf19c
+static void lo_lseek(fuse_req_t req, fuse_ino_t ino, off_t off, int whence,
ddf19c
+		     struct fuse_file_info *fi)
ddf19c
+{
ddf19c
+	off_t res;
ddf19c
+
ddf19c
+	(void)ino;
ddf19c
+	res = lseek(fi->fh, off, whence);
ddf19c
+	if (res != -1)
ddf19c
+		fuse_reply_lseek(req, res);
ddf19c
+	else
ddf19c
+		fuse_reply_err(req, errno);
ddf19c
+}
ddf19c
+
ddf19c
+static struct fuse_lowlevel_ops lo_oper = {
ddf19c
+	.init		= lo_init,
ddf19c
+	.lookup		= lo_lookup,
ddf19c
+	.mkdir		= lo_mkdir,
ddf19c
+	.mknod		= lo_mknod,
ddf19c
+	.symlink	= lo_symlink,
ddf19c
+	.link		= lo_link,
ddf19c
+	.unlink		= lo_unlink,
ddf19c
+	.rmdir		= lo_rmdir,
ddf19c
+	.rename		= lo_rename,
ddf19c
+	.forget		= lo_forget,
ddf19c
+	.forget_multi	= lo_forget_multi,
ddf19c
+	.getattr	= lo_getattr,
ddf19c
+	.setattr	= lo_setattr,
ddf19c
+	.readlink	= lo_readlink,
ddf19c
+	.opendir	= lo_opendir,
ddf19c
+	.readdir	= lo_readdir,
ddf19c
+	.readdirplus	= lo_readdirplus,
ddf19c
+	.releasedir	= lo_releasedir,
ddf19c
+	.fsyncdir	= lo_fsyncdir,
ddf19c
+	.create		= lo_create,
ddf19c
+	.open		= lo_open,
ddf19c
+	.release	= lo_release,
ddf19c
+	.flush		= lo_flush,
ddf19c
+	.fsync		= lo_fsync,
ddf19c
+	.read		= lo_read,
ddf19c
+	.write_buf      = lo_write_buf,
ddf19c
+	.statfs		= lo_statfs,
ddf19c
+	.fallocate	= lo_fallocate,
ddf19c
+	.flock		= lo_flock,
ddf19c
+	.getxattr	= lo_getxattr,
ddf19c
+	.listxattr	= lo_listxattr,
ddf19c
+	.setxattr	= lo_setxattr,
ddf19c
+	.removexattr	= lo_removexattr,
ddf19c
+#ifdef HAVE_COPY_FILE_RANGE
ddf19c
+	.copy_file_range = lo_copy_file_range,
ddf19c
+#endif
ddf19c
+	.lseek		= lo_lseek,
ddf19c
+};
ddf19c
+
ddf19c
+int main(int argc, char *argv[])
ddf19c
+{
ddf19c
+	struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
ddf19c
+	struct fuse_session *se;
ddf19c
+	struct fuse_cmdline_opts opts;
ddf19c
+	struct lo_data lo = { .debug = 0,
ddf19c
+	                      .writeback = 0 };
ddf19c
+	int ret = -1;
ddf19c
+
ddf19c
+	/* Don't mask creation mode, kernel already did that */
ddf19c
+	umask(0);
ddf19c
+
ddf19c
+	pthread_mutex_init(&lo.mutex, NULL);
ddf19c
+	lo.root.next = lo.root.prev = &lo.root;
ddf19c
+	lo.root.fd = -1;
ddf19c
+	lo.cache = CACHE_NORMAL;
ddf19c
+
ddf19c
+	if (fuse_parse_cmdline(&args, &opts) != 0)
ddf19c
+		return 1;
ddf19c
+	if (opts.show_help) {
ddf19c
+		printf("usage: %s [options] <mountpoint>\n\n", argv[0]);
ddf19c
+		fuse_cmdline_help();
ddf19c
+		fuse_lowlevel_help();
ddf19c
+		ret = 0;
ddf19c
+		goto err_out1;
ddf19c
+	} else if (opts.show_version) {
ddf19c
+		printf("FUSE library version %s\n", fuse_pkgversion());
ddf19c
+		fuse_lowlevel_version();
ddf19c
+		ret = 0;
ddf19c
+		goto err_out1;
ddf19c
+	}
ddf19c
+
ddf19c
+	if(opts.mountpoint == NULL) {
ddf19c
+		printf("usage: %s [options] <mountpoint>\n", argv[0]);
ddf19c
+		printf("       %s --help\n", argv[0]);
ddf19c
+		ret = 1;
ddf19c
+		goto err_out1;
ddf19c
+	}
ddf19c
+
ddf19c
+	if (fuse_opt_parse(&args, &lo, lo_opts, NULL)== -1)
ddf19c
+		return 1;
ddf19c
+
ddf19c
+	lo.debug = opts.debug;
ddf19c
+	lo.root.refcount = 2;
ddf19c
+	if (lo.source) {
ddf19c
+		struct stat stat;
ddf19c
+		int res;
ddf19c
+
ddf19c
+		res = lstat(lo.source, &stat;;
ddf19c
+		if (res == -1) {
ddf19c
+			fuse_log(FUSE_LOG_ERR, "failed to stat source (\"%s\"): %m\n",
ddf19c
+				 lo.source);
ddf19c
+			exit(1);
ddf19c
+		}
ddf19c
+		if (!S_ISDIR(stat.st_mode)) {
ddf19c
+			fuse_log(FUSE_LOG_ERR, "source is not a directory\n");
ddf19c
+			exit(1);
ddf19c
+		}
ddf19c
+
ddf19c
+	} else {
ddf19c
+		lo.source = "/";
ddf19c
+	}
ddf19c
+	lo.root.is_symlink = false;
ddf19c
+	if (!lo.timeout_set) {
ddf19c
+		switch (lo.cache) {
ddf19c
+		case CACHE_NEVER:
ddf19c
+			lo.timeout = 0.0;
ddf19c
+			break;
ddf19c
+
ddf19c
+		case CACHE_NORMAL:
ddf19c
+			lo.timeout = 1.0;
ddf19c
+			break;
ddf19c
+
ddf19c
+		case CACHE_ALWAYS:
ddf19c
+			lo.timeout = 86400.0;
ddf19c
+			break;
ddf19c
+		}
ddf19c
+	} else if (lo.timeout < 0) {
ddf19c
+		fuse_log(FUSE_LOG_ERR, "timeout is negative (%lf)\n",
ddf19c
+			 lo.timeout);
ddf19c
+		exit(1);
ddf19c
+	}
ddf19c
+
ddf19c
+	lo.root.fd = open(lo.source, O_PATH);
ddf19c
+	if (lo.root.fd == -1) {
ddf19c
+		fuse_log(FUSE_LOG_ERR, "open(\"%s\", O_PATH): %m\n",
ddf19c
+			 lo.source);
ddf19c
+		exit(1);
ddf19c
+	}
ddf19c
+
ddf19c
+	se = fuse_session_new(&args, &lo_oper, sizeof(lo_oper), &lo);
ddf19c
+	if (se == NULL)
ddf19c
+	    goto err_out1;
ddf19c
+
ddf19c
+	if (fuse_set_signal_handlers(se) != 0)
ddf19c
+	    goto err_out2;
ddf19c
+
ddf19c
+	if (fuse_session_mount(se, opts.mountpoint) != 0)
ddf19c
+	    goto err_out3;
ddf19c
+
ddf19c
+	fuse_daemonize(opts.foreground);
ddf19c
+
ddf19c
+	/* Block until ctrl+c or fusermount -u */
ddf19c
+	if (opts.singlethread)
ddf19c
+		ret = fuse_session_loop(se);
ddf19c
+	else
ddf19c
+		ret = fuse_session_loop_mt(se, opts.clone_fd);
ddf19c
+
ddf19c
+	fuse_session_unmount(se);
ddf19c
+err_out3:
ddf19c
+	fuse_remove_signal_handlers(se);
ddf19c
+err_out2:
ddf19c
+	fuse_session_destroy(se);
ddf19c
+err_out1:
ddf19c
+	free(opts.mountpoint);
ddf19c
+	fuse_opt_free_args(&args);
ddf19c
+
ddf19c
+	if (lo.root.fd >= 0)
ddf19c
+		close(lo.root.fd);
ddf19c
+
ddf19c
+	return ret ? 1 : 0;
ddf19c
+}
ddf19c
-- 
ddf19c
1.8.3.1
ddf19c