/*
 * Copyright (C) 2014-2015 Red Hat, Inc.
 *
 * This file is part of LVM2.
 *
 * This copyrighted material is made available to anyone wishing to use,
 * modify, copy, or redistribute it subject to the terms and conditions
 * of the GNU Lesser General Public License v.2.1.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include "lvmpolld-common.h"

#include "config-util.h"

#include <fcntl.h>
#include <signal.h>

static char *_construct_full_lvname(const char *vgname, const char *lvname)
{
	char *name;
	size_t l;

	l = strlen(vgname) + strlen(lvname) + 2; /* vg/lv and \0 */
	name = (char *) dm_malloc(l * sizeof(char));
	if (!name)
		return NULL;

	if (dm_snprintf(name, l, "%s/%s", vgname, lvname) < 0) {
		dm_free(name);
		name = NULL;
	}

	return name;
}

static char *_construct_lvm_system_dir_env(const char *sysdir)
{
	/*
	 *  Store either "LVM_SYSTEM_DIR=/path/to..."
	 *		    - or -
	 *  just single char to store NULL byte
	 */
	size_t l = sysdir ? strlen(sysdir) + 16 : 1;
	char *env = (char *) dm_malloc(l * sizeof(char));

	if (!env)
		return NULL;

	*env = '\0';

	if (sysdir && dm_snprintf(env, l, "LVM_SYSTEM_DIR=%s", sysdir) < 0) {
		dm_free(env);
		env = NULL;
	}

	return env;
}

static const char *_get_lvid(const char *lvmpolld_id, const char *sysdir)
{
	return lvmpolld_id ? (lvmpolld_id + (sysdir ? strlen(sysdir) : 0)) : NULL;
}

char *construct_id(const char *sysdir, const char *uuid)
{
	char *id;
	int r;
	size_t l;

	l = strlen(uuid) + (sysdir ? strlen(sysdir) : 0) + 1;
	id = (char *) dm_malloc(l * sizeof(char));
	if (!id)
		return NULL;

	r = sysdir ? dm_snprintf(id, l, "%s%s", sysdir, uuid) :
		     dm_snprintf(id, l, "%s", uuid);

	if (r < 0) {
		dm_free(id);
		id = NULL;
	}

	return id;
}

struct lvmpolld_lv *pdlv_create(struct lvmpolld_state *ls, const char *id,
			   const char *vgname, const char *lvname,
			   const char *sysdir, enum poll_type type,
			   const char *sinterval, unsigned pdtimeout,
			   struct lvmpolld_store *pdst)
{
	char *lvmpolld_id = dm_strdup(id), /* copy */
	     *full_lvname = _construct_full_lvname(vgname, lvname), /* copy */
	     *lvm_system_dir_env = _construct_lvm_system_dir_env(sysdir); /* copy */

	struct lvmpolld_lv tmp = {
		.ls = ls,
		.type = type,
		.lvmpolld_id = lvmpolld_id,
		.lvid = _get_lvid(lvmpolld_id, sysdir),
		.lvname = full_lvname,
		.lvm_system_dir_env = lvm_system_dir_env,
		.sinterval = dm_strdup(sinterval), /* copy */
		.pdtimeout = pdtimeout < MIN_POLLING_TIMEOUT ? MIN_POLLING_TIMEOUT : pdtimeout,
		.cmd_state = { .retcode = -1, .signal = 0 },
		.pdst = pdst,
		.init_rq_count = 1
	}, *pdlv = (struct lvmpolld_lv *) dm_malloc(sizeof(struct lvmpolld_lv));

	if (!pdlv || !tmp.lvid || !tmp.lvname || !tmp.lvm_system_dir_env || !tmp.sinterval)
		goto err;

	memcpy(pdlv, &tmp, sizeof(*pdlv));

	if (pthread_mutex_init(&pdlv->lock, NULL))
		goto err;

	return pdlv;

err:
	dm_free((void *)full_lvname);
	dm_free((void *)lvmpolld_id);
	dm_free((void *)lvm_system_dir_env);
	dm_free((void *)tmp.sinterval);
	dm_free((void *)pdlv);

	return NULL;
}

void pdlv_destroy(struct lvmpolld_lv *pdlv)
{
	dm_free((void *)pdlv->lvmpolld_id);
	dm_free((void *)pdlv->lvname);
	dm_free((void *)pdlv->sinterval);
	dm_free((void *)pdlv->lvm_system_dir_env);
	dm_free((void *)pdlv->cmdargv);
	dm_free((void *)pdlv->cmdenvp);

	pthread_mutex_destroy(&pdlv->lock);

	dm_free((void *)pdlv);
}

unsigned pdlv_get_polling_finished(struct lvmpolld_lv *pdlv)
{
	unsigned ret;

	pdlv_lock(pdlv);
	ret = pdlv->polling_finished;
	pdlv_unlock(pdlv);

	return ret;
}

