// SPDX-License-Identifier: BSD-3-Clause
/* Copyright 2015-2019, Intel Corporation */

/*
 * pmemobjfs.c -- simple filesystem based on libpmemobj tx API
 */
#include <stdio.h>
#include <fuse.h>
#include <stdlib.h>
#include <libpmemobj.h>
#include <libpmem.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <libgen.h>
#include <err.h>
#include <sys/ioctl.h>
#include <linux/kdev_t.h>

#include <map.h>
#include <map_ctree.h>

#ifndef PMEMOBJFS_TRACK_BLOCKS
#define PMEMOBJFS_TRACK_BLOCKS	1
#endif

#if DEBUG
static FILE *log_fh;
static uint64_t log_cnt;
#define log(fmt, args...) do {\
if (log_fh) {\
	fprintf(log_fh, "[%016lx] %s: " fmt "\n", log_cnt, __func__, ## args);\
	log_cnt++;\
	fflush(log_fh);\
}\
} while (0)
#else
#define log(fmt, args...) do {} while (0)
#endif

#define PMEMOBJFS_MOUNT "pmemobjfs"
#define PMEMOBJFS_MKFS	"mkfs.pmemobjfs"
#define PMEMOBJFS_TX_BEGIN "pmemobjfs.tx_begin"
#define PMEMOBJFS_TX_COMMIT "pmemobjfs.tx_commit"
#define PMEMOBJFS_TX_ABORT  "pmemobjfs.tx_abort"

#define PMEMOBJFS_TMP_TEMPLATE	"/.tx_XXXXXX"

#define PMEMOBJFS_CTL		'I'
#define PMEMOBJFS_CTL_TX_BEGIN	_IO(PMEMOBJFS_CTL, 1)
#define PMEMOBJFS_CTL_TX_COMMIT	_IO(PMEMOBJFS_CTL, 2)
#define PMEMOBJFS_CTL_TX_ABORT	_IO(PMEMOBJFS_CTL, 3)

/*
 * struct pmemobjfs -- volatile state of pmemobjfs
 */
struct pmemobjfs {
	PMEMobjpool *pop;
	struct map_ctx *mapc;
	uint64_t pool_uuid_lo;
	int ioctl_cmd;
	uint64_t ioctl_off;
	uint64_t block_size;
	uint64_t max_name;
};

#define PMEMOBJFS (struct pmemobjfs *)fuse_get_context()->private_data

#define PDLL_ENTRY(type)\
struct {\
	TOID(type) next;\
	TOID(type) prev;\
}

#define PDLL_HEAD(type)\
struct {\
	TOID(type) first;\
	TOID(type) last;\
}

#define PDLL_HEAD_INIT(head) do {\
	((head)).first = ((typeof((head).first))OID_NULL);\
	((head)).last = ((typeof((head).first))OID_NULL);\
} while (0)

#define PDLL_FOREACH(entry, head, field)\
for ((entry) = ((head)).first; !TOID_IS_NULL(entry);\
		(entry) = D_RO(entry)->field.next)

#define PDLL_FOREACH_SAFE(entry, next, head, field)\
for ((entry) = ((head)).first; !TOID_IS_NULL(entry) &&\
		((next) = D_RO(entry)->field.next, 1);\
		(entry) = (next))

#define PDLL_INSERT_HEAD(head, entry, field) do {\
	pmemobj_tx_add_range_direct(&(head).first, sizeof((head).first));\
	TX_ADD_FIELD(entry, field);\
	D_RW(entry)->field.next = (head).first;\
	D_RW(entry)->field.prev =\
		(typeof(D_RW(entry)->field.prev))OID_NULL;\
	(head).first = entry;\
	if (TOID_IS_NULL((head).last)) {\
		pmemobj_tx_add_range_direct(&(head).last, sizeof((head).last));\
		(head).last = entry;\
	}\
	typeof(entry) next = D_RO(entry)->field.next;\
	if (!TOID_IS_NULL(next)) {\
		pmemobj_tx_add_range_direct(&D_RW(next)->field.prev,\
				sizeof(D_RW(next)->field.prev));\
		D_RW(next)->field.prev = entry;\
	}\
} while (0)

#define PDLL_REMOVE(head, entry, field) do {\
	if (TOID_EQUALS((head).first, entry) &&\
		TOID_EQUALS((head).last, entry)) {\
		pmemobj_tx_add_range_direct(&(head).first,\
				sizeof((head).first));\
		pmemobj_tx_add_range_direct(&(head).last, sizeof((head).last));\
		(head).first = (typeof(D_RW(entry)->field.prev))OID_NULL;\
		(head).last = (typeof(D_RW(entry)->field.prev))OID_NULL;\
	} else if (TOID_EQUALS((head).first, entry)) {\
		typeof(entry) next = D_RW(entry)->field.next;\
		pmemobj_tx_add_range_direct(&D_RW(next)->field.prev,\
				sizeof(D_RW(next)->field.prev));\
		pmemobj_tx_add_range_direct(&(head).first,\
				sizeof((head).first));\
		(head).first = D_RO(entry)->field.next;\
		D_RW(next)->field.prev.oid = OID_NULL;\
	} else if (TOID_EQUALS((head).last, entry)) {\
		typeof(entry) prev = D_RW(entry)->field.prev;\
		pmemobj_tx_add_range_direct(&D_RW(prev)->field.next,\
				sizeof(D_RW(prev)->field.next));\
		pmemobj_tx_add_range_direct(&(head).last, sizeof((head).last));\
		(head).last = D_RO(entry)->field.prev;\
		D_RW(prev)->field.next.oid = OID_NULL;\
	} else {\
		typeof(entry) prev = D_RW(entry)->field.prev;\
		typeof(entry) next = D_RW(entry)->field.next;\
		pmemobj_tx_add_range_direct(&D_RW(prev)->field.next,\
				sizeof(D_RW(prev)->field.next));\
		pmemobj_tx_add_range_direct(&D_RW(next)->field.prev,\
				sizeof(D_RW(next)->field.prev));\
		D_RW(prev)->field.next = D_RO(entry)->field.next;\
		D_RW(next)->field.prev = D_RO(entry)->field.prev;\
	}\
} while (0)

typedef uint8_t objfs_block_t;

/*
 * pmemobjfs persistent layout
 */
POBJ_LAYOUT_BEGIN(pmemobjfs);
POBJ_LAYOUT_ROOT(pmemobjfs, struct objfs_super);
POBJ_LAYOUT_TOID(pmemobjfs, struct objfs_inode);
POBJ_LAYOUT_TOID(pmemobjfs, struct objfs_dir_entry);
POBJ_LAYOUT_TOID(pmemobjfs, objfs_block_t);
POBJ_LAYOUT_TOID(pmemobjfs, char);
POBJ_LAYOUT_END(pmemobjfs);

#define PMEMOBJFS_MIN_BLOCK_SIZE ((size_t)(512 - 64))

/*
 * struct objfs_super -- pmemobjfs super (root) object
 */
struct objfs_super {
	TOID(struct objfs_inode) root_inode;	/* root dir inode */
	TOID(struct map) opened;		/* map of opened files / dirs */
	uint64_t block_size;			/* size of data block */
};

/*
 * struct objfs_dir_entry -- pmemobjfs directory entry structure
 */
struct objfs_dir_entry {
	PDLL_ENTRY(struct objfs_dir_entry) pdll; /* list entry */
	TOID(struct objfs_inode) inode;	/* pointer to inode */
	char name[];			/* name */
};

/*
 * struct objfs_dir -- pmemobjfs directory structure
 */
struct objfs_dir {
	PDLL_HEAD(struct objfs_dir_entry) entries; /* directory entries */
};

/*
 * key == 0 for ctree_map is not allowed
 */
#define GET_KEY(off) ((off) + 1)

/*
 * struct objfs_file -- pmemobjfs file structure
 */
struct objfs_file {
	TOID(struct map) blocks;	/* blocks map */
};

/*
 * struct objfs_symlink -- symbolic link
 */
struct objfs_symlink {
	uint64_t len;	/* length of symbolic link */
	TOID(char) name; /* symbolic link data */
};

/*
 * struct objfs_inode -- pmemobjfs inode structure
 */
struct objfs_inode {
	uint64_t size;	/* size of file */
	uint64_t flags;	/* file flags */
	uint64_t dev;	/* device info */
	uint32_t ctime;	/* time of last status change */
	uint32_t mtime; /* time of last modification */
	uint32_t atime; /* time of last access */
	uint32_t uid;	/* user ID */
	uint32_t gid;	/* group ID */
	uint32_t ref;	/* reference counter */
	struct objfs_file file; /* file specific data */
	struct objfs_dir dir;	/* directory specific data */
	struct objfs_symlink symlink; /* symlink specific data */
};

/*
 * pmemobjfs_ioctl -- do the ioctl command
 */
static void
pmemobjfs_ioctl(struct pmemobjfs *objfs)
{
	switch (objfs->ioctl_cmd) {
	case PMEMOBJFS_CTL_TX_BEGIN:
		(void) pmemobj_tx_begin(objfs->pop, NULL, TX_PARAM_NONE);
		break;
	case PMEMOBJFS_CTL_TX_ABORT:
		pmemobj_tx_abort(-1);
		(void) pmemobj_tx_end();
		break;
	case PMEMOBJFS_CTL_TX_COMMIT:
		pmemobj_tx_commit();
		(void) pmemobj_tx_end();
		break;
	default:
		break;
	}

	/* clear deferred inode offset and command */
	objfs->ioctl_cmd = 0;
	objfs->ioctl_off = 0;
}

