/*
 * rwlock.c
 *
 * Description:
 * This translation unit implements read/write lock primitives.
 *
 * Pthreads-win32 - POSIX Threads Library for Win32
 * Copyright (C) 1998 Ben Elliston and Ross Johnson
 * Copyright (C) 1999,2000,2001 Ross Johnson
 *
 * Contact Email: rpj@ise.canberra.edu.au
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library 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
 */

#include <errno.h>
#include <limits.h>

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

static INLINE int
ptw32_rwlock_check_need_init(pthread_rwlock_t *rwlock)
{
  int result = 0;

  /*
   * The following guarded test is specifically for statically
   * initialised rwlocks (via PTHREAD_RWLOCK_INITIALIZER).
   *
   * Note that by not providing this synchronisation we risk
   * introducing race conditions into applications which are
   * correctly written.
   *
   * Approach
   * --------
   * We know that static rwlocks will not be PROCESS_SHARED
   * so we can serialise access to internal state using
   * Win32 Critical Sections rather than Win32 Mutexes.
   *
   * If using a single global lock slows applications down too much,
   * multiple global locks could be created and hashed on some random
   * value associated with each mutex, the pointer perhaps. At a guess,
   * a good value for the optimal number of global locks might be
   * the number of processors + 1.
   *
   */
  EnterCriticalSection(&ptw32_rwlock_test_init_lock);

  /*
   * We got here possibly under race
   * conditions. Check again inside the critical section
   * and only initialise if the rwlock is valid (not been destroyed).
   * If a static rwlock has been destroyed, the application can
   * re-initialise it only by calling pthread_rwlock_init()
   * explicitly.
   */
  if (*rwlock == PTHREAD_RWLOCK_INITIALIZER)
    {
      result = pthread_rwlock_init(rwlock, NULL);
    }
  else if (*rwlock == NULL)
    {
      /*
       * The rwlock has been destroyed while we were waiting to
       * initialise it, so the operation that caused the
       * auto-initialisation should fail.
       */
      result = EINVAL;
    }

  LeaveCriticalSection(&ptw32_rwlock_test_init_lock);

  return result;
}

int
pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr)
{
    int result;
    pthread_rwlock_t rwl = 0;

    if (rwlock == NULL)
      {
        return EINVAL;
      }

    if (attr != NULL && *attr != NULL)
      {
        result = EINVAL; /* Not supported */
        goto DONE;
      }

    rwl = (pthread_rwlock_t) calloc(1, sizeof(*rwl));

    if (rwl == NULL)
      {
        result = ENOMEM;
        goto DONE;
      }

    rwl->nSharedAccessCount = 0;
    rwl->nExclusiveAccessCount = 0;
    rwl->nCompletedSharedAccessCount = 0;

    result = pthread_mutex_init(&rwl->mtxExclusiveAccess, NULL);
    if (result != 0)
      {
        goto FAIL0;
      }

    result = pthread_mutex_init(&rwl->mtxSharedAccessCompleted, NULL);
    if (result != 0)
      {
        goto FAIL1;
      }

    result = pthread_cond_init(&rwl->cndSharedAccessCompleted, NULL);
    if (result != 0)
      {
        goto FAIL2;
      }

    rwl->nMagic = PTW32_RWLOCK_MAGIC;

    result = 0;
    goto DONE;

FAIL2:
    (void) pthread_mutex_destroy(&(rwl->mtxSharedAccessCompleted));

FAIL1:
    (void) pthread_mutex_destroy(&(rwl->mtxExclusiveAccess));

FAIL0:
    (void) free(rwl);
    rwl = NULL;

DONE:
    *rwlock = rwl;

    return result;
}

