Merge branch 'bug8746_v2_squashed'

Conflicts:
	src/common/include.am
This commit is contained in:
Nick Mathewson 2014-06-14 11:46:38 -04:00
commit a7cafb1ea9
14 changed files with 437 additions and 59 deletions

4
changes/bug8746 Normal file
View File

@ -0,0 +1,4 @@
o Major bugfixes:
- When managing pluggable transports, use OS notification facilities to
learn if they have crashed, and do not attempt to kill any process
that has already exited. Fix for bug 8746; bugfix on 0.2.3.6-alpha.

View File

@ -367,6 +367,7 @@ AC_CHECK_FUNCS(
sysconf \ sysconf \
sysctl \ sysctl \
uname \ uname \
usleep \
vasprintf \ vasprintf \
_vscprintf _vscprintf
) )
@ -898,6 +899,7 @@ AC_CHECK_HEADERS(
sys/param.h \ sys/param.h \
sys/prctl.h \ sys/prctl.h \
sys/resource.h \ sys/resource.h \
sys/select.h \
sys/socket.h \ sys/socket.h \
sys/sysctl.h \ sys/sysctl.h \
sys/syslimits.h \ sys/syslimits.h \

View File

@ -114,6 +114,12 @@
/* Only use the linux prctl; the IRIX prctl is totally different */ /* Only use the linux prctl; the IRIX prctl is totally different */
#include <sys/prctl.h> #include <sys/prctl.h>
#endif #endif
#ifdef TOR_UNIT_TESTS
#if !defined(HAVE_USLEEP) && defined(HAVE_SYS_SELECT_H)
/* as fallback implementation for tor_sleep_msec */
#include <sys/select.h>
#endif
#endif
#include "torlog.h" #include "torlog.h"
#include "util.h" #include "util.h"
@ -3556,3 +3562,23 @@ get_total_system_memory(size_t *mem_out)
return 0; return 0;
} }
#ifdef TOR_UNIT_TESTS
/** Delay for <b>msec</b> milliseconds. Only used in tests. */
void
tor_sleep_msec(int msec)
{
#ifdef _WIN32
Sleep(msec);
#elif defined(HAVE_USLEEP)
sleep(msec / 1000);
/* Some usleep()s hate sleeping more than 1 sec */
usleep((msec % 1000) * 1000);
#elif defined(HAVE_SYS_SELECT_H)
struct timeval tv = { msec / 1000, (msec % 1000) * 1000};
select(0, NULL, NULL, NULL, &tv);
#else
sleep(CEIL_DIV(msec, 1000));
#endif
}
#endif

View File

@ -749,6 +749,10 @@ char *format_win32_error(DWORD err);
#endif #endif
#ifdef TOR_UNIT_TESTS
void tor_sleep_msec(int msec);
#endif
#ifdef COMPAT_PRIVATE #ifdef COMPAT_PRIVATE
#if !defined(HAVE_SOCKETPAIR) || defined(_WIN32) || defined(TOR_UNIT_TESTS) #if !defined(HAVE_SOCKETPAIR) || defined(_WIN32) || defined(TOR_UNIT_TESTS)
#define NEED_ERSATZ_SOCKETPAIR #define NEED_ERSATZ_SOCKETPAIR

View File

@ -64,9 +64,9 @@ LIBOR_A_SOURCES = \
src/common/di_ops.c \ src/common/di_ops.c \
src/common/log.c \ src/common/log.c \
src/common/memarea.c \ src/common/memarea.c \
src/common/procmon.c \
src/common/util.c \ src/common/util.c \
src/common/util_codedigest.c \ src/common/util_codedigest.c \
src/common/util_process.c \
src/common/sandbox.c \ src/common/sandbox.c \
src/ext/csiphash.c \ src/ext/csiphash.c \
$(libor_extra_source) \ $(libor_extra_source) \
@ -80,7 +80,9 @@ LIBOR_CRYPTO_A_SOURCES = \
src/common/tortls.c \ src/common/tortls.c \
$(libcrypto_extra_source) $(libcrypto_extra_source)
LIBOR_EVENT_A_SOURCES = src/common/compat_libevent.c LIBOR_EVENT_A_SOURCES = \
src/common/compat_libevent.c \
src/common/procmon.c
src_common_libor_a_SOURCES = $(LIBOR_A_SOURCES) src_common_libor_a_SOURCES = $(LIBOR_A_SOURCES)
src_common_libor_crypto_a_SOURCES = $(LIBOR_CRYPTO_A_SOURCES) src_common_libor_crypto_a_SOURCES = $(LIBOR_CRYPTO_A_SOURCES)
@ -119,6 +121,7 @@ COMMONHEADERS = \
src/common/torlog.h \ src/common/torlog.h \
src/common/tortls.h \ src/common/tortls.h \
src/common/util.h \ src/common/util.h \
src/common/util_process.h \
$(libor_mempool_header) $(libor_mempool_header)
noinst_HEADERS+= $(COMMONHEADERS) noinst_HEADERS+= $(COMMONHEADERS)

