/* run.c --- routines for executing subprocesses.
   
   This file is part of GNU CVS.

   GNU CVS 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.  */

#include <process.h>
#include "cvs.h"

#ifndef HAVE_UNISTD_H
extern int execvp PROTO((char *file, char **argv));
#endif

static void run_add_arg PROTO((const char *s));

extern char *strtok ();

/*
 * To exec a program under CVS, first call run_setup() to setup initial
 * arguments.  The argument to run_setup will be parsed into whitespace 
 * separated words and added to the global run_argv list.
 * 
 * Then, optionally call run_arg() for each additional argument that you'd like
 * to pass to the executed program.
 * 
 * Finally, call run_exec() to execute the program with the specified arguments.
 * The execvp() syscall will be used, so that the PATH is searched correctly.
 * File redirections can be performed in the call to run_exec().
 */
static char **run_argv;
static int run_argc;
static int run_argc_allocated;

/* VARARGS */
void 
run_setup (prog)
    const char *prog;
{
    char *cp;
    int i;
    char *run_prog;

    /* clean out any malloc'ed values from run_argv */
    for (i = 0; i < run_argc; i++)
    {
	if (run_argv[i])
	{
	    free (run_argv[i]);
	    run_argv[i] = (char *) 0;
	}
    }
    run_argc = 0;

    run_prog = xstrdup (prog);

    /* put each word into run_argv, allocating it as we go */
    for (cp = strtok (run_prog, " \t"); cp; cp = strtok ((char *) NULL, " \t"))
	run_add_arg (cp);
    free (run_prog);
}

void
run_arg (s)
    const char *s;
{
    run_add_arg (s);
}

static void
run_add_arg (s)
    const char *s;
{
    /* allocate more argv entries if we've run out */
    if (run_argc >= run_argc_allocated)
    {
	run_argc_allocated += 50;
	run_argv = (char **) xrealloc ((char *) run_argv,
				     run_argc_allocated * sizeof (char **));
    }

    if (s)
	run_argv[run_argc++] = xstrdup (s);
    else
	run_argv[run_argc] = (char *) 0;	/* not post-incremented on purpose! */
}

