/*
 * Copyright (c) 2015 Red Hat, Inc.
 *
 * 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 <errno.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <inttypes.h>
#include <scsi/scsi.h>
#include <pthread.h>
#include <glusterfs/api/glfs.h>
#include "darray.h"

#include "tcmu-runner.h"
#include "libtcmu.h"
#include "tcmur_device.h"
#include "tcmur_cmd_handler.h"
#include "version.h"

#define ALLOWED_BSOFLAGS (O_DIRECT | O_RDWR | O_LARGEFILE)

#define GLUSTER_PORT "24007"
#define TCMU_GLFS_LOG_FILENAME "tcmu-runner-glfs.log"  /* MAX 32 CHAR */
#define TCMU_GLFS_DEBUG_LEVEL  7

/* cache protection */
pthread_mutex_t glfs_lock;

#if GFAPI_VERSION766
#define GF_ENFORCE_MANDATORY_LOCK "trusted.glusterfs.enforce-mandatory-lock"
#endif

typedef enum gluster_transport {
	GLUSTER_TRANSPORT_TCP,
	GLUSTER_TRANSPORT_UNIX,
	GLUSTER_TRANSPORT_RDMA,
	GLUSTER_TRANSPORT__MAX,
} gluster_transport;

typedef struct unix_sockaddr {
	char *socket;
} unix_sockaddr;

typedef struct inet_sockaddr {
	char *addr;
	char *port;
} inet_sockaddr;

typedef struct gluster_hostdef {
	gluster_transport type;
	union { /* union tag is @type */
		unix_sockaddr uds;
		inet_sockaddr inet;
	} u;
} gluster_hostdef;

typedef struct gluster_server {
	char *volname;     /* volume name*/
	char *path;        /* path of file in the volume */
	gluster_hostdef *server; /* gluster server definition */
} gluster_server;

struct glfs_state {
	glfs_t *fs;
	glfs_fd_t *gfd;
	gluster_server *hosts;
	bool no_fencing;

	/*
	 * Current tcmu helper API reports WCE=1, but doesn't
	 * implement inquiry VPD 0xb2, so clients will not know UNMAP
	 * or WRITE_SAME are supported. TODO: fix this
	 */
};

typedef struct glfs_cbk_cookie {
	struct tcmu_device *dev;
	struct tcmur_cmd *tcmur_cmd;
	size_t length;
	enum {
		TCMU_GLFS_READ  = 1,
		TCMU_GLFS_WRITE = 2,
		TCMU_GLFS_FLUSH = 3,
		TCMU_GLFS_DISCARD = 4,
		TCMU_GLFS_WRITESAME = 5
	} op;
} glfs_cbk_cookie;

struct gluster_cacheconn {
	char *volname;
	gluster_hostdef *server;
	glfs_t *fs;
	darray(char *) cfgstring;
} gluster_cacheconn;

static darray(struct gluster_cacheconn *) glfs_cache;

const char *const gluster_transport_lookup[] = {
	[GLUSTER_TRANSPORT_TCP] = "tcp",
	[GLUSTER_TRANSPORT_UNIX] = "unix",
	[GLUSTER_TRANSPORT_RDMA] = "rdma",
	[GLUSTER_TRANSPORT__MAX] = NULL,
};

static void gluster_free_host(gluster_hostdef *host)
{
	if(!host)
		return;

	switch (host->type) {
	case GLUSTER_TRANSPORT_UNIX:
		free(host->u.uds.socket);
		break;
	case GLUSTER_TRANSPORT_TCP:
	case GLUSTER_TRANSPORT_RDMA:
		free(host->u.inet.addr);
		free(host->u.inet.port);
		break;
	case GLUSTER_TRANSPORT__MAX:
		break;
	}
}

