/* A cheap pipe emulation for DOS.
   Uses one read file descriptor and one write file descriptor. */


#if defined (__DJGPP__)

#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <io.h>

extern int line_number;

#define CLOSE_RD_END(FD)  (pipes[i].name && fd_in_use[(FD)] == true && pipes[i].fds[BACK_UP] == (FD) && pipes[i].fds[READ_END] == (FD))
#define CLOSE_WR_END(FD)  (pipes[i].name && fd_in_use[(FD)] == true && pipes[i].fds[BACK_UP] == (FD) && pipes[i].fds[WRITE_END] == (FD))
#define IS_CLOSED(FD)     (((pipes[i].fds[READ_END] == -(FD) && pipes[i].fds[WRITE_END] < -1) || (pipes[i].fds[READ_END] < -1 && pipes[i].fds[WRITE_END] == -(FD))) \
                           && fd_in_use[pipes[i].fds[READ_END] < -1 ? -1 * pipes[i].fds[READ_END] : pipes[i].fds[READ_END]] == true \
                           && fd_in_use[pipes[i].fds[WRITE_END] < -1 ? -1 * pipes[i].fds[WRITE_END] : pipes[i].fds[WRITE_END]] == true \
                           && pipes[i].name)
#define SWITCH_MODE(FD)   (pipes[i].fds[READ_END] > -1 && pipes[i].fds[WRITE_END] == -(FD) && pipes[i].fds[BACK_UP] == (FD) \
                           && fd_in_use[(FD)] == true && fd_in_use[pipes[i].fds[READ_END]] == true)
#define PIPE_FOUND(FD)    (((pipes[i].fds[READ_END] == (FD) || pipes[i].fds[WRITE_END] == (FD)) && fd_in_use[(FD)] == true \
                           && pipes[i].name) || IS_CLOSED((FD)))

#include "dospipe_internal.h"
#include "dosdebug.h"
#include "dosmalloc.h"


static struct pipe_t pipes[OPEN_MAX];
static bool fd_in_use[256];
static int open_pipes = 0;
static int table_size = 0;
#if (DOS_PIPE_DEBUG - 1) == 0
bool keep_pipe_files = false;
bool log_pipe_files = false;
char *pipe_log_filename = NULL;
char *pipe_log_buffer = NULL;
char *pipe_log_buffer_ptr = NULL;
char *bash_script_command_line = NULL;
char *previous_script_command_line = NULL;
char *bash_script_name = NULL;
char *previous_script_name = NULL;
int bash_script_line_number = -1;
#endif /* DOS_PIPE_DEBUG */


static int
register_fd(int fd)
{
  while (fd_in_use[fd] == true || fd < 20)
  {
    int i, tfd;

    for (i = fd; i < OPEN_MAX; i++)
      if (fd_in_use[i] == false)
        break;
    if (i == OPEN_MAX)
      return -1;

    tfd = fcntl(fd, F_DUPFD, i);
    close(fd);
    if (tfd < 0)
      return -1;
    fd = tfd;
  }
  fd_in_use[fd] = true;

  return fd;
}

void
remove_pipe(int fd)
{
  int i, orig_errno = errno;

  for (i = 0; i < table_size; i++)
  {
    if (PIPE_FOUND(fd))
    {
      int tfd;
      close(fd);
      fd_in_use[fd] = false;
      if ((tfd = pipes[i].fds[READ_END]) != -1)
      {
        if (tfd < -1)
          tfd *= -1;
        else if (tfd > -1 && tfd != fd)
          close(tfd);
        pipes[i].fds[READ_END] = -1;
        fd_in_use[tfd] = false;
      }
      if ((tfd = pipes[i].fds[WRITE_END]) != -1)
      {
        if (tfd < -1)
          tfd *= -1;
        else if (tfd > -1 && tfd != fd)
          close(tfd);
        pipes[i].fds[WRITE_END] = -1;
        fd_in_use[tfd] = false;
      }
#if (DOS_PIPE_DEBUG - 1) == 0
      if (keep_pipe_files || 0 == remove(pipes[i].name))
#else  /* !DOS_PIPE_DEBUG */
      if (0 == remove(pipes[i].name))
#endif /* !DOS_PIPE_DEBUG */
      {
        pipes[i].fds[BACK_UP] = -1;
#if (DOS_PIPE_DEBUG - 1) == 0
        log_pipe_operation(i, "removing", keep_pipe_files ? "pipe file will not be removed." : "\"remove_pipe\"" " successfully removed pipe file.", pipes + i);
        log_pipe_dump_used_fd_array(fd_in_use);
#endif /* DOS_PIPE_DEBUG */
        free(pipes[i].name);
        pipes[i].name = NULL;
      }
      else
#if (DOS_PIPE_DEBUG - 1) == 0
      {
        pipes[i].fds[BACK_UP] = fd;
        log_pipe_operation(i, "removing", "\"remove_pipe\"" " failed to remove pipe file.", pipes + i);
        log_pipe_dump_used_fd_array(fd_in_use);
      }
#else  /* !DOS_PIPE_DEBUG */
        pipes[i].fds[BACK_UP] = fd;
#endif /* !DOS_PIPE_DEBUG */
      if (!pipes[i].name)
      {
        open_pipes--;
        if (++i == table_size)
          table_size--;
      }
      break;
    }
  }

  errno = orig_errno;
}

