/* Subprocesses with pipes.

   Copyright (C) 2002 Free Software Foundation, Inc.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software Foundation,
   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */

/* Written by Paul Eggert <eggert@twinsun.com>
   and Florian Krohm <florian@edamail.fishkill.ibm.com>.  */

#if HAVE_CONFIG_H
# include <config.h>
#endif

#if HAVE_SYS_TYPES_H
# include <sys/types.h>
#endif

#include <errno.h>
#ifndef errno
extern int errno;
#endif

#include <signal.h>
#if ! defined SIGCHLD && defined SIGCLD
# define SIGCHLD SIGCLD
#endif

#if HAVE_STDLIB_H
# include <stdlib.h>
#endif
/* The following test is to work around the gross typo in
   systems like Sony NEWS-OS Release 4.0C, whereby EXIT_FAILURE
   is defined to 0, not 1.  */
#if ! EXIT_FAILURE
# undef EXIT_FAILURE
# define EXIT_FAILURE 1
#endif

#if HAVE_UNISTD_H
# include <unistd.h>
#endif
#ifndef STDIN_FILENO
# define STDIN_FILENO 0
#endif
#ifndef STDOUT_FILENO
# define STDOUT_FILENO 1
#endif
#if ! HAVE_DUP2 && ! defined dup2
# if HAVE_FCNTL_H
#  include <fcntl.h>
# endif
# define dup2(f, t) (close (t), fcntl (f, F_DUPFD, t))
#endif

#if HAVE_SYS_WAIT_H
# include <sys/wait.h>
#endif
#ifndef WEXITSTATUS
# define WEXITSTATUS(stat_val) ((unsigned int) (stat_val) >> 8)
#endif
#ifndef WIFEXITED
# define WIFEXITED(stat_val) (((stat_val) & 255) == 0)
#endif

#if HAVE_VFORK_H
# include <vfork.h>
#endif
#if ! HAVE_WORKING_VFORK
# define vfork fork
#endif

#include "error.h"

#include "gettext.h"
#define _(Msgid)  gettext (Msgid)

#include "subpipe.h"


#if HAVE_WORKING_VFORK || HAVE_WORKING_FORK

/* Initialize this module.  */

void
init_subpipe (void)
{
#ifdef SIGCHLD
  /* System V fork+wait does not work if SIGCHLD is ignored.  */
  signal (SIGCHLD, SIG_DFL);
#endif
}


/* Create a subprocess that is run as a filter.  ARGV is the
   NULL-terminated argument vector for the subprocess.  Store read and
   write file descriptors for communication with the subprocess into
   FD[0] and FD[1]: input meant for the process can be written into
   FD[0], and output from the process can be read from FD[1].  Return
   the subprocess id.

   To avoid deadlock, the invoker must not let incoming data pile up
   in FD[1] while writing data to FD[0].  */

pid_t
create_subpipe (char const * const *argv, int fd[2])
{
  int pipe_fd[2];
  int from_in_fd;
  int from_out_fd;
  int to_in_fd;
  int to_out_fd;
  pid_t pid;

  if (pipe (pipe_fd) != 0)
    error (EXIT_FAILURE, errno, "pipe");
  to_in_fd = pipe_fd[0];
  to_out_fd = pipe_fd[1];

  if (pipe (pipe_fd) != 0)
    error (EXIT_FAILURE, errno, "pipe");
  from_in_fd = pipe_fd[0];
  from_out_fd = pipe_fd[1];

  pid = vfork ();
  if (pid < 0)
    error (EXIT_FAILURE, errno, "fork");

  if (! pid)
    {
      /* Child.  */
      close (to_out_fd);
      close (from_in_fd);

      if (to_in_fd != STDIN_FILENO)
	{
	  dup2 (to_in_fd, STDIN_FILENO);
	  close (to_in_fd);
	}
      if (from_out_fd != STDOUT_FILENO)
	{
	  dup2 (from_out_fd, STDOUT_FILENO);
	  close (from_out_fd);
	}

      /* The cast to (char **) rather than (char * const *) is needed
	 for portability to older hosts with a nonstandard prototype
	 for execvp.  */
      execvp (argv[0], (char **) argv);
    
      _exit (errno == ENOENT ? 127 : 126);
    }

  /* Parent.  */
  close (to_in_fd);
  close (from_out_fd);
  fd[0] = to_out_fd;
  fd[1] = from_in_fd;
  return pid;
}


/* Wait for the subprocess to exit.  */