int
run_exec (stin, stout, sterr, flags)
    const char *stin;
    const char *stout;
    const char *sterr;
    int flags;
{
    int saved_shin = -1, saved_shout = -1, saved_sherr = -1;
    int shin, shout, sherr;
    int mode_out, mode_err;
    int status;
    int rc = -1;
    int rerrno = 0;
    int pid, w;

#ifdef POSIX_SIGNALS
    sigset_t sigset_mask, sigset_omask;
    struct sigaction act, iact, qact;

#else
#ifdef BSD_SIGNALS
    int mask;
    struct sigvec vec, ivec, qvec;

#else
    RETSIGTYPE (*istat) (), (*qstat) ();
#endif
#endif

    if (trace)
    {
#ifdef SERVER_SUPPORT
	cvs_outerr (server_active ? "S" : " ", 1);
#endif
	cvs_outerr ("-> system(", 0);
	run_print (stderr);
	cvs_outerr (")\n", 0);
    }
    if (noexec && (flags & RUN_REALLY) == 0)
	return (0);

    /* make sure that we are null terminated, since we didn't calloc */
    run_add_arg ((char *) 0);

    /* setup default file descriptor numbers */
    shin = STDIN_FILENO;
    shout = STDOUT_FILENO;
    sherr = STDERR_FILENO;

    /* set the file modes for stdout and stderr */
    mode_out = mode_err = O_WRONLY | O_CREAT;
    mode_out |= ((flags & RUN_STDOUT_APPEND) ? O_APPEND : O_TRUNC);
    mode_err |= ((flags & RUN_STDERR_APPEND) ? O_APPEND : O_TRUNC);

    if (stin && (shin = open (stin, O_RDONLY)) == -1)
    {
	rerrno = errno;
	error (0, rerrno, "cannot open %s for reading (prog %s)",
	       stin, run_argv[0]);
	goto out;
    }
    if (stout && (shout = open (stout, mode_out, 0666)) == -1)
    {
	rerrno = errno;
	if (shin != STDIN_FILENO)
	    (void) close (shin);
	error (0, rerrno, "cannot open %s for writing (prog %s)",
	       stout, run_argv[0]);
	goto out;
    }
    if (sterr && (flags & RUN_COMBINED) == 0)
    {
	if ((sherr = open (sterr, mode_err, 0666)) == -1)
	{
	    rerrno = errno;
	    if (shin != STDIN_FILENO)
		(void) close (shin);
	    if (shout != STDOUT_FILENO)
		(void) close (shout);
	    error (0, rerrno, "cannot open %s for writing (prog %s)",
		   sterr, run_argv[0]);
	    goto out;
	}
    }

    /* Make sure we don't flush this twice, once in the subprocess.  */
    fflush (stdout);
    fflush (stderr);

    /* The output files, if any, are now created. */

    if (shin != STDIN_FILENO)
    {
	saved_shin = dup (STDIN_FILENO);
	(void) dup2 (shin, STDIN_FILENO);
	(void) close (shin);
    }
    if (shout != STDOUT_FILENO)
    {
	saved_shout = dup (STDOUT_FILENO);
	(void) dup2 (shout, STDOUT_FILENO);
	(void) close (shout);
    }
    if (flags & RUN_COMBINED)
    {
	saved_sherr = dup (STDERR_FILENO);
	(void) dup2 (STDOUT_FILENO, STDERR_FILENO);
    }
    else if (sherr != STDERR_FILENO)
    {
	saved_sherr = dup (STDERR_FILENO);
	(void) dup2 (sherr, STDERR_FILENO);
	(void) close (sherr);
    }

    /* Ignore some signals for now */
#ifdef POSIX_SIGNALS
    if (flags & RUN_SIGIGNORE)
    {
	act.sa_handler = SIG_IGN;
	(void) sigemptyset (&act.sa_mask);
	act.sa_flags = 0;
	(void) sigaction (SIGINT, &act, &iact);
	(void) sigaction (SIGQUIT, &act, &qact);
    }
    else
    {
	(void) sigemptyset (&sigset_mask);
	(void) sigaddset (&sigset_mask, SIGINT);
	(void) sigaddset (&sigset_mask, SIGQUIT);
	(void) sigprocmask (SIG_SETMASK, &sigset_mask, &sigset_omask);
    }
#else
#ifdef BSD_SIGNALS
    if (flags & RUN_SIGIGNORE)
    {
	memset ((char *) &vec, 0, sizeof (vec));
	vec.sv_handler = SIG_IGN;
	(void) sigvec (SIGINT, &vec, &ivec);
	(void) sigvec (SIGQUIT, &vec, &qvec);
    }
    else
	mask = sigblock (sigmask (SIGINT) | sigmask (SIGQUIT));
#else
    istat = signal (SIGINT, SIG_IGN);
    qstat = signal (SIGQUIT, SIG_IGN);
#endif
#endif

    /* dup'ing and signals are done.  try to run it now */
#ifdef SYSTEM_SPAWNVP
    pid = SYSTEM_SPAWNVP (P_NOWAIT, run_argv[0], run_argv);
#else
    pid = spawnvp (P_NOWAIT, run_argv[0], run_argv);
#endif
    if (pid < 0)
	rerrno = errno;

    if (saved_shin >= 0)
    {
	(void) dup2 (saved_shin, STDIN_FILENO);
	(void) close (saved_shin);
    }
    if (saved_shout >= 0)
    {
	(void) dup2 (saved_shout, STDOUT_FILENO);
	(void) close (saved_shout);
    }
    if (saved_sherr >= 0)
    {
	(void) dup2 (saved_sherr, STDERR_FILENO);
	(void) close (saved_sherr);
    }

    if (pid < 0)
	goto out0;

    /* wait for our process to die and munge return status */
#ifdef POSIX_SIGNALS
    while ((w = waitpid (pid, &status, 0)) == -1 && errno == EINTR)
	;
#else
    while ((w = wait (&status)) != pid)
    {
	if (w == -1 && errno != EINTR)
	    break;
    }
#endif

    if (w == -1)
    {
	rc = -1;
	rerrno = errno;
    }
#ifndef VMS /* status is return status */
    else if (WIFEXITED (status))
	rc = WEXITSTATUS (status);
    else if (WIFSIGNALED (status))
    {
	if (WTERMSIG (status) == SIGPIPE)
	    error (1, 0, "broken pipe");
	rc = 2;
    }
    else
	rc = 1;
#else /* VMS */
    rc = WEXITSTATUS (status);
#endif /* VMS */

  out0:
    /* restore the signals */
#ifdef POSIX_SIGNALS
    if (flags & RUN_SIGIGNORE)
    {
	(void) sigaction (SIGINT, &iact, (struct sigaction *) NULL);
	(void) sigaction (SIGQUIT, &qact, (struct sigaction *) NULL);
    }
    else
	(void) sigprocmask (SIG_SETMASK, &sigset_omask, (sigset_t *) NULL);
#else
#ifdef BSD_SIGNALS
    if (flags & RUN_SIGIGNORE)
    {
	(void) sigvec (SIGINT, &ivec, (struct sigvec *) NULL);
	(void) sigvec (SIGQUIT, &qvec, (struct sigvec *) NULL);
    }
    else
	(void) sigsetmask (mask);
#else
    (void) signal (SIGINT, istat);
    (void) signal (SIGQUIT, qstat);
#endif
#endif

  out:
    if (rerrno)
	errno = rerrno;
    return (rc);
}