static bool
gluster_compare_hosts(gluster_hostdef *src_server, gluster_hostdef *dst_server)
{
	if (src_server->type != dst_server->type)
		return false;

	switch (src_server->type) {
	case GLUSTER_TRANSPORT_UNIX:
		if (!strcmp(src_server->u.uds.socket, dst_server->u.uds.socket))
			return true;
		break;
	case GLUSTER_TRANSPORT_TCP:
	case GLUSTER_TRANSPORT_RDMA:
		if (!strcmp(src_server->u.inet.addr, dst_server->u.inet.addr)
				&&
			!strcmp(src_server->u.inet.port, dst_server->u.inet.port))
			return true;
		break;
	case GLUSTER_TRANSPORT__MAX:
		break;
	}

	return false;
}

static int gluster_cache_add(gluster_server *dst, glfs_t *fs, char* cfgstring)
{
	struct gluster_cacheconn *entry;
	char* cfg_copy = NULL;

	entry = calloc(1, sizeof(gluster_cacheconn));
	if (!entry)
		goto error;

	entry->volname = strdup(dst->volname);

	entry->server = calloc(1, sizeof(gluster_hostdef));
	if (!entry->server)
		goto free_entry;

	entry->server->type = dst->server->type;

	if (entry->server->type == GLUSTER_TRANSPORT_UNIX) {
		entry->server->u.uds.socket = strdup(dst->server->u.uds.socket);
	} else {
		entry->server->u.inet.addr = strdup(dst->server->u.inet.addr);
		entry->server->u.inet.port = strdup(dst->server->u.inet.port);
	}

	entry->fs = fs;

	cfg_copy = strdup(cfgstring);
	darray_init(entry->cfgstring);
	darray_append(entry->cfgstring, cfg_copy);

	darray_append(glfs_cache, entry);

	return 0;

free_entry:
	if (entry->volname)
		free(entry->volname);
	free(entry);
error:
	return -1;
}

static bool tcmu_glfs_update_logdir(void)
{
	struct gluster_cacheconn **entry;
	char logfilepath[PATH_MAX];
	int ret;

	darray_foreach(entry, glfs_cache) {
		ret = tcmu_make_absolute_logfile(logfilepath, TCMU_GLFS_LOG_FILENAME);
		if (ret < 0) {
			tcmu_err("tcmu_make_absolute_logfile failed: %d\n", ret);
			return false;
		}

		if (glfs_set_logging((*entry)->fs, logfilepath, TCMU_GLFS_DEBUG_LEVEL)) {
			tcmu_err("glfs_set_logging() on %s failed[%s]",
				 (*entry)->volname, strerror(errno));
			return false;
		}
	}

	return true;
}

static glfs_t* gluster_cache_query(gluster_server *dst, char *cfgstring)
{
	struct gluster_cacheconn **entry;
	char** config;
	char* cfg_copy = NULL;
	bool cfgmatch = false;

	darray_foreach(entry, glfs_cache) {
		if (strcmp((*entry)->volname, dst->volname))
			continue;
		if (gluster_compare_hosts((*entry)->server, dst->server)) {

			darray_foreach(config, (*entry)->cfgstring) {
				if (!strcmp(*config, cfgstring)) {
					cfgmatch = true;
					break;
				}
			}
			if (!cfgmatch) {
				cfg_copy = strdup(cfgstring);
				darray_append((*entry)->cfgstring, cfg_copy);
			}
			return (*entry)->fs;
		}
	}

	return NULL;
}

static void gluster_cache_refresh(glfs_t *fs, const char *cfgstring)
{
	struct gluster_cacheconn **entry;
	char** config;
	size_t i = 0;
	size_t j = 0;

	if (!fs)
		return;

	darray_foreach(entry, glfs_cache) {
		if ((*entry)->fs == fs) {
			if (cfgstring) {
				darray_foreach(config, (*entry)->cfgstring) {
					if (!strcmp(*config, cfgstring)) {
						free(*config);
						darray_remove((*entry)->cfgstring, j);
						break;
					}
					j++;
				}
			}

			if (darray_size((*entry)->cfgstring))
				return;

			free((*entry)->volname);
			glfs_fini((*entry)->fs);
			(*entry)->fs = NULL;
			gluster_free_host((*entry)->server);
			free((*entry)->server);
			(*entry)->server = NULL;
			free((*entry));

			darray_remove(glfs_cache, i);
			return;
		} else {
			i++;
		}
	}
}

