/*
 * msvcrt.dll spawn/exec functions
 *
 * Copyright 1996,1998 Marcus Meissner
 * Copyright 1996 Jukka Iivonen
 * Copyright 1997,2000 Uwe Bonnes
 * Copyright 2000 Jon Griffiths
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 *
 * FIXME:
 * -File handles need some special handling. Sometimes children get
 *  open file handles, sometimes not. The docs are confusing
 * -No check for maximum path/argument/environment size is done
 */
#include "config.h"

#include <stdarg.h>

#include "msvcrt.h"
#include "wine/debug.h"

WINE_DEFAULT_DEBUG_CHANNEL(msvcrt);

/* INTERNAL: Spawn a child process */
static MSVCRT_intptr_t msvcrt_spawn(int flags, const char* exe, char* cmdline, char* env)
{
  STARTUPINFOA si;
  PROCESS_INFORMATION pi;

  if ((unsigned)flags > MSVCRT__P_DETACH)
  {
    *MSVCRT__errno() = MSVCRT_EINVAL;
    return -1;
  }

  memset(&si, 0, sizeof(si));
  si.cb = sizeof(si);
  msvcrt_create_io_inherit_block(&si);
  if (!CreateProcessA(exe, cmdline, NULL, NULL, TRUE,
                     flags == MSVCRT__P_DETACH ? DETACHED_PROCESS : 0,
                     env, NULL, &si, &pi))
  {
    msvcrt_set_errno(GetLastError());
    MSVCRT_free(si.lpReserved2);
    return -1;
  }

  MSVCRT_free(si.lpReserved2);
  switch(flags)
  {
  case MSVCRT__P_WAIT:
    WaitForSingleObject(pi.hProcess, INFINITE);
    GetExitCodeProcess(pi.hProcess,&pi.dwProcessId);
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);
    return pi.dwProcessId;
  case MSVCRT__P_DETACH:
    CloseHandle(pi.hProcess);
    pi.hProcess = 0;
    /* fall through */
  case MSVCRT__P_NOWAIT:
  case MSVCRT__P_NOWAITO:
    CloseHandle(pi.hThread);
    return (MSVCRT_intptr_t)pi.hProcess;
  case  MSVCRT__P_OVERLAY:
    MSVCRT__exit(0);
  }
  return -1; /* can't reach here */
}

/* INTERNAL: Convert argv list to a single 'delim'-separated string, with an
 * extra '\0' to terminate it
 */
static char* msvcrt_argvtos(const char* const* arg, char delim)
{
  const char* const* a;
  long size;
  char* p;
  char* ret;

  if (!arg && !delim)
  {
      /* Return NULL for an empty environment list */
      return NULL;
  }

  /* get length */
  a = arg;
  size = 0;
  while (*a)
  {
    size += strlen(*a) + 1;
    a++;
  }

  ret = (char*)MSVCRT_malloc(size + 1);
  if (!ret)
    return NULL;

  /* fill string */
  a = arg;
  p = ret;
  while (*a)
  {
    int len = strlen(*a);
    memcpy(p,*a,len);
    p += len;
    *p++ = delim;
    a++;
  }
  if (delim && p > ret) p[-1] = 0;
  else *p = 0;
  return ret;
}

/* INTERNAL: Convert va_list to a single 'delim'-separated string, with an
 * extra '\0' to terminate it
 */
static char* msvcrt_valisttos(const char* arg0, va_list alist, char delim)
{
  va_list alist2;
  long size;
  const char *arg;
  char* p;
  char *ret;

#ifdef HAVE_VA_COPY
  va_copy(alist2,alist);
#else
# ifdef HAVE___VA_COPY
  __va_copy(alist2,alist);
# else
  alist2 = alist;
# endif
#endif

  if (!arg0 && !delim)
  {
      /* Return NULL for an empty environment list */
      return NULL;
  }

  /* get length */
  arg = arg0;
  size = 0;
  do {
      size += strlen(arg) + 1;
      arg = va_arg(alist, char*);
  } while (arg != NULL);

  ret = (char*)MSVCRT_malloc(size + 1);
  if (!ret)
    return NULL;

  /* fill string */
  arg = arg0;
  p = ret;
  do {
      int len = strlen(arg);
      memcpy(p,arg,len);
      p += len;
      *p++ = delim;
      arg = va_arg(alist2, char*);
  } while (arg != NULL);
  if (delim && p > ret) p[-1] = 0;
  else *p = 0;
  return ret;
}

