daily update
[external/binutils.git] / gdb / ser-mingw.c
index 7a6f232..27a2ce8 100644 (file)
@@ -1,13 +1,12 @@
 /* Serial interface for local (hardwired) serial ports on Windows systems
 
-   Copyright (C) 2006
-   Free Software Foundation, Inc.
+   Copyright (C) 2006, 2007, 2008 Free Software Foundation, Inc.
 
    This file is part of GDB.
 
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
-   the Free Software Foundation; either version 2 of the License, or
+   the Free Software Foundation; either version 3 of the License, or
    (at your option) any later version.
 
    This program is distributed in the hope that it will be useful,
@@ -16,9 +15,7 @@
    GNU General Public License for more details.
 
    You should have received a copy of the GNU General Public License
-   along with this program; if not, write to the Free Software
-   Foundation, Inc., 51 Franklin Street, Fifth Floor,
-   Boston, MA 02110-1301, USA.  */
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
 
 #include "defs.h"
 #include "serial.h"
@@ -26,6 +23,7 @@
 #include "ser-tcp.h"
 
 #include <windows.h>
+#include <conio.h>
 
 #include <fcntl.h>
 #include <unistd.h>
@@ -343,30 +341,172 @@ ser_windows_write_prim (struct serial *scb, const void *buf, size_t len)
   return bytes_written;
 }
 
+/* On Windows, gdb_select is implemented using WaitForMulpleObjects.
+   A "select thread" is created for each file descriptor.  These
+   threads looks for activity on the corresponding descriptor, using
+   whatever techniques are appropriate for the descriptor type.  When
+   that activity occurs, the thread signals an appropriate event,
+   which wakes up WaitForMultipleObjects.
+
+   Each select thread is in one of two states: stopped or started.
+   Select threads begin in the stopped state.  When gdb_select is
+   called, threads corresponding to the descriptors of interest are
+   started by calling a wait_handle function.  Each thread that
+   notices activity signals the appropriate event and then reenters
+   the stopped state.  Before gdb_select returns it calls the
+   wait_handle_done functions, which return the threads to the stopped
+   state.  */
+
+enum select_thread_state {
+  STS_STARTED,
+  STS_STOPPED
+};
+
 struct ser_console_state
 {
+  /* Signaled by the select thread to indicate that data is available
+     on the file descriptor.  */
   HANDLE read_event;
+  /* Signaled by the select thread to indicate that an exception has
+     occurred on the file descriptor.  */
   HANDLE except_event;
-
+  /* Signaled by the select thread to indicate that it has entered the
+     started state.  HAVE_STARTED and HAVE_STOPPED are never signaled
+     simultaneously.  */
+  HANDLE have_started;
+  /* Signaled by the select thread to indicate that it has stopped,
+     either because data is available (and READ_EVENT is signaled),
+     because an exception has occurred (and EXCEPT_EVENT is signaled),
+     or because STOP_SELECT was signaled.  */
+  HANDLE have_stopped;
+
+  /* Signaled by the main program to tell the select thread to enter
+     the started state.  */
   HANDLE start_select;
+  /* Signaled by the main program to tell the select thread to enter
+     the stopped state. */
   HANDLE stop_select;
+  /* Signaled by the main program to tell the select thread to
+     exit.  */
+  HANDLE exit_select;
+
+  /* The handle for the select thread.  */
+  HANDLE thread;
+  /* The state of the select thread.  This field is only accessed in
+     the main program, never by the select thread itself.  */
+  enum select_thread_state thread_state;
 };
 
