/* ``The contents of this file are subject to the Erlang Public License,
 * Version 1.1, (the "License"); you may not use this file except in
 * compliance with the License. You should have received a copy of the
 * Erlang Public License along with this software. If not, it can be
 * retrieved via the world wide web at http://www.erlang.org/.
 * 
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 * 
 * The Initial Developer of the Original Code is Ericsson Utvecklings AB.
 * Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
 * AB. All Rights Reserved.''
 * 
 *     $Id$
 */

/*
 * Description: A Thread library for use in the ERTS and other OTP
 *              applications.
 * Author: Rickard Green
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#if defined(ETHR_PTHREADS)

#ifdef ETHR_TIME_WITH_SYS_TIME
#  include <time.h>
#  include <sys/time.h>
#else
#  ifdef ETHR_HAVE_SYS_TIME_H
#    include <sys/time.h>
#  else
#    include <time.h>
#  endif
#endif
#include <sys/types.h>
#include <unistd.h>

#elif defined(ETHR_WIN32_THREADS)

#undef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <process.h>
#include <winerror.h>

#else
#error "Missing thread implementation"
#endif

#undef ETHR_TRY_INLINE_FUNCS
#include "ethread.h"

#ifndef ETHR_HAVE_ETHREAD_DEFINES
#error Missing configure defines
#endif

/*
 * ----------------------------------------------------------------------------
 * Common stuff
 * ----------------------------------------------------------------------------
 */

#define ETHR_MAX_THREADS 2048 /* Has to be an even power of 2 */

#if ETHR_XCHK
#  if defined(EDEADLK)
#    define ETHR_XCHK_ERROR_DEADLOCK EDEADLK
#  elif defined(EDEADLOCK)
#    define ETHR_XCHK_ERROR_DEADLOCK EDEADLOCK
#  else
#    define ETHR_XCHK_ERROR_DEADLOCK EINVAL
#  endif
static int xchk_locking_mutex(ethr_mutex *mutex);
static int xchk_unlocking_mutex(ethr_mutex *mutex);
static long xchk_get_my_mutex_lock_count(ethr_mutex *mutex);
static int xchk_init(void);
#endif

static int ethr_not_inited = 1;

#ifdef DEBUG
#define ASSERT(A) ((void) ((A) ? 1 : assert_failed(__FILE__, __LINE__, #A)))
static int assert_failed(char *f, int l, char *a);
#else
#define ASSERT(A) ((void) 1)
#endif

static void *(*allocp)(size_t) = malloc;
static void *(*reallocp)(void *, size_t) = realloc;
static void (*freep)(void *) = free;

void *(*thread_create_prepare_func)(void) = NULL;
void (*thread_create_parent_func)(void *) = NULL;
void (*thread_create_child_func)(void *) = NULL;

typedef struct ethr_xhndl_list_ ethr_xhndl_list;
struct ethr_xhndl_list_ {
    ethr_xhndl_list *next;
    void (*funcp)(void);
};

ethr_mutex xhndl_mtx;
ethr_xhndl_list *xhndl_list;

static int
init_common(ethr_init_data *id)
{
    int res;
    if (id) {
	allocp				= id->alloc;
	reallocp			= id->realloc;
	freep				= id->free;
	thread_create_prepare_func	= id->thread_create_prepare_func;
	thread_create_parent_func	= id->thread_create_parent_func;
	thread_create_child_func	= id->thread_create_child_func;
    }
    if (!allocp || !reallocp || !freep)
	return EINVAL;

    xhndl_list = NULL;

    res = ethr_mutex_init(&xhndl_mtx);
    if (res != 0)
	return res;

    res = ethr_mutex_set_forksafe(&xhndl_mtx);
    if (res != 0 && res != ENOTSUP)
	return res;

#if ETHR_XCHK
    return xchk_init();
#else
    return 0;
#endif
}

int
ethr_install_exit_handler(void (*funcp)(void))
{
    ethr_xhndl_list *xhp;
    int res;

#if ETHR_XCHK
    if (ethr_not_inited) {
	ASSERT(0);
	return EACCES;
    }
#endif

    if (!funcp)
	return EINVAL;

    xhp = (ethr_xhndl_list *) (*allocp)(sizeof(ethr_xhndl_list));
    if (!xhp)
	return ENOMEM;

    res = ethr_mutex_lock(&xhndl_mtx);
    if (res != 0) {
	(*freep)((void *) xhp);
	return res;
    }

    xhp->funcp = funcp;
    xhp->next = xhndl_list;
    xhndl_list = xhp;

    res = ethr_mutex_unlock(&xhndl_mtx);
    if (res != 0)
	abort();

    return res;
}

static void
run_exit_handlers(void)
{
    int res;
    ethr_xhndl_list *xhp;

    res = ethr_mutex_lock(&xhndl_mtx);
    if (res != 0)
	abort();

    xhp = xhndl_list;

    res = ethr_mutex_unlock(&xhndl_mtx);
    if (res != 0)
	abort();

    for (; xhp; xhp = xhp->next)
	(*xhp->funcp)();
}

#if defined(ETHR_PTHREADS)
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
 * pthread implementation                                                    *
\*                                                                           */

typedef struct {
    pthread_mutex_t mtx;
    pthread_cond_t cnd;
    int initialized;
    void *(*thr_func)(void *);
    void *arg;
    void *prep_func_res;
} thr_wrap_data_;

static int no_ethreads;
static ethr_mutex no_ethrs_mtx;

#ifndef ETHR_HAVE_PTHREAD_ATFORK
#define ETHR_HAVE_PTHREAD_ATFORK 0
#endif

#if !ETHR_HAVE_PTHREAD_ATFORK
#warning "Cannot enforce fork-safety"
#endif

#ifndef ETHR_HAVE_OPTIMIZED_ATOMIC_OPS
pthread_mutex_t ethr_atomic_mutex[1 << ETHR_ATOMIC_ADDR_BITS];
#endif

/*
 * ----------------------------------------------------------------------------
 * Static functions
 * ----------------------------------------------------------------------------
 */

/*
 * Functions with safe_ prefix aborts on failure. To be used when
 * we cannot recover after failure.
 */

static ETHR_INLINE void
safe_mutex_lock(pthread_mutex_t *mtxp)
{
    int res = pthread_mutex_lock(mtxp);
    if (res != 0)
	abort();
}

static ETHR_INLINE void
safe_mutex_unlock(pthread_mutex_t *mtxp)
{
    int res = pthread_mutex_unlock(mtxp);
    if (res != 0)
	abort();
}

static ETHR_INLINE void
safe_cond_signal(pthread_cond_t *cndp)
{
    int res = pthread_cond_signal(cndp);
    if (res != 0)
	abort();
}

#ifdef ETHR_HAVE_ETHR_REC_MUTEX_INIT

static volatile int rec_mtx_attr_need_init = 1;
static pthread_mutexattr_t rec_mtx_attr;

static int init_rec_mtx_attr(void);

#endif

#if ETHR_HAVE_PTHREAD_ATFORK

static ethr_mutex forksafe_mtx = ETHR_MUTEX_INITER;

static void lock_mutexes(void)
{
    ethr_mutex *m = &forksafe_mtx;
    do {

	safe_mutex_lock(&m->pt_mtx);

	m = m->next;

    } while (m != &forksafe_mtx);
}

static void unlock_mutexes(void)
{
    ethr_mutex *m = forksafe_mtx.prev;
    do {

	safe_mutex_unlock(&m->pt_mtx);

	m = m->prev;

    } while (m->next != &forksafe_mtx);
}

#if ETHR_INIT_MUTEX_IN_CHILD_AT_FORK

static void reinit_mutexes(void)
{
    ethr_mutex *m = forksafe_mtx.prev;
    do {
	pthread_mutexattr_t *attrp = NULL;

#ifdef ETHR_HAVE_ETHR_REC_MUTEX_INIT
	if (m->is_rec_mtx) {
	    if (rec_mtx_attr_need_init) {
		int res = init_rec_mtx_attr();
		if (res != 0)
		    abort();
	    }
	    attrp = &rec_mtx_attr;
	}
#endif
	if (pthread_mutex_init(&m->pt_mtx, attrp) != 0)
	    abort();

	m = m->prev;

    } while (m->next != &forksafe_mtx);
}

#endif

static int
init_forksafe(void)
{
    static int init_done = 0;
    int res = 0;

    if (init_done)
	return res;

    forksafe_mtx.prev = &forksafe_mtx;
    forksafe_mtx.next = &forksafe_mtx;

    res = pthread_atfork(lock_mutexes,
			 unlock_mutexes,
#if ETHR_INIT_MUTEX_IN_CHILD_AT_FORK
			 reinit_mutexes
#else
			 unlock_mutexes
#endif
	);

    init_done = 1;
    return res;
}

#endif


#ifdef ETHR_HAVE_ETHR_REC_MUTEX_INIT

#if defined(ETHR_HAVE_PTHREAD_MUTEXATTR_SETTYPE)

#define SET_REC_MUTEX_ATTR(AP) \
  pthread_mutexattr_settype((AP), PTHREAD_MUTEX_RECURSIVE);

#elif defined(ETHR_HAVE_PTHREAD_MUTEXATTR_SETKIND_NP)

#define SET_REC_MUTEX_ATTR(AP) \
  pthread_mutexattr_setkind_np((AP), PTHREAD_MUTEX_RECURSIVE_NP);

#else

#error "Don't know how to set recursive mutex attributes"

#endif

static int
init_rec_mtx_attr(void)
{
    int res, mres;
    static pthread_mutex_t attrinit_mtx = PTHREAD_MUTEX_INITIALIZER;

    mres = pthread_mutex_lock(&attrinit_mtx);
    if (mres != 0)
	return mres;
    /* Got here under race conditions; check again ... */
    if (!rec_mtx_attr_need_init)
	res = 0;
    else {
	res = pthread_mutexattr_init(&rec_mtx_attr);
	if (res == 0) {
	    res = SET_REC_MUTEX_ATTR(&rec_mtx_attr);
	    if (res == 0)
		rec_mtx_attr_need_init = 0;
	    else
		(void) pthread_mutexattr_destroy(&rec_mtx_attr);
	}
    }

    mres = pthread_mutex_unlock(&attrinit_mtx);
    if (mres != 0)
	return mres;
    return res;
}

#endif /* #if ETHR_HAVE_ETHR_REC_MUTEX_INIT */

static ETHR_INLINE void thr_exit_cleanup(void)
{
    run_exit_handlers();
    safe_mutex_lock(&no_ethrs_mtx.pt_mtx);
    ASSERT(no_ethreads > 0);
    no_ethreads--;
    safe_mutex_unlock(&no_ethrs_mtx.pt_mtx);
#if ETHR_XCHK
    if (ethr_xchk_have_locked_mutexes())
	abort();
#endif
}

