/*
 * Copyright (c) 2004, 2005 Christophe Varoqui
 * Copyright (c) 2005 Kiyoshi Ueda, NEC
 * Copyright (c) 2005 Edward Goggin, EMC
 * Copyright (c) 2005, 2018 Benjamin Marzinski, Redhat
 */
#include <unistd.h>
#include <libdevmapper.h>
#include <sys/mman.h>
#include <pthread.h>
#include <urcu.h>
#include <poll.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <linux/dm-ioctl.h>
#include <errno.h>

#include "vector.h"
#include "structs.h"
#include "structs_vec.h"
#include "devmapper.h"
#include "debug.h"
#include "main.h"
#include "dmevents.h"
#include "util.h"

#ifndef DM_DEV_ARM_POLL
#define DM_DEV_ARM_POLL _IOWR(DM_IOCTL, DM_DEV_SET_GEOMETRY_CMD + 1, struct dm_ioctl)
#endif

enum event_actions {
	EVENT_NOTHING,
	EVENT_REMOVE,
	EVENT_UPDATE,
};

struct dev_event {
	char name[WWID_SIZE];
	uint32_t evt_nr;
	enum event_actions action;
};

struct dmevent_waiter {
	int fd;
	struct vectors *vecs;
	vector events;
	pthread_mutex_t events_lock;
};

static struct dmevent_waiter *waiter;
/*
 * DM_VERSION_MINOR hasn't been updated when DM_DEV_ARM_POLL
 * was added in kernel 4.13. 4.37.0 (4.14) has it, safely.
 */
static const unsigned int DM_VERSION_FOR_ARM_POLL[] = {4, 37, 0};

int dmevent_poll_supported(void)
{
	unsigned int v[3];

	if (libmp_get_version(DM_KERNEL_VERSION, v))
		return 0;

	if (VERSION_GE(v, DM_VERSION_FOR_ARM_POLL))
		return 1;
	return 0;
}


int init_dmevent_waiter(struct vectors *vecs)
{
	if (!vecs) {
		condlog(0, "can't create waiter structure. invalid vectors");
		goto fail;
	}
	waiter = (struct dmevent_waiter *)malloc(sizeof(struct dmevent_waiter));
	if (!waiter) {
		condlog(0, "failed to allocate waiter structure");
		goto fail;
	}
	memset(waiter, 0, sizeof(struct dmevent_waiter));
	waiter->events = vector_alloc();
	if (!waiter->events) {
		condlog(0, "failed to allocate waiter events vector");
		goto fail_waiter;
	}
	waiter->fd = open("/dev/mapper/control", O_RDWR);
	if (waiter->fd < 0) {
		condlog(0, "failed to open /dev/mapper/control for waiter");
		goto fail_events;
	}
	pthread_mutex_init(&waiter->events_lock, NULL);
	waiter->vecs = vecs;

	return 0;
fail_events:
	vector_free(waiter->events);
fail_waiter:
	free(waiter);
fail:
	waiter = NULL;
	return -1;
}

void cleanup_dmevent_waiter(void)
{
	struct dev_event *dev_evt;
	int i;

	if (!waiter)
		return;
	pthread_mutex_destroy(&waiter->events_lock);
	close(waiter->fd);
	vector_foreach_slot(waiter->events, dev_evt, i)
		free(dev_evt);
	vector_free(waiter->events);
	free(waiter);
	waiter = NULL;
}

static int arm_dm_event_poll(int fd)
{
	struct dm_ioctl dmi;
	memset(&dmi, 0, sizeof(dmi));
	dmi.version[0] = DM_VERSION_FOR_ARM_POLL[0];
	dmi.version[1] = DM_VERSION_FOR_ARM_POLL[1];
	dmi.version[2] = DM_VERSION_FOR_ARM_POLL[2];
	/* This flag currently does nothing. It simply exists to
	 * duplicate the behavior of libdevmapper */
	dmi.flags = 0x4;
	dmi.data_start = offsetof(struct dm_ioctl, data);
	dmi.data_size = sizeof(dmi);
	return ioctl(fd, DM_DEV_ARM_POLL, &dmi);
}

