#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include "go32.h"
#include "dpmi.h"
/*#include "process.h"*/
#if 1
#define P_WAIT		1
#define P_NOWAIT	2	/* always generates error */
#define P_OVERLAY	3
#endif
extern const char **environ;
#define environ ((const char **)environ)

#define scan_ptr() \
	char const **ptr; \
	for (ptr = &argv0; *ptr; ptr++); \
	ptr = (char const **)(*++ptr);

int execl(const char *path, const char *argv0, ...)
{
  return spawnve(P_OVERLAY, path, &argv0, environ);
}

int execle(const char *path, const char *argv0, ... /*, const char **envp */)
{
  scan_ptr();
  return spawnve(P_OVERLAY, path, &argv0, ptr);
}

int execlp(const char *path, const char *argv0, ...)
{
  return spawnvpe(P_OVERLAY, path, &argv0, environ);
}

int execlpe(const char *path, const char *argv0, ... /*, const char **envp */)
{
  scan_ptr();
  return spawnvpe(P_OVERLAY, path, &argv0, ptr);
}

/*-------------------------------------------------*/

int execv(const char *path, const char **argv)
{
  return spawnve(P_OVERLAY, path, argv, environ);
}

int execve(const char *path, const char **argv, const char **envp)
{
  return spawnve(P_OVERLAY, path, argv, envp);
}

int execvp(const char *path, const char **argv)
{
  return spawnvpe(P_OVERLAY, path, argv, environ);
}

int execvpe(const char *path, const char **argv, const char **envp)
{
  return spawnvpe(P_OVERLAY, path, argv, envp);
}

/*-------------------------------------------------*/

int spawnl(int mode, const char *path, const char *argv0, ...)
{
  return spawnve(mode, path, &argv0, environ);
}

int spawnle(int mode, const char *path, const char *argv0, ... /*, const char **envp */)
{
  scan_ptr();
  return spawnve(mode, path, &argv0, ptr);
}

int spawnlp(int mode, const char *path, const char *argv0, ...)
{
  return spawnvpe(mode, path, &argv0, environ);
}

int spawnlpe(int mode, const char *path, const char *argv0, ... /*, const char **envp */)
{
  scan_ptr();
  return spawnvpe(mode, path, &argv0, ptr);
}

/*-------------------------------------------------*/

typedef struct {
  u_short eseg;
  u_short argoff;
  u_short argseg;
  u_short fcb1_off;
  u_short fcb1_seg;
  u_short fcb2_off;
  u_short fcb2_seg;
} Execp;

static Execp parm;

static u_long tbuf;

static u_long talloc(size_t amt)
{
  u_long rv = tbuf;
  tbuf += amt;
  return rv;
}

static int direct_exec_tail(const char *program, const char *args, const char **envp)
{
  _go32_dpmi_registers r;
  u_long program_la;
  u_long arg_la;
  u_long parm_la;
  u_long env_la, env_e_la;
  char arg_header[3];
  int i;

  program_la = talloc(strlen(program)+1);
  arg_la = talloc(strlen(args)+3);
  parm_la = talloc(sizeof(Execp));

  dosmemput(program, strlen(program)+1, program_la);
  
  arg_header[0] = strlen(args);
  arg_header[1] = '\r';
  dosmemput(arg_header, 1, arg_la);
  dosmemput(args, strlen(args), arg_la+1);
  dosmemput(arg_header+1, 1, arg_la+1+strlen(args));

  do {
    env_la = talloc(1);
  } while (env_la & 15);
  talloc(-1);
  for (i=0; envp[i]; i++)
  {
    env_e_la = talloc(strlen(envp[i])+1);
    dosmemput(envp[i], strlen(envp[i])+1, env_e_la);
  }
  arg_header[0] = 0;
  arg_header[1] = 1;
  arg_header[2] = 0;
  dosmemput(arg_header, 3, talloc(3));
  env_e_la = talloc(strlen(program)+1);
  dosmemput(program, strlen(program)+1, env_e_la);

  parm.eseg = env_la / 16;
  parm.argseg = arg_la / 16;
  parm.argoff = arg_la & 15;
  dosmemput(&parm, sizeof(parm), parm_la);

  memset(&r, 0, sizeof(r));
  r.x.ax = 0x4b00;
  r.x.ds = program_la / 16;
  r.x.dx = program_la & 15;
  r.x.es = parm_la / 16;
  r.x.bx = parm_la & 15;
  _go32_dpmi_simulate_int(0x21, &r);
  if (r.x.flags & 1)
  {
    errno = r.x.ax;
    return -1;
  }
  
  memset(&r, 0, sizeof(r));
  r.h.ah = 0x4d;
  _go32_dpmi_simulate_int(0x21, &r);
  
  if (r.x.flags & 1)
  {
    errno = r.x.ax;
    return -1;
  }
  return r.x.ax;
}

