/*
 * private.c
 *
 * Description:
 * This translation unit implements routines which are private to
 * the implementation and may be used throughout it.
 *
 * --------------------------------------------------------------------------
 *
 *      Pthreads-win32 - POSIX Threads Library for Win32
 *      Copyright(C) 1998 John E. Bossom
 *      Copyright(C) 1999,2002 Pthreads-win32 contributors
 * 
 *      Contact Email: rpj@ise.canberra.edu.au
 * 
 *      The current list of contributors is contained
 *      in the file CONTRIBUTORS included with the source
 *      code distribution. The list can also be seen at the
 *      following World Wide Web location:
 *      http://sources.redhat.com/pthreads-win32/contributors.html
 * 
 *      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 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 in the file COPYING.LIB;
 *      if not, write to the Free Software Foundation, Inc.,
 *      59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
 */

#include "pthread.h"
#include "semaphore.h"
#include "implement.h"


int
ptw32_processInitialize (void)
     /*
      * ------------------------------------------------------
      * DOCPRIVATE
      *      This function performs process wide initialization for
      *      the pthread library.
      *
      * PARAMETERS
      *      N/A
      *
      * DESCRIPTION
      *      This function performs process wide initialization for
      *      the pthread library.
      *      If successful, this routine sets the global variable
      *      ptw32_processInitialized to TRUE.
      *
      * RESULTS
      * 	     TRUE    if successful,
      * 	     FALSE   otherwise
      *
      * ------------------------------------------------------
      */
{
	if (ptw32_processInitialized) {
		/* 
		 * ignore if already initialized. this is useful for 
		 * programs that uses a non-dll pthread
		 * library. such programs must call ptw32_processInitialize() explicitely,
		 * since this initialization routine is automatically called only when
		 * the dll is loaded.
		 */
		return TRUE;
	}

  ptw32_processInitialized = TRUE;

  /*
   * Initialize Keys
   */
  if ((pthread_key_create (&ptw32_selfThreadKey, NULL) != 0) ||
      (pthread_key_create (&ptw32_cleanupKey, NULL) != 0))
    {

      ptw32_processTerminate ();
    }

  /* 
   * Set up the global test and init check locks.
   */
  InitializeCriticalSection(&ptw32_mutex_test_init_lock);
  InitializeCriticalSection(&ptw32_cond_test_init_lock);
  InitializeCriticalSection(&ptw32_rwlock_test_init_lock);
  InitializeCriticalSection(&ptw32_spinlock_test_init_lock);

  return (ptw32_processInitialized);

}				/* processInitialize */

void
ptw32_processTerminate (void)
     /*
      * ------------------------------------------------------
      * DOCPRIVATE
      *      This function performs process wide termination for
      *      the pthread library.
      *
      * PARAMETERS
      *      N/A
      *
      * DESCRIPTION
      *      This function performs process wide termination for
      *      the pthread library.
      *      This routine sets the global variable
      *      ptw32_processInitialized to FALSE
      *
      * RESULTS
      * 	     N/A
      *
      * ------------------------------------------------------
      */
{
  if (ptw32_processInitialized)
    {

      if (ptw32_selfThreadKey != NULL)
	{
	  /*
	   * Release ptw32_selfThreadKey
	   */
	  pthread_key_delete (ptw32_selfThreadKey);

	  ptw32_selfThreadKey = NULL;
	}

      if (ptw32_cleanupKey != NULL)
	{
	  /*
	   * Release ptw32_cleanupKey
	   */
	  pthread_key_delete (ptw32_cleanupKey);

	  ptw32_cleanupKey = NULL;
	}

      /* 
       * Destroy the global test and init check locks.
       */
      DeleteCriticalSection(&ptw32_spinlock_test_init_lock);
      DeleteCriticalSection(&ptw32_rwlock_test_init_lock);
      DeleteCriticalSection(&ptw32_cond_test_init_lock);
      DeleteCriticalSection(&ptw32_mutex_test_init_lock);

      ptw32_processInitialized = FALSE;
    }

}				/* processTerminate */

#ifdef __CLEANUP_SEH

static DWORD
ExceptionFilter (EXCEPTION_POINTERS * ep, DWORD * ei)
{
  switch (ep->ExceptionRecord->ExceptionCode)
    {
      case EXCEPTION_PTW32_SERVICES:
	{
	  DWORD param;
	  DWORD numParams = ep->ExceptionRecord->NumberParameters;

	  numParams = (numParams > 3) ? 3 : numParams;

	  for (param = 0; param < numParams; param++)
	    {
	      ei[param] = ep->ExceptionRecord->ExceptionInformation[param];
	    }

	  return EXCEPTION_EXECUTE_HANDLER;
	  break;
	}
      default:
	{
	  /*
	   * A system unexpected exception has occurred running the user's
	   * routine. We need to cleanup before letting the exception
	   * out of thread scope.
	   */
	  pthread_t self = pthread_self();

	  (void) pthread_mutex_destroy(&self->cancelLock);
	  ptw32_callUserDestroyRoutines(self);

	  return EXCEPTION_CONTINUE_SEARCH;
	  break;
	}
    }
}

#elif defined(__CLEANUP_CXX)

#if defined(_MSC_VER)
#include <eh.h>
static terminate_function ptw32_oldTerminate;
#else
#include <new.h>
static terminate_handler ptw32_oldTerminate;
#endif

#if 0
#include <stdio.h>
static pthread_mutex_t termLock = PTHREAD_MUTEX_INITIALIZER;
#endif

void
ptw32_terminate ()
{
  pthread_t self = pthread_self();
#if 0
  FILE * fp;
  pthread_mutex_lock(&termLock);
  fp = fopen("pthread.log", "a");
  fprintf(fp, "Terminate\n");
  fclose(fp);
  pthread_mutex_unlock(&termLock);
#endif
  set_terminate(ptw32_oldTerminate);
  (void) pthread_mutex_destroy(&self->cancelLock);
  ptw32_callUserDestroyRoutines(self);
  terminate();
}

#endif /* _MSC_VER */