int
pthread_rwlock_destroy(pthread_rwlock_t *rwlock)
{
    pthread_rwlock_t rwl;
    int result = 0, result1 = 0, result2 = 0;

    if (rwlock == NULL || *rwlock == NULL)
      {
        return EINVAL;
      }

    if (*rwlock != PTHREAD_RWLOCK_INITIALIZER)
      {
        rwl = *rwlock;

        if (rwl->nMagic != PTW32_RWLOCK_MAGIC)
          {
            return EINVAL;
          }

        if ((result = pthread_mutex_lock(&(rwl->mtxExclusiveAccess))) != 0)
          {
            return result;
          }

        if ((result = pthread_mutex_lock(&(rwl->mtxSharedAccessCompleted))) != 0)
          {
            (void) pthread_mutex_unlock(&(rwl->mtxExclusiveAccess));
            return result;
          }

        /*
         * Check whether any threads own/wait for the lock (wait for ex.access);
         * report "BUSY" if so.
         */
        if (rwl->nExclusiveAccessCount > 0
            || rwl->nSharedAccessCount > rwl->nCompletedSharedAccessCount)
          {
            result = pthread_mutex_unlock(&(rwl->mtxSharedAccessCompleted));
            result1 = pthread_mutex_unlock(&(rwl->mtxExclusiveAccess));
            result2 = EBUSY;
          }
        else 
          {
            rwl->nMagic = 0;

            if ((result = pthread_mutex_unlock(&(rwl->mtxSharedAccessCompleted))) != 0)
              {
                pthread_mutex_unlock(&rwl->mtxExclusiveAccess);
                return result;
              }

            if ((result = pthread_mutex_unlock(&(rwl->mtxExclusiveAccess))) != 0)
              {
                return result;
              }

            *rwlock = NULL; /* Invalidate rwlock before anything else */
            result = pthread_cond_destroy(&(rwl->cndSharedAccessCompleted));
            result1 = pthread_mutex_destroy(&(rwl->mtxSharedAccessCompleted));
            result2 = pthread_mutex_destroy(&(rwl->mtxExclusiveAccess));
            (void) free(rwl);
          }
      }
    else
      {
        /*
         * See notes in ptw32_rwlock_check_need_init() above also.
         */
        EnterCriticalSection(&ptw32_rwlock_test_init_lock);

        /*
         * Check again.
         */
        if (*rwlock == PTHREAD_RWLOCK_INITIALIZER)
          {
            /*
             * This is all we need to do to destroy a statically
             * initialised rwlock that has not yet been used (initialised).
             * If we get to here, another thread
             * waiting to initialise this rwlock will get an EINVAL.
             */
            *rwlock = NULL;
          }
        else
          {
            /*
             * The rwlock has been initialised while we were waiting
             * so assume it's in use.
             */
            result = EBUSY;
          }

        LeaveCriticalSection(&ptw32_rwlock_test_init_lock);
      }

    return ((result != 0) ? result : ((result1 != 0) ? result1 : result2));
}


int
pthread_rwlockattr_init (pthread_rwlockattr_t * attr)
     /*
      * ------------------------------------------------------
      * DOCPUBLIC
      *      Initializes a rwlock attributes object with default
      *      attributes.
      *
      * PARAMETERS
      *      attr
      *              pointer to an instance of pthread_rwlockattr_t
      *
      *
      * DESCRIPTION
      *      Initializes a rwlock attributes object with default
      *      attributes.
      *
      * RESULTS
      *              0               successfully initialized attr,
      *              ENOMEM          insufficient memory for attr.
      *
      * ------------------------------------------------------
      */
{
  int result = 0;
  pthread_rwlockattr_t rwa;

  rwa = (pthread_rwlockattr_t) calloc (1, sizeof (*rwa));

  if (rwa == NULL)
    {
      result = ENOMEM;
    }
  else
    {
      rwa->pshared = PTHREAD_PROCESS_PRIVATE;
    }

  *attr = rwa;

  return(result);
}                               /* pthread_rwlockattr_init */


int
pthread_rwlockattr_destroy (pthread_rwlockattr_t * attr)
     /*
      * ------------------------------------------------------
      * DOCPUBLIC
      *      Destroys a rwlock attributes object. The object can
      *      no longer be used.
      *
      * PARAMETERS
      *      attr
      *              pointer to an instance of pthread_rwlockattr_t
      *
      *
      * DESCRIPTION
      *      Destroys a rwlock attributes object. The object can
      *      no longer be used.
      *
      *      NOTES:
      *              1)      Does not affect rwlockss created using 'attr'
      *
      * RESULTS
      *              0               successfully released attr,
      *              EINVAL          'attr' is invalid.
      *
      * ------------------------------------------------------
      */
{
  int result = 0;

  if (attr == NULL || *attr == NULL)
    {
      result = EINVAL;
    }
  else
    {
      pthread_rwlockattr_t rwa = *attr;

      *attr = NULL;
      free (rwa);
    }

  return(result);
}                               /* pthread_rwlockattr_destroy */


