/* run.c --- routines for executing subprocesses under DJGPP.
   
   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 "cvs.h"

#include <stdlib.h>
#include <process.h>
#include <errno.h>
#include <io.h>
#include <fcntl.h>

#ifndef DJ_WANT_QUOTED_ARGS
#define DJ_WANT_QUOTED_ARGS 0
#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 spawnvp() 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;

void
run_setup (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);
}

#if DJ_WANT_QUOTED_ARGS
/* Return a malloc'd copy of s, with double quotes around it.  */
static char *
quote (const char *s)
{
    size_t s_len = 0;
    char *copy = NULL;
    char *scan = (char *) s;

    /* scan string for extra quotes ... */
    while (*scan)
      if ('"' == *scan++)
        s_len += 2;   /* one extra for the quote character */
      else
        s_len++;
    /* allocate length + byte for ending zero + for double quotes around */
    scan = copy = xmalloc(s_len + 3);
    *scan++ = '"';
    while (*s)
    {
      if ('"' == *s)
        *scan++ = '\\';
      *scan++ = *s++;
    }
    /* ending quote and closing zero */
    *scan++ = '"';
    *scan++ = '\0';
    return copy;
}
#endif

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)
#if DJ_WANT_QUOTED_ARGS
    {
      run_argv[run_argc] = (run_argc ? quote (s) : xstrdup (s));
      ++run_argc;
    }
#else
      run_argv[run_argc++] = xstrdup (s);
#endif
    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 shin, shout, sherr;
    int sain, saout, saerr;	/* saved handles */
    int mode_out, mode_err;
    int status = -1;
    int rerrno = 0;
    int rval   = -1;
    RETSIGTYPE (*istat) (), (*qstat) ();

    if (trace)			/* if in trace mode */
    {
#ifdef SERVER_SUPPORT
      cvs_outerr (server_active ? "S" : " ", 1);
#endif
      cvs_outerr ("-> system(", 0);
      run_print (stderr);
      cvs_outerr (")\n", 0);
    }

    // Not sure this is required; WinNT does it, so I'm inclined to do so.
    fflush (stderr);
    fflush (stdout);

    if (noexec && (flags & RUN_REALLY) == 0) /* if in noexec mode */
      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 = 0;
    shout = 1;
    sherr = 2;

    /* 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);

    /* open the files as required, shXX are shadows of stdin... */
    if (stin && (shin = open (stin, O_RDONLY)) == -1)
    {
      rerrno = errno;
      error (0, errno, "cannot open %s for reading (prog %s)",
             stin, run_argv[0]);
      goto out0;
    }
    if (stout && (shout = open (stout, mode_out, 0666)) == -1)
    {
      rerrno = errno;
      error (0, errno, "cannot open %s for writing (prog %s)",
             stout, run_argv[0]);
      goto out1;
    }
    if (sterr && (flags & RUN_COMBINED) == 0)
    {
      if ((sherr = open (sterr, mode_err, 0666)) == -1)
        {
          rerrno = errno;
          error (0, errno, "cannot open %s for writing (prog %s)",
                 sterr, run_argv[0]);
          goto out2;
        }
    }

    // Not sure this is required; WinNT does it, so I'm inclined to do so.
    fflush (stderr);
    fflush (stdout);

    /* now save the standard handles */
    sain = saout = saerr = -1;
    sain  = dup( 0); /* dup stdin  */
    saout = dup( 1); /* dup stdout */
    saerr = dup( 2); /* dup stderr */
    /* the new handles will be dup'd to the standard handles for the spawn. */

    if (shin != 0)
      {
        (void) dup2 (shin, 0);
        (void) close (shin);
      }
    if (shout != 1)
      {
        (void) dup2 (shout, 1);
        (void) close (shout);
      }
    if (flags & RUN_COMBINED)
      (void) dup2 (1, 2);
    else if (sherr != 2)
      {
        (void) dup2 (sherr, 2);
        (void) close (sherr);
      }

    /* Ignore some signals for now */
    istat = signal (SIGINT, SIG_IGN);
    qstat = signal (SIGQUIT, SIG_IGN);

    /* dup'ing is done.  try to run it now */
    rval = spawnvp ( P_WAIT, run_argv[0], run_argv);

    /* Restore signal handling.  */
    (void) signal (SIGINT, istat);
    (void) signal (SIGQUIT, qstat);

    /* restore the original file handles   */
    if (sain  != -1) {
      (void) dup2( sain, 0);	/* re-connect stdin  */
      (void) close( sain);
    }
    if (saout != -1) {
      (void) dup2( saout, 1);	/* re-connect stdout */
      (void) close( saout);
    }
    if (saerr != -1) {
      (void) dup2( saerr, 2);	/* re-connect stderr */
      (void) close( saerr);
    }

    // Not sure this is required; WinNT does it, so I'm inclined to do so.
    fflush (stderr);
    fflush (stdout);

    /* Recognize the return code for an interrupted subprocess.  */
#if 0 // This is a WinNT thing (but does DJGPP have a similar exit code?)
    if (rval == CONTROL_C_EXIT)
        return 2;
    else
#endif
      return rval;		/* end, if all went correct */

    /* error cases */
    /* cleanup the open file descriptors */
  out:
    if (sterr)
      (void) close (sherr);
  out2:
    if (stout)
      (void) close (shout);
  out1:
    if (stin)
      (void) close (shin);

  out0:
    if (rerrno)
      errno = rerrno;
    return (status);
}

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);
    }
}

#if DJ_WANT_QUOTED_ARGS
static char *
requote (const char *cmd)
{
    char *requoted = xmalloc (strlen (cmd) + 1);
    char *p = requoted;

    strcpy (requoted, cmd);
    while ((p = strchr (p, '\'')) != NULL)
    {
        *p++ = '"';
    }

    return requoted;
}
#endif

/* 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);

#if DJ_WANT_QUOTED_ARGS
    { char *requoted = requote (cmd);
      FILE *result = popen (requoted, mode);
        free (requoted);
        return result;
    }
#else
    return popen (cmd, mode);
#endif
}


/* Running children with pipes connected to them.  */

/* It's kind of ridiculous the hoops we're jumping through to get
   this working.  _pipe and dup2 and _spawnmumble work just fine, except
   that the child inherits a file descriptor for the writing end of the
   pipe, and thus will never receive end-of-file on it.  If you know of
   a better way to implement the piped_child function, please let me know. 
   
   You can apparently specify _O_NOINHERIT when you open a file, but there's
   apparently no fcntl function, so you can't change that bit on an existing
   file descriptor.  */

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
}

// TODO: Maybe create a workaround to enable these under DJGPP?

int
piped_child (command, tofdp, fromfdp)
     char **command;
     int *tofdp;
     int *fromfdp;
{
  // Why bother pretending?
  error(1, ENOEXEC, "no pipe() under DJGPP");
}


/*
 * 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;
{
  // Why bother pretending?
  error(1, ENOEXEC, "no pipe() under DJGPP");
}
