/*  spawn.c - replacements for MSC spawn*() functions
    Copyright (C) 1990 by Thorsten Ohl, ohl@gnu.ai.mit.edu

    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 1, 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., 675 Mass Ave, Cambridge, MA 02139, USA.

    IMPORTANT:

    This code is not an official part of the GNU project and the
    author is not affiliated to the Free Software Foundation.
    He just likes their code and spirit.  */

static char RCS_id[] =
"$Header: e:/gnu/make/RCS/spawn.c'v 0.18 90/07/22 14:42:38 tho Exp $";


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <process.h>
#include <malloc.h>
#include <errno.h>
#include <direct.h>
#include <dos.h>
#include <io.h>
#include <sys/types.h>


#define MAX_MSDOS_CMDLINE	126
#define MAX_MSDOS_PATH		144

/* IMPORTED DECLARATIONS */

enum swapping_mode
{
  none, disk, ems, xms
};

extern int
spawn_child (enum swapping_mode mode, char *path, char *cmdline, char *env,
	     int len, char *swap_file);


/* PRIVATE DECLARATIONS */

static void swapping_mode_hook (enum swapping_mode * swapping_ptr, char *dev);
static int commandline_hook (char *cmd, char **argv, char **env0,
			     char **respond_file_name_ptr);
static char *putenv_cmdline (char *cmd, char **argv);
static int build_environment (char _far ** envbuf, char *cmdline,
			      char **envvec);
static char *build_link_respond_file (char **argv);
static char *build_lib_respond_file (char **argv);
static char *build_zip_respond_file (char **argv);
static int build_cmdline (char **argv);
static char *expand_path (char *name, char *env, char *(*select) (char *));
static char *executable_p (char *path);
extern void *xmalloc (size_t size);
extern void *xrealloc (void *ptr, size_t size);

static char *extensions[] =/* extensions recognized during */
{				/* path search */
  ".bat",			/* this will be passed to the shell */
  ".exe",
  ".com",
  NULL,
};

#define PATH_SEP	';'
#define FSLASH		'/'
#define BSLASH		'\\'


/* PUBLIC DECLARATIONS */

extern char *mktmpname (char *prefix);


/* The main entry points, work just like spawnvpe?() from the standard MSC
   library.  */

int swap_and_spawnvp (char *device, char *cmd, char **argv);
int swap_and_spawnvpe (char *device, char *cmd, char **argv, char **envvec);

int
swap_and_spawnvp (char *device, char *cmd, char **argv)
{
  return swap_and_spawnvpe (device, cmd, argv, NULL);
}

int
swap_and_spawnvpe (char *device, char *cmd, char **argv, char **envvec)
{
  int len;
  int rc;
  char _far *env = (char _far *) 0;
  char *env0 = NULL;
  char *respond_file_name = NULL;
  char *swap_file_name = mktmpname ("vm");
  enum swapping_mode swapping = xms;

  if (device != NULL)
    swapping_mode_hook (&swapping, device);

  argv[0] = expand_path (cmd, "PATH", executable_p);
  if (argv[0] == NULL)
    return ENOENT;

  rc = commandline_hook (cmd, argv, &env0, &respond_file_name);
  if (rc)
    return rc;

  len = build_environment (&env, env0, (envvec == NULL) ? environ : envvec);
  if (!len)
    return E2BIG;

  rc = spawn_child (swapping, argv[0], argv[1], env, len, swap_file_name);

  if (env0)
    free (env0);
  if (env)
    free (env);
  if (*argv[1])
    free (argv[1]);
  free (swap_file_name);

  if (respond_file_name)	/* clean up disk */
    unlink (respond_file_name);

  return rc;
}


/* Look if DEV corresponds to a known swapping device and set *SWAPPING_PTR
   accordingly;  */

void
swapping_mode_hook (enum swapping_mode * swapping_ptr, char *dev)
{
  if (dev)
    {
      strlwr (dev);
      if (!strcmp ("xms", dev))
	*swapping_ptr = xms;
      else if (!strcmp ("ems", dev))
	{
	  fprintf (stderr, "Swapping to expanded memory is not supported yet, "
		   "will try extended memory.\n");
	  *swapping_ptr = xms;
	}
      else if (!strcmp ("disk", dev))
	*swapping_ptr = disk;
      else if (!strcmp ("none", dev))
	{
	  fprintf (stderr, "No swapping is not supported yet, "
			    "will swap to disk.\n");
	  *swapping_ptr = disk;
	}
      else
	{
	  fprintf (stderr, "Can't swap to `%s' (no such device), "
		   "will try extended memory.\n", dev);
	  *swapping_ptr = xms;
	}
    }
}