int
rpl_close(int fd)
{
  int i;

  for (i = 0; i < table_size; i++)
  {
    if (CLOSE_RD_END(fd))
    {
      /* read end closed.  */
      pipes[i].fds[READ_END] *= -1;
#if (DOS_PIPE_DEBUG - 1) == 0
      log_pipe_operation(i, "closing read end", "OK.", pipes + i);
      log_pipe_dump_used_fd_array(fd_in_use);
#endif /* DOS_PIPE_DEBUG */
      break;
    }
    else
    if (CLOSE_WR_END(fd))
    {
      /* write end closed.  */
      pipes[i].fds[WRITE_END] *= -1;
      close(fd);
      if (pipes[i].fds[READ_END] != -1)
      {
        int ifd, ifd_orig = pipes[i].fds[READ_END];
        int orig_errno = errno;

        close(ifd_orig);
        ifd = open(pipes[i].name, O_RDONLY, 0666);
        if (ifd < 0)
#if (DOS_PIPE_DEBUG - 1) == 0
        {
          log_pipe_operation(i, "closing write end", "failed to open read end.", pipes + i);
          log_pipe_dump_used_fd_array(fd_in_use);
          return -1;
        }
#else  /* !DOS_PIPE_DEBUG */
          return -1;
#endif /* !DOS_PIPE_DEBUG */

        if (ifd != ifd_orig && ifd_orig > -1)
        {
          int ifd_new = dup2(ifd, ifd_orig);
          close(ifd);
          if (ifd_new != ifd_orig)
#if (DOS_PIPE_DEBUG - 1) == 0
          {
            log_pipe_operation(i, "closing write end", "failed to dup2 read end to original read fd.", pipes + i);
            log_pipe_dump_used_fd_array(fd_in_use);
            return -1;
          }
#else  /* !DOS_PIPE_DEBUG */
            return -1;
#endif /* !DOS_PIPE_DEBUG */
        }
#if (DOS_PIPE_DEBUG - 1) == 0
        log_pipe_operation(i, "closing write end", "OK.", pipes + i);
        log_pipe_dump_used_fd_array(fd_in_use);
#endif /* DOS_PIPE_DEBUG */
        errno = orig_errno;
        return 0;
      }
      break;
    }
    else
    if (IS_CLOSED(fd))
    {
      /* both pipe ends closed; pipe file removed.  */
      int orig_errno = errno;
#if (DOS_PIPE_DEBUG - 1) == 0
      if (keep_pipe_files || 0 == remove(pipes[i].name))
#else  /* !DOS_PIPE_DEBUG */
      if (0 == remove(pipes[i].name))
#endif /* !DOS_PIPE_DEBUG */
      {
        fd_in_use[pipes[i].fds[READ_END] < -1 ? -1 * pipes[i].fds[READ_END] : pipes[i].fds[READ_END]] = false;
        fd_in_use[pipes[i].fds[WRITE_END] < -1 ? -1 * pipes[i].fds[WRITE_END] : pipes[i].fds[WRITE_END]] = false;
        pipes[i].fds[READ_END] = -1;
        pipes[i].fds[WRITE_END] = -1;
        pipes[i].fds[BACK_UP] = -1;
#if (DOS_PIPE_DEBUG - 1) == 0
        log_pipe_operation(i, "removing", keep_pipe_files ? "pipe file will not be removed." : "\"rpl_close\"" " successfully removed pipe file.", pipes + i);
        log_pipe_dump_used_fd_array(fd_in_use);
#endif /* DOS_PIPE_DEBUG */
        free(pipes[i].name);
        pipes[i].name = NULL;
      }
#if (DOS_PIPE_DEBUG - 1) == 0
      else
      {
        log_pipe_operation(i, "removing", "\"rpl_close\"" " failed to remove pipe file.", pipes + i);
        log_pipe_dump_used_fd_array(fd_in_use);
      }
#endif /* DOS_PIPE_DEBUG */
      if (!pipes[i].name)
      {
        open_pipes--;
        if (++i == table_size)
          table_size--;
      }

      errno = orig_errno;
      return 0;
    }
    else
    if (SWITCH_MODE(fd))
    {
      /* switch from write mode into read mode.  */
      int ifd, ifd_orig = pipes[i].fds[READ_END];
      int orig_errno = errno;

      close(ifd_orig);
      ifd = open(pipes[i].name, O_RDONLY, 0666);
      if (ifd < 0)
#if (DOS_PIPE_DEBUG - 1) == 0
      {
        log_pipe_operation(i, "switching from write to read mode", "failed to open read end.", pipes + i);
        log_pipe_dump_used_fd_array(fd_in_use);
        return -1;
      }
#else  /* !DOS_PIPE_DEBUG */
        return -1;
#endif /* !DOS_PIPE_DEBUG */

      if (ifd != ifd_orig && ifd_orig > -1)
      {
        int ifd_new = dup2(ifd, ifd_orig);
        close(ifd);
        if (ifd_new != ifd_orig)
#if (DOS_PIPE_DEBUG - 1) == 0
        {
          log_pipe_operation(i, "switching from write to read mode", "failed to dup2 read end to original read fd.", pipes + i);
          log_pipe_dump_used_fd_array(fd_in_use);
          return -1;
        }
#else  /* !DOS_PIPE_DEBUG */
          return -1;
#endif /* !DOS_PIPE_DEBUG */
      }
      pipes[i].fds[BACK_UP] = pipes[i].fds[READ_END];
      errno = orig_errno;

#if (DOS_PIPE_DEBUG - 1) == 0
      log_pipe_operation(i, "switching from write to read mode", "OK.", pipes + i);
      log_pipe_dump_used_fd_array(fd_in_use);
#endif /* DOS_PIPE_DEBUG */
      return 0;
    }
  }

  return close(fd);
}

