/*
 * This file is licensed to you under your choice of the GNU Lesser
 * General Public License, version 2.1 or any later version (LGPLv2.1 or
 * later), or the Apache License 2.0.
 */

#define _GNU_SOURCE
#include <memory.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <limits.h>
#include <sys/mman.h>
#include <string.h>
#include <stdint.h>
#include <stdarg.h>
#include <errno.h>
#include <dirent.h>
#include <scsi/scsi.h>

#include <libnl3/netlink/genl/genl.h>
#include <libnl3/netlink/genl/mngt.h>
#include <libnl3/netlink/genl/ctrl.h>
#include <libnl3/netlink/errno.h>

#include "target_core_user_local.h"
#include "libtcmu.h"
#include "libtcmu_log.h"
#include "libtcmu_priv.h"
#include "scsi_defs.h"

#define TCMU_NL_VERSION 2

static struct nla_policy tcmu_attr_policy[TCMU_ATTR_MAX+1] = {
	[TCMU_ATTR_DEVICE]	= { .type = NLA_STRING },
	[TCMU_ATTR_MINOR]	= { .type = NLA_U32 },
	[TCMU_ATTR_CMD_STATUS]	= { .type = NLA_S32 },
	[TCMU_ATTR_DEVICE_ID]	= { .type = NLA_U32 },
	[TCMU_ATTR_DEV_CFG]	= { .type = NLA_STRING },
	[TCMU_ATTR_DEV_SIZE]	= { .type = NLA_U64 },
	[TCMU_ATTR_WRITECACHE]	= { .type = NLA_U8 },
	[TCMU_ATTR_SUPP_KERN_CMD_REPLY] = { .type = NLA_U8 },
};

static int device_add(struct tcmulib_context *ctx, char *dev_name,
		      char *cfgstring, bool reopen);
static void device_remove(struct tcmulib_context *ctx, char *dev_name,
			  bool should_block);
static int handle_netlink(struct nl_cache_ops *unused, struct genl_cmd *cmd,
			  struct genl_info *info, void *arg);

static struct genl_cmd tcmu_cmds[] = {
	{
		.c_id		= TCMU_CMD_ADDED_DEVICE,
		.c_name		= "ADDED DEVICE",
		.c_msg_parser	= handle_netlink,
		.c_maxattr	= TCMU_ATTR_MAX,
		.c_attr_policy	= tcmu_attr_policy,
	},
	{
		.c_id		= TCMU_CMD_REMOVED_DEVICE,
		.c_name		= "REMOVED DEVICE",
		.c_msg_parser	= handle_netlink,
		.c_maxattr	= TCMU_ATTR_MAX,
		.c_attr_policy	= tcmu_attr_policy,
	},
	{
		.c_id		= TCMU_CMD_RECONFIG_DEVICE,
		.c_name		= "RECONFIG DEVICE",
		.c_msg_parser	= handle_netlink,
		.c_maxattr	= TCMU_ATTR_MAX,
		.c_attr_policy	= tcmu_attr_policy,
	},
};

static struct genl_ops tcmu_ops = {
	.o_name		= "TCM-USER",
	.o_cmds		= tcmu_cmds,
	.o_ncmds	= ARRAY_SIZE(tcmu_cmds),
};

static int send_netlink_reply(struct tcmulib_context *ctx, int reply_cmd,
			      uint32_t dev_id, int status)
{
	struct nl_sock *sock = ctx->nl_sock;
	struct nl_msg *msg;
	void *hdr;
	int ret = -ENOMEM;

	msg = nlmsg_alloc();
	if (!msg)
		return ret;

	hdr = genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, tcmu_ops.o_id,
			  0, 0, reply_cmd, TCMU_NL_VERSION);
	if (!hdr)
		goto free_msg;

	ret = nla_put_s32(msg, TCMU_ATTR_CMD_STATUS, status);
	if (ret < 0)
		goto free_msg;

	ret = nla_put_u32(msg, TCMU_ATTR_DEVICE_ID, dev_id);
	if (ret < 0)
		goto free_msg;

	/* Ignore ack. There is nothing we can do. */
	ret = nl_send_auto(sock, msg);
free_msg:
	nlmsg_free(msg);

	if (ret < 0)
		tcmu_err("Could not send netlink cmd %d\n", reply_cmd);
	return ret;
}

static struct tcmu_device *
lookup_dev_by_name(struct tcmulib_context *ctx, char *dev_name, int *index)
{
	struct tcmu_device **dev_ptr;
	struct tcmu_device *dev;
	int i = 0;

	*index = 0;

	darray_foreach(dev_ptr, ctx->devices) {
		dev = *dev_ptr;

		if (!strcmp(dev->dev_name, dev_name)) {
			*index = i;
			return dev;
		}
		i++;
	}

	return NULL;
}

static const char *const tcmulib_cfg_type_lookup[] = {
	[TCMULIB_CFG_DEV_CFGSTR]  = "TCMULIB_CFG_DEV_CFGSTR",
	[TCMULIB_CFG_DEV_SIZE]    = "TCMULIB_CFG_DEV_SIZE",
	[TCMULIB_CFG_WRITE_CACHE] = "TCMULIB_CFG_WRITE_CACHE",
};

static int reconfig_device(struct tcmulib_context *ctx, char *dev_name,
			   struct genl_info *info)
{
	struct tcmu_device *dev;
	struct tcmulib_cfg_info cfg;
	int i, ret;

	memset(&cfg, 0, sizeof(cfg));

	dev = lookup_dev_by_name(ctx, dev_name, &i);
	if (!dev) {
		tcmu_err("Could not reconfigure device %s: not found.\n",
			 dev_name);
		return -ENODEV;
	}

