summary refs log tree commit diff stats
path: root/libotr/libgpg-error-1.42/src/w32-estream.c
diff options
context:
space:
mode:
Diffstat (limited to 'libotr/libgpg-error-1.42/src/w32-estream.c')
-rw-r--r--libotr/libgpg-error-1.42/src/w32-estream.c1078
1 files changed, 1078 insertions, 0 deletions
diff --git a/libotr/libgpg-error-1.42/src/w32-estream.c b/libotr/libgpg-error-1.42/src/w32-estream.c
new file mode 100644
index 0000000..5bb1bcf
--- /dev/null
+++ b/libotr/libgpg-error-1.42/src/w32-estream.c
@@ -0,0 +1,1078 @@
+/* w32-estream.c - es_poll support on W32.
+ * Copyright (C) 2000 Werner Koch (dd9jn)
+ * Copyright (C) 2001, 2002, 2003, 2004, 2007, 2010, 2016 g10 Code GmbH
+ *
+ * This file is part of libgpg-error.
+ *
+ * libgpg-error is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * libgpg-error is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+/*
+ * This file is based on GPGME's w32-io.c started in 2001.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#ifdef HAVE_SYS_TIME_H
+# include <sys/time.h>
+#endif
+#ifdef HAVE_SYS_TYPES_H
+# include <sys/types.h>
+#endif
+#include <io.h>
+#include <windows.h>
+
+/* Enable tracing.  The value is the module name to be printed.  */
+/*#define ENABLE_TRACING "estream" */
+
+#include "gpgrt-int.h"
+
+/*
+ * In order to support es_poll on Windows, we create a proxy shim that
+ * we use as the estream I/O functions.  This shim creates reader and
+ * writer threads that use the original I/O functions.
+ */
+
+
+/* Calculate array dimension.  */
+#ifndef DIM
+#define DIM(array) (sizeof (array) / sizeof (*array))
+#endif
+
+#define READBUF_SIZE 8192
+#define WRITEBUF_SIZE 8192
+
+
+typedef struct estream_cookie_w32_pollable *estream_cookie_w32_pollable_t;
+
+struct reader_context_s
+{
+  estream_cookie_w32_pollable_t pcookie;
+  HANDLE thread_hd;
+
+  CRITICAL_SECTION mutex;
+
+  int stop_me;
+  int eof;
+  int eof_shortcut;
+  int error;
+  int error_code;
+
+  /* This is manually reset.  */
+  HANDLE have_data_ev;
+  /* This is automatically reset.  */
+  HANDLE have_space_ev;
+  /* This is manually reset but actually only triggered once.  */
+  HANDLE close_ev;
+
+  size_t readpos, writepos;
+  char buffer[READBUF_SIZE];
+};
+
+struct writer_context_s
+{
+  estream_cookie_w32_pollable_t pcookie;
+  HANDLE thread_hd;
+
+  CRITICAL_SECTION mutex;
+
+  int stop_me;
+  int error;
+  int error_code;
+
+  /* This is manually reset.  */
+  HANDLE have_data;
+  HANDLE is_empty;
+  HANDLE close_ev;
+  size_t nbytes;
+  char buffer[WRITEBUF_SIZE];
+};
+
+/* Cookie for pollable objects.  */
+struct estream_cookie_w32_pollable
+{
+  unsigned int modeflags;
+
+  struct cookie_io_functions_s next_functions;
+  void *next_cookie;
+
+  struct reader_context_s *reader;
+  struct writer_context_s *writer;
+};
+
+
+static DWORD CALLBACK
+reader (void *arg)
+{
+  struct reader_context_s *ctx = arg;
+  int nbytes;
+  ssize_t nread;
+
+  trace (("%p: reader starting", ctx));
+
+  for (;;)
+    {
+      EnterCriticalSection (&ctx->mutex);
+      /* Leave a 1 byte gap so that we can see whether it is empty or
+	 full.  */
+      while ((ctx->writepos + 1) % READBUF_SIZE == ctx->readpos)
+	{
+	  /* Wait for space.  */
+	  if (!ResetEvent (ctx->have_space_ev))
+	    trace (("%p: ResetEvent failed: ec=%d", ctx, (int)GetLastError()));
+          LeaveCriticalSection (&ctx->mutex);
+          trace (("%p: waiting for space", ctx));
+	  WaitForSingleObject (ctx->have_space_ev, INFINITE);
+	  trace (("%p: got space", ctx));
+          EnterCriticalSection (&ctx->mutex);
+        }
+      gpgrt_assert (((ctx->writepos + 1) % READBUF_SIZE != ctx->readpos));
+      if (ctx->stop_me)
+	{
+          LeaveCriticalSection (&ctx->mutex);
+	  break;
+        }
+      nbytes = (ctx->readpos + READBUF_SIZE
+		- ctx->writepos - 1) % READBUF_SIZE;
+      gpgrt_assert (nbytes);
+      if (nbytes > READBUF_SIZE - ctx->writepos)
+	nbytes = READBUF_SIZE - ctx->writepos;
+      LeaveCriticalSection (&ctx->mutex);
+
+      trace (("%p: reading up to %d bytes", ctx, nbytes));
+
+      nread = ctx->pcookie->next_functions.public.func_read
+        (ctx->pcookie->next_cookie, ctx->buffer + ctx->writepos, nbytes);
+      trace (("%p: got %d bytes", ctx, nread));
+      if (nread < 0)
+        {
+          ctx->error_code = (int) errno;
+          /* NOTE (W32CE): Do not ignore ERROR_BUSY!  Check at
+             least stop_me if that happens.  */
+          if (ctx->error_code == ERROR_BROKEN_PIPE)
+            {
+              ctx->eof = 1;
+              trace (("%p: got EOF (broken pipe)", ctx));
+            }
+          else
+            {
+              ctx->error = 1;
+              trace (("%p: read error: ec=%d", ctx, ctx->error_code));
+            }
+          break;
+        }
+
+      EnterCriticalSection (&ctx->mutex);
+      if (ctx->stop_me)
+	{
+          LeaveCriticalSection (&ctx->mutex);
+	  break;
+        }
+      if (!nread)
+	{
+	  ctx->eof = 1;
+	  trace (("%p: got eof", ctx));
+          LeaveCriticalSection (&ctx->mutex);
+	  break;
+        }
+
+      ctx->writepos = (ctx->writepos + nread) % READBUF_SIZE;
+      if (!SetEvent (ctx->have_data_ev))
+	trace (("%p: SetEvent (%p) failed: ec=%d",
+                ctx, ctx->have_data_ev, (int)GetLastError ()));
+      LeaveCriticalSection (&ctx->mutex);
+    }
+  /* Indicate that we have an error or EOF.  */
+  if (!SetEvent (ctx->have_data_ev))
+    trace (("%p: SetEvent (%p) failed: ec=%d",
+            ctx, ctx->have_data_ev, (int)GetLastError ()));
+
+  trace (("%p: waiting for close", ctx));
+  WaitForSingleObject (ctx->close_ev, INFINITE);
+
+  CloseHandle (ctx->close_ev);
+  CloseHandle (ctx->have_data_ev);
+  CloseHandle (ctx->have_space_ev);
+  CloseHandle (ctx->thread_hd);
+  DeleteCriticalSection (&ctx->mutex);
+  free (ctx);  /* Standard free!  See comment in create_reader. */
+
+  return 0;
+}
+
+
+static struct reader_context_s *
+create_reader (estream_cookie_w32_pollable_t pcookie)
+{
+  struct reader_context_s *ctx;
+  SECURITY_ATTRIBUTES sec_attr;
+  DWORD tid;
+
+  memset (&sec_attr, 0, sizeof sec_attr);
+  sec_attr.nLength = sizeof sec_attr;
+  sec_attr.bInheritHandle = FALSE;
+
+  /* The CTX must be allocated in standard system memory so that we
+   * won't use any custom allocation handler which may use our lock
+   * primitives for its implementation.  The problem here is that the
+   * syscall clamp mechanism (e.g. nPth) would be called recursively:
+   * 1. For example by the caller of _gpgrt_w32_poll and 2. by
+   * gpgrt_lock_lock on behalf of the the custom allocation and free
+   * functions.  */
+  ctx = calloc (1, sizeof *ctx);
+  if (!ctx)
+    {
+      return NULL;
+    }
+
+  ctx->pcookie = pcookie;
+
+  ctx->have_data_ev = CreateEvent (&sec_attr, TRUE, FALSE, NULL);
+  if (ctx->have_data_ev)
+    ctx->have_space_ev = CreateEvent (&sec_attr, FALSE, TRUE, NULL);
+  if (ctx->have_space_ev)
+    ctx->close_ev = CreateEvent (&sec_attr, TRUE, FALSE, NULL);
+  if (!ctx->have_data_ev || !ctx->have_space_ev || !ctx->close_ev)
+    {
+      trace (("%p: CreateEvent failed: ec=%d", ctx, (int)GetLastError ()));
+      if (ctx->have_data_ev)
+	CloseHandle (ctx->have_data_ev);
+      if (ctx->have_space_ev)
+	CloseHandle (ctx->have_space_ev);
+      if (ctx->close_ev)
+	CloseHandle (ctx->close_ev);
+      _gpgrt_free (ctx);
+      return NULL;
+    }
+
+  InitializeCriticalSection (&ctx->mutex);
+
+#ifdef HAVE_W32CE_SYSTEM
+  ctx->thread_hd = CreateThread (&sec_attr, 64 * 1024, reader, ctx,
+				 STACK_SIZE_PARAM_IS_A_RESERVATION, &tid);
+#else
+  ctx->thread_hd = CreateThread (&sec_attr, 0, reader, ctx, 0, &tid);
+#endif
+
+  if (!ctx->thread_hd)
+    {
+      trace (("%p: CreateThread failed: ec=%d", ctx, (int)GetLastError ()));
+      DeleteCriticalSection (&ctx->mutex);
+      if (ctx->have_data_ev)
+	CloseHandle (ctx->have_data_ev);
+      if (ctx->have_space_ev)
+	CloseHandle (ctx->have_space_ev);
+      if (ctx->close_ev)
+	CloseHandle (ctx->close_ev);
+      _gpgrt_free (ctx);
+      return NULL;
+    }
+  else
+    {
+#if 0
+      /* We set the priority of the thread higher because we know that
+         it only runs for a short time.  This greatly helps to
+         increase the performance of the I/O.  */
+      SetThreadPriority (ctx->thread_hd, get_desired_thread_priority ());
+#endif
+    }
+
+  return ctx;
+}
+
+
+/* Prepare destruction of the reader thread for CTX.  Returns 0 if a
+   call to this function is sufficient and destroy_reader_finish shall
+   not be called.  */
+static void
+destroy_reader (struct reader_context_s *ctx)
+{
+  EnterCriticalSection (&ctx->mutex);
+  ctx->stop_me = 1;
+  if (ctx->have_space_ev)
+    SetEvent (ctx->have_space_ev);
+  LeaveCriticalSection (&ctx->mutex);
+
+#ifdef HAVE_W32CE_SYSTEM
+  /* Scenario: We never create a full pipe, but already started
+     reading.  Then we need to unblock the reader in the pipe driver
+     to make our reader thread notice that we want it to go away.  */
+
+  if (ctx->file_hd != INVALID_HANDLE_VALUE)
+    {
+      if (!DeviceIoControl (ctx->file_hd, GPGCEDEV_IOCTL_UNBLOCK,
+			NULL, 0, NULL, 0, NULL, NULL))
+	{
+	  trace (("%p: unblock control call failed: ec=%d",
+                  ctx, (int)GetLastError ()));
+	}
+    }
+#endif
+
+  /* XXX is it feasible to unblock the thread?  */
+
+  /* After setting this event CTX is void. */
+  SetEvent (ctx->close_ev);
+}
+
+
+/*
+ * Read function for pollable objects.
+ */
+static gpgrt_ssize_t
+func_w32_pollable_read (void *cookie, void *buffer, size_t count)
+{
+  estream_cookie_w32_pollable_t pcookie = cookie;
+  gpgrt_ssize_t nread;
+  struct reader_context_s *ctx;
+
+  trace (("%p: enter buffer=%p count=%u", cookie, buffer, count));
+
+  /* FIXME: implement pending check if COUNT==0 */
+
+  ctx = pcookie->reader;
+  if (ctx == NULL)
+    {
+      pcookie->reader = ctx = create_reader (pcookie);
+      if (!ctx)
+        {
+          _gpg_err_set_errno (EBADF);
+          nread = -1;
+          goto leave;
+        }
+      trace (("%p: new reader %p", cookie, pcookie->reader));
+    }
+
+  if (ctx->eof_shortcut)
+    {
+      nread = 0;
+      goto leave;
+    }
+
+  EnterCriticalSection (&ctx->mutex);
+  trace (("%p: readpos: %d, writepos %d", cookie, ctx->readpos, ctx->writepos));
+  if (ctx->readpos == ctx->writepos && !ctx->error)
+    {
+      /* No data available.  */
+      int eof = ctx->eof;
+
+      LeaveCriticalSection (&ctx->mutex);
+
+      if (pcookie->modeflags & O_NONBLOCK && ! eof)
+        {
+          _gpg_err_set_errno (EAGAIN);
+          nread = -1;
+          goto leave;
+        }
+
+      trace (("%p: waiting for data", cookie));
+      WaitForSingleObject (ctx->have_data_ev, INFINITE);
+      trace (("%p: data available", cookie));
+      EnterCriticalSection (&ctx->mutex);
+    }
+
+  if (ctx->readpos == ctx->writepos || ctx->error)
+    {
+      LeaveCriticalSection (&ctx->mutex);
+      ctx->eof_shortcut = 1;
+      if (ctx->eof)
+	return 0;
+      if (!ctx->error)
+	{
+	  trace (("%p: EOF but ctx->eof flag not set", cookie));
+          nread = 0;
+          goto leave;
+	}
+      _gpg_err_set_errno (ctx->error_code);
+      return -1;
+    }
+
+  nread = ctx->readpos < ctx->writepos
+    ? ctx->writepos - ctx->readpos
+    : READBUF_SIZE - ctx->readpos;
+  if (nread > count)
+    nread = count;
+  memcpy (buffer, ctx->buffer + ctx->readpos, nread);
+  ctx->readpos = (ctx->readpos + nread) % READBUF_SIZE;
+  if (ctx->readpos == ctx->writepos && !ctx->eof)
+    {
+      if (!ResetEvent (ctx->have_data_ev))
+	{
+	  trace (("%p: ResetEvent failed: ec=%d",
+                  cookie, (int)GetLastError ()));
+          LeaveCriticalSection (&ctx->mutex);
+	  /* FIXME: Should translate the error code.  */
+	  _gpg_err_set_errno (EIO);
+	  nread = -1;
+          goto leave;
+	}
+    }
+  if (!SetEvent (ctx->have_space_ev))
+    {
+      trace (("%p: SetEvent (%p) failed: ec=%d",
+              cookie, ctx->have_space_ev, (int)GetLastError ()));
+      LeaveCriticalSection (&ctx->mutex);
+      /* FIXME: Should translate the error code.  */
+      _gpg_err_set_errno (EIO);
+      nread = -1;
+      goto leave;
+    }
+  LeaveCriticalSection (&ctx->mutex);
+
+ leave:
+  trace_errno (nread==-1,("%p: leave nread=%d", cookie, (int)nread));
+  return nread;
+}
+
+
+/* The writer does use a simple buffering strategy so that we are
+   informed about write errors as soon as possible (i. e. with the the
+   next call to the write function.  */
+static DWORD CALLBACK
+writer (void *arg)
+{
+  struct writer_context_s *ctx = arg;
+  ssize_t nwritten;
+
+  trace (("%p: writer starting", ctx));
+
+  for (;;)
+    {
+      EnterCriticalSection (&ctx->mutex);
+      if (ctx->stop_me && !ctx->nbytes)
+	{
+          LeaveCriticalSection (&ctx->mutex);
+	  break;
+        }
+      if (!ctx->nbytes)
+	{
+	  if (!SetEvent (ctx->is_empty))
+	    trace (("%p: SetEvent failed: ec=%d", ctx, (int)GetLastError ()));
+	  if (!ResetEvent (ctx->have_data))
+	    trace (("%p: ResetEvent failed: ec=%d", ctx, (int)GetLastError ()));
+          LeaveCriticalSection (&ctx->mutex);
+	  trace (("%p: idle", ctx));
+	  WaitForSingleObject (ctx->have_data, INFINITE);
+	  trace (("%p: got data to write", ctx));
+          EnterCriticalSection (&ctx->mutex);
+        }
+      if (ctx->stop_me && !ctx->nbytes)
+	{
+          LeaveCriticalSection (&ctx->mutex);
+	  break;
+        }
+      LeaveCriticalSection (&ctx->mutex);
+
+      trace (("%p: writing up to %d bytes", ctx, ctx->nbytes));
+
+      nwritten = ctx->pcookie->next_functions.public.func_write
+        (ctx->pcookie->next_cookie, ctx->buffer, ctx->nbytes);
+      trace (("%p: wrote %d bytes", ctx, nwritten));
+      if (nwritten < 1)
+        {
+          /* XXX */
+          if (errno == ERROR_BUSY)
+            {
+              /* Probably stop_me is set now.  */
+              trace (("%p: pipe busy (unblocked?)", ctx));
+              continue;
+            }
+
+          ctx->error_code = errno;
+          ctx->error = 1;
+          trace (("%p: write error: ec=%d", ctx, ctx->error_code));
+          break;
+        }
+
+      EnterCriticalSection (&ctx->mutex);
+      ctx->nbytes -= nwritten;
+      LeaveCriticalSection (&ctx->mutex);
+    }
+  /* Indicate that we have an error.  */
+  if (!SetEvent (ctx->is_empty))
+    trace (("%p: SetEvent failed: ec=%d", ctx, (int)GetLastError ()));
+
+  trace (("%p: waiting for close", ctx));
+  WaitForSingleObject (ctx->close_ev, INFINITE);
+
+  if (ctx->nbytes)
+    trace (("%p: still %d bytes in buffer at close time", ctx, ctx->nbytes));
+
+  CloseHandle (ctx->close_ev);
+  CloseHandle (ctx->have_data);
+  CloseHandle (ctx->is_empty);
+  CloseHandle (ctx->thread_hd);
+  DeleteCriticalSection (&ctx->mutex);
+  trace (("%p: writer is destroyed", ctx));
+  free (ctx); /* Standard free!  See comment in create_writer. */
+
+  return 0;
+}
+
+
+static struct writer_context_s *
+create_writer (estream_cookie_w32_pollable_t pcookie)
+{
+  struct writer_context_s *ctx;
+  SECURITY_ATTRIBUTES sec_attr;
+  DWORD tid;
+
+  memset (&sec_attr, 0, sizeof sec_attr);
+  sec_attr.nLength = sizeof sec_attr;
+  sec_attr.bInheritHandle = FALSE;
+
+  /* See comment at create_reader.  */
+  ctx = calloc (1, sizeof *ctx);
+  if (!ctx)
+    {
+      return NULL;
+    }
+
+  ctx->pcookie = pcookie;
+
+  ctx->have_data = CreateEvent (&sec_attr, TRUE, FALSE, NULL);
+  if (ctx->have_data)
+    ctx->is_empty  = CreateEvent (&sec_attr, TRUE, TRUE, NULL);
+  if (ctx->is_empty)
+    ctx->close_ev = CreateEvent (&sec_attr, TRUE, FALSE, NULL);
+  if (!ctx->have_data || !ctx->is_empty || !ctx->close_ev)
+    {
+      trace (("%p: CreateEvent failed: ec=%d", ctx, (int)GetLastError ()));
+      if (ctx->have_data)
+	CloseHandle (ctx->have_data);
+      if (ctx->is_empty)
+	CloseHandle (ctx->is_empty);
+      if (ctx->close_ev)
+	CloseHandle (ctx->close_ev);
+      _gpgrt_free (ctx);
+      return NULL;
+    }
+
+  InitializeCriticalSection (&ctx->mutex);
+
+#ifdef HAVE_W32CE_SYSTEM
+  ctx->thread_hd = CreateThread (&sec_attr, 64 * 1024, writer, ctx,
+				 STACK_SIZE_PARAM_IS_A_RESERVATION, &tid);
+#else
+  ctx->thread_hd = CreateThread (&sec_attr, 0, writer, ctx, 0, &tid );
+#endif
+
+  if (!ctx->thread_hd)
+    {
+      trace (("%p: CreateThread failed: ec=%d", ctx, (int)GetLastError ()));
+      DeleteCriticalSection (&ctx->mutex);
+      if (ctx->have_data)
+	CloseHandle (ctx->have_data);
+      if (ctx->is_empty)
+	CloseHandle (ctx->is_empty);
+      if (ctx->close_ev)
+	CloseHandle (ctx->close_ev);
+      _gpgrt_free (ctx);
+      return NULL;
+    }
+  else
+    {
+#if 0
+      /* We set the priority of the thread higher because we know
+	 that it only runs for a short time.  This greatly helps to
+	 increase the performance of the I/O.  */
+      SetThreadPriority (ctx->thread_hd, get_desired_thread_priority ());
+#endif
+    }
+
+  return ctx;
+}
+
+
+static void
+destroy_writer (struct writer_context_s *ctx)
+{
+  trace (("%p: enter pollable_destroy_writer", ctx));
+  EnterCriticalSection (&ctx->mutex);
+  trace (("%p: setting stopme", ctx));
+  ctx->stop_me = 1;
+  if (ctx->have_data)
+    SetEvent (ctx->have_data);
+  LeaveCriticalSection (&ctx->mutex);
+
+  trace (("%p: waiting for empty", ctx));
+
+  /* Give the writer a chance to flush the buffer.  */
+  WaitForSingleObject (ctx->is_empty, INFINITE);
+
+#ifdef HAVE_W32CE_SYSTEM
+  /* Scenario: We never create a full pipe, but already started
+     writing more than the pipe buffer.  Then we need to unblock the
+     writer in the pipe driver to make our writer thread notice that
+     we want it to go away.  */
+
+  if (!DeviceIoControl (ctx->file_hd, GPGCEDEV_IOCTL_UNBLOCK,
+			NULL, 0, NULL, 0, NULL, NULL))
+    {
+      trace (("%p: unblock control call failed: ec=%d",
+              ctx, (int)GetLastError ()));
+    }
+#endif
+
+  /* After setting this event CTX is void.  */
+  trace (("%p: set close_ev", ctx));
+  SetEvent (ctx->close_ev);
+  trace (("%p: leave pollable_destroy_writer", ctx));
+}
+
+
+/*
+ * Write function for pollable objects.
+ */
+static gpgrt_ssize_t
+func_w32_pollable_write (void *cookie, const void *buffer, size_t count)
+{
+  estream_cookie_w32_pollable_t pcookie = cookie;
+  struct writer_context_s *ctx = pcookie->writer;
+  int nwritten;
+
+  trace (("%p: enter buffer: %p count: %d", cookie, buffer, count));
+  if (count == 0)
+    {
+      nwritten = 0;
+      goto leave;
+    }
+
+  if (ctx == NULL)
+    {
+      pcookie->writer = ctx = create_writer (pcookie);
+      if (!ctx)
+        {
+          nwritten = -1;
+          goto leave;
+        }
+      trace (("%p: new writer %p", cookie, pcookie->writer));
+    }
+
+  EnterCriticalSection (&ctx->mutex);
+  trace (("%p: buffer: %p, count: %d, nbytes: %d",
+          cookie, buffer, count, ctx->nbytes));
+  if (!ctx->error && ctx->nbytes)
+    {
+      /* Bytes are pending for send.  */
+
+      /* Reset the is_empty event.  Better safe than sorry.  */
+      if (!ResetEvent (ctx->is_empty))
+	{
+          trace (("%p: ResetEvent failed: ec=%d",
+                  cookie, (int)GetLastError ()));
+          LeaveCriticalSection (&ctx->mutex);
+	  /* FIXME: Should translate the error code.  */
+	  _gpg_err_set_errno (EIO);
+	  nwritten = -1;
+          goto leave;
+	}
+      LeaveCriticalSection (&ctx->mutex);
+
+      if (pcookie->modeflags & O_NONBLOCK)
+        {
+          trace (("%p: would block", cookie));
+          _gpg_err_set_errno (EAGAIN);
+          nwritten = -1;
+          goto leave;
+        }
+
+      trace (("%p: waiting for empty buffer", cookie));
+      WaitForSingleObject (ctx->is_empty, INFINITE);
+      trace (("%p: buffer is empty", cookie));
+      EnterCriticalSection (&ctx->mutex);
+    }
+
+  if (ctx->error)
+    {
+      LeaveCriticalSection (&ctx->mutex);
+      if (ctx->error_code == ERROR_NO_DATA)
+        _gpg_err_set_errno (EPIPE);
+      else
+        _gpg_err_set_errno (EIO);
+      nwritten = -1;
+      goto leave;
+    }
+
+  /* If no error occurred, the number of bytes in the buffer must be
+     zero.  */
+  gpgrt_assert (!ctx->nbytes);
+
+  if (count > WRITEBUF_SIZE)
+    count = WRITEBUF_SIZE;
+  memcpy (ctx->buffer, buffer, count);
+  ctx->nbytes = count;
+
+  /* We have to reset the is_empty event early, because it is also
+     used by the select() implementation to probe the channel.  */
+  if (!ResetEvent (ctx->is_empty))
+    {
+      trace (("%p: ResetEvent failed: ec=%d", cookie, (int)GetLastError ()));
+      LeaveCriticalSection (&ctx->mutex);
+      /* FIXME: Should translate the error code.  */
+      _gpg_err_set_errno (EIO);
+      nwritten = -1;
+      goto leave;
+    }
+  if (!SetEvent (ctx->have_data))
+    {
+      trace (("%p: SetEvent failed: ec=%d", cookie, (int)GetLastError ()));
+      LeaveCriticalSection (&ctx->mutex);
+      /* FIXME: Should translate the error code.  */
+      _gpg_err_set_errno (EIO);
+      nwritten = -1;
+      goto leave;
+    }
+  LeaveCriticalSection (&ctx->mutex);
+
+  nwritten = count;
+
+ leave:
+  trace_errno (nwritten==-1,("%p: leave nwritten=%d", cookie, nwritten));
+  return nwritten;
+}
+
+
+/* This is the core of _gpgrt_poll.  The caller needs to make sure that
+ * the syscall clamp has been engaged.  */
+int
+_gpgrt_w32_poll (gpgrt_poll_t *fds, size_t nfds, int timeout)
+{
+  HANDLE waitbuf[MAXIMUM_WAIT_OBJECTS];
+  int waitidx[MAXIMUM_WAIT_OBJECTS];
+#ifdef ENABLE_TRACING
+  char waitinfo[MAXIMUM_WAIT_OBJECTS];
+#endif
+  unsigned int code;
+  int nwait;
+  int i;
+  int any;
+  int count;
+
+#if 0
+ restart:
+#endif
+
+  any = 0;
+  nwait = 0;
+  count = 0;
+  for (i = 0; i < nfds; i++)
+    {
+      struct estream_cookie_w32_pollable *pcookie;
+
+      if (fds[i].ignore)
+	continue;
+
+      if (fds[i].stream->intern->kind != BACKEND_W32_POLLABLE)
+        {
+          /* This stream does not support polling.  */
+          fds[i].got_err = 1;
+          continue;
+        }
+
+      pcookie = fds[i].stream->intern->cookie;
+
+      if (fds[i].want_read || fds[i].want_write)
+	{
+          /* XXX: What if one wants read and write, is that supported?  */
+	  if (fds[i].want_read)
+	    {
+	      struct reader_context_s *ctx = pcookie->reader;
+              if (ctx == NULL)
+                {
+                  pcookie->reader = ctx = create_reader (pcookie);
+                  if (!ctx)
+                    {
+                      /* FIXME:  Is the error code appropriate?  */
+                      _gpg_err_set_errno (EBADF);
+                      return -1;
+                    }
+                  trace (("%p: new reader %p", pcookie, pcookie->reader));
+                }
+              trace (("%p: using reader %p", pcookie, pcookie->reader));
+
+              if (nwait >= DIM (waitbuf))
+                {
+                  trace (("oops: too many objects for WFMO"));
+                  /* FIXME: Should translate the error code.  */
+                  _gpg_err_set_errno (EIO);
+                  return -1;
+                }
+              waitidx[nwait] = i;
+#ifdef ENABLE_TRACING
+              waitinfo[nwait] = 'r';
+#endif /*ENABLE_TRACING*/
+              waitbuf[nwait++] = ctx->have_data_ev;
+	      any = 1;
+            }
+	  else if (fds[i].want_write)
+	    {
+	      struct writer_context_s *ctx = pcookie->writer;
+              if (ctx == NULL)
+                {
+                  pcookie->writer = ctx = create_writer (pcookie);
+                  if (!ctx)
+                    {
+                      trace (("oops: create writer failed"));
+                      /* FIXME:  Is the error code appropriate?  */
+                      _gpg_err_set_errno (EBADF);
+                      return -1;
+                    }
+                  trace (("%p: new writer %p", pcookie, pcookie->writer));
+                }
+              trace (("%p: using writer %p", pcookie, pcookie->writer));
+
+              if (nwait >= DIM (waitbuf))
+                {
+                  trace (("oops: Too many objects for WFMO"));
+                  /* FIXME: Should translate the error code.  */
+                  _gpg_err_set_errno (EIO);
+                  return -1;
+                }
+              waitidx[nwait] = i;
+#ifdef ENABLE_TRACING
+              waitinfo[nwait] = 'w';
+#endif /*ENABLE_TRACING*/
+              waitbuf[nwait++] = ctx->is_empty;
+	      any = 1;
+            }
+        }
+    }
+#ifdef ENABLE_TRACING
+  trace_start (("poll on [ "));
+  for (i = 0; i < nwait; i++)
+    trace_append (("%d/%c ", waitidx[i], waitinfo[i]));
+  trace_finish (("]"));
+#endif /*ENABLE_TRACING*/
+
+  if (!any)
+    {
+      /* WFMO needs at least one object, thus we use use sleep here.
+       * INFINITE wait does not make any sense in this case, so we
+       * error out. */
+      if (timeout == -1)
+        {
+          _gpg_err_set_errno (EINVAL);
+          return -1;
+        }
+      if (timeout)
+        Sleep (timeout);
+      code = WAIT_TIMEOUT;
+    }
+  else
+    code = WaitForMultipleObjects (nwait, waitbuf, 0,
+                                   timeout == -1 ? INFINITE : timeout);
+
+  if (code < WAIT_OBJECT_0 + nwait)
+    {
+      /* This WFMO is a really silly function: It does return either
+	 the index of the signaled object or if 2 objects have been
+	 signalled at the same time, the index of the object with the
+	 lowest object is returned - so and how do we find out how
+	 many objects have been signaled???.  The only solution I can
+	 imagine is to test each object starting with the returned
+	 index individually - how dull.  */
+      any = 0;
+      for (i = code - WAIT_OBJECT_0; i < nwait; i++)
+	{
+	  if (WaitForSingleObject (waitbuf[i], 0) == WAIT_OBJECT_0)
+	    {
+	      gpgrt_assert (waitidx[i] >=0 && waitidx[i] < nfds);
+              /* XXX: What if one wants read and write, is that
+                 supported?  */
+              if (fds[waitidx[i]].want_read)
+                fds[waitidx[i]].got_read = 1;
+              else if (fds[waitidx[i]].want_write)
+                fds[waitidx[i]].got_write = 1;
+	      any = 1;
+	      count++;
+	    }
+	}
+      if (!any)
+	{
+	  trace (("no signaled objects found after WFMO"));
+	  count = -1;
+	}
+    }
+  else if (code == WAIT_TIMEOUT)
+    trace (("WFMO timed out"));
+  else if (code == WAIT_FAILED)
+    {
+      trace (("WFMO failed: ec=%d", (int)GetLastError ()));
+#if 0
+      if (GetLastError () == ERROR_INVALID_HANDLE)
+	{
+	  int k;
+	  int j = handle_to_fd (waitbuf[i]);
+
+	  trace (("WFMO invalid handle %d removed", j));
+	  for (k = 0 ; k < nfds; k++)
+	    {
+	      if (fds[k].fd == j)
+		{
+		  fds[k].want_read = fds[k].want_write = 0;
+		  goto restart;
+                }
+            }
+	  trace ((" oops, or not???"));
+        }
+#endif
+      count = -1;
+    }
+  else
+    {
+      trace (("WFMO returned %u", code));
+      count = -1;
+    }
+
+  if (count > 0)
+    {
+      trace_start (("poll OK [ "));
+      for (i = 0; i < nfds; i++)
+	{
+	  if (fds[i].ignore)
+	    continue;
+	  if (fds[i].got_read || fds[i].got_write)
+	    trace_append (("%c%d ", fds[i].want_read ? 'r' : 'w', i));
+        }
+      trace_finish (("]"));
+    }
+
+  if (count < 0)
+    {
+      /* FIXME: Should determine a proper error code.  */
+      _gpg_err_set_errno (EIO);
+    }
+
+  return count;
+}
+
+
+
+/*
+ * Implementation of pollable I/O on Windows.
+ */
+
+/*
+ * Constructor for pollable objects.
+ */
+int
+_gpgrt_w32_pollable_create (void *_GPGRT__RESTRICT *_GPGRT__RESTRICT cookie,
+                            unsigned int modeflags,
+                            struct cookie_io_functions_s next_functions,
+                            void *next_cookie)
+{
+  estream_cookie_w32_pollable_t pcookie;
+  int err;
+
+  pcookie = _gpgrt_malloc (sizeof *pcookie);
+  if (!pcookie)
+    err = -1;
+  else
+    {
+      pcookie->modeflags = modeflags;
+      pcookie->next_functions = next_functions;
+      pcookie->next_cookie = next_cookie;
+      pcookie->reader = NULL;
+      pcookie->writer = NULL;
+      *cookie = pcookie;
+      err = 0;
+    }
+
+  trace_errno (err,("cookie=%p", *cookie));
+  return err;
+}
+
+
+/*
+ * Seek function for pollable objects.
+ */
+static int
+func_w32_pollable_seek (void *cookie, gpgrt_off_t *offset, int whence)
+{
+  estream_cookie_w32_pollable_t pcookie = cookie;
+  (void) pcookie;
+  (void) offset;
+  (void) whence;
+  /* XXX */
+  _gpg_err_set_errno (EOPNOTSUPP);
+  return -1;
+}
+
+
+/*
+ * The IOCTL function for pollable objects.
+ */
+static int
+func_w32_pollable_ioctl (void *cookie, int cmd, void *ptr, size_t *len)
+{
+  estream_cookie_w32_pollable_t pcookie = cookie;
+  cookie_ioctl_function_t func_ioctl = pcookie->next_functions.func_ioctl;
+
+  if (cmd == COOKIE_IOCTL_NONBLOCK)
+    {
+      if (ptr)
+        pcookie->modeflags |= O_NONBLOCK;
+      else
+        pcookie->modeflags &= ~O_NONBLOCK;
+      return 0;
+    }
+
+  if (func_ioctl)
+    return func_ioctl (pcookie->next_cookie, cmd, ptr, len);
+
+  _gpg_err_set_errno (EOPNOTSUPP);
+  return -1;
+}
+
+
+/*
+ * The destroy function for pollable objects.
+ */
+static int
+func_w32_pollable_destroy (void *cookie)
+{
+  estream_cookie_w32_pollable_t pcookie = cookie;
+
+  if (cookie)
+    {
+      if (pcookie->reader)
+        destroy_reader (pcookie->reader);
+      if (pcookie->writer)
+        destroy_writer (pcookie->writer);
+      pcookie->next_functions.public.func_close (pcookie->next_cookie);
+      _gpgrt_free (pcookie);
+    }
+  return 0;
+}
+
+/*
+ * Access object for the pollable functions.
+ */
+struct cookie_io_functions_s _gpgrt_functions_w32_pollable =
+  {
+    {
+      func_w32_pollable_read,
+      func_w32_pollable_write,
+      func_w32_pollable_seek,
+      func_w32_pollable_destroy,
+    },
+    func_w32_pollable_ioctl,
+  };