+/* Called by a select thread to enter the stopped state.  This
+   function does not return until the thread has re-entered the
+   started state.  */
+static void
+select_thread_wait (struct ser_console_state *state)
+{
+  HANDLE wait_events[2];
+
+  /* There are two things that can wake us up: a request that we enter
+     the started state, or that we exit this thread.  */
+  wait_events[0] = state->start_select;
+  wait_events[1] = state->exit_select;
+  if (WaitForMultipleObjects (2, wait_events, FALSE, INFINITE) 
+      != WAIT_OBJECT_0)
+    /* Either the EXIT_SELECT event was signaled (requesting that the
+       thread exit) or an error has occurred.  In either case, we exit
+       the thread.  */
+    ExitThread (0);
+  
+  /* We are now in the started state.  */
+  SetEvent (state->have_started);
+}
+
+typedef DWORD WINAPI (*thread_fn_type)(void *);
+
+/* Create a new select thread for SCB executing THREAD_FN.  The STATE
+   will be filled in by this function before return.  */
+void
+create_select_thread (thread_fn_type thread_fn,
+                     struct serial *scb,
+                     struct ser_console_state *state)
+{
+  DWORD threadId;
+
+  /* Create all of the events.  These are all auto-reset events.  */
+  state->read_event = CreateEvent (NULL, FALSE, FALSE, NULL);
+  state->except_event = CreateEvent (NULL, FALSE, FALSE, NULL);
+  state->have_started = CreateEvent (NULL, FALSE, FALSE, NULL);
+  state->have_stopped = CreateEvent (NULL, FALSE, FALSE, NULL);
+  state->start_select = CreateEvent (NULL, FALSE, FALSE, NULL);
+  state->stop_select = CreateEvent (NULL, FALSE, FALSE, NULL);
+  state->exit_select = CreateEvent (NULL, FALSE, FALSE, NULL);
+
+  state->thread = CreateThread (NULL, 0, thread_fn, scb, 0, &threadId);
+  /* The thread begins in the stopped state.  */
+  state->thread_state = STS_STOPPED;
+}
+
+/* Destroy the select thread indicated by STATE.  */
+static void
+destroy_select_thread (struct ser_console_state *state)
+{
+  /* Ask the thread to exit.  */
+  SetEvent (state->exit_select);
+  /* Wait until it does.  */
+  WaitForSingleObject (state->thread, INFINITE);
+
+  /* Destroy the events.  */
+  CloseHandle (state->read_event);
+  CloseHandle (state->except_event);
+  CloseHandle (state->have_started);
+  CloseHandle (state->have_stopped);
+  CloseHandle (state->start_select);
+  CloseHandle (state->stop_select);
+  CloseHandle (state->exit_select);
+}
+
+/* Called by gdb_select to start the select thread indicated by STATE.
+   This function does not return until the thread has started.  */
+static void
+start_select_thread (struct ser_console_state *state)
+{
+  /* Ask the thread to start.  */
+  SetEvent (state->start_select);
+  /* Wait until it does.  */
+  WaitForSingleObject (state->have_started, INFINITE);
+  /* The thread is now started.  */
+  state->thread_state = STS_STARTED;
+}
+
+/* Called by gdb_select to stop the select thread indicated by STATE.
+   This function does not return until the thread has stopped.  */
+static void
+stop_select_thread (struct ser_console_state *state)
+{
+  /* If the thread is already in the stopped state, we have nothing to
+     do.  Some of the wait_handle functions avoid calling
+     start_select_thread if they notice activity on the relevant file
+     descriptors.  The wait_handle_done functions still call
+     stop_select_thread -- but it is already stopped.  */
+  if (state->thread_state != STS_STARTED)
+    return;
+  /* Ask the thread to stop.  */
+  SetEvent (state->stop_select);
+  /* Wait until it does.  */
+  WaitForSingleObject (state->have_stopped, INFINITE);
+  /* The thread is now stopped.  */
+  state->thread_state = STS_STOPPED;
+}
+
 static DWORD WINAPI
 console_select_thread (void *arg)
 {
   struct serial *scb = arg;
-  struct ser_console_state *state, state_copy;
-  int event_index, fd;
+  struct ser_console_state *state;
+  int event_index;
   HANDLE h;
 
-  /* Copy useful information out of the control block, to make sure
-     that we do not race with freeing it.  */
-  state_copy = *(struct ser_console_state *) scb->state;
-  state = &state_copy;
-  fd = scb->fd;
-
-  h = (HANDLE) _get_osfhandle (fd);
+  state = scb->state;
+  h = (HANDLE) _get_osfhandle (scb->fd);
 
   while (1)
     {
@@ -374,55 +514,69 @@ console_select_thread (void *arg)
       INPUT_RECORD record;
       DWORD n_records;
 
-      wait_events[0] = state->start_select;
-      wait_events[1] = state->stop_select;
-
-      if (WaitForMultipleObjects (2, wait_events, FALSE, INFINITE) != WAIT_OBJECT_0)
-       {
-         CloseHandle (state->stop_select);
-         return 0;
-       }
-
-    retry:
-      wait_events[0] = state->stop_select;
-      wait_events[1] = h;
-
-      event_index = WaitForMultipleObjects (2, wait_events, FALSE, INFINITE);
-
-      if (event_index == WAIT_OBJECT_0
-         || WaitForSingleObject (state->stop_select, 0) == WAIT_OBJECT_0)
-       {
-         CloseHandle (state->stop_select);
-         return 0;
-       }
-
-      if (event_index != WAIT_OBJECT_0 + 1)
-       {
-         /* Wait must have failed; assume an error has occured, e.g.
-            the handle has been closed.  */
-         SetEvent (state->except_event);
-         continue;
-       }
-
-      /* We've got a pending event on the console.  See if it's
-        of interest.  */
-      if (!PeekConsoleInput (h, &record, 1, &n_records) || n_records != 1)
-       {
-         /* Something went wrong.  Maybe the console is gone.  */
-         SetEvent (state->except_event);
-         continue;
-       }
+      select_thread_wait (state);
 
-      if (record.EventType == KEY_EVENT && record.Event.KeyEvent.bKeyDown)
+      while (1)
        {
-         /* This is really a keypress.  */
-         SetEvent (state->read_event);
-         continue;
+         wait_events[0] = state->stop_select;
+         wait_events[1] = h;
+
+         event_index = WaitForMultipleObjects (2, wait_events, FALSE, INFINITE);
+
+         if (event_index == WAIT_OBJECT_0
+             || WaitForSingleObject (state->stop_select, 0) == WAIT_OBJECT_0)
+           break;
+
+         if (event_index != WAIT_OBJECT_0 + 1)
+           {
+             /* Wait must have failed; assume an error has occured, e.g.
+                the handle has been closed.  */
+             SetEvent (state->except_event);
+             break;
+           }
+
+         /* We've got a pending event on the console.  See if it's
+            of interest.  */
+         if (!PeekConsoleInput (h, &record, 1, &n_records) || n_records != 1)
+           {
+             /* Something went wrong.  Maybe the console is gone.  */
+             SetEvent (state->except_event);
+             break;
+           }
+
+         if (record.EventType == KEY_EVENT && record.Event.KeyEvent.bKeyDown)
+           {
+             WORD keycode = record.Event.KeyEvent.wVirtualKeyCode;
+
+             /* Ignore events containing only control keys.  We must
+                recognize "enhanced" keys which we are interested in
+                reading via getch, if they do not map to ASCII.  But we
+                do not want to report input available for e.g. the
+                control key alone.  */
+
+             if (record.Event.KeyEvent.uChar.AsciiChar != 0
+                 || keycode == VK_PRIOR
+                 || keycode == VK_NEXT
+                 || keycode == VK_END
+                 || keycode == VK_HOME
+                 || keycode == VK_LEFT
+                 || keycode == VK_UP
+                 || keycode == VK_RIGHT
+                 || keycode == VK_DOWN
+                 || keycode == VK_INSERT
+                 || keycode == VK_DELETE)
+               {
+                 /* This is really a keypress.  */
+                 SetEvent (state->read_event);
+                 break;
+               }
+           }
+
+         /* Otherwise discard it and wait again.  */
+         ReadConsoleInput (h, &record, 1, &n_records);
        }
 
-      /* Otherwise discard it and wait again.  */
-      ReadConsoleInput (h, &record, 1, &n_records);
-      goto retry;
+      SetEvent(state->have_stopped);
     }
 }
 