static void gluster_thread_cleanup(void *arg)
{
	pthread_mutex_unlock(arg);
}

static int gluster_cache_query_or_add(struct tcmu_device *dev,
                                      glfs_t **fs, gluster_server *entry,
                                      char *config, bool *init)
{
	int ret = -1;

	pthread_cleanup_push(gluster_thread_cleanup, &glfs_lock);
	pthread_mutex_lock(&glfs_lock);

	*fs = gluster_cache_query(entry, config);
	if (*fs) {
		*init = false;
		ret = 0;
		goto out;
	}

	*fs = glfs_new(entry->volname);
	if (!*fs) {
		tcmu_dev_err(dev, "glfs_new(vol=%s) failed: %m\n", entry->volname);
		goto out;
	}

	ret = gluster_cache_add(entry, *fs, config);
	if (ret) {
		tcmu_dev_err(dev, "gluster_cache_add(vol=%s, config=%s) failed: %m\n",
		             entry->volname, config);
		glfs_fini(*fs);
		*fs = NULL;
		goto out;
	}

out:
	pthread_mutex_unlock(&glfs_lock);
	pthread_cleanup_pop(0);

	return ret;
}

static void gluster_free_server(gluster_server **hosts)
{
	if (!*hosts)
		return;
	free((*hosts)->volname);
	free((*hosts)->path);

	gluster_free_host((*hosts)->server);
	free((*hosts)->server);
	(*hosts)->server = NULL;
	free(*hosts);
	*hosts = NULL;
}

/*
 * Break image string into server, volume, and path components.
 * Returns -1 on failure.
 */
static int parse_imagepath(char *cfgstring, gluster_server **hosts)
{
	gluster_server *entry = NULL;
	char *origp = strdup(cfgstring);
	char *p, *sep;

	if (!origp)
		goto fail;

	/* part before '@' is the volume name */
	p = origp;
	sep = strchr(p, '@');
	if (!sep)
		goto fail;

	*hosts = calloc(1, sizeof(gluster_server));
	if (!hosts)
		goto fail;
	entry = *hosts;

	entry->server = calloc(1, sizeof(gluster_hostdef));
	if (!entry->server)
		goto fail;

	*sep = '\0';
	entry->volname = strdup(p);
	if (!entry->volname)
		goto fail;

	/* part between '@' and 1st '/' is the server name */
	p = sep + 1;
	sep = strchr(p, '/');
	if (!sep)
		goto fail;

	*sep = '\0';
	entry->server->type = GLUSTER_TRANSPORT_TCP; /* FIXME: Get type dynamically */
	entry->server->u.inet.addr = strdup(p);
	if (!entry->server->u.inet.addr)
		goto fail;
	entry->server->u.inet.port = strdup(GLUSTER_PORT); /* FIXME: Get port dynamically */

	/* The rest is the path name */
	p = sep + 1;
	entry->path = strdup(p);
	if (!entry->path)
		goto fail;

	if (entry->server->type == GLUSTER_TRANSPORT_UNIX) {
		if (!strlen(entry->server->u.uds.socket) ||
		    !strlen(entry->volname) || !strlen(entry->path))
			goto fail;
	} else {
		if (!strlen(entry->server->u.inet.addr) ||
		    !strlen(entry->volname) || !strlen(entry->path))
			goto fail;
	}

	free(origp);

	return 0;

fail:
	gluster_free_server(hosts);
	free(origp);

	return -1;
}

