diff options
Diffstat (limited to 'src/common/util.c')
-rw-r--r-- | src/common/util.c | 380 |
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 } |