Skip to content

Instantly share code, notes, and snippets.

@sjmurdoch
Created August 25, 2011 14:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sjmurdoch/1170726 to your computer and use it in GitHub Desktop.
Save sjmurdoch/1170726 to your computer and use it in GitHub Desktop.
diff --git a/configure.in b/configure.in
index 4a89df6..26792a6 100644
--- a/configure.in
+++ b/configure.in
@@ -325,6 +325,7 @@ dnl Where do you live, libevent? And how do we call you?
if test "$bwin32" = true; then
TOR_LIB_WS32=-lws2_32
+ TOR_LIB_IPHLPAPI=-liphlpapi
# Some of the cargo-cults recommend -lwsock32 as well, but I don't
# think it's actually necessary.
TOR_LIB_GDI=-lgdi32
@@ -334,6 +335,7 @@ else
fi
AC_SUBST(TOR_LIB_WS32)
AC_SUBST(TOR_LIB_GDI)
+AC_SUBST(TOR_LIB_IPHLPAPI)
dnl We need to do this before we try our disgusting hack below.
AC_CHECK_HEADERS([sys/types.h])
@@ -559,7 +561,7 @@ dnl There are no packages for Debian or Redhat as of this patch
if test "$upnp" = "true"; then
AC_DEFINE(MINIUPNPC, 1, [Define to 1 if we are building with UPnP.])
- TOR_SEARCH_LIBRARY(libminiupnpc, $trylibminiupnpcdir, [-lminiupnpc],
+ TOR_SEARCH_LIBRARY(libminiupnpc, $trylibminiupnpcdir, [-lminiupnpc $TOR_LIB_WS32 $TOR_LIB_IPHLPAPI],
[#include <miniupnpc/miniwget.h>
#include <miniupnpc/miniupnpc.h>
#include <miniupnpc/upnpcommands.h>],
diff --git a/src/common/util.c b/src/common/util.c
index c387318..d69d8f1 100644
--- a/src/common/util.c
+++ b/src/common/util.c
@@ -3034,28 +3034,129 @@ format_helper_exit_status(unsigned char child_state, int saved_errno,
#define SPAWN_ERROR_MESSAGE "ERR: Failed to spawn background process - code "
-/** Start a program in the background. If <b>filename</b> contains a '/',
- * then it will be treated as an absolute or relative path. Otherwise the
- * system path will be searched for <b>filename</b>. The strings in
- * <b>argv</b> will be passed as the command line arguments of the child
- * program (following convention, argv[0] should normally be the filename of
- * the executable). The last element of argv must be NULL. If the child
- * program is launched, the PID will be returned and <b>stdout_read</b> and
- * <b>stdout_err</b> will be set to file descriptors from which the stdout
- * 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.
+/** Start a program in the background. If <b>filename</b> contains a '/', then
+ * it will be treated as an absolute or relative path. Otherwise, on
+ * non-Windows systems, the system path will be searched for <b>filename</b>.
+ * On Windows, only the current directory will be searched. Here, to search the
+ * system path (as well as the application directory, current working
+ * directory, and system directories), set filename to NULL.
+ *
+ * The strings in <b>argv</b> will be passed as the command line arguments of
+ * the child program (following convention, argv[0] should normally be the
+ * filename of the executable, and this must be the case if <b>filename</b> is
+ * NULL). The last element of argv must be NULL. If the child program is
+ * launched, a handle to it will be returned.
+ *
+ * Some parts of this code are based on the POSIX subprocess module from
+ * 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;
+
+ STARTUPINFO siStartInfo;
+ BOOL retval = FALSE;
+
+ SECURITY_ATTRIBUTES saAttr;
+ smartlist_t *argv_list;
+ char *joined_argv;
+ int i;
+
+ saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
+ saAttr.bInheritHandle = TRUE;
+ saAttr.lpSecurityDescriptor = NULL;
+
+ /* Assume failure to start process */
+ memset(&process_handle, 0, sizeof(process_handle));
+ 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()));
+ return process_handle;
+ }
+
+ /* 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()));
+ return process_handle;
+ }
+
+ /* 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);
+
+ 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
+ /*(TODO: set CREATE_NEW CONSOLE/PROCESS_GROUP to make GetExitCodeProcess()
+ * work?) */
+ 0, // creation flags
+ 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?filename:argv[0],
+ 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 +3174,10 @@ tor_spawn_background(const char *const filename, int *stdout_read,
static int max_fd = -1;
+ /* Assume failure to start */
+ memset(&process_handle, 0, sizeof(process_handle));
+ process_handle.status = -1;
+
/* 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 +3190,7 @@ 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;
+ return process_handle;
}
retval = pipe(stderr_pipe);
@@ -3093,7 +3198,7 @@ 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;
+ return process_handle;
}
child_state = CHILD_STATE_MAXFD;
@@ -3172,13 +3277,14 @@ 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 */
+ return process_handle; /* Never reached, but avoids compiler warning */
}
/* In parent */
@@ -3189,36 +3295,262 @@ tor_spawn_background(const char *const filename, int *stdout_read,
close(stdout_pipe[1]);
close(stderr_pipe[0]);
close(stderr_pipe[1]);
- return -1;
+ return process_handle;
}
+ process_handle.pid = pid;
+
+ /* 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) {
log_warn(LD_GENERAL,
"Failed to close write end of stdout pipe in parent process: %s",
strerror(errno));
- /* Do not return -1, because the child is running, so the parent
- 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) {
log_warn(LD_GENERAL,
"Failed to close write end of stderr pipe in parent process: %s",
strerror(errno));
- /* Do not return -1, because the child is running, so the parent
- needs to know about the pid in order to reap it later */
}
- return pid;
+ process_handle.status = 1;
+ /* 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
+}
+
+/* Get the exit code of a process specified by <b>process_handle</b> and
+ * store it in <b>exit_code</b>, if set to a non-NULL value. If
+ * <b>block</b> is set to true, the call will block until the process has
+ * exited. Otherwise if the process is still running, the function will
+ * return -2, and exit_code will be left unchanged. Returns 0 if the
+ * process did exit. If there is a failure, -1 will be returned and the
+ * contents of exit_code (if non-NULL) will be undefined. N.B. Under *nix
+ * operating systems, this will probably not work in Tor, because
+ * waitpid() is called in main.c to reap any terminated child
+ * processes.*/
+int
+tor_get_exit_code(const process_handle_t process_handle,
+ int block, int *exit_code)
+{
+#ifdef MS_WINDOWS
+ DWORD retval;
+ BOOL success;
+
+ if (block) {
+ /* Wait for the process to exit */
+ retval = WaitForSingleObject(process_handle.pid.hProcess, INFINITE);
+ if (retval != WAIT_OBJECT_0) {
+ log_warn(LD_GENERAL, "WaitForSingleObject() failed (%d): %s",
+ (int)retval, format_win32_error(GetLastError()));
+ return -1;
+ }
+ } else {
+ retval = WaitForSingleObject(process_handle.pid.hProcess, 0);
+ if (WAIT_TIMEOUT == retval) {
+ /* Process has not exited */
+ return -2;
+ } else if (retval != WAIT_OBJECT_0) {
+ log_warn(LD_GENERAL, "WaitForSingleObject() failed (%d): %s",
+ (int)retval, format_win32_error(GetLastError()));
+ return -1;
+ }
+ }
+
+ if (exit_code != NULL) {
+ success = GetExitCodeProcess(process_handle.pid.hProcess,
+ (PDWORD)exit_code);
+ if (!success) {
+ log_warn(LD_GENERAL, "GetExitCodeProcess() failed: %s",
+ format_win32_error(GetLastError()));
+ return -1;
+ }
+ }
+#else
+ int stat_loc;
+ int retval;
+
+ retval = waitpid(process_handle.pid, &stat_loc, block?0:WNOHANG);
+ if (!block && 0 == retval) {
+ /* Process has not exited */
+ return -2;
+ } else 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;
+ }
+
+ if (exit_code != NULL)
+ *exit_code = WEXITSTATUS(stat_loc);
+#endif // MS_WINDOWS
+
+ return 0;
+}
+
+#ifdef MS_WINDOWS
+/** Read from a handle <b>h</b> into <b>buf</b>, up to <b>count</b> bytes. If
+ * <b>hProcess</b> is NULL, the function will return immediately if there is
+ * nothing more to read. Otherwise <b>hProcess</b> should be set to the handle
+ * to the process owning the <b>h</b>. In this case, the function will exit
+ * only once the process has exited, or <b>count</b> bytes are read. Returns
+ * the number of bytes read, or -1 on error. */
+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) {
+ /* Check if there is anything to read */
+ 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
+
+/* Read from stdout of a process until the process exits. */
+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
+}
+
+/* Read from stdout of a process until the process exits. */
+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
+/** Read from stream, and send lines to log at the specified log level.
+ * Returns -1 if there is a error reading, and 0 otherwise.
+ */
+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);
+
+ /* Split buf into lines and log each one */
+ 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++) {
+ /* On Windows \r means end of line */
+ if ('\r' == buf[cur]) {
+ buf[cur] = '\0';
+ next = cur + 1;
+ /* If \n follows, remove it too */
+ if ((cur + 1) < pos && '\n' == buf[cur+1]) {
+ 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 0;
}
+#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 +3626,26 @@ 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 */
+/* When fw-helper failed to start, 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;
+ /* Static variables are initialized to zero, so child_handle.status=0
+ * which corresponds to it not running on startup */
+#ifdef MS_WINDOWS
+ static process_handle_t child_handle;
+#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 +3670,47 @@ 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) {
+#ifdef MS_WINDOWS
+ /* Passing NULL as lpApplicationName makes Windows search for the .exe */
+ child_handle = tor_spawn_background(NULL, argv);
+#else
+ child_handle = tor_spawn_background(filename, argv);
+#endif
+ if (child_handle.status < 0) {
log_warn(LD_GENERAL, "Failed to start port forwarding helper %s",
filename);
- child_pid = -1;
+ time_to_run_helper = now + TIME_TO_EXEC_FWHELPER_FAIL;
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) with pid %d", filename, child_pid);
+ "Started port forwarding helper (%s)", filename);
+#else
+ log_info(LD_GENERAL,
+ "Started port forwarding helper (%s) with pid %d", filename,
+ child_handle.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 +3720,21 @@ 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, NULL) >= 0) {
+ /* process has exited */
+ /* TODO: Do something with the process return value */
+ /* TODO: What if the process output something since
+ * between log_from_handle and tor_get_exit_code? */
+ retval = 1;
+ }
+#else
else if (1 == stdout_status || 1 == stderr_status)
- /* stdout or stderr was closed */
+ /* stdout or stderr was closed, the process probably
+ * exited. It will be reaped by waitpid() in main.c */
+ /* TODO: Do something with the process return value */
retval = 1;
+#endif
else
/* Both are fine */
retval = 0;
@@ -3389,15 +3743,15 @@ tor_check_port_forwarding(const char *filename, int dir_port, int or_port,
if (0 != retval) {
if (1 == retval) {
log_info(LD_GENERAL, "Port forwarding helper terminated");
+ child_handle.status = 0;
} else {
log_warn(LD_GENERAL, "Failed to read from port forwarding helper");
+ child_handle.status = -1;
}
/* 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;
}
}
-#endif
}
diff --git a/src/common/util.h b/src/common/util.h
index a1def6c..442001f 100644
--- a/src/common/util.h
+++ b/src/common/util.h
@@ -354,8 +354,34 @@ 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;
+ pid_t 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 process_handle,
+ int block, int *exit_code);
+#ifdef MS_WINDOWS
+ssize_t tor_read_all_handle(HANDLE h, char *buf, size_t count,
+ HANDLE hProcess);
+#endif
+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 ca52750..1b9c5e3 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 c4769e6..cb98030 100644
--- a/src/test/test_util.c
+++ b/src/test/test_util.c
@@ -1376,46 +1376,54 @@ 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, exit_code;
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;
+#ifdef MS_WINDOWS
+ process_handle = tor_spawn_background(NULL, argv);
+#else
+ process_handle = tor_spawn_background(argv[0], argv);
+#endif
+
+ 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, &exit_code);
+ tt_int_op(retval, ==, 0);
+ tt_int_op(exit_code, ==, 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 +1433,124 @@ run_util_spawn_background(const char *argv[], const char *expected_out,
static void
test_util_spawn_background_ok(void *ptr)
{
+#ifdef MS_WINDOWS
+ const char *argv[] = {"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, 1);
}
/** 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 = 1;
+#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)
+{
+ const int expected_exit = 0;
+ const int expected_status = 1;
+
+ int retval, exit_code;
+ ssize_t pos;
+ process_handle_t process_handle;
+ char stdout_buf[100], stderr_buf[100];
+#ifdef MS_WINDOWS
+ const char *argv[] = {"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
+ (void)ptr;
+
+ /* Start the program */
+ process_handle = tor_spawn_background(NULL, 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, &exit_code);
+ tt_int_op(retval, ==, 0);
+ tt_int_op(exit_code, ==, 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 +1635,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
};
diff --git a/src/tools/tor-fw-helper/Makefile.am b/src/tools/tor-fw-helper/Makefile.am
index 77ff63f..8f64ad2 100644
--- a/src/tools/tor-fw-helper/Makefile.am
+++ b/src/tools/tor-fw-helper/Makefile.am
@@ -25,7 +25,7 @@ endif
if MINIUPNPC
miniupnpc_ldflags = @TOR_LDFLAGS_libminiupnpc@
-miniupnpc_ldadd = -lminiupnpc -lm
+miniupnpc_ldadd = -lminiupnpc -lm @TOR_LIB_IPHLPAPI@
miniupnpc_cppflags = @TOR_CPPFLAGS_libminiupnpc@
else
miniupnpc_ldflags =
diff --git a/src/tools/tor-fw-helper/tor-fw-helper-upnp.c b/src/tools/tor-fw-helper/tor-fw-helper-upnp.c
index 18ca563..c4b14a8 100644
--- a/src/tools/tor-fw-helper/tor-fw-helper-upnp.c
+++ b/src/tools/tor-fw-helper/tor-fw-helper-upnp.c
@@ -9,6 +9,9 @@
#include "orconfig.h"
#ifdef MINIUPNPC
+#ifdef MS_WINDOWS
+#define STATICLIB
+#endif
#include <stdint.h>
#include <string.h>
#include <stdio.h>
diff --git a/src/tools/tor-fw-helper/tor-fw-helper.c b/src/tools/tor-fw-helper/tor-fw-helper.c
index 20d60d7..0022397 100644
--- a/src/tools/tor-fw-helper/tor-fw-helper.c
+++ b/src/tools/tor-fw-helper/tor-fw-helper.c
@@ -13,6 +13,7 @@
* later date.
*/
+#include "orconfig.h"
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
@@ -20,7 +21,10 @@
#include <time.h>
#include <string.h>
-#include "orconfig.h"
+#ifdef MS_WINDOWS
+#include <winsock2.h>
+#endif
+
#include "tor-fw-helper.h"
#ifdef NAT_PMP
#include "tor-fw-helper-natpmp.h"
@@ -219,6 +223,30 @@ tor_fw_add_dir_port(tor_fw_options_t *tor_fw_options,
}
}
+/** Called before we make any calls to network-related functions.
+ * (Some operating systems require their network libraries to be
+ * initialized.) (from common/compat.c) */
+static int
+network_init(void)
+{
+#ifdef MS_WINDOWS
+ /* This silly exercise is necessary before windows will allow
+ * gethostbyname to work. */
+ WSADATA WSAData;
+ int r;
+ r = WSAStartup(0x101, &WSAData);
+ if (r) {
+ fprintf(stderr, "E: Error initializing Windows network layer "
+ "- code was %d", r);
+ return -1;
+ }
+ /* WSAData.iMaxSockets might show the max sockets we're allowed to use.
+ * We might use it to complain if we're trying to be a server but have
+ * too few sockets available. */
+#endif
+ return 0;
+}
+
int
main(int argc, char **argv)
{
@@ -229,6 +257,7 @@ main(int argc, char **argv)
backends_t backend_state;
memset(&tor_fw_options, 0, sizeof(tor_fw_options));
+ memset(&backend_state, 0, sizeof(backend_state));
while (1) {
int option_index = 0;
@@ -329,6 +358,10 @@ main(int argc, char **argv)
tor_fw_options.public_dir_port);
}
+ // Initialize networking
+ if (network_init())
+ exit(1);
+
// Initalize the various fw-helper backend helpers
r = init_backends(&tor_fw_options, &backend_state);
if (r)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment