[BACK]Return to fuse_ops.c CVS log [TXT][DIR] Up to [local] / src / lib / libfuse

File: [local] / src / lib / libfuse / fuse_ops.c (download)

Revision 1.35, Mon Jul 16 13:10:53 2018 UTC (5 years, 10 months ago) by helg
Branch: MAIN
CVS Tags: OPENBSD_7_5_BASE, OPENBSD_7_5, OPENBSD_7_4_BASE, OPENBSD_7_4, OPENBSD_7_3_BASE, OPENBSD_7_3, OPENBSD_7_2_BASE, OPENBSD_7_2, OPENBSD_7_1_BASE, OPENBSD_7_1, OPENBSD_7_0_BASE, OPENBSD_7_0, OPENBSD_6_9_BASE, OPENBSD_6_9, OPENBSD_6_8_BASE, OPENBSD_6_8, OPENBSD_6_7_BASE, OPENBSD_6_7, OPENBSD_6_6_BASE, OPENBSD_6_6, OPENBSD_6_5_BASE, OPENBSD_6_5, OPENBSD_6_4_BASE, OPENBSD_6_4, HEAD
Changes since 1.34: +40 -1 lines

Implement FBT_FSYNC, which is called on fsync(2) and fdatasync(2).
Currently ignores the a_waitfor argument and always invokes the file
system's fsync implementation synchronously.

ok mpi@

/* $OpenBSD: fuse_ops.c,v 1.35 2018/07/16 13:10:53 helg Exp $ */
/*
 * Copyright (c) 2013 Sylvestre Gallon <ccna.syl@gmail.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <errno.h>
#include <string.h>
#include <stdlib.h>

#include "fuse_private.h"
#include "debug.h"

#define CHECK_OPT(opname)	DPRINTF("Opcode: %s\t", #opname);	\
				DPRINTF("Inode: %llu\t",		\
				    (unsigned long long)fbuf->fb_ino);	\
				if (!f->op.opname) {			\
					fbuf->fb_err = -ENOSYS;		\
					return (0);			\
				}

static int
update_attr(struct fuse *f, struct stat *attr, const char *realname,
    struct fuse_vnode *vn)
{
	int ret;

	memset(attr, 0, sizeof(struct stat));
	ret = f->op.getattr(realname, attr);

	if (attr->st_blksize == 0)
		attr->st_blksize = 512;
	if (attr->st_blocks == 0)
		attr->st_blocks = 4;

	if (!f->conf.use_ino)
		attr->st_ino = vn->ino;

	if (f->conf.set_mode)
		attr->st_mode = (attr->st_mode & S_IFMT) | (0777 & ~f->conf.umask);

	if (f->conf.set_uid)
		attr->st_uid = f->conf.uid;

	if (f->conf.set_gid)
		attr->st_gid = f->conf.gid;

	return (ret);
}

static int
ifuse_ops_init(struct fuse *f)
{
	struct fuse_conn_info fci;

	DPRINTF("Opcode: init\t");

	if (f->op.init) {
		memset(&fci, 0, sizeof(fci));
		fci.proto_minor = FUSE_MINOR_VERSION;
		fci.proto_major = FUSE_MAJOR_VERSION;

		f->op.init(&fci);
	}
	return (0);
}

static int
ifuse_ops_getattr(struct fuse *f, struct fusebuf *fbuf)
{
	struct fuse_vnode *vn;
	char *realname;

	DPRINTF("Opcode: getattr\t");
	DPRINTF("Inode: %llu\t", (unsigned long long)fbuf->fb_ino);

	memset(&fbuf->fb_attr, 0, sizeof(struct stat));

	vn = tree_get(&f->vnode_tree, fbuf->fb_ino);
	if (vn == NULL) {
		fbuf->fb_err = -errno;
		return (0);
	}

	realname = build_realname(f, vn->ino);
	if (realname == NULL) {
		fbuf->fb_err = -errno;
		return (0);
	}

	fbuf->fb_err = update_attr(f, &fbuf->fb_attr, realname, vn);
	free(realname);

	return (0);
}

static int
ifuse_ops_access(struct fuse *f, struct fusebuf *fbuf)
{
	struct fuse_vnode *vn;
	char *realname;

	CHECK_OPT(access);

	vn = tree_get(&f->vnode_tree, fbuf->fb_ino);
	if (vn == NULL) {
		fbuf->fb_err = -errno;
		return (0);
	}

	realname = build_realname(f, vn->ino);
	if (realname == NULL) {
		fbuf->fb_err = -errno;
		return (0);
	}

	fbuf->fb_err = f->op.access(realname, fbuf->fb_io_mode);
	free(realname);

	return (0);
}

static int
ifuse_ops_open(struct fuse *f, struct fusebuf *fbuf)
{
	struct fuse_file_info ffi;
	struct fuse_vnode *vn;
	char *realname;

	CHECK_OPT(open);

	memset(&ffi, 0, sizeof(ffi));
	ffi.flags = fbuf->fb_io_flags;

	vn = tree_get(&f->vnode_tree, fbuf->fb_ino);
	if (vn == NULL) {
		fbuf->fb_err = -errno;
		return (0);
	}

	realname = build_realname(f, vn->ino);
	if (realname == NULL) {
		fbuf->fb_err = -errno;
		return (0);
	}

	fbuf->fb_err = f->op.open(realname, &ffi);
	free(realname);

	if (!fbuf->fb_err)
		fbuf->fb_io_fd = ffi.fh;

	return (0);
}

static int
ifuse_ops_opendir(struct fuse *f, struct fusebuf *fbuf)
{
	struct fuse_file_info ffi;
	struct fuse_vnode *vn;
	char *realname;

	DPRINTF("Opcode: opendir\t");
	DPRINTF("Inode: %llu\t", (unsigned long long)fbuf->fb_ino);

	memset(&ffi, 0, sizeof(ffi));
	ffi.flags = fbuf->fb_io_flags;

	vn = tree_get(&f->vnode_tree, fbuf->fb_ino);
	if (vn == NULL) {
		fbuf->fb_err = -errno;
		return (0);
	}

	if (f->op.opendir) {
		realname = build_realname(f, vn->ino);
		if (realname == NULL) {
			fbuf->fb_err = -errno;
			return (0);
		}

		fbuf->fb_err = f->op.opendir(realname, &ffi);
		free(realname);
	}

	if (!fbuf->fb_err)
		fbuf->fb_io_fd = ffi.fh;

	return (0);
}

#define GENERIC_DIRSIZ(NLEN) \
((sizeof (struct dirent) - (MAXNAMLEN+1)) + ((NLEN+1 + 7) &~ 7))

/*
 * This function adds one directory entry to the buffer.
 * FUSE file systems can implement readdir in one of two ways.
 *
 * 1. Read all directory entries in one operation. The off parameter
 *    will always be 0 and this filler function always returns 0.
 * 2. The file system keeps track of the directory entry offsets and
 *    this filler function returns 1 when the buffer is full.
 *
 * OpenBSD currently supports 1. but will still call the file system's
 * readdir function multiple times if either the kernel buffer or the
 * buffer supplied by the calling application is too small to fit all
 * entries. Each call to the file system's readdir function will fill
 * the buffer with the next set of entries.
 */
