/*
 * DUMA - Red-Zone memory allocator.
 * Copyright (C) 2002-2008 Hayati Ayguen <h_ayguen@web.de>, Procitec GmbH
 * License: GNU LGPL (GNU Lesser General Public License, see COPYING-GPL)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * FILE CONTENTS:
 * internal implementation file
 * contains thread safety functions (semaphore lock/release)
 */

#include "duma_config.h"
#include "duma_sem.h"
#include "print.h"

#ifndef DUMA_NO_THREAD_SAFETY

/* check for pthread library */
/* use WIN32_SEMAPHORES on Win32-Cygwin,
 * with this configuration testmt.c works either with pthreads and with the Win32 API
 */
#if (!defined(WIN32))
#define HAVE_PTHREADS         1
#define USE_WIN32_SEMAPHORES  0
#define USE_WIN32_CRIT_SECT   0
#elif ( defined(__MINGW32__) || defined(__MINGW64__) || defined(__CYGWIN__) || defined(__CYGWIN32__) || defined(__CYGWIN64__) )
#define HAVE_PTHREADS         0
#define USE_WIN32_SEMAPHORES  0
#define USE_WIN32_CRIT_SECT   1
#else
#define HAVE_PTHREADS         0
#define USE_WIN32_SEMAPHORES  0
#define USE_WIN32_CRIT_SECT   1
#endif

#if HAVE_PTHREADS
  #include <pthread.h>
  #include <semaphore.h>
#elif USE_WIN32_SEMAPHORES || USE_WIN32_CRIT_SECT
  #define WIN32_LEAN_AND_MEAN 1
  #include <windows.h>
  #include <winbase.h>
#endif


/*
 * DUMA_sem is a semaphore used to allow one thread at a time into
 * these routines.
 * Also, we use semInited as a boolean to see if we should be
 * using the semaphore.
 * semThread is set to the thread id of the thread that currently
 * has the semaphore so that when/if it tries to get the semaphore
 * again (realloc calling malloc/free) - nothing will happen to the
 * semaphore.
 * semDepth is used to keep track of how many times the same thread
 * gets the semaphore - so we know when it is actually freed.
 */


#if HAVE_PTHREADS

  #define DUMA_thread_self()  pthread_self()

  #ifndef DUMA_SEMAPHORES
	static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
	static pthread_t mutextid=0;
	static int locknr=0;
  #else
	static sem_t      DUMA_sem = { 0 };
	static pthread_t  semThread = (pthread_t) 0;
  #endif

#elif USE_WIN32_SEMAPHORES

  #define DUMA_thread_self()  GetCurrentThreadId()

  #ifndef UNICODE
	#define SEM_NAME_TYPE char
	#define SEM_STRCPY    strcpy
	#define SEM_STRCAT    strcat
	static char    semObjectName[] = "DUMA_";
  #else
	#define SEM_NAME_TYPE wchar_t
	#define SEM_STRCPY    wcscpy
	#define SEM_STRCAT    wcscat
	static wchar_t semObjectName[] = L"DUMA_";
  #endif

  static SECURITY_ATTRIBUTES  semSecAttr;
  static DWORD      semThread = 0;
  static HANDLE     semHandle = 0;

#elif USE_WIN32_CRIT_SECT

  /* see http://msdn.microsoft.com/en-us/library/ms682530(VS.85).aspx */
  static CRITICAL_SECTION critsect;

#endif


static int semInInit = 0;

#if HAVE_PTHREADS && !defined(DUMA_SEMAPHORES)
static int semInited = 1;
static int semDepth  = 0;
#elif USE_WIN32_SEMAPHORES
static int semInited = 0;
static int semDepth  = 0;
#elif USE_WIN32_CRIT_SECT
static int semInited = 0;
static int semDepth  = 0;  /* not really used, but avoids more ifdefs below */
#endif

#if HAVE_PTHREADS

#ifndef DUMA_SEMAPHORES

static void lock()
{
  if (pthread_mutex_trylock(&mutex))
  {
	if ( mutextid==pthread_self() )
	{
	  ++locknr;
	  return;
	}
	else
	{
	  pthread_mutex_lock(&mutex);
	}
  }
  mutextid=pthread_self();
  locknr=1;
}

static void unlock()
{
  --locknr;
  if (!locknr)
  {
	mutextid=0;
	pthread_mutex_unlock(&mutex);
  }
}

#endif
#endif