	if (info->attrs[TCMU_ATTR_DEV_CFG]) {
		cfg.type = TCMULIB_CFG_DEV_CFGSTR;
		cfg.data.dev_cfgstring =
				nla_get_string(info->attrs[TCMU_ATTR_DEV_CFG]);
	} else if (info->attrs[TCMU_ATTR_DEV_SIZE]) {
		cfg.type = TCMULIB_CFG_DEV_SIZE;
		cfg.data.dev_size = nla_get_u64(info->attrs[TCMU_ATTR_DEV_SIZE]);
	} else if (info->attrs[TCMU_ATTR_WRITECACHE]) {
		cfg.type = TCMULIB_CFG_WRITE_CACHE;
		cfg.data.write_cache =
				nla_get_u8(info->attrs[TCMU_ATTR_WRITECACHE]);
	} else {
		tcmu_dev_err(dev,
			     "Unknown reconfig attr. Try updating libtcmu.\n");
		return -EOPNOTSUPP;
	}

	if (!dev->handler->reconfig) {
		tcmu_dev_dbg(dev, "Reconfiguration is not supported with this device. "
		             "Request for %s.\n", tcmulib_cfg_type_lookup[cfg.type]);
		return -EOPNOTSUPP;
	}

	ret = dev->handler->reconfig(dev, &cfg);
	if (ret < 0) {
		tcmu_dev_dbg(dev, "Handler reconfig for %s failed with error %s.\n",
		             tcmulib_cfg_type_lookup[cfg.type], strerror(-ret));
		return ret;
	}

	return 0;
}

static int handle_netlink(struct nl_cache_ops *unused, struct genl_cmd *cmd,
			  struct genl_info *info, void *arg)
{
	struct tcmulib_context *ctx = arg;
	int ret, reply_cmd, version = info->genlhdr->version;
	char buf[32];

	tcmu_dbg("cmd %d. Got header version %d. Supported %d.\n",
		 cmd->c_id, info->genlhdr->version, TCMU_NL_VERSION);

	if (!info->attrs[TCMU_ATTR_MINOR] || !info->attrs[TCMU_ATTR_DEVICE]) {
		tcmu_err("TCMU_ATTR_MINOR or TCMU_ATTR_DEVICE not set, dropping netlink command.\n");
		return 0;
	}

	if (version > 1 && !info->attrs[TCMU_ATTR_DEVICE_ID]) {
		tcmu_err("TCMU_ATTR_DEVICE_ID not set in v%d cmd %d, dropping netink command.\n", version, cmd->c_id);
		return 0;
	}

	snprintf(buf, sizeof(buf), "uio%d", nla_get_u32(info->attrs[TCMU_ATTR_MINOR]));

	switch (cmd->c_id) {
	case TCMU_CMD_ADDED_DEVICE:
		reply_cmd = TCMU_CMD_ADDED_DEVICE_DONE;
		ret = device_add(ctx, buf,
				 nla_get_string(info->attrs[TCMU_ATTR_DEVICE]),
				 false);
		break;
	case TCMU_CMD_REMOVED_DEVICE:
		reply_cmd = TCMU_CMD_REMOVED_DEVICE_DONE;
		device_remove(ctx, buf, false);
		ret = 0;
		break;
	case TCMU_CMD_RECONFIG_DEVICE:
		reply_cmd = TCMU_CMD_RECONFIG_DEVICE_DONE;
		ret = reconfig_device(ctx, buf, info);
		break;
	default:
		tcmu_err("Unknown netlink command %d. Netlink header received version %d. libtcmu supports %d\n",
			 cmd->c_id, version, TCMU_NL_VERSION);
		return -EOPNOTSUPP;
	}

	if (version > 1)
		ret = send_netlink_reply(ctx, reply_cmd,
				nla_get_u32(info->attrs[TCMU_ATTR_DEVICE_ID]),
				ret);

	return ret;
}

static int set_genl_features(struct nl_sock *sock)
{
	struct nl_msg *msg;
	void *hdr;
	int ret = -NLE_NOMEM;

	msg = nlmsg_alloc();
	if (!msg) {
		tcmu_err("Could not allocate a new message.\n");
		return ret;
	}

	hdr = genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, tcmu_ops.o_id,
			  0, NLM_F_ACK, TCMU_CMD_SET_FEATURES, TCMU_NL_VERSION);
	if (!hdr)
		goto free_msg;

	ret = nla_put_u8(msg, TCMU_ATTR_SUPP_KERN_CMD_REPLY, 1);
	if (ret < 0)
		goto free_msg;

	/*
	 * Could be a older kernel. Ignore failure and just work in degraded
	 * mode.
	 */
	ret = nl_send_sync(sock, msg);
	if (ret == -NLE_OPNOTSUPP) {
		tcmu_warn("Kernel does not support the operation.\n");
		ret = 0;
	}
	goto done;

free_msg:
	nlmsg_free(msg);

done:
	if (ret < 0)
		tcmu_err("Could not set features. Error %d\n", ret);

	return ret;
}

static struct nl_sock *setup_netlink(struct tcmulib_context *ctx)
{
	struct nl_sock *sock;
	int ret;

	sock = nl_socket_alloc();
	if (!sock) {
		tcmu_err("couldn't alloc socket\n");
		return NULL;
	}

	nl_socket_disable_seq_check(sock);

	nl_socket_modify_cb(sock, NL_CB_VALID, NL_CB_CUSTOM, genl_handle_msg, ctx);

	ret = genl_connect(sock);
	if (ret < 0) {
		tcmu_err("couldn't connect\n");
		goto err_free;
	}

	ret = genl_register_family(&tcmu_ops);
	if (ret < 0) {
		tcmu_err("couldn't register family\n");
		goto err_close;
	}

