|
@@ -2957,6 +2957,105 @@ load_windows_system_library(const TCHAR *library_name)
|
|
|
}
|
|
|
#endif
|
|
|
|
|
|
+/* Format a single argument for being put on a Windows command line.
|
|
|
+ * Returns a newly allocated string */
|
|
|
+static char *
|
|
|
+format_cmdline_argument(const char *arg)
|
|
|
+{
|
|
|
+ char *formatted_arg;
|
|
|
+ char need_quotes;
|
|
|
+ const char *c;
|
|
|
+ int i;
|
|
|
+ int bs_counter = 0;
|
|
|
+ /* Backslash we can point to when one is inserted into the string */
|
|
|
+ const char backslash = '\\';
|
|
|
+
|
|
|
+ /* Smartlist of *char */
|
|
|
+ smartlist_t *arg_chars;
|
|
|
+ arg_chars = smartlist_create();
|
|
|
+
|
|
|
+ /* Quote string if it contains whitespace or is empty */
|
|
|
+ need_quotes = (strchr(arg, ' ') || strchr(arg, '\t') || '\0' == arg[0]);
|
|
|
+
|
|
|
+ /* Build up smartlist of *chars */
|
|
|
+ for (c=arg; *c != '\0'; c++) {
|
|
|
+ if ('"' == *c) {
|
|
|
+ /* Double up backslashes preceding a quote */
|
|
|
+ for (i=0; i<(bs_counter*2); i++)
|
|
|
+ smartlist_add(arg_chars, (void*)&backslash);
|
|
|
+ bs_counter = 0;
|
|
|
+ /* Escape the quote */
|
|
|
+ smartlist_add(arg_chars, (void*)&backslash);
|
|
|
+ smartlist_add(arg_chars, (void*)c);
|
|
|
+ } else if ('\\' == *c) {
|
|
|
+ /* Count backslashes until we know whether to double up */
|
|
|
+ bs_counter++;
|
|
|
+ } else {
|
|
|
+ /* Don't double up slashes preceding a non-quote */
|
|
|
+ for (i=0; i<bs_counter; i++)
|
|
|
+ smartlist_add(arg_chars, (void*)&backslash);
|
|
|
+ bs_counter = 0;
|
|
|
+ smartlist_add(arg_chars, (void*)c);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ /* Don't double up trailing backslashes */
|
|
|
+ for (i=0; i<bs_counter; i++)
|
|
|
+ smartlist_add(arg_chars, (void*)&backslash);
|
|
|
+
|
|
|
+ /* Allocate space for argument, quotes (if needed), and terminator */
|
|
|
+ formatted_arg = tor_malloc(sizeof(char) *
|
|
|
+ (smartlist_len(arg_chars) + (need_quotes?2:0) + 1));
|
|
|
+
|
|
|
+ /* Add leading quote */
|
|
|
+ i=0;
|
|
|
+ if (need_quotes)
|
|
|
+ formatted_arg[i++] = '"';
|
|
|
+
|
|
|
+ /* Add characters */
|
|
|
+ SMARTLIST_FOREACH(arg_chars, char*, c,
|
|
|
+ {
|
|
|
+ formatted_arg[i++] = *c;
|
|
|
+ });
|
|
|
+
|
|
|
+ /* Add trailing quote */
|
|
|
+ if (need_quotes)
|
|
|
+ formatted_arg[i++] = '"';
|
|
|
+ formatted_arg[i] = '\0';
|
|
|
+
|
|
|
+ smartlist_free(arg_chars);
|
|
|
+ return formatted_arg;
|
|
|
+}
|
|
|
+
|
|
|
+/* Format a command line for use on Windows, which takes the command as a
|
|
|
+ * string rather than string array. Follows the rules from "Parsing C++
|
|
|
+ * Command-Line Arguments" in MSDN. Algorithm based on list2cmdline in the
|
|
|
+ * Python subprocess module. Returns a newly allocated string */
|
|
|
+char *
|
|
|
+tor_join_cmdline(const char *argv[])
|
|
|
+{
|
|
|
+ smartlist_t *argv_list;
|
|
|
+ char *joined_argv;
|
|
|
+ int i;
|
|
|
+
|
|
|
+ /* Format each argument and put the result in a smartlist */
|
|
|
+ argv_list = smartlist_create();
|
|
|
+ for (i=0; argv[i] != NULL; i++) {
|
|
|
+ smartlist_add(argv_list, (void *)format_cmdline_argument(argv[i]));
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Join the arguments with whitespace */
|
|
|
+ joined_argv = smartlist_join_strings(argv_list, " ", 0, NULL);
|
|
|
+
|
|
|
+ /* Free the newly allocated arguments, and the smartlist */
|
|
|
+ SMARTLIST_FOREACH(argv_list, char *, arg,
|
|
|
+ {
|
|
|
+ tor_free(arg);
|
|
|
+ });
|
|
|
+ smartlist_free(argv_list);
|
|
|
+
|
|
|
+ return joined_argv;
|
|
|
+}
|
|
|
+
|
|
|
/** Format <b>child_state</b> and <b>saved_errno</b> as a hex string placed in
|
|
|
* <b>hex_errno</b>. Called between fork and _exit, so must be signal-handler
|
|
|
* safe.
|
|
@@ -3038,28 +3137,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. A handle to the child process
|
|
|
+ * will be returned in process_handle (which must be non-NULL). Read
|
|
|
+ * process_handle.status to find out if the process was successfully launched.
|
|
|
+ * For convenience, process_handle.status is returned by this function.
|
|
|
+ *
|
|
|
+ * 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)
|
|
|
+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;
|
|
|
+ char *joined_argv;
|
|
|
+
|
|
|
+ /* process_handle must not be NULL */
|
|
|
+ tor_assert(process_handle != NULL);
|
|
|
+
|
|
|
+ saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
|
|
|
+ saAttr.bInheritHandle = TRUE;
|
|
|
+ /* TODO: should we set explicit security attributes? (#2046, comment 5) */
|
|
|
+ saAttr.lpSecurityDescriptor = NULL;
|
|
|
+
|
|
|
+ /* Assume failure to start process */
|
|
|
+ memset(process_handle, 0, sizeof(process_handle_t));
|
|
|
+ process_handle->status = PROCESS_STATUS_ERROR;
|
|
|
+
|
|
|
+ /* 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->status;
|
|
|
+ }
|
|
|
+ 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->status;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 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->status;
|
|
|
+ }
|
|
|
+ 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->status;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Create the child process */
|
|
|
+
|
|
|
+ /* Windows expects argv to be a whitespace delimited string, so join argv up
|
|
|
+ */
|
|
|
+ joined_argv = tor_join_cmdline(argv);
|
|
|
+
|
|
|
+ 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
|
|
|
+ /* TODO: should we set explicit security attributes? (#2046, comment 5) */
|
|
|
+ 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 = PROCESS_STATUS_RUNNING;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* TODO: Close pipes on exit */
|
|
|
+
|
|
|
+ return process_handle->status;
|
|
|
+#else // MS_WINDOWS
|
|
|
pid_t pid;
|
|
|
int stdout_pipe[2];
|
|
|
int stderr_pipe[2];
|
|
@@ -3077,6 +3277,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_t));
|
|
|
+ process_handle->status = PROCESS_STATUS_ERROR;
|
|
|
+
|
|
|
/* 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);
|
|
@@ -3089,7 +3293,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->status;
|
|
|
}
|
|
|
|
|
|
retval = pipe(stderr_pipe);
|
|
@@ -3097,7 +3301,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->status;
|
|
|
}
|
|
|
|
|
|
child_state = CHILD_STATE_MAXFD;
|
|
@@ -3176,13 +3380,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 */
|
|
|
+ /* Never reached, but avoids compiler warning */
|
|
|
+ return process_handle->status;
|
|
|
}
|
|
|
|
|
|
/* In parent */
|
|
@@ -3193,36 +3399,309 @@ 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->status;
|
|
|
}
|
|
|
|
|
|
+ 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 = PROCESS_STATUS_RUNNING;
|
|
|
+ /* 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->status;
|
|
|
+#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
|
|
|
+ * PROCESS_EXIT_RUNNING, and exit_code will be left unchanged. Returns
|
|
|
+ * PROCESS_EXIT_EXITED if the process did exit. If there is a failure,
|
|
|
+ * PROCESS_EXIT_ERROR 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 PROCESS_EXIT_ERROR;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ retval = WaitForSingleObject(process_handle.pid.hProcess, 0);
|
|
|
+ if (WAIT_TIMEOUT == retval) {
|
|
|
+ /* Process has not exited */
|
|
|
+ return PROCESS_EXIT_RUNNING;
|
|
|
+ } else if (retval != WAIT_OBJECT_0) {
|
|
|
+ log_warn(LD_GENERAL, "WaitForSingleObject() failed (%d): %s",
|
|
|
+ (int)retval, format_win32_error(GetLastError()));
|
|
|
+ return PROCESS_EXIT_ERROR;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ 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 PROCESS_EXIT_ERROR;
|
|
|
+ }
|
|
|
+ }
|
|
|
+#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 PROCESS_EXIT_RUNNING;
|
|
|
+ } else if (retval != process_handle.pid) {
|
|
|
+ log_warn(LD_GENERAL, "waitpid() failed for PID %d: %s", process_handle.pid,
|
|
|
+ strerror(errno));
|
|
|
+ return PROCESS_EXIT_ERROR;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!WIFEXITED(stat_loc)) {
|
|
|
+ log_warn(LD_GENERAL, "Process %d did not exit normally",
|
|
|
+ process_handle.pid);
|
|
|
+ return PROCESS_EXIT_ERROR;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (exit_code != NULL)
|
|
|
+ *exit_code = WEXITSTATUS(stat_loc);
|
|
|
+#endif // MS_WINDOWS
|
|
|
+
|
|
|
+ return PROCESS_EXIT_EXITED;
|
|
|
+}
|
|
|
+
|
|
|
+#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;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* There is data to read; read it */
|
|
|
+ retval = ReadFile(h, buf+numread, count-numread, &byte_count, NULL);
|
|
|
+ tor_assert(byte_count + numread <= count);
|
|
|
+ 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
|
|
|
}
|
|
|
|
|
|
+/* Split buf into lines, and add to smartlist. The buffer <b>buf</b> will be
|
|
|
+ * modified. The resulting smartlist will consist of pointers to buf, so there
|
|
|
+ * is no need to free the contents of sl. <b>buf</b> must be a NULL terminated
|
|
|
+ * string. <b>len</b> should be set to the length of the buffer excluding the
|
|
|
+ * NULL. Non-printable characters (including NULL) will be replaced with "." */
|
|
|
+
|
|
|
+int
|
|
|
+tor_split_lines(smartlist_t *sl, char *buf, int len)
|
|
|
+{
|
|
|
+ /* Index in buf of the start of the current line */
|
|
|
+ int start = 0;
|
|
|
+ /* Index in buf of the current character being processed */
|
|
|
+ int cur = 0;
|
|
|
+ /* Are we currently in a line */
|
|
|
+ char in_line = 0;
|
|
|
+
|
|
|
+ /* Loop over string */
|
|
|
+ while (cur < len) {
|
|
|
+ /* Loop until end of line or end of string */
|
|
|
+ for (; cur < len; cur++) {
|
|
|
+ if (in_line) {
|
|
|
+ if ('\r' == buf[cur] || '\n' == buf[cur]) {
|
|
|
+ /* End of line */
|
|
|
+ buf[cur] = '\0';
|
|
|
+ /* Point cur to the next line */
|
|
|
+ cur++;
|
|
|
+ /* Line starts at start and ends with a null */
|
|
|
+ break;
|
|
|
+ } else {
|
|
|
+ if (!TOR_ISPRINT(buf[cur]))
|
|
|
+ buf[cur] = '.';
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if ('\r' == buf[cur] || '\n' == buf[cur]) {
|
|
|
+ /* Skip leading vertical space */
|
|
|
+ ;
|
|
|
+ } else {
|
|
|
+ in_line = 1;
|
|
|
+ start = cur;
|
|
|
+ if (!TOR_ISPRINT(buf[cur]))
|
|
|
+ buf[cur] = '.';
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ /* We are at the end of the line or end of string. If in_line is true there
|
|
|
+ * is a line which starts at buf+start and ends at a NULL. cur points to
|
|
|
+ * the character after the NULL. */
|
|
|
+ if (in_line)
|
|
|
+ smartlist_add(sl, (void *)(buf+start));
|
|
|
+ in_line = 0;
|
|
|
+ }
|
|
|
+ return smartlist_len(sl);
|
|
|
+}
|
|
|
+
|
|
|
+#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.
|
|
|
+ * If the generated stream is flushed more often than on new lines, or
|
|
|
+ * a read exceeds 256 bytes, lines will be truncated. This should be fixed,
|
|
|
+ * along with the corresponding problem on *nix (see bug #2045).
|
|
|
+ */
|
|
|
+static int
|
|
|
+log_from_handle(HANDLE *pipe, int severity)
|
|
|
+{
|
|
|
+ char buf[256];
|
|
|
+ int pos;
|
|
|
+ smartlist_t *lines;
|
|
|
+
|
|
|
+ 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 up the buffer */
|
|
|
+ lines = smartlist_create();
|
|
|
+ tor_split_lines(lines, buf, pos);
|
|
|
+
|
|
|
+ /* Log each line */
|
|
|
+ SMARTLIST_FOREACH(lines, char *, line,
|
|
|
+ {
|
|
|
+ log_fn(severity, LD_GENERAL, "Port forwarding helper says: %s", line);
|
|
|
+ });
|
|
|
+ smartlist_free(lines);
|
|
|
+
|
|
|
+ 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
|
|
@@ -3298,26 +3777,22 @@ 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 */
|
|
|
+ static process_handle_t child_handle;
|
|
|
+
|
|
|
static time_t time_to_run_helper = 0;
|
|
|
int stdout_status, stderr_status, retval;
|
|
|
const char *argv[10];
|
|
@@ -3342,37 +3817,48 @@ 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 &&
|
|
|
+ if (child_handle.status != PROCESS_STATUS_RUNNING &&
|
|
|
time_to_run_helper < now) {
|
|
|
- int fd_out=-1, fd_err=-1;
|
|
|
-
|
|
|
/* 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 */
|
|
|
+ tor_spawn_background(NULL, argv, &child_handle);
|
|
|
+#else
|
|
|
+ tor_spawn_background(filename, argv, &child_handle);
|
|
|
+#endif
|
|
|
+ if (PROCESS_STATUS_ERROR == child_handle.status) {
|
|
|
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 (PROCESS_STATUS_RUNNING == child_handle.status) {
|
|
|
/* 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;
|
|
@@ -3382,9 +3868,22 @@ 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) !=
|
|
|
+ PROCESS_EXIT_RUNNING) {
|
|
|
+ /* process has exited or there was an error */
|
|
|
+ /* 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;
|
|
@@ -3393,15 +3892,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 = PROCESS_STATUS_NOTRUNNING;
|
|
|
} else {
|
|
|
log_warn(LD_GENERAL, "Failed to read from port forwarding helper");
|
|
|
+ child_handle.status = PROCESS_STATUS_ERROR;
|
|
|
}
|
|
|
|
|
|
/* 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
|
|
|
}
|
|
|
|