[tor-commits] [tor/master] Add Windows backend for the Process subsystem.

nickm at torproject.org nickm at torproject.org
Tue Dec 18 18:36:43 UTC 2018


commit bb784cf4f36256a8276ba60641d3ff766b9cd9df
Author: Alexander Færøy <ahf at torproject.org>
Date:   Thu Nov 22 04:49:23 2018 +0100

    Add Windows backend for the Process subsystem.
    
    This patch adds support for Microsoft Windows in the Process subsystem.
    
    Libevent does not support mixing different types of handles (sockets,
    named pipes, etc.) on Windows in its core event loop code. This have
    historically meant that Tor have avoided attaching any non-networking
    handles to the event loop. This patch uses a slightly different approach
    to roughly support the same features for the Process subsystem as we do
    with the Unix backend.
    
    In this patch we use Windows Extended I/O functions (ReadFileEx() and
    WriteFileEx()) which executes asynchronously in the background and
    executes a completion routine when the scheduled read or write operation
    have completed. This is much different from the Unix backend where the
    operating system signals to us whenever a file descriptor is "ready" to
    either being read from or written to.
    
    To make the Windows operating system execute the completion routines of
    ReadFileEx() and WriteFileEx() we must get the Tor process into what
    Microsoft calls an "alertable" state. To do this we execute SleepEx()
    with a zero millisecond sleep time from a main loop timer that ticks
    once a second.  This moves the process into the "alertable" state and
    when we return from the zero millisecond timeout all the outstanding I/O
    completion routines will be called and we can schedule the next reads
    and writes.
    
    The timer loop is also responsible for detecting whether our child
    processes have terminated since the last timer tick.
    
    See: https://bugs.torproject.org/28179
---
 src/lib/process/include.am      |   2 +
 src/lib/process/process.c       |  35 +-
 src/lib/process/process.h       |   3 +
 src/lib/process/process_win32.c | 857 ++++++++++++++++++++++++++++++++++++++++
 src/lib/process/process_win32.h |  90 +++++
 src/test/test_process.c         |  23 +-
 6 files changed, 1007 insertions(+), 3 deletions(-)

diff --git a/src/lib/process/include.am b/src/lib/process/include.am
index f06803268..aa7335614 100644
--- a/src/lib/process/include.am
+++ b/src/lib/process/include.am
@@ -11,6 +11,7 @@ src_lib_libtor_process_a_SOURCES =		\
 	src/lib/process/pidfile.c		\
 	src/lib/process/process.c		\
 	src/lib/process/process_unix.c		\
+	src/lib/process/process_win32.c		\
 	src/lib/process/restrict.c		\
 	src/lib/process/setuid.c		\
 	src/lib/process/subprocess.c		\
@@ -28,6 +29,7 @@ noinst_HEADERS +=				\
 	src/lib/process/pidfile.h		\
 	src/lib/process/process.h		\
 	src/lib/process/process_unix.h		\
+	src/lib/process/process_win32.h		\
 	src/lib/process/restrict.h		\
 	src/lib/process/setuid.h		\
 	src/lib/process/subprocess.h		\
diff --git a/src/lib/process/process.c b/src/lib/process/process.c
index 8d6a9d3fa..d4237b2b1 100644
--- a/src/lib/process/process.c
+++ b/src/lib/process/process.c
@@ -16,6 +16,7 @@
 #include "lib/log/util_bug.h"
 #include "lib/process/process.h"
 #include "lib/process/process_unix.h"
+#include "lib/process/process_win32.h"
 #include "lib/process/env.h"
 
 #ifdef HAVE_STDDEF_H
@@ -69,6 +70,9 @@ struct process_t {
 #ifndef _WIN32
   /** Our Unix process handle. */
   process_unix_t *unix_process;
+#else
+  /** Our Win32 process handle. */
+  process_win32_t *win32_process;
 #endif
 };
 
@@ -117,6 +121,10 @@ void
 process_init(void)
 {
   processes = smartlist_new();
+
+#ifdef _WIN32
+  process_win32_init();
+#endif
 }
 
 /** Free up all resources that is handled by the Process subsystem. Note that
@@ -124,6 +132,10 @@ process_init(void)
 void
 process_free_all(void)
 {
+#ifdef _WIN32
+  process_win32_deinit();
+#endif
+
   SMARTLIST_FOREACH(processes, process_t *, x, process_free(x));
   smartlist_free(processes);
 }
@@ -170,6 +182,9 @@ process_new(const char *command)
 #ifndef _WIN32
   /* Prepare our Unix process handle. */
   process->unix_process = process_unix_new();
+#else
+  /* Prepare our Win32 process handle. */
+  process->win32_process = process_win32_new();
 #endif
 
   smartlist_add(processes, process);
@@ -202,6 +217,9 @@ process_free_(process_t *process)
 #ifndef _WIN32
   /* Cleanup our Unix process handle. */
   process_unix_free(process->unix_process);
+#else
+  /* Cleanup our Win32 process handle. */
+  process_win32_free(process->win32_process);
 #endif
 
   smartlist_remove(processes, process);
@@ -222,6 +240,8 @@ process_exec(process_t *process)
 
 #ifndef _WIN32
   status = process_unix_exec(process);
+#else
+  status = process_win32_exec(process);
 #endif
 
   /* Update our state. */
@@ -420,6 +440,15 @@ process_get_unix_process(const process_t *process)
   tor_assert(process->unix_process);
   return process->unix_process;
 }
