blob: 8ac837ed5aa1591fc045971ba4d97a12663e128d [file] [log] [blame]
#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