fuse: Update to 2.9.4

Change-Id: I0877c624531286f47da29a7ab16c7ff3cfac0090
diff --git a/fuse/modules/iconv.c b/fuse/modules/iconv.c
new file mode 100644
index 0000000..9b78cfd
--- /dev/null
+++ b/fuse/modules/iconv.c
@@ -0,0 +1,739 @@
+/*
+  fuse iconv module: file name charset conversion
+  Copyright (C) 2007  Miklos Szeredi <miklos@szeredi.hu>
+
+  This program can be distributed under the terms of the GNU LGPLv2.
+  See the file COPYING.LIB
+*/
+
+#define FUSE_USE_VERSION 26
+
+#include <fuse.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <errno.h>
+#include <iconv.h>
+#include <pthread.h>
+#include <locale.h>
+#include <langinfo.h>
+
+struct iconv {
+	struct fuse_fs *next;
+	pthread_mutex_t lock;
+	char *from_code;
+	char *to_code;
+	iconv_t tofs;
+	iconv_t fromfs;
+};
+
+struct iconv_dh {
+	struct iconv *ic;
+	void *prev_buf;
+	fuse_fill_dir_t prev_filler;
+};
+
+static struct iconv *iconv_get(void)
+{
+	return fuse_get_context()->private_data;
+}
+
+static int iconv_convpath(struct iconv *ic, const char *path, char **newpathp,
+			  int fromfs)
+{
+	size_t pathlen;
+	size_t newpathlen;
+	char *newpath;
+	size_t plen;
+	char *p;
+	size_t res;
+	int err;
+
+	if (path == NULL) {
+		*newpathp = NULL;
+		return 0;
+	}
+
+	pathlen = strlen(path);
+	newpathlen = pathlen * 4;
+	newpath = malloc(newpathlen + 1);
+	if (!newpath)
+		return -ENOMEM;
+
+	plen = newpathlen;
+	p = newpath;
+	pthread_mutex_lock(&ic->lock);
+	do {
+		res = iconv(fromfs ? ic->fromfs : ic->tofs, (char **) &path,
+			    &pathlen, &p, &plen);
+		if (res == (size_t) -1) {
+			char *tmp;
+			size_t inc;
+
+			err = -EILSEQ;
+			if (errno != E2BIG)
+				goto err;
+
+			inc = (pathlen + 1) * 4;
+			newpathlen += inc;
+			tmp = realloc(newpath, newpathlen + 1);
+			err = -ENOMEM;
+			if (!tmp)
+				goto err;
+
+			p = tmp + (p - newpath);
+			plen += inc;
+			newpath = tmp;
+		}
+	} while (res == (size_t) -1);
+	pthread_mutex_unlock(&ic->lock);
+	*p = '\0';
+	*newpathp = newpath;
+	return 0;
+
+err:
+	iconv(fromfs ? ic->fromfs : ic->tofs, NULL, NULL, NULL, NULL);
+	pthread_mutex_unlock(&ic->lock);
+	free(newpath);
+	return err;
+}
+
+static int iconv_getattr(const char *path, struct stat *stbuf)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_getattr(ic->next, newpath, stbuf);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_fgetattr(const char *path, struct stat *stbuf,
+			  struct fuse_file_info *fi)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_fgetattr(ic->next, newpath, stbuf, fi);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_access(const char *path, int mask)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_access(ic->next, newpath, mask);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_readlink(const char *path, char *buf, size_t size)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_readlink(ic->next, newpath, buf, size);
+		if (!err) {
+			char *newlink;
+			err = iconv_convpath(ic, buf, &newlink, 1);
+			if (!err) {
+				strncpy(buf, newlink, size - 1);
+				buf[size - 1] = '\0';
+				free(newlink);
+			}
+		}
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_opendir(const char *path, struct fuse_file_info *fi)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_opendir(ic->next, newpath, fi);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_dir_fill(void *buf, const char *name,
+			  const struct stat *stbuf, loff_t off)
+{
+	struct iconv_dh *dh = buf;
+	char *newname;
+	int res = 0;
+	if (iconv_convpath(dh->ic, name, &newname, 1) == 0) {
+		res = dh->prev_filler(dh->prev_buf, newname, stbuf, off);
+		free(newname);
+	}
+	return res;
+}
+
+static int iconv_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+			 loff_t offset, struct fuse_file_info *fi)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		struct iconv_dh dh;
+		dh.ic = ic;
+		dh.prev_buf = buf;
+		dh.prev_filler = filler;
+		err = fuse_fs_readdir(ic->next, newpath, &dh, iconv_dir_fill,
+				      offset, fi);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_releasedir(const char *path, struct fuse_file_info *fi)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_releasedir(ic->next, newpath, fi);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_mknod(const char *path, mode_t mode, dev_t rdev)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_mknod(ic->next, newpath, mode, rdev);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_mkdir(const char *path, mode_t mode)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_mkdir(ic->next, newpath, mode);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_unlink(const char *path)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_unlink(ic->next, newpath);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_rmdir(const char *path)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_rmdir(ic->next, newpath);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_symlink(const char *from, const char *to)
+{
+	struct iconv *ic = iconv_get();
+	char *newfrom;
+	char *newto;
+	int err = iconv_convpath(ic, from, &newfrom, 0);
+	if (!err) {
+		err = iconv_convpath(ic, to, &newto, 0);
+		if (!err) {
+			err = fuse_fs_symlink(ic->next, newfrom, newto);
+			free(newto);
+		}
+		free(newfrom);
+	}
+	return err;
+}
+
+static int iconv_rename(const char *from, const char *to)
+{
+	struct iconv *ic = iconv_get();
+	char *newfrom;
+	char *newto;
+	int err = iconv_convpath(ic, from, &newfrom, 0);
+	if (!err) {
+		err = iconv_convpath(ic, to, &newto, 0);
+		if (!err) {
+			err = fuse_fs_rename(ic->next, newfrom, newto);
+			free(newto);
+		}
+		free(newfrom);
+	}
+	return err;
+}
+
+static int iconv_link(const char *from, const char *to)
+{
+	struct iconv *ic = iconv_get();
+	char *newfrom;
+	char *newto;
+	int err = iconv_convpath(ic, from, &newfrom, 0);
+	if (!err) {
+		err = iconv_convpath(ic, to, &newto, 0);
+		if (!err) {
+			err = fuse_fs_link(ic->next, newfrom, newto);
+			free(newto);
+		}
+		free(newfrom);
+	}
+	return err;
+}
+
+static int iconv_chmod(const char *path, mode_t mode)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_chmod(ic->next, newpath, mode);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_chown(const char *path, uid_t uid, gid_t gid)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_chown(ic->next, newpath, uid, gid);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_truncate(const char *path, loff_t size)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_truncate(ic->next, newpath, size);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_ftruncate(const char *path, loff_t size,
+			   struct fuse_file_info *fi)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_ftruncate(ic->next, newpath, size, fi);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_utimens(const char *path, const struct timespec ts[2])
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_utimens(ic->next, newpath, ts);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_create(const char *path, mode_t mode,
+			struct fuse_file_info *fi)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_create(ic->next, newpath, mode, fi);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_open_file(const char *path, struct fuse_file_info *fi)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_open(ic->next, newpath, fi);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_read_buf(const char *path, struct fuse_bufvec **bufp,
+			  size_t size, loff_t offset, struct fuse_file_info *fi)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_read_buf(ic->next, newpath, bufp, size, offset, fi);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_write_buf(const char *path, struct fuse_bufvec *buf,
+			   loff_t offset, struct fuse_file_info *fi)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_write_buf(ic->next, newpath, buf, offset, fi);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_statfs(const char *path, struct statvfs *stbuf)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_statfs(ic->next, newpath, stbuf);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_flush(const char *path, struct fuse_file_info *fi)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_flush(ic->next, newpath, fi);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_release(const char *path, struct fuse_file_info *fi)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_release(ic->next, newpath, fi);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_fsync(const char *path, int isdatasync,
+		       struct fuse_file_info *fi)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_fsync(ic->next, newpath, isdatasync, fi);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_fsyncdir(const char *path, int isdatasync,
+			  struct fuse_file_info *fi)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_fsyncdir(ic->next, newpath, isdatasync, fi);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_setxattr(const char *path, const char *name,
+			  const char *value, size_t size, int flags)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_setxattr(ic->next, newpath, name, value, size,
+				       flags);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_getxattr(const char *path, const char *name, char *value,
+			  size_t size)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_getxattr(ic->next, newpath, name, value, size);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_listxattr(const char *path, char *list, size_t size)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_listxattr(ic->next, newpath, list, size);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_removexattr(const char *path, const char *name)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_removexattr(ic->next, newpath, name);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_lock(const char *path, struct fuse_file_info *fi, int cmd,
+		      struct flock *lock)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_lock(ic->next, newpath, fi, cmd, lock);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_flock(const char *path, struct fuse_file_info *fi, int op)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_flock(ic->next, newpath, fi, op);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_bmap(const char *path, size_t blocksize, uint64_t *idx)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_bmap(ic->next, newpath, blocksize, idx);
+		free(newpath);
+	}
+	return err;
+}
+
+static void *iconv_init(struct fuse_conn_info *conn)
+{
+	struct iconv *ic = iconv_get();
+	fuse_fs_init(ic->next, conn);
+	return ic;
+}
+
+static void iconv_destroy(void *data)
+{
+	struct iconv *ic = data;
+	fuse_fs_destroy(ic->next);
+	iconv_close(ic->tofs);
+	iconv_close(ic->fromfs);
+	pthread_mutex_destroy(&ic->lock);
+	free(ic->from_code);
+	free(ic->to_code);
+	free(ic);
+}
+
+static const struct fuse_operations iconv_oper = {
+	.destroy	= iconv_destroy,
+	.init		= iconv_init,
+	.getattr	= iconv_getattr,
+	.fgetattr	= iconv_fgetattr,
+	.access		= iconv_access,
+	.readlink	= iconv_readlink,
+	.opendir	= iconv_opendir,
+	.readdir	= iconv_readdir,
+	.releasedir	= iconv_releasedir,
+	.mknod		= iconv_mknod,
+	.mkdir		= iconv_mkdir,
+	.symlink	= iconv_symlink,
+	.unlink		= iconv_unlink,
+	.rmdir		= iconv_rmdir,
+	.rename		= iconv_rename,
+	.link		= iconv_link,
+	.chmod		= iconv_chmod,
+	.chown		= iconv_chown,
+	.truncate	= iconv_truncate,
+	.ftruncate	= iconv_ftruncate,
+	.utimens	= iconv_utimens,
+	.create		= iconv_create,
+	.open		= iconv_open_file,
+	.read_buf	= iconv_read_buf,
+	.write_buf	= iconv_write_buf,
+	.statfs		= iconv_statfs,
+	.flush		= iconv_flush,
+	.release	= iconv_release,
+	.fsync		= iconv_fsync,
+	.fsyncdir	= iconv_fsyncdir,
+	.setxattr	= iconv_setxattr,
+	.getxattr	= iconv_getxattr,
+	.listxattr	= iconv_listxattr,
+	.removexattr	= iconv_removexattr,
+	.lock		= iconv_lock,
+	.flock		= iconv_flock,
+	.bmap		= iconv_bmap,
+
+	.flag_nullpath_ok = 1,
+	.flag_nopath = 1,
+};
+
+static const struct fuse_opt iconv_opts[] = {
+	FUSE_OPT_KEY("-h", 0),
+	FUSE_OPT_KEY("--help", 0),
+	{ "from_code=%s", offsetof(struct iconv, from_code), 0 },
+	{ "to_code=%s", offsetof(struct iconv, to_code), 1 },
+	FUSE_OPT_END
+};
+
+static void iconv_help(void)
+{
+	char *old = strdup(setlocale(LC_CTYPE, ""));
+	char *charmap = strdup(nl_langinfo(CODESET));
+	setlocale(LC_CTYPE, old);
+	free(old);
+	fprintf(stderr,
+"    -o from_code=CHARSET   original encoding of file names (default: UTF-8)\n"
+"    -o to_code=CHARSET	    new encoding of the file names (default: %s)\n",
+		charmap);
+	free(charmap);
+}
+
+static int iconv_opt_proc(void *data, const char *arg, int key,
+			  struct fuse_args *outargs)
+{
+	(void) data; (void) arg; (void) outargs;
+
+	if (!key) {
+		iconv_help();
+		return -1;
+	}
+
+	return 1;
+}
+
+static struct fuse_fs *iconv_new(struct fuse_args *args,
+				 struct fuse_fs *next[])
+{
+	struct fuse_fs *fs;
+	struct iconv *ic;
+	char *old = NULL;
+	const char *from;
+	const char *to;
+
+	ic = calloc(1, sizeof(struct iconv));
+	if (ic == NULL) {
+		fprintf(stderr, "fuse-iconv: memory allocation failed\n");
+		return NULL;
+	}
+
+	if (fuse_opt_parse(args, ic, iconv_opts, iconv_opt_proc) == -1)
+		goto out_free;
+
+	if (!next[0] || next[1]) {
+		fprintf(stderr, "fuse-iconv: exactly one next filesystem required\n");
+		goto out_free;
+	}
+
+	from = ic->from_code ? ic->from_code : "UTF-8";
+	to = ic->to_code ? ic->to_code : "";
+	/* FIXME: detect charset equivalence? */
+	if (!to[0])
+		old = strdup(setlocale(LC_CTYPE, ""));
+	ic->tofs = iconv_open(from, to);
+	if (ic->tofs == (iconv_t) -1) {
+		fprintf(stderr, "fuse-iconv: cannot convert from %s to %s\n",
+			to, from);
+		goto out_free;
+	}
+	ic->fromfs = iconv_open(to, from);
+	if (ic->tofs == (iconv_t) -1) {
+		fprintf(stderr, "fuse-iconv: cannot convert from %s to %s\n",
+			from, to);
+		goto out_iconv_close_to;
+	}
+	if (old) {
+		setlocale(LC_CTYPE, old);
+		free(old);
+	}
+
+	ic->next = next[0];
+	fs = fuse_fs_new(&iconv_oper, sizeof(iconv_oper), ic);
+	if (!fs)
+		goto out_iconv_close_from;
+
+	return fs;
+
+out_iconv_close_from:
+	iconv_close(ic->fromfs);
+out_iconv_close_to:
+	iconv_close(ic->tofs);
+out_free:
+	free(ic->from_code);
+	free(ic->to_code);
+	free(ic);
+	if (old) {
+		setlocale(LC_CTYPE, old);
+		free(old);
+	}
+	return NULL;
+}
+
+FUSE_REGISTER_MODULE(iconv, iconv_new);
diff --git a/fuse/modules/subdir.c b/fuse/modules/subdir.c
new file mode 100644
index 0000000..05b3379
--- /dev/null
+++ b/fuse/modules/subdir.c
@@ -0,0 +1,697 @@
+/*
+  fuse subdir module: offset paths with a base directory
+  Copyright (C) 2007  Miklos Szeredi <miklos@szeredi.hu>
+
+  This program can be distributed under the terms of the GNU LGPLv2.
+  See the file COPYING.LIB
+*/
+
+#define FUSE_USE_VERSION 26
+
+#include <fuse.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <errno.h>
+
+struct subdir {
+	char *base;
+	size_t baselen;
+	int rellinks;
+	struct fuse_fs *next;
+};
+
+static struct subdir *subdir_get(void)
+{
+	return fuse_get_context()->private_data;
+}
+
+static int subdir_addpath(struct subdir *d, const char *path, char **newpathp)
+{
+	char *newpath = NULL;
+
+	if (path != NULL) {
+		unsigned newlen = d->baselen + strlen(path);
+
+		newpath = malloc(newlen + 2);
+		if (!newpath)
+			return -ENOMEM;
+
+		if (path[0] == '/')
+			path++;
+		strcpy(newpath, d->base);
+		strcpy(newpath + d->baselen, path);
+		if (!newpath[0])
+			strcpy(newpath, ".");
+	}
+	*newpathp = newpath;
+
+	return 0;
+}
+
+static int subdir_getattr(const char *path, struct stat *stbuf)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_getattr(d->next, newpath, stbuf);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_fgetattr(const char *path, struct stat *stbuf,
+			   struct fuse_file_info *fi)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_fgetattr(d->next, newpath, stbuf, fi);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_access(const char *path, int mask)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_access(d->next, newpath, mask);
+		free(newpath);
+	}
+	return err;
+}
+
+
+static int count_components(const char *p)
+{
+	int ctr;
+
+	for (; *p == '/'; p++);
+	for (ctr = 0; *p; ctr++) {
+		for (; *p && *p != '/'; p++);
+		for (; *p == '/'; p++);
+	}
+	return ctr;
+}
+
+static void strip_common(const char **sp, const char **tp)
+{
+	const char *s = *sp;
+	const char *t = *tp;
+	do {
+		for (; *s == '/'; s++);
+		for (; *t == '/'; t++);
+		*tp = t;
+		*sp = s;
+		for (; *s == *t && *s && *s != '/'; s++, t++);
+	} while ((*s == *t && *s) || (!*s && *t == '/') || (*s == '/' && !*t));
+}
+
+static void transform_symlink(struct subdir *d, const char *path,
+			      char *buf, size_t size)
+{
+	const char *l = buf;
+	size_t llen;
+	char *s;
+	int dotdots;
+	int i;
+
+	if (l[0] != '/' || d->base[0] != '/')
+		return;
+
+	strip_common(&l, &path);
+	if (l - buf < (long) d->baselen)
+		return;
+
+	dotdots = count_components(path);
+	if (!dotdots)
+		return;
+	dotdots--;
+
+	llen = strlen(l);
+	if (dotdots * 3 + llen + 2 > size)
+		return;
+
+	s = buf + dotdots * 3;
+	if (llen)
+		memmove(s, l, llen + 1);
+	else if (!dotdots)
+		strcpy(s, ".");
+	else
+		*s = '\0';
+
+	for (s = buf, i = 0; i < dotdots; i++, s += 3)
+		memcpy(s, "../", 3);
+}
+
+
+static int subdir_readlink(const char *path, char *buf, size_t size)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_readlink(d->next, newpath, buf, size);
+		if (!err && d->rellinks)
+			transform_symlink(d, newpath, buf, size);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_opendir(const char *path, struct fuse_file_info *fi)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_opendir(d->next, newpath, fi);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_readdir(const char *path, void *buf,
+			  fuse_fill_dir_t filler, loff_t offset,
+			  struct fuse_file_info *fi)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_readdir(d->next, newpath, buf, filler, offset,
+				      fi);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_releasedir(const char *path, struct fuse_file_info *fi)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_releasedir(d->next, newpath, fi);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_mknod(const char *path, mode_t mode, dev_t rdev)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_mknod(d->next, newpath, mode, rdev);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_mkdir(const char *path, mode_t mode)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_mkdir(d->next, newpath, mode);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_unlink(const char *path)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_unlink(d->next, newpath);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_rmdir(const char *path)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_rmdir(d->next, newpath);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_symlink(const char *from, const char *path)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_symlink(d->next, from, newpath);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_rename(const char *from, const char *to)
+{
+	struct subdir *d = subdir_get();
+	char *newfrom;
+	char *newto;
+	int err = subdir_addpath(d, from, &newfrom);
+	if (!err) {
+		err = subdir_addpath(d, to, &newto);
+		if (!err) {
+			err = fuse_fs_rename(d->next, newfrom, newto);
+			free(newto);
+		}
+		free(newfrom);
+	}
+	return err;
+}
+
+static int subdir_link(const char *from, const char *to)
+{
+	struct subdir *d = subdir_get();
+	char *newfrom;
+	char *newto;
+	int err = subdir_addpath(d, from, &newfrom);
+	if (!err) {
+		err = subdir_addpath(d, to, &newto);
+		if (!err) {
+			err = fuse_fs_link(d->next, newfrom, newto);
+			free(newto);
+		}
+		free(newfrom);
+	}
+	return err;
+}
+
+static int subdir_chmod(const char *path, mode_t mode)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_chmod(d->next, newpath, mode);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_chown(const char *path, uid_t uid, gid_t gid)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_chown(d->next, newpath, uid, gid);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_truncate(const char *path, loff_t size)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_truncate(d->next, newpath, size);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_ftruncate(const char *path, loff_t size,
+			    struct fuse_file_info *fi)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_ftruncate(d->next, newpath, size, fi);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_utimens(const char *path, const struct timespec ts[2])
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_utimens(d->next, newpath, ts);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_create(const char *path, mode_t mode,
+			 struct fuse_file_info *fi)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_create(d->next, newpath, mode, fi);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_open(const char *path, struct fuse_file_info *fi)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_open(d->next, newpath, fi);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_read_buf(const char *path, struct fuse_bufvec **bufp,
+			   size_t size, loff_t offset, struct fuse_file_info *fi)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_read_buf(d->next, newpath, bufp, size, offset, fi);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_write_buf(const char *path, struct fuse_bufvec *buf,
+			loff_t offset, struct fuse_file_info *fi)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_write_buf(d->next, newpath, buf, offset, fi);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_statfs(const char *path, struct statvfs *stbuf)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_statfs(d->next, newpath, stbuf);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_flush(const char *path, struct fuse_file_info *fi)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_flush(d->next, newpath, fi);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_release(const char *path, struct fuse_file_info *fi)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_release(d->next, newpath, fi);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_fsync(const char *path, int isdatasync,
+			struct fuse_file_info *fi)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_fsync(d->next, newpath, isdatasync, fi);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_fsyncdir(const char *path, int isdatasync,
+			   struct fuse_file_info *fi)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_fsyncdir(d->next, newpath, isdatasync, fi);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_setxattr(const char *path, const char *name,
+			   const char *value, size_t size, int flags)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_setxattr(d->next, newpath, name, value, size,
+				       flags);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_getxattr(const char *path, const char *name, char *value,
+			   size_t size)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_getxattr(d->next, newpath, name, value, size);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_listxattr(const char *path, char *list, size_t size)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_listxattr(d->next, newpath, list, size);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_removexattr(const char *path, const char *name)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_removexattr(d->next, newpath, name);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_lock(const char *path, struct fuse_file_info *fi, int cmd,
+		       struct flock *lock)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_lock(d->next, newpath, fi, cmd, lock);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_flock(const char *path, struct fuse_file_info *fi, int op)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_flock(d->next, newpath, fi, op);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_bmap(const char *path, size_t blocksize, uint64_t *idx)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_bmap(d->next, newpath, blocksize, idx);
+		free(newpath);
+	}
+	return err;
+}
+
+static void *subdir_init(struct fuse_conn_info *conn)
+{
+	struct subdir *d = subdir_get();
+	fuse_fs_init(d->next, conn);
+	return d;
+}
+
+static void subdir_destroy(void *data)
+{
+	struct subdir *d = data;
+	fuse_fs_destroy(d->next);
+	free(d->base);
+	free(d);
+}
+
+static const struct fuse_operations subdir_oper = {
+	.destroy	= subdir_destroy,
+	.init		= subdir_init,
+	.getattr	= subdir_getattr,
+	.fgetattr	= subdir_fgetattr,
+	.access		= subdir_access,
+	.readlink	= subdir_readlink,
+	.opendir	= subdir_opendir,
+	.readdir	= subdir_readdir,
+	.releasedir	= subdir_releasedir,
+	.mknod		= subdir_mknod,
+	.mkdir		= subdir_mkdir,
+	.symlink	= subdir_symlink,
+	.unlink		= subdir_unlink,
+	.rmdir		= subdir_rmdir,
+	.rename		= subdir_rename,
+	.link		= subdir_link,
+	.chmod		= subdir_chmod,
+	.chown		= subdir_chown,
+	.truncate	= subdir_truncate,
+	.ftruncate	= subdir_ftruncate,
+	.utimens	= subdir_utimens,
+	.create		= subdir_create,
+	.open		= subdir_open,
+	.read_buf	= subdir_read_buf,
+	.write_buf	= subdir_write_buf,
+	.statfs		= subdir_statfs,
+	.flush		= subdir_flush,
+	.release	= subdir_release,
+	.fsync		= subdir_fsync,
+	.fsyncdir	= subdir_fsyncdir,
+	.setxattr	= subdir_setxattr,
+	.getxattr	= subdir_getxattr,
+	.listxattr	= subdir_listxattr,
+	.removexattr	= subdir_removexattr,
+	.lock		= subdir_lock,
+	.flock		= subdir_flock,
+	.bmap		= subdir_bmap,
+
+	.flag_nullpath_ok = 1,
+	.flag_nopath = 1,
+};
+
+static const struct fuse_opt subdir_opts[] = {
+	FUSE_OPT_KEY("-h", 0),
+	FUSE_OPT_KEY("--help", 0),
+	{ "subdir=%s", offsetof(struct subdir, base), 0 },
+	{ "rellinks", offsetof(struct subdir, rellinks), 1 },
+	{ "norellinks", offsetof(struct subdir, rellinks), 0 },
+	FUSE_OPT_END
+};
+
+static void subdir_help(void)
+{
+	fprintf(stderr,
+"    -o subdir=DIR	    prepend this directory to all paths (mandatory)\n"
+"    -o [no]rellinks	    transform absolute symlinks to relative\n");
+}
+
+static int subdir_opt_proc(void *data, const char *arg, int key,
+			   struct fuse_args *outargs)
+{
+	(void) data; (void) arg; (void) outargs;
+
+	if (!key) {
+		subdir_help();
+		return -1;
+	}
+
+	return 1;
+}
+
+static struct fuse_fs *subdir_new(struct fuse_args *args,
+				  struct fuse_fs *next[])
+{
+	struct fuse_fs *fs;
+	struct subdir *d;
+
+	d = calloc(1, sizeof(struct subdir));
+	if (d == NULL) {
+		fprintf(stderr, "fuse-subdir: memory allocation failed\n");
+		return NULL;
+	}
+
+	if (fuse_opt_parse(args, d, subdir_opts, subdir_opt_proc) == -1)
+		goto out_free;
+
+	if (!next[0] || next[1]) {
+		fprintf(stderr, "fuse-subdir: exactly one next filesystem required\n");
+		goto out_free;
+	}
+
+	if (!d->base) {
+		fprintf(stderr, "fuse-subdir: missing 'subdir' option\n");
+		goto out_free;
+	}
+
+	if (d->base[0] && d->base[strlen(d->base)-1] != '/') {
+		char *tmp = realloc(d->base, strlen(d->base) + 2);
+		if (!tmp) {
+			fprintf(stderr, "fuse-subdir: memory allocation failed\n");
+			goto out_free;
+		}
+		d->base = tmp;
+		strcat(d->base, "/");
+	}
+	d->baselen = strlen(d->base);
+	d->next = next[0];
+	fs = fuse_fs_new(&subdir_oper, sizeof(subdir_oper), d);
+	if (!fs)
+		goto out_free;
+	return fs;
+
+out_free:
+	free(d->base);
+	free(d);
+	return NULL;
+}
+
+FUSE_REGISTER_MODULE(subdir, subdir_new);