static void *thr_wrapper(void *vtwd)
{
    void *res;
    thr_wrap_data_ *twd = (thr_wrap_data_ *) vtwd;
    void *(*thr_func)(void *) = twd->thr_func;
    void *arg = twd->arg;

    safe_mutex_lock(&twd->mtx);

    if (thread_create_child_func)
	(*thread_create_child_func)(twd->prep_func_res);

    twd->initialized = 1;

    safe_cond_signal(&twd->cnd);
    safe_mutex_unlock(&twd->mtx);

    res = (*thr_func)(arg);
    thr_exit_cleanup();
    return res;
}


/*
 * ----------------------------------------------------------------------------
 * Exported functions
 * ----------------------------------------------------------------------------
 */

int
ethr_init(ethr_init_data *id)
{
    int res;

    if (!ethr_not_inited)
	return EINVAL;

    ethr_not_inited = 0;

    res = init_common(id);
    if (res != 0)
	goto error;

#if ETHR_HAVE_PTHREAD_ATFORK
    init_forksafe();
#endif

    no_ethreads = 1;
    res = ethr_mutex_init(&no_ethrs_mtx);
    if (res != 0)
	goto error;
    res = ethr_mutex_set_forksafe(&no_ethrs_mtx);
    if (res != 0 && res != ENOTSUP)
	goto error;

#ifndef ETHR_HAVE_OPTIMIZED_ATOMIC_OPS
    {
	int i;
	for (i = 0; i < (1 << ETHR_ATOMIC_ADDR_BITS); i++) {
	    res = pthread_mutex_init(&ethr_atomic_mutex[i], NULL);
	    if (res != 0)
		goto error;
	}
    }
#endif

    return 0;

 error:
    ethr_not_inited = 1;
    return res;

}


int
ethr_thr_create(ethr_tid *tid, void * (*func)(void *), void *arg, int detached)
{
    thr_wrap_data_ twd;
    pthread_attr_t attr;
    int res, dres;

    twd.initialized = 0;
    twd.thr_func = func;
    twd.arg = arg;

#if ETHR_XCHK
    if (ethr_not_inited) {
	ASSERT(0);
	return EACCES;
    }
    if (!tid || !func) {
	ASSERT(0);
	return EINVAL;
    }
#endif

    /* Call prepare func if it exist */
    if (thread_create_prepare_func)
	twd.prep_func_res = (*thread_create_prepare_func)();
    else
	twd.prep_func_res = NULL;

    /* Set som thread attributes */
    res = pthread_attr_init(&attr);
    if (res != 0)
	goto cleanup_parent_func;
    res = pthread_mutex_init(&twd.mtx, NULL);
    if (res != 0)
	goto cleanup_attr_destroy;
    res = pthread_cond_init(&twd.cnd, NULL);
    if (res != 0)
	goto cleanup_mutex_destroy;

    /* Schedule child thread in system scope (if possible) ... */
    res = pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);
    if (res != 0 && res != ENOTSUP)
	goto cleanup_cond_destroy;
    /* Detached or joinable... */
    res = pthread_attr_setdetachstate(&attr,
				      (detached
				       ? PTHREAD_CREATE_DETACHED
				       : PTHREAD_CREATE_JOINABLE));
    if (res != 0)
	goto cleanup_cond_destroy;
    
    res = pthread_mutex_lock(&twd.mtx);

    if (res != 0)
	goto cleanup_cond_destroy;

    safe_mutex_lock(&no_ethrs_mtx.pt_mtx);
    if (no_ethreads < ETHR_MAX_THREADS) {
	no_ethreads++;
	safe_mutex_unlock(&no_ethrs_mtx.pt_mtx);
    }
    else {
	res = EAGAIN;
	safe_mutex_unlock(&no_ethrs_mtx.pt_mtx);
	goto cleanup_mutex_unlock;
    }

    res = pthread_create((pthread_t *) tid, &attr, thr_wrapper, (void *) &twd);

    if (res != 0) {
	safe_mutex_lock(&no_ethrs_mtx.pt_mtx);
	ASSERT(no_ethreads > 0);
	no_ethreads--;
	safe_mutex_unlock(&no_ethrs_mtx.pt_mtx);
    }
    else {

	/* Wait for child to initialize... */
	while (!twd.initialized) {
	    res = pthread_cond_wait(&twd.cnd, &twd.mtx);
	    if (res != 0 && res != EINTR)
		break;
	}

    }

    /* Cleanup... */
 cleanup_mutex_unlock:
    dres = pthread_mutex_unlock(&twd.mtx);
    if (res == 0)
	res = dres;
 cleanup_cond_destroy:
    dres = pthread_cond_destroy(&twd.cnd);
    if (res == 0)
	res = dres;
 cleanup_mutex_destroy:
    dres = pthread_mutex_destroy(&twd.mtx);
    if (res == 0)
	res = dres;
 cleanup_attr_destroy:
    dres = pthread_attr_destroy(&attr);
    if (res == 0)
	res = dres;
 cleanup_parent_func:
    if (thread_create_parent_func)
	(*thread_create_parent_func)(twd.prep_func_res);

    return res;
}

int
ethr_thr_join(ethr_tid tid, void **res)
{
#if ETHR_XCHK
    if (ethr_not_inited) {
	ASSERT(0);
	return EACCES;
    }
#endif
    return pthread_join((pthread_t) tid, res);
}

int
ethr_thr_detach(ethr_tid tid)
{
#if ETHR_XCHK
    if (ethr_not_inited) {
	ASSERT(0);
	return EACCES;
    }
#endif
    return pthread_detach((pthread_t) tid);
}

void
ethr_thr_exit(void *res)
{
#if ETHR_XCHK
    if (ethr_not_inited) {
	ASSERT(0);
	return;
    }
#endif
    thr_exit_cleanup();
    pthread_exit(res);
}

ethr_tid
ethr_self(void)
{
    return (ethr_tid) pthread_self();
}

int
ethr_equal_tids(ethr_tid tid1, ethr_tid tid2)
{
    return pthread_equal((pthread_t) tid1, (pthread_t) tid2);
}


/*
 * Mutex functions
 */


int
ethr_mutex_init(ethr_mutex *mtx)
{
#if ETHR_XCHK
    if (ethr_not_inited) {
	ASSERT(0);
	return EACCES;
    }
    if (!mtx) {
	ASSERT(0);
	return EINVAL;
    }
    mtx->initialized = ETHR_MUTEX_INITIALIZED;
#endif
    mtx->prev = NULL;
    mtx->next = NULL;
    mtx->is_rec_mtx = 0;
    return pthread_mutex_init(&mtx->pt_mtx, NULL);
}

#ifdef ETHR_HAVE_ETHR_REC_MUTEX_INIT

int
ethr_rec_mutex_init(ethr_mutex *mtx)
{
#if ETHR_XCHK
    if (ethr_not_inited) {
	ASSERT(0);
	return EACCES;
    }
    if (!mtx) {
	ASSERT(0);
	return EINVAL;
    }
    mtx->initialized = ETHR_MUTEX_INITIALIZED;
#endif
    if (rec_mtx_attr_need_init)
	init_rec_mtx_attr();

    mtx->prev = NULL;
    mtx->next = NULL;
    mtx->is_rec_mtx = 1;
    return pthread_mutex_init(&mtx->pt_mtx, &rec_mtx_attr);
}

#endif /* #if ETHR_HAVE_ETHR_REC_MUTEX_INIT */

int
ethr_mutex_destroy(ethr_mutex *mtx)
{
#if ETHR_XCHK
    if (ethr_not_inited) {
	ASSERT(0);
	return EACCES;
    }
    if (!mtx || mtx->initialized != ETHR_MUTEX_INITIALIZED) {
	ASSERT(0);
	return EINVAL;
    }
    if (xchk_get_my_mutex_lock_count(mtx) != 0)
	return EINVAL;
#endif
    if (mtx->next) {
	ASSERT(mtx->prev);
	ethr_mutex_unset_forksafe(mtx);
    }
#if ETHR_XCHK
    mtx->initialized = 0;
#endif
    return pthread_mutex_destroy(&mtx->pt_mtx);
}

int ethr_mutex_set_forksafe(ethr_mutex *mtx)
{
    int res;
#if ETHR_XCHK
    if (ethr_not_inited) {
	ASSERT(0);
	return EACCES;
    }
    if (!mtx || mtx->initialized != ETHR_MUTEX_INITIALIZED) {
	ASSERT(0);
	return EINVAL;
    }
#endif
#if ETHR_HAVE_PTHREAD_ATFORK
    res = pthread_mutex_lock(&forksafe_mtx.pt_mtx);
    if (res != 0)
	return res;
    if (!forksafe_mtx.next) {
	ASSERT(!forksafe_mtx.prev);
	init_forksafe();
    }
    if (mtx->next) {
	/* forksafe already set for this mutex */ 
	ASSERT(mtx->prev);
    }
    else {
	mtx->next = forksafe_mtx.next;
	mtx->prev = &forksafe_mtx;
	forksafe_mtx.next->prev = mtx;
	forksafe_mtx.next = mtx;
    }

    res = pthread_mutex_unlock(&forksafe_mtx.pt_mtx);

#else /* #if ETHR_HAVE_PTHREAD_ATFORK */
    res = ENOTSUP;
#endif /* #if ETHR_HAVE_PTHREAD_ATFORK */
    return res;
}

int ethr_mutex_unset_forksafe(ethr_mutex *mtx)
{
    int res;
#if ETHR_XCHK
    if (ethr_not_inited) {
	ASSERT(0);
	return EACCES;
    }
    if (!mtx || mtx->initialized != ETHR_MUTEX_INITIALIZED) {
	ASSERT(0);
	return EINVAL;
    }
#endif
#if ETHR_HAVE_PTHREAD_ATFORK
    res = pthread_mutex_lock(&forksafe_mtx.pt_mtx);
    if (res != 0)
	return res;
    if (!forksafe_mtx.next) {
	ASSERT(!forksafe_mtx.prev);
	init_forksafe();
    }
    if (!mtx->next) {
	/* forksafe already unset for this mutex */ 
	ASSERT(!mtx->prev);
    }
    else {
	mtx->prev->next = mtx->next;
	mtx->next->prev = mtx->prev;
	mtx->next = NULL;
	mtx->prev = NULL;
    }
    res = pthread_mutex_unlock(&forksafe_mtx.pt_mtx);

#else /* #if ETHR_HAVE_PTHREAD_ATFORK */
    res = ENOTSUP;
#endif /* #if ETHR_HAVE_PTHREAD_ATFORK */
    return res;
}

int
ethr_mutex_trylock(ethr_mutex *mtx)
{
    int res;
#if ETHR_XCHK
    if (ethr_not_inited) {
	ASSERT(0);
	return EACCES;
    }
    if (!mtx || mtx->initialized != ETHR_MUTEX_INITIALIZED) {
	ASSERT(0);
	return EINVAL;
    }
    res = xchk_locking_mutex(mtx);
    if (res != 0)
	return res;
#endif
    res = pthread_mutex_trylock(&mtx->pt_mtx);
#if ETHR_XCHK
    if (res != 0)
	(void) xchk_unlocking_mutex(mtx);
#endif
    return res;
}