/*
 * pmemobjfs_inode_alloc -- allocate inode structure
 */
static TOID(struct objfs_inode)
pmemobjfs_inode_alloc(struct pmemobjfs *objfs, uint64_t flags,
		uint32_t uid, uint32_t gid, uint64_t dev)
{
	TOID(struct objfs_inode) inode = TOID_NULL(struct objfs_inode);
	TX_BEGIN(objfs->pop) {
		inode = TX_ZNEW(struct objfs_inode);
		time_t cur_time = time(NULL);

		D_RW(inode)->flags = flags;
		D_RW(inode)->dev = dev;
		D_RW(inode)->ctime = cur_time;
		D_RW(inode)->mtime = cur_time;
		D_RW(inode)->atime = cur_time;
		D_RW(inode)->uid = uid;
		D_RW(inode)->gid = gid;
		D_RW(inode)->ref = 0;
	} TX_ONABORT {
		inode = TOID_NULL(struct objfs_inode);
	} TX_END

	return inode;
}

/*
 * pmemobjfs_inode_init_dir -- initialize directory in inode
 */
static void
pmemobjfs_inode_init_dir(struct pmemobjfs *objfs,
		TOID(struct objfs_inode) inode)
{
	TX_BEGIN(objfs->pop) {
		PDLL_HEAD_INIT(D_RW(inode)->dir.entries);
	} TX_END;
}

/*
 * pmemobjfs_inode_destroy_dir -- destroy directory from inode
 */
static void
pmemobjfs_inode_destroy_dir(struct pmemobjfs *objfs,
		TOID(struct objfs_inode) inode)
{
	/* nothing to do */
}

/*
 * pmemobjfs_file_alloc -- allocate file structure
 */
static void
pmemobjfs_inode_init_file(struct pmemobjfs *objfs,
		TOID(struct objfs_inode) inode)
{
	TX_BEGIN(objfs->pop) {
		map_create(objfs->mapc, &D_RW(inode)->file.blocks, NULL);
	} TX_END
}

/*
 * pmemobjfs_file_free -- free file structure
 */
static void
pmemobjfs_inode_destroy_file(struct pmemobjfs *objfs,
		TOID(struct objfs_inode) inode)
{
	TX_BEGIN(objfs->pop) {
		map_destroy(objfs->mapc, &D_RW(inode)->file.blocks);
	} TX_END
}

/*
 * pmemobjfs_inode_hold -- increase reference counter of inode
 */
static void
pmemobjfs_inode_hold(struct pmemobjfs *objfs, TOID(struct objfs_inode) inode)
{
	if (TOID_IS_NULL(inode))
		return;

	TX_BEGIN(objfs->pop) {
		/* update number of references */
		TX_ADD_FIELD(inode, ref);
		D_RW(inode)->ref++;

		/* update status change time */
		TX_ADD_FIELD(inode, ctime);
		D_RW(inode)->ctime = time(NULL);
	} TX_END
}

/*
 * pmemobjfs_dir_entry_alloc -- allocate directory entry structure
 */
static TOID(struct objfs_dir_entry)
pmemobjfs_dir_entry_alloc(struct pmemobjfs *objfs, const char *name,
		TOID(struct objfs_inode) inode)
{
	TOID(struct objfs_dir_entry) entry = TOID_NULL(struct objfs_dir_entry);
	TX_BEGIN(objfs->pop) {
		size_t len = strlen(name) + 1;
		entry = TX_ALLOC(struct objfs_dir_entry, objfs->block_size);

		memcpy(D_RW(entry)->name, name, len);

		D_RW(entry)->inode = inode;
		pmemobjfs_inode_hold(objfs, inode);
	} TX_ONABORT {
		entry = TOID_NULL(struct objfs_dir_entry);
	} TX_END

	return entry;
}

/*
 * pmemobjfs_dir_entry_free -- free dir entry structure
 */
static void
pmemobjfs_dir_entry_free(struct pmemobjfs *objfs,
		TOID(struct objfs_dir_entry) entry)
{
	TX_BEGIN(objfs->pop) {
		TX_FREE(entry);
	} TX_END
}

/*
 * pmemobjfs_inode_init_symlink -- initialize symbolic link
 */
static void
pmemobjfs_inode_init_symlink(struct pmemobjfs *objfs,
		TOID(struct objfs_inode) inode,
		const char *name)
{
	TX_BEGIN(objfs->pop) {
		size_t len = strlen(name) + 1;
		D_RW(inode)->symlink.len = len;
		TOID_ASSIGN(D_RW(inode)->symlink.name,
			TX_STRDUP(name, TOID_TYPE_NUM(char)));
	} TX_END
}

/*
 * pmemobjfs_inode_destroy_symlink -- destroy symbolic link
 */
static void
pmemobjfs_inode_destroy_symlink(struct pmemobjfs *objfs,
		TOID(struct objfs_inode) inode)
{
	TX_BEGIN(objfs->pop) {
		TX_FREE(D_RO(inode)->symlink.name);
	} TX_END
}

/*
 * pmemobjfs_symlink_read -- read symlink to buffer
 */
static int
pmemobjfs_symlink_read(TOID(struct objfs_inode) inode,
		char *buff, size_t size)
{
	/* check inode type */
	switch (D_RO(inode)->flags & S_IFMT) {
	case S_IFLNK:
		break;
	case S_IFDIR:
		return -EISDIR;
	case S_IFREG:
	default:
		return -EINVAL;
	}

	char *name = D_RW(D_RW(inode)->symlink.name);
	strncpy(buff, name, size);

	return 0;
}

/*
 * pmemobjfs_symlink_size -- get size of symlink
 */
static size_t
pmemobjfs_symlink_size(TOID(struct objfs_inode) inode)
{
	return D_RO(inode)->symlink.len - 1;
}

/*
 * pmemobjfs_inode_free -- free inode structure
 */
static void
pmemobjfs_inode_free(struct pmemobjfs *objfs, TOID(struct objfs_inode) inode)
{
	TX_BEGIN(objfs->pop) {
		/* release data specific for inode type */
		if (S_ISREG(D_RO(inode)->flags)) {
			pmemobjfs_inode_destroy_file(objfs, inode);
		} else if (S_ISDIR(D_RO(inode)->flags)) {
			pmemobjfs_inode_destroy_dir(objfs, inode);
		} else if (S_ISLNK(D_RO(inode)->flags)) {
			pmemobjfs_inode_destroy_symlink(objfs, inode);
		}

		TX_FREE(inode);
	} TX_END
}

/*
 * pmemobjfs_inode_put -- decrease reference counter of inode and free
 */
static void
pmemobjfs_inode_put(struct pmemobjfs *objfs, TOID(struct objfs_inode) inode)
{
	if (TOID_IS_NULL(inode))
		return;

	TX_BEGIN(objfs->pop) {
		/* update number of references */
		TX_ADD_FIELD(inode, ref);
		D_RW(inode)->ref--;

		/* update status change time */
		TX_ADD_FIELD(inode, ctime);
		D_RW(inode)->ctime = time(NULL);

		if (!D_RO(inode)->ref)
			pmemobjfs_inode_free(objfs, inode);
	} TX_END
}

/*
 * pmemobjfs_dir_get_inode -- get inode from dir of given name
 */
static TOID(struct objfs_inode)
pmemobjfs_dir_get_inode(TOID(struct objfs_inode) inode, const char *name)
{
	log("%s", name);
	TOID(struct objfs_dir_entry) entry;
	PDLL_FOREACH(entry, D_RW(inode)->dir.entries, pdll) {
		if (strcmp(name, D_RO(entry)->name) == 0)
			return D_RO(entry)->inode;
	}

	return TOID_NULL(struct objfs_inode);
}

/*
 * pmemobjfs_get_dir_entry -- get dir entry from dir of given name
 */
static TOID(struct objfs_dir_entry)
pmemobjfs_get_dir_entry(TOID(struct objfs_inode) inode, const char *name)
{
	log("%s", name);
	TOID(struct objfs_dir_entry) entry;
	PDLL_FOREACH(entry, D_RW(inode)->dir.entries, pdll) {
		if (strcmp(name, D_RO(entry)->name) == 0)
			return entry;
	}

	return TOID_NULL(struct objfs_dir_entry);
}

/*
 * pmemobjfs_inode_lookup_parent -- lookup for parent inode and child name
 */
static int
pmemobjfs_inode_lookup_parent(struct pmemobjfs *objfs,
		const char *path, TOID(struct objfs_inode) *inodep,
		const char **child)
{
	log("%s", path);
	TOID(struct objfs_super) super =
		POBJ_ROOT(objfs->pop, struct objfs_super);
	TOID(struct objfs_inode) cur = D_RO(super)->root_inode;
	TOID(struct objfs_inode) par = TOID_NULL(struct objfs_inode);

	if (path[0] == '/')
		path++;

	int ret = 0;
	char *p = strdup(path);
	char *name = p;
	char *ch = NULL;
	while (name && *name != '\0' && !TOID_IS_NULL(cur)) {
		char *slash = strchr(name, '/');
		if (slash) {
			*slash = '\0';
			slash++;
		}

		if (!S_ISDIR(D_RO(cur)->flags)) {
			ret = -ENOTDIR;
			goto out;
		}

		if (strlen(name) > objfs->max_name) {
			ret = -ENAMETOOLONG;
			goto out;
		}

		par = cur;
		cur = pmemobjfs_dir_get_inode(cur, name);
		ch = name;
		name = slash;
	}

	if (child) {
		if (strchr(ch, '/')) {
			ret = -ENOENT;
			goto out;
		}

		if (TOID_IS_NULL(par)) {
			ret = -ENOENT;
			goto out;
		}

		cur = par;
		size_t parent_len = ch - p;
		*child = path + parent_len;
	} else {
		if (TOID_IS_NULL(cur))
			ret = -ENOENT;
	}

	if (inodep)
		*inodep = cur;
out:
	free(p);

	return ret;
}

/*
 * pmemobjfs_inode_lookup -- get inode for given path
 */
static int
pmemobjfs_inode_lookup(struct pmemobjfs *objfs, const char *path,
		TOID(struct objfs_inode) *inodep)
{
	log("%s", path);
	return pmemobjfs_inode_lookup_parent(objfs, path, inodep, NULL);
}

/*
 * pmemobjfs_file_get_block -- get block at given offset
 */
static TOID(objfs_block_t)
pmemobjfs_file_get_block(struct pmemobjfs *objfs,
		TOID(struct objfs_inode) inode,
		uint64_t offset)
{
	TOID(objfs_block_t) block;
	PMEMoid block_oid =
		map_get(objfs->mapc, D_RO(inode)->file.blocks, GET_KEY(offset));
	TOID_ASSIGN(block, block_oid);
	return block;
}

/*
 * pmemobjfs_file_get_block_for_write -- get or allocate block at given offset
 */
static TOID(objfs_block_t)
pmemobjfs_file_get_block_for_write(struct pmemobjfs *objfs,
		TOID(struct objfs_inode) inode, uint64_t offset)
{
	TOID(objfs_block_t) block =
		pmemobjfs_file_get_block(objfs, inode, offset);
	if (TOID_IS_NULL(block)) {
		TX_BEGIN(objfs->pop) {
			block = TX_ALLOC(objfs_block_t,
					objfs->block_size);
			map_insert(objfs->mapc, D_RW(inode)->file.blocks,
					GET_KEY(offset), block.oid);
		} TX_ONABORT {
			block = TOID_NULL(objfs_block_t);
		} TX_END
	} else {
#if PMEMOBJFS_TRACK_BLOCKS
		TX_ADD(block);
#endif
	}

	return block;
}

/*
 * pmemobjfs_truncate -- truncate file
 */
static int
pmemobjfs_truncate(struct pmemobjfs *objfs,
		TOID(struct objfs_inode) inode, off_t off)
{
	/* check inode type */
	switch (D_RO(inode)->flags & S_IFMT) {
	case S_IFREG:
		break;
	case S_IFDIR:
		return -EISDIR;
	default:
		return -EINVAL;
	}

	int ret = 0;

	TX_BEGIN(objfs->pop) {
		off_t old_off = D_RO(inode)->size;
		if (old_off > off) {
			/* release blocks */
			uint64_t old_boff = (old_off - 1) / objfs->block_size;
			uint64_t boff = (off + 1) / objfs->block_size;

			for (uint64_t o = boff; o <= old_boff; o++) {
				map_remove_free(objfs->mapc,
					D_RW(inode)->file.blocks, GET_KEY(o));
			}
		}

		time_t t = time(NULL);
		/* update modification time */
		TX_ADD_FIELD(inode, mtime);
		D_RW(inode)->mtime = t;

		/* update status change time */
		TX_ADD_FIELD(inode, ctime);
		D_RW(inode)->ctime = t;

		/* update size */
		TX_ADD_FIELD(inode, size);
		D_RW(inode)->size = off;
	} TX_ONABORT {
		ret = -ECANCELED;
	} TX_END

	return ret;
}

/*
 * pmemobjfs_read -- read from file
 */
static int
pmemobjfs_read(struct pmemobjfs *objfs, TOID(struct objfs_inode) inode,
		char *buff, size_t size, off_t offset)
{
	/* check inode type */
	switch (D_RO(inode)->flags & S_IFMT) {
	case S_IFREG:
		break;
	case S_IFDIR:
		return -EISDIR;
	default:
		return -EINVAL;
	}

	uint64_t fsize = D_RO(inode)->size;

	size_t sz = size;
	size_t off = offset;
	while (sz > 0) {
		if (off >= fsize)
			break;

		uint64_t block_id = off / objfs->block_size;
		uint64_t block_off = off % objfs->block_size;
		uint64_t block_size = sz < objfs->block_size ?
			sz : objfs->block_size;

		TOID(objfs_block_t) block =
			pmemobjfs_file_get_block(objfs, inode, block_id);

		if (block_off + block_size > objfs->block_size)
			block_size = objfs->block_size - block_off;

		if (TOID_IS_NULL(block)) {
			memset(buff, 0, block_size);
		} else {
			memcpy(buff, &D_RW(block)[block_off], block_size);
		}

		buff += block_size;
		off += block_size;
		sz -= block_size;
	}

	return size - sz;
}

/*
 * pmemobjfs_write -- write to file
 */
static int
pmemobjfs_write(struct pmemobjfs *objfs, TOID(struct objfs_inode) inode,
		const char *buff, size_t size, off_t offset)
{
	/* check inode type */
	switch (D_RO(inode)->flags & S_IFMT) {
	case S_IFREG:
		break;
	case S_IFDIR:
		return -EISDIR;
	default:
		return -EINVAL;
	}

	int ret = 0;

	TX_BEGIN(objfs->pop) {
		size_t sz = size;
		off_t off = offset;
		while (sz > 0) {
			uint64_t block_id = off / objfs->block_size;
			uint64_t block_off = off % objfs->block_size;
			uint64_t block_size = sz < objfs->block_size ?
				sz : objfs->block_size;

			TOID(objfs_block_t) block =
				pmemobjfs_file_get_block_for_write(objfs,
						inode, block_id);

			if (TOID_IS_NULL(block))
				return -ENOSPC;

			if (block_off + block_size > objfs->block_size)
				block_size = objfs->block_size - block_off;

			memcpy(&D_RW(block)[block_off], buff, block_size);

			buff += block_size;
			off += block_size;
			sz -= block_size;
		}

		time_t t = time(NULL);

		if (offset + size > D_RO(inode)->size) {
			/* update size */
			TX_ADD_FIELD(inode, size);
			D_RW(inode)->size = offset + size;

			/* update status change time */
			TX_ADD_FIELD(inode, ctime);
			D_RW(inode)->ctime = t;
		}

		/* update modification time */
		TX_ADD_FIELD(inode, mtime);
		D_RW(inode)->mtime = t;
	} TX_ONCOMMIT {
		ret = size;
	} TX_ONABORT {
		ret = -ECANCELED;
	} TX_END

	return ret;
}

/*
 * pmemobjfs_fallocate -- allocate blocks for file
 */
static int
pmemobjfs_fallocate(struct pmemobjfs *objfs,
		TOID(struct objfs_inode) inode, off_t offset, off_t size)
{
	/* check inode type */
	switch (D_RO(inode)->flags & S_IFMT) {
	case S_IFREG:
		break;
	case S_IFDIR:
		return -EISDIR;
	default:
		return -EINVAL;
	}

	int ret = 0;

	TX_BEGIN(objfs->pop) {
		/* allocate blocks from requested range */
		uint64_t b_off = offset / objfs->block_size;
		uint64_t e_off = (offset + size) / objfs->block_size;
		for (uint64_t off = b_off; off <= e_off; off++)
			pmemobjfs_file_get_block_for_write(objfs, inode, off);

		time_t t = time(NULL);
		/* update modification time */
		TX_ADD_FIELD(inode, mtime);
		D_RW(inode)->mtime = t;

		/* update status change time */
		TX_ADD_FIELD(inode, ctime);
		D_RW(inode)->ctime = t;

		/* update inode size */
		D_RW(inode)->size = offset + size;
		TX_ADD_FIELD(inode, size);
	} TX_ONABORT {
		ret = -ECANCELED;
	} TX_END

	return ret;
}

/*
 * pmemobjfs_remove_dir_entry -- remove dir entry from directory
 */
static void
pmemobjfs_remove_dir_entry(struct pmemobjfs *objfs,
		TOID(struct objfs_inode) inode,
		TOID(struct objfs_dir_entry) entry)
{
	TX_BEGIN(objfs->pop) {
		pmemobjfs_inode_put(objfs, D_RO(entry)->inode);

		PDLL_REMOVE(D_RW(inode)->dir.entries, entry, pdll);

		pmemobjfs_dir_entry_free(objfs, entry);
	} TX_END
}

/*
 * pmemobjfs_remove_dir_entry_name -- remove dir entry of given name
 */
static void
pmemobjfs_remove_dir_entry_name(struct pmemobjfs *objfs,
		TOID(struct objfs_inode) inode, const char *name)
{
	TX_BEGIN(objfs->pop) {
		TOID(struct objfs_dir_entry) entry =
			pmemobjfs_get_dir_entry(inode, name);

		pmemobjfs_remove_dir_entry(objfs, inode, entry);
	} TX_END
}

