Delete old process_handle_t code.

This patch removes the old process_handle_t code. Everything should by
now be using the process_t interface.

See: https://bugs.torproject.org/28179
This commit is contained in:
Alexander Færøy 2018-11-26 05:34:30 +01:00 committed by Nick Mathewson
parent 289ed0849d
commit f7d13425fc
12 changed files with 17 additions and 1948 deletions

2
.gitignore vendored
View File

@ -239,7 +239,6 @@ uptime-*.json
/src/test/test
/src/test/test-slow
/src/test/test-bt-cl
/src/test/test-child
/src/test/test-process
/src/test/test-memwipe
/src/test/test-ntor-cl
@ -250,7 +249,6 @@ uptime-*.json
/src/test/test.exe
/src/test/test-slow.exe
/src/test/test-bt-cl.exe
/src/test/test-child.exe
/src/test/test-process.exe
/src/test/test-ntor-cl.exe
/src/test/test-hs-ntor-cl.exe

View File

@ -913,22 +913,6 @@ handle_proxy_line(const char *line, managed_proxy_t *mp)
} else if (!strcmpstart(line, PROTO_LOG)) {
parse_log_line(line);
return;
} else if (!strcmpstart(line, SPAWN_ERROR_MESSAGE)) {
/* managed proxy launch failed: parse error message to learn why. */
int retval, child_state, saved_errno;
retval = tor_sscanf(line, SPAWN_ERROR_MESSAGE "%x/%x",
&child_state, &saved_errno);
if (retval == 2) {
log_warn(LD_GENERAL,
"Could not launch managed proxy executable at '%s' ('%s').",
mp->argv[0], strerror(saved_errno));
} else { /* failed to parse error message */
log_warn(LD_GENERAL,"Could not launch managed proxy executable at '%s'.",
mp->argv[0]);
}
mp->conf_state = PT_PROTO_FAILED_LAUNCH;
return;
}
log_notice(LD_GENERAL, "Unknown line received by managed proxy (%s).", line);

File diff suppressed because it is too large Load Diff

View File