#if ! defined (__MINGW32__) || defined (__MSVCRT__)
unsigned __stdcall
#else
void
#endif
ptw32_threadStart (void * vthreadParms)
{
  ThreadParms *threadParms = (ThreadParms *) vthreadParms;
  pthread_t self;
  void *(*start) (void *);
  void *arg;

#ifdef __CLEANUP_SEH
  DWORD ei[] = {0,0,0};
#endif

#ifdef __CLEANUP_C
  int setjmp_rc;
#endif

  void * status = (void *) 0;

  self = threadParms->tid;
  start = threadParms->start;
  arg = threadParms->arg;

  free (threadParms);

#if defined (__MINGW32__) && ! defined (__MSVCRT__)
  /*
   * beginthread does not return the thread id and is running
   * before it returns us the thread handle, and so we do it here.
   */
  self->thread = GetCurrentThreadId ();
  /*
   * Here we're using cancelLock as a general-purpose lock
   * to make the new thread wait until the creating thread
   * has the new handle.
   */
  if (pthread_mutex_lock(&self->cancelLock) == 0)
    {
      (void) pthread_mutex_unlock(&self->cancelLock);
    }
#endif

  pthread_setspecific (ptw32_selfThreadKey, self);

  self->state = PThreadStateRunning;

#ifdef __CLEANUP_SEH

  __try
  {
    /*
     * Run the caller's routine;
     */
    status = self->exitStatus = (*start) (arg);

#ifdef _UWIN
	if (--pthread_count <= 0)
		exit(0);
#endif

  }
  __except (ExceptionFilter(GetExceptionInformation(), ei))
  {
     switch (ei[0])
       {
	case PTW32_EPS_CANCEL:
	  status = PTHREAD_CANCELED;
#ifdef _UWIN
		if (--pthread_count <= 0)
			exit(0);
#endif
	  break;
	case PTW32_EPS_EXIT:
	  status = self->exitStatus;
	  break;
	default:
	  status = PTHREAD_CANCELED;
	  break;
       }
  }

#else /* __CLEANUP_SEH */

#ifdef __CLEANUP_C

  setjmp_rc = setjmp( self->start_mark );

  if( 0 == setjmp_rc ) {

	  /*
	   * Run the caller's routine;
	   */
	  status = self->exitStatus = (*start) (arg);
  }

  else {

     switch (setjmp_rc)
       {
	case PTW32_EPS_CANCEL:
	  status = PTHREAD_CANCELED;
	  break;
	case PTW32_EPS_EXIT:
	  status = self->exitStatus;
	  break;
	default:
	  status = PTHREAD_CANCELED;
	  break;
       }
  }

#else /* __CLEANUP_C */

#ifdef __CLEANUP_CXX

  ptw32_oldTerminate = set_terminate(&ptw32_terminate);

  try
  {
    /*
     * Run the caller's routine in a nested try block so that we
     * can run the user's terminate function, which may call
     * pthread_exit() or be canceled.
     */
    try
      {
	status = self->exitStatus = (*start) (arg);
      }
    catch (ptw32_exception &)
      {
	/*
	 * Pass these through to the outer block.
	 */
	throw;
      }
    catch(...)
     {
       /*
	* We want to run the user's terminate function if supplied.
	* That function may call pthread_exit() or be canceled, which will
	* be handled by the outer try block.
	*
	* ptw32_terminate() will be called if there is no user
	* supplied function.
	*/

#if defined(_MSC_VER)
       terminate_function term_func = set_terminate(0);
#else
       terminate_handler term_func = set_terminate(0);
#endif

       set_terminate(term_func);

       if (term_func != 0) {
	   term_func();
       }

       throw;
     }
  }
  catch (ptw32_exception_cancel &)
    {
      /*
       * Thread was canceled.
       */
      status = self->exitStatus = PTHREAD_CANCELED;
    }
  catch (ptw32_exception_exit &)
    {
      /*
       * Thread was exited via pthread_exit().
       */
      status = self->exitStatus;
    }
  catch (...)
    {
      /*
       * A system unexpected exception has occurred running the user's
       * terminate routine. We get control back within this block - cleanup
       * and release the exception out of thread scope.
       */
      status = self->exitStatus = PTHREAD_CANCELED;
      (void) pthread_mutex_lock(&self->cancelLock);
      self->state = PThreadStateException;
      (void) pthread_mutex_unlock(&self->cancelLock);
      (void) pthread_mutex_destroy(&self->cancelLock);
      (void) set_terminate(ptw32_oldTerminate);
      ptw32_callUserDestroyRoutines(self);
      throw;

      /*
       * Never reached.
       */
    }

  (void) set_terminate(ptw32_oldTerminate);

#else

#error ERROR [__FILE__, line __LINE__]: Cleanup type undefined.

#endif /* __CLEANUP_CXX */
#endif /* __CLEANUP_C */
#endif /* __CLEANUP_SEH */

  (void) pthread_mutex_lock(&self->cancelLock);
  self->state = PThreadStateLast;
  (void) pthread_mutex_unlock(&self->cancelLock);


  (void) pthread_mutex_destroy(&self->cancelLock);

#if 1
  if (self->detachState == PTHREAD_CREATE_DETACHED)
    {
      /*
       * We need to cleanup the pthread now in case we have
       * been statically linked, in which case the cleanup
       * in dllMain won't get done. Joinable threads will
       * be cleaned up by pthread_join().
       *
       * Note that implicitly created pthreads (those created
       * for Win32 threads which have called pthreads routines)
       * must be cleaned up explicitly by the application 
       * (by calling pthread_win32_thread_detach_np()) if
       * this library has been statically linked. For the dll,
       * dllMain will do the cleanup automatically.
       */
      (void) pthread_win32_thread_detach_np ();
    }
  else
    {
      ptw32_callUserDestroyRoutines (self);
    }
#else
  ptw32_callUserDestroyRoutines (self);
#endif

#if ! defined (__MINGW32__) || defined (__MSVCRT__)
  _endthreadex ((unsigned) status);
#else
  _endthread ();
#endif

  /*
   * Never reached.
   */

#if ! defined (__MINGW32__) || defined (__MSVCRT__)
  return (unsigned) status;
#endif

}				/* ptw32_threadStart */

void
ptw32_threadDestroy (pthread_t thread)
{
  if (thread != NULL)
    {
      ptw32_callUserDestroyRoutines (thread);

      if (thread->cancelEvent != NULL)
	{
	  CloseHandle (thread->cancelEvent);
	}

#if ! defined (__MINGW32__) || defined (__MSVCRT__)
      /* See documentation for endthread vs endthreadex. */
      if( thread->threadH != 0 )
	{
	  CloseHandle( thread->threadH );
	}
#endif

      free (thread);
    }

}				/* ptw32_threadDestroy */