@@ -435,57 +589,78 @@ fd_is_pipe (int fd)
     return 0;
 }
 
+static int
+fd_is_file (int fd)
+{
+  if (GetFileType ((HANDLE) _get_osfhandle (fd)) == FILE_TYPE_DISK)
+    return 1;
+  else
+    return 0;
+}
+
 static DWORD WINAPI
 pipe_select_thread (void *arg)
 {
   struct serial *scb = arg;
-  struct ser_console_state *state, state_copy;
-  int event_index, fd;
+  struct ser_console_state *state;
+  int event_index;
   HANDLE h;
 
-  /* Copy useful information out of the control block, to make sure
-     that we do not race with freeing it.  */
-  state_copy = *(struct ser_console_state *) scb->state;
-  state = &state_copy;
-  fd = scb->fd;
-
-  h = (HANDLE) _get_osfhandle (fd);
+  state = scb->state;
+  h = (HANDLE) _get_osfhandle (scb->fd);
 
   while (1)
     {
-      HANDLE wait_events[2];
       DWORD n_avail;
 
-      wait_events[0] = state->start_select;
-      wait_events[1] = state->stop_select;
+      select_thread_wait (state);
 
-      if (WaitForMultipleObjects (2, wait_events, FALSE, INFINITE) != WAIT_OBJECT_0)
+      /* Wait for something to happen on the pipe.  */
+      while (1)
        {
-         CloseHandle (state->stop_select);
-         return 0;
+         if (!PeekNamedPipe (h, NULL, 0, NULL, &n_avail, NULL))
+           {
+             SetEvent (state->except_event);
+             break;
+           }
+
+         if (n_avail > 0)
+           {
+             SetEvent (state->read_event);
+             break;
+           }
+
+         /* Delay 10ms before checking again, but allow the stop
+            event to wake us.  */
+         if (WaitForSingleObject (state->stop_select, 10) == WAIT_OBJECT_0)
+           break;
        }
 
-    retry:
-      if (!PeekNamedPipe (h, NULL, 0, NULL, &n_avail, NULL))
-       {
-         SetEvent (state->except_event);
-         continue;
-       }
+      SetEvent (state->have_stopped);
+    }
+}
 