static int
ifuse_fill_readdir(void *dh, const char *name, const struct stat *stbuf,
    off_t off)
{
	struct fuse *f;
	struct fuse_dirhandle *fd = dh;
	struct fuse_vnode *v;
	struct fusebuf *fbuf;
	struct dirent *dir;
	uint32_t namelen;
	uint32_t len;

	f = fd->fuse;
	fbuf = fd->buf;
	namelen = strnlen(name, MAXNAMLEN);
	len = GENERIC_DIRSIZ(namelen);

	/* buffer is full so ignore the remaining entries */
	if (fd->full || (fbuf->fb_len + len > fd->size)) {
		fd->full = 1;
		return (0);
	}

	/* already returned these entries in a previous call so skip */
	if (fd->start != 0 && fd->idx < fd->start) {
		fd->idx += len;
		return (0);
	}

	dir = (struct dirent *) &fbuf->fb_dat[fbuf->fb_len];

	if (stbuf != NULL && f->conf.use_ino)
		dir->d_fileno = stbuf->st_ino;
	else {
		/*
		 * This always behaves as if readdir_ino option is set so
		 * getcwd(3) works.
		 */
		v = get_vn_by_name_and_parent(f, (uint8_t *)name, fbuf->fb_ino);
		if (v == NULL) {
			if (strcmp(name, ".") == 0)
				dir->d_fileno = fbuf->fb_ino;
			else
				dir->d_fileno = 0xffffffff;
		} else
			dir->d_fileno = v->ino;
	}

	if (stbuf != NULL)
		dir->d_type = IFTODT(stbuf->st_mode);
	else
		dir->d_type = DT_UNKNOWN;

	dir->d_reclen = len;
	dir->d_off = off + len;		/* XXX */
	strlcpy(dir->d_name, name, sizeof(dir->d_name));
	dir->d_namlen = strlen(dir->d_name);

	fbuf->fb_len += len;
	fd->idx += len;

	return (0);
}

