aboutsummaryrefslogtreecommitdiff
path: root/src/common/util.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/common/util.c')
-rw-r--r--src/common/util.c380
1 files changed, 336 insertions, 44 deletions
diff --git a/src/common/util.c b/src/common/util.c
index c387318c0..517cc3e49 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
}