/* Job execution and handling for GNU Make.
Copyright (C) 1988, 1989, 1990 Free Software Foundation, Inc.
This file is part of GNU Make.

GNU Make 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 1, or (at your option)
any later version.

GNU Make 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 GNU Make; see the file COPYING.  If not, write to
the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */

/*
 * MS-DOS port (c) 1990 by Thorsten Ohl <ohl@gnu.ai.mit.edu>
 *
 * To this port, the same copying conditions apply as to the
 * original release.
 *
 * IMPORTANT:
 * This file is not identical to the original GNU release!
 * You should have received this code as patch to the official
 * GNU release.
 *
 * MORE IMPORTANT:
 * This port comes with ABSOLUTELY NO WARRANTY.
 *
 * $Header: e:/gnu/make/RCS/job.c'v 3.58.0.7 90/08/27 01:24:50 tho Exp $
 */

#include "make.h"
#include "commands.h"
#include "job.h"
#include "file.h"
#include "variable.h"
#include <errno.h>

#ifdef MSDOS
#define PATHSEP	';'
#else
#define PATHSEP	':'
#endif

#ifndef MSDOS
extern int errno;
#endif /* not MSDOS */

#ifdef MSDOS

#include <time.h>
#include <process.h>
#include <io.h>

#include <swaplib.h>

int last_child_pid = 0;			/* no child started yet ... */
int last_child_status = 0;

#endif /* MSDOS */

#if	defined(USG) && !defined(HAVE_VFORK)
#define	vfork	fork
#define	VFORK_NAME	"fork"
#else	/* Have vfork or not USG.  */
#define	VFORK_NAME	"vfork"
#endif	/* USG and don't have vfork.  */
extern int vfork ();

#if	defined(HAVE_SYS_WAIT) || !defined(USG)
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <signal.h>
extern int wait3 ();
#endif

#if	defined(WTERMSIG) || (defined(USG) && !defined(HAVE_SYS_WAIT))
#define	WAIT_T int

#ifndef	WTERMSIG
#define WTERMSIG(x) ((x) & 0x7f)
#endif
#ifndef	WCOREDUMP
#define WCOREDUMP(x) ((x) & 0x80)
#endif
#ifndef	WEXITSTATUS
#define WEXITSTATUS(x) (((x) >> 8) & 0xff)
#endif
#ifndef	WIFSIGNALED
#define WIFSIGNALED(x) (WTERMSIG (x) != 0)
#endif
#ifndef	WIFEXITED
#define WIFEXITED(x) (WTERMSIG (x) == 0)
#endif

#else	/* WTERMSIG not defined and have <sys/wait.h> or not USG.  */

#define WAIT_T union wait
#define WTERMSIG(x) ((x).w_termsig)
#define WCOREDUMP(x) ((x).w_coredump)
#define WEXITSTATUS(x) ((x).w_retcode)
#ifndef	WIFSIGNALED
#define	WIFSIGNALED(x)	(WTERMSIG(x) != 0)
#endif

#endif	/* WTERMSIG defined or USG and don't have <sys/wait.h>.  */

#ifdef MSDOS
static char *search_path (char *file, char *path);
static  void child_error (char *target_name, int exit_code, int exit_sig, int coredump, int ignored);
#endif /* MSDOS */

extern int dup2 ();
extern int fork (), wait (), execve ();
extern void _exit ();
extern int geteuid (), getegid ();

#if	!defined(USG) && !defined(sigmask)
#define sigmask(sig) (1 << ((sig) - 1))
#endif

#ifdef MSDOS
#define getdtablesize() _NFILE
#else /* not MSDOS */
#ifndef USG
extern int getdtablesize ();
#else
#include <sys/param.h>
#define getdtablesize() NOFILE
#endif
#endif /* not MSDOS */

extern void wait_to_start_job ();
extern int start_remote_job_p ();
extern int start_remote_job (), remote_status ();


#if	defined(USG) && !defined(HAVE_SIGLIST)
static char *sys_siglist[NSIG];
void init_siglist ();
#else	/* Not USG and or HAVE_SIGLIST.  */
extern char *sys_siglist[];
#endif	/* USG and not HAVE_SIGLIST.  */

#ifdef MSDOS
extern  int child_handler (int sig);
static void start_job (struct child *child);
static void free_child (struct child *child);
#else /* not MSDOS */
int child_handler ();
static void free_child (), start_job ();
#endif /* not MSDOS */

/* Chain of all children.  */

struct child *children = 0;

/* Number of children currently running.  */

unsigned int job_slots_used = 0;

/* Nonzero if the `good' standard input is in use.  */

static int good_stdin_used = 0;

/* Write an error message describing the exit status given in
   EXIT_CODE, EXIT_SIG, and COREDUMP, for the target TARGET_NAME.
   Append "(ignored)" if IGNORED is nonzero.  */

static void
child_error (target_name, exit_code, exit_sig, coredump, ignored)
     char *target_name;
     int exit_code, exit_sig, coredump;
     int ignored;
{
  char *ignore_string = ignored ? " (ignored)" : "";

  if (exit_sig == 0)
    error ("*** [%s] Error %d%s", target_name, exit_code, ignore_string);
  else
    {
      char *coredump_string = coredump ? " (core dumped)" : "";
      if (exit_sig > 0 && exit_sig < NSIG)
	error ("*** [%s] %s%s",
	       target_name, sys_siglist[exit_sig], coredump_string);
      else
	error ("*** [%s] Signal %d%s", target_name, exit_sig, coredump_string);
    }
}

extern void block_remote_children (), unblock_remote_children ();

/* Block the child termination signal.  */

void
block_children ()
{
#ifndef MSDOS
#ifdef USG
  /* Ignoring SIGCLD makes wait always return -1.
     Using the default action does the right thing.  */
  (void) signal (SIGCLD, SIG_DFL);
#else
  (void) sigblock (sigmask (SIGCHLD));
#endif
#endif /* ! MSDOS */

  block_remote_children ();
}

/* Unblock the child termination signal.  */
void
unblock_children ()
{
#ifndef MSDOS
#ifdef	USG
  (void) signal (SIGCLD, child_handler);
#else
  (void) sigsetmask (sigblock (0) & ~sigmask (SIGCHLD));
#endif
#endif /* ! MSDOS */

  unblock_remote_children ();
}

extern int shell_function_pid, shell_function_completed;

/* Handle a child-termination signal (SIGCHLD, or SIGCLD for USG),
   storing the returned status and the new command state (`cs_finished')
   in the `file' member of the `struct child' for the dead child,
   and removing the child from the chain.

   If we were called as a signal handler, SIG should be SIGCHLD
   (SIGCLD for USG).  If instead it is zero, we were called explicitly
   and should block waiting for running children.
   If SIG is < 0, - SIG is the maximum number of children to bury (record
   status of and remove from the chain).  */

int
child_handler (sig)
     int sig;
{
  WAIT_T status;
  unsigned int dead_children = 0;

  if (sig > 0)
    block_remote_children ();

  while (1)
    {
      int remote = 0;
      register int pid;
      int exit_code, exit_sig, coredump;
      register struct child *lastc, *c;
      int child_failed;

      /* First, check for remote children.  */
      pid = remote_status (&exit_code, &exit_sig, &coredump, 0);
      if (pid < 0)
	{
	  /* No remote children.  Check for local children.  */

#if	!defined(USG) || defined(HAVE_SYS_WAIT)
	  if (sig > 0)
	    pid = wait3 (&status, WNOHANG, (struct rusage *) 0);
	  else
	    pid = wait (&status);
#else	/* USG and don't HAVE_SYS_WAIT.  */
	  /* System V cannot do non-blocking waits, so we have two
	     choices if called as a signal handler: handle only one
	     child (there may be more if the signal was blocked),
	     or block waiting for more.  The latter option makes
	     parallelism useless, so we must choose the former.  */
	  pid = wait (&status);
#endif	/* HAVE_SYS_WAIT or not USG.  */

	  if (pid <= 0)
	    /* No local children.  */
	    break;
	  else
	    {
	      /* Chop the status word up.  */
	      exit_code = WEXITSTATUS (status);
	      exit_sig = WIFSIGNALED (status) ? WTERMSIG (status) : 0;
	      coredump = WCOREDUMP (status);
	    }
	}
      else
	/* We got a remote child.  */
	remote = 1;

      /* Check if this is the child of the `shell' function.  */
      if (!remote && pid == shell_function_pid)
	{
	  /* It is.  Leave an indicator for the `shell' function.  */
	  if (exit_sig == 0 && exit_code == 127)
	    shell_function_completed = -1;
	  else
	    shell_function_completed = 1;

	  /* Check if we have reached our quota of children.  */
	  ++dead_children;
	  if (sig < 0 && dead_children == -sig)
	    break;
#if	defined(USG) && !defined(HAVE_SYS_WAIT)
	  else if (sig > 0)
	    break;
#endif
	  else
	    continue;
	}

      child_failed = exit_sig != 0 || exit_code != 0;

      /* Search for a child matching the deceased one.  */
      lastc = 0;
      for (c = children; c != 0; lastc = c, c = c->next)
	if (c->remote == remote && c->pid == pid)
	  break;

      if (c == 0)
	{
	  /* An unknown child died.  */
	  char buf[100];
	  sprintf (buf, "Unknown%s job %d", remote ? " remote" : "", pid);
	  if (child_failed)
	    child_error (buf, exit_code, exit_sig, coredump,
			 ignore_errors_flag);
	  else
	    error ("%s finished.", buf);
	}
      else
	{
	  /* If this child had the good stdin, say it is now free.  */
	  if (c->good_stdin)
	    good_stdin_used = 0;

	  if (child_failed && !c->noerror && !ignore_errors_flag)
	    {
	      /* The commands failed.  Write an error message,
		 delete non-precious targets, and abort.  */
	      child_error (c->file->name, exit_code, exit_sig, coredump, 0);
	      c->file->update_status = 1;
	      if (exit_sig != 0)
		delete_child_targets (c);
	    }
	  else
	    {
	      if (child_failed)
		{
		  /* The commands failed, but we don't care.  */
		  child_error (c->file->name,
			       exit_code, exit_sig, coredump, 1);
		  child_failed = 0;
		}

	      /* If there are more commands to run, try to start them.  */
	      start_job (c);
	      switch (c->file->command_state)
		{
		case cs_running:
		  /* Successfully started.  Loop to reap more children.  */
		  continue;

		case cs_finished:
		  if (c->file->update_status != 0)
		    {
		      /* We failed to start the commands.  */
		      delete_child_targets (c);
		    }
		  break;

		default:
		  error ("internal error: `%s' command_state \
%d in child_handler", c->file->name);
		  abort ();
		  break;
		}
	    }

	  /* Set the state flag to say the commands have finished.  */
	  c->file->command_state = cs_finished;
	  notice_finished_file (c->file);

	  /* Remove the child from the chain and free it.  */
	  if (lastc == 0)
	    children = c->next;
	  else
	    lastc->next = c->next;
	  free_child (c);

	  /* There is now another slot open.  */
	  --job_slots_used;

	  /* If the job failed, and the -k flag was not given, die.  */
	  if (child_failed && !keep_going_flag)
	    die (1);

	  /* See if we have reached our quota for blocking.  */
	  ++dead_children;
	  if (sig < 0 && dead_children == -sig)
	    break;
#if	defined(USG) && !defined(HAVE_SYS_WAIT)
	  else if (sig > 0)
	    break;
#endif
	}
    }

#ifdef	USG
  if (sig > 0)
    (void) signal (sig, child_handler);
#endif

  if (sig > 0)
    unblock_remote_children ();

  return 0;
}