static glfs_t* tcmu_create_glfs_object(struct tcmu_device *dev,
                                       char *config, gluster_server **hosts)
{
	gluster_server *entry = NULL;
	char logfilepath[PATH_MAX];
	glfs_t *fs =  NULL;
	int ret = -1;
	bool init = true;

	if (parse_imagepath(config, hosts) == -1) {
		tcmu_dev_err(dev, "hostaddr, volname, or path missing in %s\n",
		             config);
		goto fail;
	}
	entry = *hosts;

	ret = gluster_cache_query_or_add(dev, &fs, entry, config, &init);
	if (ret) {
		tcmu_dev_err(dev, "gluster_cache_query_or_add(vol=%s, config=%s) failed\n",
		             entry->volname, config);
		goto fail;
	}

	if (!init)
		return fs;

	ret = glfs_set_volfile_server(fs,
				gluster_transport_lookup[entry->server->type],
				entry->server->u.inet.addr,
				atoi(entry->server->u.inet.port));
	if (ret) {
		tcmu_dev_err(dev, "glfs_set_volfile_server(vol=%s, addr=%s) failed: %m\n",
		             entry->volname, entry->server->u.inet.addr);
		goto unref;
	}

	ret = tcmu_make_absolute_logfile(logfilepath, TCMU_GLFS_LOG_FILENAME);
	if (ret < 0) {
		tcmu_dev_err(dev, "tcmu_make_absolute_logfile failed: %d\n", ret);
		goto unref;
	}

	ret = glfs_set_logging(fs, logfilepath, TCMU_GLFS_DEBUG_LEVEL);
	if (ret < 0) {
		tcmu_dev_err(dev, "glfs_set_logging(vol=%s, path=%s) failed: %m\n",
		             entry->volname, logfilepath);
		goto unref;
	}

	ret = glfs_init(fs);
	if (ret) {
		tcmu_dev_err(dev, "glfs_init(vol=%s) failed: %m\n", entry->volname);
		goto unref;
	}

	return fs;

unref:
	gluster_cache_refresh(fs, config);

fail:
	gluster_free_server(hosts);
	return NULL;
}

static char* tcmu_get_path( struct tcmu_device *dev)
{
	char *config;

	config = strchr(tcmu_dev_get_cfgstring(dev), '/');
	if (!config) {
		tcmu_dev_err(dev, "no configuration found in cfgstring\n");
		return NULL;
	}
	config += 1; /* get past '/' */

	return config;
}

static int tcmu_glfs_open(struct tcmu_device *dev, bool reopen)
{
	struct glfs_state *gfsp;
	char *config;
	struct stat st;
	int ret = -EIO;
	long long dev_size;
	uint32_t block_size = tcmu_dev_get_block_size(dev);

	gfsp = calloc(1, sizeof(*gfsp));
	if (!gfsp)
		return -ENOMEM;

	tcmur_dev_set_private(dev, gfsp);
	tcmu_dev_set_write_cache_enabled(dev, 1);

	config = tcmu_get_path(dev);
	if (!config)
		goto fail;

	gfsp->fs = tcmu_create_glfs_object(dev, config, &gfsp->hosts);
	if (!gfsp->fs) {
		tcmu_dev_err(dev, "tcmu_create_glfs_object(config=%s) failed\n", config);
		goto fail;
	}

	gfsp->gfd = glfs_open(gfsp->fs, gfsp->hosts->path, ALLOWED_BSOFLAGS);
	if (!gfsp->gfd) {
		tcmu_dev_err(dev, "glfs_open(vol=%s, file=%s) failed: %m\n",
		             gfsp->hosts->volname, gfsp->hosts->path);
		goto unref;
	}

#if GFAPI_VERSION766
	ret = glfs_fsetxattr(gfsp->gfd, GF_ENFORCE_MANDATORY_LOCK, "set", 4, 0);
	if (ret) {
		if (errno == EINVAL) {
			gfsp->no_fencing = true;
		} else {
			tcmu_dev_err(dev,"glfs_fsetxattr failed: %m\n");
			goto close;
		}
	}
#endif

	ret = glfs_lstat(gfsp->fs, gfsp->hosts->path, &st);
	if (ret) {
		tcmu_dev_warn(dev, "glfs_lstat failed: %m\n");
		goto close;
	}

	dev_size = tcmu_dev_get_num_lbas(dev) * block_size;
	if (st.st_size != dev_size) {
		/*
		 * The glfs allows the backend file size not to align
		 * to the block_size. But the dev_size here in tcmu-runner
		 * will round down and align it to the block_size.
		 */
		if (round_down(st.st_size, block_size) == dev_size)
			goto out;

		if (reopen)
			goto out;

		tcmu_dev_warn(dev,
			      "device size (%lld) and backing file size (%lld) not matching, updating it to kernel\n",
			      (long long)dev_size, (long long) st.st_size);

		/* Update the device size in kernel. */
		ret = tcmur_dev_update_size(dev, st.st_size);
		if (ret)
			goto close;

		tcmu_dev_info(dev, "loaded with size (%lld)\n", (long long) st.st_size);
	}

out:
	return 0;
close:
	glfs_close(gfsp->gfd);
unref:
	gluster_cache_refresh(gfsp->fs, tcmu_get_path(dev));
	gluster_free_server(&gfsp->hosts);
fail:
	free(gfsp);

	return ret;
}