void
run_print (fp)
    FILE *fp;
{
    int i;
    void (*outfn) PROTO ((const char *, size_t));

    if (fp == stderr)
	outfn = cvs_outerr;
    else if (fp == stdout)
	outfn = cvs_output;
    else
    {
	error (1, 0, "internal error: bad argument to run_print");
	/* Solely to placate gcc -Wall.
	   FIXME: it'd be better to use a function named `fatal' that
	   is known never to return.  Then kludges wouldn't be necessary.  */
	outfn = NULL;
    }

    for (i = 0; i < run_argc; i++)
    {
	(*outfn) ("'", 1);
	(*outfn) (run_argv[i], 0);
	(*outfn) ("'", 1);
	if (i != run_argc - 1)
	    (*outfn) (" ", 1);
    }
}

/* Return value is NULL for error, or if noexec was set.  If there was an
   error, return NULL and I'm not sure whether errno was set (the Red Hat
   Linux 4.1 popen manpage was kind of vague but discouraging; and the noexec
   case complicates this even aside from popen behavior).  */

FILE *
run_popen (cmd, mode)
    const char *cmd;
    const char *mode;
{
    if (trace)
#ifdef SERVER_SUPPORT
	(void) fprintf (stderr, "%c-> run_popen(%s,%s)\n",
			(server_active) ? 'S' : ' ', cmd, mode);
#else
	(void) fprintf (stderr, "-> run_popen(%s,%s)\n", cmd, mode);
#endif
    if (noexec)
	return (NULL);

#ifdef SYSTEM_POPEN
    return (SYSTEM_POPEN (cmd, mode));
#else
    return (popen (cmd, mode));
#endif
}