static int direct_exec(const char *program, const char **argv, const char **envp)
{
  int i, arglen;
  char *args, *argp;

  tbuf = _go32_info_block.linear_address_of_transfer_buffer;

  arglen = 0;
  for (i=1; argv[i]; i++)
    arglen += strlen(argv[i]) + 1;
  args = (char *)malloc(arglen+1);
  argp = args;
  for (i=1; argv[i]; i++)
  {
    const char *p = argv[i];
    if (argp - args > 125)
      break;
    *argp++ = ' ';
    while (*p)
    {
      if (argp - args > 125)
        break;
      *argp++ = *p++;
    }
  }
  *argp = 0;
  
  return direct_exec_tail(program, args, envp);
}

typedef struct {
  char magic[16];
  int struct_length;
  char go32[16];
} StubInfo;
#define STUB_INFO_MAGIC "StubInfoMagic!!"

static int go32_exec(const char *program, const char **argv, const char **envp)
{
  int is_stubbed = 0;
  int found_si = 0;
  StubInfo si;
  unsigned short header[3];
  int pf, has_dot, i;
  char *go32, *sip;
  const char *pp, *pe;
  char rpath[80], *rp;
  int stub_offset, argc;

  int si_la, rm_la, rm_seg;
  short *rm_argv;
  char cmdline[34];
  
  pf = open(program, O_RDONLY|O_BINARY);

  read(pf, header, sizeof(header));
  if (header[0] == 0x010b || header[0] == 0x014c)
  {
    is_stubbed = 1;
  }
  else if (header[0] == 0x5a4d)
  {
    int header_offset = (long)header[2]*512L;
    if (header[1])
      header_offset += (long)header[1] - 512L;
    lseek(pf, header_offset - 4, 0);
    read(pf, &stub_offset, 4);
    header[0] = 0;
    read(pf, header, sizeof(header));
    if (header[0] == 0x010b)
      is_stubbed = 1;
    if (header[0] == 0x014c)
      is_stubbed = 1;
    lseek(pf, stub_offset, 0);
    read(pf, &si, sizeof(si));
    if (memcmp(STUB_INFO_MAGIC, si.magic, 16) == 0)
      found_si = 1;
  }
  if (!is_stubbed)
  {
    close(pf);
    return direct_exec(program, argv, envp);
  }

  if (found_si)
    go32 = si.go32;
  else
    go32 = "go32.exe";
  has_dot = 0;
  for (i=0; go32[i]; i++)
    if (go32[i] == '.')
      has_dot = 1;
  if (!has_dot)
    strcpy(go32+i, ".exe");
  for (i=0; envp[i]; i++)
    if (strncmp(envp[i], "PATH=", 5) == 0)
      pp = envp[i]+5;
  strcpy(rpath, go32);
  while (access(rpath, 0))
  {
    char *ptr;
    rp = rpath;
    for (pe=pp; *pe && *pe != ';'; pe++)
      *rp++ = *pe;
    pp = pe+1;
    if (rp > rpath && rp[-1] != '/' && rp[-1] != '\\' && rp[-1] != ':')
      *rp++ = '/';
    for (ptr = go32; *ptr; ptr++)
      *rp++ = *ptr;
    *rp = 0;
    if (access(rpath, 0) == 0)
      break;
    if (*pe == 0)
      return direct_exec(program, argv, envp); /* give up and just run it */
  }

  if (found_si)
  {  
    lseek(pf, stub_offset, 0);
    sip = (char *)malloc(si.struct_length);
    read(pf, sip, si.struct_length);
  }
  close(pf);

  argv[0] = program; /* since that's where we really found it */

  tbuf = _go32_info_block.linear_address_of_transfer_buffer;

  if (found_si)
  {
    si_la = talloc(si.struct_length);
    dosmemput(sip, si.struct_length, si_la);
    free(sip);
  }
  
  for (argc=0; argv[argc]; argc++);
  rm_la = talloc(2*(argc+1));
  rm_seg = (_go32_info_block.linear_address_of_transfer_buffer >> 4) & 0xffff;
  rm_argv = (short *)malloc((argc+1) * sizeof(short));
  for (i=0; i<argc; i++)
  {
    int sl = strlen(argv[i]) + 1;
    int q = talloc(sl);
    dosmemput(argv[i], sl, q);
    rm_argv[i] = (q - (rm_seg<<4)) & 0xffff;
  }
  rm_argv[i] = 0;
  dosmemput(rm_argv, 2*(argc+1), rm_la);
  
  sprintf(cmdline, " !proxy %04x %04x %04x %04x %04x",
    argc, rm_seg, (rm_la - (rm_seg<<4))&0xffff,
    rm_seg, (si_la - (rm_seg<<4))&0xffff);
  if (!found_si)
    cmdline[22] = 0; /* remove stub information */

  return direct_exec_tail(rpath, cmdline, envp);
}