int
pthread_rwlockattr_getpshared (const pthread_rwlockattr_t * attr,
                               int *pshared)
     /*
      * ------------------------------------------------------
      * DOCPUBLIC
      *      Determine whether rwlocks created with 'attr' can be
      *      shared between processes.
      *
      * PARAMETERS
      *      attr
      *              pointer to an instance of pthread_rwlockattr_t
      *
      *      pshared
      *              will be set to one of:
      *
      *                      PTHREAD_PROCESS_SHARED
      *                              May be shared if in shared memory
      *
      *                      PTHREAD_PROCESS_PRIVATE
      *                              Cannot be shared.
      *
      *
      * DESCRIPTION
      *      Rwlocks creatd with 'attr' can be shared between
      *      processes if pthread_rwlock_t variable is allocated
      *      in memory shared by these processes.
      *      NOTES:
      *              1)      pshared rwlocks MUST be allocated in shared
      *                      memory.
      *              2)      The following macro is defined if shared rwlocks
      *                      are supported:
      *                              _POSIX_THREAD_PROCESS_SHARED
      *
      * RESULTS
      *              0               successfully retrieved attribute,
      *              EINVAL          'attr' is invalid,
      *
      * ------------------------------------------------------
      */
{
  int result;

  if ((attr != NULL && *attr != NULL) &&
      (pshared != NULL))
    {
      *pshared = (*attr)->pshared;
      result = 0;
    }
  else
    {
      result = EINVAL;
    }

  return (result);

}                               /* pthread_rwlockattr_getpshared */


int
pthread_rwlockattr_setpshared (pthread_rwlockattr_t * attr,
			      int pshared)
     /*
      * ------------------------------------------------------
      * DOCPUBLIC
      *      Rwlocks created with 'attr' can be shared between
      *      processes if pthread_rwlock_t variable is allocated
      *      in memory shared by these processes.
      *
      * PARAMETERS
      *      attr
      *              pointer to an instance of pthread_rwlockattr_t
      *
      *      pshared
      *              must be one of:
      *
      *                      PTHREAD_PROCESS_SHARED
      *                              May be shared if in shared memory
      *
      *                      PTHREAD_PROCESS_PRIVATE
      *                              Cannot be shared.
      *
      * DESCRIPTION
      *      Rwlocks creatd with 'attr' can be shared between
      *      processes if pthread_rwlock_t variable is allocated
      *      in memory shared by these processes.
      *
      *      NOTES:
      *              1)      pshared rwlocks MUST be allocated in shared
      *                      memory.
      *
      *              2)      The following macro is defined if shared rwlocks
      *                      are supported:
      *                              _POSIX_THREAD_PROCESS_SHARED
      *
      * RESULTS
      *              0               successfully set attribute,
      *              EINVAL          'attr' or pshared is invalid,
      *              ENOSYS          PTHREAD_PROCESS_SHARED not supported,
      *
      * ------------------------------------------------------
      */
{
  int result;

  if ((attr != NULL && *attr != NULL) &&
      ((pshared == PTHREAD_PROCESS_SHARED) ||
       (pshared == PTHREAD_PROCESS_PRIVATE)))
    {
      if (pshared == PTHREAD_PROCESS_SHARED)
        {

#if !defined( _POSIX_THREAD_PROCESS_SHARED )

          result = ENOSYS;
          pshared = PTHREAD_PROCESS_PRIVATE;

#else

          result = 0;

#endif /* _POSIX_THREAD_PROCESS_SHARED */

        }
      else
        {
          result = 0;
        }

      (*attr)->pshared = pshared;
    }
  else
    {
      result = EINVAL;
    }

  return (result);

}                               /* pthread_rwlockattr_setpshared */


int
pthread_rwlock_rdlock(pthread_rwlock_t *rwlock)
{
    int result;
    pthread_rwlock_t rwl;

    if (rwlock == NULL || *rwlock == NULL)
      {
        return EINVAL;
      }

    /*
     * We do a quick check to see if we need to do more work
     * to initialise a static rwlock. We check
     * again inside the guarded section of ptw32_rwlock_check_need_init()
     * to avoid race conditions.
     */
    if (*rwlock == PTHREAD_RWLOCK_INITIALIZER)
      {
        result = ptw32_rwlock_check_need_init(rwlock);

        if (result != 0 && result != EBUSY)
          {
            return result;
          }
      }

    rwl = *rwlock;

    if (rwl->nMagic != PTW32_RWLOCK_MAGIC)
      {
        return EINVAL;
      }

    if ((result = pthread_mutex_lock(&(rwl->mtxExclusiveAccess))) != 0)
      {
        return result;
      }

    if (++rwl->nSharedAccessCount == INT_MAX)
      {
        if ((result = pthread_mutex_lock(&(rwl->mtxSharedAccessCompleted))) != 0)
          {
            (void) pthread_mutex_unlock(&(rwl->mtxExclusiveAccess));
            return result;
          }

        rwl->nSharedAccessCount -= rwl->nCompletedSharedAccessCount;
        rwl->nCompletedSharedAccessCount = 0;

        if ((result = pthread_mutex_unlock(&(rwl->mtxSharedAccessCompleted))) != 0)
          {
            (void) pthread_mutex_unlock(&(rwl->mtxExclusiveAccess));
            return result;
          }
      }

    return (pthread_mutex_unlock(&(rwl->mtxExclusiveAccess)));
}