@ -11,124 +11,10 @@
#ifndef TOR_SUBPROCESS_H
#define TOR_SUBPROCESS_H
#include "lib/cc/torint.h"
#include "lib/testsupport/testsupport.h"
#include <stddef.h>
#ifdef _WIN32
#include <windows.h>
#endif
struct smartlist_t;
void tor_disable_spawning_background_processes(void);
typedef struct process_handle_t process_handle_t;
struct process_environment_t;
int tor_spawn_background(const char *const filename, const char **argv,
struct process_environment_t *env,
process_handle_t **process_handle_out);
#define SPAWN_ERROR_MESSAGE "ERR: Failed to spawn background process - code "
/** Status of an I/O stream. */
enum stream_status {
IO_STREAM_OKAY,
IO_STREAM_EAGAIN,
IO_STREAM_TERM,
IO_STREAM_CLOSED
};
const char *stream_status_to_string(enum stream_status stream_status);
enum stream_status get_string_from_pipe(int fd, char *buf, size_t count);
/* Values of process_handle_t.status. */
#define PROCESS_STATUS_NOTRUNNING 0
#define PROCESS_STATUS_RUNNING 1
#define PROCESS_STATUS_ERROR -1
#ifdef SUBPROCESS_PRIVATE
struct waitpid_callback_t;
/** Structure to represent the state of a process with which Tor is
* communicating. The contents of this structure are private to util.c */
struct process_handle_t {
/** One of the PROCESS_STATUS_* values */
int status;
#ifdef _WIN32
HANDLE stdin_pipe;
HANDLE stdout_pipe;
HANDLE stderr_pipe;
PROCESS_INFORMATION pid;
#else /* !(defined(_WIN32)) */
int stdin_pipe;
int stdout_pipe;
int stderr_pipe;
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 /* defined(_WIN32) */
};
#endif /* defined(SUBPROCESS_PRIVATE) */
/* Return values of tor_get_exit_code() */
#define PROCESS_EXIT_RUNNING 1
#define PROCESS_EXIT_EXITED 0
#define PROCESS_EXIT_ERROR -1
int tor_get_exit_code(process_handle_t *process_handle,
int block, int *exit_code);
int tor_split_lines(struct smartlist_t *sl, char *buf, int len);
#ifdef _WIN32
ssize_t tor_read_all_handle(HANDLE h, char *buf, size_t count,
const process_handle_t *process);
#else
ssize_t tor_read_all_handle(int fd, char *buf, size_t count,
const process_handle_t *process,
int *eof);
#endif /* defined(_WIN32) */
ssize_t tor_read_all_from_process_stdout(
const process_handle_t *process_handle, char *buf, size_t count);
ssize_t tor_read_all_from_process_stderr(
const process_handle_t *process_handle, char *buf, size_t count);
char *tor_join_win_cmdline(const char *argv[]);
int tor_process_get_pid(process_handle_t *process_handle);
#ifdef _WIN32
HANDLE tor_process_get_stdout_pipe(process_handle_t *process_handle);
#else
int tor_process_get_stdout_pipe(process_handle_t *process_handle);
#endif
#ifdef _WIN32
MOCK_DECL(struct smartlist_t *, tor_get_lines_from_handle,(HANDLE *handle,
enum stream_status *stream_status));
#else
MOCK_DECL(struct smartlist_t *, tor_get_lines_from_handle,(int fd,
enum stream_status *stream_status));
#endif /* defined(_WIN32) */
int tor_terminate_process(process_handle_t *process_handle);
MOCK_DECL(void, tor_process_handle_destroy,(process_handle_t *process_handle,
int also_terminate_process));
#ifdef SUBPROCESS_PRIVATE
/* Prototypes for private functions only used by util.c (and unit tests) */
#ifndef _WIN32
STATIC int format_helper_exit_status(unsigned char child_state,
int saved_errno, char *hex_errno);
/* Space for hex values of child state, a slash, saved_errno (with
leading minus) and newline (no null) */
#define HEX_ERRNO_SIZE (sizeof(char) * 2 + 1 + \
1 + sizeof(int) * 2 + 1)
#endif /* !defined(_WIN32) */
#endif /* defined(SUBPROCESS_PRIVATE) */
char *tor_join_win_cmdline(const char *argv[]);
#endif
#endif

View File

@ -1,4 +1,4 @@
all: test.exe test-child.exe bench.exe
all: test.exe bench.exe
CFLAGS = /I ..\win32 /I ..\..\..\build-alpha\include /I ..\common /I ..\or \
/I ..\ext
@ -30,8 +30,5 @@ test.exe: $(TEST_OBJECTS)
bench.exe: bench.obj
$(CC) $(CFLAGS) bench.obj $(LIBS) ..\common\*.lib /Fe$@
test-child.exe: test-child.obj
$(CC) $(CFLAGS) test-child.obj /Fe$@
clean:
del *.obj *.lib test.exe bench.exe test-child.exe
del *.obj *.lib test.exe bench.exe

View File

@ -65,7 +65,6 @@ noinst_PROGRAMS+= \
src/test/test \
src/test/test-slow \
src/test/test-memwipe \
src/test/test-child \
src/test/test-process \
src/test/test_workqueue \
src/test/test-switch-id \
@ -204,7 +203,6 @@ src_test_test_slow_SOURCES += \
src/test/test_slow.c \
src/test/test_crypto_slow.c \
src/test/test_process_slow.c \
src/test/test_util_slow.c \
src/test/testing_common.c \
src/test/testing_rsakeys.c \
src/ext/tinytest.c

View File