/* Wait for N children, blocking if necessary.
   If N is zero, wait until we run out of children.
   If ERR is nonzero and we have any children to wait for,
   print a message on stderr.  */

void
wait_for_children (n, err)
     unsigned int n;
     int err;
{
  block_children ();

#ifndef MSDOS
  if (err && (children != 0 || shell_function_pid != 0))
    {
      fflush (stdout);
      error ("*** Waiting for unfinished jobs....");
    }
#endif /* not MSDOS */

  /* Call child_handler to do the work.  */
  (void) child_handler (- (int) n);

  unblock_children ();
}

/* Free the storage allocated for CHILD.  */

static void
free_child (child)
     register struct child *child;
{
  if (child->commands != 0)
    free (child->commands);
  free ((char *) child);
}

/* Start a job to run the commands specified in CHILD.
   CHILD is updated to reflect the commands and ID of the child process.  */

static void
start_job (child)
     register struct child *child;
{
  static int bad_stdin = -1;
  char *end;
  register char *p;
  int backslash;
  char noprint = 0, recursive;
  char **argv;

  if (child->command_ptr == 0 || *child->command_ptr == '\0')
    /* There are no more lines in the expansion of this line.  */
    if (child->file->cmds->command_lines[child->command_line] == 0)
      {
	/* There are no more lines to be expanded.  */
	child->command_ptr = 0;
	child->file->command_state = cs_finished;
	child->file->update_status = 0;
	return;
      }
    else
      {
	/* Expand and run the next line.  */
	child->command_ptr = child->commands
#ifdef MSDOS
	  = allocated_var_exp_for_file
#else
	  = allocated_variable_expand_for_file
#endif /* MSDOS */
	    (child->file->cmds->command_lines[child->command_line++],
	     child->file);
      }

  /* Set RECURSIVE if the unexpanded line contains $(MAKE).  */
  recursive = child->file->cmds->lines_recurse[child->command_line - 1];

  /* Find the end of this line.  Backslash-newlines don't mean the end.  */

  end = child->command_ptr;
  while (*end != '\0')
    {
      p = index (end, '\n');
      if (p == 0)
	{
	  end += strlen (end);
	  break;
	}

      end = p;
      backslash = 0;
      while (*--p == '\\')
	backslash = !backslash;

      if (backslash)
	{
	  ++end;
	  /* If there is a tab after a backslash-newline,
	     remove it, since it was most likely used to line
	     up the continued line with the previous one.  */
	  if (*end == '\t')
	    strcpy (end, end + 1);
	}
      else
	break;
    }

  p = child->command_ptr;

  if (*end == '\0')
    child->command_ptr = 0;
  else
    {
      *end = '\0';
      child->command_ptr = end + 1;
    }

  child->noerror = 0;
  while (*p != '\0')
    {
      if (*p == '@')
	noprint = 1;
      else if (*p == '-')
	child->noerror = 1;
      else if (*p == '+')
	recursive = 1;
      else if (*p != ' ' && *p != '\t')
	break;
      ++p;
    }

  /* If -q was given, just say that updating `failed'.  */
  if (question_flag && !recursive)
    goto error;

  /* There may be some preceding whitespace left if there
     was nothing but a backslash on the first line.  */
  p = next_token (p);

  if (*p == '\0')
    /* There were no commands on this line.  Go to the next.  */
    {
      start_job (child);
      return;
    }

  /* Print out the command.  */

  if (just_print_flag || (!noprint && !silent_flag))
    puts (p);

  /* If -n was given, recurse to get the next line in the sequence.  */

  if (just_print_flag && !recursive)
    {
      start_job (child);
      return;
    }

  /* Collapse backslash-newlines in this line.  */

  collapse_continuations (p);

  /* Figure out an argument list from this command line.  */

  argv = construct_command_argv (p, child->file);

  /* Flush the output streams so they won't have things written twice.  */

  fflush (stdout);
  fflush (stderr);
  
  /* Set up a bad standard input that reads from a broken pipe.  */

#ifndef MSDOS
  if (bad_stdin == -1)
    {
      /* Make a file descriptor that is the read end of a broken pipe.
	 This will be used for some children's standard inputs.  */
      int pd[2];
      if (pipe (pd) == 0)
	{
	  /* Close the write side.  */
	  (void) close (pd[1]);
	  /* Save the read side.  */
	  bad_stdin = pd[0];
	}
    }
#endif /* ! MSDOS */

  /* Decide whether to give this child the `good' standard input
     (one that points to the terminal or whatever), or the `bad' one
     that points to the read side of a broken pipe.  */

  child->good_stdin = !good_stdin_used;
  if (child->good_stdin)
    good_stdin_used = 1;

  child->deleted = 0;

  /* Set up the environment for the child.  */
  if (child->environment == 0)
    child->environment = target_environment (child->file);

#ifdef MSDOS

  child->remote = 0;
  child->pid = abs ((int) clock());		/* reasonably random */

#if (0)
       {
       	   int ac = 0;
       	   char **av = argv;
       	   printf ( "start_job(): spawning process");
       	   while (*av)
       		printf( " $%d=`%s'", ac++, *av++);
       	   printf( "\n");
       }
#endif /* NEVER */

  /* LAST_CHILD_STATUS and LAST_CHILD_PID will be used by our
     (faked) wait for MS-DOS to bury this 'child' */

  last_child_status = swap_spawnvpe (argv[0], argv, child->environment);

  last_child_pid = child->pid;

  if (last_child_status == -1)
    pfatal_with_name (argv[0]);

  child->file->command_state = cs_running;	/* in *some* sense ...  */

#else /* not MSDOS */

  if (start_remote_job_p ())
    {
      int is_remote, id, used_stdin;
      if (start_remote_job (argv, child->good_stdin ? 0 : bad_stdin,
			    &is_remote, &id, &used_stdin))
	goto error;
      else
	{
	  if (child->good_stdin && !used_stdin)
	    {
	      child->good_stdin = 0;
	      good_stdin_used = 0;
	    }
	  child->remote = is_remote;
	  child->pid = id;
	}
    }
  else
    {
      if (child->command_line - 1 == 0)
	/* Wait for the load to be low enough if this
	   is the first command in the sequence.  */
	wait_to_start_job ();

      /* Fork the child process.  */

      child->remote = 0;
      child->pid = vfork ();
      if (child->pid == 0)
	/* We are the child side.  */
	child_execute_job (child->good_stdin ? 0 : bad_stdin, 1,
			   child->file, argv, child->environment);
      else if (child->pid < 0)
	{
	  /* Fork failed!  */
	  perror_with_name (VFORK_NAME, "");
	  goto error;
	}
    }

  /* We are the parent side.  Set the state to
     say the commands are running and return.  */

  child->file->command_state = cs_running;

#endif /* not MSDOS */

  return;

 error:;
  child->file->update_status = 1;
  child->file->command_state = cs_finished;
}