	ret = genl_ops_resolve(sock, &tcmu_ops);
	if (ret < 0) {
		tcmu_err("couldn't resolve ops, is target_core_user.ko loaded?\n");
		goto err_unregister;
	}

	ret = genl_ctrl_resolve_grp(sock, "TCM-USER", "config");
	if (ret < 0) {
		tcmu_err("couldn't resolve netlink family group, is target_core_user.ko loaded?\n");
		goto err_unregister;
	}

	ret = nl_socket_add_membership(sock, ret);
	if (ret < 0) {
		tcmu_err("couldn't add membership\n");
		goto err_unregister;
	}

	ret = set_genl_features(sock);
	if (ret < 0)
		goto err_unregister;

	return sock;

err_unregister:
	genl_unregister_family(&tcmu_ops);
err_close:
	nl_close(sock);
err_free:
	nl_socket_free(sock);

	return NULL;
}

static void teardown_netlink(struct nl_sock *sock)
{
	int ret;

	ret = genl_unregister_family(&tcmu_ops);
	if (ret != 0) {
		tcmu_err("genl_unregister_family failed, %d\n", ret);
	}

	nl_close(sock);
	nl_socket_free(sock);
}

static struct tcmulib_handler *find_handler(struct tcmulib_context *ctx,
					    char *cfgstring)
{
	struct tcmulib_handler *handler;
	size_t len;
	char *found_at;

	found_at = strchrnul(cfgstring, '/');
	len = found_at - cfgstring;

	darray_foreach(handler, ctx->handlers) {
		if (!strncmp(cfgstring, handler->subtype, len))
		    return handler;
	}

	return NULL;
}

void tcmu_dev_flush_ring(struct tcmu_device *dev)
{
	struct tcmu_mailbox *mb = dev->map;

	tcmu_dev_dbg(dev, "waiting for ring to clear\n");
	while (mb->cmd_head != mb->cmd_tail)
		usleep(50000);
	tcmu_dev_dbg(dev, "ring clear\n");
}

bool tcmu_dev_oooc_supported(struct tcmu_device* dev)
{
	return dev->map->flags & TCMU_MAILBOX_FLAG_CAP_OOOC;
}

/* Read a size_t from a file.  Returns -1 on error. */
static ssize_t read_size(const char *filename)
{
	int fd;
	int len, rc;
	char buf[256], *endbuf;
	ssize_t ret;

	fd = open(filename, O_RDONLY);
	if (fd == -1)
		goto err;

	len = read(fd, buf, sizeof(buf)-1);
	rc = close(fd);
	if (len <= 0 || rc == -1)
		goto err;

	buf[len] = '\0'; /* null-terminate */
	ret = strtoull(buf, &endbuf, 0);
	if (buf == endbuf || ret == ULLONG_MAX)
		goto err;

	return ret;

err:
	tcmu_warn("cannot read size from %s\n", filename);
	return -1;
}

/* Extract configuration parameters into dev. */
static bool device_parse_cfg(struct tcmu_device *dev,
			     const char *dev_name, const char *cfgstring)
{
	int len;
	const char *ptr, *oldptr;

	len = snprintf(dev->dev_name, sizeof(dev->dev_name), "%s", dev_name);
	if (len >= sizeof(dev->dev_name)) {
		tcmu_err("device name too long for tcmu_device\n");
		goto err_recompile;
	}

	/* Check valid cfgstring */
	oldptr = cfgstring;
	ptr = strchr(oldptr, '/');
	if (!ptr)
		goto err_badcfg;
	if (strncmp(cfgstring, "tcm-user", ptr-oldptr))
		goto err_badcfg;

	/* Get HBA name */
	oldptr = ptr+1;
	ptr = strchr(oldptr, '/');
	if (!ptr)
		goto err_badcfg;
	len = ptr-oldptr;
	len = snprintf(dev->tcm_hba_name, sizeof(dev->tcm_hba_name), "user_%.*s", len, oldptr);
	if (len >= sizeof(dev->tcm_hba_name)) {
		tcmu_err("hba name too long for tcmu_device\n");
		goto err_recompile;
	}

	/* Get device name */
	oldptr = ptr+1;
	ptr = strchr(oldptr, '/');
	if (!ptr)
		goto err_badcfg;
	len = ptr-oldptr;
	len = snprintf(dev->tcm_dev_name, sizeof(dev->tcm_dev_name), "%.*s", len, oldptr);
	if (len >= sizeof(dev->tcm_dev_name)) {
		tcmu_err("tcm device name too long for tcmu_device\n");
		goto err_recompile;
	}

	/* The rest is the handler-specific cfgstring */
	oldptr = ptr+1;
	len = snprintf(dev->cfgstring, sizeof(dev->cfgstring), "%s", oldptr);
	if (len >= sizeof(dev->cfgstring)) {
		tcmu_warn("additional handler cfgstring was truncated\n");
		/* not a terminal error. snprintf() will null-terminate */
	}
	return true;

err_badcfg:
	tcmu_err("invalid cfgstring: expecting \"tcm-user/<hba_name>/<tcm_device_name>/<handler_name_config>\"\n");
err_recompile: /* consider expanding string lengths in dev */
	return false;
}

static void device_close_shm(struct tcmu_device *dev)
{
	int ret;

	ret = close(dev->fd);
	if (ret != 0) {
		tcmu_err("could not close device fd for %s: %d\n", dev->dev_name, errno);
	}
	ret = munmap(dev->map, dev->map_len);
	if (ret != 0) {
		tcmu_err("could not unmap device %s: %d\n", dev->dev_name, errno);
	}
}

