/* spawn.c (rsxnt) -- Copyright (c) 1996-1998 Rainer Schnitker */

#include "rsxnt.h"
#include <string.h>

#ifdef EMX_SYS_LIB /* dynamic link via ctordtor in ptrace() */
WAIT_DEBUG _func_rsxnt_wait_debuggee = NULL;
#endif

static const char * getname (const char * path)
{
    const char *p;

    p = path;
    while (*path != 0){
	switch (*path) {
	    case ':':
	    case '/':
	    case '\\':
		p = path + 1;
		break;
	}
	++path;
    }
    return ((char *)p);
}

#define ARGS_SIZE 2048
#define ADD(n) if (size + (n) > ARGS_SIZE) return (E2BIG)
#define DST (args+size)

static long spawn_args (char *args, const char *arg_ptr, DWORD argc,
			 DWORD mode32, UCHAR session, const char *prog_name)
{
  size_t len, size;
  const char *src, *s, *base;
  int i, quote, bs, method;

  base = (const char *) getname (prog_name);
  method = 0;
  if (lstrcmpi (base, "cmd.exe") == 0)
    method = 1;
  else if (lstrcmpi (base, "command.com") == 0) /* does not like a quote */
    method = 2;
  src = arg_ptr;
  size = 0;
  if (argc > 0)
    {
      ++src;		    /* skip flags byte */
      len = lstrlen (src);
      if (!session)
	{
	  ADD (len);
	  memcpy (DST, src, len);
	  size += len;
	}
      src += len + 1;
    }
  for (i = 1; i < argc; ++i)
    {
      ADD (1);
      *DST = ' '; ++size;

      ++src;			/* skip flags byte */
      quote = FALSE;
      if (*src == 0)
	quote = TRUE;
      else if ( /* opt_quote ||*/ (mode32 & P_QUOTE))
	{
	  if (src[0] == '@' && src[1] != 0)
	    quote = TRUE;
	  else
	    for (s = src; *s != 0; ++s)
	      if (*s == '?' || *s == '*')
		{
		  quote = TRUE;
		  break;
		}
	}
      if (!quote)
	for (s = src; *s != 0; ++s)
	  if (method <= 1) /* not command.com */
	    if (*s == ' ' || *s == '\t' || *s == '&' || (*s == '"' && method == 1))
	      {
		quote = TRUE;
		break;
	      }
      if (quote)
	{
	  ADD (1);
	  *DST = '"'; ++size;
	}
      bs = 0;
      while (*src != 0)
	{
	  if (*src == '"' && method == 0)
	    {
	      ++bs;
	      ADD (bs);
	      while (bs > 0)
		{
		  *DST = '\\';
		  ++size; --bs;
		}
	    }
	  else if (*src == '\\' && method == 0)
	    ++bs;
	  else
	    bs = 0;
	  ADD (1);
	  *DST = *src; ++size;
	  ++src;
	}
      if (quote)
	{
	  ADD (1+bs);
	  while (bs > 0)
	    {
	      *DST = '\\'; ++size;
	      --bs;
	    }
	  *DST = '"'; ++size;
	}
      ++src;
    }
  ADD (1);
  *DST = 0; ++size;
  if (!session && argc > 0 && (mode32 & P_TILDE))
    {
      ADD (1);
      *DST = '~'; ++size;
      src = arg_ptr;
      for (i = 0; i < argc; ++i)
	{
	  ++src;		    /* skip flags byte */
	  if (*src == 0 || *src == '~')
	    {
	      ADD (1);
	      *DST = '~'; ++size;
	    }
	  len = lstrlen (src) + 1;
	  ADD (len);
	  memcpy (DST, src, len);
	  size += len; src += len;
	}
    }
  ADD (1);
  *DST = 0; ++size;
  return 0;
}

static DWORD WINAPI exit_thread(LPVOID lpThreadParameter)
{
    EMXPROCESS *p = _rsxnt_get_process_ptr();
    int i = (int) lpThreadParameter;

    WaitForSingleObject(p->childs[i].hProcess, INFINITE);

    _rsxnt_send_signal(p, SIGCLD);
    return _rsxnt_do_signal(SIGCLD);
}