/* Create a `struct child' for FILE and start its commands running.  */

void
new_job (file)
     register struct file *file;
{
  extern unsigned int files_remade;
  register struct commands *cmds = file->cmds;
  register struct child *c;

  /* Chop the commands up into lines if they aren't already.  */
  chop_commands (cmds);

  if (job_slots > 0)
    /* Wait for a job slot to be freed up.  */
    while (job_slots_used == job_slots)
      wait_for_children (1, 0);

  /* Start the command sequence, record it in a new
     `struct child', and add that to the chain.  */

  block_children ();

  c = (struct child *) xmalloc (sizeof (struct child));
  c->file = file;
  c->command_line = 0;
  c->command_ptr = c->commands = 0;
  c->environment = 0;
  start_job (c);
  switch (file->command_state)
    {
    case cs_running:
      c->next = children;
      children = c;
      /* One more job slot is in use.  */
      ++job_slots_used;
      break;

    case cs_finished:
      free_child (c);
      notice_finished_file (file);
      break;

    default:
      error ("internal error: `%s' command_state == %d in new_job",
	     file->name, (int) file->command_state);
      abort ();
      break;
    }

  unblock_children ();

  ++files_remade;

  if (job_slots == 1 && file->command_state == cs_running)
    {
      /* Since there is only one job slot, make things run linearly.
	 Wait for the child to finish, setting the state to `cs_finished'.  */
      while (file->command_state != cs_finished)
	wait_for_children (1, 0);
    }
}