/*
 * pmemobjfs_add_dir_entry -- add new directory entry
 */
static int
pmemobjfs_add_dir_entry(struct pmemobjfs *objfs,
		TOID(struct objfs_inode) inode,
		TOID(struct objfs_dir_entry) entry)
{
	/* check inode type */
	if (!S_ISDIR(D_RO(inode)->flags))
		return -ENOTDIR;

	int ret = 0;
	TX_BEGIN(objfs->pop) {
		/* insert new dir entry to list */
		PDLL_INSERT_HEAD(D_RW(inode)->dir.entries, entry, pdll);

		/* update dir size */
		TX_ADD_FIELD(inode, size);
		D_RW(inode)->size++;
	} TX_ONABORT {
		ret = -ECANCELED;
	} TX_END

	return ret;
}

/*
 * pmemobjfs_unlink_dir_entry -- unlink directory entry
 */
static int
pmemobjfs_unlink_dir_entry(struct pmemobjfs *objfs,
		TOID(struct objfs_inode) inode,
		TOID(struct objfs_dir_entry) entry)
{
	/* check inode type */
	if (!S_ISDIR(D_RO(inode)->flags))
		return -ENOTDIR;

	int ret = 0;

	TX_BEGIN(objfs->pop) {
		pmemobjfs_remove_dir_entry(objfs, inode, entry);

		/* update dir size */
		TX_ADD_FIELD(inode, size);
		D_RW(inode)->size--;
	} TX_ONABORT {
		ret = -ECANCELED;
	} TX_END

	return ret;
}

/*
 * pmemobjfs_new_dir -- create new directory
 */
static TOID(struct objfs_inode)
pmemobjfs_new_dir(struct pmemobjfs *objfs, TOID(struct objfs_inode) parent,
		const char *name, uint64_t flags, uint32_t uid, uint32_t gid)
{
	TOID(struct objfs_inode) inode = TOID_NULL(struct objfs_inode);

	TX_BEGIN(objfs->pop) {
		inode = pmemobjfs_inode_alloc(objfs, flags, uid, gid, 0);

		pmemobjfs_inode_init_dir(objfs, inode);

		/* add . and .. to new directory */
		TOID(struct objfs_dir_entry) dot =
			pmemobjfs_dir_entry_alloc(objfs, ".", inode);
		TOID(struct objfs_dir_entry) dotdot =
			pmemobjfs_dir_entry_alloc(objfs, "..", parent);

		pmemobjfs_add_dir_entry(objfs, inode, dot);
		pmemobjfs_add_dir_entry(objfs, inode, dotdot);
	} TX_ONABORT {
		inode = TOID_NULL(struct objfs_inode);
	} TX_END

	return inode;
}

/*
 * pmemobjfs_mkdir -- make new directory
 */
static int
pmemobjfs_mkdir(struct pmemobjfs *objfs, TOID(struct objfs_inode) inode,
		const char *name, uint64_t flags, uint32_t uid, uint32_t gid)
{
	/* check inode type */
	if (!S_ISDIR(D_RO(inode)->flags))
		return -ENOTDIR;

	int ret = 0;
	TX_BEGIN(objfs->pop) {
		TOID(struct objfs_inode) new_inode =
			pmemobjfs_new_dir(objfs, inode, name, flags, uid, gid);

		TOID(struct objfs_dir_entry) entry =
			pmemobjfs_dir_entry_alloc(objfs, name, new_inode);

		pmemobjfs_add_dir_entry(objfs, inode, entry);

		/* update modification time */
		TX_ADD_FIELD(inode, mtime);
		D_RW(inode)->mtime = time(NULL);
	} TX_ONABORT {
		ret = -ECANCELED;
	} TX_END

	return ret;
}

/*
 * pmemobjfs_remove_dir -- remove directory from directory
 */
static void
pmemobjfs_remove_dir(struct pmemobjfs *objfs,
		TOID(struct objfs_inode) inode,
		TOID(struct objfs_dir_entry) entry)
{
	/* removing entry inode */
	TOID(struct objfs_inode) rinode = D_RO(entry)->inode;
	TX_BEGIN(objfs->pop) {
		/* remove . and .. from removing dir */
		pmemobjfs_remove_dir_entry_name(objfs, rinode, ".");
		pmemobjfs_remove_dir_entry_name(objfs, rinode, "..");
		/* remove dir entry from parent */
		pmemobjfs_remove_dir_entry(objfs, inode, entry);
	} TX_END
}

/*
 * pmemobjfs_rmdir -- remove directory of given name
 */
static int
pmemobjfs_rmdir(struct pmemobjfs *objfs, TOID(struct objfs_inode) inode,
		const char *name)
{
	/* check parent inode type */
	if (!S_ISDIR(D_RO(inode)->flags))
		return -ENOTDIR;

	TOID(struct objfs_dir_entry) entry =
		pmemobjfs_get_dir_entry(inode, name);

	if (TOID_IS_NULL(entry))
		return -ENOENT;

	TOID(struct objfs_inode) entry_inode =
		D_RO(entry)->inode;

	/* check removing dir type */
	if (!S_ISDIR(D_RO(entry_inode)->flags))
		return -ENOTDIR;

	/* check if dir is empty (contains only . and ..) */
	if (D_RO(entry_inode)->size > 2)
		return -ENOTEMPTY;

	int ret = 0;

	TX_BEGIN(objfs->pop) {
		pmemobjfs_remove_dir(objfs, inode, entry);

		/* update dir size */
		TX_ADD_FIELD(inode, size);
		D_RW(inode)->size--;

		/* update modification time */
		TX_ADD_FIELD(inode, mtime);
		D_RW(inode)->mtime = time(NULL);
	} TX_ONABORT {
		ret = -ECANCELED;
	} TX_END

	return ret;
}

/*
 * pmemobjfs_create -- create new file in directory
 */
static int
pmemobjfs_create(struct pmemobjfs *objfs, TOID(struct objfs_inode) inode,
		const char *name, mode_t mode, uid_t uid, gid_t gid,
		TOID(struct objfs_inode) *inodep)
{
	int ret = 0;

	uint64_t flags = mode | S_IFREG;

	TOID(struct objfs_dir_entry) entry = TOID_NULL(struct objfs_dir_entry);
	TX_BEGIN(objfs->pop) {
		TOID(struct objfs_inode) new_file=
			pmemobjfs_inode_alloc(objfs, flags, uid, gid, 0);
		pmemobjfs_inode_init_file(objfs, new_file);

		entry = pmemobjfs_dir_entry_alloc(objfs, name, new_file);
		pmemobjfs_add_dir_entry(objfs, inode, entry);

		time_t t = time(NULL);
		/* update modification time */
		TX_ADD_FIELD(inode, mtime);
		D_RW(inode)->mtime = t;

		/* update status change time */
		TX_ADD_FIELD(inode, ctime);
		D_RW(inode)->ctime = t;
	} TX_ONABORT {
		ret = -ECANCELED;
	} TX_ONCOMMIT {
		if (inodep)
			*inodep = D_RO(entry)->inode;
	} TX_END

	return ret;
}

/*
 * pmemobjfs_open -- open inode
 */
static int
pmemobjfs_open(struct pmemobjfs *objfs, TOID(struct objfs_inode) inode)
{
	TOID(struct objfs_super) super =
		POBJ_ROOT(objfs->pop, struct objfs_super);

	int ret = 0;

	TX_BEGIN(objfs->pop) {
		/* insert inode to opened inodes map */
		map_insert(objfs->mapc, D_RW(super)->opened,
				inode.oid.off, inode.oid);
		/* hold inode */
		pmemobjfs_inode_hold(objfs, inode);
	} TX_ONABORT {
		ret = -ECANCELED;
	} TX_END

	return ret;
}

/*
 * pmemobjfs_close -- release inode
 */
static int
pmemobjfs_close(struct pmemobjfs *objfs,
		TOID(struct objfs_inode) inode)
{
	TOID(struct objfs_super) super =
		POBJ_ROOT(objfs->pop, struct objfs_super);

	int ret = 0;

	TX_BEGIN(objfs->pop) {
		/* remove inode from opened inodes map */
		map_remove(objfs->mapc, D_RW(super)->opened,
				inode.oid.off);
		/* release inode */
		pmemobjfs_inode_put(objfs, inode);
	} TX_ONABORT {
		ret = -ECANCELED;
	} TX_END

	return ret;
}

/*
 * pmemobjfs_rename -- rename/move inode
 */
static int
pmemobjfs_rename(struct pmemobjfs *objfs,
		TOID(struct objfs_inode) src_parent,
		const char *src_name,
		TOID(struct objfs_inode) dst_parent,
		const char *dst_name)
{
	/* check source and destination inodes type */
	if (!S_ISDIR(D_RO(src_parent)->flags))
		return -ENOTDIR;

	if (!S_ISDIR(D_RO(dst_parent)->flags))
		return -ENOTDIR;

	/* get source dir entry */
	TOID(struct objfs_dir_entry) src_entry =
		pmemobjfs_get_dir_entry(src_parent, src_name);

	TOID(struct objfs_inode) src_inode = D_RO(src_entry)->inode;

	if (TOID_IS_NULL(src_entry))
		return -ENOENT;

	int ret = 0;
	TX_BEGIN(objfs->pop) {
		/*
		 * Allocate new dir entry with destination name
		 * and source inode.
		 * NOTE:
		 * This *must* be called before removing dir entry from
		 * source directory because otherwise the source inode
		 * could be released before inserting to new dir entry.
		 */
		TOID(struct objfs_dir_entry) dst_entry =
			pmemobjfs_dir_entry_alloc(objfs, dst_name, src_inode);

		/* remove old dir entry from source */
		pmemobjfs_unlink_dir_entry(objfs, src_parent, src_entry);
		/* add new dir entry to destination */
		pmemobjfs_add_dir_entry(objfs, dst_parent, dst_entry);
	} TX_ONABORT {
		ret = -ECANCELED;
	} TX_END

	return ret;
}

