/*
 * Copyright (c) 2004, Bull S.A..  All rights reserved.
 * Created by: Sebastien Decugis

 * This program is free software; you can redistribute it and/or modify it
 * under the terms of version 2 of the GNU General Public License as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it would be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

 * This sample test aims to check the following assertion:
 *
 * This function is a cancelation point: when cancelability
 * is PTHREAD_CANCEL_DEFERRED and a cancel request arrives, the thread
 * must relock the mutex before the first (if any) clean up handler is called.

 * The steps are:
 * -> Create a thread
 *   ->  this thread locks a mutex then waits for a condition
 * -> cancel the thread
 *   -> the cancelation handler will test if the thread owns the mutex.
 */

/********************************************************************************************/
/****************************** standard includes *****************************************/
/********************************************************************************************/
#include <pthread.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <errno.h>
#include <time.h>
#include <semaphore.h>

/********************************************************************************************/
/******************************   Test framework   *****************************************/
/********************************************************************************************/
#include "../testfrmw/testfrmw.h"
#include "../testfrmw/testfrmw.c"
 /* This header is responsible for defining the following macros:
  * UNRESOLVED(ret, descr);
  *    where descr is a description of the error and ret is an int (error code for example)
  * FAILED(descr);
  *    where descr is a short text saying why the test has failed.
  * PASSED();
  *    No parameter.
  *
  * Both three macros shall terminate the calling process.
  * The testcase shall not terminate in any other maneer.
  *
  * The other file defines the functions
  * void output_init()
  * void output(char * string, ...)
  *
  * Those may be used to output information.
  */

/********************************************************************************************/
/********************************** Configuration ******************************************/
/********************************************************************************************/
#ifndef VERBOSE
#define VERBOSE 1
#endif

#ifndef WITHOUT_ALTCLK
#define USE_ALTCLK		/* make tests with MONOTONIC CLOCK if supported */
#endif

/********************************************************************************************/
/***********************************    Test case   *****************************************/
/********************************************************************************************/

static struct {
	pthread_mutex_t mtx;
	pthread_cond_t cnd;
	int type;
	sem_t semA;
	sem_t semB;
	int bool;
} data;

/****  First handler that will be poped
 *  This one works only with recursive mutexes
 */
static void clnp1(void *arg PTS_ATTRIBUTE_UNUSED)
{
	int ret;
	if (data.type == PTHREAD_MUTEX_RECURSIVE) {
		ret = pthread_mutex_trylock(&(data.mtx));
		if (ret != 0) {
			FAILED
			    ("Unable to double-lock a recursive mutex in clean-up handler 1");
		}
		ret = pthread_mutex_unlock(&(data.mtx));
		if (ret != 0) {
			UNRESOLVED(ret,
				   "Unable to unlock double-locked recursive mutex in clean-up handler 1");
		}
	}
	return;
}

/**** Second handler
 *  This one will trigger an action in main thread, while we are owning the mutex
 */
static void clnp2(void *arg PTS_ATTRIBUTE_UNUSED)
{
	int ret;
	do {
		ret = sem_post(&(data.semA));
	} while ((ret != 0) && (errno == EINTR));
	if (ret != 0) {
		UNRESOLVED(errno, "Sem post failed in cleanup handler 2");
	}

	do {
		ret = sem_wait(&(data.semB));
	} while ((ret != 0) && (errno == EINTR));
	if (ret != 0) {
		UNRESOLVED(errno, "Sem wait failed in cleanup handler 2");
	}

	return;
}

/**** Third handler
 *  Will actually unlock the mutex, then try to unlock second time to check an error is returned
 */
static void clnp3(void *arg PTS_ATTRIBUTE_UNUSED)
{
	int ret;

	ret = pthread_mutex_unlock(&(data.mtx));
	if (ret != 0) {
		UNRESOLVED(ret, "Unable to unlock mutex in clean-up handler 3");
	}

	if ((data.type == PTHREAD_MUTEX_ERRORCHECK)
	    || (data.type == PTHREAD_MUTEX_RECURSIVE)) {
		ret = pthread_mutex_unlock(&(data.mtx));
		if (ret == 0) {
			UNRESOLVED(ret,
				   "Was able to unlock unlocked mutex in clean-up handler 3");
		}
	}

	return;
}

/**** Thread function
 * This function will lock the mutex, then install the cleanup handlers
 * and wait for the cond. At this point it will be canceled.
 */
