/* Copyright (C) 1996 DJ Delorie, see COPYING.DJ for details */
/* Copyright (C) 1995 DJ Delorie, see COPYING.DJ for details */
/* GO32V2 - A quick hack for loading non-stubbed images for V2.0
   Charles Sandmann 6/95 NO WARRANTY - build with -ldbg
   Eli Zaretskii    6/96 fix code which runs V1 EXE and COFF.
   Eli Zaretskii    7/96 fix code which runs V2 COFF.
   Eli Zaretskii    8/96 *really* fix code which runs V2 COFF;
			 support for V2.1 long command lines.
   Bugs:  doesn't scan manifest for versions installed yet */

/* If you want to change this, remember to test it with all the
   various ways `go32' can be called.  The following is the
   list of different cases I know about:

     go32 is called from DOS prompt without any arguments
     go32 is called from DOS prompt to run unstubbed COFF image of a v2 program
     go32 is called from DOS prompt to debug a v1 COFF image (go32 -d)
     v2's Make calls go32 to run unstubbed COFF image of a v2 program
     v1's Make calls go32 to run unstubbed COFF image of a v2 program
     16-bit Make calls go32 to run unstubbed COFF image of a v2 program
     v2's Make calls go32 to run unstubbed COFF image of a v1 program
     v1's Make calls go32 to run unstubbed COFF image of a v1 program
     16-bit Make calls go32 to run unstubbed COFF image of a v1 program
     v1 .EXE program is called from the DOS prompt
     v1 symlink is called from the DOS prompt
     v2's Make calls a v1 .EXE program
     v1's Make calls a v1 .EXE program
     16-bit Make calls a v1 .EXE program
     when v2's Make calls go32, you need to test command lines which
       are both shorter and longer than the DOS 126-character limit

   Note that there is nothing special about Make, it just serves as an
   example of one program that calls another.  It is convenient to use
   Make to test the above cases, because Make is available as both v2
   and v1 executable and as a 16-bit program, and because it can be
   easily used to call different programs with different command lines
   by tweaking a Makefile.  */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <process.h>
#include <go32.h>
#include <errno.h>
#include <dpmi.h>
#include <debug/v2load.h>
#include <sys/exceptn.h>
#include <sys/farptr.h>

/*======================================================================*/

#define IMG_ERROR	0
#define IMG_EXE		1
#define IMG_COFF	2
#define IMG_V2		4
#define IMG_UNKNOWN	8

#define IMG_PLAIN_EXE	(IMG_EXE | IMG_UNKNOWN)
#define IMG_EXE_V1	(IMG_EXE | IMG_COFF)
#define IMG_EXE_V2	(IMG_EXE | IMG_COFF | IMG_V2)
#define IMG_COFF_V1	(IMG_COFF)
#define IMG_COFF_V2	(IMG_COFF | IMG_V2)

/* Non-zero if debugging printout is required.  */
static int verbose = 0;

/* Non-zero if we were called by the !proxy method.  */
static int proxy_call = 0;

/* If we were called as `go32', holds the length of go32 full pathname.  */
static int go32_len = 0;

/* Read the given file and determine what kind of program it is.
   If IS_GO32 is non-zero, we *know* that IMAGE should be v1's go32.exe
   (therefore it cannot be a v1 symlink).  */