static void
ptw32_rwlock_cancelwrwait(void * arg)
{
    pthread_rwlock_t rwl = (pthread_rwlock_t) arg;

    rwl->nSharedAccessCount = -rwl->nCompletedSharedAccessCount;
    rwl->nCompletedSharedAccessCount = 0;

    (void) pthread_mutex_unlock(&(rwl->mtxSharedAccessCompleted));
    (void) pthread_mutex_unlock(&(rwl->mtxExclusiveAccess));
}

int
pthread_rwlock_wrlock(pthread_rwlock_t * rwlock)
{
    int result;
    pthread_rwlock_t rwl;

    if (rwlock == NULL || *rwlock == NULL)
      {
        return EINVAL;
      }

    /*
     * We do a quick check to see if we need to do more work
     * to initialise a static rwlock. We check
     * again inside the guarded section of ptw32_rwlock_check_need_init()
     * to avoid race conditions.
     */
    if (*rwlock == PTHREAD_RWLOCK_INITIALIZER)
      {
        result = ptw32_rwlock_check_need_init(rwlock);

        if (result != 0 && result != EBUSY)
          {
            return result;
          }
      }

    rwl = *rwlock;

    if (rwl->nMagic != PTW32_RWLOCK_MAGIC)
      {
        return EINVAL;
      }

    if ((result = pthread_mutex_lock(&(rwl->mtxExclusiveAccess))) != 0)
      {
        return result;
      }

    if ((result = pthread_mutex_lock(&(rwl->mtxSharedAccessCompleted))) != 0)
      {
        (void) pthread_mutex_unlock(&(rwl->mtxExclusiveAccess));
        return result;
      }

    if (rwl->nExclusiveAccessCount == 0) 
      {
        if (rwl->nCompletedSharedAccessCount > 0) 
          {
            rwl->nSharedAccessCount -= rwl->nCompletedSharedAccessCount;
            rwl->nCompletedSharedAccessCount = 0;
          }

        if (rwl->nSharedAccessCount > 0) 
          {
            rwl->nCompletedSharedAccessCount = -rwl->nSharedAccessCount;

            /*
             * This routine may be a cancelation point
             * according to POSIX 1003.1j section 18.1.2.
             */
            pthread_cleanup_push(ptw32_rwlock_cancelwrwait, (void*)rwl);

            do 
              {
                result = pthread_cond_wait(&(rwl->cndSharedAccessCompleted), 
                                           &(rwl->mtxSharedAccessCompleted));
              }
            while (result == 0 && rwl->nCompletedSharedAccessCount < 0);

            pthread_cleanup_pop ((result != 0) ? 1 : 0);

            if (result == 0)
              {
                rwl->nSharedAccessCount = 0;
              }
          }
      }

    if (result == 0)
      {
        rwl->nExclusiveAccessCount++;
      }

    return result;
}

int
pthread_rwlock_unlock(pthread_rwlock_t * rwlock)
{
    int result, result1;
    pthread_rwlock_t rwl;

    if (rwlock == NULL || *rwlock == NULL)
      {
        return(EINVAL);
      }

    if (*rwlock == PTHREAD_RWLOCK_INITIALIZER)
      {
        /*
         * Assume any race condition here is harmless.
         */
        return 0;
      }

    rwl = *rwlock;

    if (rwl->nMagic != PTW32_RWLOCK_MAGIC)
      {
        return EINVAL;
      }

    if (rwl->nExclusiveAccessCount == 0) 
      {
        if ((result = pthread_mutex_lock(&(rwl->mtxSharedAccessCompleted))) != 0)
          {
            return result;
          }

        if (++rwl->nCompletedSharedAccessCount == 0)
          {
            result = pthread_cond_signal(&(rwl->cndSharedAccessCompleted));
          }

        result1 = pthread_mutex_unlock(&(rwl->mtxSharedAccessCompleted));
      }
    else 
      {
        rwl->nExclusiveAccessCount--;

        result = pthread_mutex_unlock(&(rwl->mtxSharedAccessCompleted));
        result1 = pthread_mutex_unlock(&(rwl->mtxExclusiveAccess));

      }
 
    return ((result != 0) ? result : result1);
}