/*
 * pmemobjfs_symlink -- create symbolic link
 */
static int
pmemobjfs_symlink(struct pmemobjfs *objfs,
		TOID(struct objfs_inode) inode,
		const char *name, const char *path,
		uid_t uid, gid_t gid)
{
	/* check inode type */
	if (!S_ISDIR(D_RO(inode)->flags))
		return -ENOTDIR;

	/* set 0777 permissions for symbolic links */
	uint64_t flags = 0777 | S_IFLNK;

	int ret = 0;

	TX_BEGIN(objfs->pop) {
		TOID(struct objfs_inode) symlink =
			pmemobjfs_inode_alloc(objfs, flags, uid, gid, 0);

		pmemobjfs_inode_init_symlink(objfs, symlink, path);

		D_RW(symlink)->size = pmemobjfs_symlink_size(symlink);

		TOID(struct objfs_dir_entry) entry =
			pmemobjfs_dir_entry_alloc(objfs, name, symlink);

		pmemobjfs_add_dir_entry(objfs, inode, entry);

		time_t t = time(NULL);
		/* update modification time */
		TX_ADD_FIELD(inode, mtime);
		D_RW(inode)->mtime = t;

		/* update status change time */
		TX_ADD_FIELD(inode, ctime);
		D_RW(inode)->ctime = t;
	} TX_ONABORT {
		ret = -ECANCELED;
	} TX_END

	return ret;
}

/*
 * pmemobjfs_mknod -- create node
 */
static int
pmemobjfs_mknod(struct pmemobjfs *objfs, TOID(struct objfs_inode) inode,
		const char *name, mode_t mode, uid_t uid, gid_t gid, dev_t dev)
{
	/* check inode type */
	if (!S_ISDIR(D_RO(inode)->flags))
		return -ENOTDIR;

	int ret = 0;

	TX_BEGIN(objfs->pop) {
		TOID(struct objfs_inode) node =
			pmemobjfs_inode_alloc(objfs, mode, uid, gid, dev);
		D_RW(node)->size = 0;

		TOID(struct objfs_dir_entry) entry =
			pmemobjfs_dir_entry_alloc(objfs, name, node);

		pmemobjfs_add_dir_entry(objfs, inode, entry);

		time_t t = time(NULL);
		/* update modification time */
		TX_ADD_FIELD(inode, mtime);
		D_RW(inode)->mtime = t;

		/* update status change time */
		TX_ADD_FIELD(inode, ctime);
		D_RW(inode)->ctime = t;
	} TX_ONABORT {
		ret = -ECANCELED;
	} TX_END

	return ret;
}

/*
 * pmemobjfs_chmod -- change mode of inode
 */
static int
pmemobjfs_chmod(struct pmemobjfs *objfs, TOID(struct objfs_inode) inode,
		mode_t mode)
{
	int ret = 0;

	TX_BEGIN(objfs->pop) {
		TX_ADD_FIELD(inode, flags);

		/* mask file type bit fields */
		uint64_t flags = D_RO(inode)->flags;
		flags = flags & S_IFMT;
		D_RW(inode)->flags = flags | (mode & ~S_IFMT);

		/* update status change time */
		TX_ADD_FIELD(inode, ctime);
		D_RW(inode)->ctime = time(NULL);
	} TX_ONABORT {
		ret = -ECANCELED;
	} TX_END

	return ret;
}

/*
 * pmemobjfs_chown -- change owner and group of inode
 */
static int
pmemobjfs_chown(struct pmemobjfs *objfs, TOID(struct objfs_inode) inode,
		uid_t uid, gid_t gid)
{
	int ret = 0;

	TX_BEGIN(objfs->pop) {
		TX_ADD_FIELD(inode, uid);
		D_RW(inode)->uid = uid;

		TX_ADD_FIELD(inode, gid);
		D_RW(inode)->gid = gid;

		/* update status change time */
		TX_ADD_FIELD(inode, ctime);
		D_RW(inode)->ctime = time(NULL);
	} TX_ONABORT {
		ret = -ECANCELED;
	} TX_END

	return ret;
}

/*
 * pmemobjfs_getattr -- get inode's attributes
 */
static int
pmemobjfs_getattr(TOID(struct objfs_inode) inode, struct stat *statbuf)
{
	memset(statbuf, 0, sizeof(*statbuf));

	statbuf->st_size = D_RO(inode)->size;
	statbuf->st_ctime = D_RO(inode)->ctime;
	statbuf->st_mtime = D_RO(inode)->mtime;
	statbuf->st_atime = D_RO(inode)->atime;
	statbuf->st_mode = D_RO(inode)->flags;
	statbuf->st_uid = D_RO(inode)->uid;
	statbuf->st_gid = D_RO(inode)->gid;
	statbuf->st_rdev = D_RO(inode)->dev;

	return 0;
}

/*
 * pmemobjfs_utimens -- set atime and mtime
 */
static int
pmemobjfs_utimens(struct pmemobjfs *objfs,
		TOID(struct objfs_inode) inode, const struct timespec tv[2])
{
	int ret = 0;

	TX_BEGIN(objfs->pop) {
		TX_ADD_FIELD(inode, atime);
		D_RW(inode)->atime = tv[0].tv_sec;

		TX_ADD_FIELD(inode, mtime);
		D_RW(inode)->mtime = tv[0].tv_sec;
	} TX_ONABORT {
		ret = -ECANCELED;
	} TX_END

	return ret;
}

/*
 * pmemobjfs_unlink -- unlink file from inode
 */
static int
pmemobjfs_unlink(struct pmemobjfs *objfs, TOID(struct objfs_inode) inode,
		const char *name)
{
	/* check inode type */
	if (!S_ISDIR(D_RO(inode)->flags))
		return -ENOTDIR;

	TOID(struct objfs_dir_entry) entry =
		pmemobjfs_get_dir_entry(inode, name);

	if (TOID_IS_NULL(entry))
		return -ENOENT;

	TOID(struct objfs_inode) entry_inode = D_RO(entry)->inode;

	/* check unlinking inode type */
	if (S_ISDIR(D_RO(entry_inode)->flags))
		return -EISDIR;

	int ret = 0;

	TX_BEGIN(objfs->pop) {
		pmemobjfs_remove_dir_entry(objfs, inode, entry);

		TX_ADD_FIELD(inode, size);
		D_RW(inode)->size--;
	} TX_ONABORT {
		ret = -ECANCELED;
	} TX_END

	return ret;
}

/*
 * pmemobjfs_put_opened_cb -- release all opened inodes
 */
static int
pmemobjfs_put_opened_cb(uint64_t key, PMEMoid value, void *arg)
{
	struct pmemobjfs *objfs = arg;

	TOID(struct objfs_inode) inode;
	TOID_ASSIGN(inode, value);

	TOID(struct objfs_super) super =
		POBJ_ROOT(objfs->pop, struct objfs_super);

	/*
	 * Set current value to OID_NULL so the tree_map_clear won't
	 * free this inode and release the inode.
	 */
	map_insert(objfs->mapc, D_RW(super)->opened,
			key, OID_NULL);
	pmemobjfs_inode_put(objfs, inode);

	return 0;
}

/*
 * pmemobjfs_fuse_getattr -- (FUSE) get file attributes
 */
static int
pmemobjfs_fuse_getattr(const char *path, struct stat *statbuf)
{
	log("%s", path);
	struct pmemobjfs *objfs = PMEMOBJFS;

	TOID(struct objfs_inode) inode;
	int ret = pmemobjfs_inode_lookup(objfs, path, &inode);
	if (ret)
		return ret;

	return pmemobjfs_getattr(inode, statbuf);
}

/*
 * pmemobjfs_fuse_opendir -- (FUSE) open directory
 */
static int
pmemobjfs_fuse_opendir(const char *path, struct fuse_file_info *fi)
{
	log("%s", path);
	struct pmemobjfs *objfs = PMEMOBJFS;

	TOID(struct objfs_inode) inode;
	int ret = pmemobjfs_inode_lookup(objfs, path, &inode);
	if (ret)
		return ret;

	/* check inode type */
	switch (D_RO(inode)->flags & S_IFMT) {
	case S_IFDIR:
		break;
	case S_IFREG:
		return -ENOTDIR;
	default:
		return -EINVAL;
	}

	/* add inode to opened inodes map */
	ret = pmemobjfs_open(objfs, inode);
	if (!ret)
		fi->fh = inode.oid.off;

	return ret;
}

/*
 * pmemobjfs_fuse_releasedir -- (FUSE) release opened dir
 */