/* Look if CMD allows or requires special treatment, manipulate ARGV
   accordingly. If necessary, create ENV0 and RESPOND_FILE_NAME_PTR.
   In any case, merge ARGV[1], ... into ARGV[1].
   ~~~~~~~~~~~
   Note that the special treatment can be switched off by giving CMD
   uppercase!  */

int
commandline_hook (char *cmd, char **argv, char **env0,
		  char **respond_file_name_ptr)
{
  if (!strcmp ("cl", cmd) || !strcmp ("fl", cmd) || !strcmp ("masm", cmd))
    *env0 = putenv_cmdline (cmd, argv);
  else if (!strcmp ("link", cmd))
    {
      *respond_file_name_ptr = build_link_respond_file (argv);
      if (*respond_file_name_ptr == NULL)
	return E2BIG;
    }
  else if (!strcmp ("lib", cmd))
    {
      *respond_file_name_ptr = build_lib_respond_file (argv);
      if (*respond_file_name_ptr == NULL)
	return E2BIG;
    }
  else if (!strcmp ("pkzip", cmd) || !strcmp ("pkunzip", cmd))
    {
      *respond_file_name_ptr = build_zip_respond_file (argv);
      if (*respond_file_name_ptr == NULL)
	return E2BIG;
    }
  else if (!build_cmdline (argv))
    return E2BIG;

  return 0;			/* success */
}


/* Manipulating the environment.  */

/* Merge CMDLINE, ENVVEC[0], ... into ENVBUF as a well formed MS-DOS
   environment (will be malloc()'d).  Returns the length (incl. the
   trailing '\0's)  of the resulting environment.  */

int
build_environment (char _far ** envbuf, char *cmdline, char **envvec)
{
  char *p;
  char **envptr = envvec;
  int len = 1;

  if (envvec == NULL && cmdline == NULL)
    return 0;

  if (cmdline != NULL)
    len += strlen (cmdline) + 1;
  if (envvec != NULL)
    while (*envptr)
      len += strlen (*envptr++) + 1;

  p = *envbuf = (char *) xmalloc (len);

  if (cmdline != NULL)
    {
      strcpy (p, cmdline);
      p += strlen (cmdline) + 1;
    }
  envptr = envvec;
  if (envvec != NULL)
    while (*envptr)
      {
	strcpy (p, *envptr);
	p += strlen (*envptr++) + 1;
      }
  *p++ = '\0';			/* end of environment! */

  return (len);
}


/* Manipulating the commandline.  */

/* The following functions for manipulating the commandline do NOT free
   the storage of the ARGV[] strings, since they can't know how it was
   allocated (separately or as single block).  */

/* Build an environment entry for passing options to the Microsoft C
   and Fortran Compilers and Macro Assembler.  CMD is the command to
   execute.  ARGV[1],... will be moved to an environment entry "CMD" and
   we will have ARGV[1] == "".  */

char *
putenv_cmdline (char *cmd, char **argv)
{
  int nologo_flag = 0;
  char **av = argv + 1;
  char *p;
  char *env;
  int len = 3 + strlen (cmd);

  /* `fl' and `cl' understand the "-nologo" option to suppress the banner,
      `masm' not.  */
  if (!strcmp (cmd, "fl") || !strcmp (cmd, "cl"))
    {
      nologo_flag = 1;
      len += 8;
    }

  while (*av)
    len += strlen (*av++) + 1;

  p = env = (char *) xmalloc (len);

  strcpy (p, strupr (cmd));
  p += strlen (cmd);
  *p++ = '=';

  if (nologo_flag)
    {
      strcpy (p, "-nologo");
      p += 7;
    }

  av = argv + 1;
  while (*av)			/* paste arguments together */
    {
      *p++ = ' ';
      strcpy (p, *av);
      p += strlen (*av++);
    }

  *argv[1] = '\0';		/* commandline terminated */

  return env;
}


/* Build a respondfile for the Microsoft linker from ARGV[0].  Returns the
   name of the generated file on success, NULL otherwise.  The new
   commandline ARGV[1] holds the proper command for instructing the linker
   to use this respondfile.  */

char *
build_link_respond_file (char **argv)
{
  int len = 0;
  char **av = argv;
  FILE *respond_file;
  char *respond_file_name = mktmpname ("lk");

  if (!(respond_file = fopen (respond_file_name, "w")))
    {
      fprintf (stderr, "can't open link respond_file %s.\n",
	       respond_file_name);
      return NULL;
    }

  while (*++av)
    {
      char *cp;

      if (strlen (*av) + len > 50)
	{
	  fprintf (respond_file, "+\n");
	  len = 0;
	}

      while (cp = strchr (*av, ','))	/* new respond group? */
	{
	  *cp = '\n';
	  len = *av - cp - 1;	/* precompensate */
	}

      len += fprintf (respond_file, "%s ", *av);
    }

  fprintf (respond_file, ";\n");/* avoid prompts! */

  fclose (respond_file);

  argv[1] = (char *) xmalloc (strlen (respond_file_name) + 9);
  sprintf (argv[1], "/batch @%s", respond_file_name);

  return respond_file_name;
}