static int
ifuse_fill_getdir(fuse_dirh_t fd, const char *name, int type, ino_t ino)
{
	struct stat st;

	memset(&st, 0, sizeof(st));
	st.st_mode = type << 12;
	if (ino == 0)
		st.st_ino = 0xffffffff;
	else
		st.st_ino = ino;

	return (fd->filler(fd, name, &st, 0));
}

static int
ifuse_ops_readdir(struct fuse *f, struct fusebuf *fbuf)
{
	struct fuse_file_info ffi;
	struct fuse_dirhandle fd;
	struct fuse_vnode *vn;
	char *realname;
	uint64_t offset;
	uint32_t size;

	DPRINTF("Opcode: readdir\t");
	DPRINTF("Inode: %llu\t", (unsigned long long)fbuf->fb_ino);
	DPRINTF("Offset: %llu\t", fbuf->fb_io_off);
	DPRINTF("Size: %lu\t", fbuf->fb_io_len);

	memset(&ffi, 0, sizeof(ffi));
	ffi.fh = fbuf->fb_io_fd;
	offset = fbuf->fb_io_off;
	size = fbuf->fb_io_len;

	fbuf->fb_dat = calloc(1, size);

	if (fbuf->fb_dat == NULL) {
		fbuf->fb_err = -errno;
		return (0);
	}

	vn = tree_get(&f->vnode_tree, fbuf->fb_ino);
	if (vn == NULL) {
		fbuf->fb_err = -errno;
		free(fbuf->fb_dat);
		return (0);
	}

	memset(&fd, 0, sizeof(fd));
	fd.filler = ifuse_fill_readdir;
	fd.buf = fbuf;
	fd.full = 0;
	fd.size = size;
	fd.off = offset;
	fd.idx = 0;
	fd.fuse = f;
	fd.start = offset;

	realname = build_realname(f, vn->ino);
	if (realname == NULL) {
		fbuf->fb_err = -errno;
		free(fbuf->fb_dat);
		return (0);
	}

	if (f->op.readdir)
		fbuf->fb_err = f->op.readdir(realname, &fd, ifuse_fill_readdir,
		    offset, &ffi);
	else if (f->op.getdir)
		fbuf->fb_err = f->op.getdir(realname, &fd, ifuse_fill_getdir);
	else
		fbuf->fb_err = -ENOSYS;
	free(realname);

	if (fbuf->fb_err)
		fbuf->fb_len = 0;
	else if (fd.full && fbuf->fb_len == 0)
		fbuf->fb_err = -ENOBUFS;

	if (fbuf->fb_len == 0)
		free(fbuf->fb_dat);

	return (0);
}

static int
ifuse_ops_releasedir(struct fuse *f, struct fusebuf *fbuf)
{
	struct fuse_file_info ffi;
	struct fuse_vnode *vn;
	char *realname;

	DPRINTF("Opcode: releasedir\t");
	DPRINTF("Inode: %llu\t", (unsigned long long)fbuf->fb_ino);

	memset(&ffi, 0, sizeof(ffi));
	ffi.fh = fbuf->fb_io_fd;
	ffi.fh_old = ffi.fh;
	ffi.flags = fbuf->fb_io_flags;

	vn = tree_get(&f->vnode_tree, fbuf->fb_ino);
	if (vn == NULL) {
		fbuf->fb_err = -errno;
		return (0);
	}

	if (f->op.releasedir) {
		realname = build_realname(f, vn->ino);
		if (realname == NULL) {
			fbuf->fb_err = -errno;
			return (0);
		}

		fbuf->fb_err = f->op.releasedir(realname, &ffi);
		free(realname);
	}

	return (0);
}

static int
ifuse_ops_release(struct fuse *f, struct fusebuf *fbuf)
{
	struct fuse_file_info ffi;
	struct fuse_vnode *vn;
	char *realname;

	CHECK_OPT(release);

	memset(&ffi, 0, sizeof(ffi));
	ffi.fh = fbuf->fb_io_fd;
	ffi.fh_old = ffi.fh;
	ffi.flags = fbuf->fb_io_flags;

	vn = tree_get(&f->vnode_tree, fbuf->fb_ino);
	if (vn == NULL) {
		fbuf->fb_err = -errno;
		return (0);
	}

	realname = build_realname(f, vn->ino);
	if (realname == NULL) {
		fbuf->fb_err = -errno;
		return (0);
	}
	fbuf->fb_err = f->op.release(realname, &ffi);
	free(realname);

	return (0);
}