/*
 * As of version 4.37.0 device-mapper stores the event number in the
 * dm_names structure after the name, when DM_DEVICE_LIST is called
 */
static uint32_t dm_event_nr(struct dm_names *n)
{
	return *(uint32_t *)(((uintptr_t)(strchr(n->name, 0) + 1) + 7) & ~7);
}

static int dm_get_events(void)
{
	struct dm_task *dmt;
	struct dm_names *names;
	struct dev_event *dev_evt;
	int i;

	if (!(dmt = libmp_dm_task_create(DM_DEVICE_LIST)))
		return -1;

	dm_task_no_open_count(dmt);

	if (!libmp_dm_task_run(dmt)) {
		dm_log_error(3, DM_DEVICE_LIST, dmt);
		goto fail;
	}

	if (!(names = dm_task_get_names(dmt)))
		goto fail;

	pthread_mutex_lock(&waiter->events_lock);
	vector_foreach_slot(waiter->events, dev_evt, i)
		dev_evt->action = EVENT_REMOVE;
	while (names->dev) {
		uint32_t event_nr;

		/* Don't delete device if dm_is_mpath() fails without
		 * checking the device type */
		if (dm_is_mpath(names->name) == 0)
			goto next;

		event_nr = dm_event_nr(names);
		vector_foreach_slot(waiter->events, dev_evt, i) {
			if (!strcmp(dev_evt->name, names->name)) {
				if (event_nr != dev_evt->evt_nr) {
					dev_evt->evt_nr = event_nr;
					dev_evt->action = EVENT_UPDATE;
				} else
					dev_evt->action = EVENT_NOTHING;
				break;
			}
		}
next:
		if (!names->next)
			break;
		names = (void *)names + names->next;
	}
	pthread_mutex_unlock(&waiter->events_lock);
	dm_task_destroy(dmt);
	return 0;

fail:
	dm_task_destroy(dmt);
	return -1;
}

/* You must call __setup_multipath() after calling this function, to
 * deal with any events that came in before the device was added */
int watch_dmevents(char *name)
{
	int event_nr;
	struct dev_event *dev_evt, *old_dev_evt;
	int i;

	/* We know that this is a multipath device, so only fail if
	 * device-mapper tells us that we're wrong */
	if (dm_is_mpath(name) == 0) {
		condlog(0, "%s: not a multipath device. can't watch events",
			name);
		return -1;
	}

	if ((event_nr = dm_geteventnr(name)) < 0)
		return -1;

	dev_evt = (struct dev_event *)malloc(sizeof(struct dev_event));
	if (!dev_evt) {
		condlog(0, "%s: can't allocate event waiter structure", name);
		return -1;
	}

	strlcpy(dev_evt->name, name, WWID_SIZE);
	dev_evt->evt_nr = event_nr;
	dev_evt->action = EVENT_NOTHING;

	pthread_mutex_lock(&waiter->events_lock);
	vector_foreach_slot(waiter->events, old_dev_evt, i){
		if (!strcmp(dev_evt->name, old_dev_evt->name)) {
			/* caller will be updating this device */
			old_dev_evt->evt_nr = event_nr;
			old_dev_evt->action = EVENT_NOTHING;
			pthread_mutex_unlock(&waiter->events_lock);
			condlog(2, "%s: already waiting for events on device",
				name);
			free(dev_evt);
			return 0;
		}
	}
	if (!vector_alloc_slot(waiter->events)) {
		pthread_mutex_unlock(&waiter->events_lock);
		free(dev_evt);
		return -1;
	}
	vector_set_slot(waiter->events, dev_evt);
	pthread_mutex_unlock(&waiter->events_lock);
	return 0;
}

