#include <config.h>

//#define SPAWN_DEBUG

#if !defined(SPAWN_DEBUG) || defined(_MSC_VER)
#define PING()
#else
#define PING() fprintf (stderr, "%s:%s:%d\n", __FILE__, __FUNCTION__, __LINE__); fflush (stderr)
#endif

#include <stdio.h>

/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/* dbus-spawn-win32.c Wrapper around g_spawn
 * 
 * Copyright (C) 2002, 2003, 2004  Red Hat, Inc.
 * Copyright (C) 2003 CodeFactory AB
 * Copyright (C) 2005 Novell, Inc.
 *
 * Licensed under the Academic Free License version 2.1
 * 
 * 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 2 of the License, 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */
#include "dbus-spawn.h"
#include "dbus-sysdeps.h"
#include "dbus-sysdeps-win.h"
#include "dbus-internals.h"
#include "dbus-test.h"
#include "dbus-protocol.h"

#define WIN32_LEAN_AND_MEAN
//#define STRICT
//#include <windows.h>
//#undef STRICT
#include <winsock2.h>
#undef interface

#include <stdlib.h>

#ifndef DBUS_WINCE
#include <process.h>
#endif

/**
 * Babysitter implementation details
 */
struct DBusBabysitter
  {
    int refcount;

    HANDLE start_sync_event;
#ifdef DBUS_BUILD_TESTS

    HANDLE end_sync_event;
#endif

    char *executable;
    DBusSpawnChildSetupFunc child_setup;
    void *user_data;

    int argc;
    char **argv;
    char **envp;

    HANDLE child_handle;
    int socket_to_babysitter;	/* Connection to the babysitter thread */
    int socket_to_main;

    DBusWatchList *watches;
    DBusWatch *sitter_watch;

    dbus_bool_t have_spawn_errno;
    int spawn_errno;
    dbus_bool_t have_child_status;
    int child_status;
  };

static DBusBabysitter*
_dbus_babysitter_new (void)
{
  DBusBabysitter *sitter;

  sitter = dbus_new0 (DBusBabysitter, 1);
  if (sitter == NULL)
    return NULL;

  sitter->refcount = 1;

  sitter->start_sync_event = CreateEvent (NULL, FALSE, FALSE, NULL);
  if (sitter->start_sync_event == NULL)
    {
      _dbus_babysitter_unref (sitter);
      return NULL;
    }

#ifdef DBUS_BUILD_TESTS
  sitter->end_sync_event = CreateEvent (NULL, FALSE, FALSE, NULL);
  if (sitter->end_sync_event == NULL)
    {
      _dbus_babysitter_unref (sitter);
      return NULL;
    }
#endif

  sitter->child_handle = NULL;

  sitter->socket_to_babysitter = sitter->socket_to_main = -1;

  sitter->argc = 0;
  sitter->argv = NULL;
  sitter->envp = NULL;

  sitter->watches = _dbus_watch_list_new ();
  if (sitter->watches == NULL)
    {
      _dbus_babysitter_unref (sitter);
      return NULL;
    }

  sitter->have_spawn_errno = FALSE;
  sitter->have_child_status = FALSE;

  return sitter;
}

/**
 * Increment the reference count on the babysitter object.
 *
 * @param sitter the babysitter
 * @returns the babysitter
 */
DBusBabysitter *
_dbus_babysitter_ref (DBusBabysitter *sitter)
{
  PING();
  _dbus_assert (sitter != NULL);
  _dbus_assert (sitter->refcount > 0);

  sitter->refcount += 1;

  return sitter;
}

/**
 * Decrement the reference count on the babysitter object.
 *
 * @param sitter the babysitter
 */