static bool device_open_shm(struct tcmu_device *dev)
{
	size_t mmap_size;
	char *mmap_name;
	off_t mmap_offset;

	/* get filename, size and offset */
	mmap_name = tcmu_dev_get_memory_info(dev, NULL, &mmap_size, &mmap_offset);
	if (!mmap_name)
		goto err_fail;

	/* cache the map size */
	dev->map_len = mmap_size;

	/* open the map */
	dev->fd = open(mmap_name, O_RDWR | O_NONBLOCK | O_CLOEXEC);
	if (dev->fd == -1) {
		tcmu_err("could not open %s\n", mmap_name);
		goto err_mmap_name;
	}

	/* bring the map into memory */
	dev->map = mmap(NULL, dev->map_len, PROT_READ|PROT_WRITE, MAP_SHARED, dev->fd, mmap_offset);
	if (dev->map == MAP_FAILED) {
		tcmu_err("could not mmap %s\n", mmap_name);
		goto err_fd_close;
	}

	if (dev->map->version != KERN_IFACE_VER) {
		tcmu_err("Kernel interface version mismatch: wanted %d got %d\n",
			KERN_IFACE_VER, dev->map->version);
		goto err_munmap;
	}

	free(mmap_name);
	return true;

err_munmap:
	munmap(dev->map, dev->map_len);
err_fd_close:
	close(dev->fd);
err_mmap_name:
	free(mmap_name);
err_fail:
	return false;
}

static int device_add(struct tcmulib_context *ctx, char *dev_name,
		      char *cfgstring, bool reopen)
{
	struct tcmu_device *dev;
	char *reason = NULL;
	int rc;
	bool reset_supp = true;

	dev = calloc(1, sizeof(*dev));
	if (!dev) {
		tcmu_err("calloc failed for device_add()\n");
		return -ENOMEM;
	}

	if (!device_parse_cfg(dev, dev_name, cfgstring))
		goto err_free;

	dev->handler = find_handler(ctx, dev->cfgstring);
	if (!dev->handler) {
		tcmu_err("could not find handler for %s\n", dev->dev_name);
		goto err_free;
	}

	if (dev->handler->check_config &&
	    !dev->handler->check_config(dev->cfgstring, &reason)) {
		/* It may be handled by other handlers */
		tcmu_err("check_config failed for %s because of %s\n", dev->dev_name, reason);
		free(reason);
		goto err_free;
	}

	if (reopen) {
		/*
		 * We might not have cleanly shutdown and IO might be
		 * running in the kernel or have timed out. Block the device
		 * so new IO is stopped, and reset the ring so we can start
		 * from a fresh slate. We will unblock below when we are
		 * completely setup.
		 */
		rc = tcmu_cfgfs_dev_exec_action(dev, "block_dev", 1);
		/*
		 * As long as the block_dev file existed, try to reset
		 * just in case the kernel was in a invald state.
		 */
		if (rc == -ENOENT) {
			reset_supp = false;
		} else {
			/*
			 * Force a retry of the outstanding commands.
			 */
			rc = tcmu_cfgfs_dev_exec_action(dev, "reset_ring", 1);
			if (rc)
				tcmu_dev_err(dev, "Could not reset ring %d.\n", rc);
		}
	}

	if (!device_open_shm(dev))
		goto err_unblock;

	dev->cmd_tail = dev->map->cmd_tail;
	dev->ctx = ctx;

	rc = dev->handler->added(dev);
	if (rc != 0) {
		tcmu_err("handler open failed for %s\n", dev->dev_name);
		goto err_closeshm;
	}

	darray_append(ctx->devices, dev);

	if (reopen && reset_supp)
		tcmu_cfgfs_dev_exec_action(dev, "block_dev", 0);

	return 0;

err_closeshm:
	device_close_shm(dev);
err_unblock:
	if (reopen && reset_supp)
		tcmu_cfgfs_dev_exec_action(dev, "block_dev", 0);
err_free:
	free(dev);
	return -ENOENT;
}

static void close_devices(struct tcmulib_context *ctx)
{
	struct tcmu_device **dev_ptr;
	struct tcmu_device *dev;

	darray_foreach_reverse(dev_ptr, ctx->devices) {
		dev = *dev_ptr;
		device_remove(ctx, dev->dev_name, true);
	}
}

static void device_remove(struct tcmulib_context *ctx, char *dev_name,
			  bool should_block)
{
	struct tcmu_device *dev;
	int i;

	dev = lookup_dev_by_name(ctx, dev_name, &i);
	if (!dev) {
		tcmu_err("Could not remove device %s: not found.\n", dev_name);
		return;
	}

	/*
	 * If called through nl, IO will be stopped. If called by a
	 * app/daemon, IO might be runnning. Try to do a ordered
	 * shutdown and allow IO to complete normally.
	 */
	if (should_block) {
		tcmu_cfgfs_dev_exec_action(dev, "block_dev", 1);
		tcmu_dev_flush_ring(dev);
	}

	darray_remove(ctx->devices, i);

	dev->handler->removed(dev);

	device_close_shm(dev);

	if (should_block)
		tcmu_cfgfs_dev_exec_action(dev, "block_dev", 0);

	tcmu_dev_dbg(dev, "removed from tcmulib.\n");
	free(dev);
}

