/*
 * 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 file is a helper file for the pthread_create tests
 * It defines the following objects:
 * scenarii: array of struct __scenario type.
 * NSCENAR : macro giving the total # of scenarii
 * scenar_init(): function to call before use the scenarii array.
 * scenar_fini(): function to call after end of use of the scenarii array.
 */

static struct __scenario {
	/*
	 * Object to hold the given configuration,
	 * and which will be used to create the threads
	 */
	pthread_attr_t ta;

	/* General parameters */
	/* 0 => joinable; 1 => detached */
	int detached;

	/* Scheduling parameters */
	/*
	 * 0 => sched policy is inherited;
	 * 1 => sched policy from the attr param
	 */
	int explicitsched;
	/* 0 => default; 1=> SCHED_FIFO; 2=> SCHED_RR */
	int schedpolicy;
	/*
	 * 0 => default sched param;
	 * 1 => max value for sched param;
	 * -1 => min value for sched param
	 */
	int schedparam;
	/*
	 * 0 => default contension scope;
	 * 1 => alternative contension scope
	 */
	int altscope;

	/* Stack parameters */
	/* 0 => system manages the stack; 1 => stack is provided */
	int altstack;
	/*
	 * 0 => default guardsize;
	 * 1=> guardsize is 0;
	 * 2=> guard is 1 page
	 *     -- this setting only affect system stacks (not user's).
	 */
	int guard;
	/*
	 * 0 => default stack size;
	 * 1 => stack size specified (min value)
	 *      -- ignored when stack is provided
	 */
	int altsize;

	/* Additionnal information */
	/* object description */
	char *descr;
	/* Stores the stack start when an alternate stack is required */
	void *bottom;
	/*
	 * This thread creation is expected to:
	 * 0 => succeed; 1 => fail; 2 => unknown
	 */
	int result;
	/*
	 * This semaphore is used to signal the end of
	 * the detached threads execution
	 */
	sem_t sem;
} scenarii[] =
#define CASE(det, expl, scp, spa, sco, sta, gua, ssi, desc, res)	\
{									\
	.detached = det,						\
	.explicitsched = expl,						\
	.schedpolicy = scp,						\
	.schedparam = spa,						\
	.altscope = sco,						\
	.altstack = sta,						\
	.guard = gua,							\
	.altsize = ssi,							\
	.descr = desc,							\
	.bottom = NULL,							\
	.result = res							\
}
#define CASE_POS(det, expl, scp, spa, sco, sta, gua, ssi, desc)		\
	CASE(det, expl, scp, spa, sco, sta, gua, ssi, desc, 0)
#define CASE_NEG(det, expl, scp, spa, sco, sta, gua, ssi, desc)		\
	CASE(det, expl, scp, spa, sco, sta, gua, ssi, desc, 1)
#define CASE_UNK(det, expl, scp, spa, sco, sta, gua, ssi, desc)		\
	CASE(det, expl, scp, spa, sco, sta, gua, ssi, desc, 2)