int __spawnve (struct _new_proc *np)
{
    long mode32, rc;
    char mode8;
    DWORD fdwCreate;
    BOOL session;
    PROCESS_INFORMATION procinfo;
    STARTUPINFO si;
    char fname[260];
    char args[ARGS_SIZE];
    EMXPROCESS *p = _rsxnt_get_process_ptr();

    mode32 = np->mode;
    if (mode32 & 0x8000)
	mode32 |= np->mode2 << 16;
    mode8 = np->mode & 0xff;

    switch (mode8) {
	case P_NOWAIT:
	case P_WAIT:
	case P_OVERLAY:
	    session = FALSE;
	    fdwCreate = 0;
	    break;
	case P_DEBUG:
	    session = !(mode32 & P_NOSESSION);
	    fdwCreate = DEBUG_ONLY_THIS_PROCESS;
	    if (session)
		fdwCreate |= CREATE_NEW_CONSOLE;
	    break;
	case P_PM:
	    session = TRUE;
	    fdwCreate = 0;
	    break;
	case P_DETACH:
	    session = FALSE;
	    fdwCreate = DETACHED_PROCESS;
	    break;
	case P_SESSION:
	    fdwCreate = CREATE_NEW_CONSOLE;
	    session = TRUE;
	    break;
	default:
	    return _rsxnt_errno(EINVAL);
    }

    /* for rsxio32: if no console detach process */
    if (p->bConsoleApp != FALSE && (mode8 == P_WAIT || mode8 == P_NOWAIT)
	    && !GetConsoleTitle(fname, 512)) {
	if (!fdwCreate)
	    fdwCreate = DETACHED_PROCESS;
    }

    memset(&si, 0, sizeof (STARTUPINFO));
    si.cb = sizeof(STARTUPINFO);
    if (p->file[0].f_handle != (HANDLE)-2) {	/* dummy GUI handles */
	si.dwFlags = STARTF_USESTDHANDLES;
	si.hStdInput = p->file[0].f_handle;
	si.hStdOutput = p->file[1].f_handle;
	si.hStdError = p->file[2].f_handle;
    }

    _rsxnt_truncate_name(fname, (char *) np->fname_off);

    rc = spawn_args (args, (const char *)np->arg_off, np->arg_count,
		    mode32, 0, fname);

    if (rc != 0)
	return (-rc);

    if (CreateProcess(fname, args, NULL, NULL,
            TRUE, fdwCreate, (LPVOID)np->env_off, NULL,
            &si, &procinfo) == FALSE)
        return _rsxnt_errno(EINVAL);

    if (mode8 != P_DEBUG)
        CloseHandle (procinfo.hThread);

    if (mode8 == P_WAIT || mode8 == P_OVERLAY) {
	DWORD dwExit;

	if (_rsxnt_win32_version == WIN32S) {
	    for (;;) {
		if (GetExitCodeProcess(procinfo.hProcess, &dwExit) == FALSE)
		    break;
		else if (dwExit != STILL_ACTIVE)
		    break;
		Sleep(500);
	    }
	} else {
	    WaitForSingleObject(procinfo.hProcess, INFINITE);
	    GetExitCodeProcess(procinfo.hProcess, &dwExit);
	}
	CloseHandle (procinfo.hProcess);

        if (mode8 == P_OVERLAY)
            ExitProcess(dwExit);
	return dwExit;
    } else {
	int i;
	DWORD tid;
	HANDLE h;

	for (i = 0; i < MAX_CHILD; i++) {
	    if (!p->childs[i].hProcess)
		break;
	}
	if (i == MAX_CHILD)	/* kill last child entry */
	    --i;

	p->childs[i].hProcess = procinfo.hProcess;
        p->childs[i].pid = p->childs[i].dwProcessId = procinfo.dwProcessId;
	p->childs[i].hThread = procinfo.hThread;
	p->childs[i].dwThreadId = procinfo.dwThreadId;
        if (p->childs[i].pid < 0) /* Win95 hack */
            p->childs[i].pid = - p->childs[i].pid;

	if (mode8 == P_DEBUG) {
            int status;

            p->debuggee.pid = p->childs[i].pid;
	    p->debuggee.hProcess = procinfo.hProcess;
	    p->debuggee.dwProcessId = procinfo.dwProcessId;
	    p->debuggee.hThread[0] = procinfo.hThread;
	    p->debuggee.dwThreadId[0] = procinfo.dwThreadId;
            p->debuggee.hCurrentThread = procinfo.hThread;
            p->debuggee.dwCurrentThreadId = procinfo.dwThreadId;
            p->debuggee.status = DEB_INIT;
            p->debuggee.wait_status = 0;

#ifdef EMX_SYS_LIB
            if (_func_rsxnt_wait_debuggee)
                (*_func_rsxnt_wait_debuggee) (&status);
#else
            _rsxnt_wait_debuggee (&status);
#endif
	}
	h = CreateThread(NULL, 0, exit_thread, (LPVOID) i, 0, &tid);
	if (h)
	  CloseHandle (h);
	return p->childs[i].pid;
    }
}