static int
ifuse_ops_fsync(struct fuse *f, struct fusebuf *fbuf)
{
	struct fuse_file_info ffi;
	struct fuse_vnode *vn;
	char *realname;
	int datasync;

	CHECK_OPT(fsync);

	memset(&ffi, 0, sizeof(ffi));
	ffi.fh = fbuf->fb_io_fd;

	/*
	 * fdatasync(2) is just a wrapper around fsync(2) so datasync
	 * is always false.
	 */
	datasync = 0;

	vn = tree_get(&f->vnode_tree, fbuf->fb_ino);
	if (vn == NULL) {
		fbuf->fb_err = -errno;
		return (0);
	}

	realname = build_realname(f, vn->ino);
	if (realname == NULL) {
		fbuf->fb_err = -errno;
		return (0);
	}
	fbuf->fb_err = f->op.fsync(realname, datasync, &ffi);
	free(realname);

	return (0);
}

static int
ifuse_ops_flush(struct fuse *f, struct fusebuf *fbuf)
{
	struct fuse_file_info ffi;
	struct fuse_vnode *vn;
	char *realname;

	CHECK_OPT(flush);

	memset(&ffi, 0, sizeof(ffi));
	ffi.fh = fbuf->fb_io_fd;
	ffi.fh_old = ffi.fh;
	ffi.flush = 1;

	vn = tree_get(&f->vnode_tree, fbuf->fb_ino);
	if (vn == NULL) {
		fbuf->fb_err = -errno;
		return (0);
	}

	realname = build_realname(f, vn->ino);
	if (realname == NULL) {
		fbuf->fb_err = -errno;
		return (0);
	}
	fbuf->fb_err = f->op.flush(realname, &ffi);
	free(realname);

	return (0);
}

static int
ifuse_ops_lookup(struct fuse *f, struct fusebuf *fbuf)
{
	struct fuse_vnode *vn;
	char *realname;

	DPRINTF("Opcode: lookup\t");
	DPRINTF("Inode: %llu\t", (unsigned long long)fbuf->fb_ino);

	if (strcmp((const char *)fbuf->fb_dat, "..") == 0) {
		vn = tree_get(&f->vnode_tree, fbuf->fb_ino);
		if (vn == NULL || vn->parent == NULL) {
			fbuf->fb_err = -ENOENT;
			return (0);
		}
		vn = vn->parent;
		if (vn->ino != FUSE_ROOT_INO)
			ref_vn(vn);
	} else {
		vn = get_vn_by_name_and_parent(f, fbuf->fb_dat, fbuf->fb_ino);
		if (vn == NULL) {
			vn = alloc_vn(f, (const char *)fbuf->fb_dat, -1,
			    fbuf->fb_ino);
			if (vn == NULL) {
				fbuf->fb_err = -errno;
				free(fbuf->fb_dat);
				return (0);
			}
			set_vn(f, vn); /*XXX*/
		} else if (vn->ino != FUSE_ROOT_INO)
			ref_vn(vn);
	}

	realname = build_realname(f, vn->ino);
	if (realname == NULL) {
		fbuf->fb_err = -errno;
		free(fbuf->fb_dat);
		return (0);
	}

	fbuf->fb_err = update_attr(f, &fbuf->fb_attr, realname, vn);
	fbuf->fb_ino = vn->ino;
	free(fbuf->fb_dat);
	free(realname);

	return (0);
}

static int
ifuse_ops_read(struct fuse *f, struct fusebuf *fbuf)
{
	struct fuse_file_info ffi;
	struct fuse_vnode *vn;
	char *realname;
	uint64_t offset;
	uint32_t size;
	int ret;

	CHECK_OPT(read);

	memset(&ffi, 0, sizeof(ffi));
	ffi.fh = fbuf->fb_io_fd;
	size = fbuf->fb_io_len;
	offset = fbuf->fb_io_off;

	fbuf->fb_dat = malloc(size);
	if (fbuf->fb_dat == NULL) {
		fbuf->fb_err = -errno;
		return (0);
	}

	vn = tree_get(&f->vnode_tree, fbuf->fb_ino);
	if (vn == NULL) {
		fbuf->fb_err = -errno;
		free(fbuf->fb_dat);
		return (0);
	}

	realname = build_realname(f, vn->ino);
	if (realname == NULL) {
		fbuf->fb_err = -errno;
		free(fbuf->fb_dat);
		return (0);
	}

	ret = f->op.read(realname, (char *)fbuf->fb_dat, size, offset, &ffi);
	free(realname);
	if (ret >= 0)
		fbuf->fb_len = ret;
	else
		fbuf->fb_err = ret;

	if (fbuf->fb_len == 0)
		free(fbuf->fb_dat);

	return (0);
}