void
reap_subpipe (pid_t pid, char const *program)
{
#if HAVE_WAITPID || defined waitpid
  int wstatus;
  if (waitpid (pid, &wstatus, 0) < 0)
    error (EXIT_FAILURE, errno, "waitpid");
  else
    {
      int status = WIFEXITED (wstatus) ? WEXITSTATUS (wstatus) : -1;
      if (status)
	error (EXIT_FAILURE, 0,
	       _(status == 126
		 ? "subsidiary program `%s' could not be invoked"
		 : status == 127
		 ? "subsidiary program `%s' not found"
		 : status < 0
		 ? "subsidiary program `%s' failed"
		 : "subsidiary program `%s' failed (exit status %d)"),
	       program, status);
    }
#endif
}
#else /* !HAVE_WORKING_VFORK and !HAVE_WORKING_FORK */
# if __DJGPP__
/* Initialize this module.  */

#include <fcntl.h>
#include <sys/stat.h>
#include <process.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "xalloc.h"

static int old_stdin;
static int old_stdout;
static char **arguments;
static char tmp_file_name[2][L_tmpnam];

#define remove_tmp_file(fd, name)                                     \
  do {                                                                \
    close ((fd));                                                     \
    if (unlink ((name)))                                              \
      error (EXIT_FAILURE, 0, _("removing of `%s' failed"), (name));  \
  } while (0)


void
init_subpipe (void)
{
  int fd;

  strcpy (tmp_file_name[0], "/dev/env/TMPDIR/bnXXXXXX");
  fd = mkstemp (tmp_file_name[0]);
  if (fd < 0)
    error (EXIT_FAILURE, 0, _("creation of tmpfile failed"));
  close (fd);
  
  strcpy (tmp_file_name[1], "/dev/env/TMPDIR/bnXXXXXX");
  fd = mkstemp (tmp_file_name[1]);
  if (fd < 0)
    error (EXIT_FAILURE, 0, _("creation of tmpfile failed"));
  close (fd);
}


/* Create a subprocess that is run as a filter.  ARGV is the
   NULL-terminated argument vector for the subprocess.  Store read and
   write file descriptors for communication with the subprocess into
   FD[0] and FD[1]: input meant for the process can be written into
   FD[0], and output from the process can be read from FD[1].  Return
   the subprocess id.

   Because DOS has neither fork nor pipe functionality to run the subprocess
   as a filter, the filter is reproduced using tmeporary files. First bison's
   stdout is redirected to a temp. file. After bison has produced all of is
   output, this file is closed and connected to m4's stdin. All m4's output
   is redirected from m4's stdout to a second temp. file and reopened as
   bison's stdin.  */

pid_t
create_subpipe (char const * const *argv, int fd[2])
{
  int argc;
  int from_in_fd;  /* pipe from bison to m4. */
  pid_t pid;


  pid = getpid ();

  /*
   *  Save original stdin and stdout
   *  for later restauration.
   */
  old_stdin = dup (STDIN_FILENO);
  if (old_stdin < 0)
    error (EXIT_FAILURE, 0, _("saving stdin failed"));

  old_stdout = dup (STDOUT_FILENO);
  if (old_stdout < 0)
    error (EXIT_FAILURE, 0, _("saving stdout failed"));

  /*
   *  Save argv for later use.
   */
  for (argc = 0; argv[argc]; argc++)
    ;
  argc++;
  arguments = xmalloc (argc * sizeof(arguments[0]));
  for (argc = 0; argv[argc]; argc++)
  {
    arguments[argc] = xmalloc ((strlen (argv[argc]) + 1) * sizeof(arguments[0][0]));
    strcpy (arguments[argc], argv[argc]);
  }
  arguments[argc] = NULL;

  /*
   *  All bison's output will be gathered in this temp. file
   *  and will be redirected to m4's stdin.
   */
  from_in_fd = open (tmp_file_name[0], O_WRONLY | O_CREAT | O_TRUNC, S_IWUSR);
  if (from_in_fd < 0)
    error (EXIT_FAILURE, 0, _("opening of tmpfile failed"));
  if (dup2 (from_in_fd, STDOUT_FILENO) < 0)
  {
    remove_tmp_file (from_in_fd, tmp_file_name[0]);
    error (EXIT_FAILURE, 0, _("redirecting bison's stdout to tmpfile failed"));
  }
  close (from_in_fd);


  fd[0] = STDOUT_FILENO;
  return pid;
}


/* A signal handler that just records that a signal has happened.  */
static int child_interrupted;

static void
signal_catcher (int signo)
{
  child_interrupted++;
}


