/* rmutex.c (emx+gcc) -- Copyright (c) 1996 by Eberhard Mattes */

#define INCL_DOSERRORS
#include <os2.h>
#include <stdlib.h>
#include <limits.h>
#include <sys/builtin.h>
#include <sys/fmutex.h>
#include <sys/rmutex.h>

/* These functions are available even in single-thread libraries. */


static _rmutex *_rmutex_head;   /* Initialized to NULL */
static _rmutex *_rmutex_tail;   /* Initialized to NULL */

static void _rmutex_add (_rmutex *sem)
{
  sem->prev = NULL;
  if (_rmutex_head == NULL)
    _rmutex_tail = sem;
  else
    _rmutex_head->prev = sem;
  sem->next = _rmutex_head;
  _rmutex_head = sem;
}


static void _rmutex_remove (_rmutex *sem)
{
  if (sem->prev == NULL)
    _rmutex_head = sem->next;
  else
    sem->prev->next = sem->next;
  if (sem->next == NULL)
    _rmutex_tail = sem->prev;
  else
    sem->next->prev = sem->prev;
}


unsigned _rmutex_create (_rmutex *sem, unsigned flags)
{
  unsigned rc;

  rc = _fmutex_create (&sem->fm, flags);
  if (rc == 0)
    {
#if !defined (NDEBUG)
      _rmutex *p;
      for (p = _rmutex_head; p != NULL; p = p->next)
        if (sem == p)
          abort ();
#endif
      sem->count = 1;
      sem->flags = (unsigned char)flags;
      if (flags & _FMC_SHARED)
        {
          /* Shared _rmutex semaphores cannot be registered -- that
             would mess up the doubly-linked list. */

          sem->next = NULL;
          sem->prev = NULL;
        }
      else
        _rmutex_add (sem);
    }
  return rc;
}


unsigned _rmutex_open (_rmutex *sem)
{
  unsigned rc;

  rc = _fmutex_open (&sem->fm);
  if (rc == 0)
    {
#if !defined (NDEBUG)
      _rmutex *p;
      for (p = _rmutex_head; p != NULL; p = p->next)
        if (sem == p)
          break;
      if (p == NULL)
        abort ();
#endif
      if (sem->count == USHRT_MAX)
        abort ();
      sem->count += 1;
    }
  return rc;
}


unsigned _rmutex_close (_rmutex *sem)
{
  unsigned rc;

  rc = _fmutex_close (&sem->fm);

  /* If it can't be closed, it probably wasn't created or opened.
     Avoid messing up the doubly-linked list.  Shared semaphores
     haven't been added to the list. */

  if (rc == 0 && !(sem->flags & _FMC_SHARED))
    {
      if (sem->count == 0)
        abort ();
      sem->count -= 1;
      if (sem->count == 0)
        _rmutex_remove (sem);
    }
  return rc;
}


void _rmutex_dummy (_rmutex *sem)
{
  sem->prev = NULL;
  sem->next = NULL;
  sem->flags = 0;
  sem->count = 0;
  _fmutex_dummy (&sem->fm);
}


void _rmutex_checked_close (_rmutex *sem)
{
  if (_rmutex_close (sem) != 0)
    abort ();
}


void _rmutex_checked_create (_rmutex *sem, unsigned flags)
{
  if (_rmutex_create (sem, flags) != 0)
    abort ();
}


void _rmutex_checked_open (_rmutex *sem)
{
  if (_rmutex_open (sem) != 0)
    abort ();
}


void _rmutex_fork (void)
{
  _rmutex *p;
  unsigned i;

  for (p = _rmutex_head; p != NULL; p = p->next)
    {
      if (p->flags & _FMC_SHARED)
        _fmutex_checked_open (&p->fm);
      else
        _fmutex_checked_create (&p->fm, p->flags);

      /* This is important for the _rmutex semaphores of heaps! */

      for (i = 1; i < p->count; ++i)
        _fmutex_checked_open (&p->fm);

      /* Even if the semaphore was owned by the parent process, it's
         available now in the child process.  This might cause
         problems due to inconsistent data structures -- resolving
         this problem (by making sure that all _rmutex semaphores are
         available when calling fork()) is left to the application. */
    }
}