static int
check_image_version(char *image, int is_go32)
{
  int rv = 0;
  unsigned short header[3];
  unsigned char firstbytes[1];
  unsigned long coffhdr[42];
  int pf, i;
  int coff_offset, text_foffset;

  if (verbose)
    fprintf (stderr, "Hmmm... `%s\': ", image);

  errno = 0;
  pf = open(image, O_RDONLY|O_BINARY);
  if(pf < 0)
  {
    if (verbose)
      fprintf (stderr, "(error: %s)\n", strerror(errno));
    return IMG_ERROR;
  }

  /* See if it has an EXE header */
  read(pf, header, sizeof(header));
  if (header[0] == 0x5a4d)	/* MZ exe signature, stubbed? */
  {
    if (verbose)
      fprintf (stderr, "MZ, ");
    coff_offset = (long)header[2]*512L;
    if (header[1])
      coff_offset += (long)header[1] - 512L;
    lseek(pf, coff_offset, 0);
    read(pf, header, sizeof(header));
    rv |= IMG_EXE;
  }
  else
    coff_offset = 0;

  /* See if it has a COFF header (maybe after exe) */
  if (header[0] != 0x014c)	/* COFF? */
  {
    /* We need to pretend that v1 symlinks are v1 executables, to avoid
       endless looping, unless we were called as "go32 !proxy...", in
       which case we *know* it cannot be a symlink.  */
    int symlink = proxy_call && !is_go32;

    close(pf);
    if (verbose)
      fprintf (stderr, "without COFF signature%s\n",
	       symlink ? ", V1.x symlink" : "");
    if (symlink)
      return rv | IMG_COFF;
    else
      return rv | IMG_UNKNOWN;
  }
  if (verbose)
    fprintf (stderr, "COFF, ");
  rv |= IMG_COFF;

  /* Read the COFF header */
  errno = 0;
  lseek(pf, coff_offset, 0);
  i = read(pf, coffhdr, 0x0a8);    
  if (i != 0x0a8)
  {
    close(pf);
    if (verbose)
      fprintf (stderr, "(error reading COFF header: %s)\n", strerror(errno));
    return rv | IMG_UNKNOWN;
  }

  /* See what the first opcode is */
  text_foffset = coff_offset + coffhdr[12 + 5];	/* scnptr */
  errno = 0;
  lseek(pf, text_foffset, 0);
  read(pf, firstbytes, 1);
  if (errno)
  {
    if (verbose)
      fprintf (stderr, "(error reading 1st opcode: %s)\n", strerror(errno));
    return rv | IMG_UNKNOWN;
  }
  if (firstbytes[0] == 0xa3)	/* Opcode for movl %eax, 0x12345678 (V1) */
  {
    close(pf);
    if (verbose)
      fprintf (stderr, "V1.x\n");
    return rv;
  }
  if (verbose)
    fprintf (stderr, "V2\n");
  return rv | IMG_V2;
}

/*======================================================================*/

static int
far_strlen(int selector, int linear_addr)
{
  int save=linear_addr;
  _farsetsel(selector);
  while (_farnspeekb(linear_addr))
    linear_addr++;
  return linear_addr - save;
}

extern char **environ;

/* The original DOS command-line tail.  */
char DosCmdLine[128];

/* Points to the first non-blank character of the DOS command-line tail. */
char *argv1_start = &DosCmdLine[1];

char PROXY_STRING[] = "!proxy ";

/* We were called when the V1 go32 should have been called.  Call it
   with the same arguments we were passed */

int
run_v1_coff(int argc, char **argv)
{
  char *path = getenv("PATH");
  char *tokbuf = alloca(strlen(path)+1);
  char *dir;
  char *cmdbuf = alloca(strlen(argv1_start)+sizeof(PROXY_STRING));

  strcpy(tokbuf, path);
  if (strncmp(argv1_start, PROXY_STRING, sizeof(PROXY_STRING)-1) == 0)
    strcpy(cmdbuf, argv1_start);
  else
  {
    /* We should never get here, because v1 programs aren't run
       with !proxy in the environment.  But let's be defensive...  */
    strcpy(cmdbuf, PROXY_STRING);
    strcpy(cmdbuf + sizeof(PROXY_STRING) - 1, argv1_start);
  }

  /* we don't check "." because if v1's go32 was in "." we
     would never get a chance to run. */
  for (dir=strtok(tokbuf, ";"); dir; dir=strtok(0, ";"))
  {
    char *cp;
    char tmp[300];
    strcpy(tmp, dir);
    cp = tmp + strlen(tmp) - 1;
    if (*cp != ':' && *cp != '/' && *cp != '\\')
      *++cp = '/';
    strcpy(cp+1, "go32.exe");
    if (check_image_version(tmp, 1) == IMG_PLAIN_EXE)
    {
      /* We will try to pass the v1's go32 the original command line
	 as we got it (before our startup code built the argv[] array).  */
      if (!go32_len || !proxy_call)
      {
	if (verbose)
	  fprintf (stderr, "Exec: `%s %s\'\n", tmp, argv1_start);
	return _dos_exec(tmp, argv1_start, environ);
      }
      else
      {
	int proxy_argc, proxy_seg, proxy_ofs, i;
	unsigned short *rm_argv;
	char **arglist;

	/* Now, for the tricky part.

	   When a v2 program calls us, it sees that we are a v2 image,
	   and constructs the `!proxy' arguments accordingly.  This
	   means the arguments pointed to by `!proxy' include the
	   pathname of v2's go32.  We need to bump the SEG:OFF pointer
	   into the transfer buffer so that the v2's go32 pathname is
	   not seen by v1's go32.  How? see below.  */
	if (sscanf (cmdbuf + 7, "%x %x %x",
		    &proxy_argc, &proxy_seg, &proxy_ofs) != 3)
	{
	  fprintf (stderr, "go32/v2: malformed !proxy command line\n");
	  return -1;
	}

	/* Pull in the full command line from the transfer buffer
	   (actually, only needed for verbose operation).  */
	rm_argv=(unsigned short *)alloca(proxy_argc*sizeof(unsigned short));
	arglist = (char **)alloca (proxy_argc * sizeof (char *));
	movedata(_dos_ds, proxy_seg * 16 + proxy_ofs,
		 _my_ds(), (int)rm_argv, proxy_argc*sizeof(unsigned short));
    
	for (i = 0; i < proxy_argc; i++)
	{
	  int al = far_strlen(_dos_ds, proxy_seg*16 + rm_argv[i]);
	  char *arg = (char *)alloca(al+1);
	  movedata(_dos_ds, proxy_seg*16 + rm_argv[i],
		   _my_ds(), (int)(arg), al+1);
	  arglist[i] = arg;
	  if (verbose)
	    fprintf (stderr, "%s ", arg);
	}
	if (verbose)
	  fprintf (stderr, "\n");

	/* `rm_argv[i]' is the offset into the transfer buffer of the
	   i'th argument from the command line.  We need to bump
	   `proxy_ofs' so that it points to `rm_argv[1]' instead of
	   `rm_argv[0]'.  This way, the first argument that will be
	   seen by v1's go32 will be the image to run, not the
	   pathname of v2's go32.  */
	proxy_ofs += sizeof (rm_argv[0]);
	sprintf (cmdbuf + 7, "%04x %04x %04x",
		 proxy_argc - 1, proxy_seg, proxy_ofs);
	if (verbose)
	  fprintf (stderr, "Exec: `%s %s\'\n", tmp, cmdbuf);

	return _dos_exec(tmp, cmdbuf, environ);
      }
    }
  }
  fprintf(stderr, "go32/v2: cannot find v1's go32.exe\n");
  return -1;
}