int
ptw32_tkAssocCreate (ThreadKeyAssoc ** assocP,
			pthread_t thread,
			pthread_key_t key)
     /*
      * -------------------------------------------------------------------
      * This routine creates an association that
      * is unique for the given (thread,key) combination.The association 
      * is referenced by both the thread and the key.
      * This association allows us to determine what keys the
      * current thread references and what threads a given key
      * references.
      * See the detailed description
      * at the beginning of this file for further details.
      *
      * Notes:
      *      1)      New associations are pushed to the beginning of the
      * 	     chain so that the internal ptw32_selfThreadKey association
      * 	     is always last, thus allowing selfThreadExit to
      * 	     be implicitly called by pthread_exit last.
      *
      * Parameters:
      * 	     assocP
      * 		     address into which the association is returned.
      * 	     thread
      * 		     current running thread. If NULL, then association
      * 		     is only added to the key. A NULL thread indicates
      * 		     that the user called pthread_setspecific prior
      * 		     to starting a thread. That's ok.
      * 	     key
      * 		     key on which to create an association.
      * Returns:
      *       0 	     - if successful,
      *       ENOMEM	     - not enough memory to create assoc or other object
      *       EINVAL	     - an internal error occurred
      *       ENOSYS	     - an internal error occurred
      * -------------------------------------------------------------------
      */
{
  int result;
  ThreadKeyAssoc *assoc;

  /*
   * Have to create an association and add it
   * to both the key and the thread.
   */
  assoc = (ThreadKeyAssoc *) calloc (1, sizeof (*assoc));

  if (assoc == NULL)
    {
      result = ENOMEM;
      goto FAIL0;
    }

  /*
   * Initialise only when used for the first time.
   */
  assoc->lock = PTHREAD_MUTEX_INITIALIZER;
  assoc->thread = thread;
  assoc->key = key;

  /*
   * Register assoc with key
   */
  if ((result = pthread_mutex_lock (&(key->threadsLock))) != 0)
    {
      goto FAIL2;
    }

  assoc->nextThread = (ThreadKeyAssoc *) key->threads;
  key->threads = (void *) assoc;

  pthread_mutex_unlock (&(key->threadsLock));

  if (thread != NULL)
    {
      /*
       * Register assoc with thread
       */
      assoc->nextKey = (ThreadKeyAssoc *) thread->keys;
      thread->keys = (void *) assoc;
    }

  *assocP = assoc;

  return (result);

  /*
   * -------------
   * Failure Code
   * -------------
   */
FAIL2:
  pthread_mutex_destroy (&(assoc->lock));
  free (assoc);

FAIL0:

  return (result);

}				/* ptw32_tkAssocCreate */


void
ptw32_tkAssocDestroy (ThreadKeyAssoc * assoc)
     /*
      * -------------------------------------------------------------------
      * This routine releases all resources for the given ThreadKeyAssoc
      * once it is no longer being referenced
      * ie) both the key and thread have stopped referencing it.
      *
      * Parameters:
      * 	     assoc
      * 		     an instance of ThreadKeyAssoc.
      * Returns:
      *      N/A
      * -------------------------------------------------------------------
      */
{

  if ((assoc != NULL) &&
      (assoc->key == NULL && assoc->thread == NULL))
    {

      pthread_mutex_destroy (&(assoc->lock));

      free (assoc);
    }

}				/* ptw32_tkAssocDestroy */