#ifndef MSDOS
/* Replace the current process with one executing the command in ARGV.
   STDIN_FD and STDOUT_FD are used as the process's stdin and stdout;
   FILE, if not nil, is used as the variable-set to expand `PATH' and `SHELL';
   ENVP is the environment of the new program.  This function won't return.  */

void
child_execute_job (stdin_fd, stdout_fd, file, argv, envp)
     int stdin_fd, stdout_fd;
     struct file *file;
     char **argv, **envp;
{
  if (stdin_fd != 0)
    (void) dup2 (stdin_fd, 0);
  if (stdout_fd != 1)
    (void) dup2 (stdout_fd, 1);

  /* Free up file descriptors.  */
  {
    register int d;
    int max = getdtablesize ();
    for (d = 3; d < max; ++d)
      (void) close (d);
  }

  /* Don't block children for our child.  */
  unblock_children ();

  /* Run the command.  */
  exec_command (argv, envp,
		allocated_variable_expand_for_file ("$(PATH)", file),
		allocated_variable_expand_for_file ("$(SHELL)", file));
}
#endif /* not MSDOS */

/* Search PATH for FILE, returning a full pathname or nil.
   The returned pathname is allocated via `malloc'.  */

static char *
search_path (file, path)
     char *file, *path;
{
  if (*path == '\0' || index (file, '/') != 0)
    return file;
  else
    {
      char *program;
      unsigned int len;

#ifndef	USG
      extern int getgroups ();
      static int groups[NGROUPS];
      static int ngroups = -1;
      if (ngroups == -1)
	ngroups = getgroups (NGROUPS, groups);
#endif	/* Not USG.  */

      len = strlen (file) + 1;
      program = (char *) xmalloc (strlen (path) + 1 + len);
      do
	{
	  struct stat st;
	  int perm;
	  char *p;

	  p = index (path, PATHSEP);
	  if (p == 0)
	    p = path + strlen (path);

	  if (p == path)
	    bcopy (file, program, len);
	  else
	    {
	      bcopy (path, program, p - path);
	      program[p - path] = '/';
	      bcopy (file, program + (p - path) + 1, len);
	    }

	  if (stat (program, &st) == 0
	      && (st.st_mode & S_IFMT) == S_IFREG)
	    {
#ifdef MSDOS			/* we always have permission */
	      return program;
#else /* not MSDOS */
	      if (st.st_uid == geteuid ())
		perm = (st.st_mode & 0100);
	      else if (st.st_gid == getegid ())
		perm = (st.st_mode & 0010);
	      else
		{
#ifndef	USG
		  register int i;
		  for (i = 0; i < ngroups; ++i)
		    if (groups[i] == st.st_gid)
		      break;
		  if (i < ngroups)
		    perm = (st.st_mode & 0010);
		  else
#endif	/* Not USG.  */
		    perm = (st.st_mode & 0001);
		}

	      if (perm != 0)
		return program;
#endif /* not MSDOS */
	    }

	  path = p + 1;
	} while (*path != '\0');
    }

  return 0;
}

#ifndef MSDOS
/* Replace the current process with one running the command in ARGV,
   with environment ENVP.  The program named in ARGV[0] is searched
   for in PATH.  SHELL is the shell program to run for shell scripts.
   This function does not return.  */