-      if (n_avail > 0)
-       {
-         SetEvent (state->read_event);
-         continue;
-       }
+static DWORD WINAPI
+file_select_thread (void *arg)
+{
+  struct serial *scb = arg;
+  struct ser_console_state *state;
+  int event_index;
+  HANDLE h;
 
-      if (WaitForSingleObject (state->stop_select, 0) == WAIT_OBJECT_0)
-       {
-         CloseHandle (state->stop_select);
-         return 0;
-       }
+  state = scb->state;
+  h = (HANDLE) _get_osfhandle (scb->fd);
+
+  while (1)
+    {
+      select_thread_wait (state);
+
+      if (SetFilePointer (h, 0, NULL, FILE_CURRENT) == INVALID_SET_FILE_POINTER)
+       SetEvent (state->except_event);
+      else
+       SetEvent (state->read_event);
 
-      Sleep (10);
-      goto retry;
+      SetEvent (state->have_stopped);
     }
 }
 
@@ -496,11 +671,11 @@ ser_console_wait_handle (struct serial *scb, HANDLE *read, HANDLE *except)
 
   if (state == NULL)
     {
-      DWORD threadId;
+      thread_fn_type thread_fn;
       int is_tty;
 
       is_tty = isatty (scb->fd);
-      if (!is_tty && !fd_is_pipe (scb->fd))
+      if (!is_tty && !fd_is_file (scb->fd) && !fd_is_pipe (scb->fd))
        {
          *read = NULL;
          *except = NULL;
@@ -511,29 +686,48 @@ ser_console_wait_handle (struct serial *scb, HANDLE *read, HANDLE *except)
       memset (state, 0, sizeof (struct ser_console_state));
       scb->state = state;
 
-      /* Create auto reset events to wake and terminate the select thread.  */
-      state->start_select = CreateEvent (0, FALSE, FALSE, 0);
-      state->stop_select = CreateEvent (0, FALSE, FALSE, 0);
-
-      /* Create our own events to report read and exceptions separately.
-        The exception event is currently never used.  */
-      state->read_event = CreateEvent (0, FALSE, FALSE, 0);
-      state->except_event = CreateEvent (0, FALSE, FALSE, 0);
-
-      /* And finally start the select thread.  */
       if (is_tty)
-       CreateThread (NULL, 0, console_select_thread, scb, 0, &threadId);
+       thread_fn = console_select_thread;
+      else if (fd_is_pipe (scb->fd))
+       thread_fn = pipe_select_thread;
       else
-       CreateThread (NULL, 0, pipe_select_thread, scb, 0, &threadId);
+       thread_fn = file_select_thread;
+
+      create_select_thread (thread_fn, scb, state);
     }
 
+  *read = state->read_event;
+  *except = state->except_event;
+
+  /* Start from a blank state.  */
   ResetEvent (state->read_event);
   ResetEvent (state->except_event);
+  ResetEvent (state->stop_select);
+
+  /* First check for a key already in the buffer.  If there is one,
+     we don't need a thread.  This also catches the second key of
+     multi-character returns from getch, for instance for arrow
+     keys.  The second half is in a C library internal buffer,
+     and PeekConsoleInput will not find it.  */
+  if (_kbhit ())
+    {
+      SetEvent (state->read_event);
+      return;
+    }
 
-  SetEvent (state->start_select);
+  /* Otherwise, start the select thread.  */
+  start_select_thread (state);
+}
 
-  *read = state->read_event;
-  *except = state->except_event;
+static void
+ser_console_done_wait_handle (struct serial *scb)
+{
+  struct ser_console_state *state = scb->state;
+
+  if (state == NULL)
+    return;
+
+  stop_select_thread (state);
 }
 
 static void
@@ -543,11 +737,7 @@ ser_console_close (struct serial *scb)
 
   if (scb->state)
     {
-      SetEvent (state->stop_select);
-
-      CloseHandle (state->read_event);
-      CloseHandle (state->except_event);
-
+      destroy_select_thread (state);
       xfree (scb->state);
     }
 }
@@ -571,13 +761,236 @@ ser_console_get_tty_state (struct serial *scb)
     return NULL;
 }
 
-struct net_windows_state
+struct pipe_state
 {
-  HANDLE read_event;
-  HANDLE except_event;
+  /* Since we use the pipe_select_thread for our select emulation,
+     we need to place the state structure it requires at the front
+     of our state.  */
+  struct ser_console_state wait;
 
-  HANDLE start_select;
-  HANDLE stop_select;
+  /* The pex obj for our (one-stage) pipeline.  */
+  struct pex_obj *pex;
+
+  /* Streams for the pipeline's input and output.  */
+  FILE *input, *output;
+};
+
+static struct pipe_state *
+make_pipe_state (void)
+{
+  struct pipe_state *ps = XMALLOC (struct pipe_state);
+
+  memset (ps, 0, sizeof (*ps));
+  ps->wait.read_event = INVALID_HANDLE_VALUE;
+  ps->wait.except_event = INVALID_HANDLE_VALUE;
+  ps->wait.start_select = INVALID_HANDLE_VALUE;
+  ps->wait.stop_select = INVALID_HANDLE_VALUE;
+
+  return ps;
+}
+
+static void
+free_pipe_state (struct pipe_state *ps)
+{
+  int saved_errno = errno;
+
+  if (ps->wait.read_event != INVALID_HANDLE_VALUE)
+    destroy_select_thread (&ps->wait);
+
+  /* Close the pipe to the child.  We must close the pipe before
+     calling pex_free because pex_free will wait for the child to exit
+     and the child will not exit until the pipe is closed.  */
+  if (ps->input)
+    fclose (ps->input);
+  if (ps->pex)
+    pex_free (ps->pex);
+  /* pex_free closes ps->output.  */
+
+  xfree (ps);
+
+  errno = saved_errno;
+}
+
+static void
+cleanup_pipe_state (void *untyped)
+{
+  struct pipe_state *ps = untyped;
+
+  free_pipe_state (ps);
+}
+
+static int
+pipe_windows_open (struct serial *scb, const char *name)
+{
+  struct pipe_state *ps;
+  FILE *pex_stderr;
+
+  char **argv = buildargv (name);
+  struct cleanup *back_to = make_cleanup_freeargv (argv);
+  if (! argv[0] || argv[0][0] == '\0')
+    error ("missing child command");
+
+
+  ps = make_pipe_state ();
+  make_cleanup (cleanup_pipe_state, ps);
+
+  ps->pex = pex_init (PEX_USE_PIPES, "target remote pipe", NULL);
+  if (! ps->pex)
+    goto fail;
+  ps->input = pex_input_pipe (ps->pex, 1);
+  if (! ps->input)
+    goto fail;
+
+  {
+    int err;
+    const char *err_msg
+      = pex_run (ps->pex, PEX_SEARCH | PEX_BINARY_INPUT | PEX_BINARY_OUTPUT
+                | PEX_STDERR_TO_PIPE,
+                 argv[0], argv, NULL, NULL,
+                 &err);
+
+    if (err_msg)
+      {
+        /* Our caller expects us to return -1, but all they'll do with
+           it generally is print the message based on errno.  We have
+           all the same information here, plus err_msg provided by
+           pex_run, so we just raise the error here.  */
+        if (err)
+          error ("error starting child process '%s': %s: %s",
+                 name, err_msg, safe_strerror (err));
+        else
+          error ("error starting child process '%s': %s",
+                 name, err_msg);
+      }
+  }
+
+  ps->output = pex_read_output (ps->pex, 1);
+  if (! ps->output)
+    goto fail;
+  scb->fd = fileno (ps->output);
+
+  pex_stderr = pex_read_err (ps->pex, 1);
+  if (! pex_stderr)
+    goto fail;
+  scb->error_fd = fileno (pex_stderr);
+
+  scb->state = (void *) ps;
+
+  discard_cleanups (back_to);
+  return 0;
+
+ fail:
+  do_cleanups (back_to);
+  return -1;
+}
+
+
+static void
+pipe_windows_close (struct serial *scb)
+{
+  struct pipe_state *ps = scb->state;
+
+  /* In theory, we should try to kill the subprocess here, but the pex
+     interface doesn't give us enough information to do that.  Usually
+     closing the input pipe will get the message across.  */
+
+  free_pipe_state (ps);
+}
+
+
+static int
+pipe_windows_read (struct serial *scb, size_t count)
+{
+  HANDLE pipeline_out = (HANDLE) _get_osfhandle (scb->fd);
+  DWORD available;
+  DWORD bytes_read;
+
+  if (pipeline_out == INVALID_HANDLE_VALUE)
+    return -1;
+
+  if (! PeekNamedPipe (pipeline_out, NULL, 0, NULL, &available, NULL))
+    return -1;
+
+  if (count > available)
+    count = available;
+
+  if (! ReadFile (pipeline_out, scb->buf, count, &bytes_read, NULL))
+    return -1;
+
+  return bytes_read;
+}
+
+
+static int
+pipe_windows_write (struct serial *scb, const void *buf, size_t count)
+{
+  struct pipe_state *ps = scb->state;
+  HANDLE pipeline_in;
+  DWORD written;
+
+  int pipeline_in_fd = fileno (ps->input);
+  if (pipeline_in_fd < 0)
+    return -1;
+
+  pipeline_in = (HANDLE) _get_osfhandle (pipeline_in_fd);
+  if (pipeline_in == INVALID_HANDLE_VALUE)
+    return -1;
+
+  if (! WriteFile (pipeline_in, buf, count, &written, NULL))
+    return -1;
+
+  return written;
+}
+
+
+static void
+pipe_wait_handle (struct serial *scb, HANDLE *read, HANDLE *except)
+{
+  struct pipe_state *ps = scb->state;
+
+  /* Have we allocated our events yet?  */
+  if (ps->wait.read_event == INVALID_HANDLE_VALUE)
+    /* Start the thread.  */
+    create_select_thread (pipe_select_thread, scb, &ps->wait);
+
+  *read = ps->wait.read_event;
+  *except = ps->wait.except_event;
+
+  /* Start from a blank state.  */
+  ResetEvent (ps->wait.read_event);
+  ResetEvent (ps->wait.except_event);
+  ResetEvent (ps->wait.stop_select);
+
+  start_select_thread (&ps->wait);
+}
+
+static void
+pipe_done_wait_handle (struct serial *scb)
+{
+  struct pipe_state *ps = scb->state;
+
+  /* Have we allocated our events yet?  */
+  if (ps->wait.read_event == INVALID_HANDLE_VALUE)
+    return;
+
+  stop_select_thread (&ps->wait);
+}
+
+static int
+pipe_avail (struct serial *scb, int fd)
+{
+  HANDLE h = (HANDLE) _get_osfhandle (fd);
+  DWORD numBytes;
+  BOOL r = PeekNamedPipe (h, NULL, 0, NULL, &numBytes, NULL);
+  if (r == FALSE)
+    numBytes = 0;
+  return numBytes;
+}
+
+struct net_windows_state
+{
+  struct ser_console_state base;
+  
   HANDLE sock_event;
 };
 