static int
pmemobjfs_fuse_releasedir(const char *path, struct fuse_file_info *fi)
{
	log("%s", path);
	struct pmemobjfs *objfs = PMEMOBJFS;

	if (!fi->fh)
		return -EINVAL;

	TOID(struct objfs_inode) inode;
	inode.oid.off = fi->fh;
	inode.oid.pool_uuid_lo = objfs->pool_uuid_lo;

	/* remove inode from opened inodes map */
	int ret = pmemobjfs_close(objfs, inode);

	fi->fh = 0;

	return ret;
}

/*
 * pmemobjfs_fuse_readdir -- (FUSE) read directory entries
 */
static int
pmemobjfs_fuse_readdir(const char *path, void *buff, fuse_fill_dir_t fill,
		off_t off, struct fuse_file_info *fi)
{
	log("%s off = %lu", path, off);
	struct pmemobjfs *objfs = PMEMOBJFS;

	if (!fi->fh)
		return -EINVAL;

	TOID(struct objfs_inode) inode;
	inode.oid.off = fi->fh;
	inode.oid.pool_uuid_lo = objfs->pool_uuid_lo;

	if (!TOID_VALID(inode))
		return -EINVAL;

	/* check inode type */
	if (!S_ISDIR(D_RO(inode)->flags))
		return -ENOTDIR;

	/* walk through all dir entries and fill fuse buffer */
	int ret;
	TOID(struct objfs_dir_entry) entry;
	PDLL_FOREACH(entry, D_RW(inode)->dir.entries, pdll) {
		ret = fill(buff, D_RW(entry)->name, NULL, 0);
		if (ret)
			return ret;
	}

	return 0;
}

/*
 * pmemobjfs_fuse_mkdir -- (FUSE) create directory
 */
static int
pmemobjfs_fuse_mkdir(const char *path, mode_t mode)
{
	log("%s", path);
	struct pmemobjfs *objfs = PMEMOBJFS;

	TOID(struct objfs_inode) inode;
	const char *name;
	int ret = pmemobjfs_inode_lookup_parent(objfs, path, &inode, &name);
	if (ret)
		return ret;

	uid_t uid = fuse_get_context()->uid;
	uid_t gid = fuse_get_context()->gid;

	return pmemobjfs_mkdir(objfs, inode, name, mode | S_IFDIR, uid, gid);
}

/*
 * pmemobjfs_fuse_rmdir -- (FUSE) remove directory
 */
static int
pmemobjfs_fuse_rmdir(const char *path)
{
	log("%s", path);
	struct pmemobjfs *objfs = PMEMOBJFS;

	TOID(struct objfs_inode) inode;
	const char *name;
	int ret = pmemobjfs_inode_lookup_parent(objfs, path, &inode, &name);
	if (ret)
		return ret;

	return pmemobjfs_rmdir(objfs, inode, name);
}

/*
 * pmemobjfs_fuse_chmod -- (FUSE) change file permissions
 */
static int
pmemobjfs_fuse_chmod(const char *path, mode_t mode)
{
	log("%s 0%o", path, mode);
	struct pmemobjfs *objfs = PMEMOBJFS;

	TOID(struct objfs_inode) inode;
	int ret = pmemobjfs_inode_lookup(objfs, path, &inode);
	if (ret)
		return ret;

	return pmemobjfs_chmod(objfs, inode, mode);
}

/*
 * pmemobjfs_fuse_chown -- (FUSE) change owner
 */
static int
pmemobjfs_fuse_chown(const char *path, uid_t uid, gid_t gid)
{
	log("%s", path);
	struct pmemobjfs *objfs = PMEMOBJFS;

	TOID(struct objfs_inode) inode;
	int ret = pmemobjfs_inode_lookup(objfs, path, &inode);
	if (ret)
		return ret;

	return pmemobjfs_chown(objfs, inode, uid, gid);
}

/*
 * pmemobjfs_fuse_create -- (FUSE) create file
 */
static int
pmemobjfs_fuse_create(const char *path, mode_t mode, struct fuse_file_info *fi)
{
	log("%s mode %o", path, mode);
	struct pmemobjfs *objfs = PMEMOBJFS;

	TOID(struct objfs_inode) inode;
	const char *name;
	int ret = pmemobjfs_inode_lookup_parent(objfs, path, &inode, &name);
	if (ret)
		return ret;

	if (!S_ISDIR(D_RO(inode)->flags))
		return -EINVAL;

	uid_t uid = fuse_get_context()->uid;
	uid_t gid = fuse_get_context()->gid;

	TOID(struct objfs_inode) new_file;
	ret = pmemobjfs_create(objfs, inode, name, mode, uid, gid,
			&new_file);
	if (ret)
		return ret;

	/* add new inode to opened inodes */
	ret = pmemobjfs_open(objfs, new_file);
	if (ret)
		return ret;

	fi->fh = new_file.oid.off;

	return 0;
}

/*
 * pmemobjfs_fuse_utimens -- (FUSE) update access and modification times
 */
static int
pmemobjfs_fuse_utimens(const char *path, const struct timespec tv[2])
{
	log("%s", path);
	struct pmemobjfs *objfs = PMEMOBJFS;

	TOID(struct objfs_inode) inode;
	int ret = pmemobjfs_inode_lookup(objfs, path, &inode);
	if (ret)
		return ret;

	return pmemobjfs_utimens(objfs, inode, tv);
}

/*
 * pmemobjfs_fuse_open -- (FUSE) open file
 */
static int
pmemobjfs_fuse_open(const char *path, struct fuse_file_info *fi)
{
	log("%s", path);
	struct pmemobjfs *objfs = PMEMOBJFS;

	TOID(struct objfs_inode) inode;
	int ret = pmemobjfs_inode_lookup(objfs, path, &inode);
	if (ret)
		return ret;

	/* check inode type */
	switch (D_RO(inode)->flags & S_IFMT) {
	case S_IFREG:
		break;
	case S_IFDIR:
		return -EISDIR;
	default:
		return -EINVAL;
	}

	ret = pmemobjfs_open(objfs, inode);
	if (!ret)
		fi->fh = inode.oid.off;

	return ret;
}

/*
 * pmemobjfs_fuse_release -- (FUSE) release opened file
 */
static int
pmemobjfs_fuse_release(const char *path, struct fuse_file_info *fi)
{
	log("%s", path);
	struct pmemobjfs *objfs = PMEMOBJFS;

	if (!fi->fh)
		return -EINVAL;

	TOID(struct objfs_inode) inode;
	inode.oid.off = fi->fh;
	inode.oid.pool_uuid_lo = objfs->pool_uuid_lo;

	int ret = pmemobjfs_close(objfs, inode);

	/* perform deferred ioctl operation */
	if (!ret && objfs->ioctl_off && objfs->ioctl_off == fi->fh)
		pmemobjfs_ioctl(objfs);

	fi->fh = 0;

	return ret;
}

/*
 * pmemobjfs_fuse_write -- (FUSE) write to file
 */
static int
pmemobjfs_fuse_write(const char *path, const char *buff, size_t size,
		off_t offset, struct fuse_file_info *fi)
{
	log("%s size = %zu off = %lu", path, size, offset);
	struct pmemobjfs *objfs = PMEMOBJFS;

	if (!fi->fh)
		return -EINVAL;

	TOID(struct objfs_inode) inode;
	inode.oid.off = fi->fh;
	inode.oid.pool_uuid_lo = objfs->pool_uuid_lo;

	if (!TOID_VALID(inode))
		return -EINVAL;

	return pmemobjfs_write(objfs, inode, buff, size, offset);
}

/*
 * pmemobjfs_fuse_read -- (FUSE) read from file
 */
static int
pmemobjfs_fuse_read(const char *path, char *buff, size_t size, off_t off,
		struct fuse_file_info *fi)
{
	log("%s size = %zu off = %lu", path, size, off);
	struct pmemobjfs *objfs = PMEMOBJFS;

	if (!fi->fh)
		return -EINVAL;

	TOID(struct objfs_inode) inode;
	inode.oid.off = fi->fh;
	inode.oid.pool_uuid_lo = objfs->pool_uuid_lo;

	if (!TOID_VALID(inode))
		return -EINVAL;

	return pmemobjfs_read(objfs, inode, buff, size, off);
}

/*
 * pmemobjfs_fuse_truncate -- (FUSE) truncate file
 */
static int
pmemobjfs_fuse_truncate(const char *path, off_t off)
{
	log("%s", path);
	struct pmemobjfs *objfs = PMEMOBJFS;

	TOID(struct objfs_inode) inode;
	int ret = pmemobjfs_inode_lookup(objfs, path, &inode);
	if (ret)
		return ret;

	return pmemobjfs_truncate(objfs, inode, off);
}

/*
 * pmemobjfs_fuse_ftruncate -- (FUSE) truncate file
 */
static int
pmemobjfs_fuse_ftruncate(const char *path, off_t off, struct fuse_file_info *fi)
{
	log("%s off = %lu", path, off);
	struct pmemobjfs *objfs = PMEMOBJFS;

	if (!fi->fh)
		return -EINVAL;

	TOID(struct objfs_inode) inode;
	inode.oid.off = fi->fh;
	inode.oid.pool_uuid_lo = objfs->pool_uuid_lo;

	if (!TOID_VALID(inode))
		return -EINVAL;

	return pmemobjfs_truncate(objfs, inode, off);
}