void
exec_command (argv, envp, path, shell)
     char **argv, **envp;
     char *path, *shell;
{
  char *program;

  program = search_path (argv[0], path);
  if (program == 0)
    error ("%s: Command not found", argv[0]);
  else
    {
      /* Run the program.  */
      execve (program, argv, envp);

      if (errno == ENOEXEC)
	{
	  char *shell_path = search_path (shell, path);
	  if (shell_path == 0)
	    error ("%s: Shell program not found", shell);
	  else
	    {
	      char **new_argv;
	      int argc;

	      argc = 1;
	      while (argv[argc] != 0)
		++argc;

	      new_argv = (char **) alloca ((1 + argc + 1) * sizeof (char *));
	      new_argv[0] = shell_path;
	      new_argv[1] = program;
	      while (argc > 0)
		{
		  new_argv[1 + argc] = argv[argc];
		  --argc;
		}

	      execve (shell_path, new_argv, envp);
	      perror_with_name ("execve: ", shell_path);
	    }
	}
      else
	perror_with_name ("execve: ", program);
    }

  _exit (127);
}
#endif /* not MSDOS */

/* Figure out the argument list necessary to run LINE as a command.
   Try to avoid using a shell.  This routine handles only ' quoting.
   Starting quotes may be escaped with a backslash.  If any of the
   characters in sh_chars[] is seen, or any of the builtin commands
   listed in sh_cmds[] is the first word of a line, the shell is used.

   FILE is the target whose commands these are.  It is used for
   variable expansion for $(SHELL) and $(IFS).  */

char **
construct_command_argv (line, file)
     char *line;
     struct file *file;
{
  register int i;
  register char *p;
  register char *ap;
  char *end;
  int instring;
  char **new_argv = 0;

#ifdef MSDOS

  char *sh_chars;
  char **sh_cmds;
  char *base;
  char *shell_command;
  char switch_char = '/';

  static char _sh_chars[]
    = "#;\"*?[]&|<>(){}=$`";
  static char *_sh_cmds[]
    = {	"cd", "echo", "eval", "exec", "exit", "login", "logout", "set",
	"umask", "wait", "while", "for", "case", "if", ":", ".", "break",
	"continue", "export", "read", "readonly", "shift", "times",
	"trap", "switch", NULL };

  static char _4dos_chars[]
    = "<|>^&%";
  static char *_4dos_cmds[]
    = {	"alias", "attrib", "beep", "cd", "cdd", "chdir", "copy", "del",
	"dir", "echo", "erase", "except", "for", "global", "if", "iff",
	"keystack", "md", "move", "popd", "pushd", "rd", "ren", "rename",
	"set", "timer", "unalias", "unset", NULL };

  static char _command_com_chars[]
    = "<|>%";
  static char *_command_com_cmds[]
    = { "attrib", "cd", "chdir", "copy", "del", "dir", "echo", "erase",
	"for", "if", "md", "quit", "rd", "ren", "rename", "set", "unset",
	NULL };


  /* See if it is safe to parse commands internally.  */

  /* $(SHELL) is always defined (see variable.c)  */
  p = base = shell_command
    = strlwr (allocated_var_exp_for_file ("$(SHELL)", file));

  while (*p)
    {
      if ((*p == '/' || *p == '\\' || *p == ':') && p[1])
	base = p + 1;
      p++;
    }

  if (!strncmp ("sh", base, 2))
    {
      sh_chars = _sh_chars;
      sh_cmds = _sh_cmds;
      switch_char = '/';	/* this is true for by Bourne shell */
    }
  else if (!strncmp ("4dos", base, 4))
    {
      sh_chars = _4dos_chars;
      sh_cmds = _4dos_cmds;
    }
  else if (!strncmp ("command", base, 7))
    {
      sh_chars = _command_com_chars;
      sh_cmds = _command_com_cmds;
    }
  else
    {
      free (shell_command);
      goto slow;
    }

#else /* not MSDOS */

  static char sh_chars[] = "#;\"*?[]&|<>(){}=$`";
  static char *sh_cmds[] = { "cd", "eval", "exec", "exit", "login",
			     "logout", "set", "umask", "wait", "while", "for",
			     "case", "if", ":", ".", "break", "continue",
			     "export", "read", "readonly", "shift", "times",
			     "trap", "switch", 0 };

  /* See if it is safe to parse commands internally.  */
  p = variable_expand_for_file ("$(SHELL)", file);
  if (strcmp (p, "/bin/sh"))
    goto slow;

#endif /* not MSDOS */

  p = variable_expand_for_file ("$(IFS)", file);
  for (ap = p; *ap != '\0'; ++ap)
    if (*ap != ' ' && *ap != '\t' && *ap != '\n')
      goto slow;

  i = strlen (line) + 1;