static int read_uio_name(const char *uio_dev, char **dev_name)
{
	int fd;
	char *tmp_path;
	int ret = -1;
	char buf[PATH_MAX] = {'\0'};

	if (asprintf(&tmp_path, "/sys/class/uio/%s/name", uio_dev) == -1)
		return -1;

	fd = open(tmp_path, O_RDONLY);
	if (fd == -1) {
		tcmu_err("could not open %s\n", tmp_path);
		goto free_path;
	}

	ret = read(fd, buf, sizeof(buf));
	if (ret <= 0 || ret >= sizeof(buf)) {
		tcmu_err("read of %s had issues\n", tmp_path);
		goto close;
	}

	buf[ret-1] = '\0'; /* null-terminate and chop off the \n */

	*dev_name = strdup(buf);

	ret = 0;

close:
	close(fd);
free_path:
	free(tmp_path);
	return ret;
}

static int is_uio(const struct dirent *dirent)
{
	char *dev_name = NULL;
	ssize_t ret = 0;

	if (strncmp(dirent->d_name, "uio", 3))
		return 0;

	if (read_uio_name(dirent->d_name, &dev_name))
		goto out;

	/* we only want uio devices whose name is a format we expect */
	if (strncmp(dev_name, "tcm-user", 8))
		goto out;

	ret = 1;

out:
	if (dev_name)
		free(dev_name);
	return ret;
}

static int open_devices(struct tcmulib_context *ctx)
{
	struct dirent **dirent_list;
	int num_devs;
	int num_good_devs = 0;
	int i;

	num_devs = scandir("/dev", &dirent_list, is_uio, alphasort);
	if (num_devs == -1)
		return -1;

	for (i = 0; i < num_devs; i++) {
		char *dev_name = NULL;

		if (read_uio_name(dirent_list[i]->d_name, &dev_name))
			continue;

		if (device_add(ctx, dirent_list[i]->d_name, dev_name, true) < 0) {
			free (dev_name);
			continue;
		}
		free(dev_name);

		num_good_devs++;
	}

	for (i = 0; i < num_devs; i++)
		free(dirent_list[i]);
	free(dirent_list);

	return num_good_devs;
}

static void release_resources(struct tcmulib_context *ctx)
{
	teardown_netlink(ctx->nl_sock);
	darray_free(ctx->handlers);
	darray_free(ctx->devices);
	free(ctx);
}

struct tcmulib_context *tcmulib_initialize(
	struct tcmulib_handler *handlers,
	size_t handler_count)
{
	struct tcmulib_context *ctx;
	int ret;
	int i;

	ctx = calloc(1, sizeof(*ctx));
	if (!ctx)
		return NULL;

	ctx->nl_sock = setup_netlink(ctx);
	if (!ctx->nl_sock) {
		free(ctx);
		return NULL;
	}

	darray_init(ctx->handlers);
	darray_init(ctx->devices);

	for (i = 0; i < handler_count; i++) {
		struct tcmulib_handler handler = handlers[i];
		handler.ctx = ctx;
		darray_append(ctx->handlers, handler);
	}

	ret = open_devices(ctx);
	if (ret < 0) {
		release_resources(ctx);
		return NULL;
	}

	return ctx;
}

void tcmulib_close(struct tcmulib_context *ctx)
{
	close_devices(ctx);
	release_resources(ctx);
}

int tcmulib_get_master_fd(struct tcmulib_context *ctx)
{
	return nl_socket_get_fd(ctx->nl_sock);
}

int tcmulib_master_fd_ready(struct tcmulib_context *ctx)
{
	return nl_recvmsgs_default(ctx->nl_sock);
}

void *tcmu_dev_get_private(struct tcmu_device *dev)
{
	return dev->hm_private;
}

void tcmu_dev_set_private(struct tcmu_device *dev, void *private)
{
	dev->hm_private = private;
}

const char *tcmu_dev_get_uio_name(struct tcmu_device *dev)
{
	return dev->dev_name;
}

void tcmu_set_thread_name(const char *prefix, struct tcmu_device *dev)
{
	const char *uio = dev ? tcmu_dev_get_uio_name(dev) : NULL;
	char cname[TCMU_THREAD_NAME_LEN];
	char *pname;

	if (pthread_getname_np(pthread_self(), cname, TCMU_THREAD_NAME_LEN))
		return;

	/*
	 * If we are trying to set the pthread name in the
	 * event work thread, we must ignore it.
	 */
	if (!strcmp(cname, "ework-thread")) {
		tcmu_dev_warn(dev, "Do not set name for event work thread in the callback fn\n");
		return;
	}

	if (!prefix) {
		tcmu_dev_err(dev, "Failed to set name for thread %lu\n",
			     pthread_self());
		return;
	}

	if (asprintf(&pname, "%s%s%s", prefix, uio ? "-" : "",  uio ? uio : "") == -1) {
		tcmu_dev_err(dev, "Could not allocate thread name.\n");
		return;
	}

	if (strlen(pname) >= TCMU_THREAD_NAME_LEN) {
		tcmu_dev_warn(dev, "Cannot set thread name to %s. Name must be less than %d chars. ",
			      pname, TCMU_THREAD_NAME_LEN);
		pname[TCMU_THREAD_NAME_LEN - 1] = '\0';
		tcmu_dev_warn(dev, "Truncating to %s.\n", pname);
	}

	if (pthread_setname_np(pthread_self(), pname))
		tcmu_dev_err(dev, "Could not set thread name to %s\n", pname);
	free(pname);
}

void tcmu_dev_set_num_lbas(struct tcmu_device *dev, uint64_t num_lbas)
{
	dev->num_lbas = num_lbas;
}

uint64_t tcmu_dev_get_num_lbas(struct tcmu_device *dev)
{
	return dev->num_lbas;
}

uint64_t tcmu_lba_to_byte(struct tcmu_device *dev, uint64_t lba)
{
	return lba << dev->block_size_shift;
}

uint64_t tcmu_byte_to_lba(struct tcmu_device *dev, uint64_t byte)
{
	return byte >> dev->block_size_shift;
}