void
ptw32_callUserDestroyRoutines (pthread_t thread)
     /*
      * -------------------------------------------------------------------
      * DOCPRIVATE
      *
      * This the routine runs through all thread keys and calls
      * the destroy routines on the user's data for the current thread.
      * It simulates the behaviour of POSIX Threads.
      *
      * PARAMETERS
      * 	     thread
      * 		     an instance of pthread_t
      *
      * RETURNS
      * 	     N/A
      * -------------------------------------------------------------------
      */
{
  ThreadKeyAssoc **nextP;
  ThreadKeyAssoc *assoc;

  if (thread != NULL)
    {
      /*
       * Run through all Thread<-->Key associations
       * for the current thread.
       * If the pthread_key_t still exits (ie the assoc->key
       * is not NULL) then call the user's TSD destroy routine.
       * Notes:
       *      If assoc->key is NULL, then the user previously called
       *      PThreadKeyDestroy. The association is now only referenced
       *      by the current thread and must be released; otherwise
       *      the assoc will be destroyed when the key is destroyed.
       */
      nextP = (ThreadKeyAssoc **) & (thread->keys);
      assoc = *nextP;

      while (assoc != NULL)
	{

	  if (pthread_mutex_lock (&(assoc->lock)) == 0)
	    {
	      pthread_key_t k;
	      if ((k = assoc->key) != NULL)
		{
		  /*
		   * Key still active; pthread_key_delete
		   * will block on this same mutex before
		   * it can release actual key; therefore,
		   * key is valid and we can call the destroy
		   * routine;
		   */
		  void *value = NULL;

		  value = pthread_getspecific (k);
		  if (value != NULL && k->destructor != NULL)
		    {

#ifdef __cplusplus

		      try
		      {
			/*
			 * Run the caller's cleanup routine.
			 */
			(*(k->destructor)) (value);
		      }
		      catch (...)
		      {
			/*
			 * A system unexpected exception has occurred
			 * running the user's destructor.
			 * We get control back within this block in case
			 * the application has set up it's own terminate
			 * handler. Since we are leaving the thread we
			 * should not get any internal pthreads
			 * exceptions.
			 */
			terminate();
		      }

#else  /* __cplusplus */

			/*
			 * Run the caller's cleanup routine.
			 */
			(*(k->destructor)) (value);

#endif /* __cplusplus */
		    }
		}

	      /*
	       * mark assoc->thread as NULL to indicate the
	       * thread no longer references this association
	       */
	      assoc->thread = NULL;

	      /*
	       * Remove association from the pthread_t chain
	       */
	      *nextP = assoc->nextKey;

	      pthread_mutex_unlock (&(assoc->lock));

	      ptw32_tkAssocDestroy (assoc);

	      assoc = *nextP;
	    }
	}
    }

}				/* ptw32_callUserDestroyRoutines */



#ifdef NEED_FTIME

/*
 * time between jan 1, 1601 and jan 1, 1970 in units of 100 nanoseconds
 */
#define TIMESPEC_TO_FILETIME_OFFSET \
	  ( ((LONGLONG) 27111902 << 32) + (LONGLONG) 3577643008 )

static INLINE void
timespec_to_filetime(const struct timespec *ts, FILETIME *ft)
     /*
      * -------------------------------------------------------------------
      * converts struct timespec
      * where the time is expressed in seconds and nanoseconds from Jan 1, 1970.
      * into FILETIME (as set by GetSystemTimeAsFileTime), where the time is
      * expressed in 100 nanoseconds from Jan 1, 1601,
      * -------------------------------------------------------------------
      */
{
	*(LONGLONG *)ft = ts->tv_sec * 10000000 + (ts->tv_nsec + 50) / 100 + TIMESPEC_TO_FILETIME_OFFSET;
}

static INLINE void
filetime_to_timespec(const FILETIME *ft, struct timespec *ts)
     /*
      * -------------------------------------------------------------------
      * converts FILETIME (as set by GetSystemTimeAsFileTime), where the time is
      * expressed in 100 nanoseconds from Jan 1, 1601,
      * into struct timespec
      * where the time is expressed in seconds and nanoseconds from Jan 1, 1970.
      * -------------------------------------------------------------------
      */
{
	ts->tv_sec = (int)((*(LONGLONG *)ft - TIMESPEC_TO_FILETIME_OFFSET) / 10000000);
	ts->tv_nsec = (int)((*(LONGLONG *)ft - TIMESPEC_TO_FILETIME_OFFSET - ((LONGLONG)ts->tv_sec * (LONGLONG)10000000)) * 100);
}

#endif /* NEED_FTIME */


DWORD
ptw32_get_exception_services_code(void)
{
#ifdef __CLEANUP_SEH

  return EXCEPTION_PTW32_SERVICES;

#else

  return (DWORD) NULL;

#endif
}