static void tcmu_glfs_close(struct tcmu_device *dev)
{
	struct glfs_state *gfsp = tcmur_dev_get_private(dev);

	glfs_close(gfsp->gfd);
	gluster_cache_refresh(gfsp->fs, tcmu_get_path(dev));
	gluster_free_server(&gfsp->hosts);
	free(gfsp);
}

#if GFAPI_VERSION760
static void glfs_async_cbk(glfs_fd_t *fd, ssize_t ret,
			   struct glfs_stat *prestat,
			   struct glfs_stat *poststat,
			   void *data)
#else
static void glfs_async_cbk(glfs_fd_t *fd, ssize_t ret, void *data)
#endif
{
	glfs_cbk_cookie *cookie = data;
	struct tcmu_device *dev = cookie->dev;
	struct tcmur_cmd *tcmur_cmd = cookie->tcmur_cmd;
#if GFAPI_VERSION766
	struct glfs_state *gfsp = tcmur_dev_get_private(dev);
#endif
	size_t length = cookie->length;
	int err = -errno;

	if (ret < 0) {
		switch (err) {
		case -ETIMEDOUT:
			/*
			 * TIMEDOUT is a scenario where the fop can
			 * not reach the server for 30 minutes.
			 */
			tcmu_dev_err(dev, "Timing out cmd after 30 minutes.\n");

			tcmu_notify_cmd_timed_out(dev);
			ret = TCMU_STS_TIMEOUT;
			break;
#if GFAPI_VERSION766
		case -EAGAIN:
		case -EBUSY:
		case -ENOTCONN:
			/*
			 * The lock maybe preemted and then any IO to
			 * land on the file without a lock will be
			 * rejected with EBUSY.
			 *
			 * And if local is disconnected, we should get
			 * ENOTCONN for further requests. But if the
			 * connection is reestablished and at the same
			 * time another client has taken the lock we
			 * will get EBUSY too.
			 */
			if (!gfsp->no_fencing) {
				tcmu_dev_dbg(dev, "failed with errno %d.\n", err);
				tcmu_notify_lock_lost(dev);
				ret = TCMU_STS_BUSY;
				break;
			}
#endif
		default:
			tcmu_dev_dbg(dev, "failed with errno %d.\n", err);
			ret = TCMU_STS_HW_ERR;
		}
	} else if (ret != length) {
		tcmu_dev_dbg(dev, "ret(%zu) != length(%zu).\n", ret, length);

		/* Read/write/flush failed */
		switch (cookie->op) {
		case TCMU_GLFS_READ:
			/* ENOENT for READ operation means EOF,
			 * see glusterfs commit 9fe5c6d3
			 */
			if (err == -ENOENT) {
				ret = TCMU_STS_OK;
				break;
			}
			ret = TCMU_STS_RD_ERR;
			break;
		case TCMU_GLFS_WRITE:
		case TCMU_GLFS_FLUSH:
		case TCMU_GLFS_DISCARD:
		case TCMU_GLFS_WRITESAME:
			ret = TCMU_STS_WR_ERR;
			break;
		}
	} else {
		ret = TCMU_STS_OK;
	}

	tcmur_cmd_complete(dev, tcmur_cmd, ret);
	free(cookie);
}