void
run_m4 (int fd[2])
{
  char *program;
  int from_out_fd = open (tmp_file_name[0], O_RDONLY, S_IRUSR);                   /* pipe from bison to m4. */
  int to_in_fd = open (tmp_file_name[1], O_WRONLY | O_CREAT | O_TRUNC, S_IWUSR);  /* pipe from m4 to bison. */
  int status;
  void (*previous_handler)(int);


  program = strrchr (arguments[0], '/');
  if (program)
    program++;
  else
    program = arguments[0];

  /*
   *  Redirect bison's output to m4's stdin.
   */
  if (from_out_fd < 0)
    error (EXIT_FAILURE, 0, _("opening of tmpfile failed"));
  if (dup2 (from_out_fd, STDIN_FILENO) < 0)
  {
    remove_tmp_file (from_out_fd, tmp_file_name[0]);
    error (EXIT_FAILURE, 0, _("redirecting m4's stdin from tmpfile failed"));
  }
  close (from_out_fd);

  /*
   *  All m4's output will be gathered in this temp. file
   *  and will be redirected to bison's stdin.
   */
  if (to_in_fd < 0)
  {
    remove_tmp_file (STDIN_FILENO, tmp_file_name[0]);
    error (EXIT_FAILURE, 0, _("opening of tmpfile failed"));
  }
  if (dup2 (to_in_fd, STDOUT_FILENO) < 0)
  {
    remove_tmp_file (STDIN_FILENO, tmp_file_name[0]);
    remove_tmp_file (to_in_fd, tmp_file_name[1]);
    error (EXIT_FAILURE, 0, _("redirecting m4's stdout to tmpfile failed"));
  }
  close (to_in_fd);

  /*
   *  Run m4.
   */
  child_interrupted = 0;
  errno = 0;
  previous_handler = signal (SIGINT, signal_catcher);
  status = spawnvp (P_WAIT, program, arguments);
  signal (SIGINT, previous_handler);
  if (child_interrupted)
  {
    remove_tmp_file (STDIN_FILENO, tmp_file_name[0]);
    remove_tmp_file (STDOUT_FILENO, tmp_file_name[1]);
    error (EXIT_FAILURE, 0, _("subsidiary program `%s' interrupted"), program);
  }
  if (status)
  {
    remove_tmp_file (STDIN_FILENO, tmp_file_name[0]);
    remove_tmp_file (STDOUT_FILENO, tmp_file_name[1]);
    error (EXIT_FAILURE, 0, _(errno == ENOENT
                              ? "subsidiary program `%s' not found"
                              : status < 1
                              ? "subsidiary program `%s' failed"
                              : "subsidiary program `%s' failed (status=%i, errno=%i)"), program, status, errno);
  }


  /*
   *  Redirect m4's output to bison's stdin.
   */
  if (dup2 (old_stdout, STDOUT_FILENO) < 0)
    error (EXIT_FAILURE, 0, "restore of bison's stdout failed");
  close (old_stdout);
  to_in_fd = open (tmp_file_name[1], O_RDONLY, S_IRUSR);  /* pipe from m4 to bison. */
  if (to_in_fd < 0)
  {
    remove_tmp_file (STDIN_FILENO, tmp_file_name[0]);
    error (EXIT_FAILURE, 0, _("opening of tmpfile failed"));
  }
  if (dup2 (to_in_fd, STDIN_FILENO) < 0)
  {
    remove_tmp_file (STDIN_FILENO, tmp_file_name[0]);
    remove_tmp_file (to_in_fd, tmp_file_name[1]);
    error (EXIT_FAILURE, -1, "dup2");
    error (EXIT_FAILURE, 0, _("redirecting bison's stdin from tmpfile failed"));
  }
  close (to_in_fd);


  fd[1] = STDIN_FILENO;
}


/* Free resources, unlink temporary files and restore stdin and stdout.  */

void
reap_subpipe (pid_t pid, char const *program)
{
  int argc;

  for (argc = 0; arguments[argc]; argc++)
    free (arguments[argc]);
  free (arguments);

  if (unlink (tmp_file_name[0]))
    error (EXIT_FAILURE, 0, _("removing of `%s' failed"), tmp_file_name[0]);
  if (unlink (tmp_file_name[1]))
    error (EXIT_FAILURE, 0, _("removing of `%s' failed"), tmp_file_name[1]);

  if (dup2 (old_stdin, STDIN_FILENO) < 0)
    error (EXIT_FAILURE, 0, "restore of bison's stdin failed");
  close (old_stdin);
}
# elif /* !__DJGPP__ */
#  error "vfork, fork or an emulation is required."
# endif /* !__DJGPP__ */
#endif /* !HAVE_WORKING_VFORK and !HAVE_WORKING_FORK */