uint64_t tcmu_cdb_to_byte(struct tcmu_device *dev, uint8_t *cdb)
{
	return tcmu_lba_to_byte(dev, tcmu_cdb_get_lba(cdb));
}

void tcmu_dev_set_block_size(struct tcmu_device *dev, uint32_t block_size)
{
	dev->block_size = block_size;
	dev->block_size_shift = ffs(block_size) - 1;
}

uint32_t tcmu_dev_get_block_size(struct tcmu_device *dev)
{
	return dev->block_size;
}

/**
 * tcmu_dev_set_max_xfer_len - set device's max command size
 * @dev: tcmu device
 * @len: max transfer length in block_size sectors
 */
void tcmu_dev_set_max_xfer_len(struct tcmu_device *dev, uint32_t len)
{
	dev->max_xfer_len = len;
}

uint32_t tcmu_dev_get_max_xfer_len(struct tcmu_device *dev)
{
	return dev->max_xfer_len;
}

/**
 * tcmu_dev_set_opt_xcopy_rw_len - set device's emulated xcopy chunk len
 * @dev: tcmu device
 * @len: optimal RW len, in block_size sectors, for emulate xcopy operations
 */
void tcmu_dev_set_opt_xcopy_rw_len(struct tcmu_device *dev, uint32_t len)
{
	dev->opt_xcopy_rw_len = len;
}

uint32_t tcmu_dev_get_opt_xcopy_rw_len(struct tcmu_device *dev)
{
	return dev->opt_xcopy_rw_len;
}

/**
 * tcmu_dev_set/get_opt_unmap_gran - set/get device's optimal unmap granularity
 * @dev: tcmu device
 * @len: optimal unmap granularity length in block_size sectors
 * @split: true if handler needs unmaps larger then len to be split for it.
 */
void tcmu_dev_set_opt_unmap_gran(struct tcmu_device *dev, uint32_t len,
				 bool split)
{
	dev->split_unmaps = split;
	dev->opt_unmap_gran = len;
}

uint32_t tcmu_dev_get_opt_unmap_gran(struct tcmu_device *dev)
{
	return dev->opt_unmap_gran;
}

/**
 * tcmu_dev_set/get_max_unmap_len - set/get device's man unmap len
 * @dev: tcmu device
 * @len: max unmap len in block_size sectors
 */
void tcmu_dev_set_max_unmap_len(struct tcmu_device *dev, uint32_t len)
{
	dev->max_unmap_len = len;
}

uint32_t tcmu_dev_get_max_unmap_len(struct tcmu_device *dev)
{
	return dev->max_unmap_len;
}

/**
 * tcmu_dev_set/get_unmap_gran_align - set/get device's unmap granularity alignment
 * @dev: tcmu device
 * @len: unmap granularity alignment length in block_size sectors
 */
void tcmu_dev_set_unmap_gran_align(struct tcmu_device *dev, uint32_t len)
{
	dev->unmap_gran_align = len;
}

uint32_t tcmu_dev_get_unmap_gran_align(struct tcmu_device *dev)
{
	return dev->unmap_gran_align;
}

void tcmu_dev_set_write_cache_enabled(struct tcmu_device *dev, bool enabled)
{
	dev->write_cache_enabled = enabled;
}

bool tcmu_dev_get_write_cache_enabled(struct tcmu_device *dev)
{
	return dev->write_cache_enabled;
}

void tcmu_dev_set_solid_state_media(struct tcmu_device *dev, bool solid_state)
{
	dev->solid_state_media = solid_state;
}

bool tcmu_dev_get_solid_state_media(struct tcmu_device *dev)
{
	return dev->solid_state_media;
}

void tcmu_dev_set_unmap_enabled(struct tcmu_device *dev, bool enabled)
{
	dev->unmap_enabled = enabled;
}

bool tcmu_dev_get_unmap_enabled(struct tcmu_device *dev)
{
	return dev->unmap_enabled;
}

int tcmu_dev_get_fd(struct tcmu_device *dev)
{
	return dev->fd;
}

/**
 * tcmu_dev_get_memory_info - retrieve information about tcmu's shared
 * memory block.  If the memory is mapped to another address (e.g., in
 * another process) then the information in struct iovec can be converted
 * using these values.  Returns the filename of the mmap() that fd uses, or
 * NULL if information is not available.  The caller must free() the
 * returned filename.
 * @dev: tcmu device
 * @base: receives the base address of the shared memory region
 * @len: receives the size of the shared memory region
 * @offset: receives the offset within fd for mmap() (conventionally 0).
 */
char *
tcmu_dev_get_memory_info(struct tcmu_device *dev, void **base,
			 size_t *len, off_t *offset)
{
	char *mmap_name;
	const char *namefmt = "/dev/%s";
	const char *sizefmt = "/sys/class/uio/%s/maps/map0/size";

	if (asprintf(&mmap_name, namefmt, dev->dev_name) == -1) {
		tcmu_err("cannot construct device map filename\n");
		goto err_fail;
	}
	if (base)
		*base = dev->map;
	if (len) {
		if (dev->map_len != 0) {
			/* cached */
			*len = dev->map_len;
		} else {
			/* get length of map from file */
			ssize_t size;
			char *size_name;
			
			if (asprintf(&size_name, sizefmt, dev->dev_name) == -1) {
				tcmu_err("cannot construct device map size filename\n");
				goto err_free;
			}
			size = read_size(size_name);
			free(size_name);
			if (size == -1) {
				tcmu_err("unable to read device map0 size\n");
				goto err_free;
			}
			*len = size;
		}
	}
	if (offset)
		*offset = 0;
	return mmap_name;

err_free:
	free(mmap_name);
err_fail:
	return NULL;
}