void
_dbus_babysitter_unref (DBusBabysitter *sitter)
{
  int i;

  PING();
  _dbus_assert (sitter != NULL);
  _dbus_assert (sitter->refcount > 0);

  sitter->refcount -= 1;

  if (sitter->refcount == 0)
    {
      if (sitter->socket_to_babysitter != -1)
        {
          _dbus_close_socket (sitter->socket_to_babysitter, NULL);
          sitter->socket_to_babysitter = -1;
        }

      if (sitter->socket_to_main != -1)
        {
          _dbus_close_socket (sitter->socket_to_main, NULL);
          sitter->socket_to_main = -1;
        }

      PING();
      if (sitter->argv != NULL)
        {
          for (i = 0; i < sitter->argc; i++)
            if (sitter->argv[i] != NULL)
              {
                dbus_free (sitter->argv[i]);
                sitter->argv[i] = NULL;
              }
          dbus_free (sitter->argv);
          sitter->argv = NULL;
        }

      if (sitter->envp != NULL)
        {
          char **e = sitter->envp;

          while (*e)
            dbus_free (*e++);
          dbus_free (sitter->envp);
          sitter->envp = NULL;
        }

      if (sitter->child_handle != NULL)
        {
          CloseHandle (sitter->child_handle);
          sitter->child_handle = NULL;
        }

      if (sitter->sitter_watch)
        {
          _dbus_watch_invalidate (sitter->sitter_watch);
          _dbus_watch_unref (sitter->sitter_watch);
          sitter->sitter_watch = NULL;
        }

      if (sitter->watches)
        _dbus_watch_list_free (sitter->watches);

      if (sitter->start_sync_event != NULL)
        {
          PING();
          CloseHandle (sitter->start_sync_event);
          sitter->start_sync_event = NULL;
        }

#ifdef DBUS_BUILD_TESTS
      if (sitter->end_sync_event != NULL)
        {
          CloseHandle (sitter->end_sync_event);
          sitter->end_sync_event = NULL;
        }
#endif

      dbus_free (sitter->executable);

      dbus_free (sitter);
    }
}

void
_dbus_babysitter_kill_child (DBusBabysitter *sitter)
{
  PING();
  if (sitter->child_handle == NULL)
    return; /* child is already dead, or we're so hosed we'll never recover */

  PING();
  TerminateProcess (sitter->child_handle, 12345);
}

/**
 * Checks whether the child has exited, without blocking.
 *
 * @param sitter the babysitter
 */
dbus_bool_t
_dbus_babysitter_get_child_exited (DBusBabysitter *sitter)
{
  PING();
  return (sitter->child_handle == NULL);
}

/**
 * Gets the exit status of the child. We do this so implementation specific
 * detail is not cluttering up dbus, for example the system launcher code.
 * This can only be called if the child has exited, i.e. call
 * _dbus_babysitter_get_child_exited(). It returns FALSE if the child
 * did not return a status code, e.g. because the child was signaled
 * or we failed to ever launch the child in the first place.
 *
 * @param sitter the babysitter
 * @param status the returned status code
 * @returns #FALSE on failure
 */
dbus_bool_t
_dbus_babysitter_get_child_exit_status (DBusBabysitter *sitter,
                                        int            *status)
{
  if (!_dbus_babysitter_get_child_exited (sitter))
    _dbus_assert_not_reached ("Child has not exited");

  if (!sitter->have_child_status ||
      sitter->child_status == STILL_ACTIVE)
    return FALSE;

  *status = sitter->child_status;
  return TRUE;
}

/**
 * Sets the #DBusError with an explanation of why the spawned
 * child process exited (on a signal, or whatever). If
 * the child process has not exited, does nothing (error
 * will remain unset).
 *
 * @param sitter the babysitter
 * @param error an error to fill in
 */
void
_dbus_babysitter_set_child_exit_error (DBusBabysitter *sitter,
                                       DBusError      *error)
{
  PING();
  if (!_dbus_babysitter_get_child_exited (sitter))
    return;

  PING();
  if (sitter->have_spawn_errno)
    {
      char *emsg = _dbus_win_error_string (sitter->spawn_errno);
      dbus_set_error (error, DBUS_ERROR_SPAWN_EXEC_FAILED,
                      "Failed to execute program %s: %s",
                      sitter->executable, emsg);
      _dbus_win_free_error_string (emsg);
    }
  else if (sitter->have_child_status)
    {
      PING();
      dbus_set_error (error, DBUS_ERROR_SPAWN_CHILD_EXITED,
                      "Process %s exited with status %d",
                      sitter->executable, sitter->child_status);
    }
  else
    {
      PING();
      dbus_set_error (error, DBUS_ERROR_FAILED,
                      "Process %s exited, status unknown",
                      sitter->executable);
    }
  PING();
}