+#else
+/** Get the internal handle for Windows backend. */
+process_win32_t *
+process_get_win32_process(const process_t *process)
+{
+  tor_assert(process);
+  tor_assert(process->win32_process);
+  return process->win32_process;
+}
 #endif
 
 /** Write <b>size</b> bytes of <b>data</b> to the given process's standard
@@ -544,7 +573,7 @@ MOCK_IMPL(STATIC int, process_read_stdout, (process_t *process, buf_t *buffer))
 #ifndef _WIN32
   return process_unix_read_stdout(process, buffer);
 #else
-  return 0;
+  return process_win32_read_stdout(process, buffer);
 #endif
 }
 
@@ -559,7 +588,7 @@ MOCK_IMPL(STATIC int, process_read_stderr, (process_t *process, buf_t *buffer))
 #ifndef _WIN32
   return process_unix_read_stderr(process, buffer);
 #else
-  return 0;
+  return process_win32_read_stderr(process, buffer);
 #endif
 }
 
@@ -573,6 +602,8 @@ MOCK_IMPL(STATIC void, process_write_stdin,
 
 #ifndef _WIN32
   process_unix_write(process, buffer);
+#else
+  process_win32_write(process, buffer);
 #endif
 }
 
diff --git a/src/lib/process/process.h b/src/lib/process/process.h
index b17b8dac7..f759c7193 100644
--- a/src/lib/process/process.h
+++ b/src/lib/process/process.h
@@ -98,6 +98,9 @@ process_status_t process_get_status(const process_t *process);
 #ifndef _WIN32
 struct process_unix_t;
 struct process_unix_t *process_get_unix_process(const process_t *process);
+#else
+struct process_win32_t;
+struct process_win32_t *process_get_win32_process(const process_t *process);
 #endif
 
 void process_write(process_t *process,
diff --git a/src/lib/process/process_win32.c b/src/lib/process/process_win32.c
new file mode 100644
index 000000000..a019e0b4f
--- /dev/null
+++ b/src/lib/process/process_win32.c
@@ -0,0 +1,857 @@
+/* Copyright (c) 2003, Roger Dingledine
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file process_win32.c
+ * \brief Module for working with Windows processes.
+ **/
+
+#define PROCESS_WIN32_PRIVATE
+#include "lib/intmath/cmp.h"
+#include "lib/container/buffers.h"
+#include "lib/net/buffers_net.h"
+#include "lib/container/smartlist.h"
+#include "lib/log/log.h"
+#include "lib/log/util_bug.h"
+#include "lib/log/win32err.h"
+#include "lib/process/process.h"
+#include "lib/process/process_win32.h"
+#include "lib/process/subprocess.h"
+#include "lib/process/env.h"
+
+#ifdef HAVE_SYS_TIME_H
+#include <sys/time.h>
+#endif
+
+#ifdef _WIN32
+
+/** The size of our intermediate buffers. */
+#define BUFFER_SIZE (1024)
+
+/** Timer that ticks once a second and calls the process_win32_timer_callback()
+ * function. */
+static periodic_timer_t *periodic_timer;
+
+/** Structure to represent the state around the pipe HANDLE.
+ *
+ * This structure is used to store state about a given HANDLE, including
+ * whether we have reached end of file, its intermediate buffers, and how much
+ * data that is available in the intermediate buffer. */
+struct process_win32_handle_t {
+  /** Standard out pipe handle. */
+  HANDLE pipe;
+
+  /** True iff we have reached EOF from the pipe. */
+  bool reached_eof;
+
+  /** How much data is available in buffer. */
+  size_t data_available;
+
+  /** Intermediate buffer for ReadFileEx() and WriteFileEx(). */
+  char buffer[BUFFER_SIZE];
+
+  /** Overlapped structure for ReadFileEx() and WriteFileEx(). */
+  OVERLAPPED overlapped;
+
+  /** Are we waiting for another I/O operation to complete? */
+  bool busy;
+};
+
+/** Structure to represent the Windows specific implementation details of this
+ * Process backend.
+ *
+ * This structure is attached to <b>process_t</b> (see process.h) and is
+ * reachable from <b>process_t</b> via the <b>process_get_win32_process()</b>
+ * method. */
+struct process_win32_t {
+  /** Standard in state. */
+  process_win32_handle_t stdin_handle;
+
+  /** Standard out state. */
+  process_win32_handle_t stdout_handle;
+
+  /** Standard error state. */
+  process_win32_handle_t stderr_handle;
+
+  /** Process Information. */
+  PROCESS_INFORMATION process_information;
+};
+
+/** Create a new <b>process_win32_t</b>.
+ *
+ * This function constructs a new <b>process_win32_t</b> and initializes the
+ * default values. */
+process_win32_t *
+process_win32_new(void)
+{
+  process_win32_t *win32_process;
+  win32_process = tor_malloc_zero(sizeof(process_win32_t));
+
+  win32_process->stdin_handle.pipe = INVALID_HANDLE_VALUE;
+  win32_process->stdout_handle.pipe = INVALID_HANDLE_VALUE;
+  win32_process->stderr_handle.pipe = INVALID_HANDLE_VALUE;
+
+  return win32_process;
+}
+
+/** Free a given <b>process_win32_t</b>.
+ *
+ * This function deinitializes and frees up the resources allocated for the
+ * given <b>process_win32_t</b>. */
+void
+process_win32_free_(process_win32_t *win32_process)
+{
+  if (! win32_process)
+    return;
+
+  /* Cleanup our handles. */
+  process_win32_cleanup_handle(&win32_process->stdin_handle);
+  process_win32_cleanup_handle(&win32_process->stdout_handle);
+  process_win32_cleanup_handle(&win32_process->stderr_handle);
+
+  tor_free(win32_process);
+}
+
+/** Initialize the Windows backend of the Process subsystem. */
+void
+process_win32_init(void)
+{
+  /* We don't start the periodic timer here because it makes no sense to have
+   * the timer running until we have some processes that benefits from the
+   * timer timer ticks. */
+}
+
+/** Deinitialize the Windows backend of the Process subsystem. */
+void
+process_win32_deinit(void)
+{
+  /* Stop our timer, but only if it's running. */
+  if (process_win32_timer_running())
+    process_win32_timer_stop();
+}
+
+/** Execute the given process. This function is responsible for setting up
+ * named pipes for I/O between the child process and the Tor process. Returns
+ * <b>PROCESS_STATUS_RUNNING</b> upon success. */
+process_status_t
+process_win32_exec(process_t *process)
+{
+  tor_assert(process);
+
+  process_win32_t *win32_process = process_get_win32_process(process);
+
+  HANDLE stdout_pipe_read = NULL;
+  HANDLE stdout_pipe_write = NULL;
+  HANDLE stderr_pipe_read = NULL;
+  HANDLE stderr_pipe_write = NULL;
+  HANDLE stdin_pipe_read = NULL;
+  HANDLE stdin_pipe_write = NULL;
+  BOOL ret = FALSE;
+  const char *filename = process_get_command(process);
+
+  /* Not much we can do if we haven't been told what to start. */
+  if (BUG(filename == NULL))
+    return PROCESS_STATUS_ERROR;
+
+  /* Setup our security attributes. */
+  SECURITY_ATTRIBUTES security_attributes;
+  security_attributes.nLength = sizeof(security_attributes);
+  security_attributes.bInheritHandle = TRUE;
+  /* FIXME: should we set explicit security attributes?
+   * (See Ticket #2046, comment 5) */
+  security_attributes.lpSecurityDescriptor = NULL;
+
+  /* Create our standard out pipe. */
+  if (! process_win32_create_pipe(&stdout_pipe_read,
+                                  &stdout_pipe_write,
+                                  &security_attributes,
+                                  PROCESS_WIN32_PIPE_TYPE_READER)) {
+    return PROCESS_STATUS_ERROR;
+  }
+
+  /* Create our standard error pipe. */
+  if (! process_win32_create_pipe(&stderr_pipe_read,
+                                  &stderr_pipe_write,
+                                  &security_attributes,
+                                  PROCESS_WIN32_PIPE_TYPE_READER)) {
+    return PROCESS_STATUS_ERROR;
+  }
+
+  /* Create out standard in pipe. */
+  if (! process_win32_create_pipe(&stdin_pipe_read,
+                                  &stdin_pipe_write,
+                                  &security_attributes,
+                                  PROCESS_WIN32_PIPE_TYPE_WRITER)) {
+    return PROCESS_STATUS_ERROR;
+  }
+
+  /* Configure startup info for our child process. */
+  STARTUPINFOA startup_info;
+
+  memset(&startup_info, 0, sizeof(startup_info));
+  startup_info.cb = sizeof(startup_info);
+  startup_info.hStdError = stderr_pipe_write;
+  startup_info.hStdOutput = stdout_pipe_write;
+  startup_info.hStdInput = stdin_pipe_read;
+  startup_info.dwFlags |= STARTF_USESTDHANDLES;
+
+  /* Create the env value for our new process. */
+  process_environment_t *env = process_get_environment(process);
+
+  /* Create the argv value for our new process. */
+  char **argv = process_get_argv(process);
+
+  /* Windows expects argv to be a whitespace delimited string, so join argv up
+   */
+  char *joined_argv = tor_join_win_cmdline((const char **)argv);
+
+  /* Create the child process */
+  ret = CreateProcessA(filename,
+                       joined_argv,
+                       NULL,
+                       NULL,
+                       TRUE,
+                       CREATE_NO_WINDOW,
+                       env->windows_environment_block[0] == '\0' ?
+                         NULL : env->windows_environment_block,
+                       NULL,
+                       &startup_info,
+                       &win32_process->process_information);
+
+  tor_free(argv);
+  tor_free(joined_argv);
+  process_environment_free(env);
+
+  if (! ret) {
+    log_warn(LD_PROCESS, "CreateProcessA() failed: %s",
+      format_win32_error(GetLastError()));
+
+    /* Cleanup our handles. */
+    CloseHandle(stdout_pipe_read);
+    CloseHandle(stdout_pipe_write);
+    CloseHandle(stderr_pipe_read);
+    CloseHandle(stderr_pipe_write);
+    CloseHandle(stdin_pipe_read);
+    CloseHandle(stdin_pipe_write);
+
+    return PROCESS_STATUS_ERROR;
+  }
+
+  /* TODO: Should we close hProcess and hThread in
+   * process_handle->process_information? */
+  win32_process->stdout_handle.pipe = stdout_pipe_read;
+  win32_process->stderr_handle.pipe = stderr_pipe_read;
+  win32_process->stdin_handle.pipe = stdin_pipe_write;
+
+  /* Used by the callback functions from ReadFileEx() and WriteFileEx() such
+   * that we can figure out which process_t that was responsible for the event.
+   *
+   * Warning, here be dragons:
+   *
+   *   MSDN says that the hEvent member of the overlapped structure is unused
+   *   for ReadFileEx() and WriteFileEx, which allows us to store a pointer to
+   *   our process state there.
+   */
+  win32_process->stdout_handle.overlapped.hEvent = (HANDLE)process;
+  win32_process->stderr_handle.overlapped.hEvent = (HANDLE)process;
+  win32_process->stdin_handle.overlapped.hEvent = (HANDLE)process;
+
+  /* Start our timer if it is not already running. */
+  if (! process_win32_timer_running())
+    process_win32_timer_start();
+
+  /* We use Windows Extended I/O functions, so our completion callbacks are
+   * called automatically for us when there is data to read. Because of this
+   * we start the read of standard out and error right away. */
+  process_notify_event_stdout(process);
+  process_notify_event_stderr(process);
+
+  return PROCESS_STATUS_RUNNING;
+}
+
+/** Schedule an async write of the data found in <b>buffer</b> for the given
+ * process.  This function runs an async write operation of the content of
+ * buffer, if we are not already waiting for a pending I/O request. Returns the
+ * number of bytes that Windows will hopefully write for us in the background.
+ * */
+int
+process_win32_write(struct process_t *process, buf_t *buffer)
+{
+  tor_assert(process);
+  tor_assert(buffer);
+
+  process_win32_t *win32_process = process_get_win32_process(process);
+  BOOL ret = FALSE;
+  const size_t buffer_size = buf_datalen(buffer);
+
+  /* Windows is still writing our buffer. */
+  if (win32_process->stdin_handle.busy)
+    return 0;
+
+  /* Nothing for us to do right now. */
+  if (buffer_size == 0)
+    return 0;
+
+  /* We have reached end of file already? */
+  if (BUG(win32_process->stdin_handle.reached_eof))
+    return 0;
+
+  /* Figure out how much data we should read. */
+  const size_t write_size = MIN(buffer_size,
+                                sizeof(win32_process->stdin_handle.buffer));
+
+  /* Read data from the process_t buffer into our intermediate buffer. */
+  buf_get_bytes(buffer, win32_process->stdin_handle.buffer, write_size);
+
+  /* Schedule our write. */
+  ret = WriteFileEx(win32_process->stdin_handle.pipe,
+                    win32_process->stdin_handle.buffer,
+                    write_size,
+                    &win32_process->stdin_handle.overlapped,
+                    process_win32_stdin_write_done);
+
+  if (! ret) {
+    log_warn(LD_PROCESS, "WriteFileEx() failed: %s",
+             format_win32_error(GetLastError()));
+    return 0;
+  }
+
+  /* This cast should be safe since our buffer can maximum be BUFFER_SIZE
+   * large. */
+  return (int)write_size;
+}
+
+/** This function is called from the Process subsystem whenever the Windows
+ * backend says it has data ready. This function also ensures that we are
+ * starting a new background read from the standard output of the child process
+ * and asks Windows to call process_win32_stdout_read_done() when that
+ * operation is finished. Returns the number of bytes moved into <b>buffer</b>.
+ * */
+int
+process_win32_read_stdout(struct process_t *process, buf_t *buffer)
+{
+  tor_assert(process);
+  tor_assert(buffer);
+
+  process_win32_t *win32_process = process_get_win32_process(process);
+
+  return process_win32_read_from_handle(&win32_process->stdout_handle,
+                                        buffer,
+                                        process_win32_stdout_read_done);
+}
+
+/** This function is called from the Process subsystem whenever the Windows
+ * backend says it has data ready. This function also ensures that we are
+ * starting a new background read from the standard error of the child process
+ * and asks Windows to call process_win32_stderr_read_done() when that
+ * operation is finished. Returns the number of bytes moved into <b>buffer</b>.
+ * */
+int
+process_win32_read_stderr(struct process_t *process, buf_t *buffer)
+{
+  tor_assert(process);
+  tor_assert(buffer);
+
+  process_win32_t *win32_process = process_get_win32_process(process);
+
+  return process_win32_read_from_handle(&win32_process->stderr_handle,
+                                        buffer,
+                                        process_win32_stderr_read_done);
+}
+
+/** This function is responsible for moving the Tor process into what Microsoft
+ * calls an "alertable" state. Once the process is in an alertable state the
+ * Windows kernel will notify us when our background I/O requests have finished
+ * and the callbacks will be executed. */
+void
+process_win32_trigger_completion_callbacks(void)
+{
+  DWORD ret;
+
+  /* The call to SleepEx(dwMilliseconds, dwAlertable) makes the process sleep
+   * for dwMilliseconds and if dwAlertable is set to TRUE it will also cause
+   * the process to enter alertable state, where the Windows kernel will notify
+   * us about completed I/O requests from ReadFileEx() and WriteFileEX(), which
+   * will cause our completion callbacks to be executed.
+   *
+   * This function returns 0 if the time interval expired or WAIT_IO_COMPLETION
+   * if one or more I/O callbacks were executed. */
+  ret = SleepEx(0, TRUE);
+
+  /* Warn us if the function returned something we did not anticipate. */
+  if (ret != 0 && ret != WAIT_IO_COMPLETION) {
+    log_warn(LD_PROCESS, "SleepEx() returned %lu", ret);
+  }
+}
+
+/** Start the periodic timer which is reponsible for checking whether processes
+ * are still alive and to make sure that the Tor process is periodically being
+ * moved into an alertable state. */
+STATIC void
+process_win32_timer_start(void)
+{
+  /* Make sure we never start our timer if it's already running. */
+  if (BUG(process_win32_timer_running()))
+    return;
+
+  /* Wake up once a second. */
+  static const struct timeval interval = {1, 0};
+
+  log_info(LD_PROCESS, "Starting Windows Process I/O timer");
+  periodic_timer = periodic_timer_new(tor_libevent_get_base(),
+                                      &interval,
+                                      process_win32_timer_callback,
+                                      NULL);
+}
+
+/** Stops the periodic timer. */
+STATIC void
+process_win32_timer_stop(void)
+{
+  if (BUG(periodic_timer == NULL))
+    return;
+
+  log_info(LD_PROCESS, "Stopping Windows Process I/O timer");
+  periodic_timer_free(periodic_timer);
+}
+
+/** Returns true iff the periodic timer is running. */
+STATIC bool
+process_win32_timer_running(void)
+{
+  return periodic_timer != NULL;
+}
+
+/** This function is called whenever the periodic_timer ticks. The function is
+ * responsible for moving the Tor process into an alertable state once a second
+ * and checking for whether our child processes have terminated since the last
+ * tick. */
+STATIC void
+process_win32_timer_callback(periodic_timer_t *timer, void *data)
+{
+  tor_assert(timer == periodic_timer);
+  tor_assert(data == NULL);
+
+  log_debug(LD_PROCESS, "Windows Process I/O timer ticked");
+  const smartlist_t *processes = process_get_all_processes();
+
+  SMARTLIST_FOREACH(processes, process_t *, p,
+                    process_win32_timer_test_process(p));
+
+  process_win32_trigger_completion_callbacks();
+}
+
+/** Test whether a given process is still alive. Notify the Process subsystem
+ * if our process have died. */
+STATIC void
+process_win32_timer_test_process(process_t *process)
+{
+  tor_assert(process);
+
+  /* No need to look at processes that don't claim they are running. */
+  if (process_get_status(process) != PROCESS_STATUS_RUNNING)
+    return;
+
+  process_win32_t *win32_process = process_get_win32_process(process);
+  BOOL ret = FALSE;
+  DWORD exit_code = 0;
+
+  /* We start by testing whether our process is still running. */
+  ret = GetExitCodeProcess(win32_process->process_information.hProcess,
+                           &exit_code);
+
+  if (! ret) {
+    log_warn(LD_PROCESS, "GetExitCodeProcess() failed: %s",
+             format_win32_error(GetLastError()));
+    return;
+  }
+
+  /* Notify our process_t that our process have terminated. */
+  if (exit_code != STILL_ACTIVE)
+    process_notify_event_exit(process, exit_code);
+}
+
+/** Create a new overlapped named pipe. This function creates a new connected,
+ * named, pipe in <b>*read_pipe</b> and <b>*write_pipe</b> if the function is
+ * succesful. Returns true on sucess, false on failure. */
+STATIC bool
+process_win32_create_pipe(HANDLE *read_pipe,
+                          HANDLE *write_pipe,
+                          SECURITY_ATTRIBUTES *attributes,
+                          process_win32_pipe_type_t pipe_type)
+{
+  tor_assert(read_pipe);
+  tor_assert(write_pipe);
+  tor_assert(attributes);
+
+  BOOL ret = FALSE;
+
+  /* Buffer size. */
+  const size_t size = 4096;
+
+  /* Our additional read/write modes that depends on which pipe type we are
+   * creating. */
+  DWORD read_mode = 0;
+  DWORD write_mode = 0;
+
+  /* Generate the unique pipe name. */
+  char pipe_name[MAX_PATH];
+  static DWORD process_id = 0;
+  static DWORD counter = 0;
+
+  if (process_id == 0)
+    process_id = GetCurrentProcessId();
+
+  tor_snprintf(pipe_name, sizeof(pipe_name),
+               "\\\\.\\Pipe\\Tor-Process-Pipe-%lu-%lu",
+               process_id, counter++);
+
+  /* Only one of our handles can be overlapped. */
+  switch (pipe_type) {
+  case PROCESS_WIN32_PIPE_TYPE_READER:
+    read_mode = FILE_FLAG_OVERLAPPED;
+    break;
+  case PROCESS_WIN32_PIPE_TYPE_WRITER:
+    write_mode = FILE_FLAG_OVERLAPPED;
+    break;
+  default:
+    /* LCOV_EXCL_START */
+    tor_assert_nonfatal_unreached_once();
+    /* LCOV_EXCL_STOP */
+  }
+
+  /* Setup our read and write handles. */
+  HANDLE read_handle;
+  HANDLE write_handle;
+
+  /* Create our named pipe. */
+  read_handle = CreateNamedPipeA(pipe_name,
+                                 (PIPE_ACCESS_INBOUND|read_mode),
+                                 (PIPE_TYPE_BYTE|PIPE_WAIT),
+                                 1,
+                                 size,
+                                 size,
+                                 1000,
+                                 attributes);
+
+  if (read_handle == INVALID_HANDLE_VALUE) {
+    log_warn(LD_PROCESS, "CreateNamedPipeA() failed: %s",
+             format_win32_error(GetLastError()));
+    return false;
+  }
+
+  /* Create our file in the pipe namespace. */
+  write_handle = CreateFileA(pipe_name,
+                             GENERIC_WRITE,
+                             0,
+                             attributes,
+                             OPEN_EXISTING,
+                             (FILE_ATTRIBUTE_NORMAL|write_mode),
+                             NULL);
+
+  if (write_handle == INVALID_HANDLE_VALUE) {
+    log_warn(LD_PROCESS, "CreateFileA() failed: %s",
+             format_win32_error(GetLastError()));
+
+    CloseHandle(read_handle);
+
+    return false;
+  }
+
+  /* Set the inherit flag for our pipe. */
+  switch (pipe_type) {
+  case PROCESS_WIN32_PIPE_TYPE_READER:
+    ret = SetHandleInformation(read_handle, HANDLE_FLAG_INHERIT, 0);
+    break;
+  case PROCESS_WIN32_PIPE_TYPE_WRITER:
+    ret = SetHandleInformation(write_handle, HANDLE_FLAG_INHERIT, 0);
+    break;
+  default:
+    /* LCOV_EXCL_START */
+    tor_assert_nonfatal_unreached_once();
+    /* LCOV_EXCL_STOP */
+  }
+
+  if (! ret) {
+    log_warn(LD_PROCESS, "SetHandleInformation() failed: %s",
+             format_win32_error(GetLastError()));
+
+    CloseHandle(read_handle);
+    CloseHandle(write_handle);
+
+    return false;
+  }
+
+  /* Everything is good. */
+  *read_pipe = read_handle;
+  *write_pipe = write_handle;
+
+  return true;
+}
+
+/** Cleanup a given <b>handle</b>. */
+STATIC void
+process_win32_cleanup_handle(process_win32_handle_t *handle)
+{
+  tor_assert(handle);
+
+#if 0
+  /* FIXME(ahf): My compiler does not set _WIN32_WINNT to a high enough value
+   * for this code to be available. Should we force it? CancelIoEx() is
+   * available from Windows 7 and above. If we decide to require this, we need
+   * to update the checks in all the three I/O completion callbacks to handle
+   * the ERROR_OPERATION_ABORTED as well as ERROR_BROKEN_PIPE. */
+
+#if _WIN32_WINNT >= 0x0600
+  /* This code is only supported from Windows 7 and onwards. */
+  BOOL ret;
+  DWORD error_code;
+
+  /* Cancel any pending I/O requests. */
+  ret = CancelIoEx(handle->pipe, &handle->overlapped);
+
+  if (! ret) {
+    error_code = GetLastError();
+
+    /* There was no pending I/O requests for our handle. */
+    if (error_code != ERROR_NOT_FOUND) {
+      log_warn(LD_PROCESS, "CancelIoEx() failed: %s",
+               format_win32_error(error_code));
+    }
+  }
+#endif
+#endif
+
+  /* Close our handle. */
+  if (handle->pipe != INVALID_HANDLE_VALUE) {
+    CloseHandle(handle->pipe);
+    handle->pipe = INVALID_HANDLE_VALUE;
+  }
+}
+
+/** This function is called when ReadFileEx() completes its background read
+ * from the child process's standard output. We notify the Process subsystem if
+ * there is data available for it to read from us. */
+STATIC VOID WINAPI
+process_win32_stdout_read_done(DWORD error_code,
+                               DWORD byte_count,
+                               LPOVERLAPPED overlapped)
+{
+  tor_assert(overlapped);
+  tor_assert(overlapped->hEvent);
+
+  /* This happens when we have asked ReadFileEx() to read some data, but we
+   * then decided to call CloseHandle() on the HANDLE. This can happen if
+   * someone runs process_free() in the exit_callback of process_t, which means
+   * we cannot call process_get_win32_process() here. */
+  if (error_code == ERROR_BROKEN_PIPE) {
+    log_debug(LD_PROCESS, "Process reported broken pipe on standard out");
+    return;
+  }
+
+  /* Extract our process_t from the hEvent member of OVERLAPPED. */
+  process_t *process = (process_t *)overlapped->hEvent;
+  process_win32_t *win32_process = process_get_win32_process(process);
+
+  if (process_win32_handle_read_completion(&win32_process->stdout_handle,
+                                           error_code,
+                                           byte_count)) {
+    /* Schedule our next read. */
+    process_notify_event_stdout(process);
+  }
+}
+
+/** This function is called when ReadFileEx() completes its background read
+ * from the child process's standard error. We notify the Process subsystem if
+ * there is data available for it to read from us. */
+STATIC VOID WINAPI
+process_win32_stderr_read_done(DWORD error_code,
+                               DWORD byte_count,
+                               LPOVERLAPPED overlapped)
+{
+  tor_assert(overlapped);
+  tor_assert(overlapped->hEvent);
+
+  /* This happens when we have asked ReadFileEx() to read some data, but we
+   * then decided to call CloseHandle() on the HANDLE. This can happen if
+   * someone runs process_free() in the exit_callback of process_t, which means
+   * we cannot call process_get_win32_process() here. */
+  if (error_code == ERROR_BROKEN_PIPE) {
+    log_debug(LD_PROCESS, "Process reported broken pipe on standard error");
+    return;
+  }
+
+  /* Extract our process_t from the hEvent member of OVERLAPPED. */
+  process_t *process = (process_t *)overlapped->hEvent;
+  process_win32_t *win32_process = process_get_win32_process(process);
+
+  if (process_win32_handle_read_completion(&win32_process->stderr_handle,
+                                           error_code,
+                                           byte_count)) {
+    /* Schedule our next read. */
+    process_notify_event_stderr(process);
+  }
+}
+
+/** This function is called when WriteFileEx() completes its background write
+ * to the child process's standard input. We notify the Process subsystem that
+ * it can write data to us again. */
+STATIC VOID WINAPI
+process_win32_stdin_write_done(DWORD error_code,
+                               DWORD byte_count,
+                               LPOVERLAPPED overlapped)
+{
+  tor_assert(overlapped);
+  tor_assert(overlapped->hEvent);
+
+  (void)byte_count;
+
+  /* This happens when we have asked WriteFileEx() to write some data, but we
+   * then decided to call CloseHandle() on the HANDLE. This can happen if
+   * someone runs process_free() in the exit_callback of process_t, which means
+   * we cannot call process_get_win32_process() here. */
+  if (error_code == ERROR_BROKEN_PIPE) {
+    log_debug(LD_PROCESS, "Process reported broken pipe on standard input");
+    return;
+  }
+
+  process_t *process = (process_t *)overlapped->hEvent;
+  process_win32_t *win32_process = process_get_win32_process(process);
+
+  /* Mark our handle as not having any outstanding I/O requests. */
+  win32_process->stdin_handle.busy = false;
+
+  /* Check if we have been asked to write to the handle that have been marked
+   * as having reached EOF. */
+  if (BUG(win32_process->stdin_handle.reached_eof))
+    return;
+
+  if (error_code == 0) {
+    /** Our data have been succesfully written. Clear our state and schedule
+     * the next write. */
+    win32_process->stdin_handle.data_available = 0;
+    memset(win32_process->stdin_handle.buffer, 0,
+           sizeof(win32_process->stdin_handle.buffer));
+
+    /* Schedule the next write. */
+    process_notify_event_stdin(process);
+  } else if (error_code == ERROR_HANDLE_EOF) {
+    /* Our WriteFileEx() call was succesful, but we reached the end of our
+     * file.  We mark our handle as having reached EOF and returns. */
+    tor_assert(byte_count == 0);
+
+    win32_process->stdin_handle.reached_eof = true;
+  } else {
+    /* An error happened: We warn the user and mark our handle as having
+     * reached EOF */
+    log_warn(LD_PROCESS,
+             "Error in I/O completion routine from WriteFileEx(): %s",
+             format_win32_error(error_code));
+    win32_process->stdin_handle.reached_eof = true;
+  }
+}
+
+/** This function reads data from the given <b>handle</b>'s internal buffer and
+ * moves it into the given <b>buffer</b>. Additionally, we start the next
+ * ReadFileEx() background operation with the given <b>callback</b> as
+ * completion callback. Returns the number of bytes written to the buffer. */
+STATIC int
+process_win32_read_from_handle(process_win32_handle_t *handle,
+                               buf_t *buffer,
+                               LPOVERLAPPED_COMPLETION_ROUTINE callback)
+{
+  tor_assert(handle);
+  tor_assert(buffer);
+  tor_assert(callback);
+
+  BOOL ret = FALSE;
+  int bytes_available = 0;
+
+  /* We already have a request to read data that isn't complete yet. */
+  if (BUG(handle->busy))
+    return 0;
+
+  /* Check if we have been asked to read from a handle that have already told
+   * us that we have reached the end of the file. */
+  if (BUG(handle->reached_eof))
+    return 0;
+
+  /* This cast should be safe since our buffer can be at maximum up to
+   * BUFFER_SIZE in size. */
+  bytes_available = (int)handle->data_available;
+
+  if (handle->data_available > 0) {
+    /* Read data from our intermediate buffer into the process_t buffer. */
+    buf_add(buffer, handle->buffer, handle->data_available);
+
+    /* Reset our read state. */
+    handle->data_available = 0;
+    memset(handle->buffer, 0, sizeof(handle->buffer));
+  }
+
+  /* Ask the Windows kernel to read data from our pipe into our buffer and call
+   * the callback function when it is done. */
+  ret = ReadFileEx(handle->pipe,
+                   handle->buffer,
+                   sizeof(handle->buffer),
+                   &handle->overlapped,
+                   callback);
+
+  if (! ret) {
+    log_warn(LD_PROCESS, "ReadFileEx() failed: %s",
+             format_win32_error(GetLastError()));
+    return bytes_available;
+  }
+
+  /* We mark our handle as having a pending I/O request. */
+  handle->busy = true;
+
+  return bytes_available;
+}
+
+/** This function checks the callback values from ReadFileEx() in
+ * <b>error_code</b> and <b>byte_count</b> if we have read data. Returns true
+ * iff our caller should request more data from ReadFileEx(). */
+STATIC bool
+process_win32_handle_read_completion(process_win32_handle_t *handle,
+                                     DWORD error_code,
+                                     DWORD byte_count)
+{
+  tor_assert(handle);
+
+  /* Mark our handle as not having any outstanding I/O requests. */
+  handle->busy = false;
+
+  if (error_code == 0) {
+    /* Our ReadFileEx() call was succesful and there is data for us. */
+
+    /* This cast should be safe since byte_count should never be larger than
+     * BUFFER_SIZE. */
+    tor_assert(byte_count <= BUFFER_SIZE);
+    handle->data_available = (size_t)byte_count;
+
+    /* Tell our caller to schedule the next read. */
+    return true;
+  } else if (error_code == ERROR_HANDLE_EOF) {
+    /* Our ReadFileEx() call was succesful, but we reached the end of our file.
+     * We mark our handle as having reached EOF and returns. */
+    tor_assert(byte_count == 0);
+
+    handle->reached_eof = true;
+  } else {
+    /* An error happened: We warn the user and mark our handle as having
+     * reached EOF */
+    log_warn(LD_PROCESS,
+             "Error in I/O completion routine from ReadFileEx(): %s",
+             format_win32_error(error_code));
+
+    handle->reached_eof = true;
+  }
+
+  /* Our caller should NOT schedule the next read. */
+  return false;
+}
+
+#endif /* ! defined(_WIN32). */
diff --git a/src/lib/process/process_win32.h b/src/lib/process/process_win32.h
new file mode 100644
index 000000000..8c3b80d34
--- /dev/null
+++ b/src/lib/process/process_win32.h
@@ -0,0 +1,90 @@
+/* Copyright (c) 2003-2004, Roger Dingledine
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file process_win32.h
+ * \brief Header for process_win32.c
+ **/
+
+#ifndef TOR_PROCESS_WIN32_H
+#define TOR_PROCESS_WIN32_H
+
+#ifdef _WIN32
+
+#include "orconfig.h"
+#include "lib/malloc/malloc.h"
+#include "lib/evloop/compat_libevent.h"
+
+#include <windows.h>
+
+struct process_t;
+
+struct process_win32_t;
+typedef struct process_win32_t process_win32_t;
+
+process_win32_t *process_win32_new(void);
+void process_win32_free_(process_win32_t *win32_process);
+#define process_win32_free(s) \
+  FREE_AND_NULL(process_win32_t, process_win32_free_, (s))
+
+void process_win32_init(void);
+void process_win32_deinit(void);
+
+process_status_t process_win32_exec(struct process_t *process);
+
+int process_win32_write(struct process_t *process, buf_t *buffer);
+int process_win32_read_stdout(struct process_t *process, buf_t *buffer);
+int process_win32_read_stderr(struct process_t *process, buf_t *buffer);
+
+void process_win32_trigger_completion_callbacks(void);
+
+#ifdef PROCESS_WIN32_PRIVATE
+/* Timer handling. */
+STATIC void process_win32_timer_start(void);
+STATIC void process_win32_timer_stop(void);
+STATIC bool process_win32_timer_running(void);
+STATIC void process_win32_timer_callback(periodic_timer_t *, void *);
+STATIC void process_win32_timer_test_process(process_t *);
+
+/* I/O pipe handling. */
+struct process_win32_handle_t;
+typedef struct process_win32_handle_t process_win32_handle_t;
+
+typedef enum process_win32_pipe_type_t {
+  /** This pipe is used for reading. */
+  PROCESS_WIN32_PIPE_TYPE_READER,
+
+  /** This pipe is used for writing. */
+  PROCESS_WIN32_PIPE_TYPE_WRITER
+} process_win32_pipe_type_t;
+
+STATIC bool process_win32_create_pipe(HANDLE *,
+                                      HANDLE *,
+                                      SECURITY_ATTRIBUTES *,
+                                      process_win32_pipe_type_t);
+
+STATIC void process_win32_cleanup_handle(process_win32_handle_t *handle);
+
+STATIC VOID WINAPI process_win32_stdout_read_done(DWORD,
+                                                  DWORD,
+                                                  LPOVERLAPPED);
+STATIC VOID WINAPI process_win32_stderr_read_done(DWORD,
+                                                  DWORD,
+                                                  LPOVERLAPPED);
+STATIC VOID WINAPI process_win32_stdin_write_done(DWORD,
+                                                  DWORD,
+                                                  LPOVERLAPPED);
+
+STATIC int process_win32_read_from_handle(process_win32_handle_t *,
+                                          buf_t *,
+                                          LPOVERLAPPED_COMPLETION_ROUTINE);
+STATIC bool process_win32_handle_read_completion(process_win32_handle_t *,
+                                                 DWORD,
+                                                 DWORD);
+#endif /* defined(PROCESS_WIN32_PRIVATE). */
+
+#endif /* ! defined(_WIN32). */
+
+#endif /* defined(TOR_PROCESS_WIN32_H). */
diff --git a/src/test/test_process.c b/src/test/test_process.c
index 816695cca..85ee9691a 100644
--- a/src/test/test_process.c
+++ b/src/test/test_process.c
@@ -14,6 +14,8 @@
 #include "lib/process/process.h"
 #define PROCESS_UNIX_PRIVATE
 #include "lib/process/process_unix.h"
+#define PROCESS_WIN32_PRIVATE
+#include "lib/process/process_win32.h"
 
 static const char *stdout_read_buffer;
 static const char *stderr_read_buffer;
@@ -553,7 +555,7 @@ test_unix(void *arg)
 #ifndef _WIN32
   process_init();
 
-  process_t *process = process_new();
+  process_t *process = process_new("");
 
   /* On Unix all processes should have a Unix process handle. */
   tt_ptr_op(NULL, OP_NE, process_get_unix_process(process));
@@ -564,6 +566,24 @@ test_unix(void *arg)
 #endif
 }
 
+static void
+test_win32(void *arg)
+{
+  (void)arg;
+#ifdef _WIN32
+  process_init();
+
+  process_t *process = process_new("");
+
+  /* On Win32 all processes should have a Win32 process handle. */
+  tt_ptr_op(NULL, OP_NE, process_get_win32_process(process));
+
+ done:
+  process_free(process);
+  process_free_all();
+#endif
+}
+
 struct testcase_t process_tests[] = {
   { "default_values", test_default_values, TT_FORK, NULL, NULL },
   { "stringified_types", test_stringified_types, TT_FORK, NULL, NULL },
@@ -575,5 +595,6 @@ struct testcase_t process_tests[] = {
   { "exit_simple", test_exit_simple, TT_FORK, NULL, NULL },
   { "argv_simple", test_argv_simple, TT_FORK, NULL, NULL },
   { "unix", test_unix, TT_FORK, NULL, NULL },
+  { "win32", test_win32, TT_FORK, NULL, NULL },
   END_OF_TESTCASES
 };





More information about the tor-commits mailing list