/* Build a respondfile for the Microsoft library manager from ARGV[0].
   Returns the name of the generated file on success, NULL otherwise.  The
   new commandline ARGV[1] holds the proper command for instructing the
   library manager to use this respondfile.   A module without command
   inherits the one of it's predecessor.  This allows constructions like
   `lib foo.lib -+ $?' in the makefiles (idea stolen from Kneller's
   `ndmake').  */

char *
build_lib_respond_file (char **argv)
{
  int len = 0;
  char **av = argv;
  FILE *respond_file;
  char *respond_file_name = mktmpname ("lb");
  static char lib_action[3] = "+";

  if (!(respond_file = fopen (respond_file_name, "w")))
    {
      fprintf (stderr, "can't open lib respond_file %s.\n",
	       respond_file_name);
      return NULL;
    }

  fprintf (respond_file, " %s\n ", *++av);	/* easy: the lib file  */

  if (access (*av, 0))		/* new library */
    fprintf (respond_file, "y\n ");	/* create it! */

  while (*++av)
    {
      char *cp;

      if (strchr ("+-*", **av))
	{
	  lib_action[0] = *(*av)++;

	  if (strchr ("+-*", **av))
	    lib_action[1] = *(*av)++;
	  else
	    lib_action[1] = '\0';
	}

      if (**av)
	{
	  if (strlen (*av) + len > 50)
	    {
	      fprintf (respond_file, "&\n ");
	      len = 0;
	    }

	  len += fprintf (respond_file, "%s", lib_action) + 1;

	  while (cp = strchr (*av, ','))	/* new respond group? */
	    {
	      *cp = '\n';
	      len = *av - cp - 1;	/* precompensate */
	      lib_action[0] = '\0';	/* no more actions! */
	    }

	  len += fprintf (respond_file, "%s ", *av) + 1;
	}
    }

  fprintf (respond_file, ";\n");/* avoid prompts! */

  fclose (respond_file);

  argv[1] = (char *) xmalloc (strlen (respond_file_name) + 10);
  sprintf (argv[1], "/nologo @%s", respond_file_name);

  return respond_file_name;
}


/* Build a respondfile for the pk(un)?zip archive managers from ARGV[0].
   Returns the name of the generated file on success, NULL otherwise.  The
   new commandline ARGV[1] holds the proper command for instructing
   pk(un)?zip to use this respondfile.  */

char *
build_zip_respond_file (char **argv)
{
  char *cmdline = (char *) xmalloc (127 * sizeof (char));
  char *ptr = cmdline;
  char **av = argv + 1;
  int len = 0;
  FILE *respond_file = NULL;
  char *respond_file_name = mktmpname ("zi");

  respond_file = fopen (respond_file_name, "w");
  if (respond_file == NULL)
    {
      fprintf (stderr, "can't open zip respondfile %s.\n", respond_file_name);
      return NULL;
    }

  while (**av == '-')		/* leave options on the commandline */
    {
      if (ptr - cmdline + strlen (*av) > 126)
	{
	  free (cmdline);
	  return NULL;
	}
      strcpy (ptr, *av);
      ptr += strlen (*av);
      *ptr++ = ' ';
      av++;
    }

  if (*av == NULL		/* missing zipfilename? */
      || ptr - cmdline + strlen (*av) + strlen (respond_file_name) > 124)
    {
      free (cmdline);
      return NULL;
    }

  sprintf (ptr, "%s @%s", *av++, respond_file_name);

  while (*av)
    fprintf (respond_file, "%s\n", *av++);

  fprintf (respond_file, "\n");
  fclose (respond_file);

  argv[1] = cmdline;

  return respond_file_name;
}


/* Build a MS-DOS commandline from the supplied argument vector ARGV and
   put it into ARGV[1].  Storage of the old argv[1], ... is NOT free()'d,
   since we can't know how it was allocated (separately or as single
   block)! ARGV[0] is assumed to be a fully expanded pathname incl
   extension.  If ARGV[0] ends int ".bat", we will pass the command to the
   shell.  Returns 1 on success, 0 if the commandline is too long.  */