int
ethr_mutex_lock(ethr_mutex *mtx)
{
    int res;
#if ETHR_XCHK
    if (ethr_not_inited) {
	ASSERT(0);
	return EACCES;
    }
    if (!mtx || mtx->initialized != ETHR_MUTEX_INITIALIZED) {
	ASSERT(0);
	return EINVAL;
    }
    res = xchk_locking_mutex(mtx);
    if (res != 0)
	return res;
#endif
    res = pthread_mutex_lock(&mtx->pt_mtx);
#if ETHR_XCHK
    if (res != 0)
	(void) xchk_unlocking_mutex(mtx);
#endif
    return res;
}

int
ethr_mutex_unlock(ethr_mutex *mtx)
{
    int res;
#if ETHR_XCHK
    if (ethr_not_inited) {
	ASSERT(0);
	return EACCES;
    }
    if (!mtx || mtx->initialized != ETHR_MUTEX_INITIALIZED) {
	ASSERT(0);
	return EINVAL;
    }
    res = xchk_unlocking_mutex(mtx);
    if (res != 0)
	return res;
#endif

    res = pthread_mutex_unlock(&mtx->pt_mtx);
#if ETHR_XCHK
    if (res != 0)
	(void) xchk_locking_mutex(mtx);
#endif
    return res;
}

/*
 * Condition variable functions
 */

int
ethr_cond_init(ethr_cond *cnd)
{
#if ETHR_XCHK
    if (ethr_not_inited) {
	ASSERT(0);
	return EACCES;
    }
    if (!cnd) {
	ASSERT(0);
	return EINVAL;
    }
    cnd->initialized = ETHR_COND_INITIALIZED;
#endif
    return pthread_cond_init(&cnd->pt_cnd, NULL);
}

int
ethr_cond_destroy(ethr_cond *cnd)
{
#if ETHR_XCHK
    if (ethr_not_inited) {
	ASSERT(0);
	return EACCES;
    }
    if (!cnd || cnd->initialized != ETHR_COND_INITIALIZED) {
	ASSERT(0);
	return EINVAL;
    }
    cnd->initialized = 0;
#endif
    return pthread_cond_destroy(&cnd->pt_cnd);
}

int
ethr_cond_signal(ethr_cond *cnd)
{
#if ETHR_XCHK
    if (ethr_not_inited) {
	ASSERT(0);
	return EACCES;
    }
    if (!cnd || cnd->initialized != ETHR_COND_INITIALIZED) {
	ASSERT(0);
	return EINVAL;
    }
#endif
    return pthread_cond_signal(&cnd->pt_cnd);
}

int
ethr_cond_broadcast(ethr_cond *cnd)
{
#if ETHR_XCHK
    if (ethr_not_inited) {
	ASSERT(0);
	return EACCES;
    }
    if (!cnd || cnd->initialized != ETHR_COND_INITIALIZED) {
	ASSERT(0);
	return EINVAL;
    }
#endif
    return pthread_cond_broadcast(&cnd->pt_cnd);
}

int
ethr_cond_wait(ethr_cond *cnd, ethr_mutex *mtx)
{
    int res;
#if ETHR_XCHK
    if (ethr_not_inited) {
	ASSERT(0);
	return EACCES;
    }
    if (!cnd
	|| cnd->initialized != ETHR_COND_INITIALIZED
	|| !mtx
	|| mtx->initialized != ETHR_MUTEX_INITIALIZED) {
	ASSERT(0);
	return EINVAL;
    }
    res = xchk_unlocking_mutex(mtx);
    if (res != 0)
	return res;
    if (xchk_get_my_mutex_lock_count(mtx) != 0)
	return EINVAL;
#endif

    res = pthread_cond_wait(&cnd->pt_cnd, &mtx->pt_mtx);

#if ETHR_XCHK
    (void) xchk_locking_mutex(mtx);
#endif

    return res;
}

int
ethr_cond_timedwait(ethr_cond *cnd, ethr_mutex *mtx, ethr_timeval *timeout)
{
    int res;
    struct timespec to;
#if ETHR_XCHK
    if (ethr_not_inited) {
	ASSERT(0);
	return EACCES;
    }
    if (!cnd
	|| cnd->initialized != ETHR_COND_INITIALIZED
	|| !mtx
	|| mtx->initialized != ETHR_MUTEX_INITIALIZED
	|| !timeout) {
	ASSERT(0);
	return EINVAL;
    }
    res = xchk_unlocking_mutex(mtx);
    if (res != 0)
	return res;
    if (xchk_get_my_mutex_lock_count(mtx) != 0)
	return EINVAL;
#endif

    to.tv_sec = timeout->tv_sec;
    to.tv_nsec = timeout->tv_nsec;

    res = pthread_cond_timedwait(&cnd->pt_cnd, &mtx->pt_mtx, &to);

#if ETHR_XCHK
    (void) xchk_locking_mutex(mtx);
#endif
    return res;
}


#ifdef ETHR_EXTENDED_LIB

int
ethr_rwmutex_init(ethr_rwmutex *rwmtx)
{
#if ETHR_XCHK
    if (ethr_not_inited) {
	ASSERT(0);
	return EACCES;
    }
    if (!rwmtx) {
	ASSERT(0);
	return EINVAL;
    }
    rwmtx->initialized = ETHR_RWMUTEX_INITIALIZED;
#endif
    return pthread_rwlock_init(&rwmtx->pt_rwlock, NULL);
}

int
ethr_rwmutex_destroy(ethr_rwmutex *rwmtx)
{
    int res;
#if ETHR_XCHK
    if (ethr_not_inited) {
	ASSERT(0);
	return EACCES;
    }
    if (!rwmtx || rwmtx->initialized != ETHR_RWMUTEX_INITIALIZED) {
	ASSERT(0);
	return EINVAL;
    }
    /* TODO: if (xchk_get_my_mutex_lock_count(rwmtx) != 0) return EINVAL; */
#endif
    res = pthread_rwlock_destroy(&rwmtx->pt_rwlock);
#if ETHR_XCHK
    rwmtx->initialized = 0;
#endif
    return res;
}

int
ethr_rwmutex_tryrlock(ethr_rwmutex *rwmtx)
{
    int res;
#if ETHR_XCHK
    if (ethr_not_inited) {
	ASSERT(0);
	return EACCES;
    }
    if (!rwmtx || rwmtx->initialized != ETHR_RWMUTEX_INITIALIZED) {
	ASSERT(0);
	return EINVAL;
    }
    /* TODO: res = xchk_rlocking_rwwmutex(rwmtx); if (res != 0) return res; */
#endif
    res = pthread_rwlock_tryrdlock(&rwmtx->pt_rwlock);
#if ETHR_XCHK
    /* TODO: if (res != 0) (void) xchk_runlocking_rwmutex(rwmtx); */
#endif
    return res;
}

int
ethr_rwmutex_rlock(ethr_rwmutex *rwmtx)
{
    int res;
#if ETHR_XCHK
    if (ethr_not_inited) {
	ASSERT(0);
	return EACCES;
    }
    if (!rwmtx || rwmtx->initialized != ETHR_RWMUTEX_INITIALIZED) {
	ASSERT(0);
	return EINVAL;
    }
    /* TODO: res = xchk_rlocking_rwwmutex(rwmtx); if (res != 0) return res; */
#endif
    res = pthread_rwlock_rdlock(&rwmtx->pt_rwlock);
#if ETHR_XCHK
    /* TODO: if (res != 0) (void) xchk_runlocking_mutex(rwmtx); */
#endif
    return res;
}

int
ethr_rwmutex_runlock(ethr_rwmutex *rwmtx)
{
    int res;
#if ETHR_XCHK
    if (ethr_not_inited) {
	ASSERT(0);
	return EACCES;
    }
    if (!rwmtx || rwmtx->initialized != ETHR_RWMUTEX_INITIALIZED) {
	ASSERT(0);
	return EINVAL;
    }
    /* TODO: res = xchk_runlocking_rwmutex(rwmtx); if (res != 0) return res; */
#endif
    res = pthread_rwlock_unlock(&rwmtx->pt_rwlock);
#if ETHR_XCHK
    /* TODO: if (res != 0) (void) xchk_rlocking_rwmutex(rwmtx); */
#endif
    return res;
}

int
ethr_rwmutex_tryrwlock(ethr_rwmutex *rwmtx)
{
    int res;
#if ETHR_XCHK
    if (ethr_not_inited) {
	ASSERT(0);
	return EACCES;
    }
    if (!rwmtx || rwmtx->initialized != ETHR_RWMUTEX_INITIALIZED) {
	ASSERT(0);
	return EINVAL;
    }
    /* TODO: res = xchk_rwlocking_rwwmutex(rwmtx); if (res != 0) return res; */
#endif
    res = pthread_rwlock_trywrlock(&rwmtx->pt_rwlock);
#if ETHR_XCHK
    /* TODO: if (res != 0) (void) xchk_rwunlocking_rwmutex(rwmtx); */
#endif
    return res;
}

int
ethr_rwmutex_rwlock(ethr_rwmutex *rwmtx)
{
    int res;
#if ETHR_XCHK
    if (ethr_not_inited) {
	ASSERT(0);
	return EACCES;
    }
    if (!rwmtx || rwmtx->initialized != ETHR_RWMUTEX_INITIALIZED) {
	ASSERT(0);
	return EINVAL;
    }
    /* TODO: res = xchk_rwlocking_rwmutex(rwmtx); if (res != 0)	return res; */
#endif
    res = pthread_rwlock_wrlock(&rwmtx->pt_rwlock);
#if ETHR_XCHK
    /* TODO: if (res != 0) (void) xchk_rwunlocking_rwmutex(rwmtx); */
#endif
    return res;
}

int
ethr_rwmutex_rwunlock(ethr_rwmutex *rwmtx)
{
    int res;
#if ETHR_XCHK
    if (ethr_not_inited) {
	ASSERT(0);
	return EACCES;
    }
    if (!rwmtx || rwmtx->initialized != ETHR_RWMUTEX_INITIALIZED) {
	ASSERT(0);
	return EINVAL;
    }
    /* TODO: res = xchk_rwunlocking_rwmutex(rwmtx); if (res != 0) return res; */
#endif
    res = pthread_rwlock_unlock(&rwmtx->pt_rwlock);
#if ETHR_XCHK
    /* TODO: if (res != 0) (void) xchk_rwlocking_rwmutex(rwmtx); */
#endif
    return res;
}

#endif /* #ifdef ETHR_EXTENDED_LIB */


#ifndef ETHR_HAVE_OPTIMIZED_ATOMIC_OPS