/*======================================================================*/

/* It was an unstubbed V2 COFF file.  Use v2load to run it */

int
run_v2_coff(int argc, char **argv)
{
  jmp_buf start_state;
  int rm_la;
  short *rm_argv;
  char newcmdline[24];
  char fullpath[FILENAME_MAX];
  int i, sl, q, cmdlen;
  char *argv0 = argv[0];
  int tbuf, max_dos_mem;

  /* The actual command line might be longer than 126 characters, so
     we cannot call `v2loadimage' with the full argv[] array as the
     command line.  We also cannot call it with the original DOS
     command line, because after expansion by the startup code of
     the image we load it will have the image name as both argv[0] and
     argv[1] and all other arguments after it.  Response file cannot
     be used either, because the thread never returns to us after we
     longjmp, so we don't get to delete the response file.

     The only way I know out of this mess is to pass the argv[] array
     (sans argv[1]) via the !proxy method.  Sigh..  (The code was
     shamelessly stolen from src/libc/dis/process/dosexec.c).  */

  /* Make argv[0] explicit (why not?).  */
  _fixpath (argv[0], fullpath);
  argv[0] = fullpath;

  /* Can't use the usual transfer buffer, because `v2loadimage' will
     overwrite it.  Allocate our own buffer, for as much bytes as we need.  */
  for (cmdlen = sizeof (short), i = 0; i < argc; i++)
    cmdlen += strlen (argv[i]) + 1 + sizeof (short);

  tbuf = __dpmi_allocate_dos_memory ((cmdlen + 15) >> 4, &max_dos_mem);
  if (tbuf == -1)
  {
    fprintf (stderr, "Not enough DOS memory to pass args to %s\n", argv[0]);
    if (verbose)
      fprintf (stderr, "(Need %d bytes, only %d available)\n",
	       (cmdlen + 15) & 0xfffffff0, max_dos_mem << 4);
    argv[0] = argv0;
    return -1;
  }

  rm_la   = (tbuf << 4) & 0xfffff;
  q	  = rm_la + (argc + 1) * sizeof (short);
  rm_argv = (short *)alloca ((argc + 1) * sizeof (short));
  for (i = 0; i < argc; i++)
  {
    sl = strlen (argv[i]) + 1;
    dosmemput (argv[i], sl, q);
    rm_argv[i] = (q - rm_la) & 0xffff;
    q += sl;
  }
  rm_argv[i] = 0;
  dosmemput (rm_argv, (argc + 1) * sizeof (short), rm_la);
  newcmdline[0] = 22;
  sprintf (newcmdline + 1, " %s%04x %04x 0000", PROXY_STRING, argc, tbuf);
  if (verbose)
    fprintf (stderr, "V2Load %s%s\n", argv[0], newcmdline + 1);

  newcmdline[23] = '\n';
  if(v2loadimage(argv0, newcmdline, start_state))
  {
    fprintf(stderr, "Load failed for image %s\n",argv[0]);
    argv[0] = argv0;
    return -1;
  }
  argv[0] = argv0;

  longjmp(start_state, 0);
  return 0;
}