static void *threaded(void *arg PTS_ATTRIBUTE_UNUSED)
{
	int ret;

	ret = pthread_mutex_lock(&(data.mtx));
	if (ret != 0) {
		UNRESOLVED(ret, "Failed to lock the mutex in thread");
	}

	/* Tell the main thread we got the lock */
	do {
		ret = sem_post(&(data.semA));
	} while ((ret != 0) && (errno == EINTR));
	if (ret != 0) {
		UNRESOLVED(errno, "Sem post failed in cleanup handler 2");
	}

	pthread_cleanup_push(clnp3, NULL);
	pthread_cleanup_push(clnp2, NULL);
	pthread_cleanup_push(clnp1, NULL);

	do {
		ret = pthread_cond_wait(&(data.cnd), &(data.mtx));
	} while ((ret == 0) && (data.bool == 0));

	if (ret != 0) {
		UNRESOLVED(ret, "Wait failed");
	}

	/* We will exit even if the error is timedwait */
	/* If we are here, the thread was not canceled */
	FAILED("The thread has not been canceled");

	pthread_cleanup_pop(0);
	pthread_cleanup_pop(0);
	pthread_cleanup_pop(1);

	return NULL;
}

int main(void)
{
	int ret, i;
	void *rc;

	pthread_mutexattr_t ma;
	pthread_condattr_t ca;
	pthread_t th;

	long altclk_ok, pshared_ok;

	struct {
		char altclk;	/* Want to use alternative clock */
		char pshared;	/* Want to use process-shared primitives */
		int type;	/* mutex type */
		char *descr;	/* Description of the case */

	} scenar[] = { {
	0, 0, PTHREAD_MUTEX_NORMAL, "Normal mutex"}
#ifdef USE_ALTCLK
	, {
	1, 0, PTHREAD_MUTEX_NORMAL, "Normal mutex + altclock cond"}
	, {
	1, 1, PTHREAD_MUTEX_NORMAL, "PShared mutex + altclock cond"}
#endif
	, {
	0, 1, PTHREAD_MUTEX_NORMAL, "Pshared mutex"}
#ifndef WITHOUT_XOPEN
	, {
	0, 0, PTHREAD_MUTEX_ERRORCHECK, "Errorcheck mutex"}
	, {
	0, 0, PTHREAD_MUTEX_RECURSIVE, "Recursive mutex"}
#ifdef USE_ALTCLK
	, {
	1, 0, PTHREAD_MUTEX_RECURSIVE, "Recursive mutex + altclock cond"}
	, {
	1, 0, PTHREAD_MUTEX_ERRORCHECK,
		    "Errorcheck mutex + altclock cond"}
	, {
	1, 1, PTHREAD_MUTEX_RECURSIVE,
		    "Recursive pshared mutex + altclock cond"}
	, {
	1, 1, PTHREAD_MUTEX_ERRORCHECK,
		    "Errorcheck pshared mutex + altclock cond"}
#endif
	, {
	0, 1, PTHREAD_MUTEX_RECURSIVE, "Recursive pshared mutex"}
	, {
	0, 1, PTHREAD_MUTEX_ERRORCHECK, "Errorcheck pshared mutex"}
#endif
	};

#define NSCENAR (sizeof(scenar) / sizeof(scenar[0]))

	output_init();

	/* Initialize the constants */
	altclk_ok = sysconf(_SC_CLOCK_SELECTION);
	if (altclk_ok > 0)
		altclk_ok = sysconf(_SC_MONOTONIC_CLOCK);
#ifndef USE_ALTCLK
	if (altclk_ok > 0)
		output
		    ("Implementation supports the MONOTONIC CLOCK but option is disabled in test.\n");
#endif

	pshared_ok = sysconf(_SC_THREAD_PROCESS_SHARED);

#if VERBOSE > 0
	output("Test starting\n");
	output(" Process-shared primitive %s be tested\n",
	       (pshared_ok > 0) ? "will" : "won't");
	output(" Alternative clock for cond %s be tested\n",
	       (altclk_ok > 0) ? "will" : "won't");
#endif

	ret = sem_init(&(data.semA), 0, 0);
	if (ret != 0) {
		UNRESOLVED(errno, "Unable to init sem A");
	}

	ret = sem_init(&(data.semB), 0, 0);
	if (ret != 0) {
		UNRESOLVED(errno, "Unable to init sem B");
	}

	struct timespec wait_ts = {0, 100000};
	for (i = 0; i < (int)NSCENAR; i++) {
#if VERBOSE > 1
		output("Starting test for %s\n", scenar[i].descr);
#endif

		/* Initialize the data structure */
		ret = pthread_mutexattr_init(&ma);
		if (ret != 0) {
			UNRESOLVED(ret, "Mutex attribute object init failed");
		}

		ret = pthread_mutexattr_settype(&ma, scenar[i].type);
		if (ret != 0) {
			UNRESOLVED(ret, "Unable to set mutex type");
		}

		if ((pshared_ok > 0) && (scenar[i].pshared != 0)) {
			ret =
			    pthread_mutexattr_setpshared(&ma,
							 PTHREAD_PROCESS_SHARED);
			if (ret != 0) {
				UNRESOLVED(ret,
					   "Unable to set mutex process-shared");
			}
		}

		ret = pthread_condattr_init(&ca);
		if (ret != 0) {
			UNRESOLVED(ret, "Cond attribute object init failed");
		}

		if ((pshared_ok > 0) && (scenar[i].pshared != 0)) {
			ret =
			    pthread_condattr_setpshared(&ca,
							PTHREAD_PROCESS_SHARED);
			if (ret != 0) {
				UNRESOLVED(ret,
					   "Unable to set cond process-shared");
			}
		}
#ifdef USE_ALTCLK
		if ((altclk_ok > 0) && (scenar[i].altclk != 0)) {
			ret = pthread_condattr_setclock(&ca, CLOCK_MONOTONIC);
			if (ret != 0) {
				UNRESOLVED(ret,
					   "Unable to set alternative (monotonic) clock for cond");
			}
		}
#endif

		ret = pthread_mutex_init(&(data.mtx), &ma);
		if (ret != 0) {
			UNRESOLVED(ret, "Unable to init mutex");
		}

		ret = pthread_cond_init(&(data.cnd), &ca);
		if (ret != 0) {
			UNRESOLVED(ret, "Unable to initialize condvar");
		}

		ret = pthread_mutexattr_gettype(&ma, &(data.type));
		if (ret != 0) {
			UNRESOLVED(ret, "Unable to get type from mutex attr");
		}

		data.bool = 0;

		/** Data is ready, create the thread */
#if VERBOSE > 1
		output("Initialization OK, starting thread\n");
#endif

		ret = pthread_create(&th, NULL, threaded, NULL);
		if (ret != 0) {
			UNRESOLVED(ret, "Thread creation failed");
		}

		/** Wait for the thread to be waiting */
		do {
			ret = sem_wait(&(data.semA));
		} while ((ret != 0) && (errno == EINTR));
		if (ret != 0) {
			UNRESOLVED(errno, "Sem wait failed in main");
		}

		ret = pthread_mutex_lock(&(data.mtx));
		if (ret != 0) {
			UNRESOLVED(ret, "Unable to lock mutex in main");
		}

		data.bool = 1;

		/** Cancel the thread */
		ret = pthread_cancel(th);
		if (ret != 0) {
			UNRESOLVED(ret, "Thread cancelation failed");
		}

		sched_yield();
		nanosleep(&wait_ts, NULL);

		ret = pthread_mutex_unlock(&(data.mtx));
		if (ret != 0) {
			UNRESOLVED(ret, "Unable to unlock mutex in main");
		}

		/** Wait for the thread to be executing second cleanup handler */
		do {
			ret = sem_wait(&(data.semA));
		} while ((ret != 0) && (errno == EINTR));
		if (ret != 0) {
			UNRESOLVED(errno, "Sem wait failed in main");
		}

		/** Here the child should own the mutex, we check this */
		ret = pthread_mutex_trylock(&(data.mtx));
		if (ret == 0) {
			FAILED
			    ("The child did not own the mutex inside the cleanup handler");
		}

		/** Let the cleanups go on */
		do {
			ret = sem_post(&(data.semB));
		} while ((ret != 0) && (errno == EINTR));
		if (ret != 0) {
			UNRESOLVED(errno, "Sem post failed in main");
		}

		/** Join the thread */
		ret = pthread_join(th, &rc);
		if (ret != 0) {
			UNRESOLVED(ret, "Unable to join the thread");
		}
		if (rc != PTHREAD_CANCELED) {
			FAILED("thread was not canceled");
		}
#if VERBOSE > 1
		output("Test passed for %s\n\n", scenar[i].descr);
#endif

		/* Destroy datas */
		ret = pthread_cond_destroy(&(data.cnd));
		if (ret != 0) {
			UNRESOLVED(ret, "Cond destroy failed");
		}

		ret = pthread_mutex_destroy(&(data.mtx));
		if (ret != 0) {
			UNRESOLVED(ret, "Mutex destroy failed");
		}

		ret = pthread_condattr_destroy(&ca);
		if (ret != 0) {
			UNRESOLVED(ret, "Cond attribute destroy failed");
		}

		ret = pthread_mutexattr_destroy(&ma);
		if (ret != 0) {
			UNRESOLVED(ret, "Mutex attr destroy failed");
		}
	}			/* Proceed to next case */

	ret = sem_destroy(&(data.semA));
	if (ret != 0) {
		UNRESOLVED(errno, "Sem destroy failed");
	}

	ret = sem_destroy(&(data.semB));
	if (ret != 0) {
		UNRESOLVED(errno, "Sem destroy failed");
	}

	PASSED;
}
