/*
 * %CopyrightBegin%
 * 
 * Copyright Ericsson AB 2001-2009. All Rights Reserved.
 * 
 * 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 online 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.
 * 
 * %CopyrightEnd%
 *

 */

/* FIXME why not use ei_malloc here? */

#include "eidef.h"

#include <stdlib.h>
#include "ei.h"
#include "ei_locking.h"

#ifdef __WIN32__
#ifdef USE_DECLSPEC_THREAD
/* Define (and initialize) the variable __erl_errno */
volatile __declspec(thread) int __erl_errno = 0;
#else
static volatile DWORD errno_tls_index = TLS_OUT_OF_INDEXES;
static LONG volatile tls_init_mutex = 0;
#endif
#endif

#if defined(VXWORKS)

/* 
   Moved to each of the erl_*threads.c files, as they seem to know how
   to get thread-safety. 
*/
static volatile int __erl_errno;
volatile int *__erl_errno_place(void)
{
    /* This check is somewhat insufficient, double task var entries will occur
       if __erl_errno is actually -1, which on the other hand is an invalid 
       error code. */
    if (taskVarGet(taskIdSelf(), &__erl_errno) == ERROR) {
	taskVarAdd(taskIdSelf(), &__erl_errno);
    }
    return &__erl_errno;
}
#endif /* VXWORKS */

#if defined(__WIN32__)

#ifdef USE_DECLSPEC_THREAD

volatile int *__erl_errno_place(void)
{
    return &__erl_errno;
}

#else
static void tls_init_once(void)
{

    if (errno_tls_index != TLS_OUT_OF_INDEXES) {
	return;
    }
    if (InterlockedExchange((LPLONG) &tls_init_mutex,1L) == 0) {
	/* I was first */
	errno_tls_index = TlsAlloc();
	if (errno_tls_index == TLS_OUT_OF_INDEXES) {
	    fprintf(stderr, 
		    "FATAL ERROR: can not allocate TLS index for "
		    "erl_errno (error code = %d)!\n",GetLastError());
	    exit(1);
	}
    } else {
	while (errno_tls_index == TLS_OUT_OF_INDEXES) {
	    SwitchToThread();
	}
    }
}

volatile int *__erl_errno_place(void)
{
    volatile int *ptr;
    tls_init_once();
    ptr = TlsGetValue(errno_tls_index);
    if (ptr == NULL) {
	ptr = malloc(sizeof(int));
	*ptr = 0;
	TlsSetValue(errno_tls_index, (PVOID) ptr);
    }
    return ptr;
}

#endif /* USE_DECLSPEC_THREAD */

#endif /* __WIN32__ */

#if defined(_REENTRANT) && !defined(VXWORKS) && !defined(__WIN32__)

#if defined(HAVE_PTHREAD_H) || defined(HAVE_MIT_PTHREAD_H)

void *ei_m_create(void)  
{ 
  pthread_mutex_t *l;
 
  if ((l = malloc(sizeof(*l)))) { /* FIXME get memory or abort */
    pthread_mutex_init(l,NULL);
  }

  return l;
}

int ei_m_destroy(void *l) 
{ 
  int r = pthread_mutex_destroy(l);
  free(l);
  
  return r;
}

int ei_m_lock(void *l)    
{ 
  return pthread_mutex_lock(l);
}

int ei_m_trylock(void *l) 
{ 
  return pthread_mutex_trylock(l);
}

int ei_m_unlock(void *l)  
{ 
  return pthread_mutex_unlock(l);
} 


/*
 * Thread-specific erl_errno variable.
 *
 * The second line below will give a "missing braces around initializer"
 * on Solaris but the code will work.
 */

static pthread_key_t erl_errno_key;
static pthread_once_t erl_errno_key_once = PTHREAD_ONCE_INIT;

/*
 * Destroy per-thread erl_errno locus
 */
static void erl_errno_destroy(void * ptr)
{
    free(ptr);
}

/*
 * Allocate erl_errno key.
 * This will be done once for all threads
 */
static void erl_errno_key_alloc(void)
{
    pthread_key_create(&erl_errno_key, erl_errno_destroy);
}

/*
 * Return a pointer to the erl_errno locus.
 * If pthread functions fail we fall back to using fallback_errno
 * so that the main thread (actually not a thread in all ascpects)
 * still will set and get an erl_errno value.
 * Actually this is a bit to nice, it would be preferrable to exit fatal
 * as we do on windows, but we might break some code with one thread
 * but still compiled with -D_REENTRANT, so we'll leave it here.
 */
volatile int *__erl_errno_place(void)
{
    int *erl_errno_p;
    static volatile int use_fallback = 0;
    static volatile int fallback_errno = 0;

    if (use_fallback) {
	return &fallback_errno;
    }	

    /* This will create the key once for all threads */
    if (pthread_once(&erl_errno_key_once, erl_errno_key_alloc) != 0) {
	use_fallback = 1;
	return &fallback_errno;
    }

    /* This is the normal case, return the pointer to the data */
    if ((erl_errno_p = pthread_getspecific(erl_errno_key)) != NULL) {
	return erl_errno_p;
    }

    if ((erl_errno_p = malloc(sizeof(int))) == NULL) {
	use_fallback = 1;
	return &fallback_errno;
    }

    if (pthread_setspecific(erl_errno_key, erl_errno_p) != 0 ||
	(erl_errno_p = pthread_getspecific(erl_errno_key)) == NULL) {
	free(erl_errno_p);
	return &fallback_errno;
    }

    return erl_errno_p;
}

#endif /* HAVE_PTHREAD_H || HAVE_MIT_PTHREAD_H */

#endif /* _REENTRANT && !VXWORKS && !__WIN32__ */

#if !defined(_REENTRANT) && !defined(VXWORKS) && !defined(__WIN32__)

volatile int __erl_errno;

#endif