dbus_bool_t
_dbus_babysitter_set_watch_functions (DBusBabysitter            *sitter,
                                      DBusAddWatchFunction       add_function,
                                      DBusRemoveWatchFunction    remove_function,
                                      DBusWatchToggledFunction   toggled_function,
                                      void                      *data,
                                      DBusFreeFunction           free_data_function)
{
  PING();
  return _dbus_watch_list_set_functions (sitter->watches,
                                         add_function,
                                         remove_function,
                                         toggled_function,
                                         data,
                                         free_data_function);
}

static dbus_bool_t
handle_watch (DBusWatch       *watch,
              unsigned int     condition,
              void            *data)
{
  DBusBabysitter *sitter = data;

  /* On Unix dbus-spawn uses a babysitter *process*, thus it has to
   * actually send the exit statuses, error codes and whatnot through
   * sockets and/or pipes. On Win32, the babysitter is jus a thread,
   * so it can set the status fields directly in the babysitter struct
   * just fine. The socket pipe is used just so we can watch it with
   * select(), as soon as anything is written to it we know that the
   * babysitter thread has recorded the status in the babysitter
   * struct.
   */

  PING();
  _dbus_close_socket (sitter->socket_to_babysitter, NULL);
  PING();
  sitter->socket_to_babysitter = -1;

  return TRUE;
}

/* protect_argv lifted from GLib, relicensed by author, Tor Lillqvist */
static int
protect_argv (char  **argv,
              char ***new_argv)
{
  int i;
  int argc = 0;

  while (argv[argc])
    ++argc;
  *new_argv = dbus_malloc ((argc + 1) * sizeof (char *));
  if (*new_argv == NULL)
    return -1;

  for (i = 0; i < argc; i++)
    (*new_argv)[i] = NULL;

  /* Quote each argv element if necessary, so that it will get
   * reconstructed correctly in the C runtime startup code.  Note that
   * the unquoting algorithm in the C runtime is really weird, and
   * rather different than what Unix shells do. See stdargv.c in the C
   * runtime sources (in the Platform SDK, in src/crt).
   *
   * Note that an new_argv[0] constructed by this function should
   * *not* be passed as the filename argument to a spawn* or exec*
   * family function. That argument should be the real file name
   * without any quoting.
   */
  for (i = 0; i < argc; i++)
    {
      char *p = argv[i];
      char *q;
      int len = 0;
      int need_dblquotes = FALSE;
      while (*p)
        {
          if (*p == ' ' || *p == '\t')
            need_dblquotes = TRUE;
          else if (*p == '"')
            len++;
          else if (*p == '\\')
            {
              char *pp = p;
              while (*pp && *pp == '\\')
                pp++;
              if (*pp == '"')
                len++;
            }
          len++;
          p++;
        }

      q = (*new_argv)[i] = dbus_malloc (len + need_dblquotes*2 + 1);

      if (q == NULL)
        return -1;


      p = argv[i];

      if (need_dblquotes)
        *q++ = '"';

      while (*p)
        {
          if (*p == '"')
            *q++ = '\\';
          else if (*p == '\\')
            {
              char *pp = p;
              while (*pp && *pp == '\\')
                pp++;
              if (*pp == '"')
                *q++ = '\\';
            }
          *q++ = *p;
          p++;
        }

      if (need_dblquotes)
        *q++ = '"';
      *q++ = '\0';
      /* printf ("argv[%d]:%s, need_dblquotes:%s len:%d => %s\n", i, argv[i], need_dblquotes?"TRUE":"FALSE", len, (*new_argv)[i]); */
    }
  (*new_argv)[argc] = NULL;

  return argc;
}