static int tcmu_glfs_read(struct tcmu_device *dev,
                          struct tcmur_cmd *tcmur_cmd,
                          struct iovec *iov, size_t iov_cnt,
                          size_t length, off_t offset)
{
	struct glfs_state *state = tcmur_dev_get_private(dev);
	glfs_cbk_cookie *cookie;

	cookie = calloc(1, sizeof(*cookie));
	if (!cookie) {
		tcmu_dev_err(dev, "Could not allocate cookie: %m\n");
		goto out;
	}
	cookie->dev = dev;
	cookie->tcmur_cmd = tcmur_cmd;
	cookie->length = length;
	cookie->op = TCMU_GLFS_READ;

	if (glfs_preadv_async(state->gfd, iov, iov_cnt, offset, SEEK_SET,
	                      glfs_async_cbk, cookie) < 0) {
		tcmu_dev_err(dev, "glfs_preadv_async(vol=%s, file=%s) failed: %m\n",
		             state->hosts->volname, state->hosts->path);
		goto out;
	}

	return TCMU_STS_OK;

out:
	free(cookie);
	return TCMU_STS_NO_RESOURCE;
}

static int tcmu_glfs_write(struct tcmu_device *dev,
                           struct tcmur_cmd *tcmur_cmd,
                           struct iovec *iov, size_t iov_cnt,
                           size_t length, off_t offset)
{
	struct glfs_state *state = tcmur_dev_get_private(dev);
	glfs_cbk_cookie *cookie;

	cookie = calloc(1, sizeof(*cookie));
	if (!cookie) {
		tcmu_dev_err(dev, "Could not allocate cookie: %m\n");
		goto out;
	}
	cookie->dev = dev;
	cookie->tcmur_cmd = tcmur_cmd;
	cookie->length = length;
	cookie->op = TCMU_GLFS_WRITE;

	if (glfs_pwritev_async(state->gfd, iov, iov_cnt, offset,
	                       ALLOWED_BSOFLAGS, glfs_async_cbk, cookie) < 0) {
		tcmu_dev_err(dev, "glfs_pwritev_async(vol=%s, file=%s) failed: %m\n",
		             state->hosts->volname, state->hosts->path);
		goto out;
	}

	return TCMU_STS_OK;

out:
	free(cookie);
	return TCMU_STS_NO_RESOURCE;
}

static int tcmu_glfs_reconfig(struct tcmu_device *dev,
                              struct tcmulib_cfg_info *cfg)
{
	struct glfs_state *gfsp = tcmur_dev_get_private(dev);
	struct stat st;
	int ret = -EIO;

	switch (cfg->type) {
	case TCMULIB_CFG_DEV_SIZE:
		ret = glfs_lstat(gfsp->fs, gfsp->hosts->path, &st);
		if (ret) {
			tcmu_dev_warn(dev, "glfs_lstat failed: %m\n");
			tcmu_notify_conn_lost(dev);

			/* Let the targetcli command return success */
			ret = 0;
		} else if (st.st_size != cfg->data.dev_size) {
			/*
			 * Currently we cannot update the size to kernel here,
			 * because it will be overrided by kernel with the old
			 * value after it gets the genl reply.
			 */
			tcmu_dev_warn(dev,
				      "device size (%lld) and backing file size (%lld) not matching, and ignoring it\n",
				      (long long)cfg->data.dev_size, (long long) st.st_size);
			return -EINVAL;
		}
		return ret;
	case TCMULIB_CFG_DEV_CFGSTR:
	case TCMULIB_CFG_WRITE_CACHE:
	default:
		return -EOPNOTSUPP;
	}
}