char *tcmu_dev_get_cfgstring(struct tcmu_device *dev)
{
	return dev->cfgstring;
}

struct tcmulib_handler *tcmu_dev_get_handler(struct tcmu_device *dev)
{
	return dev->handler;
}

static inline struct tcmu_cmd_entry *
device_cmd_head(struct tcmu_device *dev)
{
	struct tcmu_mailbox *mb = dev->map;

	return (struct tcmu_cmd_entry *) ((char *) mb + mb->cmdr_off + mb->cmd_head);
}

static inline struct tcmu_cmd_entry *
device_cmd_tail(struct tcmu_device *dev)
{
	struct tcmu_mailbox *mb = dev->map;

	return (struct tcmu_cmd_entry *) ((char *) mb + mb->cmdr_off + dev->cmd_tail);
}

/* update the tcmu_device's tail */
#define TCMU_UPDATE_DEV_TAIL(dev, mb, ent) \
do { \
	dev->cmd_tail = (dev->cmd_tail + tcmu_hdr_get_len((ent)->hdr.len_op)) % mb->cmdr_size; \
} while (0)

struct tcmulib_cmd *tcmulib_get_next_command(struct tcmu_device *dev,
					     int hm_cmd_size)
{
	struct tcmu_mailbox *mb = dev->map;
	struct tcmu_cmd_entry *ent;

	while ((ent = device_cmd_tail(dev)) != device_cmd_head(dev)) {

		switch (tcmu_hdr_get_op(ent->hdr.len_op)) {
		case TCMU_OP_PAD:
			/* do nothing */
			break;
		case TCMU_OP_CMD: {
			int i;
			struct tcmulib_cmd *cmd;
			uint8_t *cdb = (uint8_t *) mb + ent->req.cdb_off;
			int cdb_len = tcmu_cdb_get_length(cdb);

			if (cdb_len < 0) {
				/*
				 * This should never happen so just drop cmd
				 * for now instead of adding a lock in the
				 * main IO path.
				 */
				break;
			}

			/* Alloc memory for cmd itself, iovec and cdb */
			cmd = malloc(sizeof(*cmd) + hm_cmd_size + cdb_len +
				     sizeof(*cmd->iovec) * ent->req.iov_cnt);
			if (!cmd)
				return NULL;
			cmd->cmd_id = ent->hdr.cmd_id;

			/* Convert iovec addrs in-place to not be offsets */
			cmd->iov_cnt = ent->req.iov_cnt;
			cmd->iovec = (struct iovec *) (cmd + 1);
			for (i = 0; i < ent->req.iov_cnt; i++) {
				cmd->iovec[i].iov_base = (void *) mb +
					(size_t) ent->req.iov[i].iov_base;
				cmd->iovec[i].iov_len = ent->req.iov[i].iov_len;
			}

			/* Copy cdb that currently points to the command ring */
			cmd->cdb = (uint8_t *) (cmd->iovec + cmd->iov_cnt);
			memcpy(cmd->cdb, (void *) mb + ent->req.cdb_off, cdb_len);

			/* Setup handler memory area after iovecs and cdb */
			if (hm_cmd_size)
				cmd->hm_private = cmd->cdb + cdb_len;

			TCMU_UPDATE_DEV_TAIL(dev, mb, ent);
			return cmd;
		}
		default:
			/* We don't even know how to handle this TCMU opcode. */
			ent->hdr.uflags |= TCMU_UFLAG_UNKNOWN_OP;
		}

		TCMU_UPDATE_DEV_TAIL(dev, mb, ent);
	}

	return NULL;
}