int
ethr_atomic_init(ethr_atomic_t *var, long i)
{
    pthread_mutex_t *mtxp = ETHR_ATOMIC_PTR2MTX(var);
    int res;
#if ETHR_XCHK
    if (ethr_not_inited) {
       ASSERT(0);
       return EACCES;
    }  
    if (!var) {
       ASSERT(0);
       return EINVAL;
    }  
#endif 
    res = pthread_mutex_lock(mtxp);
    if (res != 0)
	return res;
    *var = (ethr_atomic_t) i;
    return pthread_mutex_unlock(mtxp);
}

int
ethr_atomic_set(ethr_atomic_t *var, long i)
{
    pthread_mutex_t *mtxp = ETHR_ATOMIC_PTR2MTX(var);
    int res;
#if ETHR_XCHK
    if (ethr_not_inited) {
       ASSERT(0);
       return EACCES;
    }  
    if (!var) {
       ASSERT(0);
       return EINVAL;
    }  
#endif
    res = pthread_mutex_lock(mtxp);
    if (res != 0)
	return res;
    *var = (ethr_atomic_t) i;
    return pthread_mutex_unlock(mtxp);
}

int
ethr_atomic_read(ethr_atomic_t *var, long *i)
{
    pthread_mutex_t *mtxp = ETHR_ATOMIC_PTR2MTX(var);
    int res;
#if ETHR_XCHK
    if (ethr_not_inited) {
       ASSERT(0);
       return EACCES;
    }  
    if (!var || !i) {
       ASSERT(0);
       return EINVAL;
    }  
#endif
    res = pthread_mutex_lock(mtxp);
    if (res != 0)
	return res;
    *i = (long) *var;
    return pthread_mutex_unlock(mtxp);
}


int
ethr_atomic_addtest(ethr_atomic_t *var, long incr, long *testp)
{
    pthread_mutex_t *mtxp = ETHR_ATOMIC_PTR2MTX(var);
    int res;
#if ETHR_XCHK
    if (ethr_not_inited) {
       ASSERT(0);
       return EACCES;
    }  
    if (!var) {
       ASSERT(0);
       return EINVAL;
    }  
#endif 
    res = pthread_mutex_lock(mtxp);
    if (res != 0)
       return res;
    *testp = *var += incr; 
    return pthread_mutex_unlock(mtxp);
}   
    
int
ethr_atomic_inctest(ethr_atomic_t *incp, long *testp)
{
    pthread_mutex_t *mtxp = ETHR_ATOMIC_PTR2MTX(incp);
    int res;
#if ETHR_XCHK
    if (ethr_not_inited) {
	ASSERT(0);
	return EACCES;
    }
    if (!incp || !testp) {
	ASSERT(0);
	return EINVAL;
    }
#endif
    res = pthread_mutex_lock(mtxp);
    if (res != 0)
	return res;
    *testp = ++(*incp);
#ifdef DEBUG
    if (*testp > 0)
	*testp = 4711;
    else if (*testp < 0)
	*testp = -4711;
#endif
    return pthread_mutex_unlock(mtxp);
}

int
ethr_atomic_dectest(ethr_atomic_t *decp, long *testp)
{
    pthread_mutex_t *mtxp = ETHR_ATOMIC_PTR2MTX(decp);
    int res;
#if ETHR_XCHK
    if (ethr_not_inited) {
	ASSERT(0);
	return EACCES;
    }
    if (!decp || !testp) {
	ASSERT(0);
	return EINVAL;
    }
#endif
    res = pthread_mutex_lock(mtxp);
    if (res != 0)
	return res;
    *testp = --(*decp);
#ifdef DEBUG
    if (*testp > 0)
	*testp = 4711;
    else if (*testp < 0)
	*testp = -4711;
#endif
    return pthread_mutex_unlock(mtxp);
}

int
ethr_atomic_add(ethr_atomic_t *var, long incr)
{
    pthread_mutex_t *mtxp = ETHR_ATOMIC_PTR2MTX(var);
    int res;
#if ETHR_XCHK
    if (ethr_not_inited) {
       ASSERT(0);
       return EACCES;
    }  
    if (!var) {
       ASSERT(0);
       return EINVAL;
    }  
#endif 
    res = pthread_mutex_lock(mtxp);
    if (res != 0)
       return res;
    *var += incr; 
    return pthread_mutex_unlock(mtxp);
}   
    
int 
ethr_atomic_inc(ethr_atomic_t *incp)
{
    pthread_mutex_t *mtxp = ETHR_ATOMIC_PTR2MTX(incp);
    int res;
#if ETHR_XCHK
    if (ethr_not_inited) {
	ASSERT(0);
	return EACCES;
    }
    if (!incp) {
	ASSERT(0);
	return EINVAL;
    }
#endif
    res = pthread_mutex_lock(mtxp);
    if (res != 0)
	return res;
    ++(*incp);
    return pthread_mutex_unlock(mtxp);
}

int
ethr_atomic_dec(ethr_atomic_t *decp)
{
    pthread_mutex_t *mtxp = ETHR_ATOMIC_PTR2MTX(decp);
    int res;
#if ETHR_XCHK
    if (ethr_not_inited) {
	ASSERT(0);
	return EACCES;
    }
    if (!decp) {
	ASSERT(0);
	return EINVAL;
    }
#endif
    res = pthread_mutex_lock(mtxp);
    if (res != 0)
	return res;
    --(*decp);
    return pthread_mutex_unlock(mtxp);
}

int
ethr_atomic_and_old(ethr_atomic_t *var, long mask, long *old)
{
    pthread_mutex_t *mtxp = ETHR_ATOMIC_PTR2MTX(var);
    int res = pthread_mutex_lock(mtxp);
#if ETHR_XCHK
    if (ethr_not_inited) {
	ASSERT(0);
	return EACCES;
    }
    if (!var || !old) {
	ASSERT(0);
	return EINVAL;
    }
#endif
    if (res != 0)
	return res;
    *old = *var;
    *var &= mask;
    return pthread_mutex_unlock(mtxp);
}

int
ethr_atomic_or_old(ethr_atomic_t *var, long mask, long *old)
{
    pthread_mutex_t *mtxp = ETHR_ATOMIC_PTR2MTX(var);
    int res = pthread_mutex_lock(mtxp);
#if ETHR_XCHK
    if (ethr_not_inited) {
	ASSERT(0);
	return EACCES;
    }
    if (!var || !old) {
	ASSERT(0);
	return EINVAL;
    }
#endif
    if (res != 0)
	return res;
    *old = *var;
    *var |= mask;
    return pthread_mutex_unlock(mtxp);
}

int
ethr_atomic_xchg(ethr_atomic_t *var, long new, long *old)
{
    pthread_mutex_t *mtxp = ETHR_ATOMIC_PTR2MTX(var);
    int res = pthread_mutex_lock(mtxp);
#if ETHR_XCHK 
    if (ethr_not_inited) {
	ASSERT(0);
	return EACCES;
    }  
    if (!var || !old) {
	ASSERT(0);
	return EINVAL;
    }  
#endif 
    if (res != 0)
	return res;
    *old = *var;  
    *var = new;   
    return pthread_mutex_unlock(mtxp);
}   
    
#endif /* #ifndef ETHR_HAVE_OPTIMIZED_ATOMIC_OPS */

/*
 * Current time
 */

int
ethr_time_now(ethr_timeval *time)
{
    int res;
    struct timeval tv;
#if ETHR_XCHK
    if (ethr_not_inited) {
	ASSERT(0);
	return EACCES;
    }
    if (!time) {
	ASSERT(0);
	return EINVAL;
    }
#endif

    res = gettimeofday(&tv, NULL);
    time->tv_sec = (long) tv.tv_sec;
    time->tv_nsec = ((long) tv.tv_usec)*1000;
    return res;
}

/*
 * Thread specific data
 */

int
ethr_tsd_key_create(ethr_tsd_key *keyp)
{
#if ETHR_XCHK
    if (ethr_not_inited) {
	ASSERT(0);
	return EACCES;
    }
    if (!keyp) {
	ASSERT(0);
	return EINVAL;
    }
#endif
    return pthread_key_create((pthread_key_t *) keyp, NULL);
}

int
ethr_tsd_key_delete(ethr_tsd_key key)
{
#if ETHR_XCHK
    if (ethr_not_inited) {
	ASSERT(0);
	return EACCES;
    }
#endif
    return pthread_key_delete((pthread_key_t) key);
}

int
ethr_tsd_set(ethr_tsd_key key, void *value)
{
#if ETHR_XCHK
    if (ethr_not_inited) {
	ASSERT(0);
	return EACCES;
    }
#endif
    return pthread_setspecific((pthread_key_t) key, value);
}

void *
ethr_tsd_get(ethr_tsd_key key)
{
#if ETHR_XCHK
    if (ethr_not_inited) {
	ASSERT(0);
	return NULL;
    }
#endif
    return pthread_getspecific((pthread_key_t) key);
}

/*
 * Signal functions
 */

#if ETHR_HAVE_ETHR_SIG_FUNCS

int ethr_sigmask(int how, const sigset_t *set, sigset_t *oset)
{
#if ETHR_XCHK
    if (ethr_not_inited) {
	ASSERT(0);
	return EACCES;
    }
    if (!set && !oset) {
	ASSERT(0);
	return EINVAL;
    }
#endif
  return pthread_sigmask(how, set, oset);
}

int ethr_sigwait(const sigset_t *set, int *sig)
{
#if ETHR_XCHK
    if (ethr_not_inited) {
	ASSERT(0);
	return EACCES;
    }
    if (!set || !sig) {
	ASSERT(0);
	return EINVAL;
    }
#endif
    if (sigwait(set, sig) < 0)
	return errno;
    return 0;
}

#endif /* #if ETHR_HAVE_ETHR_SIG_FUNCS */

#elif defined(ETHR_WIN32_THREADS)
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
 * Native win32 threads implementation                                       *
\*                                                                           */

#define INVALID_TID -1

/* The spin count values are more or less taken out of the blue */
#define ETHR_MUTEX_SPIN_COUNT	5000
#define ETHR_COND_SPIN_COUNT	1000

ethr_tid serial_shift; /* Bits to shift serial when constructing a tid */
ethr_tid last_serial; /* Last thread table serial used */
ethr_tid last_ix; /* Last thread table index used */
ethr_tid thr_ix_mask; /* Mask used to mask out thread table index from a tid */

/* Event used for conditional variables. On per thread. */
/*typedef struct cnd_wait_event__ cnd_wait_event_;*/
struct cnd_wait_event__ {
    HANDLE handle;
    cnd_wait_event_ *prev;
    cnd_wait_event_ *next;
    int in_queue;
};

/* Thread specific data. Stored in the thread table */
typedef struct {
    ethr_tid thr_id;
    HANDLE thr_handle;
    ethr_tid joiner;
    void *result;
    cnd_wait_event_ wait_event;
} thr_data_;

/* Argument passed to thr_wrapper() */
typedef struct {
    void * (*func)(void *);
    void * arg;
    thr_data_ *ptd;
    thr_data_ *td;
    int res;
    void *prep_func_res;
} thr_wrap_data_;


