Fix Windows port (spawn and assuan engine).
[gpgme.git] / src / w32-io.c
index 1a65e537db695cd74c846a1f71339b0ebfa89c9c..10e0dade187d600dc7bd069bd91776f057fd32e0 100644 (file)
@@ -1,6 +1,6 @@
 /* w32-io.c - W32 API I/O functions.
    Copyright (C) 2000 Werner Koch (dd9jn)
-   Copyright (C) 2001, 2002, 2003, 2004, 2007 g10 Code GmbH
+   Copyright (C) 2001, 2002, 2003, 2004, 2007, 2010 g10 Code GmbH
 
    This file is part of GPGME.
  
 #include <string.h>
 #include <assert.h>
 #include <errno.h>
-#include <signal.h>
 #include <fcntl.h>
-#include <sys/time.h>
-#include <sys/types.h>
-#include <windows.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 "util.h"
+
+#ifdef HAVE_W32CE_SYSTEM
+#include <assuan.h>
+#include <winioctl.h>
+#define GPGCEDEV_IOCTL_UNBLOCK                                        \
+  CTL_CODE (FILE_DEVICE_STREAMS, 2050, METHOD_BUFFERED, FILE_ANY_ACCESS)
+#define GPGCEDEV_IOCTL_ASSIGN_RVID                                    \
+  CTL_CODE (FILE_DEVICE_STREAMS, 2051, METHOD_BUFFERED, FILE_ANY_ACCESS)
+#endif
+
 #include "sema.h"
 #include "priv-io.h"
 #include "debug.h"
 
 
-/* We assume that a HANDLE can be represented by an int which should
-   be true for all i386 systems (HANDLE is defined as void *) and
-   these are the only systems for which Windows is available.  Further
-   we assume that -1 denotes an invalid handle.  */
+/* FIXME: Optimize.  */
+#define MAX_SLAFD 512
+
+static struct
+{
+  int used;
+
+  /* If this is not INVALID_HANDLE_VALUE, then it's a handle.  */
+  HANDLE handle;
+
+  /* If this is not INVALID_SOCKET, then it's a Windows socket.  */
+  int socket;
+
+  /* If this is not 0, then it's a rendezvous ID for the pipe server.  */
+  int rvid;
+
+  /* DUP_FROM is -1 if this file descriptor was allocated by pipe or
+     socket functions.  Only then should the handle or socket be
+     destroyed when this FD is closed.  This, together with the fact
+     that dup'ed file descriptors are closed before the file
+     descriptors from which they are dup'ed are closed, ensures that
+     the handle or socket is always valid, and shared among all file
+     descriptors refering to the same underlying object.
+
+     The logic behind this is that there is only one reason for us to
+     dup file descriptors anyway: to allow simpler book-keeping of
+     file descriptors shared between GPGME and libassuan, which both
+     want to close something.  Using the same handle for these
+     duplicates works just fine.  */
+  int dup_from;
+} fd_table[MAX_SLAFD];  
+
+
+/* Returns the FD or -1 on resource limit.  */
+int
+new_fd (void)
+{
+  int idx;
+
+  for (idx = 0; idx < MAX_SLAFD; idx++)
+    if (! fd_table[idx].used)
+      break;
+
+  if (idx == MAX_SLAFD)
+    {
+      gpg_err_set_errno (EIO);
+      return -1;
+    }
+
+  fd_table[idx].used = 1;
+  fd_table[idx].handle = INVALID_HANDLE_VALUE;
+  fd_table[idx].socket = INVALID_SOCKET;
+  fd_table[idx].rvid = 0;
+  fd_table[idx].dup_from = -1;
+
+  return idx;
+}
+
+
+void
+release_fd (int fd)
+{
+  if (fd < 0 || fd >= MAX_SLAFD || !fd_table[fd].used)
+    return;
+
+  fd_table[fd].used = 0;
+  fd_table[fd].handle = INVALID_HANDLE_VALUE;
+  fd_table[fd].socket = INVALID_SOCKET;
+  fd_table[fd].rvid = 0;
+  fd_table[fd].dup_from = -1;
+}
+
 
-#define fd_to_handle(a)  ((HANDLE)(a))
 #define handle_to_fd(a)  ((int)(a))
-#define pid_to_handle(a) ((HANDLE)(a))
-#define handle_to_pid(a) ((int)(a))
 
 #define READBUF_SIZE 4096
 #define WRITEBUF_SIZE 4096
 #define PIPEBUF_SIZE  4096