/*======================================================================*/

/* Save on space, don't expand command-line wildcards.  */
char **__crt0_glob_function(char *argument) { return 0; }
void __crt0_load_environment_file(char *app) {}

int
main(int argc, char **argv)
{
  int i;
  char *tail;
  char *proxy_ev = getenv(" !proxy");

  __djgpp_set_ctrl_c(0);

  /* Debugging printout, anyone?  */
  if (getenv ("GO32_V2_DEBUG"))
    verbose = 1;

  /* Get the original DOS command-line tail.  */
  dosmemget(_go32_info_block.linear_address_of_original_psp+128, 128,
	    DosCmdLine);
  DosCmdLine[1+DosCmdLine[0]] = 0;

  /* Get past any whitespace in DOS command line.  */
  argv1_start = &DosCmdLine[1];
  while (*argv1_start && isspace(*argv1_start))
    argv1_start++;

  if (verbose)
  {
    fprintf (stderr, "Called as `%s\'\n", argv[0]);
    fprintf (stderr, "DOS CmdTail: `%s\'\n", argv1_start);
  }

  /* !proxy in the environment overrides the command line.  */
  if (proxy_ev)
  {
    argv1_start = proxy_ev;
    proxy_call  = 1;
    if (verbose)
    {
      fprintf (stderr, "Environ CmdTail: `%s\'\n", argv1_start);
    }
    while (*argv1_start && isspace(*argv1_start))
      argv1_start++;
  }
  else if (strncmp(argv1_start, PROXY_STRING, sizeof(PROXY_STRING)-1) == 0)
  {
    proxy_call = 1;
  }

  /* Are we called as GO32?
     If we are, then the *real* image is in `argv[1]'.  */
  for (tail = argv[0] + strlen (argv[0]); tail > argv[0]; tail--)
    if (*tail == '/' || *tail == '\\')
    {
      ++tail;
      break;
    }

  /* I don't want to rely too much on the way argv[0] looks like.
     `go32', `go32.exe', `Go32.EXE'--I think all of these should be OK.  */
  if (strlen(tail) >= 4 && strnicmp (tail, "go32", 4) == 0
      && (tail[4] == '.' || tail[4] == '-' || tail[4] == '\0'))
  {
    go32_len = strlen (argv[0]);
    ++argv;
    --argc;
  }

  /* `go32 -d' means the *real* image (the debugger) is after `-d'.  */
  if (go32_len && argc > 0 && strcmp (argv[0], "-d") == 0)
  {
    ++argv;
    --argc;
  }

  if (argc < 1)
  {
    printf("go32/v2 version %s built %s %s\n","2.0",__DATE__,__TIME__);
    printf("Usage: go32 coff-image [args]\n");
    printf("Rename this to go32.exe only if you need a go32 that can run v2 binaries as\n"
	   " well as v1 binaries (old makefiles).  Put ahead of the old go32 in your PATH\n"
	   " but do not delete your old go32 - leave it in the PATH after this one.\n");
    
    printf("Set GO32_V2_DEBUG=y in the environment to get verbose output.\n\n");
    /* Add the memory that we use for ourselves to the free amount.  */
    i = (_go32_dpmi_remaining_physical_memory() + (int)sbrk(0))/1024;
    printf("DPMI memory available: %d Kb\n",i);
    i = _go32_dpmi_remaining_virtual_memory()/1024-i;
    if(i < 0)
      i = 0;
    printf("DPMI swap space available: %d Kb\n", i);
    return 1;
  }

  switch (check_image_version(argv[0], 0))
  {
  case IMG_UNKNOWN:
    fprintf(stderr, "go32/v2: Unknown file type: %s\n", argv[0]);
    return -1;
  case IMG_ERROR:
    fprintf(stderr, "go32/v2: Error: %s: %s\n", argv[0], strerror(errno));
    return -1;
  case IMG_COFF_V2:
    return run_v2_coff(argc, argv);
  case IMG_COFF_V1:
  case IMG_EXE_V1:
    return run_v1_coff(argc, argv);
  case IMG_PLAIN_EXE:
  case IMG_EXE_V2:
    return spawnv(P_WAIT, argv[0], argv);
  }
  return 1;
}