/*
 * pmemobjfs_fuse_unlink -- (FUSE) unlink inode
 */
static int
pmemobjfs_fuse_unlink(const char *path)
{
	log("%s", path);
	struct pmemobjfs *objfs = PMEMOBJFS;

	TOID(struct objfs_inode) inode;
	const char *name;
	int ret = pmemobjfs_inode_lookup_parent(objfs, path, &inode, &name);
	if (ret)
		return ret;

	return pmemobjfs_unlink(objfs, inode, name);
}

/*
 * pmemobjfs_fuse_flush -- (FUSE) flush file
 */
static int
pmemobjfs_fuse_flush(const char *path, struct fuse_file_info *fi)
{
	log("%s", path);
	struct pmemobjfs *objfs = PMEMOBJFS;

	if (!fi->fh)
		return -EINVAL;

	TOID(struct objfs_inode) inode;
	inode.oid.off = fi->fh;
	inode.oid.pool_uuid_lo = objfs->pool_uuid_lo;

	if (!TOID_VALID(inode))
		return -EINVAL;

	/* check inode type */
	switch (D_RO(inode)->flags & S_IFMT) {
	case S_IFREG:
		break;
	case S_IFDIR:
		return -EISDIR;
	default:
		return -EINVAL;
	}

	/* nothing to do */
	return 0;
}

/*
 * pmemobjfs_fuse_ioctl -- (FUSE) ioctl for file
 */
#ifdef __FreeBSD__
#define EBADFD EBADF /* XXX */
#endif
static int
pmemobjfs_fuse_ioctl(const char *path, int cmd, void *arg,
		struct fuse_file_info *fi, unsigned flags, void *data)
{
	log("%s cmd %d", path, _IOC_NR(cmd));

	struct pmemobjfs *objfs = PMEMOBJFS;

	/* check transaction stage */
	switch (cmd) {
	case PMEMOBJFS_CTL_TX_BEGIN:
		if (pmemobj_tx_stage() != TX_STAGE_NONE)
			return -EINPROGRESS;
		break;
	case PMEMOBJFS_CTL_TX_ABORT:
		if (pmemobj_tx_stage() != TX_STAGE_WORK)
			return -EBADFD;
		break;
	case PMEMOBJFS_CTL_TX_COMMIT:
		if (pmemobj_tx_stage() != TX_STAGE_WORK)
			return -EBADFD;
		break;
	default:
		return -EINVAL;
	}

	/*
	 * Store the inode offset and command and defer ioctl
	 * execution to releasing the file. This is required
	 * to avoid unlinking .tx_XXXXXX file inside transaction
	 * because one would be rolled back if transaction abort
	 * would occur.
	 */
	objfs->ioctl_off = fi->fh;
	objfs->ioctl_cmd = cmd;

	return 0;
}

/*
 * pmemobjfs_fuse_rename -- (FUSE) rename file or directory
 */
static int
pmemobjfs_fuse_rename(const char *path, const char *dest)
{
	log("%s dest %s\n", path, dest);
	struct pmemobjfs *objfs = PMEMOBJFS;

	int ret;

	/* get source inode's parent and name */
	TOID(struct objfs_inode) src_parent;
	const char *src_name;
	ret = pmemobjfs_inode_lookup_parent(objfs, path,
			&src_parent, &src_name);
	if (ret)
		return ret;

	/* get destination inode's parent and name */
	TOID(struct objfs_inode) dst_parent;
	const char *dst_name;
	ret = pmemobjfs_inode_lookup_parent(objfs, dest,
			&dst_parent, &dst_name);
	if (ret)
		return ret;

	return pmemobjfs_rename(objfs,
			src_parent, src_name,
			dst_parent, dst_name);
}

/*
 * pmemobjfs_fuse_symlink -- (FUSE) create symbolic link
 */
static int
pmemobjfs_fuse_symlink(const char *path, const char *link)
{
	log("%s link %s", path, link);

	struct pmemobjfs *objfs = PMEMOBJFS;

	TOID(struct objfs_inode) inode;
	const char *name;
	int ret = pmemobjfs_inode_lookup_parent(objfs, link, &inode, &name);
	if (ret)
		return ret;

	uid_t uid = fuse_get_context()->uid;
	uid_t gid = fuse_get_context()->gid;

	return pmemobjfs_symlink(objfs, inode, name, path, uid, gid);
}

/*
 * pmemobjfs_fuse_readlink -- (FUSE) read symbolic link
 */
static int
pmemobjfs_fuse_readlink(const char *path, char *buff, size_t size)
{
	log("%s", path);
	struct pmemobjfs *objfs = PMEMOBJFS;

	TOID(struct objfs_inode) inode;
	int ret = pmemobjfs_inode_lookup(objfs, path, &inode);
	if (ret)
		return ret;

	return pmemobjfs_symlink_read(inode, buff, size);
}

/*
 * pmemobjfs_fuse_mknod -- (FUSE) create node
 */
static int
pmemobjfs_fuse_mknod(const char *path, mode_t mode, dev_t dev)
{
	log("%s mode %o major %u minor %u", path, mode,
			(unsigned)MAJOR(dev), (unsigned)MINOR(dev));
	struct pmemobjfs *objfs = PMEMOBJFS;

	TOID(struct objfs_inode) inode;
	const char *name;
	int ret = pmemobjfs_inode_lookup_parent(objfs, path, &inode, &name);
	if (ret)
		return ret;

	uid_t uid = fuse_get_context()->uid;
	uid_t gid = fuse_get_context()->gid;

	return pmemobjfs_mknod(objfs, inode, name, mode, uid, gid, dev);
}

/*
 * pmemobjfs_fuse_fallocate -- (FUSE) allocate blocks for file
 */
static int
pmemobjfs_fuse_fallocate(const char *path, int mode, off_t offset, off_t size,
		struct fuse_file_info *fi)
{
	log("%s mode %d offset %lu size %lu", path, mode, offset, size);
	struct pmemobjfs *objfs = PMEMOBJFS;

	if (!fi->fh)
		return -EINVAL;

	TOID(struct objfs_inode) inode;
	inode.oid.off = fi->fh;
	inode.oid.pool_uuid_lo = objfs->pool_uuid_lo;

	if (!TOID_VALID(inode))
		return -EINVAL;

	return pmemobjfs_fallocate(objfs, inode, offset, size);
}

/*
 * pmemobjfs_fuse_statvfs -- (FUSE) get filesystem info
 */
static int
pmemobjfs_fuse_statvfs(const char *path, struct statvfs *buff)
{
	log("%s", path);
	struct pmemobjfs *objfs = PMEMOBJFS;

	memset(buff, 0, sizeof(*buff));

	/*
	 * Some fields are ignored by FUSE.
	 * Some fields cannot be set due to the nature of pmemobjfs.
	 */
	buff->f_bsize = objfs->block_size;
	/* ignored buff->f_frsize */
	/* unknown buff->f_blocks */
	/* unknown buff->f_bfree */
	/* unknown buff->f_bavail */
	/* unknown buff->f_files */
	/* unknown buff->f_ffree */
	/* ignored buff->f_favail */
	/* ignored buff->f_fsid */
	/* ignored buff->f_flag */
	buff->f_namemax = objfs->max_name;

	return 0;
}

/*
 * pmemobjfs_fuse_init -- (FUSE) initialization
 */
static void *
pmemobjfs_fuse_init(struct fuse_conn_info *conn)
{
	log("");
	struct pmemobjfs *objfs = PMEMOBJFS;

	TOID(struct objfs_super) super =
		POBJ_ROOT(objfs->pop, struct objfs_super);

	/* fill some runtime information */
	objfs->block_size = D_RO(super)->block_size;
	objfs->max_name = objfs->block_size - sizeof(struct objfs_dir_entry);
	objfs->pool_uuid_lo = super.oid.pool_uuid_lo;

	TX_BEGIN(objfs->pop) {
		/* release all opened inodes */
		map_foreach(objfs->mapc, D_RW(super)->opened,
				pmemobjfs_put_opened_cb, objfs);
		/* clear opened inodes map */
		map_clear(objfs->mapc, D_RW(super)->opened);
	} TX_ONABORT {
		objfs = NULL;
	} TX_END

	return objfs;
}

/*
 * pmemobjfs_ops -- fuse operations
 */
static struct fuse_operations pmemobjfs_ops = {
	/* filesystem operations */
	.init		= pmemobjfs_fuse_init,
	.statfs		= pmemobjfs_fuse_statvfs,

	/* inode operations */
	.getattr	= pmemobjfs_fuse_getattr,
	.chmod		= pmemobjfs_fuse_chmod,
	.chown		= pmemobjfs_fuse_chown,
	.utimens	= pmemobjfs_fuse_utimens,
	.ioctl		= pmemobjfs_fuse_ioctl,

	/* directory operations */
	.opendir	= pmemobjfs_fuse_opendir,
	.releasedir	= pmemobjfs_fuse_releasedir,
	.readdir	= pmemobjfs_fuse_readdir,
	.mkdir		= pmemobjfs_fuse_mkdir,
	.rmdir		= pmemobjfs_fuse_rmdir,
	.rename		= pmemobjfs_fuse_rename,
	.mknod		= pmemobjfs_fuse_mknod,
	.symlink	= pmemobjfs_fuse_symlink,
	.create		= pmemobjfs_fuse_create,
	.unlink		= pmemobjfs_fuse_unlink,