static int tcmu_sts_to_scsi(int tcmu_sts, uint8_t *sense)
{
	switch (tcmu_sts) {
	case TCMU_STS_OK:
		return SAM_STAT_GOOD;
	case TCMU_STS_NO_RESOURCE:
		return SAM_STAT_TASK_SET_FULL;
	/*
	 * We drop the session during timeout handling so force
	 * a retry to have it handled during session level recovery.
	 */
	case TCMU_STS_TIMEOUT:
	case TCMU_STS_BUSY:
		return SAM_STAT_BUSY;
	case TCMU_STS_PASSTHROUGH_ERR:
		break;
	/* Check Conditions below */
	case TCMU_STS_RANGE:
		/* LBA out of range */
		tcmu_sense_set_data(sense, ILLEGAL_REQUEST, 0x2100);
		break;
	case TCMU_STS_HW_ERR:
		/* Internal target failure */
		tcmu_sense_set_data(sense, HARDWARE_ERROR, 0x4400);
		break;
	case TCMU_STS_MISCOMPARE:
		/* Miscompare during verify operation */
		__tcmu_sense_set_data(sense, MISCOMPARE, 0x1d00);
		break;
	case TCMU_STS_RD_ERR:
		/* Read medium error */
		tcmu_sense_set_data(sense, MEDIUM_ERROR, 0x1100);
		break;
	case TCMU_STS_WR_ERR:
		/* Write medium error */
		tcmu_sense_set_data(sense, MEDIUM_ERROR, 0x0C00);
		break;
	case TCMU_STS_INVALID_CDB:
		/* Invalid field in CDB */
		tcmu_sense_set_data(sense, ILLEGAL_REQUEST, 0x2400);
		break;
	case TCMU_STS_INVALID_PARAM_LIST:
		/* Invalid field in parameter list */
		tcmu_sense_set_data(sense, ILLEGAL_REQUEST, 0x2600);
		break;
	case TCMU_STS_INVALID_PARAM_LIST_LEN:
		/* Invalid list parameter list length */
		tcmu_sense_set_data(sense, ILLEGAL_REQUEST, 0x1a00);
		break;
	case TCMU_STS_NOTSUPP_SEG_DESC_TYPE:
		/* Unsupported segment descriptor type code */
		tcmu_sense_set_data(sense, ILLEGAL_REQUEST, 0x2609);
		break;
	case TCMU_STS_NOTSUPP_TGT_DESC_TYPE:
		/* Unsupported target descriptor type code */
		tcmu_sense_set_data(sense, ILLEGAL_REQUEST, 0x2607);
		break;
	case TCMU_STS_TOO_MANY_SEG_DESC:
		/* The number of segment descriptors exceeds the allowed number */
		tcmu_sense_set_data(sense, ILLEGAL_REQUEST, 0x2608);
		break;
	case TCMU_STS_TOO_MANY_TGT_DESC:
		/* The number of CSCD descriptors exceeds the allowed number */
		tcmu_sense_set_data(sense, ILLEGAL_REQUEST, 0x2606);
		break;
	case TCMU_STS_CP_TGT_DEV_NOTCONN:
		/* Copy target device not reachable */
		tcmu_sense_set_data(sense, COPY_ABORTED, 0x0D02);
		break;
	case TCMU_STS_INVALID_CP_TGT_DEV_TYPE:
		/* Invalid copy target device type */
		tcmu_sense_set_data(sense, COPY_ABORTED, 0x0D03);
		break;
	case TCMU_STS_CAPACITY_CHANGED:
		/* Device capacity has changed */
		tcmu_sense_set_data(sense, UNIT_ATTENTION, 0x2A09);
		break;
	case TCMU_STS_TRANSITION:
		/* ALUA state transition */
		tcmu_sense_set_data(sense, NOT_READY, 0x040A);
		break;
	case TCMU_STS_IMPL_TRANSITION_ERR:
		/* Implicit ALUA state transition failed */
		tcmu_sense_set_data(sense, UNIT_ATTENTION, 0x2A07);
		break;
	case TCMU_STS_EXPL_TRANSITION_ERR:
		/* STPG failed */
		tcmu_sense_set_data(sense, HARDWARE_ERROR, 0x670A);
		break;
	case TCMU_STS_FENCED:
		/* ALUA state in standby */
		tcmu_sense_set_data(sense, NOT_READY, 0x040B);
		break;
	case TCMU_STS_WR_ERR_INCOMPAT_FRMT:
		/* Can't write - incompatible format */
		tcmu_sense_set_data(sense, ILLEGAL_REQUEST, 0x3005);
		break;
	case TCMU_STS_NOTSUPP_SAVE_PARAMS:
		/* Saving params not supported */
		tcmu_sense_set_data(sense, ILLEGAL_REQUEST, 0x3900);
		break;
	case TCMU_STS_FRMT_IN_PROGRESS:
		/* Format in progress */
		__tcmu_sense_set_data(sense, NOT_READY, 0x0404);
		break;
	case TCMU_STS_NOT_HANDLED:
	case TCMU_STS_INVALID_CMD:
		/* Invalid op code */
		tcmu_sense_set_data(sense, ILLEGAL_REQUEST, 0x2000);
		break;
	default:
		tcmu_err("Invalid tcmu status code %d\n", tcmu_sts);
		/* Fall through. Kernel will translate to LUN comm failure */
	}

	return SAM_STAT_CHECK_CONDITION;
}

/* update the ring buffer's tail */
#define TCMU_UPDATE_RB_TAIL(mb, ent) \
do { \
	mb->cmd_tail = (mb->cmd_tail + tcmu_hdr_get_len((ent)->hdr.len_op)) % mb->cmdr_size; \
} while (0)

void tcmulib_command_complete(
	struct tcmu_device *dev,
	struct tcmulib_cmd *cmd,
	int result)
{
	struct tcmu_mailbox *mb = dev->map;
	struct tcmu_cmd_entry *ent = (void *) mb + mb->cmdr_off + mb->cmd_tail;

	/* current command could be PAD in async case */
	while (ent != (void *) mb + mb->cmdr_off + mb->cmd_head) {
		if (tcmu_hdr_get_op(ent->hdr.len_op) == TCMU_OP_CMD)
			break;
		TCMU_UPDATE_RB_TAIL(mb, ent);
		ent = (void *) mb + mb->cmdr_off + mb->cmd_tail;
	}

	/* cmd_id could be different in async case */
	if (cmd->cmd_id != ent->hdr.cmd_id) {
		ent->hdr.cmd_id = cmd->cmd_id;
	}

	ent->rsp.scsi_status = tcmu_sts_to_scsi(result, cmd->sense_buf);
	if (ent->rsp.scsi_status == SAM_STAT_CHECK_CONDITION) {
		memcpy(ent->rsp.sense_buffer, cmd->sense_buf,
		       TCMU_SENSE_BUFFERSIZE);
	}

	TCMU_UPDATE_RB_TAIL(mb, ent);
	free(cmd);
}

void tcmulib_processing_start(struct tcmu_device *dev)
{
	int r;
	uint32_t buf;

	/* Clear the event on the fd */
	do {
		r = read(dev->fd, &buf, 4);
	} while (r == -1 && errno == EINTR);
	if (r == -1 && errno != EAGAIN)
		tcmu_err("failed to read device /dev/%s, %d\n",
			 dev->dev_name, errno);
}

void tcmulib_processing_complete(struct tcmu_device *dev)
{
	int r;
	uint32_t buf = 0;

	/* Tell the kernel there are completed commands */
	do {
		r = write(dev->fd, &buf, 4);
	} while (r == -1 && errno == EINTR);
	if (r == -1 && errno != EAGAIN)
		tcmu_err("failed to write device /dev/%s, %d\n",
			 dev->dev_name, errno);
}