/*
 * This array gives the different combinations of threads
 * attributes for the testcases.
 *
 * Some combinations must be avoided.
 * -> Do not have a detached thread use an alternative stack;
 *     as we don't know when the thread terminates to free the stack memory
 * -> ... (to be completed)
 */
{
	/* Unary tests */
	CASE_POS(0, 0, 0, 0, 0, 0, 0, 0, "default"),
	    CASE_POS(1, 0, 0, 0, 0, 0, 0, 0, "detached"),
	    CASE_POS(0, 1, 0, 0, 0, 0, 0, 0, "Explicit sched"),
	    CASE_UNK(0, 0, 1, 0, 0, 0, 0, 0, "FIFO Policy"),
	    CASE_UNK(0, 0, 2, 0, 0, 0, 0, 0, "RR Policy"),
	    CASE_UNK(0, 0, 0, 1, 0, 0, 0, 0, "Max sched param"),
	    CASE_UNK(0, 0, 0, -1, 0, 0, 0, 0, "Min sched param"),
	    CASE_POS(0, 0, 0, 0, 1, 0, 0, 0, "Alternative contension scope"),
	    CASE_POS(0, 0, 0, 0, 0, 1, 0, 0, "Alternative stack"),
	    CASE_POS(0, 0, 0, 0, 0, 0, 1, 0, "No guard size"),
	    CASE_UNK(0, 0, 0, 0, 0, 0, 2, 0, "1p guard size"),
	    CASE_POS(0, 0, 0, 0, 0, 0, 0, 1, "Min stack size"),
	    /* Stack play */
	    CASE_POS(0, 0, 0, 0, 0, 0, 1, 1, "Min stack size, no guard"),
	    CASE_UNK(0, 0, 0, 0, 0, 0, 2, 1, "Min stack size, 1p guard"),
	    CASE_POS(1, 0, 0, 0, 0, 1, 0, 0, "Detached, Alternative stack"),
	    CASE_POS(1, 0, 0, 0, 0, 0, 1, 1,
		     "Detached, Min stack size, no guard"), CASE_UNK(1, 0, 0, 0,
								     0, 0, 2, 1,
								     "Detached, Min stack size, 1p guard"),
	    /*
	     * Scheduling play
	     *   -- all results are unknown since it might depend on
	     *      the user priviledges
	     */
CASE_UNK(0, 1, 1, 1, 0, 0, 0, 0, "Explicit FIFO max param"),
	    CASE_UNK(0, 1, 2, 1, 0, 0, 0, 0,
				 "Explicit RR max param"),
	    CASE_UNK(0, 1, 1, -1, 0, 0, 0, 0,
				 "Explicit FIFO min param"),
	    CASE_UNK(0, 1, 2, -1, 0, 0, 0, 0,
				 "Explicit RR min param"),
	    CASE_UNK(0, 1, 1, 1, 1, 0, 0, 0,
				 "Explicit FIFO max param, alt scope"),
	    CASE_UNK(0, 1, 2, 1, 1, 0, 0, 0,
				 "Explicit RR max param, alt scope"),
	    CASE_UNK(0, 1, 1, -1, 1, 0, 0, 0,
				 "Explicit FIFO min param, alt scope"),
	    CASE_UNK(0, 1, 2, -1, 1, 0, 0, 0,
				 "Explicit RR min param, alt scope"),
	    CASE_UNK(1, 1, 1, 1, 0, 0, 0, 0,
				 "Detached, explicit FIFO max param"),
	    CASE_UNK(1, 1, 2, 1, 0, 0, 0, 0,
				 "Detached, explicit RR max param"),
	    CASE_UNK(1, 1, 1, -1, 0, 0, 0, 0,
				 "Detached, explicit FIFO min param"),
	    CASE_UNK(1, 1, 2, -1, 0, 0, 0, 0,
				 "Detached, explicit RR min param"),
	    CASE_UNK(1, 1, 1, 1, 1, 0, 0, 0,
				 "Detached, explicit FIFO max param,"
				 " alt scope"), CASE_UNK(1, 1, 2, 1,
								     1,
								     0,
								     0,
								     0,
								     "Detached, explicit RR max param,"
								     " alt scope"),
	    CASE_UNK(1, 1, 1, -1, 1, 0, 0, 0,
				 "Detached, explicit FIFO min param,"
				 " alt scope"), CASE_UNK(1, 1, 2,
								     -1,
								     1,
								     0,
								     0,
								     0,
								     "Detached, explicit RR min param,"
								     " alt scope"),};

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

/*
 * This function will initialize every pthread_attr_t object
 * in the scenarii array
 */
