diff options
-rw-r--r-- | configure.in | 2 | ||||
-rw-r--r-- | src/common/util.c | 380 | ||||
-rw-r--r-- | src/common/util.h | 26 | ||||
-rw-r--r-- | src/test/test-child.c | 22 | ||||
-rw-r--r-- | src/test/test_util.c | 145 |
5 files changed, 504 insertions, 71 deletions
diff --git a/configure.in b/configure.in index 26792a6677..e59f31b7a1 100644 --- a/configure.in +++ b/configure.in @@ -569,7 +569,7 @@ if test "$upnp" = "true"; then const char * minissdpdsock, int sameport);], [upnpDiscover(1, 0, 0, 0); exit(0);], [--with-libminiupnpc-dir], - [/usr/lib/]) + [/local/lib/]) fi AC_SYS_LARGEFILE diff --git a/src/common/util.c b/src/common/util.c index c387318c0e..517cc3e499 100644 --- a/src/common/util.c +++ b/src/common/util.c @@ -3045,17 +3045,107 @@ format_helper_exit_status(unsigned char child_state, int saved_errno, * and stderr, respectively, output of the child program can be read, and the * stdin of the child process shall be set to /dev/null. Otherwise returns * -1. Some parts of this code are based on the POSIX subprocess module from - * Python. + * Python, and example code from + * http://msdn.microsoft.com/en-us/library/ms682499%28v=vs.85%29.aspx. */ -int -tor_spawn_background(const char *const filename, int *stdout_read, - int *stderr_read, const char **argv) +process_handle_t +tor_spawn_background(const char *const filename, const char **argv) { + process_handle_t process_handle; #ifdef MS_WINDOWS - (void) filename; (void) stdout_read; (void) stderr_read; (void) argv; - log_warn(LD_BUG, "not yet implemented on Windows."); - return -1; -#else + HANDLE stdout_pipe_read = NULL; + HANDLE stdout_pipe_write = NULL; + HANDLE stderr_pipe_read = NULL; + HANDLE stderr_pipe_write = NULL; + + SECURITY_ATTRIBUTES saAttr; + smartlist_t *argv_list; + char *joined_argv; + int i; + + saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + saAttr.bInheritHandle = TRUE; + saAttr.lpSecurityDescriptor = NULL; + + process_handle.status = -1; + + /* Set up pipe for stdout */ + if (!CreatePipe(&stdout_pipe_read, &stdout_pipe_write, &saAttr, 0)) { + log_warn(LD_GENERAL, + "Failed to create pipe for stdout communication with child process: %s", + format_win32_error(GetLastError())); + return process_handle; + } + if (!SetHandleInformation(stdout_pipe_read, HANDLE_FLAG_INHERIT, 0)) { + log_warn(LD_GENERAL, + "Failed to configure pipe for stdout communication with child process: %s", + format_win32_error(GetLastError())); + } + + /* Set up pipe for stderr */ + if (!CreatePipe(&stderr_pipe_read, &stderr_pipe_write, &saAttr, 0)) { + log_warn(LD_GENERAL, + "Failed to create pipe for stderr communication with child process: %s", + format_win32_error(GetLastError())); + return process_handle; + } + if (!SetHandleInformation(stderr_pipe_read, HANDLE_FLAG_INHERIT, 0)) { + log_warn(LD_GENERAL, + "Failed to configure pipe for stderr communication with child process: %s", + format_win32_error(GetLastError())); + } + + /* Create the child process */ + + /* Windows expects argv to be a whitespace delimited string, so join argv up */ + argv_list = smartlist_create(); + for (i=0; argv[i] != NULL; i++) { + smartlist_add(argv_list, (void *)argv[i]); + } + + joined_argv = smartlist_join_strings(argv_list, " ", 0, NULL); + + STARTUPINFO siStartInfo; + BOOL retval = FALSE; + + ZeroMemory(&process_handle.pid, sizeof(PROCESS_INFORMATION)); + ZeroMemory(&siStartInfo, sizeof(STARTUPINFO)); + siStartInfo.cb = sizeof(STARTUPINFO); + siStartInfo.hStdError = stderr_pipe_write; + siStartInfo.hStdOutput = stdout_pipe_write; + siStartInfo.hStdInput = NULL; + siStartInfo.dwFlags |= STARTF_USESTDHANDLES; + + /* Create the child process */ + + retval = CreateProcess(filename, // module name + joined_argv, // command line + NULL, // process security attributes + NULL, // primary thread security attributes + TRUE, // handles are inherited + 0, // creation flags (TODO: set CREATE_NEW CONSOLE/PROCESS_GROUP to make GetExitCodeProcess() work?) + NULL, // use parent's environment + NULL, // use parent's current directory + &siStartInfo, // STARTUPINFO pointer + &process_handle.pid); // receives PROCESS_INFORMATION + + tor_free(joined_argv); + + if (!retval) { + log_warn(LD_GENERAL, + "Failed to create child process %s: %s", filename, + format_win32_error(GetLastError())); + } else { + // TODO: Close hProcess and hThread in process_handle.pid? + process_handle.stdout_pipe = stdout_pipe_read; + process_handle.stderr_pipe = stderr_pipe_read; + process_handle.status = 1; + } + + // TODO: Close pipes on exit + + return process_handle; +#else // MS_WINDOWS pid_t pid; int stdout_pipe[2]; int stderr_pipe[2]; @@ -3073,6 +3163,11 @@ tor_spawn_background(const char *const filename, int *stdout_read, static int max_fd = -1; + // XXX + process_handle.pid = 0; + process_handle.stderr_pipe = 0; + process_handle.stdout_pipe = 0; + /* We do the strlen here because strlen() is not signal handler safe, and we are not allowed to use unsafe functions between fork and exec */ error_message_length = strlen(error_message); @@ -3085,7 +3180,8 @@ tor_spawn_background(const char *const filename, int *stdout_read, log_warn(LD_GENERAL, "Failed to set up pipe for stdout communication with child process: %s", strerror(errno)); - return -1; + process_handle.status = -1; + return process_handle; } retval = pipe(stderr_pipe); @@ -3093,7 +3189,8 @@ tor_spawn_background(const char *const filename, int *stdout_read, log_warn(LD_GENERAL, "Failed to set up pipe for stderr communication with child process: %s", strerror(errno)); - return -1; + process_handle.status = -1; + return process_handle; } child_state = CHILD_STATE_MAXFD; @@ -3172,13 +3269,15 @@ tor_spawn_background(const char *const filename, int *stdout_read, /* Write the error message. GCC requires that we check the return value, but there is nothing we can do if it fails */ + // TODO: Don't use STDOUT, use a pipe set up just for this purpose nbytes = write(STDOUT_FILENO, error_message, error_message_length); nbytes = write(STDOUT_FILENO, hex_errno, sizeof(hex_errno)); (void) nbytes; _exit(255); - return -1; /* Never reached, but avoids compiler warning */ + process_handle.status = -1; + return process_handle; /* Never reached, but avoids compiler warning */ } /* In parent */ @@ -3189,11 +3288,14 @@ tor_spawn_background(const char *const filename, int *stdout_read, close(stdout_pipe[1]); close(stderr_pipe[0]); close(stderr_pipe[1]); - return -1; + process_handle.status = -1; + return process_handle; } + // TODO: If the child process forked but failed to exec, waitpid it + /* Return read end of the pipes to caller, and close write end */ - *stdout_read = stdout_pipe[0]; + process_handle.stdout_pipe = stdout_pipe[0]; retval = close(stdout_pipe[1]); if (-1 == retval) { @@ -3204,7 +3306,7 @@ tor_spawn_background(const char *const filename, int *stdout_read, needs to know about the pid in order to reap it later */ } - *stderr_read = stderr_pipe[0]; + process_handle.stderr_pipe = stderr_pipe[0]; retval = close(stderr_pipe[1]); if (-1 == retval) { @@ -3215,10 +3317,194 @@ tor_spawn_background(const char *const filename, int *stdout_read, needs to know about the pid in order to reap it later */ } - return pid; + process_handle.status = 1; + process_handle.pid = pid; + /* Set stdout/stderr pipes to be non-blocking */ + fcntl(process_handle.stdout_pipe, F_SETFL, O_NONBLOCK); + fcntl(process_handle.stderr_pipe, F_SETFL, O_NONBLOCK); + /* Open the buffered IO streams */ + process_handle.stdout_handle = fdopen(process_handle.stdout_pipe, "r"); + process_handle.stderr_handle = fdopen(process_handle.stderr_pipe, "r"); + + return process_handle; +#endif // MS_WINDOWS +} + +int +tor_get_exit_code(const process_handle_t process_handle, int block) +{ +#ifdef MS_WINDOWS + DWORD exit_code; + BOOL retval; + if (block) { + exit_code = WaitForSingleObject(process_handle.pid.hProcess, INFINITE); + retval = GetExitCodeProcess(process_handle.pid.hProcess, &exit_code); + } else { + exit_code = WaitForSingleObject(process_handle.pid.hProcess, 0); + if (WAIT_TIMEOUT == exit_code) { + // Process has not exited + return -2; + } + retval = GetExitCodeProcess(process_handle.pid.hProcess, &exit_code); + } + + if (!retval) { + log_warn(LD_GENERAL, "GetExitCodeProcess() failed: %s", + format_win32_error(GetLastError())); + return -1; + } else { + return exit_code; + } +#else + int stat_loc; + int retval; + + retval = waitpid(process_handle.pid, &stat_loc, 0); + if (retval != process_handle.pid) { + log_warn(LD_GENERAL, "waitpid() failed for PID %d: %s", process_handle.pid, + strerror(errno)); + return -1; + } + + if (!WIFEXITED(stat_loc)) { + log_warn(LD_GENERAL, "Process %d did not exit normally", process_handle.pid); + return -1; + } + + return WEXITSTATUS(stat_loc); +#endif // MS_WINDOWS +} + +#ifdef MS_WINDOWS +/* Windows equivalent of read_all */ +ssize_t +tor_read_all_handle(HANDLE h, char *buf, size_t count, HANDLE hProcess) +{ + size_t numread = 0; + BOOL retval; + DWORD byte_count; + BOOL process_exited = FALSE; + + if (count > SIZE_T_CEILING || count > SSIZE_T_MAX) + return -1; + + while (numread != count) { + retval = PeekNamedPipe(h, NULL, 0, NULL, &byte_count, NULL); + if (!retval) { + log_warn(LD_GENERAL, + "Failed to peek from handle: %s", + format_win32_error(GetLastError())); + return -1; + } else if (0 == byte_count) { + /* Nothing available: process exited or it is busy */ + + /* Exit if we don't know whether the process is running */ + if (NULL == hProcess) + break; + + /* The process exited and there's nothing left to read from it */ + if (process_exited) + break; + + /* If process is not running, check for output one more time in case + it wrote something after the peek was performed. Otherwise keep on + waiting for output */ + byte_count = WaitForSingleObject(hProcess, 0); + if (WAIT_TIMEOUT != byte_count) + process_exited = TRUE; + + continue; + } + + retval = ReadFile(h, buf+numread, count-numread, &byte_count, NULL); + if (!retval) { + log_warn(LD_GENERAL, + "Failed to read from handle: %s", + format_win32_error(GetLastError())); + return -1; + } else if (0 == byte_count) { + /* End of file */ + break; + } + numread += byte_count; + } + return (ssize_t)numread; +} +#endif + +ssize_t +tor_read_all_from_process_stdout(const process_handle_t process_handle, + char *buf, size_t count) +{ +#ifdef MS_WINDOWS + return tor_read_all_handle(process_handle.stdout_pipe, buf, count, + process_handle.pid.hProcess); +#else + return read_all(process_handle.stdout_pipe, buf, count, 0); +#endif +} + +ssize_t +tor_read_all_from_process_stderr(const process_handle_t process_handle, + char *buf, size_t count) +{ +#ifdef MS_WINDOWS + return tor_read_all_handle(process_handle.stderr_pipe, buf, count, + process_handle.pid.hProcess); +#else + return read_all(process_handle.stderr_pipe, buf, count, 0); #endif } +#ifdef MS_WINDOWS +static int +log_from_handle(HANDLE *pipe, int severity) +{ + char buf[256]; + int pos; + int start, cur, next; + + pos = tor_read_all_handle(pipe, buf, sizeof(buf) - 1, NULL); + if (pos < 0) { + // Error + log_warn(LD_GENERAL, "Failed to read data from subprocess"); + return -1; + } + + if (0 == pos) { + // There's nothing to read (process is busy or has exited) + log_debug(LD_GENERAL, "Subprocess had nothing to say"); + return 0; + } + + // End with a null even if there isn't a \r\n at the end + // TODO: What if this is a partial line? + buf[pos] = '\0'; + log_debug(LD_GENERAL, "Subprocess had %d bytes to say", pos); + + next = 0; // Start of the next line + while (next < pos) { + start = next; // Look for the end of this line + for (cur=start; cur<pos; cur++) { + if ('\r' == buf[cur]) { + buf[cur] = '\0'; + next = cur + 1; + if ((cur + 1) < pos && buf[cur+1] < pos) { + buf[cur + 1] = '\0'; + next = cur + 2; + } + // Line starts at start and ends with a null (was \r\n) + break; + } + // Line starts at start and ends at the end of a string + // but we already added a null in earlier + } + log_fn(severity, LD_GENERAL, "Port forwarding helper says: %s", buf+start); + } + return pos; +} + +#else /** Read from stream, and send lines to log at the specified log level. * Returns 1 if stream is closed normally, -1 if there is a error reading, and * 0 otherwise. Handles lines from tor-fw-helper and @@ -3294,26 +3580,23 @@ log_from_pipe(FILE *stream, int severity, const char *executable, /* We should never get here */ return -1; } +#endif void tor_check_port_forwarding(const char *filename, int dir_port, int or_port, time_t now) { -#ifdef MS_WINDOWS - (void) filename; (void) dir_port; (void) or_port; (void) now; - (void) tor_spawn_background; - (void) log_from_pipe; - log_warn(LD_GENERAL, "Sorry, port forwarding is not yet supported " - "on windows."); -#else /* When fw-helper succeeds, how long do we wait until running it again */ #define TIME_TO_EXEC_FWHELPER_SUCCESS 300 /* When fw-helper fails, how long do we wait until running it again */ #define TIME_TO_EXEC_FWHELPER_FAIL 60 - static int child_pid = -1; - static FILE *stdout_read = NULL; - static FILE *stderr_read = NULL; +#ifdef MS_WINDOWS + static process_handle_t child_handle = {0, NULL, NULL, {NULL}}; +#else + static process_handle_t child_handle; +#endif + static time_t time_to_run_helper = 0; int stdout_status, stderr_status, retval; const char *argv[10]; @@ -3338,37 +3621,40 @@ tor_check_port_forwarding(const char *filename, int dir_port, int or_port, argv[9] = NULL; /* Start the child, if it is not already running */ - if (-1 == child_pid && - time_to_run_helper < now) { - int fd_out=-1, fd_err=-1; - + if (child_handle.status <= 0 && time_to_run_helper < now) { /* Assume tor-fw-helper will succeed, start it later*/ time_to_run_helper = now + TIME_TO_EXEC_FWHELPER_SUCCESS; - child_pid = tor_spawn_background(filename, &fd_out, &fd_err, argv); - if (child_pid < 0) { + child_handle = tor_spawn_background(filename, argv); + if (child_handle.status < 0) { log_warn(LD_GENERAL, "Failed to start port forwarding helper %s", filename); - child_pid = -1; return; } - /* Set stdout/stderr pipes to be non-blocking */ - fcntl(fd_out, F_SETFL, O_NONBLOCK); - fcntl(fd_err, F_SETFL, O_NONBLOCK); - /* Open the buffered IO streams */ - stdout_read = fdopen(fd_out, "r"); - stderr_read = fdopen(fd_err, "r"); - +#ifdef MS_WINDOWS + log_info(LD_GENERAL, + "Started port forwarding helper (%s)", filename); +#else log_info(LD_GENERAL, "Started port forwarding helper (%s) with pid %d", filename, child_pid); +#endif } /* If child is running, read from its stdout and stderr) */ - if (child_pid > 0) { + if (child_handle.status > 0) { /* Read from stdout/stderr and log result */ retval = 0; - stdout_status = log_from_pipe(stdout_read, LOG_INFO, filename, &retval); - stderr_status = log_from_pipe(stderr_read, LOG_WARN, filename, &retval); +#ifdef MS_WINDOWS + stdout_status = log_from_handle(child_handle.stdout_pipe, LOG_INFO); + stderr_status = log_from_handle(child_handle.stderr_pipe, LOG_WARN); + // If we got this far (on Windows), the process started + retval = 0; +#else + stdout_status = log_from_pipe(child_handle.stdout_handle, + LOG_INFO, filename, &retval); + stderr_status = log_from_pipe(child_handle.stderr_handle, + LOG_WARN, filename, &retval); +#endif if (retval) { /* There was a problem in the child process */ time_to_run_helper = now + TIME_TO_EXEC_FWHELPER_FAIL; @@ -3378,9 +3664,16 @@ tor_check_port_forwarding(const char *filename, int dir_port, int or_port, if (-1 == stdout_status || -1 == stderr_status) /* There was a failure */ retval = -1; +#ifdef MS_WINDOWS + else if (tor_get_exit_code(child_handle, 0) >= 0) { + /* process has exited */ + retval = 1; + } +#else else if (1 == stdout_status || 1 == stderr_status) /* stdout or stderr was closed */ retval = 1; +#endif else /* Both are fine */ retval = 0; @@ -3395,9 +3688,8 @@ tor_check_port_forwarding(const char *filename, int dir_port, int or_port, /* TODO: The child might not actually be finished (maybe it failed or closed stdout/stderr), so maybe we shouldn't start another? */ - child_pid = -1; + child_handle.status = -1; } } -#endif } diff --git a/src/common/util.h b/src/common/util.h index a1def6cc3f..d0ad8eb637 100644 --- a/src/common/util.h +++ b/src/common/util.h @@ -354,8 +354,30 @@ HANDLE load_windows_system_library(const TCHAR *library_name); #ifdef UTIL_PRIVATE /* Prototypes for private functions only used by util.c (and unit tests) */ -int tor_spawn_background(const char *const filename, int *stdout_read, - int *stderr_read, const char **argv); + +typedef struct process_handle_s { + int status; // 0: not running; 1: running; -1: error +#ifdef MS_WINDOWS + HANDLE stdout_pipe; + HANDLE stderr_pipe; + PROCESS_INFORMATION pid; +#else + int stdout_pipe; + int stderr_pipe; + FILE *stdout_handle; + FILE *stderr_handle; + int pid; +#endif // MS_WINDOWS +} process_handle_t; + +process_handle_t tor_spawn_background(const char *const filename, + const char **argv); +int tor_get_exit_code(const process_handle_t pid, int block); +ssize_t tor_read_all_handle(HANDLE h, char *buf, size_t count, HANDLE hProcess); +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); void format_helper_exit_status(unsigned char child_state, int saved_errno, char *hex_errno); diff --git a/src/test/test-child.c b/src/test/test-child.c index ca52750c2f..1b9c5e3d57 100644 --- a/src/test/test-child.c +++ b/src/test/test-child.c @@ -1,4 +1,11 @@ #include <stdio.h> +#include "orconfig.h" +#ifdef MS_WINDOWS +#define WINDOWS_LEAN_AND_MEAN +#include <windows.h> +#else +#include <unistd.h> +#endif /** Trivial test program which prints out its command line arguments so we can * check if tor_spawn_background() works */ @@ -11,7 +18,22 @@ main(int argc, char **argv) fprintf(stderr, "ERR\n"); for (i = 1; i < argc; i++) fprintf(stdout, "%s\n", argv[i]); + 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); +#ifdef MS_WINDOWS + Sleep(1000); +#else + sleep(1); +#endif fprintf(stdout, "DONE\n"); +#ifdef MS_WINDOWS + Sleep(1000); +#else + sleep(1); +#endif return 0; } diff --git a/src/test/test_util.c b/src/test/test_util.c index c4769e6407..6f875606ec 100644 --- a/src/test/test_util.c +++ b/src/test/test_util.c @@ -1376,46 +1376,49 @@ test_util_fgets_eagain(void *ptr) } #endif -#ifndef MS_WINDOWS /** 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) + const char *expected_err, int expected_exit, + int expected_status) { - int stdout_pipe=-1, stderr_pipe=-1; - int retval, stat_loc; - pid_t pid; + int retval; ssize_t pos; + process_handle_t process_handle; char stdout_buf[100], stderr_buf[100]; /* Start the program */ - retval = tor_spawn_background(argv[0], &stdout_pipe, &stderr_pipe, argv); - tt_int_op(retval, >, 0); - tt_int_op(stdout_pipe, >, 0); - tt_int_op(stderr_pipe, >, 0); - pid = retval; + process_handle = tor_spawn_background(argv[0], argv); + + tt_int_op(process_handle.status, ==, expected_status); + + /* If the process failed to start, don't bother continuing */ + if (process_handle.status == -1) + return; + + tt_int_op(process_handle.stdout_pipe, >, 0); + tt_int_op(process_handle.stderr_pipe, >, 0); /* Check stdout */ - pos = read_all(stdout_pipe, stdout_buf, sizeof(stdout_buf) - 1, 0); + 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(pos, ==, strlen(expected_out)); tt_str_op(stdout_buf, ==, expected_out); + tt_int_op(pos, ==, strlen(expected_out)); /* Check it terminated correctly */ - retval = waitpid(pid, &stat_loc, 0); - tt_int_op(retval, ==, pid); - tt_assert(WIFEXITED(stat_loc)); - tt_int_op(WEXITSTATUS(stat_loc), ==, expected_exit); - tt_assert(!WIFSIGNALED(stat_loc)); - tt_assert(!WIFSTOPPED(stat_loc)); + retval = tor_get_exit_code(process_handle, 1); + tt_int_op(retval, ==, expected_exit); + // TODO: Make test-child exit with something other than 0 /* Check stderr */ - pos = read_all(stderr_pipe, stderr_buf, sizeof(stderr_buf) - 1, 0); + pos = tor_read_all_from_process_stderr(process_handle, stderr_buf, + sizeof(stderr_buf) - 1); tt_assert(pos >= 0); stderr_buf[pos] = '\0'; - tt_int_op(pos, ==, strlen(expected_err)); tt_str_op(stderr_buf, ==, expected_err); + tt_int_op(pos, ==, strlen(expected_err)); done: ; @@ -1425,30 +1428,123 @@ run_util_spawn_background(const char *argv[], const char *expected_out, static void test_util_spawn_background_ok(void *ptr) { +#ifdef MS_WINDOWS + // TODO: Under MSYS, BUILDDIR in orconfig.h needs to be tweaked + const char *argv[] = {BUILDDIR "/src/test/test-child.exe", "--test", NULL}; + const char *expected_out = "OUT\r\n--test\r\nSLEEPING\r\nDONE\r\n"; + 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\nDONE\n"; + const char *expected_out = "OUT\n--test\nSLEEPING\nDONE\n"; const char *expected_err = "ERR\n"; +#endif (void)ptr; - run_util_spawn_background(argv, expected_out, expected_err, 0); + run_util_spawn_background(argv, expected_out, expected_err, 0, 0); } /** Check that failing to find the executable works as expected */ static void test_util_spawn_background_fail(void *ptr) { +#ifdef MS_WINDOWS + const char *argv[] = {BUILDDIR "/src/test/no-such-file", "--test", NULL}; + const char *expected_out = "ERR: Failed to spawn background process " + "- code 9/2\n"; + const char *expected_err = ""; + const int expected_status = -1; +#else const char *argv[] = {BUILDDIR "/src/test/no-such-file", "--test", NULL}; const char *expected_out = "ERR: Failed to spawn background process " "- code 9/2\n"; const char *expected_err = ""; + // TODO: Once we can signal failure to exec, set this to be -1; + const int expected_status = 0; +#endif (void)ptr; - run_util_spawn_background(argv, expected_out, expected_err, 255); + run_util_spawn_background(argv, expected_out, expected_err, 255, expected_status); } + +/** Helper function for testing tor_spawn_background */ +static void +test_util_spawn_background_partial_read(void *ptr) +{ +#ifdef MS_WINDOWS + // TODO: Under MSYS, BUILDDIR in orconfig.h needs to be tweaked + const char *argv[] = {BUILDDIR "/src/test/test-child.exe", "--test", NULL}; + const char *expected_out[] = { "OUT\r\n--test\r\nSLEEPING\r\n", + "DONE\r\n", + NULL }; + const char *expected_err = "ERR\r\n"; + int expected_out_ctr; +#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\r\n"; +#endif + const int expected_exit = 0; + const int expected_status = 0; + + int retval; + ssize_t pos; + process_handle_t process_handle; + char stdout_buf[100], stderr_buf[100]; + + /* Start the program */ + process_handle = tor_spawn_background(argv[0], argv); + tt_int_op(process_handle.status, ==, expected_status); + + /* Check stdout */ +#ifdef MS_WINDOWS + for (expected_out_ctr =0; expected_out[expected_out_ctr] != NULL;) { + pos = tor_read_all_handle(process_handle.stdout_pipe, stdout_buf, + sizeof(stdout_buf) - 1, NULL); + 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(stdout_buf, ==, expected_out[expected_out_ctr]); + tt_int_op(pos, ==, strlen(expected_out[expected_out_ctr])); + expected_out_ctr++; + } + /* The process should have exited without writing more */ + pos = tor_read_all_handle(process_handle.stdout_pipe, stdout_buf, + sizeof(stdout_buf) - 1, + process_handle.pid.hProcess); + tt_int_op(pos, ==, 0); +#else + pos = tor_read_all_from_process_stdout(process_handle, stdout_buf, + sizeof(stdout_buf) - 1); + tt_assert(pos >= 0); + stdout_buf[pos] = '\0'; + tt_str_op(stdout_buf, ==, expected_out); + tt_int_op(pos, ==, strlen(expected_out)); #endif + /* Check it terminated correctly */ + retval = tor_get_exit_code(process_handle, 1); + tt_int_op(retval, ==, expected_exit); + // 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(stderr_buf, ==, expected_err); + tt_int_op(pos, ==, strlen(expected_err)); + + done: + ; +} + static void test_util_di_ops(void) { @@ -1533,9 +1629,10 @@ struct testcase_t util_tests[] = { UTIL_TEST(exit_status, 0), #ifndef MS_WINDOWS UTIL_TEST(fgets_eagain, TT_SKIP), +#endif UTIL_TEST(spawn_background_ok, 0), UTIL_TEST(spawn_background_fail, 0), -#endif + UTIL_TEST(spawn_background_partial_read, 0), END_OF_TESTCASES }; |