static int
ifuse_ops_write(struct fuse *f, struct fusebuf *fbuf)
{
	struct fuse_file_info ffi;
	struct fuse_vnode *vn;
	char *realname;
	uint64_t offset;
	uint32_t size;
	int ret;

	CHECK_OPT(write);

	memset(&ffi, 0, sizeof(ffi));
	ffi.fh = fbuf->fb_io_fd;
	ffi.fh_old = ffi.fh;
	ffi.writepage = fbuf->fb_io_flags & 1;
	size = fbuf->fb_io_len;
	offset = fbuf->fb_io_off;

	vn = tree_get(&f->vnode_tree, fbuf->fb_ino);
	if (vn == NULL) {
		fbuf->fb_err = -errno;
		free(fbuf->fb_dat);
		return (0);
	}

	realname = build_realname(f, vn->ino);
	if (realname == NULL) {
		fbuf->fb_err = -errno;
		free(fbuf->fb_dat);
		return (0);
	}

	ret = f->op.write(realname, (char *)fbuf->fb_dat, size, offset, &ffi);
	free(realname);
	free(fbuf->fb_dat);

	if (ret >= 0)
		fbuf->fb_io_len = ret;
	else
		fbuf->fb_err = ret;

	return (0);
}

static int
ifuse_ops_mkdir(struct fuse *f, struct fusebuf *fbuf)
{
	struct fuse_vnode *vn;
	char *realname;
	uint32_t mode;

	CHECK_OPT(mkdir);

	mode = fbuf->fb_io_mode;
	vn = get_vn_by_name_and_parent(f, fbuf->fb_dat, fbuf->fb_ino);
	if (vn == NULL) {
		fbuf->fb_err = -errno;
		free(fbuf->fb_dat);
		return (0);
	}

	free(fbuf->fb_dat);
	realname = build_realname(f, vn->ino);
	if (realname == NULL) {
		fbuf->fb_err = -errno;
		return (0);
	}

	fbuf->fb_err = f->op.mkdir(realname, mode);

	if (!fbuf->fb_err) {
		fbuf->fb_err = update_attr(f, &fbuf->fb_attr, realname, vn);
		fbuf->fb_io_mode = fbuf->fb_attr.st_mode;
		fbuf->fb_ino = vn->ino;
	}
	free(realname);

	return (0);
}

static int
ifuse_ops_rmdir(struct fuse *f, struct fusebuf *fbuf)
{
	struct fuse_vnode *vn;
	char *realname;

	CHECK_OPT(rmdir);
	vn = get_vn_by_name_and_parent(f, fbuf->fb_dat, fbuf->fb_ino);
	if (vn == NULL) {
		fbuf->fb_err = -errno;
		free(fbuf->fb_dat);
		return (0);
	}

	free(fbuf->fb_dat);
	realname = build_realname(f, vn->ino);
	if (realname == NULL) {
		fbuf->fb_err = -errno;
		return (0);
	}

	fbuf->fb_err = f->op.rmdir(realname);
	free(realname);

	return (0);
}

static int
ifuse_ops_readlink(struct fuse *f, struct fusebuf *fbuf)
{
	struct fuse_vnode *vn;
	char *realname;
	char name[PATH_MAX + 1];
	int len, ret;

	DPRINTF("Opcode: readlink\t");
	DPRINTF("Inode: %llu\t", (unsigned long long)fbuf->fb_ino);

	vn = tree_get(&f->vnode_tree, fbuf->fb_ino);
	if (vn == NULL) {
		fbuf->fb_err = -errno;
		return (0);
	}

	realname = build_realname(f, vn->ino);
	if (realname == NULL) {
		fbuf->fb_err = -errno;
		return (0);
	}

	if (f->op.readlink)
		ret = f->op.readlink(realname, name, sizeof(name));
	else
		ret = -ENOSYS;
	free(realname);

	fbuf->fb_err = ret;
	if (!ret) {
		len = strnlen(name, PATH_MAX);
		fbuf->fb_len = len;
		fbuf->fb_dat = malloc(fbuf->fb_len);
		if (fbuf->fb_dat == NULL) {
			fbuf->fb_err = -errno;
			return (0);
		}
		memcpy(fbuf->fb_dat, name, len);
	} else
		fbuf->fb_len = 0;

	return (0);
}

static int
ifuse_ops_unlink(struct fuse *f, struct fusebuf *fbuf)
{
	struct fuse_vnode *vn;
	char *realname;

	CHECK_OPT(unlink);

	vn = get_vn_by_name_and_parent(f, fbuf->fb_dat, fbuf->fb_ino);
	if (vn == NULL) {
		free(fbuf->fb_dat);
		fbuf->fb_err = -errno;
		return (0);
	}

	free(fbuf->fb_dat);
	realname = build_realname(f, vn->ino);
	if (realname == NULL) {
		fbuf->fb_err = -errno;
		return (0);
	}

	fbuf->fb_err = f->op.unlink(realname);
	free(realname);

	return (0);
}

static int
ifuse_ops_statfs(struct fuse *f, struct fusebuf *fbuf)
{
	struct fuse_vnode *vn;
	char *realname;

	memset(&fbuf->fb_stat, 0, sizeof(fbuf->fb_stat));

	CHECK_OPT(statfs);

	vn = tree_get(&f->vnode_tree, fbuf->fb_ino);
	if (vn == NULL) {
		fbuf->fb_err = -errno;
		return (0);
	}

	realname = build_realname(f, vn->ino);
	if (realname == NULL) {
		fbuf->fb_err = -errno;
		return (0);
	}

	fbuf->fb_err = f->op.statfs(realname, &fbuf->fb_stat);
	free(realname);

	return (0);
}

static int
ifuse_ops_link(struct fuse *f, struct fusebuf *fbuf)
{
	struct fuse_vnode *vn;
	char *realname;
	char *realname_ln;
	ino_t oldnodeid;

	CHECK_OPT(link);
	oldnodeid = fbuf->fb_io_ino;
	vn = get_vn_by_name_and_parent(f, fbuf->fb_dat, fbuf->fb_ino);
	if (vn == NULL) {
		fbuf->fb_err = -errno;
		free(fbuf->fb_dat);
		return (0);
	}

	free(fbuf->fb_dat);
	realname = build_realname(f, oldnodeid);
	if (realname == NULL) {
		fbuf->fb_err = -errno;
		return (0);
	}

	realname_ln = build_realname(f, vn->ino);
	if (realname_ln == NULL) {
		fbuf->fb_err = -errno;
		free(realname);
		return (0);
	}

	fbuf->fb_err = f->op.link(realname, realname_ln);
	free(realname);
	free(realname_ln);

	return (0);
}

static int
ifuse_ops_setattr(struct fuse *f, struct fusebuf *fbuf)
{
	struct fuse_vnode *vn;
	struct timespec ts[2];
	struct utimbuf tbuf;
	struct fb_io *io;
	char *realname;
	uid_t uid;
	gid_t gid;

	DPRINTF("Opcode: setattr\t");
	DPRINTF("Inode: %llu\t", (unsigned long long)fbuf->fb_ino);

	vn = tree_get(&f->vnode_tree, fbuf->fb_ino);
	if (vn == NULL) {
		fbuf->fb_err = -errno;
		free(fbuf->fb_dat);
		return (0);
	}

	realname = build_realname(f, vn->ino);
	if (realname == NULL) {
		fbuf->fb_err = -errno;
		free(fbuf->fb_dat);
		return (0);
	}
	io = fbtod(fbuf, struct fb_io *);

	if (io->fi_flags & FUSE_FATTR_MODE) {
		if (f->op.chmod)
			fbuf->fb_err = f->op.chmod(realname,
			    fbuf->fb_attr.st_mode);
		else
			fbuf->fb_err = -ENOSYS;
	}

	if (!fbuf->fb_err && (io->fi_flags & FUSE_FATTR_UID ||
	    io->fi_flags & FUSE_FATTR_GID) ) {
		uid = (io->fi_flags & FUSE_FATTR_UID) ?
		    fbuf->fb_attr.st_uid : (uid_t)-1;
		gid = (io->fi_flags & FUSE_FATTR_GID) ?
		    fbuf->fb_attr.st_gid : (gid_t)-1;
		if (f->op.chown)
			fbuf->fb_err = f->op.chown(realname, uid, gid);
		else
			fbuf->fb_err = -ENOSYS;
	}

	if (!fbuf->fb_err && ( io->fi_flags & FUSE_FATTR_MTIME ||
		io->fi_flags & FUSE_FATTR_ATIME)) {

		if (f->op.utimens) {
			ts[0] = fbuf->fb_attr.st_atim;
			ts[1] = fbuf->fb_attr.st_mtim;
			fbuf->fb_err = f->op.utimens(realname, ts);
		} else if (f->op.utime) {
			tbuf.actime = fbuf->fb_attr.st_atim.tv_sec;
			tbuf.modtime = fbuf->fb_attr.st_mtim.tv_sec;
			fbuf->fb_err = f->op.utime(realname, &tbuf);
		} else
			fbuf->fb_err = -ENOSYS;
	}

	if (!fbuf->fb_err && (io->fi_flags & FUSE_FATTR_SIZE)) {
		if (f->op.truncate)
			fbuf->fb_err = f->op.truncate(realname,
			    fbuf->fb_attr.st_size);
		else
			fbuf->fb_err = -ENOSYS;
	}

	memset(&fbuf->fb_attr, 0, sizeof(struct stat));

	if (!fbuf->fb_err)
		fbuf->fb_err = update_attr(f, &fbuf->fb_attr, realname, vn);
	free(realname);
	free(fbuf->fb_dat);

	return (0);
}