struct lvmpolld_lv_state pdlv_get_status(struct lvmpolld_lv *pdlv)
{
	struct lvmpolld_lv_state r;

	pdlv_lock(pdlv);
	r.error = pdlv_locked_error(pdlv);
	r.polling_finished = pdlv_locked_polling_finished(pdlv);
	r.cmd_state = pdlv_locked_cmd_state(pdlv);
	pdlv_unlock(pdlv);

	return r;
}

void pdlv_set_cmd_state(struct lvmpolld_lv *pdlv, const struct lvmpolld_cmd_stat *cmd_state)
{
	pdlv_lock(pdlv);
	pdlv->cmd_state = *cmd_state;
	pdlv_unlock(pdlv);
}

void pdlv_set_error(struct lvmpolld_lv *pdlv, unsigned error)
{
	pdlv_lock(pdlv);
	pdlv->error = error;
	pdlv_unlock(pdlv);
}

void pdlv_set_polling_finished(struct lvmpolld_lv *pdlv, unsigned finished)
{
	pdlv_lock(pdlv);
	pdlv->polling_finished = finished;
	pdlv_unlock(pdlv);
}

struct lvmpolld_store *pdst_init(const char *name)
{
	struct lvmpolld_store *pdst = (struct lvmpolld_store *) dm_malloc(sizeof(struct lvmpolld_store));
	if (!pdst)
		return NULL;

	pdst->store = dm_hash_create(32);
	if (!pdst->store)
		goto err_hash;
	if (pthread_mutex_init(&pdst->lock, NULL))
		goto err_mutex;

	pdst->name = name;
	pdst->active_polling_count = 0;

	return pdst;

err_mutex:
	dm_hash_destroy(pdst->store);
err_hash:
	dm_free(pdst);
	return NULL;
}

void pdst_destroy(struct lvmpolld_store *pdst)
{
	if (!pdst)
		return;

	dm_hash_destroy(pdst->store);
	pthread_mutex_destroy(&pdst->lock);
	dm_free(pdst);
}

void pdst_locked_lock_all_pdlvs(const struct lvmpolld_store *pdst)
{
	struct dm_hash_node *n;

	dm_hash_iterate(n, pdst->store)
		pdlv_lock(dm_hash_get_data(pdst->store, n));
}

void pdst_locked_unlock_all_pdlvs(const struct lvmpolld_store *pdst)
{
	struct dm_hash_node *n;

	dm_hash_iterate(n, pdst->store)
		pdlv_unlock(dm_hash_get_data(pdst->store, n));
}

static void _pdlv_locked_dump(struct buffer *buff, const struct lvmpolld_lv *pdlv)
{
	char tmp[1024];
	const struct lvmpolld_cmd_stat *cmd_state = &pdlv->cmd_state;

	/* pdlv-section { */
	if (dm_snprintf(tmp, sizeof(tmp), "\t%s {\n", pdlv->lvmpolld_id) > 0)
		buffer_append(buff, tmp);

	if (dm_snprintf(tmp, sizeof(tmp), "\t\tlvid=\"%s\"\n", pdlv->lvid) > 0)
		buffer_append(buff, tmp);
	if (dm_snprintf(tmp, sizeof(tmp), "\t\ttype=\"%s\"\n", polling_op(pdlv->type)) > 0)
		buffer_append(buff, tmp);
	if (dm_snprintf(tmp, sizeof(tmp), "\t\tlvname=\"%s\"\n", pdlv->lvname) > 0)
		buffer_append(buff, tmp);
	if (dm_snprintf(tmp, sizeof(tmp), "\t\tlvmpolld_internal_timeout=%d\n", pdlv->pdtimeout) > 0)
		buffer_append(buff, tmp);
	if (dm_snprintf(tmp, sizeof(tmp), "\t\tlvm_command_interval=\"%s\"\n", pdlv->sinterval ?: "<undefined>") > 0)
		buffer_append(buff, tmp);
	if (dm_snprintf(tmp, sizeof(tmp), "\t\tLVM_SYSTEM_DIR=\"%s\"\n",
			(*pdlv->lvm_system_dir_env ? (pdlv->lvm_system_dir_env + strlen("LVM_SYSTEM_DIR=")) : "<undefined>")) > 0)
		buffer_append(buff, tmp);
	if (dm_snprintf(tmp, sizeof(tmp), "\t\tlvm_command_pid=%d\n", pdlv->cmd_pid) > 0)
		buffer_append(buff, tmp);
	if (dm_snprintf(tmp, sizeof(tmp), "\t\tpolling_finished=%d\n", pdlv->polling_finished) > 0)
		buffer_append(buff, tmp);
	if (dm_snprintf(tmp, sizeof(tmp), "\t\terror_occured=%d\n", pdlv->error) > 0)
		buffer_append(buff, tmp);
	if (dm_snprintf(tmp, sizeof(tmp), "\t\tinit_requests_count=%d\n", pdlv->init_rq_count) > 0)
		buffer_append(buff, tmp);

	/* lvm_commmand-section { */
	buffer_append(buff, "\t\tlvm_command {\n");
	if (cmd_state->retcode == -1 && !cmd_state->signal)
		buffer_append(buff, "\t\t\tstate=\"" LVMPD_RESP_IN_PROGRESS "\"\n");
	else {
		buffer_append(buff, "\t\t\tstate=\"" LVMPD_RESP_FINISHED "\"\n");
		if (dm_snprintf(tmp, sizeof(tmp), "\t\t\treason=\"%s\"\n\t\t\tvalue=%d\n",
				(cmd_state->signal ? LVMPD_REAS_SIGNAL : LVMPD_REAS_RETCODE),
				(cmd_state->signal ?: cmd_state->retcode)) > 0)
			buffer_append(buff, tmp);
	}
	buffer_append(buff, "\t\t}\n");
	/* } lvm_commmand-section */

	buffer_append(buff, "\t}\n");
	/* } pdlv-section */
}