int
build_cmdline (char **argv)
{
  char *cmdline = (char *) xmalloc (128 * sizeof (char));
  char *p = cmdline;
  char *tmp;
  char **av;

  if ((tmp = strrchr (argv[0], '.'))	/* .bat files will be passed */
      && !strcmp (tmp + 1, "bat"))	/* to the shell ...  */
    {
      *tmp = '\0';		/* terminate */

      strcpy (p, "/c ");	/* return after execution */
      p += 3;

      if ((tmp = strrchr (argv[0], BSLASH))	/* strip path */
	  || (tmp = strrchr (argv[0], FSLASH)))
	strcpy (p, tmp + 1);
      else
	strcpy (p, argv[0]);

      p += strlen (p);
      *p++ = ' ';

      if (!(argv[0] = getenv ("COMSPEC")))
	{
	  fprintf (stderr, "command processor not found!");
	  exit (1);
	}
    }

  av = argv;			/* paste arguments together */
  while (*++av)
    {
      strcpy (p, *av);
      p += strlen (*av);
      *p++ = ' ';
      if ((p - cmdline) > 126)
	return (0);		/* commandline to long for MeSy-DOS */
    }

  *p = '\0';
  argv[1] = cmdline;

  return (1);			/* success */
}



/* $PATH search.  */

/* Look for NAME in the directories from $ENV ($PATH, if ENV is NULL),
   satisfying SELECT.  */

char *
expand_path (char *name, char *env, char *(*select) (char *))
{
  size_t name_len = strlen (name) + 6;	/*  SELECT may append 4 chars! */
  char *exp = (char *) xmalloc (name_len);

  strcpy (exp, name);

  if ((*select) (exp))		/* first look in current directory. */
    return exp;

  /* If not an absolute path, scan $PATH  */

  if (exp[1] != ':' && *exp != BSLASH && *exp != FSLASH)
    {
      char *ptr = getenv ((env == NULL) ? "PATH" : env);
      if (ptr != NULL)
	{
	  char *path = (char *) alloca (strlen (ptr) + 1);
	  strcpy (path, ptr);	/* get a copy strtok() can butcher. */

	  ptr = strtok (path, ";");

	  while (ptr != NULL)
	    {
	      exp = (char *) xrealloc (exp, strlen (ptr) + name_len);
	      if ((*select) (strcat (strcat (strcpy (exp, ptr), "/"), name)))
		return exp;
	      ptr = strtok (NULL, ";");
	    }
	}
    }

  free (exp);
  return NULL;			/* We've failed!  */
}


/* Return the expanded path with extension iff PATH is an executable MS-DOS
   program, NULL otherwise.  */

static char *
executable_p (char *path)
{
  char *base;
  const char **ext = extensions;

  base = strrchr (strrchr (path, BSLASH), FSLASH);
  if (base == NULL)
    base = path;

  if (strchr (base, '.'))	/* explicit extension? */
    {
      if (!access (path, 0))
	return path;
    }
  else
    {
      while (*base)		/* point to the end */
	*base++;

      while (*ext)		/* try all extensions */
	{
	  strcpy (base, *ext++);
	  if (!access (path, 0))
	    return path;
	}
    }

  return NULL;			/* failed */
}


/* Get a unique filename in the temporary directory from the environment
   entry TMP or TEMP, if this fails, use current directory.  (replacement
   for tempnam(), whish is *too* touchy about trailing path separators!) */

char *
mktmpname (char *prefix)
{
  register int len;
  register char *ptr;
  register char *tmpname;
  static char default_prefix[3] = "vm";

  if (!prefix[0] || !prefix[1])	/* did the user supply a prefix? */
    prefix = default_prefix;

  if (!(ptr = getenv ("TMP")) && !(ptr = getenv ("TEMP")))
    {

      ptr = ".";
      len = 1;
    }
  else
    {
      len = strlen (ptr) - 1;
      if ((ptr[len] == FSLASH) || (ptr[len] == BSLASH))
	ptr[len] = '\0';
    }

  tmpname = xmalloc (len + 10);

  sprintf (tmpname, "%s\\%2sXXXXXX", ptr, prefix);
  mktemp (tmpname);

  return tmpname;
}


#ifdef TEST

extern void main (int ac, char **av);

void *
xmalloc (size_t size)
{
  void *result = malloc (size);
  if (result == NULL)
    {
      fprintf (stderr, "virtual memory exhausted");
      abort ();
    }
  return result;
}

void *
xrealloc (void *ptr, size_t size)
{
  void *result = realloc (ptr, size);
  if (result == NULL)
    {
      fprintf (stderr, "virtual memory exhausted");
      abort ();
    }
  return result;
}

void
main (int ac, char **av)
{
  int n = 0;
  int j = 2;
  char **argv;

  argv = (char **) xmalloc ((ac - 1) * sizeof (char **));

  while (j < ac)
    {
      argv[n] = (char *) xmalloc (strlen (av[j]) + 1);
      strcpy (argv[n++], av[j++]);
    }
  argv[n] = NULL;

  swap_and_spawnvp (av[1], *argv, argv);
}

#endif /* TEST */


/*
 * Local Variables:
 * mode:C
 * ChangeLog:ChangeLog
 * compile-command:cl -DTEST -W3 vmspawn.c swapcore
 * End:
 */
