/*
  Copyright Red Hat, Inc. 2006

  This program is free software; you can redistribute it and/or modify it
  under the terms of the GNU General Public License as published by the
  Free Software Foundation; either version 2, or (at your option) any
  later version.

  This program is distributed in the hope that it will be useful, but
  WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; see the file COPYING.  If not, write to the
  Free Software Foundation, Inc.,  675 Mass Ave, Cambridge, 
  MA 02139, USA.
*/
/**
  pthread mutex wrapper for a global CMAN handle
 */
#include <stdio.h>
#include <pthread.h>
#include <libcman.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <stdlib.h>
#include <errno.h>
#include <signal.h>
#include <sys/socket.h>
#include <fcntl.h>

static cman_handle_t _chandle = NULL;
static pthread_mutex_t _chandle_lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t _chandle_cond = PTHREAD_COND_INITIALIZER;
static pthread_t _chandle_holder = 0;
static int _chandle_preempt = 0;
static int _wakeup_pipe[2] = { -1, -1 };

static void
_set_nonblock(int fd)
{
	int flags;

	flags = fcntl(fd, F_GETFL, 0);
	if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0)
		perror("fcntl");
}


/**
  Lock / return the global CMAN handle.

  @param block		If nonzero, we wait until the handle is released
  @param preempt	If nonzero, *try* to wake up the holder who has
			taken the lock with cman_lock_preemptible.  Will not
			wake up holders which took it with cman_lock().
  @return		NULL / errno on failure; the global CMAN handle
			on success.
 */
cman_handle_t 
cman_lock(int block, int preempt)
{
	pthread_t tid;
	cman_handle_t *ret = NULL;

	pthread_mutex_lock(&_chandle_lock);
	if (_chandle == NULL) {
		errno = ENOSYS;
		goto out_unlock;
	}

	tid = pthread_self();
	if (_chandle_holder == tid) {
		errno = EDEADLK;
		goto out_unlock;
	}

	if (_chandle_holder > 0) {
		if (!block) {
			errno = EAGAIN;
			goto out_unlock;
		}

		/* Try to wake up the holder! */
		if (preempt)
			if(write(_wakeup_pipe[1], "!", 1) < 0)
				goto out_unlock;

		/* Blocking call; do the cond-thing */
		pthread_cond_wait(&_chandle_cond, &_chandle_lock);
	}
		
	_chandle_holder = tid;
	ret = _chandle;
out_unlock:
	pthread_mutex_unlock(&_chandle_lock);
	return ret;
}


/**
  Lock / return the global CMAN handle.

  @param block		If nonzero, we wait until the handle is released
  @param preempt_fd	Caller should include this file descriptor in
			blocking calls to select(2), so that we can wake
			it up if someone calls with cman_lock(xxx, 1);
  @return		NULL / errno on failure; the global CMAN handle
			on success.
 */
cman_handle_t
cman_lock_preemptible(int block, int *preempt_fd)
{
	pthread_t tid;
	cman_handle_t *ret = NULL;

	if (preempt_fd == NULL) {
		errno = EINVAL;
		return NULL;
	}

	pthread_mutex_lock(&_chandle_lock);
	if (_chandle == NULL) {
		errno = ENOSYS;
		goto out_unlock;
	}

	tid = pthread_self();
	if (_chandle_holder == tid) {
		errno = EDEADLK;
		goto out_unlock;
	}

	if (_chandle_holder > 0) {
		if (!block) {
			errno = EAGAIN;
			goto out_unlock;
		}

		/* Blocking call; do the cond-thing */
		pthread_cond_wait(&_chandle_cond, &_chandle_lock);
	}

	*preempt_fd = _wakeup_pipe[0];
	_chandle_holder = tid;
	_chandle_preempt = 1;
	ret = _chandle;
out_unlock:
	pthread_mutex_unlock(&_chandle_lock);
	return ret;
}


/**
  Release the global CMAN handle

  @param ch		Should match the global handle
  @return		-1 on failure, 0 on success
 */
int
cman_unlock(cman_handle_t ch)
{
	int ret = -1;
	char c;

	pthread_mutex_lock(&_chandle_lock);
	if (_chandle == NULL) {
		errno = ENOSYS;
		goto out_unlock;
	}

	if (_chandle_holder != pthread_self() || !_chandle_holder) {
		errno = EBUSY;
		goto out_unlock;
	}

	if (_chandle != ch) {
		errno = EINVAL;
		goto out_unlock;
	}

	/* Empty wakeup pipe if we took it with the preempt flag */
	if (_chandle_preempt)
		if(read(_wakeup_pipe[0], &c, 1) < 0)
			goto out_unlock;

	_chandle_preempt = 0;
	_chandle_holder = 0;
	ret = 0;

out_unlock:
	pthread_mutex_unlock(&_chandle_lock);
	if (ret == 0) 
		pthread_cond_broadcast(&_chandle_cond);
	return ret;
}


int
cman_init_subsys(cman_handle_t *ch)
{
	int ret = -1;

	pthread_mutex_lock(&_chandle_lock);
	if (_chandle) {
		errno = EAGAIN;
		goto out_unlock;
	}

	if (!ch) {
		errno = EAGAIN;
		goto out_unlock;
	}

	if (pipe(_wakeup_pipe) < 0) {
		goto out_unlock;
	}

	_set_nonblock(_wakeup_pipe[0]);
	_chandle = ch;
	_chandle_holder = 0;
	ret = 0;

out_unlock:
	pthread_mutex_unlock(&_chandle_lock);
	return ret;
}


int
cman_cleanup_subsys(void)
{
	int ret = -1;

	pthread_mutex_lock(&_chandle_lock);
	if (!_chandle) {
		errno = EAGAIN;
		goto out_unlock;
	}

	if (_chandle_holder > 0) {
		pthread_cond_wait(&_chandle_cond, &_chandle_lock);
	}

	ret = 0;
	_chandle = NULL;
	_chandle_holder = 0;

	close(_wakeup_pipe[0]);
	close(_wakeup_pipe[1]);
	
out_unlock:
	pthread_mutex_unlock(&_chandle_lock);
	return ret;
}


int
cman_send_data_unlocked(void *buf, int len, int flags,
			uint8_t port, int nodeid)
{
	return cman_send_data(_chandle, buf, len, flags, port, nodeid);
}
