/*
 * 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 successfully\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 successfully\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 successfully\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 successfully 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");
    }
}

static unsigned int sc;

#ifdef STD_MAIN

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