void
ptw32_throw(DWORD exception)
{
#ifdef __CLEANUP_C
  pthread_t self = pthread_self();
#endif


#ifdef __CLEANUP_SEH
  DWORD exceptionInformation[3];
#endif

  if (exception != PTW32_EPS_CANCEL &&
      exception != PTW32_EPS_EXIT)
    {
      /* Should never enter here */
      exit(1);
    }

#ifdef __CLEANUP_SEH


  exceptionInformation[0] = (DWORD) (exception);
  exceptionInformation[1] = (DWORD) (0);
  exceptionInformation[2] = (DWORD) (0);

  RaiseException (
		  EXCEPTION_PTW32_SERVICES,
		  0,
		  3,
		  exceptionInformation);

#else /* __CLEANUP_SEH */

#ifdef __CLEANUP_C

  ptw32_pop_cleanup_all( 1 );

  longjmp( self->start_mark, exception );

#else /* __CLEANUP_C */

#ifdef __CLEANUP_CXX

  switch (exception)
    {
    case PTW32_EPS_CANCEL:
      throw ptw32_exception_cancel();
      break;
    case PTW32_EPS_EXIT:
      throw ptw32_exception_exit();
      break;
    }

#else

#error ERROR [__FILE__, line __LINE__]: Cleanup type undefined.

#endif /* __CLEANUP_CXX */

#endif /* __CLEANUP_C */

#endif /* __CLEANUP_SEH */

  /* Never reached */
}

void
ptw32_pop_cleanup_all(int execute)
{
	while( NULL != ptw32_pop_cleanup(execute) ) {
	}
}


/*
 * ptw32_InterlockedCompareExchange --
 *
 * Needed because W95 doesn't support InterlockedCompareExchange.
 * It is only used when running the dll on W95. Other versions of
 * Windows use the Win32 supported version, which may be running on
 * different processor types.
 *
 * This can't be inlined because we need to know it's address so that
 * we can call it through a pointer.
 */
PTW32_INTERLOCKED_LONG WINAPI
ptw32_InterlockedCompareExchange(PTW32_INTERLOCKED_LPLONG location,
				 PTW32_INTERLOCKED_LONG   value,
				 PTW32_INTERLOCKED_LONG   comparand)
{
  PTW32_INTERLOCKED_LONG result;

#if defined(_M_IX86) || defined(_X86_)

#if defined(_MSC_VER)

  _asm {
    PUSH	 ecx
    PUSH	 edx
    MOV 	 ecx,dword ptr [location]
    MOV 	 edx,dword ptr [value]
    MOV 	 eax,dword ptr [comparand]
    LOCK CMPXCHG dword ptr [ecx],edx	    ; if (EAX == [ECX]), 
					    ;	[ECX] = EDX
					    ; else
					    ;	EAX = [ECX]
    MOV 	 dword ptr [result], eax
    POP 	 edx
    POP 	 ecx
  }

#elif defined(__GNUC__)

  __asm__
    (
     "lock\n\t"
     "cmpxchgl	     %3,(%0)"	 /* if (EAX == [location]), */
				 /*   [location] = value    */
				 /* else		    */
				 /*   EAX = [location]		 */
     :"=r" (location), "=a" (result)
     :"0"  (location), "q" (value), "a" (comparand)
     : "memory" );

#endif

#else

  /*
   * If execution gets to here then we should be running on a Win95 system
   * but either running on something other than an X86 processor, or a
   * compiler other than MSVC or GCC. Pthreads-win32 doesn't support that
   * platform (yet).
   */

  result = 0;

#endif

  return result;
}


/*
 * ptw32_getprocessors()
 *
 * Get the number of CPUs available to the process.
 *
 * If the available number of CPUs is 1 then pthread_spin_lock()
 * will block rather than spin if the lock is already owned.
 *
 * pthread_spin_init() calls this routine when initialising
 * a spinlock. If the number of available processors changes
 * (after a call to SetProcessAffinityMask()) then only
 * newly initialised spinlocks will notice.
 */
int
ptw32_getprocessors(int * count)
{
  DWORD vProcessCPUs;
  DWORD vSystemCPUs;
  int result = 0;

  if (GetProcessAffinityMask(GetCurrentProcess(),
			     &vProcessCPUs,
			     &vSystemCPUs))
    {
      DWORD bit;
      int CPUs = 0;

      for (bit = 1; bit != 0; bit <<= 1)
	{
	  if (vProcessCPUs & bit)
	    {
	      CPUs++;
	    }
	}
      *count = CPUs;
    }
  else
    {
      result = EAGAIN;
    }

  return(result);
}