static CRITICAL_SECTION thr_table_cs; /* Critical section used to protect
					 the thread table from concurrent
					 accesses. */
static CRITICAL_SECTION fake_static_init_cs; /* Critical section used to protect
					initialazition of 'statically
					initialized' mutexes */
static thr_data_ * thr_table[ETHR_MAX_THREADS]; /* The thread table */

static DWORD tls_own_thr_data;

static thr_data_ main_thr_data;

#define THR_IX(TID)	((TID) & thr_ix_mask)
#define OWN_THR_DATA	((thr_data_ *) TlsGetValue(tls_own_thr_data))

/*
 * ----------------------------------------------------------------------------
 * Static functions
 * ----------------------------------------------------------------------------
 */

static int
get_errno(void)
{
    switch (GetLastError()) {
    case ERROR_INVALID_FUNCTION:		return EINVAL;	/* 1	*/
    case ERROR_FILE_NOT_FOUND:			return ENOENT;	/* 2	*/
    case ERROR_PATH_NOT_FOUND:			return ENOENT;	/* 3	*/
    case ERROR_TOO_MANY_OPEN_FILES:		return EMFILE;	/* 4	*/
    case ERROR_ACCESS_DENIED:			return EACCES;	/* 5	*/
    case ERROR_INVALID_HANDLE:			return EBADF;	/* 6	*/
    case ERROR_ARENA_TRASHED:			return ENOMEM;	/* 7	*/
    case ERROR_NOT_ENOUGH_MEMORY:		return ENOMEM;	/* 8	*/
    case ERROR_INVALID_BLOCK:			return ENOMEM;	/* 9	*/
    case ERROR_BAD_ENVIRONMENT:			return E2BIG;	/* 10	*/
    case ERROR_BAD_FORMAT:			return ENOEXEC;	/* 11	*/
    case ERROR_INVALID_ACCESS:			return EINVAL;	/* 12	*/
    case ERROR_INVALID_DATA:			return EINVAL;	/* 13	*/
    case ERROR_OUTOFMEMORY:			return ENOMEM;	/* 14	*/
    case ERROR_INVALID_DRIVE:			return ENOENT;	/* 15	*/
    case ERROR_CURRENT_DIRECTORY:		return EACCES;	/* 16	*/
    case ERROR_NOT_SAME_DEVICE:			return EXDEV;	/* 17	*/
    case ERROR_NO_MORE_FILES:			return ENOENT;	/* 18	*/
    case ERROR_WRITE_PROTECT:			return EACCES;	/* 19	*/
    case ERROR_BAD_UNIT:			return EACCES;	/* 20	*/
    case ERROR_NOT_READY:			return EACCES;	/* 21	*/
    case ERROR_BAD_COMMAND:			return EACCES;	/* 22	*/
    case ERROR_CRC:				return EACCES;	/* 23	*/
    case ERROR_BAD_LENGTH:			return EACCES;	/* 24	*/
    case ERROR_SEEK:				return EACCES;	/* 25	*/
    case ERROR_NOT_DOS_DISK:			return EACCES;	/* 26	*/
    case ERROR_SECTOR_NOT_FOUND:		return EACCES;	/* 27	*/
    case ERROR_OUT_OF_PAPER:			return EACCES;	/* 28	*/
    case ERROR_WRITE_FAULT:			return EACCES;	/* 29	*/
    case ERROR_READ_FAULT:			return EACCES;	/* 30	*/
    case ERROR_GEN_FAILURE:			return EACCES;	/* 31	*/
    case ERROR_SHARING_VIOLATION:		return EACCES;	/* 32	*/
    case ERROR_LOCK_VIOLATION:			return EACCES;	/* 33	*/
    case ERROR_WRONG_DISK:			return EACCES;	/* 34	*/
    case ERROR_SHARING_BUFFER_EXCEEDED:		return EACCES;	/* 36	*/
    case ERROR_BAD_NETPATH:			return ENOENT;	/* 53	*/
    case ERROR_NETWORK_ACCESS_DENIED:		return EACCES;	/* 65	*/
    case ERROR_BAD_NET_NAME:			return ENOENT;	/* 67	*/
    case ERROR_FILE_EXISTS:			return EEXIST;	/* 80	*/
    case ERROR_CANNOT_MAKE:			return EACCES;	/* 82	*/
    case ERROR_FAIL_I24:			return EACCES;	/* 83	*/
    case ERROR_INVALID_PARAMETER:		return EINVAL;	/* 87	*/
    case ERROR_NO_PROC_SLOTS:			return EAGAIN;	/* 89	*/
    case ERROR_DRIVE_LOCKED:			return EACCES;	/* 108	*/
    case ERROR_BROKEN_PIPE:			return EPIPE;	/* 109	*/
    case ERROR_DISK_FULL:			return ENOSPC;	/* 112	*/
    case ERROR_INVALID_TARGET_HANDLE:		return EBADF;	/* 114	*/
    case ERROR_WAIT_NO_CHILDREN:		return ECHILD;	/* 128	*/
    case ERROR_CHILD_NOT_COMPLETE:		return ECHILD;	/* 129	*/
    case ERROR_DIRECT_ACCESS_HANDLE:		return EBADF;	/* 130	*/
    case ERROR_NEGATIVE_SEEK:			return EINVAL;	/* 131	*/
    case ERROR_SEEK_ON_DEVICE:			return EACCES;	/* 132	*/
    case ERROR_DIR_NOT_EMPTY:			return ENOTEMPTY;/* 145	*/
    case ERROR_NOT_LOCKED:			return EACCES;	/* 158	*/
    case ERROR_BAD_PATHNAME:			return ENOENT;	/* 161	*/
    case ERROR_MAX_THRDS_REACHED:		return EAGAIN;	/* 164	*/
    case ERROR_LOCK_FAILED:			return EACCES;	/* 167	*/
    case ERROR_ALREADY_EXISTS:			return EEXIST;	/* 183	*/
    case ERROR_INVALID_STARTING_CODESEG:	return ENOEXEC;	/* 188	*/
    case ERROR_INVALID_STACKSEG:		return ENOEXEC;	/* 189	*/
    case ERROR_INVALID_MODULETYPE:		return ENOEXEC;	/* 190	*/
    case ERROR_INVALID_EXE_SIGNATURE:		return ENOEXEC;	/* 191	*/
    case ERROR_EXE_MARKED_INVALID:		return ENOEXEC;	/* 192	*/
    case ERROR_BAD_EXE_FORMAT:			return ENOEXEC;	/* 193	*/
    case ERROR_ITERATED_DATA_EXCEEDS_64k:	return ENOEXEC;	/* 194	*/
    case ERROR_INVALID_MINALLOCSIZE:		return ENOEXEC;	/* 195	*/
    case ERROR_DYNLINK_FROM_INVALID_RING:	return ENOEXEC;	/* 196	*/
    case ERROR_IOPL_NOT_ENABLED:		return ENOEXEC;	/* 197	*/
    case ERROR_INVALID_SEGDPL:			return ENOEXEC;	/* 198	*/
    case ERROR_AUTODATASEG_EXCEEDS_64k:		return ENOEXEC;	/* 199	*/
    case ERROR_RING2SEG_MUST_BE_MOVABLE:	return ENOEXEC;	/* 200	*/
    case ERROR_RELOC_CHAIN_XEEDS_SEGLIM:	return ENOEXEC;	/* 201	*/
    case ERROR_INFLOOP_IN_RELOC_CHAIN:		return ENOEXEC;	/* 202	*/
    case ERROR_FILENAME_EXCED_RANGE:		return ENOENT;	/* 206	*/
    case ERROR_NESTING_NOT_ALLOWED:		return EAGAIN;	/* 215	*/
    case ERROR_NOT_ENOUGH_QUOTA:		return ENOMEM;	/* 1816	*/
    default:					return EINVAL;
    }
}

static ETHR_INLINE thr_data_ *
tid2thr(ethr_tid tid)
{
    ethr_tid ix;
    thr_data_ *td;
    
    if (tid < 0)
	return NULL;
    ix = THR_IX(tid);
    if (ix >= ETHR_MAX_THREADS)
	return NULL;
    td = thr_table[ix];
    if (!td)
	return NULL;
    if (td->thr_id != tid)
	return NULL;
    return td;
}

static ETHR_INLINE void
new_tid(ethr_tid *new_tid, ethr_tid *new_serial, ethr_tid *new_ix)
{
    ethr_tid tmp_serial = last_serial;
    ethr_tid tmp_ix = last_ix + 1;
    ethr_tid start_ix = tmp_ix;


    do {
	if (tmp_ix >= ETHR_MAX_THREADS) {
	    tmp_serial++;
	    if ((tmp_serial << serial_shift) < 0)
		tmp_serial = 0;
	    tmp_ix = 0;
	}
	if (!thr_table[tmp_ix]) {
	    *new_tid	= (tmp_serial << serial_shift) | tmp_ix;
	    *new_serial	= tmp_serial;
	    *new_ix	= tmp_ix;
	    return;
	}
	tmp_ix++;
    } while (tmp_ix != start_ix);

    *new_tid	= INVALID_TID;
    *new_serial	= INVALID_TID;
    *new_ix	= INVALID_TID;

}


static void thr_exit_cleanup(thr_data_ *td, void *res)
{

    ASSERT(td == OWN_THR_DATA);

    run_exit_handlers();

    EnterCriticalSection(&thr_table_cs);
    CloseHandle(td->wait_event.handle);
    if (td->thr_handle == INVALID_HANDLE_VALUE) {
	/* We are detached; cleanup thread table */
	ASSERT(td->joiner == INVALID_TID);
	ASSERT(td == thr_table[THR_IX(td->thr_id)]);
	thr_table[THR_IX(td->thr_id)] = NULL;
	if (td != &main_thr_data)
	    (*freep)((void *) td);
    }
    else {
	/* Save result and let joining thread cleanup */
	td->result = res;
    }
    LeaveCriticalSection(&thr_table_cs);
#if ETHR_XCHK
    if (ethr_xchk_have_locked_mutexes())
	abort();
#endif
}

static unsigned thr_wrapper(void* args)
{
    void *(*func)(void*) = ((thr_wrap_data_ *) args)->func;
    void *arg = ((thr_wrap_data_ *) args)->arg;
    thr_data_ *td = ((thr_wrap_data_ *) args)->td;

    td->wait_event.handle = CreateEvent(NULL, FALSE, FALSE, NULL);
    if (td->wait_event.handle == INVALID_HANDLE_VALUE
	|| !TlsSetValue(tls_own_thr_data, (LPVOID) td)) {
	((thr_wrap_data_ *) args)->res = get_errno();
	if (td->wait_event.handle != INVALID_HANDLE_VALUE)
	    CloseHandle(td->wait_event.handle);
	SetEvent(((thr_wrap_data_ *) args)->ptd->wait_event.handle);
	_endthreadex((unsigned) 0);
	ASSERT(0);
    }

    td->wait_event.prev = NULL;
    td->wait_event.next = NULL;
    td->wait_event.in_queue = 0;

    if (thread_create_child_func)
	(*thread_create_child_func)(((thr_wrap_data_ *) args)->prep_func_res);

    ASSERT(td == OWN_THR_DATA);

    ((thr_wrap_data_ *) args)->res = 0;
    SetEvent(((thr_wrap_data_ *) args)->ptd->wait_event.handle);

    thr_exit_cleanup(td, (*func)(arg));
    return 0;
}