void pdst_locked_dump(const struct lvmpolld_store *pdst, struct buffer *buff)
{
	struct dm_hash_node *n;

	dm_hash_iterate(n, pdst->store)
		_pdlv_locked_dump(buff, dm_hash_get_data(pdst->store, n));
}

void pdst_locked_send_cancel(const struct lvmpolld_store *pdst)
{
	struct lvmpolld_lv *pdlv;
	struct dm_hash_node *n;

	dm_hash_iterate(n, pdst->store) {
		pdlv = dm_hash_get_data(pdst->store, n);
		if (!pdlv_locked_polling_finished(pdlv))
			pthread_cancel(pdlv->tid);
	}
}

void pdst_locked_destroy_all_pdlvs(const struct lvmpolld_store *pdst)
{
	struct dm_hash_node *n;

	dm_hash_iterate(n, pdst->store)
		pdlv_destroy(dm_hash_get_data(pdst->store, n));
}

struct lvmpolld_thread_data *lvmpolld_thread_data_constructor(struct lvmpolld_lv *pdlv)
{
	struct lvmpolld_thread_data *data = (struct lvmpolld_thread_data *) dm_malloc(sizeof(struct lvmpolld_thread_data));
	if (!data)
		return NULL;

	data->pdlv = NULL;
	data->line = NULL;
	data->line_size = 0;
	data->fout = data->ferr = NULL;
	data->outpipe[0] = data->outpipe[1] = data->errpipe[0] = data->errpipe[1] = -1;

	if (pipe(data->outpipe) || pipe(data->errpipe)) {
		lvmpolld_thread_data_destroy(data);
		return NULL;
	}

	if (fcntl(data->outpipe[0], F_SETFD, FD_CLOEXEC) ||
	    fcntl(data->outpipe[1], F_SETFD, FD_CLOEXEC) ||
	    fcntl(data->errpipe[0], F_SETFD, FD_CLOEXEC) ||
	    fcntl(data->errpipe[1], F_SETFD, FD_CLOEXEC)) {
		lvmpolld_thread_data_destroy(data);
		return NULL;
	}

	data->pdlv = pdlv;

	return data;
}

void lvmpolld_thread_data_destroy(void *thread_private)
{
	struct lvmpolld_thread_data *data = (struct lvmpolld_thread_data *) thread_private;
	if (!data)
		return;

	if (data->pdlv) {
		pdst_lock(data->pdlv->pdst);
		/*
		 * FIXME: skip this step if lvmpolld is activated
		 * 	  by systemd.
		 */
		if (!pdlv_get_polling_finished(data->pdlv))
			kill(data->pdlv->cmd_pid, SIGTERM);
		pdlv_set_polling_finished(data->pdlv, 1);
		pdst_locked_dec(data->pdlv->pdst);
		pdst_unlock(data->pdlv->pdst);
	}

	/* may get reallocated in getline(). dm_free must not be used */
	free(data->line);

	if (data->fout && !fclose(data->fout))
		data->outpipe[0] = -1;

	if (data->ferr && !fclose(data->ferr))
		data->errpipe[0] = -1;

	if (data->outpipe[0] >= 0)
		(void) close(data->outpipe[0]);

	if (data->outpipe[1] >= 0)
		(void) close(data->outpipe[1]);

	if (data->errpipe[0] >= 0)
		(void) close(data->errpipe[0]);

	if (data->errpipe[1] >= 0)
		(void) close(data->errpipe[1]);

	dm_free(data);
}