  /* More than 1 arg per character is impossible.  */
  new_argv = (char **) xmalloc (i * sizeof (char *));

  /* All the args can fit in a buffer as big as LINE is.   */
  ap = new_argv[0] = (char *) xmalloc (i);
  end = ap + i;

  /* I is how many complete arguments have been found.  */
  i = 0;
  instring = 0;
  for (p = line; *p != '\0'; ++p)
    {
      if (ap > end)
	abort ();

      if (instring)
	{
	  /* Inside a string, just copy any char except a closing quote.  */
	  if (*p == '\'')
	    instring = 0;
	  else
	    *ap++ = *p;
	}
      else if (index (sh_chars, *p) != 0)
	/* Not inside a string, but it's a special char.  */
	goto slow;
      else
	/* Not a special char.  */
	switch (*p)
	  {
	  case '\\':
	    if (p[1] != '\0' && p[1] != '\n')
	      /* Copy and skip the following char.  */
	      *ap++ = *++p;
	    break;

	  case '\'':
	    instring = 1;
	    break;

	  case '\n':
	  case ' ':
	  case '\t':
	    /* We have the end of an argument.
	       Terminate the text of the argument.  */
	    *ap++ = '\0';
	    new_argv[++i] = ap;
	    /* If this argument is the command name,
	       see if it is a built-in shell command.
	       If so, have the shell handle it.  */
	    if (i == 1)
	      {
		register int j;
		for (j = 0; sh_cmds[j] != 0; ++j)
		  if (streq (sh_cmds[j], new_argv[0]))
		    goto slow;
	      }

	    /* Ignore multiple whitespace chars.  */
	    p = next_token (p);
	    /* Next iteration should examine the first nonwhite char.  */
	    --p;
	    break;

	  default:
	    *ap++ = *p;
	    break;
	  }
    }

  /* Terminate the last argument and the argument list.  */

  *ap = '\0';
  if (new_argv[i][0] != '\0')
    ++i;
  new_argv[i] = 0;

  if (new_argv[0] == 0)
    /* Line was empty.  */
    return 0;
  else
    return new_argv;

 slow:;
  if (new_argv != 0)
    free (new_argv);
  new_argv = (char **) xmalloc (4 * sizeof (char *));
#ifdef MSDOS
  new_argv[0] = allocated_var_exp_for_file ("$(SHELL)", file);
  new_argv[1] = (switch_char == '-') ? "-c" : "/c";
#else /* not MSDOS */
  new_argv[0] = allocated_variable_expand_for_file ("$(SHELL)", file);
  new_argv[1] = "-c";
#endif /* not MSDOS */
  new_argv[2] = line;
  new_argv[3] = 0;

  return new_argv;
}

#if	defined(USG) && !defined(HAVE_SIGLIST)
/* Initialize sys_siglist.  */