static void scenar_init(void)
{
	int ret = 0;
	unsigned int i;
	int old;
	long pagesize, minstacksize;
	long tsa, tss, tps;

	pagesize = sysconf(_SC_PAGESIZE);
	minstacksize = sysconf(_SC_THREAD_STACK_MIN);
	tsa = sysconf(_SC_THREAD_ATTR_STACKADDR);
	tss = sysconf(_SC_THREAD_ATTR_STACKSIZE);
	tps = sysconf(_SC_THREAD_PRIORITY_SCHEDULING);

#if VERBOSE > 0
	output("System abilities:\n");
	output(" TSA: %li\n", tsa);
	output(" TSS: %li\n", tss);
	output(" TPS: %li\n", tps);
	output(" pagesize: %li\n", pagesize);
	output(" min stack size: %li\n", minstacksize);
#endif

	if (minstacksize % pagesize)
		UNTESTED("The min stack size is not a multiple"
			 " of the page size");

	for (i = 0; i < NSCENAR; i++) {
#if VERBOSE > 2
		output("Initializing attribute for scenario %i: %s\n",
		       i, scenarii[i].descr);
#endif

		ret = pthread_attr_init(&scenarii[i].ta);
		if (ret != 0)
			UNRESOLVED(ret, "Failed to initialize a"
				   " thread attribute object");

		/* Set the attributes according to the scenario */
		if (scenarii[i].detached == 1) {
			ret = pthread_attr_setdetachstate(&scenarii[i].ta,
							  PTHREAD_CREATE_DETACHED);
			if (ret != 0)
				UNRESOLVED(ret, "Unable to set detachstate");
		} else {
			ret =
			    pthread_attr_getdetachstate(&scenarii[i].ta, &old);
			if (ret != 0)
				UNRESOLVED(ret, "Unable to get detachstate"
					   " from initialized attribute");
			if (old != PTHREAD_CREATE_JOINABLE)
				FAILED("The default attribute is not"
				       " PTHREAD_CREATE_JOINABLE");
		}
#if VERBOSE > 4
		output("Detach state was set sucessfully\n");
#endif

		/* Sched related attributes */
		/*
		 * This routine is dependent on the Thread Execution
		 * Scheduling option
		 */
		if (tps > 0) {
			if (scenarii[i].explicitsched == 1)
				ret =
				    pthread_attr_setinheritsched(&scenarii
								 [i].ta,
								 PTHREAD_EXPLICIT_SCHED);
			else
				ret =
				    pthread_attr_setinheritsched(&scenarii
								 [i].ta,
								 PTHREAD_INHERIT_SCHED);
			if (ret != 0)
				UNRESOLVED(ret, "Unable to set inheritsched"
					   " attribute");
#if VERBOSE > 4
			output("inheritsched state was set sucessfully\n");
#endif
		}
#if VERBOSE > 4
		else
			output("TPS unsupported => inheritsched parameter"
			       " untouched\n");
#endif

		if (tps > 0) {
			if (scenarii[i].schedpolicy == 1)
				ret =
				    pthread_attr_setschedpolicy(&scenarii[i].ta,
								SCHED_FIFO);
			if (scenarii[i].schedpolicy == 2)
				ret =
				    pthread_attr_setschedpolicy(&scenarii[i].ta,
								SCHED_RR);
			if (ret != 0)
				UNRESOLVED(ret, "Unable to set the"
					   " sched policy");
#if VERBOSE > 4
			if (scenarii[i].schedpolicy)
				output("Sched policy was set sucessfully\n");
			else
				output("Sched policy untouched\n");
#endif
		}
#if VERBOSE > 4
		else
			output("TPS unsupported => sched policy parameter"
			       " untouched\n");
#endif

		if (scenarii[i].schedparam != 0) {
			struct sched_param sp;

			ret =
			    pthread_attr_getschedpolicy(&scenarii[i].ta, &old);
			if (ret != 0)
				UNRESOLVED(ret, "Unable to get sched policy"
					   " from attribute");

			if (scenarii[i].schedparam == 1)
				sp.sched_priority = sched_get_priority_max(old);
			if (scenarii[i].schedparam == -1)
				sp.sched_priority = sched_get_priority_min(old);

			ret = pthread_attr_setschedparam(&scenarii[i].ta, &sp);
			if (ret != 0)
				UNRESOLVED(ret,
					   "Failed to set the sched param");

#if VERBOSE > 4
			output("Sched param was set sucessfully to %i\n",
			       sp.sched_priority);
		} else {
			output("Sched param untouched\n");
#endif
		}

		if (tps > 0) {
			ret = pthread_attr_getscope(&scenarii[i].ta, &old);
			if (ret != 0)
				UNRESOLVED(ret, "Failed to get contension"
					   " scope from thread attribute");

			if (scenarii[i].altscope != 0) {
				if (old == PTHREAD_SCOPE_PROCESS)
					old = PTHREAD_SCOPE_SYSTEM;
				else
					old = PTHREAD_SCOPE_PROCESS;

				ret =
				    pthread_attr_setscope(&scenarii[i].ta, old);

#if VERBOSE > 0
				if (ret != 0)
					output("WARNING: The TPS option is"
					       " claimed to be supported but"
					       " setscope fails\n");
#endif

#if VERBOSE > 4
				output("Contension scope set to %s\n",
				       old == PTHREAD_SCOPE_PROCESS ?
				       "PTHREAD_SCOPE_PROCESS" :
				       "PTHREAD_SCOPE_SYSTEM");
			} else {
				output("Contension scope untouched (%s)\n",
				       old == PTHREAD_SCOPE_PROCESS ?
				       "PTHREAD_SCOPE_PROCESS" :
				       "PTHREAD_SCOPE_SYSTEM");
#endif
			}
		}
#if VERBOSE > 4
		else
			output("TPS unsupported => sched contension scope"
			       " parameter untouched\n");
#endif

		/* Stack related attributes */
		/*
		 * This routine is dependent on the Thread Stack Address
		 * Attribute and Thread Stack Size Attribute options
		 */
		if ((tss > 0) && (tsa > 0)) {
			if (scenarii[i].altstack != 0) {
				/*
				 * This is slightly more complicated.
				 * We need to alloc a new stackand free
				 * it upon test termination.
				 * We will alloc with a simulated guardsize
				 * of 1 pagesize */
				scenarii[i].bottom = malloc(minstacksize + pagesize);
				if (scenarii[i].bottom == NULL)
					UNRESOLVED(errno, "Unable to alloc"
						   " enough memory for"
						   " alternative stack");

				ret = pthread_attr_setstack(&scenarii[i].ta,
							    scenarii[i].bottom,
							    minstacksize);
				if (ret != 0)
					UNRESOLVED(ret, "Failed to specify"
						   " alternate stack");

#if VERBOSE > 1
				output("Alternate stack created successfully."
				       " Bottom=%p, Size=%i\n",
				       scenarii[i].bottom, minstacksize);
#endif
			}
		}
#if VERBOSE > 4
		else
			output("TSA or TSS unsupported => "
			       "No alternative stack\n");
#endif

#ifndef WITHOUT_XOPEN
		if (scenarii[i].guard != 0) {
			if (scenarii[i].guard == 1)
				ret =
				    pthread_attr_setguardsize(&scenarii[i].ta,
							      0);
			if (scenarii[i].guard == 2)
				ret =
				    pthread_attr_setguardsize(&scenarii[i].ta,
							      pagesize);
			if (ret != 0)
				UNRESOLVED(ret, "Unable to set guard area"
					   " size in thread stack");
#if VERBOSE > 4
			output("Guard size set to %i\n",
			       scenarii[i].guard == 1 ? 1 : pagesize);
#endif
		}
#endif

		if (tss > 0) {
			if (scenarii[i].altsize != 0) {
				ret = pthread_attr_setstacksize(&scenarii[i].ta,
								minstacksize);
				if (ret != 0)
					UNRESOLVED(ret, "Unable to change"
						   " stack size");
#if VERBOSE > 4
				output("Stack size set to %i (this is the "
				       "min)\n", minstacksize);
#endif
			}
		}
#if VERBOSE > 4
		else
			output("TSS unsupported => stack size unchanged\n");
#endif

		ret = sem_init(&scenarii[i].sem, 0, 0);
		if (ret == -1)
			UNRESOLVED(errno, "Unable to init a semaphore");

	}
#if VERBOSE > 0
	output("All %i thread attribute objects were initialized\n\n", NSCENAR);
#endif
}

