mirror of
https://gitlab.torproject.org/tpo/core/tor.git
synced 2024-11-11 13:43:47 +01:00
392 lines
12 KiB
C
392 lines
12 KiB
C
/* Copyright (c) 2001-2004, Roger Dingledine.
|
|
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
|
|
* Copyright (c) 2007-2016, The Tor Project, Inc. */
|
|
/* See LICENSE for licensing information */
|
|
|
|
#include "orconfig.h"
|
|
#define UTIL_PRIVATE
|
|
#include "util.h"
|
|
#include "util_process.h"
|
|
#include "crypto.h"
|
|
#include "torlog.h"
|
|
#include "test.h"
|
|
|
|
#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
|
|
|
|
#ifdef _WIN32
|
|
/* I've assumed Windows doesn't have the gap between fork and exec
|
|
* that causes the race condition on unix-like platforms */
|
|
#define MATCH_PROCESS_STATUS(s1,s2) ((s1) == (s2))
|
|
|
|
#else
|
|
/* work around a race condition of the timing of SIGCHLD handler updates
|
|
* to the process_handle's fields, and checks of those fields
|
|
*
|
|
* TODO: Once we can signal failure to exec, change PROCESS_STATUS_RUNNING to
|
|
* PROCESS_STATUS_ERROR (and similarly with *_OR_NOTRUNNING) */
|
|
#define PROCESS_STATUS_RUNNING_OR_NOTRUNNING (PROCESS_STATUS_RUNNING+1)
|
|
#define IS_RUNNING_OR_NOTRUNNING(s) \
|
|
((s) == PROCESS_STATUS_RUNNING || (s) == PROCESS_STATUS_NOTRUNNING)
|
|
/* well, this is ugly */
|
|
#define MATCH_PROCESS_STATUS(s1,s2) \
|
|
( (s1) == (s2) \
|
|
||((s1) == PROCESS_STATUS_RUNNING_OR_NOTRUNNING \
|
|
&& IS_RUNNING_OR_NOTRUNNING(s2)) \
|
|
||((s2) == PROCESS_STATUS_RUNNING_OR_NOTRUNNING \
|
|
&& IS_RUNNING_OR_NOTRUNNING(s1)))
|
|
|
|
#endif // _WIN32
|
|
|
|
/** Helper function for testing tor_spawn_background */
|
|
static void
|
|
run_util_spawn_background(const char *argv[], const char *expected_out,
|
|
const char *expected_err, int expected_exit,
|
|
int expected_status)
|
|
{
|
|
int retval, exit_code;
|
|
ssize_t pos;
|
|
process_handle_t *process_handle=NULL;
|
|
char stdout_buf[100], stderr_buf[100];
|
|
int status;
|
|
|
|
/* Start the program */
|
|
#ifdef _WIN32
|
|
status = tor_spawn_background(NULL, argv, NULL, &process_handle);
|
|
#else
|
|
status = tor_spawn_background(argv[0], argv, NULL, &process_handle);
|
|
#endif
|
|
|
|
notify_pending_waitpid_callbacks();
|
|
|
|
/* the race condition doesn't affect status,
|
|
* because status isn't updated by the SIGCHLD handler,
|
|
* but we still need to handle PROCESS_STATUS_RUNNING_OR_NOTRUNNING */
|
|
tt_assert(MATCH_PROCESS_STATUS(expected_status, status));
|
|
if (status == PROCESS_STATUS_ERROR) {
|
|
tt_ptr_op(process_handle, OP_EQ, NULL);
|
|
return;
|
|
}
|
|
|
|
tt_assert(process_handle != NULL);
|
|
|
|
/* When a spawned process forks, fails, then exits very quickly,
|
|
* (this typically occurs when exec fails)
|
|
* there is a race condition between the SIGCHLD handler
|
|
* updating the process_handle's fields, and this test
|
|
* checking the process status in those fields.
|
|
* The SIGCHLD update can occur before or after the code below executes.
|
|
* This causes intermittent failures in spawn_background_fail(),
|
|
* typically when the machine is under load.
|
|
* We use PROCESS_STATUS_RUNNING_OR_NOTRUNNING to avoid this issue. */
|
|
|
|
/* the race condition affects the change in
|
|
* process_handle->status from RUNNING to NOTRUNNING */
|
|
tt_assert(MATCH_PROCESS_STATUS(expected_status, process_handle->status));
|
|
|
|
#ifndef _WIN32
|
|
notify_pending_waitpid_callbacks();
|
|
/* the race condition affects the change in
|
|
* process_handle->waitpid_cb to NULL,
|
|
* so we skip the check if expected_status is ambiguous,
|
|
* that is, PROCESS_STATUS_RUNNING_OR_NOTRUNNING */
|
|
tt_assert(process_handle->waitpid_cb != NULL
|
|
|| expected_status == PROCESS_STATUS_RUNNING_OR_NOTRUNNING);
|
|
#endif
|
|
|
|
#ifdef _WIN32
|
|
tt_assert(process_handle->stdout_pipe != INVALID_HANDLE_VALUE);
|
|
tt_assert(process_handle->stderr_pipe != INVALID_HANDLE_VALUE);
|
|
tt_assert(process_handle->stdin_pipe != INVALID_HANDLE_VALUE);
|
|
#else
|
|
tt_assert(process_handle->stdout_pipe >= 0);
|
|
tt_assert(process_handle->stderr_pipe >= 0);
|
|
tt_assert(process_handle->stdin_pipe >= 0);
|
|
#endif
|
|
|
|
/* Check stdout */
|
|
pos = tor_read_all_from_process_stdout(process_handle, stdout_buf,
|
|
sizeof(stdout_buf) - 1);
|
|
tt_assert(pos >= 0);
|
|
stdout_buf[pos] = '\0';
|
|
tt_int_op(strlen(expected_out),OP_EQ, pos);
|
|
tt_str_op(expected_out,OP_EQ, stdout_buf);
|
|
|
|
notify_pending_waitpid_callbacks();
|
|
|
|
/* Check it terminated correctly */
|
|
retval = tor_get_exit_code(process_handle, 1, &exit_code);
|
|
tt_int_op(PROCESS_EXIT_EXITED,OP_EQ, retval);
|
|
tt_int_op(expected_exit,OP_EQ, exit_code);
|
|
// TODO: Make test-child exit with something other than 0
|
|
|
|
#ifndef _WIN32
|
|
notify_pending_waitpid_callbacks();
|
|
tt_ptr_op(process_handle->waitpid_cb, OP_EQ, NULL);
|
|
#endif
|
|
|
|
/* Check stderr */
|
|
pos = tor_read_all_from_process_stderr(process_handle, stderr_buf,
|
|
sizeof(stderr_buf) - 1);
|
|
tt_assert(pos >= 0);
|
|
stderr_buf[pos] = '\0';
|
|
tt_str_op(expected_err,OP_EQ, stderr_buf);
|
|
tt_int_op(strlen(expected_err),OP_EQ, pos);
|
|
|
|
notify_pending_waitpid_callbacks();
|
|
|
|
done:
|
|
if (process_handle)
|
|
tor_process_handle_destroy(process_handle, 1);
|
|
}
|
|
|
|
/** Check that we can launch a process and read the output */
|
|
static void
|
|
test_util_spawn_background_ok(void *ptr)
|
|
{
|
|
const char *argv[] = {TEST_CHILD, "--test", NULL};
|
|
const char *expected_out = "OUT"EOL "--test"EOL "SLEEPING"EOL "DONE" EOL;
|
|
const char *expected_err = "ERR"EOL;
|
|
|
|
(void)ptr;
|
|
|
|
run_util_spawn_background(argv, expected_out, expected_err, 0,
|
|
PROCESS_STATUS_RUNNING);
|
|
}
|
|
|
|
/** Check that failing to find the executable works as expected */
|
|
static void
|
|
test_util_spawn_background_fail(void *ptr)
|
|
{
|
|
const char *argv[] = {BUILDDIR "/src/test/no-such-file", "--test", NULL};
|
|
const char *expected_err = "";
|
|
char expected_out[1024];
|
|
char code[32];
|
|
#ifdef _WIN32
|
|
const int expected_status = PROCESS_STATUS_ERROR;
|
|
#else
|
|
/* TODO: Once we can signal failure to exec, set this to be
|
|
* PROCESS_STATUS_RUNNING_OR_ERROR */
|
|
const int expected_status = PROCESS_STATUS_RUNNING_OR_NOTRUNNING;
|
|
#endif
|
|
|
|
memset(expected_out, 0xf0, sizeof(expected_out));
|
|
memset(code, 0xf0, sizeof(code));
|
|
|
|
(void)ptr;
|
|
|
|
tor_snprintf(code, sizeof(code), "%x/%x",
|
|
9 /* CHILD_STATE_FAILEXEC */ , ENOENT);
|
|
tor_snprintf(expected_out, sizeof(expected_out),
|
|
"ERR: Failed to spawn background process - code %s\n", code);
|
|
|
|
run_util_spawn_background(argv, expected_out, expected_err, 255,
|
|
expected_status);
|
|
}
|
|
|
|
/** Test that reading from a handle returns a partial read rather than
|
|
* blocking */
|
|
static void
|
|
test_util_spawn_background_partial_read_impl(int exit_early)
|
|
{
|
|
const int expected_exit = 0;
|
|
const int expected_status = PROCESS_STATUS_RUNNING;
|
|
|
|
int retval, exit_code;
|
|
ssize_t pos = -1;
|
|
process_handle_t *process_handle=NULL;
|
|
int status;
|
|
char stdout_buf[100], stderr_buf[100];
|
|
|
|
const char *argv[] = {TEST_CHILD, "--test", NULL};
|
|
const char *expected_out[] = { "OUT" EOL "--test" EOL "SLEEPING" EOL,
|
|
"DONE" EOL,
|
|
NULL };
|
|
const char *expected_err = "ERR" EOL;
|
|
|
|
#ifndef _WIN32
|
|
int eof = 0;
|
|
#endif
|
|
int expected_out_ctr;
|
|
|
|
if (exit_early) {
|
|
argv[1] = "--hang";
|
|
expected_out[0] = "OUT"EOL "--hang"EOL "SLEEPING" EOL;
|
|
}
|
|
|
|
/* Start the program */
|
|
#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(expected_status,OP_EQ, status);
|
|
tt_assert(process_handle);
|
|
tt_int_op(expected_status,OP_EQ, process_handle->status);
|
|
|
|
/* Check stdout */
|
|
for (expected_out_ctr = 0; expected_out[expected_out_ctr] != NULL;) {
|
|
#ifdef _WIN32
|
|
pos = tor_read_all_handle(process_handle->stdout_pipe, stdout_buf,
|
|
sizeof(stdout_buf) - 1, NULL);
|
|
#else
|
|
/* Check that we didn't read the end of file last time */
|
|
tt_assert(!eof);
|
|
pos = tor_read_all_handle(process_handle->stdout_handle, stdout_buf,
|
|
sizeof(stdout_buf) - 1, NULL, &eof);
|
|
#endif
|
|
log_info(LD_GENERAL, "tor_read_all_handle() returned %d", (int)pos);
|
|
|
|
/* We would have blocked, keep on trying */
|
|
if (0 == pos)
|
|
continue;
|
|
|
|
tt_assert(pos > 0);
|
|
stdout_buf[pos] = '\0';
|
|
tt_str_op(expected_out[expected_out_ctr],OP_EQ, stdout_buf);
|
|
tt_int_op(strlen(expected_out[expected_out_ctr]),OP_EQ, pos);
|
|
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 */
|
|
#ifdef _WIN32
|
|
pos = tor_read_all_handle(process_handle->stdout_pipe, stdout_buf,
|
|
sizeof(stdout_buf) - 1,
|
|
process_handle);
|
|
tt_int_op(0,OP_EQ, pos);
|
|
#else
|
|
if (!eof) {
|
|
/* We should have got all the data, but maybe not the EOF flag */
|
|
pos = tor_read_all_handle(process_handle->stdout_handle, stdout_buf,
|
|
sizeof(stdout_buf) - 1,
|
|
process_handle, &eof);
|
|
tt_int_op(0,OP_EQ, pos);
|
|
tt_assert(eof);
|
|
}
|
|
/* Otherwise, we got the EOF on the last read */
|
|
#endif
|
|
|
|
/* Check it terminated correctly */
|
|
retval = tor_get_exit_code(process_handle, 1, &exit_code);
|
|
tt_int_op(PROCESS_EXIT_EXITED,OP_EQ, retval);
|
|
tt_int_op(expected_exit,OP_EQ, exit_code);
|
|
|
|
// TODO: Make test-child exit with something other than 0
|
|
|
|
/* Check stderr */
|
|
pos = tor_read_all_from_process_stderr(process_handle, stderr_buf,
|
|
sizeof(stderr_buf) - 1);
|
|
tt_assert(pos >= 0);
|
|
stderr_buf[pos] = '\0';
|
|
tt_str_op(expected_err,OP_EQ, stderr_buf);
|
|
tt_int_op(strlen(expected_err),OP_EQ, pos);
|
|
|
|
done:
|
|
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, OP_EQ, PROCESS_STATUS_RUNNING);
|
|
tt_ptr_op(process_handle, OP_NE, 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, OP_NE, 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, OP_GT, 0);
|
|
tt_ptr_op(process_handle->waitpid_cb, OP_EQ, 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, OP_GT, 0);
|
|
|
|
tt_int_op(retval, OP_EQ, PROCESS_EXIT_EXITED);
|
|
|
|
done:
|
|
tor_process_handle_destroy(process_handle, 1);
|
|
}
|
|
|
|
#undef TEST_CHILD
|
|
#undef EOL
|
|
|
|
#undef MATCH_PROCESS_STATUS
|
|
|
|
#ifndef _WIN32
|
|
#undef PROCESS_STATUS_RUNNING_OR_NOTRUNNING
|
|
#undef IS_RUNNING_OR_NOTRUNNING
|
|
#endif
|
|
|
|
#define UTIL_TEST(name, flags) \
|
|
{ #name, test_util_ ## name, flags, NULL, NULL }
|
|
|
|
struct testcase_t slow_util_tests[] = {
|
|
UTIL_TEST(spawn_background_ok, 0),
|
|
UTIL_TEST(spawn_background_fail, 0),
|
|
UTIL_TEST(spawn_background_partial_read, 0),
|
|
UTIL_TEST(spawn_background_exit_early, 0),
|
|
UTIL_TEST(spawn_background_waitpid_notify, 0),
|
|
END_OF_TESTCASES
|
|
};
|
|
|