static int
ifuse_ops_symlink(unused struct fuse *f, struct fusebuf *fbuf)
{
	struct fuse_vnode *vn;
	char *realname;
	int len;

	CHECK_OPT(symlink);

	vn = get_vn_by_name_and_parent(f, fbuf->fb_dat, fbuf->fb_ino);
	if (vn == NULL) {
		fbuf->fb_err = -errno;
		free(fbuf->fb_dat);
		return (0);
	}

	len = strlen((char *)fbuf->fb_dat);

	realname = build_realname(f, vn->ino);
	if (realname == NULL) {
		fbuf->fb_err = -errno;
		free(fbuf->fb_dat);
		return (0);
	}

	/* fuse invert the symlink params */
	fbuf->fb_err = f->op.symlink((const char *)&fbuf->fb_dat[len + 1],
	    realname);
	fbuf->fb_ino = vn->ino;
	free(fbuf->fb_dat);
	free(realname);

	return (0);
}

static int
ifuse_ops_rename(struct fuse *f, struct fusebuf *fbuf)
{
	struct fuse_vnode *vnt;
	struct fuse_vnode *vnf;
	char *realnamef;
	char *realnamet;
	int len;

	CHECK_OPT(rename);

	len = strlen((char *)fbuf->fb_dat);
	vnf = get_vn_by_name_and_parent(f, fbuf->fb_dat, fbuf->fb_ino);
	if (vnf == NULL) {
		fbuf->fb_err = -errno;
		free(fbuf->fb_dat);
		return (0);
	}

	vnt = get_vn_by_name_and_parent(f, &fbuf->fb_dat[len + 1],
	    fbuf->fb_io_ino);
	if (vnt == NULL) {
		fbuf->fb_err = -errno;
		free(fbuf->fb_dat);
		return (0);
	}

	free(fbuf->fb_dat);

	realnamef = build_realname(f, vnf->ino);
	if (realnamef == NULL) {
		fbuf->fb_err = -errno;
		return (0);
	}

	realnamet = build_realname(f, vnt->ino);
	if (realnamet == NULL) {
		fbuf->fb_err = -errno;
		free(realnamef);
		return (0);
	}

	fbuf->fb_err = f->op.rename(realnamef, realnamet);
	free(realnamef);
	free(realnamet);

	return (0);
}

static int
ifuse_ops_destroy(struct fuse *f)
{
	struct fuse_context *ctx;

	DPRINTF("Opcode: destroy\n");

	if (f->op.destroy) {
		ctx = fuse_get_context();

		f->op.destroy((ctx)?ctx->private_data:NULL);
	}

	f->fc->dead = 1;

	return (0);
}

static int
ifuse_ops_reclaim(struct fuse *f, struct fusebuf *fbuf)
{
	struct fuse_vnode *vn;

	DPRINTF("Opcode: reclaim\t");
	DPRINTF("Inode: %llu\t", (unsigned long long)fbuf->fb_ino);

	vn = tree_get(&f->vnode_tree, fbuf->fb_ino);
	if (vn != NULL)
		unref_vn(f, vn);

	return (0);
}

static int
ifuse_ops_mknod(struct fuse *f, struct fusebuf *fbuf)
{
	struct fuse_vnode *vn;
	char *realname;
	uint32_t mode;
	dev_t dev;

	CHECK_OPT(mknod);

	mode = fbuf->fb_io_mode;
	dev = fbuf->fb_io_rdev;
	vn = get_vn_by_name_and_parent(f, fbuf->fb_dat, fbuf->fb_ino);
	if (vn == NULL) {
		fbuf->fb_err = -errno;
		free(fbuf->fb_dat);
		return (0);
	}

	free(fbuf->fb_dat);
	realname = build_realname(f, vn->ino);
	if (realname == NULL) {
		fbuf->fb_err = -errno;
		return (0);
	}

	fbuf->fb_err = f->op.mknod(realname, mode, dev);

	if (!fbuf->fb_err) {
		fbuf->fb_err = update_attr(f, &fbuf->fb_attr, realname, vn);
		fbuf->fb_io_mode = fbuf->fb_attr.st_mode;
		fbuf->fb_ino = vn->ino;
	}
	free(realname);

	return (0);
}

int
ifuse_exec_opcode(struct fuse *f, struct fusebuf *fbuf)
{
	int ret = 0;

	fbuf->fb_len = 0;
	fbuf->fb_err = 0;

	switch (fbuf->fb_type) {
	case FBT_LOOKUP:
		ret = ifuse_ops_lookup(f, fbuf);
		break;
	case FBT_GETATTR:
		ret = ifuse_ops_getattr(f, fbuf);
		break;
	case FBT_SETATTR:
		ret = ifuse_ops_setattr(f, fbuf);
		break;
	case FBT_READLINK:
		ret = ifuse_ops_readlink(f, fbuf);
		break;
	case FBT_MKDIR:
		ret = ifuse_ops_mkdir(f, fbuf);
		break;
	case FBT_UNLINK:
		ret = ifuse_ops_unlink(f, fbuf);
		break;
	case FBT_RMDIR:
		ret = ifuse_ops_rmdir(f, fbuf);
		break;
	case FBT_LINK:
		ret = ifuse_ops_link(f, fbuf);
		break;
	case FBT_OPEN:
		ret = ifuse_ops_open(f, fbuf);
		break;
	case FBT_READ:
		ret = ifuse_ops_read(f, fbuf);
		break;
	case FBT_WRITE:
		ret = ifuse_ops_write(f, fbuf);
		break;
	case FBT_STATFS:
		ret = ifuse_ops_statfs(f, fbuf);
		break;
	case FBT_RELEASE:
		ret = ifuse_ops_release(f, fbuf);
		break;
	case FBT_FSYNC:
		ret = ifuse_ops_fsync(f, fbuf);
		break;
	case FBT_FLUSH:
		ret = ifuse_ops_flush(f, fbuf);
		break;
	case FBT_INIT:
		ret = ifuse_ops_init(f);
		break;
	case FBT_OPENDIR:
		ret = ifuse_ops_opendir(f, fbuf);
		break;
	case FBT_READDIR:
		ret = ifuse_ops_readdir(f, fbuf);
		break;
	case FBT_RELEASEDIR:
		ret = ifuse_ops_releasedir(f, fbuf);
		break;
	case FBT_ACCESS:
		ret = ifuse_ops_access(f, fbuf);
		break;
	case FBT_SYMLINK:
		ret = ifuse_ops_symlink(f, fbuf);
		break;
	case FBT_RENAME:
		ret = ifuse_ops_rename(f, fbuf);
		break;
	case FBT_DESTROY:
		ret = ifuse_ops_destroy(f);
		break;
	case FBT_RECLAIM:
		ret = ifuse_ops_reclaim(f, fbuf);
		break;
	case FBT_MKNOD:
		ret = ifuse_ops_mknod(f, fbuf);
		break;
	default:
		DPRINTF("Opcode: %i not supported\t", fbuf->fb_type);
		DPRINTF("Inode: %llu\t", (unsigned long long)fbuf->fb_ino);

		fbuf->fb_err = -ENOSYS;
		fbuf->fb_len = 0;
	}
	DPRINTF("\n");

	/* fuse api use negative errno */
	fbuf->fb_err = -fbuf->fb_err;
	return (ret);
}