int
pthread_rwlock_tryrdlock(pthread_rwlock_t * rwlock)
{
    int result;
    pthread_rwlock_t rwl;

    if (rwlock == NULL || *rwlock == NULL)
      {
        return EINVAL;
      }

    /*
     * We do a quick check to see if we need to do more work
     * to initialise a static rwlock. We check
     * again inside the guarded section of ptw32_rwlock_check_need_init()
     * to avoid race conditions.
     */
    if (*rwlock == PTHREAD_RWLOCK_INITIALIZER)
      {
        result = ptw32_rwlock_check_need_init(rwlock);

        if (result != 0 && result != EBUSY)
          {
            return result;
          }
      }

    rwl = *rwlock;

    if (rwl->nMagic != PTW32_RWLOCK_MAGIC)
      {
        return EINVAL;
      }

    if ((result = pthread_mutex_trylock(&(rwl->mtxExclusiveAccess))) != 0)
      {
        return result;
      }

    if (++rwl->nSharedAccessCount == INT_MAX) 
      {
        if ((result = pthread_mutex_lock(&(rwl->mtxSharedAccessCompleted))) != 0)
          {
            (void) pthread_mutex_unlock(&(rwl->mtxExclusiveAccess));
            return result;
          }

        rwl->nSharedAccessCount -= rwl->nCompletedSharedAccessCount;
        rwl->nCompletedSharedAccessCount = 0;

        if ((result = pthread_mutex_unlock(&(rwl->mtxSharedAccessCompleted))) != 0)
          {
            (void) pthread_mutex_unlock(&(rwl->mtxExclusiveAccess));
            return result;
          }
      }

    return (pthread_mutex_unlock(&rwl->mtxExclusiveAccess));
}

int
pthread_rwlock_trywrlock(pthread_rwlock_t * rwlock)
{
    int result, result1;
    pthread_rwlock_t rwl;

    if (rwlock == NULL || *rwlock == NULL)
      {
        return EINVAL;
      }

    /*
     * We do a quick check to see if we need to do more work
     * to initialise a static rwlock. We check
     * again inside the guarded section of ptw32_rwlock_check_need_init()
     * to avoid race conditions.
     */
    if (*rwlock == PTHREAD_RWLOCK_INITIALIZER)
      {
        result = ptw32_rwlock_check_need_init(rwlock);

        if (result != 0 && result != EBUSY)
          {
            return result;
          }
      }

    rwl = *rwlock;

    if (rwl->nMagic != PTW32_RWLOCK_MAGIC)
      {
        return EINVAL;
      }

    if ((result = pthread_mutex_trylock(&(rwl->mtxExclusiveAccess))) != 0)
      {
        return result;
      }

    if ((result = pthread_mutex_trylock(&(rwl->mtxSharedAccessCompleted))) != 0)
      {
        result1 = pthread_mutex_unlock(&(rwl->mtxExclusiveAccess));
        return ((result1 != 0) ? result1 : result);
      }

    if (rwl->nExclusiveAccessCount == 0) 
      {
        if (rwl->nCompletedSharedAccessCount > 0) 
          {
            rwl->nSharedAccessCount -= rwl->nCompletedSharedAccessCount;
            rwl->nCompletedSharedAccessCount = 0;
          }

        if (rwl->nSharedAccessCount > 0) 
          {
            if ((result = pthread_mutex_unlock(&(rwl->mtxSharedAccessCompleted))) != 0)
              {
                (void) pthread_mutex_unlock(&(rwl->mtxExclusiveAccess));
                return result;
              }

            if ((result = pthread_mutex_unlock(&(rwl->mtxExclusiveAccess))) == 0)
              {
                result = EBUSY;
              }
          }
        else
          {
            rwl->nExclusiveAccessCount = 1;
          }
      }
    else 
      {
        result = EBUSY;
      }

    return result;
}