	/* regular file operations */
	.open		= pmemobjfs_fuse_open,
	.release	= pmemobjfs_fuse_release,
	.write		= pmemobjfs_fuse_write,
	.read		= pmemobjfs_fuse_read,
	.flush		= pmemobjfs_fuse_flush,
	.truncate	= pmemobjfs_fuse_truncate,
	.ftruncate	= pmemobjfs_fuse_ftruncate,
	.fallocate	= pmemobjfs_fuse_fallocate,

	/* symlink operations */
	.readlink	= pmemobjfs_fuse_readlink,
};

/*
 * pmemobjfs_mkfs -- create pmemobjfs filesystem
 */
static int
pmemobjfs_mkfs(const char *fname, size_t size, size_t bsize, mode_t mode)
{
	struct pmemobjfs *objfs = calloc(1, sizeof(*objfs));
	if (!objfs)
		return -1;

	int ret = 0;

	objfs->block_size = bsize;

	objfs->pop = pmemobj_create(fname, POBJ_LAYOUT_NAME(pmemobjfs),
			size, mode);
	if (!objfs->pop) {
		fprintf(stderr, "error: %s\n", pmemobj_errormsg());
		ret = -1;
		goto out_free_objfs;
	}

	objfs->mapc = map_ctx_init(MAP_CTREE, objfs->pop);
	if (!objfs->mapc) {
		perror("map_ctx_init");
		ret = -1;
		goto out_close_pop;
	}

	TOID(struct objfs_super) super =
		POBJ_ROOT(objfs->pop, struct objfs_super);

	uid_t uid = getuid();
	gid_t gid = getgid();
	mode_t mask = umask(0);
	umask(mask);

	TX_BEGIN(objfs->pop) {
		/* inherit permissions from umask */
		uint64_t root_flags = S_IFDIR | (~mask & 0777);
		TX_ADD(super);

		/* create an opened files map */
		map_create(objfs->mapc, &D_RW(super)->opened, NULL);

		/* create root inode, inherit uid and gid from current user */
		D_RW(super)->root_inode =
			pmemobjfs_new_dir(objfs, TOID_NULL(struct objfs_inode),
					"/", root_flags, uid, gid);

		D_RW(super)->block_size = bsize;
	} TX_ONABORT {
		fprintf(stderr, "error: creating pmemobjfs aborted\n");
		ret = -ECANCELED;
	} TX_END

	map_ctx_free(objfs->mapc);
out_close_pop:
	pmemobj_close(objfs->pop);
out_free_objfs:
	free(objfs);

	return ret;
}

/*
 * parse_size -- parse size from string
 */
static int
parse_size(const char *str, uint64_t *sizep)
{
	uint64_t size = 0;
	int shift = 0;
	char unit[3] = {0};
	int ret = sscanf(str, "%lu%3s", &size, unit);
	if (ret <= 0)
		return -1;
	if (ret == 2) {
		if ((unit[1] != '\0' && unit[1] != 'B') ||
			unit[2] != '\0')
			return -1;
		switch (unit[0]) {
		case 'K':
		case 'k':
			shift = 10;
			break;
		case 'M':
			shift = 20;
			break;
		case 'G':
			shift = 30;
			break;
		case 'T':
			shift = 40;
			break;
		case 'P':
			shift = 50;
			break;
		default:
			return -1;
		}
	}

	if (sizep)
		*sizep = size << shift;

	return 0;
}

/*
 * pmemobjfs_mkfs_main -- parse arguments and create pmemobjfs
 */
static int
pmemobjfs_mkfs_main(int argc, char *argv[])
{
	static const char *usage_str =
		"usage: %s "
		"[-h] "
		"[-s <size>] "
		"[-b <block_size>] "
		"<file>\n";

	if (argc < 2) {
		fprintf(stderr, usage_str, argv[0]);
		return -1;
	}

	uint64_t size = PMEMOBJ_MIN_POOL;
	uint64_t bsize = PMEMOBJFS_MIN_BLOCK_SIZE;
	int opt;
	const char *optstr = "hs:b:";
	int size_used = 0;
	while ((opt = getopt(argc, argv, optstr)) != -1) {
		switch (opt) {
		case 'h':
			printf(usage_str, argv[0]);
			return 0;
		case 'b':
			if (parse_size(optarg, &bsize)) {
				fprintf(stderr, "error: invalid block size "
					"value specified -- '%s'\n", optarg);
				return -1;
			}
			break;
		case 's':
			if (parse_size(optarg, &size)) {
				fprintf(stderr, "error: invalid size "
					"value specified  -- '%s'\n", optarg);
				return -1;
			}
			size_used = 1;
			break;
		}
	}

	if (optind >= argc) {
		fprintf(stderr, usage_str, argv[0]);
		return -1;
	}

	const char *path = argv[optind];

	if (!access(path, F_OK)) {
		if (size_used) {
			fprintf(stderr, "error: cannot use size option "
					"for existing file\n");
			return -1;
		}
		size = 0;
	} else {
		if (size < PMEMOBJ_MIN_POOL) {
			fprintf(stderr, "error: minimum size is %lu\n",
					PMEMOBJ_MIN_POOL);
			return -1;
		}
	}

	if (bsize < PMEMOBJFS_MIN_BLOCK_SIZE) {
		fprintf(stderr, "error: minimum block size is %zu\n",
				PMEMOBJFS_MIN_BLOCK_SIZE);
		return -1;
	}

	return pmemobjfs_mkfs(path, size, bsize, 0777);

}

/*
 * pmemobjfs_tx_ioctl -- transaction ioctl
 *
 * In order to call the ioctl we need to create a temporary file in
 * specified directory and call the ioctl on that file. After calling the
 * ioctl the file is unlinked. The actual action is performed after unlinking
 * the file so if the operation was to start a transaction the temporary file
 * won't be unlinked within the transaction.
 */
static int
pmemobjfs_tx_ioctl(const char *dir, int req)
{
	int ret = 0;

	/* append temporary file template to specified path */
	size_t dirlen = strlen(dir);
	size_t tmpllen = strlen(PMEMOBJFS_TMP_TEMPLATE);
	char *path = malloc(dirlen + tmpllen + 1);
	if (!path)
		return -1;

	memcpy(path, dir, dirlen);
	strcpy(path + dirlen, PMEMOBJFS_TMP_TEMPLATE);

	/* create temporary file */
	mode_t prev_umask = umask(S_IRWXG | S_IRWXO);

	int fd = mkstemp(path);

	umask(prev_umask);

	if (fd < 0) {
		perror(path);
		ret = -1;
		goto out_free;
	}

	/* perform specified ioctl command */
	ret = ioctl(fd, req);
	if (ret) {
		perror(path);
		goto out_unlink;
	}

out_unlink:
	/* unlink temporary file */
	ret = unlink(path);
	if (ret)
		perror(path);
	close(fd);
out_free:
	free(path);
	return ret;
}

int
main(int argc, char *argv[])
{
	char *bname = basename(argv[0]);
	if (strcmp(PMEMOBJFS_MKFS, bname) == 0) {
		return pmemobjfs_mkfs_main(argc, argv);
	} else if (strcmp(PMEMOBJFS_TX_BEGIN, bname) == 0) {
		if (argc != 2) {
			fprintf(stderr, "usage: %s <dir>\n", bname);
			return -1;
		}
		char *arg = argv[1];
		return pmemobjfs_tx_ioctl(arg, PMEMOBJFS_CTL_TX_BEGIN);
	} else if (strcmp(PMEMOBJFS_TX_COMMIT, bname) == 0) {
		if (argc != 2) {
			fprintf(stderr, "usage: %s <dir>\n", bname);
			return -1;
		}
		char *arg = argv[1];
		return pmemobjfs_tx_ioctl(arg, PMEMOBJFS_CTL_TX_COMMIT);
	} else if (strcmp(PMEMOBJFS_TX_ABORT, bname) == 0) {
		if (argc != 2) {
			fprintf(stderr, "usage: %s <dir>\n", bname);
			return -1;
		}
		char *arg = argv[1];
		return pmemobjfs_tx_ioctl(arg, PMEMOBJFS_CTL_TX_ABORT);
	}
#if DEBUG
	log_fh = fopen("pmemobjfs.log", "w+");
	if (!log_fh)
		err(-1, "pmemobjfs.log");

	log("\n\n\nPMEMOBJFS\n");
#endif

	const char *fname = argv[argc - 2];
	struct pmemobjfs *objfs = calloc(1, sizeof(*objfs));
	if (!objfs) {
		perror("malloc");
		return -1;
	}

	int ret = 0;

	objfs->pop = pmemobj_open(fname, POBJ_LAYOUT_NAME(pmemobjfs));
	if (objfs->pop == NULL) {
		perror("pmemobj_open");
		ret = -1;
		goto out;
	}

	objfs->mapc = map_ctx_init(MAP_CTREE, objfs->pop);
	if (!objfs->mapc) {
		perror("map_ctx_init");
		ret = -1;
		goto out;
	}

	argv[argc - 2] = argv[argc - 1];
	argv[argc - 1] = NULL;
	argc--;

	ret = fuse_main(argc, argv, &pmemobjfs_ops, objfs);

	pmemobj_close(objfs->pop);
out:
	free(objfs);
	log("ret = %d", ret);
	return ret;
}