void
DUMA_init_sem(void)
{
#if USE_WIN32_SEMAPHORES
  SEM_NAME_TYPE   semLocalName[32];
  SEM_NAME_TYPE   acPID[16];
  DWORD pid;
#endif

  /* avoid recursive call to sem_init(),
   * when sem_init() calls malloc() or other allocation function
   */
  if (semInited || semInInit)
	return;
  semInInit = 1;

#if HAVE_PTHREADS
  #ifndef DUMA_SEMAPHORES
	pthread_mutex_init(&mutex, NULL);
	semInited = 1;
  #else
	if (sem_init(&DUMA_sem, 0, 1) >= 0)
	  semInited = 1;
  #endif
#elif USE_WIN32_SEMAPHORES
  pid = GetCurrentProcessId();
  SEM_STRCPY(semLocalName, semObjectName);
  /* append ProcessId() to get inter-process unique semaphore name */
  acPID[0] = 'A' + (SEM_NAME_TYPE)( (pid >> 28) & 0x0F );
  acPID[1] = 'A' + (SEM_NAME_TYPE)( (pid >> 24) & 0x0F );
  acPID[2] = 'A' + (SEM_NAME_TYPE)( (pid >> 20) & 0x0F );
  acPID[3] = 'A' + (SEM_NAME_TYPE)( (pid >> 16) & 0x0F );
  acPID[4] = 'A' + (SEM_NAME_TYPE)( (pid >> 12) & 0x0F );
  acPID[5] = 'A' + (SEM_NAME_TYPE)( (pid >>  8) & 0x0F );
  acPID[6] = 'A' + (SEM_NAME_TYPE)( (pid >>  4) & 0x0F );
  acPID[7] = 'A' + (SEM_NAME_TYPE)( (pid      ) & 0x0F );
  acPID[8] = 0;
  SEM_STRCAT( semLocalName, acPID );

  semSecAttr.nLength = sizeof(semSecAttr);
  semSecAttr.lpSecurityDescriptor = NULL;
  semSecAttr.bInheritHandle = FALSE;

  semHandle = CreateSemaphore( &semSecAttr      /* pointer to security attributes */
							   , 1              /* initial count */
							   , 1              /* maximum count */
							   , semLocalName   /* pointer to semaphore-object name */
							 );
  semInited = 1;
#elif USE_WIN32_CRIT_SECT
  InitializeCriticalSection(&critsect);
  semInited = 1;
#endif

  semInInit = 0;

  if (!semInited)     DUMA_Abort("\nCouldn't initialise semaphore");
}


void DUMA_get_sem(void)
{
  if (semInInit)      return;             /* avoid recursion */
  if (!semInited)     DUMA_init_sem();    /* initialize if necessary */

#if HAVE_PTHREADS
  #ifndef DUMA_SEMAPHORES
	lock();
  #else
	if (semThread != DUMA_thread_self())
	{
	  while (sem_wait(&DUMA_sem) < 0);   /* wait for the semaphore. */
	  semThread = DUMA_thread_self();     /* let everyone know who has the semaphore. */
	}
  #endif
  ++semDepth;                         /* increment semDepth - push one stack level */
#elif USE_WIN32_SEMAPHORES
  if (semThread != DUMA_thread_self())
  {
	while (WaitForSingleObject(semHandle, 1000) != WAIT_OBJECT_0) ; /* wait for the semaphore. */
	semThread = DUMA_thread_self();     /* let everyone know who has the semaphore. */
  }
  ++semDepth;                         /* increment semDepth - push one stack level */
#elif USE_WIN32_CRIT_SECT
  EnterCriticalSection(&critsect);
#endif
}


int DUMA_rel_sem(int retval)
{
  if (semInInit)      return retval;  /* avoid recursion */
  if (!semInited)     DUMA_Abort("\nSemaphore isn't initialised");

#ifdef DUMA_SEMAPHORES
  if (!semThread)     DUMA_Abort("\nSemaphore isn't owned by this thread");
#endif

#if HAVE_PTHREADS || USE_WIN32_SEMAPHORES
  if (semDepth <= 0)  DUMA_Abort("\nSemaphore isn't locked");
#endif

  --semDepth;              /* decrement semDepth - popping one stack level */
#if HAVE_PTHREADS
  #ifndef DUMA_SEMAPHORES
	unlock();
  #else
	semThread = (pthread_t) 0;        /* zero this before actually free'ing the semaphore. */
	if (sem_post(&DUMA_sem) < 0)
	  DUMA_Abort("Failed to post the semaphore.");
  #endif
#elif USE_WIN32_SEMAPHORES
  semThread = 0;                    /* zero this before actually free'ing the semaphore. */
  if (0 == ReleaseSemaphore(semHandle, 1 /* amount to add to current count */, NULL) )
	DUMA_Abort("Failed to post the semaphore.");
#elif USE_WIN32_CRIT_SECT
  LeaveCriticalSection(&critsect);
#endif
  return retval;
}

#else

/* for not having an empty file */
static int dummy = 0;

#endif /* DUMA_NO_THREAD_SAFETY */