/*********************************************************************
 *		_cwait (MSVCRT.@)
 */
MSVCRT_intptr_t CDECL _cwait(int *status, MSVCRT_intptr_t pid, int action)
{
  HANDLE hPid = (HANDLE)pid;
  int doserrno;

  action = action; /* Remove warning */

  if (!WaitForSingleObject(hPid, INFINITE))
  {
    if (status)
    {
      DWORD stat;
      GetExitCodeProcess(hPid, &stat);
      *status = (int)stat;
    }
    return pid;
  }
  doserrno = GetLastError();

  if (doserrno == ERROR_INVALID_HANDLE)
  {
    *MSVCRT__errno() =  MSVCRT_ECHILD;
    *MSVCRT___doserrno() = doserrno;
  }
  else
    msvcrt_set_errno(doserrno);

  return status ? *status = -1 : -1;
}

/*********************************************************************
 *		_execl (MSVCRT.@)
 *
 * Like on Windows, this function does not handle arguments with spaces
 * or double-quotes.
 */
MSVCRT_intptr_t CDECL _execl(const char* name, const char* arg0, ...)
{
  va_list ap;
  char * args;
  MSVCRT_intptr_t ret;

  va_start(ap, arg0);
  args = msvcrt_valisttos(arg0, ap, ' ');
  va_end(ap);

  ret = msvcrt_spawn(MSVCRT__P_OVERLAY, name, args, NULL);
  MSVCRT_free(args);

  return ret;
}

/*********************************************************************
 *		_execle (MSVCRT.@)
 */
MSVCRT_intptr_t CDECL _execle(const char* name, const char* arg0, ...)
{
    FIXME("stub\n");
    return -1;
}

/*********************************************************************
 *		_execlp (MSVCRT.@)
 *
 * Like on Windows, this function does not handle arguments with spaces
 * or double-quotes.
 */
MSVCRT_intptr_t CDECL _execlp(const char* name, const char* arg0, ...)
{
  va_list ap;
  char * args;
  MSVCRT_intptr_t ret;
  char fullname[MAX_PATH];

  _searchenv(name, "PATH", fullname);

  va_start(ap, arg0);
  args = msvcrt_valisttos(arg0, ap, ' ');
  va_end(ap);

  ret = msvcrt_spawn(MSVCRT__P_OVERLAY, fullname[0] ? fullname : name, args, NULL);
  MSVCRT_free(args);

  return ret;
}

/*********************************************************************
 *		_execlpe (MSVCRT.@)
 */
MSVCRT_intptr_t CDECL _execlpe(const char* name, const char* arg0, ...)
{
    FIXME("stub\n");
    return -1;
}

/*********************************************************************
 *		_execv (MSVCRT.@)
 *
 * Like on Windows, this function does not handle arguments with spaces
 * or double-quotes.
 */
MSVCRT_intptr_t CDECL _execv(const char* name, char* const* argv)
{
  return _spawnve(MSVCRT__P_OVERLAY, name, (const char* const*) argv, NULL);
}

/*********************************************************************
 *		_execve (MSVCRT.@)
 *
 * Like on Windows, this function does not handle arguments with spaces
 * or double-quotes.
 */
MSVCRT_intptr_t CDECL _execve(const char* name, char* const* argv, const char* const* envv)
{
  return _spawnve(MSVCRT__P_OVERLAY, name, (const char* const*) argv, envv);
}

/*********************************************************************
 *		_execvpe (MSVCRT.@)
 *
 * Like on Windows, this function does not handle arguments with spaces
 * or double-quotes.
 */
MSVCRT_intptr_t CDECL _execvpe(const char* name, char* const* argv, const char* const* envv)
{
  char fullname[MAX_PATH];

  _searchenv(name, "PATH", fullname);
  return _spawnve(MSVCRT__P_OVERLAY, fullname[0] ? fullname : name,
                  (const char* const*) argv, envv);
}

/*********************************************************************
 *		_execvp (MSVCRT.@)
 *
 * Like on Windows, this function does not handle arguments with spaces
 * or double-quotes.
 */
MSVCRT_intptr_t CDECL _execvp(const char* name, char* const* argv)
{
  return _execvpe(name, argv, NULL);
}

/*********************************************************************
 *		_spawnl (MSVCRT.@)
 *
 * Like on Windows, this function does not handle arguments with spaces
 * or double-quotes.
 */
MSVCRT_intptr_t CDECL _spawnl(int flags, const char* name, const char* arg0, ...)
{
  va_list ap;
  char * args;
  MSVCRT_intptr_t ret;

  va_start(ap, arg0);
  args = msvcrt_valisttos(arg0, ap, ' ');
  va_end(ap);

  ret = msvcrt_spawn(flags, name, args, NULL);
  MSVCRT_free(args);

  return ret;
}

/*********************************************************************
 *		_spawnle (MSVCRT.@)
 */
MSVCRT_intptr_t CDECL _spawnle(int flags, const char* name, const char* arg0, ...)
{
    va_list ap;
    char *args, *envs = NULL;
    const char * const *envp;
    MSVCRT_intptr_t ret;

    va_start(ap, arg0);
    args = msvcrt_valisttos(arg0, ap, ' ');
    va_end(ap);

    va_start(ap, arg0);
    while (va_arg( ap, char * ) != NULL) /*nothing*/;
    envp = va_arg( ap, const char * const * );
    if (envp) envs = msvcrt_argvtos(envp, 0);
    va_end(ap);

    ret = msvcrt_spawn(flags, name, args, envs);

    MSVCRT_free(args);
    if (envs) MSVCRT_free(envs);
    return ret;
}


/*********************************************************************
 *		_spawnlp (MSVCRT.@)
 *
 * Like on Windows, this function does not handle arguments with spaces
 * or double-quotes.
 */
MSVCRT_intptr_t CDECL _spawnlp(int flags, const char* name, const char* arg0, ...)
{
  va_list ap;
  char * args;
  MSVCRT_intptr_t ret;
  char fullname[MAX_PATH];

  _searchenv(name, "PATH", fullname);

  va_start(ap, arg0);
  args = msvcrt_valisttos(arg0, ap, ' ');
  va_end(ap);

  ret = msvcrt_spawn(flags, fullname[0] ? fullname : name, args, NULL);
  MSVCRT_free(args);

  return ret;
}

/*********************************************************************
 *		_spawnlpe (MSVCRT.@)
 */
MSVCRT_intptr_t CDECL _spawnlpe(int flags, const char* name, const char* arg0, ...)
{
    va_list ap;
    char *args, *envs = NULL;
    const char * const *envp;
    MSVCRT_intptr_t ret;
    char fullname[MAX_PATH];

    _searchenv(name, "PATH", fullname);

    va_start(ap, arg0);
    args = msvcrt_valisttos(arg0, ap, ' ');
    va_end(ap);

    va_start(ap, arg0);
    while (va_arg( ap, char * ) != NULL) /*nothing*/;
    envp = va_arg( ap, const char * const * );
    if (envp) envs = msvcrt_argvtos(envp, 0);
    va_end(ap);

    ret = msvcrt_spawn(flags, fullname[0] ? fullname : name, args, envs);

    MSVCRT_free(args);
    if (envs) MSVCRT_free(envs);
    return ret;
}

/*********************************************************************
 *		_spawnve (MSVCRT.@)
 *
 * Like on Windows, this function does not handle arguments with spaces
 * or double-quotes.
 */
