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

#include <stdlib.h>
#include <emx/io.h>
#include <sys/builtin.h>        /* For <sys/fmutex.h> */
#include <sys/fmutex.h>         /* For <sys/rmutex.h> */
#include <sys/rmutex.h>
#include <emx/startup.h>

/* See app/iodata.c. */

extern int _nfiles;
extern int _files[];
extern int _lookahead[];

#if defined (__MT__)

/* This semaphore protects a critical region in _fd_init(). */

static _rmutex _fdinit_rmutex;


/* Initialize the semaphore -- this function will be called by
   _startup() via the __crtinit1__ set vector. */

void _init1_fdinit (void)
{
  _rmutex_checked_create (&_fdinit_rmutex, 0);
}

_CRT_INIT1 (_init1_fdinit)

#endif


/* Return pointer to flags word for the new file descriptor HANDLE and
   initialize lookahead.  Return NULL on error. */

int *_fd_init (int handle)
{
  struct fdvec *p, *last;
  int i, n;

  if (handle < 0)
    return NULL;

  p = &_fdvec_head; last = NULL;
  while (p != NULL && handle >= p->n)
    {
      handle -= p->n;
      last = p;
      p = p->next;
    }
  if (p == NULL)
    {
      n = handle + 1;
      if (n < 32) n = 32;

      p = malloc (sizeof (*p));
      if (p == NULL)
        return NULL;
      p->flags = malloc (n * sizeof (int));
      if (p->flags == NULL)
        {
          free (p);
          return NULL;
        }
      p->lookahead = malloc (n * sizeof (int));
      if (p->lookahead == NULL)
        {
          free (p->flags);
          free (p);
          return NULL;
        }
      for (i = 0; i < n; ++i)
        {
          p->flags[i] = 0;
          p->lookahead[i] = -1;
        }
      p->n = n;
      p->next = NULL;

#if defined (__MT__)
      /* Prevent other threads from updating last->next. */

      _rmutex_checked_request (&_fdinit_rmutex, _FMR_IGNINT);

      /* Now that we have locked other threads out of this critical
         region, update last->next unless another thread has updated
         last->next before this thread entered the critical region. */

      if (last->next == NULL)
        last->next = p;

      /* End of critical region. From now on, last->next is non-NULL
         and therefore won't be changed by other threads. */

      _rmutex_checked_release (&_fdinit_rmutex);

      /* If another thread updated last->next, back out and retry,
         using the elements added by the other threads.  As we keep p
         pointing to malloc()ed memory around the critical region,
         other threads may not happen to use the same value of p. */

      if (last->next != p)
        {
          /* Another thread did the work. */

          free (p->flags);
          free (p->lookahead);
          free (p);

          /* The other thread might have requested fewer new elements
             than this thread, therefore we do not assign last->next
             to p!  We start over, instead. */

          return _fd_init (handle);
        }
#else
      last->next = p;
#endif
    }

  p->lookahead[handle] = -1;
  return &p->flags[handle];
}