View File

@ -162,6 +162,7 @@ tor_validate_process_specifier(const char *process_spec,
return parse_process_specifier(process_spec, &ppspec, msg); return parse_process_specifier(process_spec, &ppspec, msg);
} }
/* XXXX we should use periodic_timer_new() for this stuff */
#ifdef HAVE_EVENT2_EVENT_H #ifdef HAVE_EVENT2_EVENT_H
#define PERIODIC_TIMER_FLAGS EV_PERSIST #define PERIODIC_TIMER_FLAGS EV_PERSIST
#else #else

View File

@ -26,6 +26,7 @@
#include "address.h" #include "address.h"
#include "sandbox.h" #include "sandbox.h"
#include "backtrace.h" #include "backtrace.h"
#include "util_process.h"
#ifdef _WIN32 #ifdef _WIN32
#include <io.h> #include <io.h>
@ -3629,13 +3630,7 @@ tor_terminate_process(process_handle_t *process_handle)
{ {
#ifdef _WIN32 #ifdef _WIN32
if (tor_get_exit_code(process_handle, 0, NULL) == PROCESS_EXIT_RUNNING) { if (tor_get_exit_code(process_handle, 0, NULL) == PROCESS_EXIT_RUNNING) {
HANDLE handle; HANDLE handle = process_handle->pid.hProcess;
/* If the signal is outside of what GenerateConsoleCtrlEvent can use,
attempt to open and terminate the process. */
handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE,
process_handle->pid.dwProcessId);
if (!handle)
return -1;
if (!TerminateProcess(handle, 0)) if (!TerminateProcess(handle, 0))
return -1; return -1;
@ -3643,7 +3638,10 @@ tor_terminate_process(process_handle_t *process_handle)
return 0; return 0;
} }
#else /* Unix */ #else /* Unix */
return kill(process_handle->pid, SIGTERM); if (process_handle->waitpid_cb) {
/* We haven't got a waitpid yet, so we can just kill off the process. */
return kill(process_handle->pid, SIGTERM);
}
#endif #endif
return -1; return -1;
@ -3692,6 +3690,23 @@ process_handle_new(void)
return out; return out;
} }
#ifndef _WIN32
/** Invoked when a process that we've launched via tor_spawn_background() has
* been found to have terminated.
*/
static void
process_handle_waitpid_cb(int status, void *arg)
{
process_handle_t *process_handle = arg;
process_handle->waitpid_exit_status = status;
clear_waitpid_callback(process_handle->waitpid_cb);
if (process_handle->status == PROCESS_STATUS_RUNNING)
process_handle->status = PROCESS_STATUS_NOTRUNNING;
process_handle->waitpid_cb = 0;
}
#endif
/** /**
* @name child-process states * @name child-process states
* *
@ -4008,6 +4023,10 @@ tor_spawn_background(const char *const filename, const char **argv,
strerror(errno)); strerror(errno));
} }
process_handle->waitpid_cb = set_waitpid_callback(pid,
process_handle_waitpid_cb,
process_handle);
process_handle->stderr_pipe = stderr_pipe[0]; process_handle->stderr_pipe = stderr_pipe[0];
retval = close(stderr_pipe[1]); retval = close(stderr_pipe[1]);
@ -4072,6 +4091,8 @@ tor_process_handle_destroy,(process_handle_t *process_handle,
if (process_handle->stderr_handle) if (process_handle->stderr_handle)
fclose(process_handle->stderr_handle); fclose(process_handle->stderr_handle);
clear_waitpid_callback(process_handle->waitpid_cb);
#endif #endif
memset(process_handle, 0x0f, sizeof(process_handle_t)); memset(process_handle, 0x0f, sizeof(process_handle_t));
@ -4089,7 +4110,7 @@ tor_process_handle_destroy,(process_handle_t *process_handle,
* probably not work in Tor, because waitpid() is called in main.c to reap any * probably not work in Tor, because waitpid() is called in main.c to reap any
* terminated child processes.*/ * terminated child processes.*/
int int
tor_get_exit_code(const process_handle_t *process_handle, tor_get_exit_code(process_handle_t *process_handle,
int block, int *exit_code) int block, int *exit_code)
{ {
#ifdef _WIN32 #ifdef _WIN32
@ -4129,7 +4150,20 @@ tor_get_exit_code(const process_handle_t *process_handle,
int stat_loc; int stat_loc;
int retval; int retval;
retval = waitpid(process_handle->pid, &stat_loc, block?0:WNOHANG); if (process_handle->waitpid_cb) {
/* We haven't processed a SIGCHLD yet. */
retval = waitpid(process_handle->pid, &stat_loc, block?0:WNOHANG);
if (retval == process_handle->pid) {
clear_waitpid_callback(process_handle->waitpid_cb);
process_handle->waitpid_cb = NULL;
process_handle->waitpid_exit_status = stat_loc;
}
} else {
/* We already got a SIGCHLD for this process, and handled it. */
retval = process_handle->pid;
stat_loc = process_handle->waitpid_exit_status;
}
if (!block && 0 == retval) { if (!block && 0 == retval) {
/* Process has not exited */ /* Process has not exited */
return PROCESS_EXIT_RUNNING; return PROCESS_EXIT_RUNNING;

View File

@ -446,6 +446,7 @@ void set_environment_variable_in_smartlist(struct smartlist_t *env_vars,
#define PROCESS_STATUS_ERROR -1 #define PROCESS_STATUS_ERROR -1
#ifdef UTIL_PRIVATE #ifdef UTIL_PRIVATE
struct waitpid_callback_t;
/** Structure to represent the state of a process with which Tor is /** Structure to represent the state of a process with which Tor is
* communicating. The contents of this structure are private to util.c */ * communicating. The contents of this structure are private to util.c */
struct process_handle_t { struct process_handle_t {
@ -461,6 +462,12 @@ struct process_handle_t {
FILE *stdout_handle; FILE *stdout_handle;
FILE *stderr_handle; FILE *stderr_handle;
pid_t pid; pid_t pid;
/** If the process has not given us a SIGCHLD yet, this has the
* waitpid_callback_t that gets invoked once it has. Otherwise this
* contains NULL. */
struct waitpid_callback_t *waitpid_cb;
/** The exit status reported by waitpid. */
int waitpid_exit_status;
#endif // _WIN32 #endif // _WIN32
}; };
#endif #endif
@ -469,7 +476,7 @@ struct process_handle_t {
#define PROCESS_EXIT_RUNNING 1 #define PROCESS_EXIT_RUNNING 1
#define PROCESS_EXIT_EXITED 0 #define PROCESS_EXIT_EXITED 0
#define PROCESS_EXIT_ERROR -1 #define PROCESS_EXIT_ERROR -1
int tor_get_exit_code(const process_handle_t *process_handle, int tor_get_exit_code(process_handle_t *process_handle,
int block, int *exit_code); int block, int *exit_code);
int tor_split_lines(struct smartlist_t *sl, char *buf, int len); int tor_split_lines(struct smartlist_t *sl, char *buf, int len);
#ifdef _WIN32 #ifdef _WIN32

157
src/common/util_process.c Normal file
View File

@ -0,0 +1,157 @@
/* Copyright (c) 2003-2004, Roger Dingledine
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
* Copyright (c) 2007-2013, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
* \file util_process.c
* \brief utility functions for launching processes and checking their
* status. These functions are kept separately from procmon so that they
* won't require linking against libevent.
**/
#include "orconfig.h"
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef HAVE_SYS_WAIT_H
#include <sys/wait.h>
#endif
#include "compat.h"
#include "util.h"
#include "torlog.h"
#include "util_process.h"
#include "ht.h"
/* ================================================== */
/* Convenience structures for handlers for waitpid().
*
* The tor_process_monitor*() code above doesn't use them, since it is for
* monitoring a non-child process.
*/
#ifndef _WIN32
/** Mapping from a PID to a userfn/userdata pair. */
struct waitpid_callback_t {
HT_ENTRY(waitpid_callback_t) node;
pid_t pid;
void (*userfn)(int, void *userdata);
void *userdata;
unsigned running;
};
static INLINE unsigned int
process_map_entry_hash_(const waitpid_callback_t *ent)
{
return (unsigned) ent->pid;
}
static INLINE unsigned int
process_map_entries_eq_(const waitpid_callback_t *a, const waitpid_callback_t *b)
{
return a->pid == b->pid;
}
static HT_HEAD(process_map, waitpid_callback_t) process_map = HT_INITIALIZER();
HT_PROTOTYPE(process_map, waitpid_callback_t, node, process_map_entry_hash_,
process_map_entries_eq_);
HT_GENERATE(process_map, waitpid_callback_t, node, process_map_entry_hash_,
process_map_entries_eq_, 0.6, malloc, realloc, free);
/**
* Begin monitoring the child pid <b>pid</b> to see if we get a SIGCHLD for
* it. If we eventually do, call <b>fn</b>, passing it the exit status (as
* yielded by waitpid) and the pointer <b>arg</b>.
*
* To cancel this, or clean up after it has triggered, call
* clear_waitpid_callback().
*/
waitpid_callback_t *
set_waitpid_callback(pid_t pid, void (*fn)(int, void *), void *arg)
{
waitpid_callback_t *old_ent;
waitpid_callback_t *ent = tor_malloc_zero(sizeof(waitpid_callback_t));
ent->pid = pid;
ent->userfn = fn;
ent->userdata = arg;
ent->running = 1;
old_ent = HT_REPLACE(process_map, &process_map, ent);
if (old_ent) {
log_warn(LD_BUG, "Replaced a waitpid monitor on pid %u. That should be "
"impossible.", (unsigned) pid);
old_ent->running = 0;
}
return ent;
}
/**
* Cancel a waitpid_callback_t, or clean up after one has triggered. Releases
* all storage held by <b>ent</b>.
*/
void
clear_waitpid_callback(waitpid_callback_t *ent)
{
waitpid_callback_t *old_ent;
if (ent == NULL)
return;
if (ent->running) {
old_ent = HT_REMOVE(process_map, &process_map, ent);
if (old_ent != ent) {
log_warn(LD_BUG, "Couldn't remove waitpid monitor for pid %u.",
(unsigned) ent->pid);
return;
}
}
tor_free(ent);
}
/** Helper: find the callack for <b>pid</b>; if there is one, run it,
* reporting the exit status as <b>status</b>. */
static void
notify_waitpid_callback_by_pid(pid_t pid, int status)
{
waitpid_callback_t search, *ent;
search.pid = pid;
ent = HT_REMOVE(process_map, &process_map, &search);
if (!ent || !ent->running) {
log_info(LD_GENERAL, "Child process %u has exited; no callback was "
"registered", (unsigned)pid);
return;
}
log_info(LD_GENERAL, "Child process %u has exited; running callback.",
(unsigned)pid);
ent->running = 0;
ent->userfn(status, ent->userdata);
}
/** Use waitpid() to wait for all children that have exited, and invoke any
* callbacks registered for them. */
void
notify_pending_waitpid_callbacks(void)
{
/* I was going to call this function reap_zombie_children(), but
* that makes it sound way more exciting than it really is. */
pid_t child;
int status = 0;
while ((child = waitpid(-1, &status, WNOHANG)) > 0) {
notify_waitpid_callback_by_pid(child, status);
status = 0; /* should be needless */
}
}
#endif

25
src/common/util_process.h Normal file
View File

@ -0,0 +1,25 @@
/* Copyright (c) 2011-2013, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
* \file util_process.h
* \brief Headers for util_process.c
**/
#ifndef TOR_UTIL_PROCESS_H
#define TOR_UTIL_PROCESS_H
#ifndef _WIN32
/** A callback structure waiting for us to get a SIGCHLD informing us that a
* PID has been closed. Created by set_waitpid_callback. Cancelled or cleaned-
* up from clear_waitpid_callback(). Do not access outside of the main thread;
* do not access from inside a signal handler. */
typedef struct waitpid_callback_t waitpid_callback_t;
waitpid_callback_t *set_waitpid_callback(pid_t pid,
void (*fn)(int, void *), void *arg);
void clear_waitpid_callback(waitpid_callback_t *ent);
void notify_pending_waitpid_callbacks(void);
#endif
#endif

View File

@ -54,6 +54,7 @@
#include "routerparse.h" #include "routerparse.h"
#include "statefile.h" #include "statefile.h"
#include "status.h" #include "status.h"
#include "util_process.h"
#include "ext_orport.h" #include "ext_orport.h"
#ifdef USE_DMALLOC #ifdef USE_DMALLOC
#include <dmalloc.h> #include <dmalloc.h>
@ -2109,8 +2110,7 @@ process_signal(uintptr_t sig)
break; break;
#ifdef SIGCHLD #ifdef SIGCHLD
case SIGCHLD: case SIGCHLD:
while (waitpid(-1,NULL,WNOHANG) > 0) ; /* keep reaping until no more notify_pending_waitpid_callbacks();
zombies */
break; break;
#endif #endif
case SIGNEWNYM: { case SIGNEWNYM: {

View File

@ -42,9 +42,6 @@
#include <sys/param.h> /* FreeBSD needs this to know what version it is */ #include <sys/param.h> /* FreeBSD needs this to know what version it is */
#endif #endif
#include "torint.h" #include "torint.h"
#ifdef HAVE_SYS_WAIT_H
#include <sys/wait.h>
#endif
#ifdef HAVE_SYS_FCNTL_H #ifdef HAVE_SYS_FCNTL_H
#include <sys/fcntl.h> #include <sys/fcntl.h>
#endif #endif

View File

@ -9,6 +9,13 @@
#else #else
#include <unistd.h> #include <unistd.h>
#endif #endif
#include <string.h>
#ifdef _WIN32
#define SLEEP(sec) Sleep((sec)*1000)
#else
#define SLEEP(sec) sleep(sec)
#endif
/** Trivial test program which prints out its command line arguments so we can /** Trivial test program which prints out its command line arguments so we can
* check if tor_spawn_background() works */ * check if tor_spawn_background() works */
@ -16,27 +23,38 @@ int
main(int argc, char **argv) main(int argc, char **argv)
{ {
int i; int i;
int delay = 1;
int fast = 0;
if (argc > 1) {
if (!strcmp(argv[1], "--hang")) {
delay = 60;
} else if (!strcmp(argv[1], "--fast")) {
fast = 1;
delay = 0;
}
}
fprintf(stdout, "OUT\n"); fprintf(stdout, "OUT\n");
fprintf(stderr, "ERR\n"); fprintf(stderr, "ERR\n");
for (i = 1; i < argc; i++) for (i = 1; i < argc; i++)
fprintf(stdout, "%s\n", argv[i]); fprintf(stdout, "%s\n", argv[i]);
fprintf(stdout, "SLEEPING\n"); if (!fast)
fprintf(stdout, "SLEEPING\n");
/* We need to flush stdout so that test_util_spawn_background_partial_read() /* We need to flush stdout so that test_util_spawn_background_partial_read()
succeed. Otherwise ReadFile() will get the entire output in one */ succeed. Otherwise ReadFile() will get the entire output in one */
// XXX: Can we make stdio flush on newline? // XXX: Can we make stdio flush on newline?
fflush(stdout); fflush(stdout);
#ifdef _WIN32 if (!fast)
Sleep(1000); SLEEP(1);
#else
sleep(1);
#endif
fprintf(stdout, "DONE\n"); fprintf(stdout, "DONE\n");
#ifdef _WIN32 fflush(stdout);
Sleep(1000); if (fast)
#else return 0;
sleep(1);
#endif while (--delay) {
SLEEP(1);
}
return 0; return 0;
} }

View File

@ -16,6 +16,7 @@
#include "mempool.h" #include "mempool.h"
#endif /* ENABLE_MEMPOOLS */ #endif /* ENABLE_MEMPOOLS */
#include "memarea.h" #include "memarea.h"
#include "util_process.h"
#ifdef _WIN32 #ifdef _WIN32
#include <tchar.h> #include <tchar.h>
@ -2538,6 +2539,19 @@ test_util_fgets_eagain(void *ptr)
} }
#endif #endif
#ifndef BUILDDIR
#define BUILDDIR "."
#endif
#ifdef _WIN32
#define notify_pending_waitpid_callbacks() STMT_NIL
#define TEST_CHILD "test-child.exe"
#define EOL "\r\n"
#else
#define TEST_CHILD (BUILDDIR "/src/test/test-child")
#define EOL "\n"
#endif
/** Helper function for testing tor_spawn_background */ /** Helper function for testing tor_spawn_background */
static void static void
run_util_spawn_background(const char *argv[], const char *expected_out, run_util_spawn_background(const char *argv[], const char *expected_out,
@ -2557,19 +2571,28 @@ run_util_spawn_background(const char *argv[], const char *expected_out,
status = tor_spawn_background(argv[0], argv, NULL, &process_handle); status = tor_spawn_background(argv[0], argv, NULL, &process_handle);
#endif #endif
notify_pending_waitpid_callbacks();
test_eq(expected_status, status); test_eq(expected_status, status);
if (status == PROCESS_STATUS_ERROR) if (status == PROCESS_STATUS_ERROR) {
tt_ptr_op(process_handle, ==, NULL);
return; return;
}
test_assert(process_handle != NULL); test_assert(process_handle != NULL);
test_eq(expected_status, process_handle->status); test_eq(expected_status, process_handle->status);
#ifndef _WIN32
notify_pending_waitpid_callbacks();
tt_ptr_op(process_handle->waitpid_cb, !=, NULL);
#endif
#ifdef _WIN32 #ifdef _WIN32
test_assert(process_handle->stdout_pipe != INVALID_HANDLE_VALUE); test_assert(process_handle->stdout_pipe != INVALID_HANDLE_VALUE);
test_assert(process_handle->stderr_pipe != INVALID_HANDLE_VALUE); test_assert(process_handle->stderr_pipe != INVALID_HANDLE_VALUE);
#else #else
test_assert(process_handle->stdout_pipe > 0); test_assert(process_handle->stdout_pipe >= 0);
test_assert(process_handle->stderr_pipe > 0); test_assert(process_handle->stderr_pipe >= 0);
#endif #endif
/* Check stdout */ /* Check stdout */
@ -2580,12 +2603,19 @@ run_util_spawn_background(const char *argv[], const char *expected_out,
test_eq(strlen(expected_out), pos); test_eq(strlen(expected_out), pos);
test_streq(expected_out, stdout_buf); test_streq(expected_out, stdout_buf);
notify_pending_waitpid_callbacks();
/* Check it terminated correctly */ /* Check it terminated correctly */
retval = tor_get_exit_code(process_handle, 1, &exit_code); retval = tor_get_exit_code(process_handle, 1, &exit_code);
test_eq(PROCESS_EXIT_EXITED, retval); test_eq(PROCESS_EXIT_EXITED, retval);
test_eq(expected_exit, exit_code); test_eq(expected_exit, exit_code);
// TODO: Make test-child exit with something other than 0 // TODO: Make test-child exit with something other than 0
#ifndef _WIN32
notify_pending_waitpid_callbacks();
tt_ptr_op(process_handle->waitpid_cb, ==, NULL);
#endif
/* Check stderr */ /* Check stderr */
pos = tor_read_all_from_process_stderr(process_handle, stderr_buf, pos = tor_read_all_from_process_stderr(process_handle, stderr_buf,
sizeof(stderr_buf) - 1); sizeof(stderr_buf) - 1);
@ -2594,6 +2624,8 @@ run_util_spawn_background(const char *argv[], const char *expected_out,
test_streq(expected_err, stderr_buf); test_streq(expected_err, stderr_buf);
test_eq(strlen(expected_err), pos); test_eq(strlen(expected_err), pos);
notify_pending_waitpid_callbacks();
done: done:
if (process_handle) if (process_handle)
tor_process_handle_destroy(process_handle, 1); tor_process_handle_destroy(process_handle, 1);
@ -2603,29 +2635,20 @@ run_util_spawn_background(const char *argv[], const char *expected_out,
static void static void
test_util_spawn_background_ok(void *ptr) test_util_spawn_background_ok(void *ptr)
{ {
#ifdef _WIN32 const char *argv[] = {TEST_CHILD, "--test", NULL};
const char *argv[] = {"test-child.exe", "--test", NULL}; const char *expected_out = "OUT"EOL "--test"EOL "SLEEPING"EOL "DONE" EOL;
const char *expected_out = "OUT\r\n--test\r\nSLEEPING\r\nDONE\r\n"; const char *expected_err = "ERR"EOL;
const char *expected_err = "ERR\r\n";
#else
const char *argv[] = {BUILDDIR "/src/test/test-child", "--test", NULL};
const char *expected_out = "OUT\n--test\nSLEEPING\nDONE\n";
const char *expected_err = "ERR\n";
#endif
(void)ptr; (void)ptr;
run_util_spawn_background(argv, expected_out, expected_err, 0, run_util_spawn_background(argv, expected_out, expected_err, 0,
PROCESS_STATUS_RUNNING); PROCESS_STATUS_RUNNING);
} }
/** Check that failing to find the executable works as expected */ /** Check that failing to find the executable works as expected */
static void static void
test_util_spawn_background_fail(void *ptr) test_util_spawn_background_fail(void *ptr)
{ {
#ifndef BUILDDIR
#define BUILDDIR "."
#endif
const char *argv[] = {BUILDDIR "/src/test/no-such-file", "--test", NULL}; const char *argv[] = {BUILDDIR "/src/test/no-such-file", "--test", NULL};
const char *expected_err = ""; const char *expected_err = "";
char expected_out[1024]; char expected_out[1024];
@ -2646,13 +2669,13 @@ test_util_spawn_background_fail(void *ptr)
"ERR: Failed to spawn background process - code %s\n", code); "ERR: Failed to spawn background process - code %s\n", code);
run_util_spawn_background(argv, expected_out, expected_err, 255, run_util_spawn_background(argv, expected_out, expected_err, 255,
expected_status); expected_status);
} }
/** Test that reading from a handle returns a partial read rather than /** Test that reading from a handle returns a partial read rather than
* blocking */ * blocking */
static void static void
test_util_spawn_background_partial_read(void *ptr) test_util_spawn_background_partial_read_impl(int exit_early)
{ {
const int expected_exit = 0; const int expected_exit = 0;
const int expected_status = PROCESS_STATUS_RUNNING; const int expected_status = PROCESS_STATUS_RUNNING;
@ -2662,22 +2685,22 @@ test_util_spawn_background_partial_read(void *ptr)
process_handle_t *process_handle=NULL; process_handle_t *process_handle=NULL;
int status; int status;
char stdout_buf[100], stderr_buf[100]; char stdout_buf[100], stderr_buf[100];
#ifdef _WIN32
const char *argv[] = {"test-child.exe", "--test", NULL}; const char *argv[] = {TEST_CHILD, "--test", NULL};
const char *expected_out[] = { "OUT\r\n--test\r\nSLEEPING\r\n", const char *expected_out[] = { "OUT" EOL "--test" EOL "SLEEPING" EOL,
"DONE\r\n", "DONE" EOL,
NULL }; NULL };
const char *expected_err = "ERR\r\n"; const char *expected_err = "ERR" EOL;
#else
const char *argv[] = {BUILDDIR "/src/test/test-child", "--test", NULL}; #ifndef _WIN32
const char *expected_out[] = { "OUT\n--test\nSLEEPING\n",
"DONE\n",
NULL };
const char *expected_err = "ERR\n";
int eof = 0; int eof = 0;
#endif #endif
int expected_out_ctr; int expected_out_ctr;
(void)ptr;
if (exit_early) {
argv[1] = "--hang";
expected_out[0] = "OUT"EOL "--hang"EOL "SLEEPING" EOL;
}
/* Start the program */ /* Start the program */
#ifdef _WIN32 #ifdef _WIN32
@ -2713,6 +2736,12 @@ test_util_spawn_background_partial_read(void *ptr)
expected_out_ctr++; expected_out_ctr++;
} }
if (exit_early) {
tor_process_handle_destroy(process_handle, 1);
process_handle = NULL;
goto done;
}
/* The process should have exited without writing more */ /* The process should have exited without writing more */
#ifdef _WIN32 #ifdef _WIN32
pos = tor_read_all_handle(process_handle->stdout_pipe, stdout_buf, pos = tor_read_all_handle(process_handle->stdout_pipe, stdout_buf,
@ -2750,6 +2779,75 @@ test_util_spawn_background_partial_read(void *ptr)
tor_process_handle_destroy(process_handle, 1); tor_process_handle_destroy(process_handle, 1);
} }
static void
test_util_spawn_background_partial_read(void *arg)
{
(void)arg;
test_util_spawn_background_partial_read_impl(0);
}
static void
test_util_spawn_background_exit_early(void *arg)
{
(void)arg;
test_util_spawn_background_partial_read_impl(1);
}
static void
test_util_spawn_background_waitpid_notify(void *arg)
{
int retval, exit_code;
process_handle_t *process_handle=NULL;
int status;
int ms_timer;
const char *argv[] = {TEST_CHILD, "--fast", NULL};
(void) arg;
#ifdef _WIN32
status = tor_spawn_background(NULL, argv, NULL, &process_handle);
#else
status = tor_spawn_background(argv[0], argv, NULL, &process_handle);
#endif
tt_int_op(status, ==, PROCESS_STATUS_RUNNING);
tt_ptr_op(process_handle, !=, NULL);
/* We're not going to look at the stdout/stderr output this time. Instead,
* we're testing whether notify_pending_waitpid_calbacks() can report the
* process exit (on unix) and/or whether tor_get_exit_code() can notice it
* (on windows) */
#ifndef _WIN32
ms_timer = 30*1000;
tt_ptr_op(process_handle->waitpid_cb, !=, NULL);
while (process_handle->waitpid_cb && ms_timer > 0) {
tor_sleep_msec(100);
ms_timer -= 100;
notify_pending_waitpid_callbacks();
}
tt_int_op(ms_timer, >, 0);
tt_ptr_op(process_handle->waitpid_cb, ==, NULL);
#endif
ms_timer = 30*1000;
while (((retval = tor_get_exit_code(process_handle, 0, &exit_code))
== PROCESS_EXIT_RUNNING) && ms_timer > 0) {
tor_sleep_msec(100);
ms_timer -= 100;
}
tt_int_op(ms_timer, >, 0);
tt_int_op(retval, ==, PROCESS_EXIT_EXITED);
done:
tor_process_handle_destroy(process_handle, 1);
}
#undef TEST_CHILD
#undef EOL
/** /**
* Test for format_hex_number_sigsafe() * Test for format_hex_number_sigsafe()
*/ */
@ -3695,6 +3793,8 @@ struct testcase_t util_tests[] = {
UTIL_TEST(spawn_background_ok, 0), UTIL_TEST(spawn_background_ok, 0),
UTIL_TEST(spawn_background_fail, 0), UTIL_TEST(spawn_background_fail, 0),
UTIL_TEST(spawn_background_partial_read, 0), UTIL_TEST(spawn_background_partial_read, 0),
UTIL_TEST(spawn_background_exit_early, 0),
UTIL_TEST(spawn_background_waitpid_notify, 0),
UTIL_TEST(format_hex_number, 0), UTIL_TEST(format_hex_number, 0),
UTIL_TEST(format_dec_number, 0), UTIL_TEST(format_dec_number, 0),
UTIL_TEST(join_win_cmdline, 0), UTIL_TEST(join_win_cmdline, 0),