@ -1,61 +0,0 @@
/* Copyright (c) 2011-2018, The Tor Project, Inc. */
/* See LICENSE for licensing information */
#include "orconfig.h"
#include <stdio.h>
#ifdef _WIN32
#define WINDOWS_LEAN_AND_MEAN
#include <windows.h>
#else
#include <unistd.h>
#endif /* defined(_WIN32) */
#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
* check if tor_spawn_background() works */
int
main(int argc, char **argv)
{
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(stderr, "ERR\n");
for (i = 1; i < argc; i++)
fprintf(stdout, "%s\n", argv[i]);
if (!fast)
fprintf(stdout, "SLEEPING\n");
/* We need to flush stdout so that test_util_spawn_background_partial_read()
succeed. Otherwise ReadFile() will get the entire output in one */
// XXX: Can we make stdio flush on newline?
fflush(stdout);
if (!fast)
SLEEP(1);
fprintf(stdout, "DONE\n");
fflush(stdout);
if (fast)
return 0;
while (--delay) {
SLEEP(1);
}
return 0;
}

View File

@ -271,7 +271,6 @@ extern struct testcase_t x509_tests[];
extern struct testcase_t slow_crypto_tests[];
extern struct testcase_t slow_process_tests[];
extern struct testcase_t slow_util_tests[];
extern struct testgroup_t testgroups[];

View File

@ -117,22 +117,27 @@ test_sigsafe_err(void *arg)
content = read_file_to_str(fn, 0, NULL);
tt_ptr_op(content, OP_NE, NULL);
tor_split_lines(lines, content, (int)strlen(content));
smartlist_split_string(lines, content, "\n", 0, 0);
tt_int_op(smartlist_len(lines), OP_GE, 5);
if (strstr(smartlist_get(lines, 0), "opening new log file"))
if (strstr(smartlist_get(lines, 0), "opening new log file")) {
void *item = smartlist_get(lines, 0);
smartlist_del_keeporder(lines, 0);
tor_free(item);
}
tt_assert(strstr(smartlist_get(lines, 0), "Say, this isn't too cool"));
/* Next line is blank. */
tt_assert(!strcmpstart(smartlist_get(lines, 1), "=============="));
tt_assert(!strcmpstart(smartlist_get(lines, 2), "Minimal."));
/* Next line is blank. */
tt_assert(!strcmpstart(smartlist_get(lines, 3), "=============="));
tt_str_op(smartlist_get(lines, 4), OP_EQ,
tt_str_op(smartlist_get(lines, 1), OP_EQ, "");
tt_assert(!strcmpstart(smartlist_get(lines, 2), "=============="));
tt_assert(!strcmpstart(smartlist_get(lines, 3), "Minimal."));
tt_str_op(smartlist_get(lines, 4), OP_EQ, "");
tt_assert(!strcmpstart(smartlist_get(lines, 5), "=============="));
tt_str_op(smartlist_get(lines, 6), OP_EQ,
"Testing any attempt to manually log from a signal.");
done:
tor_free(content);
SMARTLIST_FOREACH(lines, char *, x, tor_free(x));
smartlist_free(lines);
}

View File

@ -21,7 +21,6 @@
struct testgroup_t testgroups[] = {
{ "slow/crypto/", slow_crypto_tests },
{ "slow/process/", slow_process_tests },
{ "slow/util/", slow_util_tests },
END_OF_GROUPS
};

View File

