/*
 * Copyright (c) 2004, 2005 Christophe Varoqui
 * Copyright (c) 2005 Kiyoshi Ueda, NEC
 * Copyright (c) 2005 Benjamin Marzinski, Redhat
 * Copyright (c) 2005 Edward Goggin, EMC
 */
#include <unistd.h>
#include <libdevmapper.h>
#include <sys/mman.h>
#include <pthread.h>
#include <signal.h>
#include <urcu.h>

#include "util.h"
#include "vector.h"
#include "memory.h"
#include "checkers.h"
#include "config.h"
#include "structs.h"
#include "structs_vec.h"
#include "devmapper.h"
#include "debug.h"
#include "lock.h"
#include "waiter.h"
#include "main.h"

pthread_attr_t waiter_attr;
struct mutex_lock waiter_lock = { .mutex = PTHREAD_MUTEX_INITIALIZER };

static struct event_thread *alloc_waiter (void)
{

	struct event_thread *wp;

	wp = (struct event_thread *)MALLOC(sizeof(struct event_thread));
	memset(wp, 0, sizeof(struct event_thread));

	return wp;
}

static void free_waiter (void *data)
{
	struct event_thread *wp = (struct event_thread *)data;

	if (wp->dmt)
		dm_task_destroy(wp->dmt);

	rcu_unregister_thread();
	FREE(wp);
}

void stop_waiter_thread (struct multipath *mpp)
{
	pthread_t thread;

	if (mpp->waiter == (pthread_t)0) {
		condlog(3, "%s: event checker thread already stopped",
			mpp->alias);
		return;
	}
	/* Don't cancel yourself. __setup_multipath is called by
	   by the waiter thread, and may remove a multipath device */
	if (pthread_equal(mpp->waiter, pthread_self()))
		return;

	condlog(3, "%s: stop event checker thread (%lu)", mpp->alias,
		(unsigned long)mpp->waiter);
	thread = mpp->waiter;
	mpp->waiter = (pthread_t)0;
	pthread_cleanup_push(cleanup_lock, &waiter_lock);
	lock(&waiter_lock);
	pthread_kill(thread, SIGUSR2);
	pthread_cancel(thread);
	lock_cleanup_pop(&waiter_lock);
}

/*
 * returns the reschedule delay
 * negative means *stop*
 */
static int waiteventloop (struct event_thread *waiter)
{
	sigset_t set, oldset;
	int event_nr;
	int r;

	if (!waiter->event_nr)
		waiter->event_nr = dm_geteventnr(waiter->mapname);

	if (!(waiter->dmt = libmp_dm_task_create(DM_DEVICE_WAITEVENT))) {
		condlog(0, "%s: devmap event #%i dm_task_create error",
				waiter->mapname, waiter->event_nr);
		return 1;
	}

	if (!dm_task_set_name(waiter->dmt, waiter->mapname)) {
		condlog(0, "%s: devmap event #%i dm_task_set_name error",
				waiter->mapname, waiter->event_nr);
		dm_task_destroy(waiter->dmt);
		waiter->dmt = NULL;
		return 1;
	}

	if (waiter->event_nr && !dm_task_set_event_nr(waiter->dmt,
						      waiter->event_nr)) {
		condlog(0, "%s: devmap event #%i dm_task_set_event_nr error",
				waiter->mapname, waiter->event_nr);
		dm_task_destroy(waiter->dmt);
		waiter->dmt = NULL;
		return 1;
	}

	dm_task_no_open_count(waiter->dmt);

	/* wait */
	sigemptyset(&set);
	sigaddset(&set, SIGUSR2);
	pthread_sigmask(SIG_UNBLOCK, &set, &oldset);

	pthread_testcancel();
	r = dm_task_run(waiter->dmt);
	if (!r)
		dm_log_error(2, DM_DEVICE_WAITEVENT, waiter->dmt);
	pthread_testcancel();

	pthread_sigmask(SIG_SETMASK, &oldset, NULL);
	dm_task_destroy(waiter->dmt);
	waiter->dmt = NULL;

	if (!r)	{ /* wait interrupted by signal. check for cancellation */
		pthread_cleanup_push(cleanup_lock, &waiter_lock);
		lock(&waiter_lock);
		pthread_testcancel();
		lock_cleanup_pop(&waiter_lock);
		return 1; /* If we weren't cancelled, just reschedule */
	}

	waiter->event_nr++;

	/*
	 * upon event ...
	 */
	while (1) {
		condlog(3, "%s: devmap event #%i",
				waiter->mapname, waiter->event_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 = update_multipath(waiter->vecs, waiter->mapname, 1);
		lock_cleanup_pop(waiter->vecs->lock);

		if (r) {
			condlog(2, "%s: event checker exit",
				waiter->mapname);
			return -1; /* stop the thread */
		}

		event_nr = dm_geteventnr(waiter->mapname);

		if (waiter->event_nr == event_nr)
			return 1; /* upon problem reschedule 1s later */

		waiter->event_nr = event_nr;
	}
	return -1; /* never reach there */
}

static void *waitevent (void *et)
{
	int r;
	struct event_thread *waiter;

	mlockall(MCL_CURRENT | MCL_FUTURE);

	waiter = (struct event_thread *)et;
	pthread_cleanup_push(free_waiter, et);

	rcu_register_thread();
	while (1) {
		r = waiteventloop(waiter);

		if (r < 0)
			break;

		sleep(r);
	}

	pthread_cleanup_pop(1);
	return NULL;
}

int start_waiter_thread (struct multipath *mpp, struct vectors *vecs)
{
	struct event_thread *wp;

	if (!mpp)
		return 0;

	wp = alloc_waiter();

	if (!wp)
		goto out;

	strlcpy(wp->mapname, mpp->alias, WWID_SIZE);
	wp->vecs = vecs;

	if (pthread_create(&wp->thread, &waiter_attr, waitevent, wp)) {
		condlog(0, "%s: cannot create event checker", wp->mapname);
		goto out1;
	}
	mpp->waiter = wp->thread;
	condlog(3, "%s: event checker started", wp->mapname);

	return 0;
out1:
	free_waiter(wp);
	mpp->waiter = (pthread_t)0;
out:
	condlog(0, "failed to start waiter thread");
	return 1;
}