/* From GPGME, relicensed by g10 Code GmbH.  */
static char *
compose_string (char **strings, char separator)
{
  int i;
  int n = 0;
  char *buf;
  char *p;
  const char *ptr;
  
  if (!strings || !strings[0])
    return 0;
  for (i = 0; strings[i]; i++)
    n += strlen (strings[i]) + 1;
  n++;

  buf = p = malloc (n);
  if (!buf)
    return NULL;
  for (i = 0; strings[i]; i++)
    {
      strcpy (p, strings[i]);
      p += strlen (strings[i]);
      *(p++) = separator;
    }
  p--;
  *(p++) = '\0';
  *p = '\0';

  return buf;
}

static char *
build_commandline (char **argv)
{
  return compose_string (argv, ' ');
}

static char *
build_env_string (char** envp)
{
  return compose_string (envp, '\0');
}

static HANDLE
spawn_program (char* name, char** argv, char** envp)
{
  PROCESS_INFORMATION pi = { NULL, 0, 0, 0 };
  STARTUPINFOA si;
  char *arg_string, *env_string;
  BOOL result;

#ifdef DBUS_WINCE
  if (argv && argv[0])
    arg_string = build_commandline (argv + 1);
  else
    arg_string = NULL;
#else
  arg_string = build_commandline (argv);
#endif
  if (!arg_string)
    return INVALID_HANDLE_VALUE;

  env_string = build_env_string(envp);

  memset (&si, 0, sizeof (si));
  si.cb = sizeof (si);
#ifdef DBUS_WINCE
  result = CreateProcessA (name, arg_string, NULL, NULL, FALSE, 0,
#else
  result = CreateProcessA (NULL, arg_string, NULL, NULL, FALSE, 0,
#endif
			   (LPVOID)env_string, NULL, &si, &pi);
  free (arg_string);
  if (env_string)
    free (env_string);

  if (!result)
    return INVALID_HANDLE_VALUE;

  CloseHandle (pi.hThread);
  return pi.hProcess;
}


static DWORD __stdcall
babysitter (void *parameter)
{
  DBusBabysitter *sitter = (DBusBabysitter *) parameter;
  int fd;
  PING();
  _dbus_babysitter_ref (sitter);

  if (sitter->child_setup)
    {
      PING();
      (*sitter->child_setup) (sitter->user_data);
    }

  _dbus_verbose ("babysitter: spawning %s\n", sitter->executable);

  PING();
  sitter->child_handle = spawn_program (sitter->executable,
					sitter->argv, sitter->envp);

  PING();
  if (sitter->child_handle == (HANDLE) -1)
    {
      sitter->child_handle = NULL;
      sitter->have_spawn_errno = TRUE;
      sitter->spawn_errno = GetLastError();
    }
  
  PING();
  SetEvent (sitter->start_sync_event);

  if (sitter->child_handle != NULL)
    {
      int ret;
      DWORD status;

      PING();
      WaitForSingleObject (sitter->child_handle, INFINITE);

      PING();
      ret = GetExitCodeProcess (sitter->child_handle, &status);

      sitter->child_status = status;
      sitter->have_child_status = TRUE;

      CloseHandle (sitter->child_handle);
      sitter->child_handle = NULL;
    }

#ifdef DBUS_BUILD_TESTS
  SetEvent (sitter->end_sync_event);
#endif

  PING();
  send (sitter->socket_to_main, " ", 1, 0);

  _dbus_babysitter_unref (sitter);

  return 0;
}

dbus_bool_t
_dbus_spawn_async_with_babysitter (DBusBabysitter           **sitter_p,
                                   char                     **argv,
                                   char                     **envp,
                                   DBusSpawnChildSetupFunc    child_setup,
                                   void                      *user_data,
                                   DBusError                 *error)
{
  DBusBabysitter *sitter;
  HANDLE sitter_thread;
  DWORD sitter_thread_id;
  
  _DBUS_ASSERT_ERROR_IS_CLEAR (error);

  *sitter_p = NULL;

  PING();
  sitter = _dbus_babysitter_new ();
  if (sitter == NULL)
    {
      _DBUS_SET_OOM (error);
      return FALSE;
    }

  sitter->child_setup = child_setup;
  sitter->user_data = user_data;

  sitter->executable = _dbus_strdup (argv[0]);
  if (sitter->executable == NULL)
    {
      _DBUS_SET_OOM (error);
      goto out0;
    }

  PING();
  if (!_dbus_full_duplex_pipe (&sitter->socket_to_babysitter,
                               &sitter->socket_to_main,
                               FALSE, error))
    goto out0;

  sitter->sitter_watch = _dbus_watch_new (sitter->socket_to_babysitter,
                                          DBUS_WATCH_READABLE,
                                          TRUE, handle_watch, sitter, NULL);
  PING();
  if (sitter->sitter_watch == NULL)
    {
      _DBUS_SET_OOM (error);
      goto out0;
    }

  PING();
  if (!_dbus_watch_list_add_watch (sitter->watches,  sitter->sitter_watch))
    {
      _DBUS_SET_OOM (error);
      goto out0;
    }

  sitter->argc = protect_argv (argv, &sitter->argv);
  if (sitter->argc == -1)
    {
      _DBUS_SET_OOM (error);
      goto out0;
    }
  sitter->envp = envp;

  PING();
  sitter_thread = (HANDLE) CreateThread (NULL, 0, babysitter,
                  sitter, 0, &sitter_thread_id);

  if (sitter_thread == 0)
    {
      PING();
      dbus_set_error_const (error, DBUS_ERROR_SPAWN_FORK_FAILED,
                            "Failed to create new thread");
      goto out0;
    }
  CloseHandle (sitter_thread);

  PING();
  WaitForSingleObject (sitter->start_sync_event, INFINITE);

  PING();
  if (sitter_p != NULL)
    *sitter_p = sitter;
  else
    _dbus_babysitter_unref (sitter);

  _DBUS_ASSERT_ERROR_IS_CLEAR (error);

  PING();
  return TRUE;

out0:
  _dbus_babysitter_unref (sitter);

  return FALSE;
}

#ifdef DBUS_BUILD_TESTS

#define LIVE_CHILDREN(sitter) ((sitter)->child_handle != NULL)

static void
_dbus_babysitter_block_for_child_exit (DBusBabysitter *sitter)
{
  if (sitter->child_handle == NULL)
    return;

  WaitForSingleObject (sitter->end_sync_event, INFINITE);
}

static dbus_bool_t
check_spawn_nonexistent (void *data)
{
  char *argv[4] = { NULL, NULL, NULL, NULL };
  DBusBabysitter *sitter;
  DBusError error;

  sitter = NULL;

  dbus_error_init (&error);

  /*** Test launching nonexistent binary */

  argv[0] = "/this/does/not/exist/32542sdgafgafdg";
  if (_dbus_spawn_async_with_babysitter (&sitter, argv, NULL,
                                         NULL, NULL,
                                         &error))
    {
      _dbus_babysitter_block_for_child_exit (sitter);
      _dbus_babysitter_set_child_exit_error (sitter, &error);
    }

  if (sitter)
    _dbus_babysitter_unref (sitter);

  if (!dbus_error_is_set (&error))
    {
      _dbus_warn ("Did not get an error launching nonexistent executable\n");
      return FALSE;
    }

  if (!(dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY) ||
        dbus_error_has_name (&error, DBUS_ERROR_SPAWN_EXEC_FAILED)))
    {
      _dbus_warn ("Not expecting error when launching nonexistent executable: %s: %s\n",
                  error.name, error.message);
      dbus_error_free (&error);
      return FALSE;
    }

  dbus_error_free (&error);

  return TRUE;
}