int
piped_child (command, tofdp, fromfdp)
     char **command;
     int *tofdp;
     int *fromfdp;
{
    int pid;
    int to_child_pipe[2];
    int from_child_pipe[2];
    int saved_shin, saved_shout;
    int rerrno = 0;

    if (pipe (to_child_pipe) < 0)
	error (1, errno, "cannot create pipe");
    if (pipe (from_child_pipe) < 0)
	error (1, errno, "cannot create pipe");

#ifdef USE_SETMODE_BINARY
    setmode (to_child_pipe[0], O_BINARY);
    setmode (to_child_pipe[1], O_BINARY);
    setmode (from_child_pipe[0], O_BINARY);
    setmode (from_child_pipe[1], O_BINARY);
#endif

    if ((saved_shin = dup (STDIN_FILENO)) < 0)
	error (1, errno, "cannot dup");
    if (dup2 (to_child_pipe[0], STDIN_FILENO) < 0)
	error (1, errno, "cannot dup2");
    close_on_exec (to_child_pipe[1]);
    if ((saved_shout = dup (STDOUT_FILENO)) < 0)
	error (1, errno, "cannot dup");
    if (dup2 (from_child_pipe[1], STDOUT_FILENO) < 0)
	error (1, errno, "cannot dup2");
    close_on_exec (from_child_pipe[0]);

#ifdef SYSTEM_SPAWNVP
    pid = SYSTEM_SPAWNVP (P_NOWAIT, command[0], command);
#else
    pid = spawnvp (P_NOWAIT, command[0], command);
#endif
    if (pid < 0)
	rerrno = errno;

    if (close (to_child_pipe[0]) < 0)
	error (1, errno, "cannot close");
    if (close (from_child_pipe[1]) < 0)
	error (1, errno, "cannot close");
    if (dup2 (saved_shin, STDIN_FILENO) < 0)
	error (1, errno, "cannot dup2");
    (void) close (saved_shin);
    if (dup2 (saved_shout, STDOUT_FILENO) < 0)
	error (1, errno, "cannot dup2");
    (void) close (saved_shout);

    if (pid < 0)
	error (1, rerrno, "cannot exec");

    *tofdp = to_child_pipe[1];
    *fromfdp = from_child_pipe[0];
    return pid;
}


void
close_on_exec (fd)
     int fd;
{
#if defined (FD_CLOEXEC) && defined (F_SETFD)
  if (fcntl (fd, F_SETFD, 1))
    error (1, errno, "can't set close-on-exec flag on %d", fd);
#endif
}

/*
 * dir = 0 : main proc writes to new proc, which writes to oldfd
 * dir = 1 : main proc reads from new proc, which reads from oldfd
 *
 * Returns: a file descriptor.  On failure (i.e., the exec fails),
 * then filter_stream_through_program() complains and dies.
 */

int
filter_stream_through_program (oldfd, dir, prog, pidp)
     int oldfd, dir;
     char **prog;
     pid_t *pidp;
{
    int p[2], newfd;
    pid_t newpid;
    int saved_shin, saved_shout;

    if (pipe (p))
	error (1, errno, "cannot create pipe");
#ifdef USE_SETMODE_BINARY
    setmode (p[0], O_BINARY);
    setmode (p[1], O_BINARY);
#endif

    saved_shin = dup (STDIN_FILENO);
    saved_shout = dup (STDOUT_FILENO);

    if (dir)
    {
	/* write to new pipe */
	dup2 (oldfd, STDIN_FILENO);
	dup2 (p[1], STDOUT_FILENO);
	close_on_exec (p[0]);
    }
    else
    {
	/* read from new pipe */
	dup2 (p[0], STDIN_FILENO);
	dup2 (oldfd, STDOUT_FILENO);
	close_on_exec (p[1]);
    }
    /* Should I be blocking some signals here?  */
#ifdef SYSTEM_SPAWNVP
    newpid = SYSTEM_SPAWNVP (P_NOWAIT, prog[0], prog);
#else
    newpid = spawnvp (P_NOWAIT, prog[0], prog);
#endif

    dup2 (saved_shin, STDIN_FILENO);
    close (saved_shin);
    dup2 (saved_shout, STDOUT_FILENO);
    close (saved_shout);

    if (pidp)
	*pidp = newpid;
    if (newpid < 0)
	error (1, errno, "couldn't exec %s", prog[0]);

    close (oldfd);
    if (dir)
    {
	/* read from new pipe */
	close (p[1]);
	newfd = p[0];
    }
    else
    {
	/* write to new pipe */
	close (p[0]);
	newfd = p[1];
    }

    return newfd;
}