static int tcmu_glfs_flush(struct tcmu_device *dev,
                           struct tcmur_cmd *tcmur_cmd)
{
	struct glfs_state *state = tcmur_dev_get_private(dev);
	glfs_cbk_cookie *cookie;

	cookie = calloc(1, sizeof(*cookie));
	if (!cookie) {
		tcmu_dev_err(dev, "Could not allocate cookie: %m\n");
		goto out;
	}
	cookie->dev = dev;
	cookie->tcmur_cmd = tcmur_cmd;
	cookie->length = 0;
	cookie->op = TCMU_GLFS_FLUSH;

	if (glfs_fdatasync_async(state->gfd, glfs_async_cbk, cookie) < 0) {
		tcmu_dev_err(dev, "glfs_fdatasync_async(vol=%s, file=%s) failed: %m\n",
		             state->hosts->volname, state->hosts->path);
		goto out;
	}

	return TCMU_STS_OK;

out:
	free(cookie);
	return TCMU_STS_NO_RESOURCE;
}

static int tcmu_glfs_discard(struct tcmu_device *dev,
			     struct tcmur_cmd *tcmur_cmd,
                             uint64_t offset, uint64_t length)
{
	struct glfs_state *state = tcmur_dev_get_private(dev);
	glfs_cbk_cookie *cookie;
	ssize_t ret;

	cookie = calloc(1, sizeof(*cookie));
	if (!cookie) {
		tcmu_dev_err(dev, "Could not allocate cookie: %m\n");
		goto out;
	}
	cookie->dev = dev;
	cookie->tcmur_cmd = tcmur_cmd;
	cookie->length = 0;
	cookie->op = TCMU_GLFS_DISCARD;

	ret = glfs_discard_async(state->gfd, offset, length, glfs_async_cbk, cookie);
	if (ret < 0) {
		tcmu_dev_err(dev, "glfs_discard_async(vol=%s, file=%s) failed: %m\n",
		             state->hosts->volname, state->hosts->path);
		goto out;
	}

	return TCMU_STS_OK;

out:
	free(cookie);
	return TCMU_STS_NO_RESOURCE;
}

static int tcmu_glfs_writesame(struct tcmu_device *dev,
			       struct tcmur_cmd *tcmur_cmd,
			       uint64_t offset, uint64_t length,
			       struct iovec *iov, size_t iov_cnt)
{
	struct glfs_state *state = tcmur_dev_get_private(dev);
	glfs_cbk_cookie *cookie;
	ssize_t ret;

	if (!tcmu_iovec_zeroed(iov, iov_cnt)) {
		tcmu_dev_warn(dev,
			      "Received none zeroed data, will fall back to writesame emulator instead.\n");
		return TCMU_STS_NOT_HANDLED;
	}

	cookie = calloc(1, sizeof(*cookie));
	if (!cookie) {
		tcmu_dev_err(dev, "Could not allocate cookie: %m\n");
		goto out;
	}
	cookie->dev = dev;
	cookie->tcmur_cmd = tcmur_cmd;
	cookie->length = 0;
	cookie->op = TCMU_GLFS_WRITESAME;

	ret = glfs_zerofill_async(state->gfd, offset, length, glfs_async_cbk, cookie);
	if (ret < 0) {
		tcmu_dev_err(dev, "glfs_zerofill_async(vol=%s, file=%s) failed: %m\n",
		             state->hosts->volname, state->hosts->path);
		goto out;
	}