static dbus_bool_t
check_spawn_segfault (void *data)
{
  char *argv[4] = { NULL, NULL, NULL, NULL };
  DBusBabysitter *sitter;
  DBusError error;

  sitter = NULL;

  dbus_error_init (&error);

  /*** Test launching segfault binary */

  argv[0] = TEST_SEGFAULT_BINARY;
  if (_dbus_spawn_async_with_babysitter (&sitter, argv, NULL,
                                         NULL, NULL,
                                         &error))
    {
      _dbus_babysitter_block_for_child_exit (sitter);
      _dbus_babysitter_set_child_exit_error (sitter, &error);
    }

  if (sitter)
    _dbus_babysitter_unref (sitter);

  if (!dbus_error_is_set (&error))
    {
      _dbus_warn ("Did not get an error launching segfaulting binary\n");
      return FALSE;
    }

  if (!(dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY) ||
        dbus_error_has_name (&error, DBUS_ERROR_SPAWN_CHILD_EXITED)))
    {
      _dbus_warn ("Not expecting error when launching segfaulting executable: %s: %s\n",
                  error.name, error.message);
      dbus_error_free (&error);
      return FALSE;
    }

  dbus_error_free (&error);

  return TRUE;
}

static dbus_bool_t
check_spawn_exit (void *data)
{
  char *argv[4] = { NULL, NULL, NULL, NULL };
  DBusBabysitter *sitter;
  DBusError error;

  sitter = NULL;

  dbus_error_init (&error);

  /*** Test launching exit failure binary */

  argv[0] = TEST_EXIT_BINARY;
  if (_dbus_spawn_async_with_babysitter (&sitter, argv, NULL,
                                         NULL, NULL,
                                         &error))
    {
      _dbus_babysitter_block_for_child_exit (sitter);
      _dbus_babysitter_set_child_exit_error (sitter, &error);
    }

  if (sitter)
    _dbus_babysitter_unref (sitter);

  if (!dbus_error_is_set (&error))
    {
      _dbus_warn ("Did not get an error launching binary that exited with failure code\n");
      return FALSE;
    }

  if (!(dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY) ||
        dbus_error_has_name (&error, DBUS_ERROR_SPAWN_CHILD_EXITED)))
    {
      _dbus_warn ("Not expecting error when launching exiting executable: %s: %s\n",
                  error.name, error.message);
      dbus_error_free (&error);
      return FALSE;
    }

  dbus_error_free (&error);

  return TRUE;
}

