#include <config.h>
#include <stdio.h>
#include "cloexec.h"
#include "xalloc.h"


#define PROGRAM_NAME_TEMPLATE  "m4XXXXXX"

#define prepare_spawn(argv)                                                  \
(__gnuc_extension__                                                          \
  ({                                                                         \
     size_t _argc;                                                           \
     char **_new_argv;                                                       \
                                                                             \
     for (_argc = 0; (argv)[_argc]; _argc++)                                 \
       ;                                                                     \
                                                                             \
     _new_argv = xmalloc((1 + _argc + 1) * sizeof(_new_argv[0]));            \
     /*                                                                      \
      *  Do not use neither an absolute path to the bash program             \
      *  nor the environment variable /dev/env/DJDIR.  It can never          \
      *  be assumed that they are setup correctly.  Let the system           \
      *  search along the PATH and hope for the best.                        \
      */                                                                     \
     *_new_argv++ = "bash.exe";                                              \
                                                                             \
     for (_argc = 0; (_new_argv[_argc] = argv[_argc]); _argc++)              \
       ;                                                                     \
                                                                             \
     _new_argv;                                                              \
  })                                                                         \
)


#define PIPE_STDIN   0
#define PIPE_STDOUT  1


char tmp_file_name[2][L_tmpnam];         /*  0: pipe stdin, 1: pipe stdout.  */
static int tmp_file_desc[2] = {-1, -1};  /*  0: pipe stdin, 1: pipe stdout.  */

#define remove_tmp_file(name)                                                \
(__gnuc_extension__                                                          \
  ({                                                                         \
     if ((name))                                                             \
       unlink((name));                                                       \
     else                                                                    \
     {                                                                       \
       if (tmp_file_name[PIPE_STDIN][0])                                     \
       {                                                                     \
         if (tmp_file_desc[PIPE_STDIN] > -1)                                 \
           close(tmp_file_desc[PIPE_STDIN]);                                 \
         unlink(tmp_file_name[PIPE_STDIN]);                                  \
         tmp_file_name[PIPE_STDIN][0] = '\0';                                \
         tmp_file_desc[PIPE_STDIN] = -1;                                     \
       }                                                                     \
       if (tmp_file_name[PIPE_STDOUT][0])                                    \
       {                                                                     \
         if (tmp_file_desc[PIPE_STDOUT] > -1)                                \
         {                                                                   \
           fsync(tmp_file_desc[PIPE_STDOUT]);                                \
           close(tmp_file_desc[PIPE_STDOUT]);                                \
         }                                                                   \
         unlink(tmp_file_name[PIPE_STDOUT]);                                 \
         tmp_file_name[PIPE_STDOUT][0] = '\0';                               \
         tmp_file_desc[PIPE_STDOUT] = -1;                                    \
       }                                                                     \
     }                                                                       \
  })                                                                         \
)


/* Duplicates a file handle, making the copy uninheritable.
   Returns -1 for a file handle that is equivalent to closed.  */
static int
dup_noinherit(int fd)
{
  fd = dup_cloexec(fd);
  if (fd < 0 && errno == EMFILE)
    error(EXIT_FAILURE, errno, _("fcntl failed"));

  return fd;
}

/* Returns a file descriptor equivalent to FD, except that the resulting file
   descriptor is none of STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO.
   FD must be open and non-inheritable.  The result will be non-inheritable as
   well.
   If FD < 0, FD itself is returned.  */
static int
fd_safer_noinherit(int fd)
{
  if (STDIN_FILENO <= fd && fd <= STDERR_FILENO)
  {
    /* The recursion depth is at most 3.  */
    int nfd = fd_safer_noinherit(dup_noinherit(fd));
    int saved_errno = errno;
    close(fd);
    errno = saved_errno;
    return nfd;
  }
  return fd;
}

/* Duplicates a file handle, making the copy uninheritable and ensuring the
   result is none of STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO.
   Returns -1 for a file handle that is equivalent to closed.  */
static int
dup_safer_noinherit(int fd)
{
  return fd_safer_noinherit(dup_noinherit(fd));
}

/* Undoes the effect of TEMPFD = dup_safer_noinherit (ORIGFD);  */
static void
undup_safer_noinherit(int tempfd, int origfd)
{
  if (tempfd >= 0)
  {
    if (dup2(tempfd, origfd) < 0)
      error(EXIT_FAILURE, errno, _("cannot restore fd %d: dup2 failed"), origfd);
    close(tempfd);
  }
  else
  {
    /* origfd was closed or open to no handle at all.  Set it to a closed
       state.  This is (nearly) equivalent to the original state.  */
    close(origfd);
  }
}

#ifdef _SPAWN_PIPE_H
static int
djgpp_pipe(int pipe_fd[2], int pipe_end)
{
  int fd;

  strcpy(tmp_file_name[pipe_end], "/dev/env/TMPDIR/"PROGRAM_NAME_TEMPLATE);
  if ((fd = mkstemp(tmp_file_name[pipe_end])) < 0)
  {
    strcpy(tmp_file_name[pipe_end], "/dev/env/TMP/"PROGRAM_NAME_TEMPLATE);
    if ((fd = mkstemp(tmp_file_name[pipe_end])) < 0)
    {
      strcpy(tmp_file_name[pipe_end], "/dev/env/TEMP/"PROGRAM_NAME_TEMPLATE);
      if ((fd = mkstemp(tmp_file_name[pipe_end])) < 0)
        return -1;
    }
  }

  if (pipe_end == PIPE_STDIN)
  {
    pipe_fd[PIPE_STDIN] = tmp_file_desc[PIPE_STDIN] = fd;
    pipe_fd[PIPE_STDOUT] = -1;
    tmp_file_name[PIPE_STDOUT][0] = '\0';
  }
  else if (pipe_end == PIPE_STDOUT)
  {
    pipe_fd[PIPE_STDOUT] = tmp_file_desc[PIPE_STDOUT] = fd;
    pipe_fd[PIPE_STDIN] = -1;
    tmp_file_name[PIPE_STDIN][0] = '\0';
  }

  return 0;
}
#endif