int
ethr_fake_static_mutex_init(ethr_mutex *mtx)
{
    EnterCriticalSection((CRITICAL_SECTION *) &fake_static_init_cs);
    /* Got here under race conditions; check again... */
    if (!mtx->initialized) {
#ifdef ETHR_HAVE_INITIALIZECRITICALSECTIONANDSPINCOUNT
	if (!InitializeCriticalSectionAndSpinCount(&mtx->cs,
						   ETHR_MUTEX_SPIN_COUNT))
	    return get_errno();
#else
	InitializeCriticalSection(&mtx->cs);
#endif
	mtx->initialized = ETHR_MUTEX_INITIALIZED;
    }
    LeaveCriticalSection((CRITICAL_SECTION *) &fake_static_init_cs);
    return 0;
}

static int
fake_static_cond_init(ethr_cond *cnd)
{
    EnterCriticalSection((CRITICAL_SECTION *) &fake_static_init_cs);
    /* Got here under race conditions; check again... */
    if (!cnd->initialized) {
#ifdef ETHR_HAVE_INITIALIZECRITICALSECTIONANDSPINCOUNT
	if (!InitializeCriticalSectionAndSpinCount(&cnd->cs,
						   ETHR_COND_SPIN_COUNT))
	    return get_errno();
#else
	InitializeCriticalSection(&cnd->cs);
#endif
	cnd->queue = NULL;
	cnd->queue_end = NULL;
	cnd->initialized = ETHR_COND_INITIALIZED;
    }
    LeaveCriticalSection((CRITICAL_SECTION *) &fake_static_init_cs);
    return 0;
}

#ifdef __GNUC__
#define LL_LITERAL(X) X##LL
#else
#define LL_LITERAL(X) X##i64
#endif

#define EPOCH_JULIAN_DIFF LL_LITERAL(11644473600)

static ETHR_INLINE void
get_curr_time(long *sec, long *nsec)
{
    SYSTEMTIME t;
    FILETIME ft;
    LONGLONG lft;

    GetSystemTime(&t);
    SystemTimeToFileTime(&t, &ft);
    memcpy(&lft, &ft, sizeof(lft));
    *nsec = ((long) (lft % LL_LITERAL(10000000)))*100;
    *sec = (long) ((lft / LL_LITERAL(10000000)) - EPOCH_JULIAN_DIFF);
}

static ETHR_INLINE int
condwait(ethr_cond *cnd,
	 ethr_mutex *mtx,
	 int with_timeout,
	 ethr_timeval *timeout)
{
    int res;
    thr_data_ *td;
    cnd_wait_event_ *cwe;
    DWORD code;
    long time; /* time until timeout in milli seconds */

    td = OWN_THR_DATA;

#if ETHR_XCHK
    if (ethr_not_inited) {
	ASSERT(0);
	return EACCES;
    }

    if (!td) {
	ASSERT(0);
	return EACCES;
    }

    if (!mtx
	|| mtx->initialized != ETHR_MTX_INITIALIZED
	|| !cnd
	|| (cnd->initialized && cnd->initialized != ETHR_COND_INITIALIZED)
	|| (with_timeout && !timeout)) {
	ASSERT(0);
	return EINVAL;
    }

    res = xchk_unlocking_mutex(mtx);
    if (res != 0)
	return res;
    if (xchk_get_my_mutex_lock_count(mtx) != 0)
	return EINVAL;

#endif

    if (!cnd->initialized)
	fake_static_cond_init(cnd);
    EnterCriticalSection(&cnd->cs);

    cwe = &td->wait_event;
    ASSERT(!cwe->in_queue);
    if (cnd->queue_end) {
	ASSERT(cnd->queue);
	cwe->prev = cnd->queue_end;
	cwe->next = NULL;
	cnd->queue_end->next = cwe;
	cnd->queue_end = cwe;
    }
    else {
	ASSERT(!cnd->queue);
	cwe->prev = NULL;
	cwe->next = NULL;
	cnd->queue = cwe;
	cnd->queue_end = cwe;
    }
    cwe->in_queue = 1;

    LeaveCriticalSection(&cnd->cs);
 
    LeaveCriticalSection(&mtx->cs);

    if (!with_timeout)
	time = INFINITE;
    else {
	long sec, nsec;
	ASSERT(timeout);
	get_curr_time(&sec, &nsec);
	time = (timeout->tv_sec - sec)*1000;
	time += (timeout->tv_nsec - nsec + 500)/1000000;
	if (time < 0)
	    time = 0;
    }

    /* wait for event to signal */
    code = WaitForSingleObject(cwe->handle, time);

    EnterCriticalSection(&mtx->cs);

    if (code == WAIT_OBJECT_0) {
	/* We were woken by a signal or a broadcast ... */
	res = 0;

	/* ... no need to remove event from wait queue since this was
	   taken care of by the signal or broadcast */
#ifdef DEBUG
	EnterCriticalSection(&cnd->cs);
	ASSERT(!cwe->in_queue);
	LeaveCriticalSection(&cnd->cs);
#endif

    }
    else {
	/* We timed out... */
	res = ETIMEDOUT;

	/* ... probably have to remove event from wait queue ... */
	EnterCriticalSection(&cnd->cs);

	if (cwe->in_queue) { /* ... but we must check that we are in queue
				since a signal or broadcast after timeout
				may have removed us from the queue */
	    if (cwe->prev) {
		cwe->prev->next = cwe->next;
	    }
	    else {
		ASSERT(cnd->queue == cwe);
		cnd->queue = cwe->next;
	    }

	    if (cwe->next) {
		cwe->next->prev = cwe->prev;
	    }
	    else {
		ASSERT(cnd->queue_end == cwe);
		cnd->queue_end = cwe->prev;
	    }
	    cwe->in_queue = 0;
	}

	LeaveCriticalSection(&cnd->cs);

    }

#if ETHR_XCHK
    (void) xchk_locking_mutex(mtx);
#endif

    return res;

}


/*
 * ----------------------------------------------------------------------------
 * Exported functions
 * ----------------------------------------------------------------------------
 */

int
ethr_init(ethr_init_data *id)
{
#ifdef _WIN32_WINNT
    DWORD major = (_WIN32_WINNT >> 8) & 0xff;
    DWORD minor = _WIN32_WINNT & 0xff;
    OSVERSIONINFO os_version;
#endif
    int err = 0;
    thr_data_ *td = &main_thr_data;
    unsigned long i;

    if (!ethr_not_inited)
	return EINVAL;

#ifdef _WIN32_WINNT
    os_version.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
    GetVersionEx(&os_version);
    if (os_version.dwPlatformId != VER_PLATFORM_WIN32_NT
	|| os_version.dwMajorVersion < major
	|| (os_version.dwMajorVersion == major
	    && os_version.dwMinorVersion < minor))
	return ENOTSUP;
#endif

    err = init_common(id);
    if (err)
	goto error;

    ASSERT(ETHR_MAX_THREADS > 0);
    for (i = ETHR_MAX_THREADS - 1, serial_shift = 0;
	 i;
	 serial_shift++, i >>= 1);
    thr_ix_mask = ~(~((ethr_tid) 0) << serial_shift);

    tls_own_thr_data = TlsAlloc();
    if (tls_own_thr_data == TLS_OUT_OF_INDEXES)
	goto error;

    last_serial = 0;
    last_ix = 0;

    td->thr_id = 0;
    td->thr_handle = GetCurrentThread();
    td->joiner = INVALID_TID;
    td->result = NULL;
    td->wait_event.handle = CreateEvent(NULL, FALSE, FALSE, NULL);
    if (td->wait_event.handle == INVALID_HANDLE_VALUE)
	goto error;
    td->wait_event.prev = NULL;
    td->wait_event.next = NULL;
    td->wait_event.in_queue = 0;
    thr_table[0] = td;

    if (!TlsSetValue(tls_own_thr_data, (LPVOID) td))
	goto error;

    ASSERT(td == OWN_THR_DATA);

    for (i = 1; i < ETHR_MAX_THREADS; i++)
	thr_table[i] = NULL;

#ifdef ETHR_HAVE_INITIALIZECRITICALSECTIONANDSPINCOUNT
    if (!InitializeCriticalSectionAndSpinCount(&thr_table_cs,
					       ETHR_MUTEX_SPIN_COUNT))
	goto error;
    if (!InitializeCriticalSectionAndSpinCount(&fake_static_init_cs,
					       ETHR_MUTEX_SPIN_COUNT))
	goto error;
#else
    InitializeCriticalSection(&thr_table_cs);
    InitializeCriticalSection(&fake_static_init_cs);
#endif
    ethr_not_inited = 0;

    return 0;

 error:
    if (err == 0)
	err = get_errno();
    ASSERT(err != 0);
    if (td->thr_handle != INVALID_HANDLE_VALUE)
	CloseHandle(td->thr_handle);
    if (td->wait_event.handle != INVALID_HANDLE_VALUE)
	CloseHandle(td->wait_event.handle);
    return err;
}

/*
 * Thread functions.
 */