	return TCMU_STS_OK;

out:
	free(cookie);
	return TCMU_STS_NO_RESOURCE;
}

#if GFAPI_VERSION766
static int tcmu_glfs_to_sts(int rc)
{
	switch (rc) {
	case 0:
		return TCMU_STS_OK;
	case -ENOTCONN:
		return TCMU_STS_FENCED;
	default:
		return TCMU_STS_HW_ERR;
	}
}

static int tcmu_glfs_lock(struct tcmu_device *dev, uint16_t tag)
{
	struct glfs_state *state = tcmur_dev_get_private(dev);
	struct flock lock;
	int ret;

	if (state->no_fencing)
		return 0;

	lock.l_type = F_WRLCK | F_RDLCK;
	lock.l_whence = SEEK_SET;
	lock.l_start = 0;
	lock.l_len = 0;

	ret = glfs_file_lock(state->gfd, F_SETLK, &lock, GLFS_LK_MANDATORY);
	if (ret)
		tcmu_dev_err(dev, "glfs_file_lock failed: %m\n");

	return tcmu_glfs_to_sts(ret);
}

static int tcmu_glfs_unlock(struct tcmu_device *dev)
{
	struct glfs_state *state = tcmur_dev_get_private(dev);
	struct flock lock;
	int ret;

	if (state->no_fencing)
		return 0;

	lock.l_type = F_UNLCK;
	lock.l_whence = SEEK_SET;
	lock.l_start = 0;
	lock.l_len = 0;

	ret = glfs_file_lock(state->gfd, F_SETLK, &lock, GLFS_LK_MANDATORY);
	if (ret)
		tcmu_dev_err(dev, "glfs_file_lock failed: %m\n");

	return tcmu_glfs_to_sts(ret);
}
#endif

static int tcmu_glfs_init(void)
{
	darray_init(glfs_cache);
	return 0;
}

static void tcmu_glfs_destroy(void)
{
	darray_free(glfs_cache);
}

/*
 * For backstore creation
 *
 * Specify volume, hostname and filepath e.g,
 *
 * $ targetcli /backstores/user:glfs create $blockname $size \
 *   $volume@$hostname/$filepath
 *
 * volume: must be the name of an existing Gluster volume.
 * hostname: the hostname or the IP address
 * filepath: is the path of the backing file in Gluster volume.
 */
static const char glfs_cfg_desc[] =
	"glfs config string is of the form:\n"
	"\"$volume@$hostname/$filepath\"\n"
	"where:\n"
	"  volume:    The volume on the Gluster server\n"
	"  hostname:  The server's hostname\n"
	"  filepath:  The path of the backing file";

struct tcmur_handler glfs_handler = {
	.name           = "Gluster glfs handler",
	.subtype        = "glfs",
	.cfg_desc       = glfs_cfg_desc,

	.open           = tcmu_glfs_open,
	.close          = tcmu_glfs_close,
	.read           = tcmu_glfs_read,
	.write          = tcmu_glfs_write,
	.reconfig       = tcmu_glfs_reconfig,
	.flush          = tcmu_glfs_flush,
	.unmap          = tcmu_glfs_discard,
	.writesame      = tcmu_glfs_writesame,

	.update_logdir  = tcmu_glfs_update_logdir,

#if GFAPI_VERSION766
	.lock           = tcmu_glfs_lock,
	.unlock         = tcmu_glfs_unlock,
#endif
	.init           = tcmu_glfs_init,
	.destroy        = tcmu_glfs_destroy,
};

/* Entry point must be named "handler_init". */
int handler_init(void)
{
	int ret;

	ret = pthread_mutex_init(&glfs_lock, NULL);
	if (ret != 0)
		return -1;

	ret = tcmur_register_handler(&glfs_handler);
	if (ret != 0)
		pthread_mutex_destroy(&glfs_lock);

	return ret;
}