void
init_siglist ()
{
  char buf[100];
  register unsigned int i;

  for (i = 0; i < NSIG; ++i)
    switch (i)
      {
      default:
	sprintf (buf, "Signal %u", i);
	sys_siglist[i] = savestring (buf, strlen (buf));
	break;
#ifndef MSDOS
      case SIGHUP:
	sys_siglist[i] = "Hangup";
	break;
#endif /* ! MSDOS */
      case SIGINT:
	sys_siglist[i] = "Interrupt";
	break;
#ifndef MSDOS
      case SIGQUIT:
	sys_siglist[i] = "Quit";
	break;
      case SIGILL:
	sys_siglist[i] = "Illegal Instruction";
	break;
      case SIGTRAP:
	sys_siglist[i] = "Trace Trap";
	break;
      case SIGIOT:
	sys_siglist[i] = "IOT Trap";
	break;
#endif /* ! MSDOS */
#ifdef	SIGEMT
      case SIGEMT:
	sys_siglist[i] = "EMT Trap";
	break;
#endif
#ifdef	SIGDANGER
      case SIGDANGER:
	sys_siglist[i] = "Danger signal";
	break;
#endif
      case SIGFPE:
	sys_siglist[i] = "Floating Point Exception";
	break;
#ifndef MSDOS
      case SIGKILL:
	sys_siglist[i] = "Killed";
	break;
      case SIGBUS:
	sys_siglist[i] = "Bus Error";
	break;
      case SIGSEGV:
	sys_siglist[i] = "Segmentation fault";
	break;
      case SIGSYS:
	sys_siglist[i] = "Bad Argument to System Call";
	break;
      case SIGPIPE:
	sys_siglist[i] = "Broken Pipe";
	break;
      case SIGALRM:
	sys_siglist[i] = "Alarm Clock";
	break;
      case SIGTERM:
	sys_siglist[i] = "Terminated";
	break;
      case SIGUSR1:
	sys_siglist[i] = "User-defined signal 1";
	break;
      case SIGUSR2:
	sys_siglist[i] = "User-defined signal 2";
	break;
#endif /* ! MSDOS */
#ifdef	SIGCLD
      case SIGCLD:
#endif
#if	defined(SIGCHLD) && !defined(SIGCLD)
      case SIGCHLD:
#endif
	sys_siglist[i] = "Child Process Exited";
	break;
#ifdef	SIGPWR
      case SIGPWR:
	sys_siglist[i] = "Power Failure";
	break;
#endif
#ifdef	SIGVTALRM
      case SIGVTALRM:
	sys_siglist[i] = "Virtual Timer Alarm";
	break;
#endif
#ifdef	SIGPROF
      case SIGPROF:
	sys_siglist[i] = "Profiling Alarm Clock";
	break;
#endif
#ifdef	SIGIO
      case SIGIO:
	sys_siglist[i] = "I/O Possible";
	break;
#endif
#ifdef	SIGWINDOW
      case SIGWINDOW:
	sys_siglist[i] = "Window System Signal";
	break;
#endif
#ifdef	SIGSTOP
      case SIGSTOP:
	sys_siglist[i] = "Stopped (signal)";
	break;
#endif
#ifdef	SIGTSTP
      case SIGTSTP:
	sys_siglist[i] = "Stopped";
	break;
#endif
#ifdef	SIGCONT
      case SIGCONT:
	sys_siglist[i] = "Continued";
	break;
#endif
#ifdef	SIGTTIN
      case SIGTTIN:
	sys_siglist[i] = "Stopped (tty input)";
	break;
#endif
#ifdef	SIGTTOU
      case SIGTTOU:
	sys_siglist[i] = "Stopped (tty output)";
	break;
#endif
#ifdef	SIGURG
      case SIGURG:
	sys_siglist[i] = "Urgent Condition on Socket";
	break;
#endif
#ifdef	SIGXCPU
      case SIGXCPU:
	sys_siglist[i] = "CPU Limit Exceeded";
	break;
#endif
#ifdef	SIGXFSZ
      case SIGXFSZ:
	sys_siglist[i] = "File Size Limit Exceeded";
	break;
#endif
      }
}
#endif	/* USG and not HAVE_SIGLIST.  */

#if defined(USG) && !defined(USGr3) && !defined(HAVE_DUP2) && !defined (MSDOS)
int
dup2 (old, new)
     int old, new;
{
  int fd;

  (void) close (new);
  fd = dup (old);
  if (fd != new)
    {
      (void) close (fd);
      errno = EMFILE;
      return -1;
    }

  return fd;
}
#endif	/* USG and not USGr3 and not HAVE_DUP2 and not MSDOS.  */

#ifdef MSDOS

/* Here we fake a wait () ... */

int
wait (WAIT_T *status)
 {
   int pid = last_child_pid;

   /* Swap the high and low byte of the STATUS word:
      error codes > 256  (e.g. those generated by spawn_child ())
      will be interpreted as signals and cause the target to be
      deleted.  In any case, we mask off the coredump bit.  */

   *status = (last_child_status << 8) + ((last_child_status >> 8) & 0x7f);

   /* This was the only child .... */
   last_child_pid = 0;

   return pid;
 }

/* Interface to swaplib.  */

#define DELIMITER ":"
static char *smart_programs (void);

int
swap_smart_p (char *name)
{
  char *list = smart_programs ();
  char *tok;

  tok = strtok (list, DELIMITER);

  while (tok && strcmp (tok, name))
    tok = strtok (NULL, DELIMITER);

  free (list);
  return (tok != NULL);
}

char *
smart_programs (void)
{
  struct variable *smart
    = lookup_variable ("LONGARGS", sizeof ("LONGARGS") - 1);
  char *string;

  if (smart && smart->value)
    {
      string = (char *) xmalloc (strlen (smart->value) + 1);
      strcpy (string, smart->value);
    }
  else
    {
      string = (char *) xmalloc (1);
      strcpy (string, "");
    }

  return string;
}


enum swap_swapping_mode
swap_set_swapping_mode (char *cmd, char **argv)
{
  /* Default to XMS.  */

  enum swap_swapping_mode mode = xms;

  struct variable *swapping
    = lookup_variable ("SWAPPING", sizeof ("SWAPPING") - 1);

  if (!swapping || !(swapping->value))
    return mode;

  if (strcmp ("xms", swapping->value) == 0)
    mode = xms;
  else if (strcmp ("ems", swapping->value) == 0)
    mode = ems;
  else if (strcmp ("disk", swapping->value) == 0)
    mode = disk;
  else if (strcmp ("none", swapping->value) == 0)
    mode = none;

  return mode;
}

#endif	/* MSDOS */