static int command_exec(const char *program, const char **argv, const char **envp)
{
  const char *comspec=0;
  char *cmdline;
  char *newargs[3];
  int cmdlen;
  int i;
  
  cmdlen = strlen(program) + 4;
  for (i=0; argv[i]; i++)
    cmdlen += strlen(argv[i]) + 1;
  cmdline = (char *)malloc(cmdlen);
  
  strcpy(cmdline, "/c ");
  for (i=0; program[i]; i++)
  {
    if (program[i] == '/')
      cmdline[i+3] = '\\';
    else
      cmdline[i+3] = program[i];
  }
  cmdline[i+3] = 0;
  for (i=1; argv[i]; i++)
  {
    strcat(cmdline, " ");
    strcat(cmdline, argv[i]);
  }
  for (i=0; envp[i]; i++)
    if (strncmp(envp[i], "COMSPEC=", 8) == 0)
      comspec = envp[i]+8;
  if (!comspec)
    for (i=0; environ[i]; i++)
      if (strncmp(environ[i], "COMSPEC=", 8) == 0)
        comspec = environ[i]+8;
  if (!comspec)
    comspec = "c:/command.com";
  newargs[0] = comspec;
  newargs[1] = cmdline;
  newargs[2] = 0;
  i = direct_exec(comspec, (const char **)newargs, envp);
  free(cmdline);
  return i;
}

static int script_exec(const char *program, const char **argv, const char **envp)
{
  return go32_exec(program, argv, envp);
}

static struct {
  char *extension;
  int (*interp)(const char *, const char **, const char **);
} interpreters[] = {
  { ".com", direct_exec },
  { ".exe", go32_exec },
  { ".bat", command_exec },
  { 0,      script_exec }
};
#define INTERP_NO_EXT 3

int spawnv(int mode, const char *path, const char **argv)
{
  return spawnve(mode, path, argv, environ);
}

int spawnve(int mode, const char *path, const char **argv, const char **envp)
{
  /* This is the one that does the work! */
  int i = -1;
  char rpath[80], *rp, *rd=0;
  fflush(stdout); /* just in case */
  for (rp=rpath; *path; *rp++ = *path++)
  {
    if (*path == '.')
      rd = rp;
    if (*path == '\\' || *path == '/')
      rd = 0;
  }
  *rp = 0;
  if (rd)
  {
    for (i=0; interpreters[i].extension; i++)
      if (strcasecmp(rd, interpreters[i].extension) == 0)
        break;
  }
  while (access(rpath, 0))
  {
    i++;
    if (interpreters[i].extension == 0 || rd)
    {
      errno = ENOENT;
      return -1;
    }
    strcpy(rp, interpreters[i].extension);
  }
  if (i == -1)
    i = INTERP_NO_EXT;
  i = interpreters[i].interp(rpath, argv, envp);
  if (mode == P_OVERLAY)
    exit(i);
  return i;
}

int spawnvp(int mode, const char *path, const char **argv)
{
  return spawnvpe(mode, path, argv, environ);
}

int spawnvpe(int mode, const char *path, const char **argv, const char **envp)
{
  const char *pp, *pe, *ptr;
  char rpath[80], *rp, *rd;
  int hasdot = 0, i, tried_dot = 0;

  for (ptr=path; *ptr; ptr++)
  {
    if (*ptr == '.')
      hasdot = 1;
    if (*ptr == '/' || *ptr == '\\' || *ptr == ':')
      return spawnve(mode, path, argv, envp);
  }

  pp = 0;
  for (i=0; envp[i]; i++)
    if (strncmp(envp[i], "PATH=", 5) == 0)
      pp = envp[i] + 5;
  if (pp == 0)
    return spawnve(mode, path, argv, envp);

  while (1)
  {
    if (!tried_dot)
    {
      rp = rpath;
      pe = pp;
      tried_dot = 1;
    }
    else
    {
      rp = rpath;
      for (pe = pp; *pe && *pe != ';'; pe++)
        *rp++ = *pe;
      pp = pe+1;
      if (rp > rpath && rp[-1] != '/' && rp[-1] != '\\' && rp[-1] != ':')
        *rp++ = '/';
    }
    for (ptr = path; *ptr; ptr++)
      *rp++ = *ptr;
    *rp = 0;
    
    if (hasdot)
    {
      if (access(rpath, 0) == 0)
        return spawnve(mode, rpath, argv, envp);
    }
    else
    {
      for (i=0; interpreters[i].extension; i++)
      {
        strcpy(rp, interpreters[i].extension);
        if (access(rpath, 0) == 0)
          return spawnve(mode, rpath, argv, envp);
      }
    }
    if (*pe == 0)
    {
      errno = ENOENT;
      return -1;
    }
  }
}