@@ -585,73 +998,125 @@ static DWORD WINAPI
 net_windows_select_thread (void *arg)
 {
   struct serial *scb = arg;
-  struct net_windows_state *state, state_copy;
-  int event_index, fd;
+  struct net_windows_state *state;
+  int event_index;
 
-  /* Copy useful information out of the control block, to make sure
-     that we do not race with freeing it.  */
-  state_copy = *(struct net_windows_state *) scb->state;
-  state = &state_copy;
-  fd = scb->fd;
+  state = scb->state;
 
   while (1)
     {
       HANDLE wait_events[2];
       WSANETWORKEVENTS events;
 
-      wait_events[0] = state->start_select;
-      wait_events[1] = state->stop_select;
-
-      if (WaitForMultipleObjects (2, wait_events, FALSE, INFINITE) != WAIT_OBJECT_0)
-       {
-         CloseHandle (state->stop_select);
-         return 0;
-       }
+      select_thread_wait (&state->base);
 
-      wait_events[0] = state->stop_select;
+      wait_events[0] = state->base.stop_select;
       wait_events[1] = state->sock_event;
 
       event_index = WaitForMultipleObjects (2, wait_events, FALSE, INFINITE);
 
       if (event_index == WAIT_OBJECT_0
-         || WaitForSingleObject (state->stop_select, 0) == WAIT_OBJECT_0)
+         || WaitForSingleObject (state->base.stop_select, 0) == WAIT_OBJECT_0)
+       /* We have been requested to stop.  */
+       ;
+      else if (event_index != WAIT_OBJECT_0 + 1)
+       /* Some error has occured.  Assume that this is an error
+          condition.  */
+       SetEvent (state->base.except_event);
+      else
        {
-         CloseHandle (state->stop_select);
-         return 0;
+         /* Enumerate the internal network events, and reset the
+            object that signalled us to catch the next event.  */
+         WSAEnumNetworkEvents (scb->fd, state->sock_event, &events);
+         
+         gdb_assert (events.lNetworkEvents & (FD_READ | FD_CLOSE));
+         
+         if (events.lNetworkEvents & FD_READ)
+           SetEvent (state->base.read_event);
+         
+         if (events.lNetworkEvents & FD_CLOSE)
+           SetEvent (state->base.except_event);
        }
 
-      if (event_index != WAIT_OBJECT_0 + 1)
-       {
-         /* Some error has occured.  Assume that this is an error
-            condition.  */
-         SetEvent (state->except_event);
-         continue;
-       }
+      SetEvent (state->base.have_stopped);
+    }
+}
+
+static void
+net_windows_wait_handle (struct serial *scb, HANDLE *read, HANDLE *except)
+{
+  struct net_windows_state *state = scb->state;
+
+  /* Start from a clean slate.  */
+  ResetEvent (state->base.read_event);
+  ResetEvent (state->base.except_event);
+  ResetEvent (state->base.stop_select);
+
+  *read = state->base.read_event;
+  *except = state->base.except_event;
+
+  /* Check any pending events.  This both avoids starting the thread
+     unnecessarily, and handles stray FD_READ events (see below).  */
+  if (WaitForSingleObject (state->sock_event, 0) == WAIT_OBJECT_0)
+    {
+      WSANETWORKEVENTS events;
+      int any = 0;
 
       /* Enumerate the internal network events, and reset the object that
         signalled us to catch the next event.  */
-      WSAEnumNetworkEvents (fd, state->sock_event, &events);
-
+      WSAEnumNetworkEvents (scb->fd, state->sock_event, &events);
+
+      /* You'd think that FD_READ or FD_CLOSE would be set here.  But,
+        sometimes, neither is.  I suspect that the FD_READ is set and
+        the corresponding event signalled while recv is running, and
+        the FD_READ is then lowered when recv consumes all the data,
+        but there's no way to un-signal the event.  This isn't a
+        problem for the call in net_select_thread, since any new
+        events after this point will not have been drained by recv.
+        It just means that we can't have the obvious assert here.  */
+
+      /* If there is a read event, it might be still valid, or it might
+        not be - it may have been signalled before we last called
+        recv.  Double-check that there is data.  */
       if (events.lNetworkEvents & FD_READ)
-       SetEvent (state->read_event);
+       {
+         unsigned long available;
+
+         if (ioctlsocket (scb->fd, FIONREAD, &available) == 0
+             && available > 0)
+           {
+             SetEvent (state->base.read_event);
+             any = 1;
+           }
+         else
+           /* Oops, no data.  This call to recv will cause future
+              data to retrigger the event, e.g. while we are
+              in net_select_thread.  */
+           recv (scb->fd, NULL, 0, 0);
+       }
 
+      /* If there's a close event, then record it - it is obviously
+        still valid, and it will not be resignalled.  */
       if (events.lNetworkEvents & FD_CLOSE)
-       SetEvent (state->except_event);
+       {
+         SetEvent (state->base.except_event);
+         any = 1;
+       }
+
+      /* If we set either handle, there's no need to wake the thread.  */
+      if (any)
+       return;
     }
+
+  start_select_thread (&state->base);
 }
 
 static void
