/* fx.c 2.9.0 92/07/06 - the simple fork-and-exec function used in mff.c
 *
 *  The only f&x needed is calling up METAFONT and GFtoPK,
 *  and these programs take only one argument.  It is convenient
 *  to allow the templates given to include spaces, e.g.,
 *  `-M "jobserver -q cmmf"' actually runs `jobserver'.
 *
 *  I have ignored the "struct wait" nonsense -- Edition 7 all the way! :-)
 *
 *  ATARI ST (and presumablu MS-DOS?) modification -- pdc 25 Jul 91
 *  There is no fork-&-exec in TOS; Use spawnvp instead.
 *
-----------------------------------------------------------------------
    This software module copyright (c) 1990, 1991 Damian Cugley.  
    It is provided for free on an "as-is" basis.
    See the file COPYING for more information.
-----------------------------------------------------------------------
 *  Created - pdc Mon 15 Jan 1990
 */

#include "config.h"

#ifndef NO_EXEC_USE_SPAWN
#  include <signal.h>		/* NSIG */
#else
#  include <process.h>		/* P_WAIT */
#endif /* no_exec_use_spawn */

#include <errno.h>

#include "stdc.h"
#include "xstdio.h"		/* <stdio.h> plus prototypes */
#include "fatal.h"		/* how to error messages */
#include "strmisc.h"		/* incl. <string(s).h> */
#include "fx.h"

NONRETURNING void exit ARGS((int));

#define TRUE	1
#define FALSE	0


#ifndef lint
addr realloc ARGS((addr, sizeof_t));
#else
#  define realloc(P,S) 0
#endif

#define MORE	16
/*
 *  generate more argv slots in batches of 16
 *  (I will be surprised if 16 are needed!)
 */

/*
 *  Construct an argv vector for the child process.
 *
 * |command| is pointer to zero-terminated string of characters;
 *  it is broken up as per strword(3) into separate arguments.
 * |arg| is a final argument to append to the |argv| vector;
 *  it is *not* broken into words.
 */
static inline char **
construct_args(command, argc, argv)
     int argc;
     char *command, **argv;
{
  int		num_args = 0, max_args = MORE + argc;
  char	      **nargv = (char **)xmalloc(max_args * sizeof(char *)),
	       *p, *strword_state;

  if (!(p = strword(command, &strword_state)))
    syntaxf("command string is empty!");
				/* There should always be at least one word */
  do 
    {
      if (max_args - num_args == 1 + argc) /* always two spare arg slots */
	{
	  /*  try to make some more room  */
	  nargv = (char **)
	    xrealloc(nargv, (max_args += MORE) * sizeof (char *));
	}
      nargv[num_args++] = p;
      p = strword((char *)NULL, &strword_state);
    } while (p);

  /*  There are always two free slots in the |nargv| array:  */
  while (argc-- > 0)
    nargv[num_args++] = *argv++;
  
  nargv[num_args] = (char *)NULL;
  return nargv;
}


/*
 *  fx -- run a subprocess and wait for it to finish
 */

#ifdef NO_EXEC_USE_SPAWN

void
fx(char *command, int argc, char **argv)
{
  char	      **argv = construct_args(command, argc, argv);
  int		exit_code = spawnvp(P_WAIT, argv[0], argv);
  
  xfree((addr)argv);
  if (exit_code < 0)		/* I am guessing positive exit codes are OK */
    fatalf("%s terminated with exit code %d\n", command, exit_code);
}

#else /* EXEC is available: */

#ifdef BSD
#  ifndef vfork
#    define fork vfork
/*
 *  use vfork in preference to fork -- unless vfork has been #defined
 *  to fork (which might be expected on post-VM-problems UNIXes
 */
#  endif
#endif

int	wait ARGS((int *));
int	fork ARGS((void));
int	execvp ARGS((const char *, const char **));
void NONRETURNING _exit ARGS((int));
     
#ifdef NO_SIGLIST

/*  
 *  Must supply own list of signal names:
 */
static const char *sys_siglist[] =
{
  "shookum hip RALLO dine!",	/* there is no signal 0 */
  "hangup",
  "interrupt",
  "quit",
  "illegal instruction",
  "trace trap",			/* 5 */
  "IOT Trap",
  "EMT trap", 
  "arithmetic exception",
  "kill -KILL",
  "bus error",			/* 10 */
  "violation of segments",
  "bad argument to system call",
  "unrequited pipe",
  "alarm clock",
  "software terminate -- from kill(2)",	/* 15 */
  /*  that was the Edition 7 signals (0-15)... */
#ifdef USG
  "user-defined signal number 1", /* 16 */
  "user-defined signal number 2", /* 17 */
  "lonely child process",	/* 18 */
  "death! death! death!",	/* 19 -- power failure */
  /*
   *  For other systems (4.1 BSD?), signals > 15
   *  will be reported numerically.
   */
#endif /* USG */
};