static dbus_bool_t
check_spawn_and_kill (void *data)
{
  char *argv[4] = { NULL, NULL, NULL, NULL };
  DBusBabysitter *sitter;
  DBusError error;

  sitter = NULL;

  dbus_error_init (&error);

  /*** Test launching sleeping binary then killing it */

  argv[0] = TEST_SLEEP_FOREVER_BINARY;
  if (_dbus_spawn_async_with_babysitter (&sitter, argv, NULL,
                                         NULL, NULL,
                                         &error))
    {
      _dbus_babysitter_kill_child (sitter);

      _dbus_babysitter_block_for_child_exit (sitter);

      _dbus_babysitter_set_child_exit_error (sitter, &error);
    }

  if (sitter)
    _dbus_babysitter_unref (sitter);

  if (!dbus_error_is_set (&error))
    {
      _dbus_warn ("Did not get an error after killing spawned binary\n");
      return FALSE;
    }

  if (!(dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY) ||
        dbus_error_has_name (&error, DBUS_ERROR_SPAWN_CHILD_EXITED)))
    {
      _dbus_warn ("Not expecting error when killing executable: %s: %s\n",
                  error.name, error.message);
      dbus_error_free (&error);
      return FALSE;
    }

  dbus_error_free (&error);

  return TRUE;
}

dbus_bool_t
_dbus_spawn_test (const char *test_data_dir)
{
  if (!_dbus_test_oom_handling ("spawn_nonexistent",
                                check_spawn_nonexistent,
                                NULL))
    return FALSE;

  /* Don't run the obnoxious segfault test by default,
   * it's a pain to have to click all those error boxes.
   */
  if (getenv ("DO_SEGFAULT_TEST"))
    if (!_dbus_test_oom_handling ("spawn_segfault",
                                  check_spawn_segfault,
                                  NULL))
      return FALSE;

  if (!_dbus_test_oom_handling ("spawn_exit",
                                check_spawn_exit,
                                NULL))
    return FALSE;

  if (!_dbus_test_oom_handling ("spawn_and_kill",
                                check_spawn_and_kill,
                                NULL))
    return FALSE;

  return TRUE;
}
#endif