int
ethr_thr_create(ethr_tid *tid, void * (*func)(void *), void *arg, int detached)
{
    int err = 0;
    thr_wrap_data_ twd;
    thr_data_ *my_td, *child_td = NULL;
    ethr_tid child_tid, child_serial, child_ix;
    DWORD code, ID;

#if ETHR_XCHK
    if (ethr_not_inited) {
	ASSERT(0);
	return EACCES;
    }
    if (!tid || !func) {
	ASSERT(0);
	return EINVAL;
    }
#endif

    my_td = OWN_THR_DATA;

#if ETHR_XCHK
    if (!my_td) {
	ASSERT(0);
	return EACCES;
    }
#endif

    EnterCriticalSection(&thr_table_cs);

    /* Call prepare func if it exist */
    if (thread_create_prepare_func)
	twd.prep_func_res = (*thread_create_prepare_func)();
    else
	twd.prep_func_res = NULL;

    /* Find a new thread id to use */
    new_tid(&child_tid, &child_serial, &child_ix);
    if (child_tid == INVALID_TID) {
	err = EAGAIN;
	goto error;
    }

    ASSERT(child_ix == THR_IX(child_tid));

    *tid = child_tid;

    ASSERT(!thr_table[child_ix]);

    /* Alloc thread data */
    thr_table[child_ix] = child_td = (thr_data_ *) (*allocp)(sizeof(thr_data_));
    if (!child_td) {
	err = ENOMEM;
	goto error;
    }

    /* Init thread data */

    child_td->thr_id		= child_tid;
    child_td->thr_handle	= INVALID_HANDLE_VALUE;
    child_td->joiner		= INVALID_TID;
    child_td->result		= NULL;
    /* 'child_td->wait_event' is initialized by child thread */


    /* Init thread wrapper data */

    twd.func			= func;
    twd.arg			= arg;
    twd.ptd			= my_td;
    twd.td			= child_td;
    twd.res			= 0;

    ASSERT(!my_td->wait_event.in_queue);

    /* spawn the thr_wrapper function */
    child_td->thr_handle
	= (HANDLE) _beginthreadex(NULL,
				  0,
				  (LPTHREAD_START_ROUTINE) thr_wrapper, 
				  (LPVOID) &twd,
				  0,
				  &ID);
    if (child_td->thr_handle == (HANDLE) 0) {
	child_td->thr_handle = INVALID_HANDLE_VALUE;
	goto error;
    }

    ASSERT(child_td->thr_handle != INVALID_HANDLE_VALUE);

    /* Wait for child to finish initialization */
    code = WaitForSingleObject(my_td->wait_event.handle, INFINITE);
    if (twd.res || code != WAIT_OBJECT_0) {
	err = twd.res;
	goto error;
    }

    if (detached) {
	CloseHandle(child_td->thr_handle);
	child_td->thr_handle = INVALID_HANDLE_VALUE;
    }

    last_serial	= child_serial;
    last_ix	= child_ix;

    ASSERT(thr_table[child_ix] == child_td);

    if (thread_create_parent_func)
	(*thread_create_parent_func)(twd.prep_func_res);

    LeaveCriticalSection(&thr_table_cs);

    return 0;

 error:

    if (err == 0)
	err = get_errno();
    ASSERT(err != 0);

    if (thread_create_parent_func)
	(*thread_create_parent_func)(twd.prep_func_res);

    if (child_ix != INVALID_TID) {

	if (child_td) {
	    ASSERT(thr_table[child_ix] == child_td);

	    if (child_td->thr_handle != INVALID_HANDLE_VALUE) {
		WaitForSingleObject(child_td->thr_handle, INFINITE);
		CloseHandle(child_td->thr_handle);
	    }

	    (*freep)((void *) child_td);
	    thr_table[child_ix] = NULL;
	}
    }

    *tid = INVALID_TID;

    LeaveCriticalSection(&thr_table_cs);
    return err;
}

int ethr_thr_join(ethr_tid tid, void **res)
{
    int err = 0;
    DWORD code;
    thr_data_ *td;
    thr_data_ *my_td;

#if ETHR_XCHK 
    if (ethr_not_inited) {
	ASSERT(0);
	return EACCES;
    }
#endif

    my_td = OWN_THR_DATA;

#if ETHR_XCHK
    if (!my_td) {
	ASSERT(0);
	return EACCES;
    }
#endif

    EnterCriticalSection(&thr_table_cs);

    td = tid2thr(tid);
    if (!td)
	err = ESRCH;
    else if (td->thr_handle == INVALID_HANDLE_VALUE /* i.e. detached */
	     || td->joiner != INVALID_TID) /* i.e. someone else is joining */
	err = EINVAL;
    else if (my_td == td)
	err = EDEADLK;
    else
	td->joiner = my_td->thr_id;

    LeaveCriticalSection(&thr_table_cs);

    if (err)
	goto error;

    /* Wait for thread to terminate */
    code = WaitForSingleObject(td->thr_handle, INFINITE);
    if (code != WAIT_OBJECT_0)
	goto error;

    EnterCriticalSection(&thr_table_cs);

    ASSERT(td == tid2thr(tid));
    ASSERT(td->thr_handle != INVALID_HANDLE_VALUE);
    ASSERT(td->joiner == my_td->thr_id);

    if (res)
	*res = td->result;

    CloseHandle(td->thr_handle);
    ASSERT(td == thr_table[THR_IX(td->thr_id)]);
    thr_table[THR_IX(td->thr_id)] = NULL;
    if (td != &main_thr_data)
	(*freep)((void *) td);

    LeaveCriticalSection(&thr_table_cs);

    return 0;

 error:
    if (err == 0)
	err = get_errno();
    ASSERT(err != 0);
    return err;
}


int
ethr_thr_detach(ethr_tid tid)
{
    int res;
    DWORD code;
    thr_data_ *td;

#if ETHR_XCHK 
    if (ethr_not_inited || !OWN_THR_DATA) {
	ASSERT(0);
	return EACCES;
    }
#endif

    EnterCriticalSection(&thr_table_cs);

    td = tid2thr(tid);
    if (!td)
	res = ESRCH;
    if (td->thr_handle == INVALID_HANDLE_VALUE /* i.e. detached */
	|| td->joiner != INVALID_TID) /* i.e. someone is joining */
	res = EINVAL;
    else {
	res = 0;
	CloseHandle(td->thr_handle);
	td->thr_handle = INVALID_HANDLE_VALUE;
    }

    LeaveCriticalSection(&thr_table_cs);

    return res;
}


void
ethr_thr_exit(void *res)
{
    thr_data_ *td;
#if ETHR_XCHK
    if (ethr_not_inited) {
	ASSERT(0);
	return;
    }
#endif
    td = OWN_THR_DATA;
#if ETHR_XCHK
    if (!td) {
	ASSERT(0);
	return;
    }
#endif
    thr_exit_cleanup(td, res);
    _endthreadex((unsigned) 0);
}

ethr_tid
ethr_self(void)
{
    thr_data_ *td;
#if ETHR_XCHK
    if (ethr_not_inited) {
	ASSERT(0);
	return INVALID_TID;
    }
#endif
    /* It is okay for non-ethreads (i.e. native win32 threads) to call
       ethr_self(). They will be returned the INVALID_TID. */
    td = OWN_THR_DATA;
    if (!td)
	return INVALID_TID;
    return td->thr_id;
}

int
ethr_equal_tids(ethr_tid tid1, ethr_tid tid2)
{
    /* INVALID_TID does not equal any tid, not even the INVALID_TID */
    return tid1 == tid2 && tid1 != INVALID_TID;
}

/*
 * Mutex functions.
 */

int
ethr_mutex_init(ethr_mutex *mtx)
{
#if ETHR_XCHK 
    if (ethr_not_inited || !OWN_THR_DATA) {
	ASSERT(0);
	return EACCES;
    }
    if (!mtx) {
	ASSERT(0);
	return EINVAL;
    }
#endif
#ifdef ETHR_HAVE_INITIALIZECRITICALSECTIONANDSPINCOUNT
    if (!InitializeCriticalSectionAndSpinCount(&mtx->cs, ETHR_MUTEX_SPIN_COUNT))
	return get_errno();
#else
    InitializeCriticalSection(&mtx->cs);
#endif
    mtx->initialized = ETHR_MUTEX_INITIALIZED;
#if ETHR_XCHK
    mtx->is_rec_mtx = 0;
#endif
    return 0;
}

int
ethr_rec_mutex_init(ethr_mutex *mtx)
{
    int res;
    res = ethr_mutex_init(mtx);
#if ETHR_XCHK
    mtx->is_rec_mtx = 1;
#endif
    return res;
}

int
ethr_mutex_destroy(ethr_mutex *mtx)
{
#if ETHR_XCHK 
    if (ethr_not_inited || !OWN_THR_DATA) {
	ASSERT(0);
	return EACCES;
    }
    if (!mtx || mtx->initialized != ETHR_MUTEX_INITIALIZED) {
	ASSERT(0);
	return EINVAL;
    }
    if (xchk_get_my_mutex_lock_count(mtx) != 0)
	return EINVAL;
#endif
    DeleteCriticalSection(&mtx->cs);
    mtx->initialized = 0;
    return 0;
}

int ethr_mutex_set_forksafe(ethr_mutex *mtx)
{
#if ETHR_XCHK 
    if (ethr_not_inited || !OWN_THR_DATA) {
	ASSERT(0);
	return EACCES;
    }
#endif
    return 0; /* No fork() */
}

int ethr_mutex_unset_forksafe(ethr_mutex *mtx)
{
#if ETHR_XCHK 
    if (ethr_not_inited || !OWN_THR_DATA) {
	ASSERT(0);
	return EACCES;
    }
#endif
    return 0; /* No fork() */
}

#ifdef ETHR_HAVE_TRYENTERCRITICALSECTION

int
ethr_mutex_trylock(ethr_mutex *mtx)
{
#if ETHR_XCHK
    if (ethr_not_inited || !OWN_THR_DATA) {
	ASSERT(0);
	return EACCES;
    }
    if (!mtx
	|| (mtx->initialized && mtx->initialized != ETHR_MUTEX_INITIALIZED)) {
	ASSERT(0);
	return EINVAL;
    }
#endif
    if (!mtx->initialized) {
	int res = ethr_fake_static_mutex_init(mtx);
	if (res != 0)
	    return res;
    }

#if ETHR_XCHK
    res = xchk_locking_mutex(mtx);
    if (res != 0)
	return res;
#endif
    if (TryEnterCriticalSection(&mtx->cs))
	return 0;
    else {
#if ETHR_XCHK
	(void) xchk_unlocking_mutex(mtx);
#endif
	return EBUSY;
    }
}

#endif

int
ethr_mutex_lock(ethr_mutex *mtx)
{
    int res;
#if ETHR_XCHK
    if (ethr_not_inited || !OWN_THR_DATA) {
	ASSERT(0);
	return EACCES;
    }
    if (!mtx
	|| (mtx->initialized && mtx->initialized != ETHR_MUTEX_INITIALIZED)) {
	ASSERT(0);
	return EINVAL;
    }
#endif
    if (!mtx->initialized) {
	res = ethr_fake_static_mutex_init(mtx);
	if (res != 0)
	    return res;
    }
#if ETHR_XCHK
    res = xchk_locking_mutex(mtx);
    if (res != 0)
	return res;
#endif
    EnterCriticalSection(&mtx->cs);
    return 0;
}

int
ethr_mutex_unlock(ethr_mutex *mtx)
{
#if ETHR_XCHK
    int res;
    if (ethr_not_inited || !OWN_THR_DATA) {
	ASSERT(0);
	return EACCES;
    }
    if (!mtx || mtx->initialized != ETHR_MUTEX_INITIALIZED) {
	ASSERT(0);
	return EINVAL;
    }
    res = xchk_unlocking_mutex(mtx);
    if (res != 0)
	return res;
#endif
    LeaveCriticalSection(&mtx->cs);
    return 0;
}

/*
 * Condition variable functions.
 */