-net_windows_wait_handle (struct serial *scb, HANDLE *read, HANDLE *except)
+net_windows_done_wait_handle (struct serial *scb)
 {
   struct net_windows_state *state = scb->state;
 
-  ResetEvent (state->read_event);
-  ResetEvent (state->except_event);
-
-  SetEvent (state->start_select);
-
-  *read = state->read_event;
-  *except = state->except_event;
+  stop_select_thread (&state->base);
 }
 
 static int
@@ -669,20 +1134,12 @@ net_windows_open (struct serial *scb, const char *name)
   memset (state, 0, sizeof (struct net_windows_state));
   scb->state = state;
 
-  /* Create auto reset events to wake and terminate the select thread.  */
-  state->start_select = CreateEvent (0, FALSE, FALSE, 0);
-  state->stop_select = CreateEvent (0, FALSE, FALSE, 0);
-
   /* Associate an event with the socket.  */
   state->sock_event = CreateEvent (0, TRUE, FALSE, 0);
   WSAEventSelect (scb->fd, state->sock_event, FD_READ | FD_CLOSE);
 
-  /* Create our own events to report read and close separately.  */
-  state->read_event = CreateEvent (0, FALSE, FALSE, 0);
-  state->except_event = CreateEvent (0, FALSE, FALSE, 0);
-
-  /* And finally start the select thread.  */
-  CreateThread (NULL, 0, net_windows_select_thread, scb, 0, &threadId);
+  /* Start the thread.  */
+  create_select_thread (net_windows_select_thread, scb, &state->base);
 
   return 0;
 }