-#define MAX_READERS 40
-#define MAX_WRITERS 40
+#define MAX_READERS 64
+#define MAX_WRITERS 64
 
 static struct
 {
@@ -62,13 +139,14 @@ static struct
   int fd;
   _gpgme_close_notify_handler_t handler;
   void *value;
-} notify_table[256];
+} notify_table[MAX_SLAFD];
 DEFINE_STATIC_LOCK (notify_table_lock);
 
 
 struct reader_context_s
 {
   HANDLE file_hd;
+  int file_sock;
   HANDLE thread_hd;    
   int refcount;
 
@@ -103,6 +181,7 @@ DEFINE_STATIC_LOCK (reader_table_lock);
 struct writer_context_s
 {
   HANDLE file_hd;
+  int file_sock;
   HANDLE thread_hd;    
   int refcount;
 
@@ -154,6 +233,9 @@ get_desired_thread_priority (void)
 static HANDLE
 set_synchronize (HANDLE hd)
 {
+#ifdef HAVE_W32CE_SYSTEM
+  return hd;
+#else
   HANDLE new_hd;
 
   /* For NT we have to set the sync flag.  It seems that the only way
@@ -165,12 +247,13 @@ set_synchronize (HANDLE hd)
       TRACE1 (DEBUG_SYSIO, "gpgme:set_synchronize", hd,
              "DuplicateHandle failed: ec=%d", (int) GetLastError ());
       /* FIXME: Should translate the error code.  */
-      errno = EIO;
+      gpg_err_set_errno (EIO);
       return INVALID_HANDLE_VALUE;
     }
 
   CloseHandle (hd);
   return new_hd;
+#endif
 }
 
 
@@ -180,9 +263,15 @@ reader (void *arg)
   struct reader_context_s *ctx = arg;
   int nbytes;
   DWORD nread;
+  int sock;
   TRACE_BEG1 (DEBUG_SYSIO, "gpgme:reader", ctx->file_hd,
              "thread=%p", ctx->thread_hd);
 
+  if (ctx->file_hd != INVALID_HANDLE_VALUE)
+    sock = 0;
+  else
+    sock = 1;
+
   for (;;)
     {
       LOCK (ctx->mutex);
@@ -210,37 +299,66 @@ reader (void *arg)
        nbytes = READBUF_SIZE - ctx->writepos;
       UNLOCK (ctx->mutex);
       
-      TRACE_LOG1 ("reading %d bytes", nbytes);
-      if (!ReadFile (ctx->file_hd,
-                    ctx->buffer + ctx->writepos, nbytes, &nread, NULL))
-       {
-         ctx->error_code = (int) GetLastError ();
-         if (ctx->error_code == ERROR_BROKEN_PIPE)
-           {
-             ctx->eof = 1;
-             TRACE_LOG ("got EOF (broken pipe)");
+      TRACE_LOG2 ("%s %d bytes", sock? "receiving":"reading", nbytes);
+
+      if (sock)
+        {
+          int n;
+
+          n = recv (ctx->file_sock, ctx->buffer + ctx->writepos, nbytes, 0);
+          if (n < 0)
+            {
+              ctx->error_code = (int) WSAGetLastError ();
+              if (ctx->error_code == ERROR_BROKEN_PIPE)
+                {
+                  ctx->eof = 1;
+                  TRACE_LOG ("got EOF (broken connection)");
+                }
+              else
+                {
+                  ctx->error = 1;
+                  TRACE_LOG1 ("recv error: ec=%d", ctx->error_code);
+                }
+              break;
             }
-         else
-           {
-             ctx->error = 1;
-             TRACE_LOG1 ("read error: ec=%d", ctx->error_code);
+          nread = n;
+        }
+      else
+        {
+          if (!ReadFile (ctx->file_hd,
+                         ctx->buffer + ctx->writepos, nbytes, &nread, NULL))
+            {
+              ctx->error_code = (int) GetLastError ();
+             /* 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_LOG ("got EOF (broken pipe)");
+                }
+              else
+                {
+                  ctx->error = 1;
+                  TRACE_LOG1 ("read error: ec=%d", ctx->error_code);
+                }
+              break;
             }
+        }
+      LOCK (ctx->mutex);
+      if (ctx->stop_me)
+       {
+         UNLOCK (ctx->mutex);
          break;
         }
       if (!nread)
        {
          ctx->eof = 1;
          TRACE_LOG ("got eof");
+         UNLOCK (ctx->mutex);
          break;
         }
       TRACE_LOG1 ("got %u bytes", nread);
       
-      LOCK (ctx->mutex);
-      if (ctx->stop_me)
-       {
-         UNLOCK (ctx->mutex);
-         break;
-        }
       ctx->writepos = (ctx->writepos + nread) % READBUF_SIZE;
       if (!SetEvent (ctx->have_data_ev))
        TRACE_LOG2 ("SetEvent (0x%x) failed: ec=%d", ctx->have_data_ev,
@@ -258,7 +376,7 @@ reader (void *arg)
 
 
 static struct reader_context_s *
-create_reader (HANDLE fd)
+create_reader (int fd)
 {
   struct reader_context_s *ctx;
   SECURITY_ATTRIBUTES sec_attr;
@@ -277,7 +395,14 @@ create_reader (HANDLE fd)
       return NULL;
     }
 
-  ctx->file_hd = fd;
+  if (fd < 0 || fd >= MAX_SLAFD || !fd_table[fd].used)
+    {
+      TRACE_SYSERR (EIO);
+      return NULL;
+    }
+  ctx->file_hd = fd_table[fd].handle;
+  ctx->file_sock = fd_table[fd].socket;
+
   ctx->refcount = 1;
   ctx->have_data_ev = CreateEvent (&sec_attr, TRUE, FALSE, NULL);
   if (ctx->have_data_ev)
@@ -302,7 +427,13 @@ create_reader (HANDLE fd)
   ctx->have_data_ev = set_synchronize (ctx->have_data_ev);
   INIT_LOCK (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_LOG1 ("CreateThread failed: ec=%d", (int) GetLastError ());
@@ -345,6 +476,22 @@ destroy_reader (struct reader_context_s *ctx)
     SetEvent (ctx->have_space_ev);
   UNLOCK (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))
+       {
+         TRACE1 (DEBUG_SYSIO, "gpgme:destroy_reader", ctx->file_hd,
+                 "unblock control call failed for thread %p", ctx->thread_hd);
+       }
+    }
+#endif
+
   TRACE1 (DEBUG_SYSIO, "gpgme:destroy_reader", ctx->file_hd,
          "waiting for termination of thread %p", ctx->thread_hd);
   WaitForSingleObject (ctx->stopped, INFINITE);
@@ -388,7 +535,7 @@ find_reader (int fd, int start_it)
 
   if (i != reader_table_size)
     {
-      rd = create_reader (fd_to_handle (fd));
+      rd = create_reader (fd);
       reader_table[i].fd = fd;
       reader_table[i].context = rd;
       reader_table[i].used = 1;
@@ -430,7 +577,7 @@ _gpgme_io_read (int fd, void *buffer, size_t count)
   ctx = find_reader (fd, 1);
   if (!ctx)
     {
-      errno = EBADF;
+      gpg_err_set_errno (EBADF);
       return TRACE_SYSRES (-1);
     }
   if (ctx->eof_shortcut)
@@ -458,7 +605,7 @@ _gpgme_io_read (int fd, void *buffer, size_t count)
          TRACE_LOG ("EOF but ctx->eof flag not set");
          return 0;
        }
-      errno = ctx->error_code;
+      gpg_err_set_errno (ctx->error_code);
       return TRACE_SYSRES (-1);
     }
   
@@ -476,7 +623,7 @@ _gpgme_io_read (int fd, void *buffer, size_t count)
          TRACE_LOG1 ("ResetEvent failed: ec=%d", (int) GetLastError ());
          UNLOCK (ctx->mutex);
          /* FIXME: Should translate the error code.  */
-         errno = EIO;
+         gpg_err_set_errno (EIO);
          return TRACE_SYSRES (-1);
        }
     }
@@ -486,7 +633,7 @@ _gpgme_io_read (int fd, void *buffer, size_t count)
                  ctx->have_space_ev, (int) GetLastError ());
       UNLOCK (ctx->mutex);
       /* FIXME: Should translate the error code.  */
-      errno = EIO;
+      gpg_err_set_errno (EIO);
       return TRACE_SYSRES (-1);
     }
   UNLOCK (ctx->mutex);
@@ -504,9 +651,15 @@ writer (void *arg)
 {
   struct writer_context_s *ctx = arg;
   DWORD nwritten;
+  int sock;
   TRACE_BEG1 (DEBUG_SYSIO, "gpgme:writer", ctx->file_hd,
              "thread=%p", ctx->thread_hd);
 
+  if (ctx->file_hd != INVALID_HANDLE_VALUE)
+    sock = 0;
+  else
+    sock = 1;
+
   for (;;)
     {
       LOCK (ctx->mutex);
@@ -534,18 +687,45 @@ writer (void *arg)
         }
       UNLOCK (ctx->mutex);
       
-      TRACE_LOG1 ("writing %d bytes", ctx->nbytes);
+      TRACE_LOG2 ("%s %d bytes", sock?"sending":"writing", ctx->nbytes);
+
       /* Note that CTX->nbytes is not zero at this point, because
         _gpgme_io_write always writes at least 1 byte before waking
         us up, unless CTX->stop_me is true, which we catch above.  */
-      if (!WriteFile (ctx->file_hd, ctx->buffer,
-                     ctx->nbytes, &nwritten, NULL))
-       {
-         ctx->error_code = (int) GetLastError ();
-         ctx->error = 1;
-         TRACE_LOG1 ("write error: ec=%d", ctx->error_code);
-         break;
-       }
+      if (sock)
+        {
+          /* We need to try send first because a socket handle can't
+             be used with WriteFile.  */
+          int n;
+
+          n = send (ctx->file_sock, ctx->buffer, ctx->nbytes, 0);
+          if (n < 0)
+            {
+              ctx->error_code = (int) WSAGetLastError ();
+              ctx->error = 1;
+              TRACE_LOG1 ("send error: ec=%d", ctx->error_code);
+              break;
+            }
+          nwritten = n;
+        }
+      else
+        {
+          if (!WriteFile (ctx->file_hd, ctx->buffer,
+                          ctx->nbytes, &nwritten, NULL))
+            {
+             if (GetLastError () == ERROR_BUSY)
+               {
+                 /* Probably stop_me is set now.  */
+                  TRACE_LOG ("pipe busy (unblocked?)");
+                 continue;
+                }
+
+              ctx->error_code = (int) GetLastError ();
+              ctx->error = 1;
+              TRACE_LOG1 ("write error: ec=%d", ctx->error_code);
+              break;
+            }
+        }
       TRACE_LOG1 ("wrote %d bytes", (int) nwritten);
       
       LOCK (ctx->mutex);
@@ -562,7 +742,7 @@ writer (void *arg)
 
 
 static struct writer_context_s *
-create_writer (HANDLE fd)
+create_writer (int fd)
 {
   struct writer_context_s *ctx;
   SECURITY_ATTRIBUTES sec_attr;
@@ -581,7 +761,14 @@ create_writer (HANDLE fd)
       return NULL;
     }
   
-  ctx->file_hd = fd;
+  if (fd < 0 || fd >= MAX_SLAFD || !fd_table[fd].used)
+    {
+      TRACE_SYSERR (EIO);
+      return NULL;
+    }
+  ctx->file_hd = fd_table[fd].handle;
+  ctx->file_sock = fd_table[fd].socket;
+
   ctx->refcount = 1;
   ctx->have_data = CreateEvent (&sec_attr, TRUE, FALSE, NULL);
   if (ctx->have_data)
@@ -606,7 +793,13 @@ create_writer (HANDLE fd)
   ctx->is_empty = set_synchronize (ctx->is_empty);
   INIT_LOCK (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_LOG1 ("CreateThread failed: ec=%d", (int) GetLastError ());
@@ -647,6 +840,20 @@ destroy_writer (struct writer_context_s *ctx)
   if (ctx->have_data) 
     SetEvent (ctx->have_data);
   UNLOCK (ctx->mutex);
+
+#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))
+    {
+      TRACE1 (DEBUG_SYSIO, "gpgme:destroy_writer", ctx->file_hd,
+             "unblock control call failed for thread %p", ctx->thread_hd);
+    }
+#endif
   
   TRACE1 (DEBUG_SYSIO, "gpgme:destroy_writer", ctx->file_hd,
          "waiting for termination of thread %p", ctx->thread_hd);
@@ -691,7 +898,7 @@ find_writer (int fd, int start_it)
 
   if (i != writer_table_size)
     {
-      wt = create_writer (fd_to_handle (fd));
+      wt = create_writer (fd);
       writer_table[i].fd = fd;
       writer_table[i].context = wt; 
       writer_table[i].used = 1;
@@ -748,7 +955,7 @@ _gpgme_io_write (int fd, const void *buffer, size_t count)
          TRACE_LOG1 ("ResetEvent failed: ec=%d", (int) GetLastError ());
          UNLOCK (ctx->mutex);
          /* FIXME: Should translate the error code.  */
-         errno = EIO;
+         gpg_err_set_errno (EIO);
          return TRACE_SYSRES (-1);
        }
       UNLOCK (ctx->mutex);
@@ -762,9 +969,9 @@ _gpgme_io_write (int fd, const void *buffer, size_t count)
     {
       UNLOCK (ctx->mutex);
       if (ctx->error_code == ERROR_NO_DATA)
-        errno = EPIPE;
+        gpg_err_set_errno (EPIPE);
       else
-        errno = EIO;
+        gpg_err_set_errno (EIO);
       return TRACE_SYSRES (-1);
     }
 
@@ -784,7 +991,7 @@ _gpgme_io_write (int fd, const void *buffer, size_t count)
       TRACE_LOG1 ("ResetEvent failed: ec=%d", (int) GetLastError ());
       UNLOCK (ctx->mutex);
       /* FIXME: Should translate the error code.  */
-      errno = EIO;
+      gpg_err_set_errno (EIO);
       return TRACE_SYSRES (-1);
     }
   if (!SetEvent (ctx->have_data))
@@ -792,7 +999,7 @@ _gpgme_io_write (int fd, const void *buffer, size_t count)
       TRACE_LOG1 ("SetEvent failed: ec=%d", (int) GetLastError ());
       UNLOCK (ctx->mutex);
       /* FIXME: Should translate the error code.  */
-      errno = EIO;
+      gpg_err_set_errno (EIO);
       return TRACE_SYSRES (-1);
     }
   UNLOCK (ctx->mutex);
@@ -804,13 +1011,57 @@ _gpgme_io_write (int fd, const void *buffer, size_t count)
 int
 _gpgme_io_pipe (int filedes[2], int inherit_idx)
 {
+  int rfd;
+  int wfd;
+#ifdef HAVE_W32CE_SYSTEM
+  HANDLE hd;
+  int rvid;
+#else
   HANDLE rh;
   HANDLE wh;
   SECURITY_ATTRIBUTES sec_attr;
+#endif
+
   TRACE_BEG2 (DEBUG_SYSIO, "_gpgme_io_pipe", filedes,
              "inherit_idx=%i (GPGME uses it for %s)",
              inherit_idx, inherit_idx ? "reading" : "writing");
 
+  rfd = new_fd ();
+  if (rfd == -1)
+    return TRACE_SYSRES (-1);
+  wfd = new_fd ();
+  if (wfd == -1)
+    {
+      release_fd (rfd);
+      return TRACE_SYSRES (-1);
+    }
+
+#ifdef HAVE_W32CE_SYSTEM
+  hd = _assuan_w32ce_prepare_pipe (&rvid, !inherit_idx);
+  if (hd == INVALID_HANDLE_VALUE)
+    {
+      TRACE_LOG1 ("_assuan_w32ce_prepare_pipe failed: ec=%d",
+                 (int) GetLastError ());
+      release_fd (rfd);
+      release_fd (wfd);
+      /* FIXME: Should translate the error code.  */
+      gpg_err_set_errno (EIO);
+      return TRACE_SYSRES (-1);
+    }
+
+  if (inherit_idx == 0)
+    {
+      fd_table[rfd].rvid = rvid;
+      fd_table[wfd].handle = hd;
+    }
+  else
+    {
+      fd_table[rfd].handle = hd;
+      fd_table[wfd].rvid = rvid;
+    }  
+
+#else
+
   memset (&sec_attr, 0, sizeof (sec_attr));
   sec_attr.nLength = sizeof (sec_attr);
   sec_attr.bInheritHandle = FALSE;
@@ -818,8 +1069,10 @@ _gpgme_io_pipe (int filedes[2], int inherit_idx)
   if (!CreatePipe (&rh, &wh, &sec_attr, PIPEBUF_SIZE))
     {
       TRACE_LOG1 ("CreatePipe failed: ec=%d", (int) GetLastError ());
+      release_fd (rfd);
+      release_fd (wfd);
       /* FIXME: Should translate the error code.  */
-      errno = EIO;
+      gpg_err_set_errno (EIO);
       return TRACE_SYSRES (-1);
     }
 
@@ -833,10 +1086,12 @@ _gpgme_io_pipe (int filedes[2], int inherit_idx)
        {
          TRACE_LOG1 ("DuplicateHandle failed: ec=%d",
                      (int) GetLastError ());
+         release_fd (rfd);
+         release_fd (wfd);
          CloseHandle (rh);
          CloseHandle (wh);
          /* FIXME: Should translate the error code.  */
-         errno = EIO;
+         gpg_err_set_errno (EIO);
          return TRACE_SYSRES (-1);
         }
       CloseHandle (rh);
@@ -851,19 +1106,26 @@ _gpgme_io_pipe (int filedes[2], int inherit_idx)
        {
          TRACE_LOG1 ("DuplicateHandle failed: ec=%d",
                      (int) GetLastError ());
+         release_fd (rfd);
+         release_fd (wfd);
          CloseHandle (rh);
          CloseHandle (wh);
          /* FIXME: Should translate the error code.  */
-         errno = EIO;
+         gpg_err_set_errno (EIO);
          return TRACE_SYSRES (-1);
         }
       CloseHandle (wh);
       wh = hd;
     }
-  
-  filedes[0] = handle_to_fd (rh);
-  filedes[1] = handle_to_fd (wh);
-  return TRACE_SUC2 ("read=%p, write=%p", rh, wh);
+  fd_table[rfd].handle = rh;
+  fd_table[wfd].handle = wh;
+#endif
+
+  filedes[0] = rfd;
+  filedes[1] = wfd;
+  return TRACE_SUC6 ("read=0x%x (%p/0x%x), write=0x%x (%p/0x%x)",
+                    rfd, fd_table[rfd].handle, fd_table[rfd].rvid,
+                    wfd, fd_table[wfd].handle, fd_table[wfd].rvid);
 }
 
 
@@ -873,11 +1135,17 @@ _gpgme_io_close (int fd)
   int i;
   _gpgme_close_notify_handler_t handler = NULL;
   void *value = NULL;
+
   TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_close", fd);
 
   if (fd == -1)
     {
-      errno = EBADF;
+      gpg_err_set_errno (EBADF);
+      return TRACE_SYSRES (-1);
+    }
+  if (fd < 0 || fd >= MAX_SLAFD || !fd_table[fd].used)
+    {
+      gpg_err_set_errno (EBADF);
       return TRACE_SYSRES (-1);
     }
 
@@ -900,14 +1168,33 @@ _gpgme_io_close (int fd)
   if (handler)
     handler (fd, value);
 
-  if (!CloseHandle (fd_to_handle (fd)))
-    { 
-      TRACE_LOG1 ("CloseHandle failed: ec=%d", (int) GetLastError ());
-      /* FIXME: Should translate the error code.  */
-      errno = EIO;
-      return TRACE_SYSRES (-1);
+  if (fd_table[fd].dup_from == -1)
+    {
+      if (fd_table[fd].handle != INVALID_HANDLE_VALUE)
+       {
+         if (!CloseHandle (fd_table[fd].handle))
+           { 
+             TRACE_LOG1 ("CloseHandle failed: ec=%d", (int) GetLastError ());
+             /* FIXME: Should translate the error code.  */
+             gpg_err_set_errno (EIO);
+             return TRACE_SYSRES (-1);
+           }
+       }
+      else if (fd_table[fd].socket != INVALID_SOCKET)
+       {
+         if (closesocket (fd_table[fd].socket))
+           { 
+             TRACE_LOG1 ("closesocket failed: ec=%d", (int) WSAGetLastError ());
+             /* FIXME: Should translate the error code.  */
+             gpg_err_set_errno (EIO);
+             return TRACE_SYSRES (-1);
+           }
+       }
+      /* Nothing to do for RVIDs.  */
     }
 
+  release_fd (fd);
+      
   return TRACE_SYSRES (0);
 }
 
@@ -933,7 +1220,7 @@ _gpgme_io_set_close_notify (int fd, _gpgme_close_notify_handler_t handler,
   if (i == DIM (notify_table))
     {
       UNLOCK (notify_table_lock);
-      errno = EINVAL;
+      gpg_err_set_errno (EINVAL);
       return TRACE_SYSRES (-1);
     }
   notify_table[i].fd = fd;
@@ -953,6 +1240,91 @@ _gpgme_io_set_nonblocking (int fd)
 }
 
 
+#ifdef HAVE_W32CE_SYSTEM
+static char *
+build_commandline (char **argv, int fd0, int fd0_isnull,
+                  int fd1, int fd1_isnull,
+                  int fd2, int fd2_isnull)
+{
+  int i, n;
+  const char *s;
+  char *buf, *p;
+  char fdbuf[3*30];
+
+  p = fdbuf;
+  *p = 0;
+  
+  if (fd0 != -1)
+    {
+      if (fd0_isnull)
+        strcpy (p, "-&S0=null ");
+      else
+       snprintf (p, 25, "-&S0=%d ", fd_table[fd0].rvid);
+      p += strlen (p);
+    }
+  if (fd1 != -1)
+    {
+      if (fd1_isnull)
+        strcpy (p, "-&S1=null ");
+      else
+       snprintf (p, 25, "-&S1=%d ", fd_table[fd1].rvid);
+      p += strlen (p);
+    }
+  if (fd2 != -1)
+    {
+      if (fd2_isnull)
+        strcpy (p, "-&S2=null ");
+      else
+        snprintf (p, 25, "-&S2=%d ", fd_table[fd2].rvid);
+      p += strlen (p);
+    }
+  strcpy (p, "-&S2=null ");
+  p += strlen (p);
+  
+  n = strlen (fdbuf);
+  for (i=0; (s = argv[i]); i++)
+    {
+      if (!i)
+        continue; /* Ignore argv[0].  */
+      n += strlen (s) + 1 + 2;  /* (1 space, 2 quoting) */
+      for (; *s; s++)
+        if (*s == '\"')
+          n++;  /* Need to double inner quotes.  */
+    }
+  n++;
+  buf = p = malloc (n);
+  if (! buf)
+    return NULL;
+
+  p = stpcpy (p, fdbuf);
+  for (i = 0; argv[i]; i++) 
+    {
+      if (!i)
+        continue; /* Ignore argv[0].  */
+      if (i > 1)
+        p = stpcpy (p, " ");
+
+      if (! *argv[i]) /* Empty string. */
+        p = stpcpy (p, "\"\"");
+      else if (strpbrk (argv[i], " \t\n\v\f\""))
+        {
+          p = stpcpy (p, "\"");
+          for (s = argv[i]; *s; s++)
+            {
+              *p++ = *s;
+              if (*s == '\"')
+                *p++ = *s;
+            }
+          *p++ = '\"';
+          *p = 0;
+        }
+      else
+        p = stpcpy (p, argv[i]);
+    }
+
+  return buf;  
+}
+#else
 static char *
 build_commandline (char **argv)
 {
@@ -1006,13 +1378,15 @@ build_commandline (char **argv)
 
   return buf;
 }
+#endif
 
 
 int
-_gpgme_io_spawn (const char *path, char *const argv[],
-                struct spawn_fd_item_s *fd_list, pid_t *r_pid)
+_gpgme_io_spawn (const char *path, char *const argv[], unsigned int flags,
+                struct spawn_fd_item_s *fd_list,
+                void (*atfork) (void *opaque, int reserved),
+                void *atforkvalue, pid_t *r_pid)
 {
-  SECURITY_ATTRIBUTES sec_attr;
   PROCESS_INFORMATION pi =
     {
       NULL,      /* returns process handle */
@@ -1020,10 +1394,125 @@ _gpgme_io_spawn (const char *path, char *const argv[],
       0,         /* returns pid */
       0          /* returns tid */
     };
-  STARTUPINFO si;
-  int cr_flags = CREATE_DEFAULT_ERROR_MODE
-    | GetPriorityClass (GetCurrentProcess ());
   int i;
+
+#ifdef HAVE_W32CE_SYSTEM
+  int fd_in = -1;
+  int fd_out = -1;
+  int fd_err = -1;
+  int fd_in_isnull = 1;
+  int fd_out_isnull = 1;
+  int fd_err_isnull = 1;
+  char *cmdline;
+  HANDLE hd = INVALID_HANDLE_VALUE;
+
+  TRACE_BEG1 (DEBUG_SYSIO, "_gpgme_io_spawn", path,
+             "path=%s", path);
+  i = 0;
+  while (argv[i])
+    {
+      TRACE_LOG2 ("argv[%2i] = %s", i, argv[i]);
+      i++;
+    }
+
+  for (i = 0; fd_list[i].fd != -1; i++)
+    {
+      int fd = fd_list[i].fd;
+
+      TRACE_LOG3 ("fd_list[%2i] = fd %i, dup_to %i", i, fd, fd_list[i].dup_to);
+      if (fd < 0 || fd >= MAX_SLAFD || !fd_table[fd].used)
+       {
+         TRACE_LOG1 ("invalid fd 0x%x", fd);
+         gpg_err_set_errno (EBADF);
+         return TRACE_SYSRES (-1);
+       }
+      if (fd_table[fd].rvid == 0)
+       {
+         TRACE_LOG1 ("fd 0x%x not inheritable (not an RVID)", fd);
+         gpg_err_set_errno (EBADF);
+         return TRACE_SYSRES (-1);
+       }
+      
+      if (fd_list[i].dup_to == 0)
+       {
+         fd_in = fd_list[i].fd;
+         fd_in_isnull = 0;
+       }
+      else if (fd_list[i].dup_to == 1)
+       {
+         fd_out = fd_list[i].fd;
+         fd_out_isnull = 0;
+       }
+      else if (fd_list[i].dup_to == 2)
+       {
+         fd_err = fd_list[i].fd;
+         fd_err_isnull = 0;
+       }
+    }
+
+  cmdline = build_commandline (argv, fd_in, fd_in_isnull,
+                              fd_out, fd_out_isnull, fd_err, fd_err_isnull);
+  if (!cmdline)
+    {
+      TRACE_LOG1 ("build_commandline failed: %s", strerror (errno));
+      return TRACE_SYSRES (-1);
+    }
+
+  if (!CreateProcessA (path,                /* Program to start.  */
+                      cmdline,             /* Command line arguments.  */
+                      NULL,                 /* (not supported)  */
+                      NULL,                 /* (not supported)  */
+                      FALSE,                /* (not supported)  */
+                      (CREATE_SUSPENDED),   /* Creation flags.  */
+                      NULL,                 /* (not supported)  */
+                      NULL,                 /* (not supported)  */
+                      NULL,                 /* (not supported) */
+                      &pi                   /* Returns process information.*/
+                      ))
+    {
+      TRACE_LOG1 ("CreateProcess failed: ec=%d", (int) GetLastError ());
+      free (cmdline);
+      gpg_err_set_errno (EIO);
+      return TRACE_SYSRES (-1);
+    }
+
+  /* Create arbitrary pipe descriptor to send in ASSIGN_RVID
+     commands.  Errors are ignored.  We don't need read or write access,
+     as ASSIGN_RVID works without any permissions, yay!  */
+  hd = CreateFile (L"GPG1:", 0, 0,
+                  NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
+  if (hd == INVALID_HANDLE_VALUE)
+    {
+      TRACE_LOG1 ("CreateFile failed (ignored): ec=%d",
+                 (int) GetLastError ());
+    }
+
+  /* Insert the inherited handles.  */
+  for (i = 0; fd_list[i].fd != -1; i++)
+    {
+      /* Return the child name of this handle.  */
+      fd_list[i].peer_name = fd_table[fd_list[i].fd].rvid;
+
+      if (hd != INVALID_HANDLE_VALUE)
+       {
+         DWORD data[2];
+         data[0] = (DWORD) fd_table[fd_list[i].fd].rvid;
+         data[1] = pi.dwProcessId;
+         if (!DeviceIoControl (hd, GPGCEDEV_IOCTL_ASSIGN_RVID,
+                               data, sizeof (data), NULL, 0, NULL, NULL))
+           {
+             TRACE_LOG3 ("ASSIGN_RVID(%i, %i) failed (ignored): %i",
+                         data[0], data[1], (int) GetLastError ());
+           }
+       }
+    }
+  if (hd != INVALID_HANDLE_VALUE)
+    CloseHandle (hd);
+
+#else
+  SECURITY_ATTRIBUTES sec_attr;
+  STARTUPINFOA si;
+  int cr_flags = CREATE_DEFAULT_ERROR_MODE;
   char **args;
   char *arg_string;
   /* FIXME.  */
@@ -1067,7 +1556,7 @@ _gpgme_io_spawn (const char *path, char *const argv[],
   if (!arg_string)
     {
       close (tmp_fd);
-      DeleteFile (tmp_name);
+      DeleteFileA (tmp_name);
       return TRACE_SYSRES (-1);
     }
 
@@ -1079,8 +1568,9 @@ _gpgme_io_spawn (const char *path, char *const argv[],
   si.hStdOutput = INVALID_HANDLE_VALUE;
   si.hStdError = INVALID_HANDLE_VALUE;
 
-  cr_flags |= CREATE_SUSPENDED; 
+  cr_flags |= CREATE_SUSPENDED;
   cr_flags |= DETACHED_PROCESS;
+  cr_flags |= GetPriorityClass (GetCurrentProcess ());
   if (!CreateProcessA (_gpgme_get_w32spawn_path (),
                       arg_string,
                       &sec_attr,     /* process security attributes */
@@ -1095,22 +1585,30 @@ _gpgme_io_spawn (const char *path, char *const argv[],
       TRACE_LOG1 ("CreateProcess failed: ec=%d", (int) GetLastError ());
       free (arg_string);
       close (tmp_fd);
-      DeleteFile (tmp_name);
+      DeleteFileA (tmp_name);
 
       /* FIXME: Should translate the error code.  */
-      errno = EIO;
+      gpg_err_set_errno (EIO);
       return TRACE_SYSRES (-1);
     }
 
   free (arg_string);
 
+  if (flags & IOSPAWN_FLAG_ALLOW_SET_FG)
+    _gpgme_allow_set_foreground_window ((pid_t)pi.dwProcessId);
+
   /* Insert the inherited handles.  */
   for (i = 0; fd_list[i].fd != -1; i++)
     {
-      HANDLE hd;
-
+      int fd = fd_list[i].fd;
+      HANDLE ohd = INVALID_HANDLE_VALUE;
+      HANDLE hd = INVALID_HANDLE_VALUE;
+      
       /* Make it inheritable for the wrapper process.  */
-      if (!DuplicateHandle (GetCurrentProcess(), fd_to_handle (fd_list[i].fd),
+      if (fd >= 0 && fd < MAX_SLAFD && fd_table[fd].used)
+       ohd = fd_table[fd].handle;
+
+      if (!DuplicateHandle (GetCurrentProcess(), ohd,
                            pi.hProcess, &hd, 0, TRUE, DUPLICATE_SAME_ACCESS))
        {
          TRACE_LOG1 ("DuplicateHandle failed: ec=%d", (int) GetLastError ());
@@ -1122,10 +1620,10 @@ _gpgme_io_spawn (const char *path, char *const argv[],
          CloseHandle (pi.hProcess);
 
          close (tmp_fd);
-         DeleteFile (tmp_name);
+         DeleteFileA (tmp_name);
 
          /* FIXME: Should translate the error code.  */
-         errno = EIO;
+         gpg_err_set_errno (EIO);
          return TRACE_SYSRES (-1);
         }
       /* Return the child name of this handle.  */
@@ -1139,14 +1637,16 @@ _gpgme_io_spawn (const char *path, char *const argv[],
        notation: "0xFEDCBA9876543210" with an extra white space after
        every quadruplet.  10*(19*4 + 1) - 1 = 769.  This plans ahead
        for a time when a HANDLE is 64 bit.  */
-#define BUFFER_MAX 800
+#define BUFFER_MAX 810
     char line[BUFFER_MAX + 1];
     int res;
     int written;
     size_t len;
 
-    line[0] = '\n';
-    line[1] = '\0';
+    if ((flags & IOSPAWN_FLAG_ALLOW_SET_FG))
+      strcpy (line, "~1 \n");
+    else
+      strcpy (line, "\n");
     for (i = 0; fd_list[i].fd != -1; i++)
       {
        /* Strip the newline.  */
@@ -1173,6 +1673,8 @@ _gpgme_io_spawn (const char *path, char *const argv[],
   close (tmp_fd);
   /* The temporary file is deleted by the gpgme-w32spawn process
      (hopefully).  */
+#endif
+
 
   TRACE_LOG4 ("CreateProcess ready: hProcess=%p, hThread=%p, "
              "dwProcessID=%d, dwThreadId=%d",
@@ -1181,6 +1683,7 @@ _gpgme_io_spawn (const char *path, char *const argv[],
   
   if (r_pid)
     *r_pid = (pid_t)pi.dwProcessId;
+
   
   if (ResumeThread (pi.hThread) < 0)
     TRACE_LOG1 ("ResumeThread failed: ec=%d", (int) GetLastError ());
@@ -1196,8 +1699,11 @@ _gpgme_io_spawn (const char *path, char *const argv[],
     TRACE_LOG1 ("CloseHandle of process failed: ec=%d",
                (int) GetLastError ());
 
-  for (i = 0; fd_list[i].fd != -1; i++)
-    _gpgme_io_close (fd_list[i].fd);
+  if (! (flags & IOSPAWN_FLAG_NOCLOSE))
+    {
+      for (i = 0; fd_list[i].fd != -1; i++)
+       _gpgme_io_close (fd_list[i].fd);
+    }
 
   for (i = 0; fd_list[i].fd != -1; i++)
     if (fd_list[i].dup_to == -1)
@@ -1254,7 +1760,7 @@ _gpgme_io_select (struct io_select_fd_s *fds, size_t nfds, int nonblock)
                      TRACE_END (dbg_help, "oops ]");
                      TRACE_LOG ("Too many objects for WFMO!");
                      /* FIXME: Should translate the error code.  */
-                     errno = EIO;
+                     gpg_err_set_errno (EIO);
                      return TRACE_SYSRES (-1);
                     }
                  waitidx[nwait] = i;
@@ -1277,7 +1783,7 @@ _gpgme_io_select (struct io_select_fd_s *fds, size_t nfds, int nonblock)
                      TRACE_END (dbg_help, "oops ]");
                      TRACE_LOG ("Too many objects for WFMO!");
                      /* FIXME: Should translate the error code.  */
-                     errno = EIO;
+                     gpg_err_set_errno (EIO);
                      return TRACE_SYSRES (-1);
                     }
                  waitidx[nwait] = i;
@@ -1324,6 +1830,7 @@ _gpgme_io_select (struct io_select_fd_s *fds, size_t nfds, int nonblock)
   else if (code == WAIT_FAILED)
     {
       int le = (int) GetLastError ();
+#if 0
       if (le == ERROR_INVALID_HANDLE)
        {
          int k;
@@ -1340,6 +1847,7 @@ _gpgme_io_select (struct io_select_fd_s *fds, size_t nfds, int nonblock)
             }
          TRACE_LOG (" oops, or not???");
         }
+#endif
       TRACE_LOG1 ("WFMO failed: %d", le);
       count = -1;
     }
@@ -1366,7 +1874,7 @@ _gpgme_io_select (struct io_select_fd_s *fds, size_t nfds, int nonblock)
   if (count < 0)
     {
       /* FIXME: Should determine a proper error code.  */
-      errno = EIO;
+      gpg_err_set_errno (EIO);
     }
   
   return TRACE_SYSRES (count);
@@ -1386,6 +1894,15 @@ _gpgme_io_subsystem_init (void)
 int
 _gpgme_io_fd2str (char *buf, int buflen, int fd)
 {
+#ifdef HAVE_W32CE_SYSTEM
+  /* FIXME: For now. See above.  */
+  if (fd < 0 || fd >= MAX_SLAFD || !fd_table[fd].used
+      || fd_table[fd].rvid == 0)
+    fd = -1;
+  else
+    fd = fd_table[fd].rvid;
+#endif
+
   return snprintf (buf, buflen, "%d", fd);
 }
 
@@ -1393,24 +1910,28 @@ _gpgme_io_fd2str (char *buf, int buflen, int fd)
 int
 _gpgme_io_dup (int fd)
 {
-  HANDLE handle = fd_to_handle (fd);
-  HANDLE new_handle = fd_to_handle (fd);
-  int i;
+  int newfd;
   struct reader_context_s *rd_ctx;
   struct writer_context_s *wt_ctx;
+  int i;
 
   TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_dup", fd);
 
-  if (!DuplicateHandle (GetCurrentProcess(), handle,
-                       GetCurrentProcess(), &new_handle,
-                       0, FALSE, DUPLICATE_SAME_ACCESS))
+  if (fd < 0 || fd >= MAX_SLAFD || !fd_table[fd].used)
     {
-      TRACE_LOG1 ("DuplicateHandle failed: ec=%d\n", (int) GetLastError ());
-      /* FIXME: Translate error code.  */
-      errno = EIO;
+      gpg_err_set_errno (EINVAL);
       return TRACE_SYSRES (-1);
     }
 
+  newfd = new_fd();
+  if (newfd == -1)
+    return TRACE_SYSRES (-1);
+  
+  fd_table[newfd].handle = fd_table[fd].handle;
+  fd_table[newfd].socket = fd_table[fd].socket;
+  fd_table[newfd].rvid = fd_table[fd].rvid;
+  fd_table[newfd].dup_from = fd;
+
   rd_ctx = find_reader (fd, 1);
   if (rd_ctx)
     {
@@ -1424,7 +1945,7 @@ _gpgme_io_dup (int fd)
          break;
       /* FIXME.  */
       assert (i != reader_table_size);
-      reader_table[i].fd = handle_to_fd (new_handle);
+      reader_table[i].fd = newfd;
       reader_table[i].context = rd_ctx;
       reader_table[i].used = 1;
       UNLOCK (reader_table_lock);
@@ -1443,13 +1964,13 @@ _gpgme_io_dup (int fd)
          break;
       /* FIXME.  */
       assert (i != writer_table_size);
-      writer_table[i].fd = handle_to_fd (new_handle);
+      writer_table[i].fd = newfd;
       writer_table[i].context = wt_ctx;
       writer_table[i].used = 1;
       UNLOCK (writer_table_lock);
     }
 
-  return TRACE_SYSRES (handle_to_fd (new_handle));
+  return TRACE_SYSRES (newfd);
 }
 
 \f
@@ -1494,18 +2015,25 @@ int
 _gpgme_io_socket (int domain, int type, int proto)
 {
   int res;
+  int fd;
 
   TRACE_BEG2 (DEBUG_SYSIO, "_gpgme_io_socket", domain,
              "type=%i, protp=%i", type, proto);
 
+  fd = new_fd();
+  if (fd == -1)
+    return TRACE_SYSRES (-1);
+      
   res = socket (domain, type, proto);
   if (res == INVALID_SOCKET)
     {
-      errno = wsa2errno (WSAGetLastError ());
+      release_fd (fd);
+      gpg_err_set_errno (wsa2errno (WSAGetLastError ()));
       return TRACE_SYSRES (-1);
     }
+  fd_table[fd].socket = res;
 
-  TRACE_SUC1 ("socket=0x%x", res);
+  TRACE_SUC2 ("socket=0x%x (0x%x)", fd, fd_table[fd].socket);
   
   return res;
 }
@@ -1514,16 +2042,21 @@ _gpgme_io_socket (int domain, int type, int proto)
 int
 _gpgme_io_connect (int fd, struct sockaddr *addr, int addrlen)
 {
-  int sockfd;
   int res;
 
   TRACE_BEG2 (DEBUG_SYSIO, "_gpgme_io_connect", fd,
              "addr=%p, addrlen=%i", addr, addrlen);
 
-  res = connect (sockfd, addr, addrlen);
-  if (!res)
+  if (fd < 0 || fd >= MAX_SLAFD || !fd_table[fd].used)
+    {
+      gpg_err_set_errno (EBADF);
+      return TRACE_SYSRES (-1);
+    }
+    
+  res = connect (fd_table[fd].socket, addr, addrlen);
+  if (res)
     {
-      errno = wsa2errno (WSAGetLastError ());
+      gpg_err_set_errno (wsa2errno (WSAGetLastError ()));
       return TRACE_SYSRES (-1);
     }