int
ethr_cond_init(ethr_cond *cnd)
{
#if ETHR_XCHK 
    if (ethr_not_inited || !OWN_THR_DATA) {
	ASSERT(0);
	return EACCES;
    }
    if (!cnd) {
	ASSERT(0);
	return EINVAL;
    }
#endif
#ifdef ETHR_HAVE_INITIALIZECRITICALSECTIONANDSPINCOUNT
    if (!InitializeCriticalSectionAndSpinCount(&cnd->cs, ETHR_COND_SPIN_COUNT))
	return get_errno();
#else
    InitializeCriticalSection(&cnd->cs);
#endif
    cnd->queue = NULL;
    cnd->queue_end = NULL;
    cnd->initialized = ETHR_COND_INITIALIZED;
    return 0;
}

int
ethr_cond_destroy(ethr_cond *cnd)
{
#if ETHR_XCHK 
    if (ethr_not_inited || !OWN_THR_DATA) {
	ASSERT(0);
	return EACCES;
    }
    if (!cnd
	|| (cnd->initialized && cnd->initialized != ETHR_COND_INITIALIZED)
	|| cnd->queue) {
	ASSERT(0);
	return EINVAL;
    }
#endif
    DeleteCriticalSection(&cnd->cs);
    cnd->initialized = 0;
    return 0;
}

int
ethr_cond_signal(ethr_cond *cnd)
{
    cnd_wait_event_ *cwe;
#if ETHR_XCHK 
    if (ethr_not_inited || !OWN_THR_DATA) {
	ASSERT(0);
	return EACCES;
    }
    if (!cnd
	|| (cnd->initialized && cnd->initialized != ETHR_COND_INITIALIZED)) {
	ASSERT(0);
	return EINVAL;
    }
#endif
    if (!cnd->initialized) {
	int res = fake_static_cond_init(cnd);
	if (res != 0)
	    return res;
    }
    EnterCriticalSection(&cnd->cs);
    cwe = cnd->queue;
    if (cwe) {
	ASSERT(cwe->in_queue);
	SetEvent(cnd->queue->handle);
	if (cwe->next)
	    cwe->next->prev = NULL;
	else {
	    ASSERT(cnd->queue_end == cnd->queue);
	    cnd->queue_end = NULL;
	}
	cnd->queue = cwe->next;
	cwe->in_queue = 0;
    }
    LeaveCriticalSection(&cnd->cs);
    return 0;
}

int
ethr_cond_broadcast(ethr_cond *cnd)
{
    cnd_wait_event_ *cwe;

#if ETHR_XCHK 
    if (ethr_not_inited || !OWN_THR_DATA) {
	ASSERT(0);
	return EACCES;
    }
    if (!cnd
	|| (cnd->initialized && cnd->initialized != ETHR_COND_INITIALIZED)) {
	ASSERT(0);
	return EINVAL;
    }
#endif
    if (!cnd->initialized) {
	int res = fake_static_cond_init(cnd);
	if (res != 0)
	    return res;
    }
    EnterCriticalSection(&cnd->cs);
    for (cwe = cnd->queue; cwe; cwe = cwe->next) {
	ASSERT(cwe->in_queue);
	SetEvent(cwe->handle);
	cwe->in_queue = 0;
    }
    cnd->queue = NULL;
    cnd->queue_end = NULL;
    LeaveCriticalSection(&cnd->cs);
    return 0;

}

int
ethr_cond_wait(ethr_cond *cnd, ethr_mutex *mtx)
{
    return condwait(cnd, mtx, 0, NULL);
}

int
ethr_cond_timedwait(ethr_cond *cnd, ethr_mutex *mtx, ethr_timeval *timeout)
{
    return condwait(cnd, mtx, 1, timeout);
}


int
ethr_atomic_inctest(ethr_atomic_t *incp, long *testp)
{
#if ETHR_XCHK 
    if (ethr_not_inited || !OWN_THR_DATA) {
	ASSERT(0);
	return EACCES;
    }
    if (!incp || !testp) {
	ASSERT(0);
	return EINVAL;
    }
#endif
    *testp = (long) InterlockedIncrement(incp);
#ifdef DEBUG
    if (*testp > 0)
	*testp = 4711;
    else if (*testp < 0)
	*testp = -4711;
#endif
    return 0;
}

int
ethr_atomic_dectest(ethr_atomic_t *decp, long *testp)
{
#if ETHR_XCHK 
    if (ethr_not_inited || !OWN_THR_DATA) {
	ASSERT(0);
	return EACCES;
    }
    if (!decp || !testp) {
	ASSERT(0);
	return EINVAL;
    }
#endif
    *testp = (long) InterlockedDecrement(decp);
#ifdef DEBUG
    if (*testp > 0)
	*testp = 4711;
    else if (*testp < 0)
	*testp = -4711;
#endif
    return 0;
}

int
ethr_atomic_inc(ethr_atomic_t *incp)
{
#if ETHR_XCHK 
    if (ethr_not_inited || !OWN_THR_DATA) {
	ASSERT(0);
	return EACCES;
    }
    if (!incp) {
	ASSERT(0);
	return EINVAL;
    }
#endif
    (void) InterlockedIncrement(incp);
    return 0;
}

int
ethr_atomic_dec(ethr_atomic_t *decp)
{
#if ETHR_XCHK 
    if (ethr_not_inited || !OWN_THR_DATA) {
	ASSERT(0);
	return EACCES;
    }
    if (!decp) {
	ASSERT(0);
	return EINVAL;
    }
#endif
    (void) InterlockedDecrement(decp);
    return 0;
}

int
ethr_time_now(ethr_timeval *time)
{
#if ETHR_XCHK 
    if (ethr_not_inited || !OWN_THR_DATA) {
	ASSERT(0);
	return EACCES;
    }
    if (!time) {
	ASSERT(0);
	return EINVAL;
    }
#endif
    get_curr_time(&time->tv_sec, &time->tv_nsec);
    return 0;
}

/*
 * Thread specific data
 */

int
ethr_tsd_key_create(ethr_tsd_key *keyp)
{
    DWORD key;
#if ETHR_XCHK
    if (ethr_not_inited || !OWN_THR_DATA) {
	ASSERT(0);
	return EACCES;
    }
    if (!keyp) {
	ASSERT(0);
	return EINVAL;
    }
#endif
    key = TlsAlloc();
    if (key == TLS_OUT_OF_INDEXES)
	return get_errno();
    *keyp = (ethr_tsd_key) key;
    return 0;
}

int
ethr_tsd_key_delete(ethr_tsd_key key)
{
#if ETHR_XCHK
    if (ethr_not_inited || !OWN_THR_DATA) {
	ASSERT(0);
	return EACCES;
    }
#endif
    if (!TlsFree((DWORD) key))
	return get_errno();
    return 0;
}

int
ethr_tsd_set(ethr_tsd_key key, void *value)
{
#if ETHR_XCHK
    if (ethr_not_inited || !OWN_THR_DATA) {
	ASSERT(0);
	return EACCES;
    }
#endif
    if (!TlsSetValue((DWORD) key, (LPVOID) value))
	return get_errno();
    return 0;
}

void *
ethr_tsd_get(ethr_tsd_key key)
{
#if ETHR_XCHK
    if (ethr_not_inited || !OWN_THR_DATA) {
	ASSERT(0);
	return EACCES;
    }
#endif
    return (void *) TlsGetValue((DWORD) key);
}

#else
#error "Missing thread implementation"
#endif


#if ETHR_XCHK

static ethr_tsd_key mutex_check_key;

typedef struct ethr_xchk_lckd_mtx_ ethr_xchk_lckd_mtx;
struct ethr_xchk_lckd_mtx_ {
    ethr_xchk_lckd_mtx *next;
    ethr_mutex *mutex;
    long lock_count;
};

#define GET_MY_LOCKED_MUTEXES					\
  ((ethr_xchk_lckd_mtx *) ethr_tsd_get(mutex_check_key))
#define SET_MY_LOCKED_MUTEXES(PLP)				\
do {								\
    int res = ethr_tsd_set(mutex_check_key, (void *) (PLP));	\
    if (res != 0)						\
	abort();						\
} while (0)

static int
xchk_locking_mutex(ethr_mutex *mutex)
{
    ethr_xchk_lckd_mtx *lmp = GET_MY_LOCKED_MUTEXES;
    ethr_xchk_lckd_mtx *tlmp;
    
    for (tlmp = lmp; tlmp; tlmp = tlmp->next)
	if (tlmp->mutex == mutex)
	    break;

    if (tlmp) {
	if (!mutex->is_rec_mtx) {
	    /* Thread tries to lock a non-recursive mutex twice */
	    return ETHR_XCHK_ERROR_DEADLOCK;
	}
	else {
	    ASSERT(tlmp->lock_count > 0);
	    tlmp->lock_count++;
	}
    }
    else {
	tlmp = (ethr_xchk_lckd_mtx *) malloc(sizeof(ethr_xchk_lckd_mtx));
	tlmp->next = lmp;
	tlmp->mutex = mutex;
	tlmp->lock_count = 1;
	SET_MY_LOCKED_MUTEXES(tlmp);
    }
    return 0;
}

static int
xchk_unlocking_mutex(ethr_mutex *mutex)
{
    ethr_xchk_lckd_mtx *lmp = GET_MY_LOCKED_MUTEXES;
    ethr_xchk_lckd_mtx *ptlmp;
    ethr_xchk_lckd_mtx *tlmp;
    
    for (ptlmp = NULL, tlmp = lmp; tlmp; ptlmp = tlmp, tlmp = tlmp->next)
	if (tlmp->mutex == mutex)
	    break;

    if (!tlmp) {
	/* Thread tries to unlock a mutex that it hasn't locked */
	return EINVAL;
    }

    tlmp->lock_count--;
    if (mutex->is_rec_mtx && tlmp->lock_count > 0)
	return 0;

    ASSERT(tlmp->lock_count == 0);

    if (!ptlmp) {
	ASSERT(lmp == tlmp);
	SET_MY_LOCKED_MUTEXES(tlmp->next);
    }
    else {
	ASSERT(ptlmp->next == tlmp);
	ptlmp->next = tlmp->next;
    }
    free((void *) tlmp);

    return 0;
}

static long
xchk_get_my_mutex_lock_count(ethr_mutex *mutex)
{
    ethr_xchk_lckd_mtx *lmp;
    
    for (lmp = GET_MY_LOCKED_MUTEXES; lmp; lmp = lmp->next)
	if (lmp->mutex == mutex)
	    break;

    return lmp ? lmp->lock_count : ((long) 0);
}

int
ethr_xchk_have_locked_mutexes(void)
{
    ethr_xchk_lckd_mtx *lmp = GET_MY_LOCKED_MUTEXES;
    return lmp != NULL;
}

static int
xchk_init(void)
{
    return ethr_tsd_key_create((void *) &mutex_check_key);
}

#endif /* #if ETHR_XCHK */

#ifdef DEBUG

#include <stdio.h>
static int assert_failed(char *f, int l, char *a)
{
    fprintf(stderr, "%s:%d: Assertion failed: %s\n", f, l, a);
    abort();
    return 0;
}

#endif