int
rpl_pipe(int fds[2])
{
  int ifd, ofd, i;
  char *tmpdir;
  char *filename;

  if (open_pipes >= OPEN_MAX && table_size >= OPEN_MAX)
  {
    errno = EMFILE;
    return -1;
  }

  tmpdir = "/dev/env/TMPDIR";
  if (access(tmpdir, D_OK))
  {
    tmpdir = "/dev/env/TMP";
    if (access(tmpdir, D_OK))
    {
      tmpdir = "/dev/env/TEMP";
      if (access(tmpdir, D_OK))
      {
        tmpdir = P_tmpdir;
        if (access(tmpdir, D_OK))
          tmpdir = ".";
      }
    }
  }
  filename = malloc(strlen(tmpdir) + sizeof("/spXXXXXX"));
  if (filename == NULL)
    return -1;
  sprintf(filename, "%s/spXXXXXX", tmpdir);
  filename = mktemp(filename);
  if (filename == NULL)
    return -1;

  ofd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
  if (ofd < 0)
  {
    free(filename);
    return -1;
  }
  if ((ofd = register_fd(ofd)) < 0)
  {
    remove(filename);
    free(filename);
    return -1;
  }

  ifd = open(filename, O_RDONLY, 0666);
  if (ifd < 0)
  {
    free(filename);
    return -1;
  }
  if ((ifd = register_fd(ifd)) < 0)
  {
    remove(filename);
    free(filename);
    return -1;
  }

  for (i = 0; i < table_size; i++)
    if (pipes[i].name == NULL)
      break;
  if (i == table_size)
    table_size++;

  pipes[i].name = filename;
  pipes[i].fds[BACK_UP] = ofd;
  fds[0] = pipes[i].fds[READ_END] = ifd;
  fds[1] = pipes[i].fds[WRITE_END] = ofd;
  open_pipes++;

#if (DOS_PIPE_DEBUG - 1) == 0
  log_pipe_creation(i, pipes + i);
  log_pipe_dump_used_fd_array(fd_in_use);
#endif /* DOS_PIPE_DEBUG */

  return 0;
}

static void
remove_pipe_files(void)
{
  int i;

  for (i = 0; i < OPEN_MAX; i++)
  {
#if (DOS_PIPE_DEBUG - 1) == 0
    int rc = 0;
#endif /* DOS_PIPE_DEBUG */
    if (pipes[i].name)
    {
      int fd = pipes[i].fds[READ_END] < -1 ? -1 * pipes[i].fds[READ_END] : pipes[i].fds[READ_END];
      if (fd != -1)
        close(fd);
      fd = pipes[i].fds[WRITE_END] < -1 ? -1 * pipes[i].fds[WRITE_END] : pipes[i].fds[WRITE_END];
      if (fd != -1)
        close(fd);
#if (DOS_PIPE_DEBUG - 1) == 0
      if (!keep_pipe_files)
        rc = remove(pipes[i].name);
      log_pipe_operation(i, "removing", keep_pipe_files ? "pipe file will not be removed." : rc ? "\"remove_pipe_files\"" " failed to remove pipe file." : "\"remove_pipe_files\"" " successfully removed pipe file.", pipes + i);
#else  /* !DOS_PIPE_DEBUG */
      remove(pipes[i].name);
#endif /* !DOS_PIPE_DEBUG */
    }
  }
#if (DOS_PIPE_DEBUG - 1) == 0
  close_log_pipe_files();
#endif /* DOS_PIPE_DEBUG */
}

/* This is called before `main' to install the temporary file deleter at exit.  */
static void __attribute__((constructor))
djgpp_bash_startup (void)
{
  /* Initialize FD table.  */
  int i;
  for (i = 0; i < 20; i++)
    fd_in_use[i] = true;  /* Verboten.  */
  for (; i < OPEN_MAX; i++)
    fd_in_use[i] = false;

#if (DOS_PIPE_DEBUG - 1) == 0
  create_log_pipe_files();
#endif /* DOS_PIPE_DEBUG */

  /* Clean disk from temporary files.  */
  atexit(remove_pipe_files);
}

#endif /* __DJGPP__ */