MSVCRT_intptr_t CDECL _spawnve(int flags, const char* name, const char* const* argv,
                               const char* const* envv)
{
  char * args = msvcrt_argvtos(argv,' ');
  char * envs = msvcrt_argvtos(envv,0);
  char fullname[MAX_PATH];
  const char *p;
  int len;
  MSVCRT_intptr_t ret = -1;

  TRACE(":call (%s), params (%s), env (%s)\n",debugstr_a(name),debugstr_a(args),
   envs?"Custom":"Null");

  /* no check for NULL name.
     native doesn't do it */

  p = memchr(name, '\0', MAX_PATH);
  if( !p )
    p = name + MAX_PATH - 1;
  len = p - name;

  /* extra-long names are silently truncated. */
  memcpy(fullname, name, len);

  for( p--; p >= name; p-- )
  {
    if( *p == '\\' || *p == '/' || *p == ':' || *p == '.' )
      break;
  }

  /* if no extension is given, assume .exe */
  if( (p < name || *p != '.') && len <= MAX_PATH - 5 )
  {
    FIXME("only trying .exe when no extension given\n");
    memcpy(fullname+len, ".exe", 4);
    len += 4;
  }

  fullname[len] = '\0';

  if (args)
  {
    ret = msvcrt_spawn(flags, fullname, args, envs);
    MSVCRT_free(args);
  }
  if (envs)
    MSVCRT_free(envs);

  return ret;
}

/*********************************************************************
 *		_spawnv (MSVCRT.@)
 *
 * Like on Windows, this function does not handle arguments with spaces
 * or double-quotes.
 */
MSVCRT_intptr_t CDECL _spawnv(int flags, const char* name, const char* const* argv)
{
  return _spawnve(flags, name, argv, NULL);
}

/*********************************************************************
 *		_spawnvpe (MSVCRT.@)
 *
 * Like on Windows, this function does not handle arguments with spaces
 * or double-quotes.
 */
MSVCRT_intptr_t CDECL _spawnvpe(int flags, const char* name, const char* const* argv,
                                const char* const* envv)
{
  char fullname[MAX_PATH];
  _searchenv(name, "PATH", fullname);
  return _spawnve(flags, fullname[0] ? fullname : name, argv, envv);
}

/*********************************************************************
 *		_spawnvp (MSVCRT.@)
 *
 * Like on Windows, this function does not handle arguments with spaces
 * or double-quotes.
 */
MSVCRT_intptr_t CDECL _spawnvp(int flags, const char* name, const char* const* argv)
{
  return _spawnvpe(flags, name, argv, NULL);
}

/*********************************************************************
 *		_popen (MSVCRT.@)
 * FIXME: convert to _wpopen and call that from here instead?  But it
 * would have to convert the command back to ANSI to call msvcrt_spawn,
 * less than ideal.
 */