@ -4301,204 +4301,6 @@ test_util_load_win_lib(void *ptr)
}
#endif /* defined(_WIN32) */
#ifndef _WIN32
static void
clear_hex_errno(char *hex_errno)
{
memset(hex_errno, '\0', HEX_ERRNO_SIZE + 1);
}
static void
test_util_exit_status(void *ptr)
{
/* Leave an extra byte for a \0 so we can do string comparison */
char hex_errno[HEX_ERRNO_SIZE + 1];
int n;
(void)ptr;
clear_hex_errno(hex_errno);
tt_str_op("",OP_EQ, hex_errno);
clear_hex_errno(hex_errno);
n = format_helper_exit_status(0, 0, hex_errno);
tt_str_op("0/0\n",OP_EQ, hex_errno);
tt_int_op(n,OP_EQ, strlen(hex_errno));
#if SIZEOF_INT == 4
clear_hex_errno(hex_errno);
n = format_helper_exit_status(0, 0x7FFFFFFF, hex_errno);
tt_str_op("0/7FFFFFFF\n",OP_EQ, hex_errno);
tt_int_op(n,OP_EQ, strlen(hex_errno));
clear_hex_errno(hex_errno);
n = format_helper_exit_status(0xFF, -0x80000000, hex_errno);
tt_str_op("FF/-80000000\n",OP_EQ, hex_errno);
tt_int_op(n,OP_EQ, strlen(hex_errno));
tt_int_op(n,OP_EQ, HEX_ERRNO_SIZE);
#elif SIZEOF_INT == 8
clear_hex_errno(hex_errno);
n = format_helper_exit_status(0, 0x7FFFFFFFFFFFFFFF, hex_errno);
tt_str_op("0/7FFFFFFFFFFFFFFF\n",OP_EQ, hex_errno);
tt_int_op(n,OP_EQ, strlen(hex_errno));
clear_hex_errno(hex_errno);
n = format_helper_exit_status(0xFF, -0x8000000000000000, hex_errno);
tt_str_op("FF/-8000000000000000\n",OP_EQ, hex_errno);
tt_int_op(n,OP_EQ, strlen(hex_errno));
tt_int_op(n,OP_EQ, HEX_ERRNO_SIZE);
#endif /* SIZEOF_INT == 4 || ... */
clear_hex_errno(hex_errno);
n = format_helper_exit_status(0x7F, 0, hex_errno);
tt_str_op("7F/0\n",OP_EQ, hex_errno);
tt_int_op(n,OP_EQ, strlen(hex_errno));
clear_hex_errno(hex_errno);
n = format_helper_exit_status(0x08, -0x242, hex_errno);
tt_str_op("8/-242\n",OP_EQ, hex_errno);
tt_int_op(n,OP_EQ, strlen(hex_errno));
clear_hex_errno(hex_errno);
tt_str_op("",OP_EQ, hex_errno);
done:
;
}
#endif /* !defined(_WIN32) */
#ifndef _WIN32
static void
test_util_string_from_pipe(void *ptr)
{
int test_pipe[2] = {-1, -1};
int retval = 0;
enum stream_status status = IO_STREAM_TERM;
ssize_t retlen;
char buf[4] = { 0 };
(void)ptr;
errno = 0;
/* Set up a pipe to test on */
retval = pipe(test_pipe);
tt_int_op(retval, OP_EQ, 0);
/* Send in a string. */
retlen = write(test_pipe[1], "ABC", 3);
tt_int_op(retlen, OP_EQ, 3);
status = get_string_from_pipe(test_pipe[0], buf, sizeof(buf)-1);
tt_int_op(errno, OP_EQ, 0);
tt_int_op(status, OP_EQ, IO_STREAM_OKAY);
tt_str_op(buf, OP_EQ, "ABC");
errno = 0;
/* Send in a string that contains a nul. */
retlen = write(test_pipe[1], "AB\0", 3);
tt_int_op(retlen, OP_EQ, 3);
status = get_string_from_pipe(test_pipe[0], buf, sizeof(buf)-1);
tt_int_op(errno, OP_EQ, 0);
tt_int_op(status, OP_EQ, IO_STREAM_OKAY);
tt_str_op(buf, OP_EQ, "AB");
errno = 0;
/* Send in a string that contains a nul only. */
retlen = write(test_pipe[1], "\0", 1);
tt_int_op(retlen, OP_EQ, 1);
status = get_string_from_pipe(test_pipe[0], buf, sizeof(buf)-1);
tt_int_op(errno, OP_EQ, 0);
tt_int_op(status, OP_EQ, IO_STREAM_OKAY);
tt_str_op(buf, OP_EQ, "");
errno = 0;
/* Send in a string that contains a trailing newline. */
retlen = write(test_pipe[1], "AB\n", 3);
tt_int_op(retlen, OP_EQ, 3);
status = get_string_from_pipe(test_pipe[0], buf, sizeof(buf)-1);
tt_int_op(errno, OP_EQ, 0);
tt_int_op(status, OP_EQ, IO_STREAM_OKAY);
tt_str_op(buf, OP_EQ, "AB");
errno = 0;
/* Send in a string that contains a newline only. */
retlen = write(test_pipe[1], "\n", 1);
tt_int_op(retlen, OP_EQ, 1);
status = get_string_from_pipe(test_pipe[0], buf, sizeof(buf)-1);
tt_int_op(errno, OP_EQ, 0);
tt_int_op(status, OP_EQ, IO_STREAM_OKAY);
tt_str_op(buf, OP_EQ, "");
errno = 0;
/* Send in a string and check that we nul terminate return values. */
retlen = write(test_pipe[1], "AAA", 3);
tt_int_op(retlen, OP_EQ, 3);
status = get_string_from_pipe(test_pipe[0], buf, sizeof(buf)-1);
tt_int_op(errno, OP_EQ, 0);
tt_int_op(status, OP_EQ, IO_STREAM_OKAY);
tt_str_op(buf, OP_EQ, "AAA");
tt_mem_op(buf, OP_EQ, "AAA\0", sizeof(buf));
errno = 0;
retlen = write(test_pipe[1], "B", 1);
tt_int_op(retlen, OP_EQ, 1);
memset(buf, '\xff', sizeof(buf));
status = get_string_from_pipe(test_pipe[0], buf, sizeof(buf)-1);
tt_int_op(errno, OP_EQ, 0);
tt_int_op(status, OP_EQ, IO_STREAM_OKAY);
tt_str_op(buf, OP_EQ, "B");
tt_mem_op(buf, OP_EQ, "B\0\xff\xff", sizeof(buf));
errno = 0;
/* Send in multiple lines. */
retlen = write(test_pipe[1], "A\nB", 3);
tt_int_op(retlen, OP_EQ, 3);
status = get_string_from_pipe(test_pipe[0], buf, sizeof(buf)-1);
tt_int_op(errno, OP_EQ, 0);
tt_int_op(status, OP_EQ, IO_STREAM_OKAY);
tt_str_op(buf, OP_EQ, "A\nB");
errno = 0;
/* Send in a line and close */
retlen = write(test_pipe[1], "AB", 2);
tt_int_op(retlen, OP_EQ, 2);
retval = close(test_pipe[1]);
tt_int_op(retval, OP_EQ, 0);
test_pipe[1] = -1;
status = get_string_from_pipe(test_pipe[0], buf, sizeof(buf)-1);
tt_int_op(errno, OP_EQ, 0);
tt_int_op(status, OP_EQ, IO_STREAM_OKAY);
tt_str_op(buf, OP_EQ, "AB");
errno = 0;
/* Check for EOF */
status = get_string_from_pipe(test_pipe[0], buf, sizeof(buf)-1);
tt_int_op(errno, OP_EQ, 0);
tt_int_op(status, OP_EQ, IO_STREAM_CLOSED);
errno = 0;
done:
if (test_pipe[0] != -1)
close(test_pipe[0]);
if (test_pipe[1] != -1)
close(test_pipe[1]);
}
#endif /* !defined(_WIN32) */
/**
* Test for format_hex_number_sigsafe()
*/
@ -4651,67 +4453,6 @@ struct split_lines_test_t {
const char *split_line[MAX_SPLIT_LINE_COUNT]; // Split lines
};
/**
* Test that we properly split a buffer into lines
*/
static void
test_util_split_lines(void *ptr)
{
/* Test cases. orig_line of last test case must be NULL.
* The last element of split_line[i] must be NULL. */
struct split_lines_test_t tests[] = {
{"", 0, {NULL}},
{"foo", 3, {"foo", NULL}},
{"\n\rfoo\n\rbar\r\n", 12, {"foo", "bar", NULL}},
{"fo o\r\nb\tar", 10, {"fo o", "b.ar", NULL}},
{"\x0f""f\0o\0\n\x01""b\0r\0\r", 12, {".f.o.", ".b.r.", NULL}},
{"line 1\r\nline 2", 14, {"line 1", "line 2", NULL}},
{"line 1\r\n\r\nline 2", 16, {"line 1", "line 2", NULL}},
{"line 1\r\n\r\r\r\nline 2", 18, {"line 1", "line 2", NULL}},
{"line 1\r\n\n\n\n\rline 2", 18, {"line 1", "line 2", NULL}},
{"line 1\r\n\r\t\r\nline 3", 18, {"line 1", ".", "line 3", NULL}},
{"\n\t\r\t\nline 3", 11, {".", ".", "line 3", NULL}},
{NULL, 0, { NULL }}
};
int i, j;
char *orig_line=NULL;
smartlist_t *sl=NULL;
(void)ptr;
for (i=0; tests[i].orig_line; i++) {
sl = smartlist_new();
/* Allocate space for string and trailing NULL */
orig_line = tor_memdup(tests[i].orig_line, tests[i].orig_length + 1);
tor_split_lines(sl, orig_line, tests[i].orig_length);
j = 0;
log_info(LD_GENERAL, "Splitting test %d of length %d",
i, tests[i].orig_length);
SMARTLIST_FOREACH_BEGIN(sl, const char *, line) {
/* Check we have not got too many lines */
tt_int_op(MAX_SPLIT_LINE_COUNT, OP_GT, j);
/* Check that there actually should be a line here */
tt_ptr_op(tests[i].split_line[j], OP_NE, NULL);
log_info(LD_GENERAL, "Line %d of test %d, should be <%s>",
j, i, tests[i].split_line[j]);
/* Check that the line is as expected */
tt_str_op(line,OP_EQ, tests[i].split_line[j]);
j++;
} SMARTLIST_FOREACH_END(line);
/* Check that we didn't miss some lines */
tt_ptr_op(NULL,OP_EQ, tests[i].split_line[j]);
tor_free(orig_line);
smartlist_free(sl);
sl = NULL;
}
done:
tor_free(orig_line);
smartlist_free(sl);
}
static void
test_util_di_ops(void *arg)
{
@ -6483,12 +6224,9 @@ struct testcase_t util_tests[] = {
UTIL_TEST(nowrap_math, 0),
UTIL_TEST(num_cpus, 0),
UTIL_TEST_WIN_ONLY(load_win_lib, 0),
UTIL_TEST_NO_WIN(exit_status, 0),
UTIL_TEST_NO_WIN(string_from_pipe, 0),
UTIL_TEST(format_hex_number, 0),
UTIL_TEST(format_dec_number, 0),
UTIL_TEST(join_win_cmdline, 0),
UTIL_TEST(split_lines, 0),
UTIL_TEST(n_bits_set, 0),
UTIL_TEST(eat_whitespace, 0),
UTIL_TEST(sl_new_from_text_lines, 0),

View File

@ -1,396 +0,0 @@
/* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
* Copyright (c) 2007-2018, The Tor Project, Inc. */
/* See LICENSE for licensing information */
#include "orconfig.h"
#define UTIL_PRIVATE
#define SUBPROCESS_PRIVATE
#include "lib/crypt_ops/crypto_cipher.h"
#include "lib/log/log.h"
#include "lib/process/subprocess.h"
#include "lib/process/waitpid.h"
#include "lib/string/printf.h"
#include "lib/time/compat_time.h"
#include "test/test.h"
#include <errno.h>
#include <string.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 /* defined(_WIN32) */
#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 /* !(defined(_WIN32)) */
/* 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 /* defined(_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_ptr_op(process_handle, OP_NE, 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 /* !defined(_WIN32) */
#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 /* defined(_WIN32) */
/* 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 /* defined(_WIN32) */
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_pipe, stdout_buf,
sizeof(stdout_buf) - 1, NULL, &eof);
#endif /* defined(_WIN32) */
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 /* !(defined(_WIN32)) */
if (!eof) {
/* We should have got all the data, but maybe not the EOF flag */
pos = tor_read_all_handle(process_handle->stdout_pipe, 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 /* defined(_WIN32) */
/* 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 /* !defined(_WIN32) */
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
};