/*  define NSIG to be # of messages in above:  */
#  undef NSIG
#  define NSIG ((sizeof sys_siglist)/sizeof (char *))

#else /* have siglist */
      extern const char *sys_siglist[];
#endif

extern char *sys_errlist[];
extern int sys_nerr;


/*
 *  classic fork and exec -- |command| and |arg| as above.
 *  Waits for child to finish.
 */
void				/* aborts if can't exec */
fx(command, argc, argv)
     int argc;
     char *command, **argv;
{
  int		pid, t;
  int		status,		/* status word from wait() */
  		exit_code;	/* exit code of child */
  
  argv = construct_args(command, argc, argv);
  
  if ((pid = fork()) < 0) pfatalf("fork");
  else if (pid == 0)
    {
      /*  This is the child  */
      (void)execvp((const char *)argv[0], (const char **)argv);
      /*  If we get here, something's gone wrong:  */
      perrorf(argv[0]);
      _exit(1);			/* don't use exit() in child */
    }

  /*  This is the parent process  */
#ifdef BSD
  t = wait(&status);
#else
  while ((t = wait(&status)) < 0 && errno == EINTR)
    ;
#endif
  if (t != pid) pfatalf("very surprised because wait returned %d", t);
  xfree((addr)argv);

  if ((status & 0xFF) != 0)
    {
      /*  Terminated by signal: */
      int sig = status & 0x7F;

      fprintf(stderr, "%s killed by signal %d", command, sig);
      if (sig < NSIG)
	fprintf(stderr, " (%s)\n", sys_siglist[sig]);
      if (status & 0x80)
	fprintf(stderr, "Wow, coredump city.\n");
      exit(1);
    }
  else if (exit_code = status >> 8 & 0xFF)
    {
      fprintf(stderr, "%s terminated with exit code %d.\n", 
	      command, exit_code);
      exit(1);
    }
}


#ifdef NO_EXECVP
/*
 *  This is intended as a version of execvp for systems that don't have it.
 *  (V7, BSD 4.2?)
 *
 *  If an executable file is found for which execv() fails,
 *  it is assumed to be a shellscript and /bin/sh is invoked with
 *  its full pathname as first argument.
 *
 *  pdc Thur. 27 June 1991
 */

#include <sys/param.h>
#ifndef MAXPATHLEN
#  define MAXPATHLEN 1024
#endif
#include "strmisc.h"
#include "searchpath.h"		/* might as well use that */

int				/* returns -1 if fails */
execvp(progname, argv)
     const char *progname;		/* name of prog to search for */
     const char *argv[];		/* vector of arguments */
{
  const char *getenv ARGS((const char *));
  int execv ARGS((const char *, const char *[]));
  
  char scratch[MAXPATHLEN];
  const char *path = getenv("PATH");

  if (!path) path = "";		/* for mad people who like PATH-free shells */
  
  if (findfile(scratch, progname, path, (char *)0))
    {
      if (execv(scratch, argv),  errno == ENOEXEC)
	{
	  /*
	   *  a /bin/sh command file
	   *  Substitute pathname of file as 1st argument to sh:
	   *    execvp("fo", {"fo","arg","brg",0}) -> 
	   *      /bin/sh /usr/local/bin/fo arg brg
	   */
	  const char **nargv; /* ptr to vect of ptrs to vects of const char */
	  int argc;

	  /*
	   *  count args:
	   */
	  {
	    register const char **t = argv;
	    while (*t) ++t;
	    argc = (t - argv);
	  }
	  
	  /* allocate room for 1 more arg & the NULL */
	  /* copy the NULL and the args except the 0'th arg */
	  nargv = (const char **)xmalloc(sizeof (char *) * (argc + 2));
	  bcopy((addr)(argv + 1), (addr)(nargv + 2),
		argc*(sizeof (char *)));
	  nargv[0] = "/bin/sh"; 
	  nargv[1] = scratch;
	  execv(nargv[0], nargv);

	  /*  failed -- */
	  xfree((addr)nargv);
	}
    }
  else
    {
      /*  findfile returned FALSE  */
      errno = ENOENT;
    }

  return -1;
}
#endif  /* NO_EXECVP */
#endif  /* NO_EXEC_USE_SPAWN */