MSVCRT_FILE* CDECL MSVCRT__popen(const char* command, const char* mode)
{
  static const char wcmd[] = "wcmd", cmdFlag[] = " /C ", comSpec[] = "COMSPEC";
  MSVCRT_FILE *ret;
  BOOL readPipe = TRUE;
  int textmode, fds[2], fdToDup, fdToOpen, fdStdHandle = -1, fdStdErr = -1;
  const char *p;
  char *cmdcopy;
  DWORD comSpecLen;

  TRACE("(command=%s, mode=%s)\n", debugstr_a(command), debugstr_a(mode));

  if (!command || !mode)
    return NULL;

  textmode = *__p__fmode() & (MSVCRT__O_BINARY | MSVCRT__O_TEXT);
  for (p = mode; *p; p++)
  {
    switch (*p)
    {
      case 'W':
      case 'w':
        readPipe = FALSE;
        break;
      case 'B':
      case 'b':
        textmode |= MSVCRT__O_BINARY;
        textmode &= ~MSVCRT__O_TEXT;
        break;
      case 'T':
      case 't':
        textmode |= MSVCRT__O_TEXT;
        textmode &= ~MSVCRT__O_BINARY;
        break;
    }
  }
  if (_pipe(fds, 0, textmode) == -1)
    return NULL;

  fdToDup = readPipe ? 1 : 0;
  fdToOpen = readPipe ? 0 : 1;

  if ((fdStdHandle = _dup(fdToDup)) == -1)
    goto error;
  if (_dup2(fds[fdToDup], fdToDup) != 0)
    goto error;
  if (readPipe)
  {
    if ((fdStdErr = _dup(MSVCRT_STDERR_FILENO)) == -1)
      goto error;
    if (_dup2(fds[fdToDup], MSVCRT_STDERR_FILENO) != 0)
      goto error;
  }

  _close(fds[fdToDup]);

  comSpecLen = GetEnvironmentVariableA(comSpec, NULL, 0);
  if (!comSpecLen)
    comSpecLen = strlen(wcmd) + 1;
  cmdcopy = HeapAlloc(GetProcessHeap(), 0, comSpecLen + strlen(cmdFlag)
   + strlen(command));
  if (!GetEnvironmentVariableA(comSpec, cmdcopy, comSpecLen))
    strcpy(cmdcopy, wcmd);
  strcat(cmdcopy, cmdFlag);
  strcat(cmdcopy, command);
  if (msvcrt_spawn(MSVCRT__P_NOWAIT, NULL, cmdcopy, NULL) == -1)
  {
    _close(fds[fdToOpen]);
    ret = NULL;
  }
  else
  {
    ret = MSVCRT__fdopen(fds[fdToOpen], mode);
    if (!ret)
      _close(fds[fdToOpen]);
  }
  HeapFree(GetProcessHeap(), 0, cmdcopy);
  _dup2(fdStdHandle, fdToDup);
  _close(fdStdHandle);
  if (readPipe)
  {
    _dup2(fdStdErr, MSVCRT_STDERR_FILENO);
    _close(fdStdErr);
  }
  return ret;

error:
  if (fdStdHandle != -1) _close(fdStdHandle);
  if (fdStdErr != -1)    _close(fdStdErr);
  _close(fds[0]);
  _close(fds[1]);
  return NULL;
}

/*********************************************************************
 *		_wpopen (MSVCRT.@)
 */
MSVCRT_FILE* CDECL MSVCRT__wpopen(const MSVCRT_wchar_t* command, const MSVCRT_wchar_t* mode)
{
  FIXME("(command=%s, mode=%s): stub\n", debugstr_w(command), debugstr_w(mode));
  return NULL;
}

/*********************************************************************
 *		_pclose (MSVCRT.@)
 */
int CDECL MSVCRT__pclose(MSVCRT_FILE* file)
{
  return MSVCRT_fclose(file);
}

/*********************************************************************
 *		system (MSVCRT.@)
 */
int CDECL MSVCRT_system(const char* cmd)
{
    char* cmdcopy;
    int res;

    /* Make a writable copy for CreateProcess */
    cmdcopy=_strdup(cmd);
    /* FIXME: should probably launch cmd interpreter in COMSPEC */
    res=msvcrt_spawn(MSVCRT__P_WAIT, NULL, cmdcopy, NULL);
    MSVCRT_free(cmdcopy);
    return res;
}

/*********************************************************************
 *		_loaddll (MSVCRT.@)
 */
MSVCRT_intptr_t CDECL _loaddll(const char* dllname)
{
  return (MSVCRT_intptr_t)LoadLibraryA(dllname);
}

/*********************************************************************
 *		_unloaddll (MSVCRT.@)
 */
int CDECL _unloaddll(MSVCRT_intptr_t dll)
{
  if (FreeLibrary((HMODULE)dll))
    return 0;
  else
  {
    int err = GetLastError();
    msvcrt_set_errno(err);
    return err;
  }
}

/*********************************************************************
 *		_getdllprocaddr (MSVCRT.@)
 */
void * CDECL _getdllprocaddr(MSVCRT_intptr_t dll, const char *name, int ordinal)
{
    if (name)
    {
        if (ordinal != -1) return NULL;
        return GetProcAddress( (HMODULE)dll, name );
    }
    if (HIWORD(ordinal)) return NULL;
    return GetProcAddress( (HMODULE)dll, (LPCSTR)(ULONG_PTR)ordinal );
}