#ifdef __DJGPP__

#include <stdio.h>
#include <string.h>
#include <io.h>
#include <fcntl.h>
#include <dos.h>
#include <libc/local.h>
#include <unistd.h>
#include <errno.h>
#include <limits.h>
#include <sys/fsext.h>
#include <sys/stat.h>
#include <stdarg.h>

#ifdef SHELL
#include "dosutil.h"
extern int debugging_login_shell;
#else
#include "opentmp.h"
#endif

extern int _dup(int);
extern int _dup2(int, int);

typedef unsigned char byte;

typedef struct
{
  char * file;
  byte ref_count_index;
} tmpfile_data;

#define HANDLE_MAX 255
#define REF_COUNT_ERROR 255

static byte ref_count[256];
static byte total_used;
static byte max_index;

static int fsext_handler (__FSEXT_Fnumber action, int *rv, va_list args);
static byte get_open_ref_count_index(void);
static void free_ref_count_index(byte);

/* Open a temporary file and set it up to be automatically deleted
   it after it is closed. */
int _opentmp (const char *path, int flags, ...)
{
  int fd;
  va_list arg_list;
  mode_t mode;
  int arg_count;

  if (flags & O_RDONLY)
    flags |= O_EXCL;

  arg_count = 0;

  if (arg_count == 0)
    fd = open(path, flags, S_IWUSR);
  else
  {
    va_start(arg_list, flags);
    mode = va_arg(arg_list, mode_t);
    va_end(arg_list);
    fd = open(path, flags, mode);
  }
  if (fd < 0)
    return fd;

  if (_remove_on_close(fd, path) < 0)
  {
    close(fd);
    return -1;
  }

  return fd;
}

/* Obtain and fill in FILE struct so the file associated with the
   descriptor is automatically removed when it is closed.
   Does the dirty work for _opentmp. */
int _remove_on_close (int fd, const char *path)
{
  char buffer[PATH_MAX];
  FILE *f;
  tmpfile_data *data;

  __FSEXT_set_function (fd, fsext_handler);

  /* Store an absolute path. */
  _fixpath (path, buffer);

  data = (tmpfile_data *)malloc (sizeof(tmpfile_data));

  if (data == NULL)
    return -1;

  data->ref_count_index = get_open_ref_count_index();
  if (data->ref_count_index == REF_COUNT_ERROR)
    return -1;
  ref_count[data->ref_count_index] = 1;
  data->file = strdup (buffer);

  __FSEXT_set_data (fd, (void *)data);

  return 0;
}

/* Closes and removes a temporary file. */
static int _closetmp (int fd, tmpfile_data *data)
{
  __file_rec *fr;
  int index;
  int ret;
  int errno_save;

  __FSEXT_set_data(fd, NULL); /* Prevent recursion. */
  __FSEXT_set_function(fd, NULL);

  if (!data)
  {
    errno = ENOENT;
    return -1;
  }

  ret = _dos_close(fd);

  errno_save = errno;
  /* It is possible the handle has been dup()'ed.
     If so, don't let the file be removed. */
  if (--(ref_count[data->ref_count_index]) == 0)
  {
    errno = 0;
    remove(data->file);
    if (errno)
      perror (data->file);
    free_ref_count_index(data->ref_count_index);
  }
  errno = errno_save;

  free(data);

  return ret;
}

static int _duptmp (int fd, int new_fd, tmpfile_data *data)
{
  tmpfile_data *new_data;

  new_data = (tmpfile_data *)malloc (sizeof(tmpfile_data));

  if (new_data == NULL)
    return -1;

  new_data->file = data->file;
  new_data->ref_count_index = data->ref_count_index;
  ref_count[new_data->ref_count_index] += 1;

  __FSEXT_set_function(new_fd, fsext_handler);
  __FSEXT_set_data(new_fd, (void *)new_data);

  return 0;
}

static
int fsext_handler (__FSEXT_Fnumber action, int *rv, va_list args)
{
  int handle = va_arg(args, int);
  tmpfile_data *data;

  if ((data = __FSEXT_get_data(handle)) == NULL)
    return 0;

  switch (action)
  {
    case __FSEXT_close:
    {
      *rv = _closetmp(handle, data);
      return 1;
    }

    case __FSEXT_fcntl:
    {
      int ret_val;
      unsigned third_arg;
      int cmd;
      __FSEXT_set_function(handle, NULL);
      cmd = va_arg(args, int);
      third_arg = va_arg(args, int);
      ret_val = fcntl(handle, cmd, third_arg);

      __FSEXT_set_function(handle, fsext_handler);

 
      if ((cmd == F_DUPFD)
          && (ret_val < 0 || _duptmp(handle, ret_val, data) < 0))
        *rv = -1;
      else
        *rv = ret_val;

      return 1;
    }

    case __FSEXT_dup:
    {
      int to_handle;

      to_handle = _dup(handle);

      if (_duptmp(handle, to_handle, data) < 0)
      {
        _dos_close(to_handle);
        *rv = -1;
      }
      else
        *rv = to_handle;
      return 1;
    }

    case __FSEXT_dup2:
    {
      int to_handle, ret;
      to_handle = va_arg(args, int);

#if 0
      close (to_handle);
#endif
      ret = _dup2(handle, to_handle);

      if (ret < 0 || _duptmp(handle, to_handle, data) < 0)
        *rv = -1;
      else
        *rv = ret;

      return 1;
    }
    default:
      break;
  }

  return 0;
}

/* Find a free reference count index.  */
static byte get_open_ref_count_index(void)
{
  int i;
  while (max_index > 0 && ref_count[max_index - 1] == 0)
    --max_index;

  if (max_index < HANDLE_MAX)
  {
    byte ref_index = max_index;
    ++max_index;
    ++total_used;
    return ref_index;
  }

  if (total_used >= HANDLE_MAX)
    return REF_COUNT_ERROR;

  for (i = 0; i < HANDLE_MAX; i++)
  {
    if (ref_count[i] == 0)
    {
      ++total_used;
      return i;
    }
  }
  
  return REF_COUNT_ERROR;
}

/* A reference count index has become free.  */
static void free_ref_count_index(byte index)
{
  if (ref_count[index] != 0)
    abort();

  --total_used;
  while (max_index > 0 && ref_count[max_index - 1] == 0)
    --max_index;
}

#endif