/*
 * This function will free all resources consumed
 * in the scenar_init() routine
 */
static void scenar_fini(void)
{
	int ret = 0;
	unsigned int i;

	for (i = 0; i < NSCENAR; i++) {
		if (scenarii[i].bottom != NULL)
			free(scenarii[i].bottom);

		ret = sem_destroy(&scenarii[i].sem);
		if (ret == -1)
			UNRESOLVED(errno, "Unable to destroy a semaphore");

		ret = pthread_attr_destroy(&scenarii[i].ta);
		if (ret != 0)
			UNRESOLVED(ret, "Failed to destroy a thread"
				   " attribute object");
	}
}

#ifdef STD_MAIN

static unsigned int sc;

static void *threaded(void *arg);

int main(void)
{
	int ret = 0;
	pthread_t child;

	output_init();
	scenar_init();

	for (sc = 0; sc < NSCENAR; sc++) {
#if VERBOSE > 0
		output("-----\n");
		output("Starting test with scenario (%i): %s\n",
		       sc, scenarii[sc].descr);
#endif

		ret = pthread_create(&child, &scenarii[sc].ta, threaded, NULL);
		switch (scenarii[sc].result) {
		case 0:	/* Operation was expected to succeed */
			if (ret != 0)
				UNRESOLVED(ret, "Failed to create this thread");
			break;
		case 1:	/* Operation was expected to fail */
			if (ret == 0)
				UNRESOLVED(-1, "An error was expected but the"
					   " thread creation succeeded");
			break;
		case 2:	/* We did not know the expected result */
		default:
#if VERBOSE > 0
			if (ret == 0)
				output("Thread has been created successfully"
				       " for this scenario\n");
			else
				output("Thread creation failed with the error:"
				       " %s\n", strerror(ret));
#endif
		}
		if (ret == 0) {
			if (scenarii[sc].detached == 0) {
				ret = pthread_join(child, NULL);
				if (ret != 0)
					UNRESOLVED(ret, "Unable to join a"
						   " thread");
			} else {
				/* Just wait for the thread to terminate */
				do {
					ret = sem_wait(&scenarii[sc].sem);
				} while ((ret == -1) && (errno == EINTR));
				if (ret == -1)
					UNRESOLVED(errno, "Failed to wait for"
						   " the semaphore");
			}
		}
	}

	scenar_fini();
#if VERBOSE > 0
	output("-----\n");
	output("All test data destroyed\n");
	output("Test PASSED\n");
#endif

	PASSED;
}
#endif