void unwatch_all_dmevents(void)
{
	struct dev_event *dev_evt;
	int i;

	if (!waiter)
		return;
	pthread_mutex_lock(&waiter->events_lock);
	vector_foreach_slot(waiter->events, dev_evt, i)
		free(dev_evt);
	vector_reset(waiter->events);
	pthread_mutex_unlock(&waiter->events_lock);
}

static void unwatch_dmevents(char *name)
{
	struct dev_event *dev_evt;
	int i;

	pthread_mutex_lock(&waiter->events_lock);
	vector_foreach_slot(waiter->events, dev_evt, i) {
		if (!strcmp(dev_evt->name, name)) {
			vector_del_slot(waiter->events, i);
			free(dev_evt);
			break;
		}
	}
	pthread_mutex_unlock(&waiter->events_lock);
}

/*
 * returns the reschedule delay
 * negative means *stop*
 */

/* poll, arm, update, return */
static int dmevent_loop (void)
{
	int r, i = 0;
	struct pollfd pfd;
	struct dev_event *dev_evt;

	pfd.fd = waiter->fd;
	pfd.events = POLLIN;
	r = poll(&pfd, 1, -1);
	if (r <= 0) {
		condlog(0, "failed polling for dm events: %s", strerror(errno));
		/* sleep 1s and hope things get better */
		return 1;
	}

	if (arm_dm_event_poll(waiter->fd) != 0) {
		condlog(0, "Cannot re-arm event polling: %s", strerror(errno));
		/* sleep 1s and hope things get better */
		return 1;
	}

	if (dm_get_events() != 0) {
		condlog(0, "failed getting dm events: %s", strerror(errno));
		/* sleep 1s and hope things get better */
		return 1;
	}

	/*
	 * upon event ...
	 */

	while (1) {
		int done = 1;
		struct dev_event curr_dev;

		pthread_mutex_lock(&waiter->events_lock);
		vector_foreach_slot(waiter->events, dev_evt, i) {
			if (dev_evt->action != EVENT_NOTHING) {
				curr_dev = *dev_evt;
				if (dev_evt->action == EVENT_REMOVE) {
					vector_del_slot(waiter->events, i);
					free(dev_evt);
				} else
					dev_evt->action = EVENT_NOTHING;
				done = 0;
				break;
			}
		}
		pthread_mutex_unlock(&waiter->events_lock);
		if (done)
			return 1;

		condlog(3, "%s: devmap event #%i", curr_dev.name,
			curr_dev.evt_nr);

		/*
		 * event might be :
		 *
		 * 1) a table reload, which means our mpp structure is
		 *    obsolete : refresh it through update_multipath()
		 * 2) a path failed by DM : mark as such through
		 *    update_multipath()
		 * 3) map has gone away : stop the thread.
		 * 4) a path reinstate : nothing to do
		 * 5) a switch group : nothing to do
		 */
		pthread_cleanup_push(cleanup_lock, &waiter->vecs->lock);
		lock(&waiter->vecs->lock);
		pthread_testcancel();
		r = 0;
		if (curr_dev.action == EVENT_REMOVE)
			remove_map_by_alias(curr_dev.name, waiter->vecs);
		else
			r = update_multipath(waiter->vecs, curr_dev.name);
		pthread_cleanup_pop(1);

		if (r) {
			condlog(2, "%s: stopped watching dmevents",
				curr_dev.name);
			unwatch_dmevents(curr_dev.name);
		}
	}
	condlog(0, "dmevent waiter thread unexpectedly quit");
	return -1; /* never reach there */
}

static void rcu_unregister(__attribute__((unused)) void *param)
{
	rcu_unregister_thread();
}

void *wait_dmevents (__attribute__((unused)) void *unused)
{
	int r;


	if (!waiter) {
		condlog(0, "dmevents waiter not initialized");
		return NULL;
	}

	pthread_cleanup_push(rcu_unregister, NULL);
	rcu_register_thread();
	mlockall(MCL_CURRENT | MCL_FUTURE);

	while (1) {
		r = dmevent_loop();

		if (r < 0)
			break;

		sleep(r);
	}

	pthread_cleanup_pop(1);
	return NULL;
}