@@ -693,11 +1150,7 @@ net_windows_close (struct serial *scb)
 {
   struct net_windows_state *state = scb->state;
 
-  SetEvent (state->stop_select);
-
-  CloseHandle (state->read_event);
-  CloseHandle (state->except_event);
-  CloseHandle (state->start_select);
+  destroy_select_thread (&state->base);
   CloseHandle (state->sock_event);
 
   xfree (scb->state);
@@ -760,6 +1213,37 @@ _initialize_ser_windows (void)
   ops->noflush_set_tty_state = ser_base_noflush_set_tty_state;
   ops->drain_output = ser_base_drain_output;
   ops->wait_handle = ser_console_wait_handle;
+  ops->done_wait_handle = ser_console_done_wait_handle;
+
+  serial_add_interface (ops);
+
+  /* The pipe interface.  */
+
+  ops = XMALLOC (struct serial_ops);
+  memset (ops, 0, sizeof (struct serial_ops));
+  ops->name = "pipe";
+  ops->next = 0;
+  ops->open = pipe_windows_open;
+  ops->close = pipe_windows_close;
+  ops->readchar = ser_base_readchar;
+  ops->write = ser_base_write;
+  ops->flush_output = ser_base_flush_output;
+  ops->flush_input = ser_base_flush_input;
+  ops->send_break = ser_base_send_break;
+  ops->go_raw = ser_base_raw;
+  ops->get_tty_state = ser_base_get_tty_state;
+  ops->set_tty_state = ser_base_set_tty_state;
+  ops->print_tty_state = ser_base_print_tty_state;
+  ops->noflush_set_tty_state = ser_base_noflush_set_tty_state;
+  ops->setbaudrate = ser_base_setbaudrate;
+  ops->setstopbits = ser_base_setstopbits;
+  ops->drain_output = ser_base_drain_output;
+  ops->async = ser_base_async;
+  ops->read_prim = pipe_windows_read;
+  ops->write_prim = pipe_windows_write;
+  ops->wait_handle = pipe_wait_handle;
+  ops->done_wait_handle = pipe_done_wait_handle;
+  ops->avail = pipe_avail;
 
   serial_add_interface (ops);
 
@@ -792,5 +1276,6 @@ _initialize_ser_windows (void)
   ops->read_prim = net_read_prim;
   ops->write_prim = net_write_prim;
   ops->wait_handle = net_windows_wait_handle;
+  ops->done_wait_handle = net_windows_done_wait_handle;
   serial_add_interface (ops);
 }