/* gpgcedrv.c - WindowsCE device driver to implement pipe and syslog.
   Copyright (C) 2010 Free Software Foundation, Inc.

   This file is part of Assuan.

   Assuan 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 3 of
   the License, or (at your option) any later version.

   Assuan 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 <http://www.gnu.org/licenses/>.
 */


#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <windows.h>
#include <devload.h>
#include <winioctl.h>

/* FIXME Cancel not handled. */

#define DBGFILENAME "\\gpgcedev.dbg"
#define LOGFILENAME L"\\gpgcedev.log"
#define GPGCEDEV_KEY_NAME  L"Drivers\\GnuPG_Device"
#define GPGCEDEV_KEY_NAME2 L"Drivers\\GnuPG_Log"


/* Missing IOCTLs in the current mingw32ce.  */
#ifndef IOCTL_PSL_NOTIFY
# define FILE_DEVICE_PSL 259
# define IOCTL_PSL_NOTIFY                               \
  CTL_CODE (259, 255, METHOD_NEITHER, FILE_ANY_ACCESS)
#endif /*IOCTL_PSL_NOTIFY*/


/* The IOCTL to return the rendezvous id of the handle.

   The required outbuf parameter is the address of a variable to store
   the rendezvous ID, which is a LONG value.  */
#define GPGCEDEV_IOCTL_GET_RVID \
  CTL_CODE (FILE_DEVICE_STREAMS, 2048, METHOD_BUFFERED, FILE_ANY_ACCESS)

/* The IOCTL used to create the pipe. 

   The caller sends this IOCTL to the read or the write handle.  The
   required inbuf parameter is address of a variable holding the
   rendezvous id of the pipe's other end.  There is one possible
   problem with the code: If a pipe is kept in non-rendezvous state
   until after the rendezvous ids overflow, it is possible that the
   wrong end will be used.  However this is not a realistic scenario.  */
#define GPGCEDEV_IOCTL_MAKE_PIPE \
  CTL_CODE (FILE_DEVICE_STREAMS, 2049, METHOD_BUFFERED, FILE_ANY_ACCESS)

/* The IOCTL used to unblock a blocking thread.

   The caller sends this IOCTL to the read or the write handle.  No
   parameter is required.  The effect is that a reader or writer
   blocked on the same handle is unblocked (and will return
   ERROR_BUSY).  Note that the operation can be repeated, if so
   desired.  The state of the pipe itself will not be affected in any
   way.  */
#define GPGCEDEV_IOCTL_UNBLOCK \
  CTL_CODE (FILE_DEVICE_STREAMS, 2050, METHOD_BUFFERED, FILE_ANY_ACCESS)

/* The IOCTL to assign a rendezvous id to a process.

   The required inbuf parameters are the rendezvous ID to assign and
   the process ID of the process receiving the RVID.  The handle on
   which this is called is not really used at all!  */
#define GPGCEDEV_IOCTL_ASSIGN_RVID \
  CTL_CODE (FILE_DEVICE_STREAMS, 2051, METHOD_BUFFERED, FILE_ANY_ACCESS)


/* An object to describe a pipe.  */
struct pipeimpl_s
{
  CRITICAL_SECTION critsect;  /* Lock for all members.  */

  int refcnt;
  char *buffer;       
  size_t buffer_size;
  size_t buffer_len;  /* The valid length of the bufer.  */
  size_t buffer_pos;  /* The actual read position.  */

#define PIPE_FLAG_NO_READER 1
#define PIPE_FLAG_NO_WRITER 2
#define PIPE_FLAG_UNBLOCK_READER 4
#define PIPE_FLAG_UNBLOCK_WRITER 8
#define PIPE_FLAG_HALT_MONITOR 16
  int flags;

  HANDLE space_available; /* Set if space is available.  */
  HANDLE data_available;  /* Set if data is available.  */ 

  /* For the monitor thread started by ASSIGN_RVID.  */
  HANDLE monitor_proc;
  int monitor_access;
  LONG monitor_rvid;
};
typedef struct pipeimpl_s *pipeimpl_t;


/* An object to describe a logging context.  We can't write directly
   to the log stream because we want to do line buffering and thus we
   need to store data until we see LF.  */
struct logimpl_s;
typedef struct logimpl_s *logimpl_t;
struct logimpl_s
{
  unsigned long logid; /* An identifier for a log source.  */
  unsigned long dsec;  /* Tenth of a second since system start.   */
  char *line;          /* Malloced line buffer.  */
  size_t linesize;     /* Allocated size of LINE.  */
  size_t linelen;      /* Used length of the line.  */
  int truncated;       /* Indicates a truncated log line.  */
};



/* An object to store information pertaining to an open-context.  */
struct opnctx_s;
typedef struct opnctx_s *opnctx_t;
struct opnctx_s
{
  int inuse;        /* True if this object has valid data.  */
  int is_log;       /* True if this describes a log device.  */
  LONG rvid;        /* The unique rendezvous identifier.  */
  DWORD access_code;/* Value from OpenFile.  */
  DWORD share_mode; /* Value from OpenFile.  */

  /* The state shared by all pipe users.  Only used if IS_LOG is false. */
  pipeimpl_t pipeimpl;

  /* The state used to implement a log stream.  Only used if IS_LOG is true. */
  logimpl_t logimpl;
};

/* A malloced table of open-context and the number of allocated slots.  */
static opnctx_t opnctx_table;
static size_t   opnctx_table_size;
/* The macros make sure that 0 is never a valid openctx_arg.  */
#define OPNCTX_TO_IDX(ctx_arg) (((ctx_arg) - opnctx_table) + 1)
#define OPNCTX_FROM_IDX(idx) (&opnctx_table[(idx) - 1])
#define OPNCTX_VALID_IDX_P(idx) ((idx) > 0 && (idx) <= opnctx_table_size)

typedef struct monitor_s *monitor_t;
struct monitor_s
{
  int inuse;        /* True if this object has valid data.  */
  pipeimpl_t pipeimpl;
};
static monitor_t monitor_table;
static size_t monitor_table_size;

/* A criticial section object used to protect the OPNCTX_TABLE and
   MONITOR_TABLE.  */
static CRITICAL_SECTION opnctx_table_cs;



/* A global object to control the logging.  */
struct {
  CRITICAL_SECTION lock; /* Lock for this structure.  */
  HANDLE filehd;         /* Handle of the log output file.  */
  int references;        /* Number of objects references this one.  */
} logcontrol;


/* We don't need a device context for the pipe thus we use the adress
   of the critical section object for it.  */
#define PIPECTX_VALUE ((DWORD)(&opnctx_table_cs))

/* The device context for the log device is the address of our
   control structure.  */
#define LOGCTX_VALUE ((DWORD)(&logcontrol))


/* True if we have enabled debugging.  */
static int enable_debug;

/* True if logging has been enabled.  */
static int enable_logging;



static void
log_debug (const char *fmt, ...)
{
  if (enable_debug)
    {
      va_list arg_ptr;
      FILE *fp;
      
      fp = fopen (DBGFILENAME, "a+");
      if (!fp)
        return;
      va_start (arg_ptr, fmt);
      vfprintf (fp, fmt, arg_ptr);
      va_end (arg_ptr);
      fclose (fp);
    }
}


/* Return a new rendezvous id.  We will never return an RVID of 0. */
static LONG
create_rendezvous_id (void)
{
  static LONG rendezvous_id;
  LONG rvid;

  while (!(rvid = InterlockedIncrement (&rendezvous_id)))
    ;
  return rvid;
}

/* Return a new log id.  These log ids are used to identify log lines
   send to the same device; ie. for each CreateFile("GPG2:") a new log
   id is assigned.  We will ever return a log id of 0. */
static LONG
create_log_id (void)
{
  static LONG log_id;
  LONG lid;

  while (!(lid = InterlockedIncrement (&log_id)))
    ;
  return lid;
}



pipeimpl_t
pipeimpl_new (void)
{
  pipeimpl_t pimpl;

  pimpl = malloc (sizeof (*pimpl));
  if (!pimpl)
    return NULL;

  InitializeCriticalSection (&pimpl->critsect);
  pimpl->refcnt = 1;
  pimpl->buffer_size = 512;
  pimpl->buffer = malloc (pimpl->buffer_size);
  if (!pimpl->buffer)
    {
      DeleteCriticalSection (&pimpl->critsect);
      free (pimpl);
      return NULL;
    }
  pimpl->buffer_len = 0;
  pimpl->buffer_pos = 0;
  pimpl->flags = 0;
  pimpl->space_available = CreateEvent (NULL, FALSE, FALSE, NULL);
  if (!pimpl->space_available)
    {
      free (pimpl->buffer);
      DeleteCriticalSection (&pimpl->critsect);
      free (pimpl);
      return NULL;
    }
  pimpl->data_available = CreateEvent (NULL, FALSE, FALSE, NULL);
  if (!pimpl->data_available)
    {
      CloseHandle (pimpl->space_available);
      free (pimpl->buffer);
      DeleteCriticalSection (&pimpl->critsect);
      free (pimpl);
      return NULL;
    }
  pimpl->monitor_proc = INVALID_HANDLE_VALUE;
  pimpl->monitor_access = 0;
  pimpl->monitor_rvid = 0;
  return pimpl;
}


/* PIMPL must be locked.  It is unlocked at exit.  */
void
pipeimpl_unref (pipeimpl_t pimpl)
{
  int release = 0;

  if (!pimpl)
    return;

  log_debug ("pipeimpl_unref (%p): dereference\n", pimpl);

  if (--pimpl->refcnt == 0)
    release = 1;
  LeaveCriticalSection (&pimpl->critsect);

  if (! release)
    return;

  log_debug ("pipeimpl_unref (%p): release\n", pimpl);

  DeleteCriticalSection (&pimpl->critsect);
  if (pimpl->buffer)
    {
      free (pimpl->buffer);
      pimpl->buffer = NULL;
      pimpl->buffer_size = 0;
    }
  if (pimpl->space_available != INVALID_HANDLE_VALUE)
    {
      CloseHandle (pimpl->space_available);
      pimpl->space_available = INVALID_HANDLE_VALUE;
    }
  if (pimpl->data_available != INVALID_HANDLE_VALUE)
    {
      CloseHandle (pimpl->data_available);
      pimpl->data_available = INVALID_HANDLE_VALUE;
    }
}



/* Allocate a new log structure.  */
logimpl_t
logimpl_new (void)
{
  logimpl_t limpl;

  limpl = calloc (1, sizeof *limpl);
  if (!limpl)
    return NULL;
  limpl->logid = create_log_id ();
  limpl->linesize = 256;
  limpl->line = malloc (limpl->linesize);
  if (!limpl->line)
    {
      free (limpl);
      return NULL;
    }

  return limpl;
}


/* There is no need to lock LIMPL, thus is a dummy function.  */
void
logimpl_unref (logimpl_t limpl)
{
  (void)limpl;
}


/* Flush a pending log line.  */
static void
logimpl_flush (logimpl_t limpl)
{
  if (!limpl->linelen || !enable_logging)
    return;

  EnterCriticalSection (&logcontrol.lock);
  if (logcontrol.filehd == INVALID_HANDLE_VALUE)
    logcontrol.filehd = CreateFile (LOGFILENAME, GENERIC_WRITE,
                                     FILE_SHARE_READ,
                                     NULL, OPEN_ALWAYS,
                                     FILE_ATTRIBUTE_NORMAL, NULL);
  if (!logcontrol.filehd)
    log_debug ("can't open log file: rc=%d\n", (int)GetLastError ());
  else
    {
      char buf[50];
      DWORD nwritten;

      snprintf (buf, sizeof buf, 
                "%06lu/%lu//", limpl->dsec % 1000000, limpl->logid);
      if (!WriteFile (logcontrol.filehd, buf, strlen (buf), &nwritten, NULL))
        log_debug ("error writing log file: rc=%d\n", (int)GetLastError ());
      else if (!WriteFile (logcontrol.filehd,
                           limpl->line, limpl->linelen, &nwritten, NULL))
        log_debug ("error writing log file: rc=%d\n", (int)GetLastError ());

      snprintf (buf, sizeof buf, "%s\n", limpl->truncated? "[...]":"");
      if (!WriteFile (logcontrol.filehd, buf, strlen (buf), &nwritten, NULL))
        log_debug ("error writing log file: rc=%d\n", (int)GetLastError ());
    }

  LeaveCriticalSection (&logcontrol.lock);
  limpl->linelen = 0;
  limpl->truncated = 0;
}


/* Return a new opnctx handle and mark it as used.  Returns NULL and
   sets LastError on memory failure etc.  opnctx_table_cs must be
   locked on entry and is locked on exit.  Note that the returned
   pointer is only valid as long as opnctx_table_cs stays locked, as
   it is not stable under table reallocation.  */
static opnctx_t
allocate_opnctx (int is_log)
{
  opnctx_t opnctx = NULL;
  int idx;

  for (idx = 0; idx < opnctx_table_size; idx++)
    if (! opnctx_table[idx].inuse)
      break;
  if (idx == opnctx_table_size)
    {
      /* We need to increase the size of the table.  The approach we
         take is straightforward to minimize the risk of bugs.  */
      opnctx_t newtbl;
      size_t newsize = opnctx_table_size + 64;

      newtbl = calloc (newsize, sizeof *newtbl);
      if (!newtbl)
        goto leave;
      memcpy (newtbl, opnctx_table, opnctx_table_size * sizeof (*newtbl));
      free (opnctx_table);
      opnctx_table = newtbl;
      idx = opnctx_table_size;
      opnctx_table_size = newsize;
    }
  opnctx = &opnctx_table[idx];
  opnctx->inuse = 1;
  opnctx->is_log = is_log;
  opnctx->rvid = 0;
  opnctx->access_code = 0;
  opnctx->share_mode = 0;
  opnctx->pipeimpl = 0;
  opnctx->logimpl = 0;

 leave:
  return opnctx;
}


/* Verify context CTX, returns NULL if not valid and the pointer to
   the context if valid.  opnctx_table_cs must be locked on entry and
   is locked on exit.  Note that the returned pointer is only valid as
   long as opnctx_table_cs remains locked.  */
opnctx_t
verify_opnctx (DWORD ctx_arg)
{
  opnctx_t ctx;

  if (! OPNCTX_VALID_IDX_P (ctx_arg))
    {
      SetLastError (ERROR_INVALID_HANDLE);
      return NULL;
    }
  ctx = OPNCTX_FROM_IDX (ctx_arg);

  if (! ctx->inuse)
    {
      SetLastError (ERROR_INVALID_HANDLE);
      return NULL;
    }
  return ctx;
}


/* Return a new monitor handle and mark it as used.  Returns NULL and
   sets LastError on memory failure etc.  opnctx_table_cs must be
   locked on entry and is locked on exit.  Note that the returned
   pointer is only valid as long as opnctx_table_cs stays locked, as
   it is not stable under table reallocation.  */
static monitor_t
allocate_monitor (void)
{
  monitor_t monitor = NULL;
  int idx;

  for (idx = 0; idx < monitor_table_size; idx++)
    if (! monitor_table[idx].inuse)
      break;
  if (idx == monitor_table_size)
    {
      /* We need to increase the size of the table.  The approach we
         take is straightforward to minimize the risk of bugs.  */
      monitor_t newtbl;
      size_t newsize = monitor_table_size + 16;

      newtbl = calloc (newsize, sizeof *newtbl);
      if (!newtbl)
        goto leave;
      memcpy (newtbl, monitor_table, monitor_table_size * sizeof (*newtbl));
      free (monitor_table);
      monitor_table = newtbl;
      idx = monitor_table_size;
      monitor_table_size = newsize;
    }
  monitor = &monitor_table[idx];
  monitor->inuse = 1;
  monitor->pipeimpl = 0;

 leave:
  return monitor;
}


static pipeimpl_t
assert_pipeimpl (opnctx_t ctx)
{
  DWORD ctx_arg = OPNCTX_TO_IDX (ctx);

  if (ctx->is_log)
    {
      log_debug ("  assert_pipeimpl (ctx=%i): "
                 "error: not valid for a log device\n", ctx_arg);
      return NULL;
    }
  if (! ctx->pipeimpl)
    {
      ctx->pipeimpl = pipeimpl_new ();
      if (! ctx->pipeimpl)
	{
	  log_debug ("  assert_pipeimpl (ctx=%i): error: can't create pipe\n",
		     ctx_arg);
	  return NULL;
	}
      log_debug ("  assert_pipeimpl (ctx=%i): created pipe 0x%p\n",
		 ctx_arg, ctx->pipeimpl);
    }
  return ctx->pipeimpl;
}


static logimpl_t
assert_logimpl (opnctx_t ctx)
{
  DWORD ctx_arg = OPNCTX_TO_IDX (ctx);

  if (!ctx->is_log)
    {
      log_debug ("  assert_logimpl (ctx=%i): "
                 "error: not valid for a pipe device\n", ctx_arg);
      return NULL;
    }
  if (!ctx->logimpl)
    {
      ctx->logimpl = logimpl_new ();
      if (!ctx->logimpl)
	{
	  log_debug ("  assert_logimpl (ctx=%i): error: can't create log\n",
		     ctx_arg);
	  return NULL;
	}
      log_debug ("  assert_logimpl (ctx=%i): created log 0x%p\n",
		 ctx_arg, ctx->logimpl);
    }
  return ctx->logimpl;
}


/* Verify access CODE for context CTX_ARG, returning a reference to
   the locked pipe or the locked log implementation.  opnctx_table_cs
   must be unlocked on entry and is unlocked on exit.  */
int
access_opnctx (DWORD ctx_arg, DWORD code, pipeimpl_t *r_pipe, logimpl_t *r_log)
{
  opnctx_t ctx;

  *r_pipe = NULL;
  *r_log  = NULL;

  EnterCriticalSection (&opnctx_table_cs);
  ctx = verify_opnctx (ctx_arg);
  if (! ctx)
    {
      /* Error is set by verify_opnctx.  */
      LeaveCriticalSection (&opnctx_table_cs);
      return -1;
    }

  if (! (ctx->access_code & code))
    {
      SetLastError (ERROR_INVALID_ACCESS);
      LeaveCriticalSection (&opnctx_table_cs);
      return -1;
    }

  if (ctx->is_log)
    {
      logimpl_t limpl;

      limpl = assert_logimpl (ctx);
      if (!limpl)
        {
          LeaveCriticalSection (&opnctx_table_cs);
          return -1;
        }
      *r_log = limpl;
    }
  else
    {
      pipeimpl_t pimpl;

      pimpl = assert_pipeimpl (ctx);
      if (! pimpl)
        {
          LeaveCriticalSection (&opnctx_table_cs);
          return -1;
        }
      EnterCriticalSection (&pimpl->critsect);
      pimpl->refcnt++;
      *r_pipe = pimpl;
    }

  LeaveCriticalSection (&opnctx_table_cs);
  return 0;
}



static char *
wchar_to_utf8 (const wchar_t *string)
{
  int n;
  size_t length = wcslen (string);
  char *result;

  n = WideCharToMultiByte (CP_UTF8, 0, string, length, NULL, 0, NULL, NULL);
  if (n < 0 || (n+1) <= 0)
    abort ();

  result = malloc (n+1);
  if (!result)
    abort ();
  n = WideCharToMultiByte (CP_ACP, 0, string, length, result, n, NULL, NULL);
  if (n < 0)
    abort ();
  
  result[n] = 0;
  return result;
}


/* Initialize the device and return a device specific context.  */
DWORD
GPG_Init (LPCTSTR active_key, DWORD bus_context)
{
  static int firsttimedone;
  HKEY handle;
  wchar_t buffer[25];
  DWORD buflen;
  DWORD result;

  (void)bus_context;

  EnterCriticalSection (&logcontrol.lock);
  if (!firsttimedone)
    {
      firsttimedone++;
      if (!RegOpenKeyEx (HKEY_LOCAL_MACHINE, GPGCEDEV_KEY_NAME,
                         0, KEY_READ, &handle))
        {
          buflen = sizeof buffer;
          if (!RegQueryValueEx (handle, L"debugDriver", 0, NULL,
                                (PBYTE)buffer, &buflen)
              && wcstol (buffer, NULL, 10) > 0)
            enable_debug = 1;
          RegCloseKey (handle);
        }
      if (!RegOpenKeyEx (HKEY_LOCAL_MACHINE, GPGCEDEV_KEY_NAME2,
                         0, KEY_READ, &handle))
        {
          buflen = sizeof buffer;
          if (!RegQueryValueEx (handle, L"enableLog", 0, NULL,
                                (PBYTE)buffer, &buflen)
              && wcstol (buffer, NULL, 10) > 0)
            enable_logging = 1;
          RegCloseKey (handle);
        }
    }
  LeaveCriticalSection (&logcontrol.lock);

  if (enable_debug)
    {
      char *tmpbuf;
      tmpbuf = wchar_to_utf8 (active_key);
      log_debug ("GPG_Init (%s)\n", tmpbuf);
      free (tmpbuf);
    }

  if (RegOpenKeyEx (HKEY_LOCAL_MACHINE, active_key, 0, KEY_READ, &handle))
    {
      log_debug ("GPG_Init: error reading registry: rc=%d\n", 
                 (int)GetLastError ());
      return 0;
    }

  buflen = sizeof buffer;
  if (RegQueryValueEx (handle, L"Name", 0, NULL, (PBYTE)buffer, &buflen))
    {
      log_debug ("GPG_Init: error reading registry value 'Name': rc=%d\n", 
                 (int)GetLastError ());
      result = 0;
    }
  else if (!wcscmp (buffer, L"GPG1:"))
    {
      /* This is the pipe device: We don't need any global data.
         However, we need to return something.  */
      log_debug ("GPG_Init: created pipe device (devctx=%p)\n", PIPECTX_VALUE);
      result = PIPECTX_VALUE;
    }
  else if (!wcscmp (buffer, L"GPG2:"))
    {
      /* This is the log device.  Clear the object and return something.  */
      logcontrol.filehd = INVALID_HANDLE_VALUE;
      log_debug ("GPG_Init: created log device (devctx=%p)\n", 0);
      result = LOGCTX_VALUE;
    }
  else
    {
      if (enable_debug)
        {
          char *tmpbuf;
          tmpbuf = wchar_to_utf8 (buffer);
          log_debug ("GPG_Init: device '%s' is not supported\n", tmpbuf);
          free (tmpbuf);
        }
      SetLastError (ERROR_DEV_NOT_EXIST);
      result = 0;
    }

  RegCloseKey (handle);
  return result;
}



/* Deinitialize this device driver.  */
BOOL
GPG_Deinit (DWORD devctx)
{
  log_debug ("GPG_Deinit (devctx=0x%p)\n", (void*)devctx);
  if (devctx == PIPECTX_VALUE)
    {
      /* No need to release resources.  */
    }
  else if (devctx == LOGCTX_VALUE)
    {
      EnterCriticalSection (&logcontrol.lock);
      if (logcontrol.filehd != INVALID_HANDLE_VALUE)
        {
          CloseHandle (logcontrol.filehd);
          logcontrol.filehd = INVALID_HANDLE_VALUE;
        }
      LeaveCriticalSection (&logcontrol.lock);
    }
  else
    {
      SetLastError (ERROR_INVALID_PARAMETER);
      return FALSE; /* Error.  */
    }
  
  return TRUE; /* Success.  */
}



/* Create a new open context.  This function is called due to a
   CreateFile from the application.  */
DWORD
GPG_Open (DWORD devctx, DWORD access_code, DWORD share_mode)
{
  opnctx_t opnctx;
  DWORD ctx_arg = 0;
  int is_log;

  log_debug ("GPG_Open (devctx=%p)\n", (void*)devctx);
  if (devctx == PIPECTX_VALUE)
    is_log = 0;
  else if (devctx == LOGCTX_VALUE)
    is_log = 1;
  else
    {
      log_debug ("GPG_Open (devctx=%p): error: wrong devctx (expected 0x%p)\n",
		 (void*) devctx);
      SetLastError (ERROR_INVALID_PARAMETER);
      return 0; /* Error.  */
    }

  EnterCriticalSection (&opnctx_table_cs);
  opnctx = allocate_opnctx (is_log);
  if (!opnctx)
    {
      log_debug ("GPG_Open (devctx=%p): error: could not allocate context\n",
		 (void*) devctx);
      goto leave;
    }

  opnctx->access_code = access_code;
  opnctx->share_mode = share_mode;

  ctx_arg = OPNCTX_TO_IDX (opnctx);

  log_debug ("GPG_Open (devctx=%p, is_log=%d): success: created context %i\n",
	     (void*) devctx, is_log, ctx_arg);
  if (is_log)
    {
      EnterCriticalSection (&logcontrol.lock);
      logcontrol.references++;
      LeaveCriticalSection (&logcontrol.lock);
    }

 leave:
  LeaveCriticalSection (&opnctx_table_cs);
  return ctx_arg;
}



BOOL
GPG_Close (DWORD opnctx_arg)
{
  opnctx_t opnctx;
  BOOL result = FALSE;

  log_debug ("GPG_Close (ctx=%i)\n", opnctx_arg);

  EnterCriticalSection (&opnctx_table_cs);
  opnctx = verify_opnctx (opnctx_arg);
  if (!opnctx)
    {
      log_debug ("GPG_Close (ctx=%i): could not find context\n", opnctx_arg);
      goto leave;
    }

  if (opnctx->pipeimpl)
    {
      pipeimpl_t pimpl = opnctx->pipeimpl;
      EnterCriticalSection (&pimpl->critsect);
      /* This needs to be adjusted if there can be multiple
	 reader/writers.  */
      if (opnctx->access_code & GENERIC_READ)
	{
	  pimpl->flags |= PIPE_FLAG_NO_READER;
	  SetEvent (pimpl->space_available);
	}
      else if (opnctx->access_code & GENERIC_WRITE)
	{
	  pimpl->flags |= PIPE_FLAG_NO_WRITER;
	  SetEvent (pimpl->data_available);
	}
      pipeimpl_unref (pimpl);
      opnctx->pipeimpl = 0;
    }
  if (opnctx->logimpl)
    {
      logimpl_t limpl = opnctx->logimpl;

      logimpl_flush (limpl);
      logimpl_unref (limpl);
      free (limpl->line);
      free (limpl);
      opnctx->logimpl = 0;
      EnterCriticalSection (&logcontrol.lock);
      logcontrol.references--;
      if (!logcontrol.references && logcontrol.filehd)
        {
          CloseHandle (logcontrol.filehd);
          logcontrol.filehd = INVALID_HANDLE_VALUE;
        }
      LeaveCriticalSection (&logcontrol.lock);
    }
  opnctx->access_code = 0;
  opnctx->share_mode = 0;
  opnctx->rvid = 0;
  opnctx->inuse = 0;
  result = TRUE;
  log_debug ("GPG_Close (ctx=%i): success\n", opnctx_arg);

 leave:
  LeaveCriticalSection (&opnctx_table_cs);
  return result;
}



DWORD
GPG_Read (DWORD opnctx_arg, void *buffer, DWORD count)
{
  pipeimpl_t pimpl;
  logimpl_t  limpl;
  const char *src;
  char *dst;
  int result = -1;

  log_debug ("GPG_Read (ctx=%i, buffer=0x%p, count=%d)\n",
	     opnctx_arg, buffer, count);

  if (access_opnctx (opnctx_arg, GENERIC_READ, &pimpl, &limpl))
    {
      log_debug ("GPG_Read (ctx=%i): error: could not access context\n",
		 opnctx_arg);
      return -1;
    }

  if (limpl)
    {
      /* Reading from a log stream does not make sense.  Return EOF.  */
      result = 0;
      goto leave;
    }

 retry:
  if (pimpl->buffer_pos == pimpl->buffer_len)
    {
      HANDLE data_available = pimpl->data_available;

      /* Check for end of file.  */
      if (pimpl->flags & PIPE_FLAG_NO_WRITER)
	{
	  log_debug ("GPG_Read (ctx=%i): success: EOF\n", opnctx_arg);
	  result = 0;
	  goto leave;
	}

      /* Check for request to unblock once.  */
      if (pimpl->flags & PIPE_FLAG_UNBLOCK_READER)
	{
	  log_debug ("GPG_Read (ctx=%i): success: EBUSY (due to unblock)\n",
		     opnctx_arg);
	  pimpl->flags &= ~PIPE_FLAG_UNBLOCK_READER;
	  SetLastError (ERROR_BUSY);
	  result = -1;
	  goto leave;
	}

      LeaveCriticalSection (&pimpl->critsect);
      log_debug ("GPG_Read (ctx=%i): waiting: data_available\n", opnctx_arg);
      WaitForSingleObject (data_available, INFINITE);
      log_debug ("GPG_Read (ctx=%i): resuming: data_available\n", opnctx_arg);
      EnterCriticalSection (&pimpl->critsect);
      goto retry;
    }
  
  dst = buffer;
  src = pimpl->buffer + pimpl->buffer_pos;
  while (count > 0 && pimpl->buffer_pos < pimpl->buffer_len)
    {
      *dst++ = *src++;
      count--;
      pimpl->buffer_pos++;
    }
  result = (dst - (char*)buffer);
  if (pimpl->buffer_pos == pimpl->buffer_len)
    pimpl->buffer_pos = pimpl->buffer_len = 0;
  
  /* Now there should be some space available.  Signal the write end.
     Even if COUNT was passed as NULL and no space is available,
     signaling must be done.  */
  if (!SetEvent (pimpl->space_available))
    log_debug ("GPG_Read (ctx=%i): warning: SetEvent(space_available) "
	       "failed: rc=%d\n", opnctx_arg, (int)GetLastError ());

  log_debug ("GPG_Read (ctx=%i): success: result=%d\n", opnctx_arg, result);

 leave:
  pipeimpl_unref (pimpl);
  logimpl_unref (limpl);
  return result;
}



DWORD
GPG_Write (DWORD opnctx_arg, const void *buffer, DWORD count)
{
  pipeimpl_t pimpl;
  logimpl_t  limpl;
  int result = -1;
  const char *src;
  char *dst;
  size_t nwritten = 0;

  log_debug ("GPG_Write (ctx=%i, buffer=0x%p, count=%d)\n", opnctx_arg,
	     buffer, count);

  if (access_opnctx (opnctx_arg, GENERIC_WRITE, &pimpl, &limpl))
    {
      log_debug ("GPG_Write (ctx=%i): error: could not access context\n",
		 opnctx_arg);
      return -1;
    }

  if (!count)
    {
      log_debug ("GPG_Write (ctx=%i): success\n", opnctx_arg);
      result = 0;
      goto leave;
    }

 retry:
  if (limpl)
    {
      /* Store it in our buffer up to a LF and print complete lines.  */
      result = count;
      if (!limpl->linelen)
        limpl->dsec = GetTickCount () / 100;
      dst = limpl->line + limpl->linelen;
      for (src = buffer; count; count--, src++)
        {
          if (*src == '\n')
            {
              logimpl_flush (limpl);
              dst = limpl->line + limpl->linelen;
            }
          else if (limpl->linelen >= limpl->linesize)
            limpl->truncated = 1;
          else
            {
              *dst++ = *src;
              limpl->linelen++;
            }
        }
    }
  else /* pimpl */
    {
      /* Check for broken pipe.  */
      if (pimpl->flags & PIPE_FLAG_NO_READER)
        {
          log_debug ("GPG_Write (ctx=%i): error: broken pipe\n", opnctx_arg);
          SetLastError (ERROR_BROKEN_PIPE);
          goto leave;
        }
      
      /* Check for request to unblock once.  */
      if (pimpl->flags & PIPE_FLAG_UNBLOCK_WRITER)
        {
          log_debug ("GPG_Write (ctx=%i): success: EBUSY (due to unblock)\n",
                     opnctx_arg);
          pimpl->flags &= ~PIPE_FLAG_UNBLOCK_WRITER;
          SetLastError (ERROR_BUSY);
          result = -1;
          goto leave;
        }
      
      /* Write to our buffer.  */
      if (pimpl->buffer_len == pimpl->buffer_size)
        {
          /* Buffer is full.  */
          HANDLE space_available = pimpl->space_available;
          LeaveCriticalSection (&pimpl->critsect);
          log_debug ("GPG_Write (ctx=%i): waiting: space_available\n",
                     opnctx_arg);
          WaitForSingleObject (space_available, INFINITE);
          log_debug ("GPG_Write (ctx=%i): resuming: space_available\n",
                     opnctx_arg);
          EnterCriticalSection (&pimpl->critsect);
          goto retry;
        }
      
      src = buffer;
      dst = pimpl->buffer + pimpl->buffer_len;
      while (count > 0 && pimpl->buffer_len < pimpl->buffer_size)
        {
          *dst++ = *src++;
          count--;
          pimpl->buffer_len++;
          nwritten++;
        }
      result = nwritten;
      
      if (!SetEvent (pimpl->data_available))
        log_debug ("GPG_Write (ctx=%i): warning: SetEvent(data_available) "
                   "failed: rc=%d\n", opnctx_arg, (int)GetLastError ());
    }
  
  log_debug ("GPG_Write (ctx=%i): success: result=%d\n", opnctx_arg, result);

 leave:
  pipeimpl_unref (pimpl);
  logimpl_unref (limpl);
  return result;
}



DWORD
GPG_Seek (DWORD opnctx_arg, long amount, WORD type)
{
  SetLastError (ERROR_SEEK_ON_DEVICE);
  return -1; /* Error.  */
}



/* opnctx_table_s is locked on entering and on exit.  */
static BOOL
make_pipe (opnctx_t ctx, LONG rvid)
{
  DWORD ctx_arg = OPNCTX_TO_IDX (ctx);
  opnctx_t peerctx = NULL;
  int idx;
  pipeimpl_t pimpl;

  log_debug ("  make_pipe (ctx=%i, rvid=%08lx)\n", ctx_arg, rvid);

  if (ctx->pipeimpl)
    {
      log_debug ("  make_pipe (ctx=%i): error: already assigned\n", ctx_arg);
      SetLastError (ERROR_ALREADY_ASSIGNED);
      return FALSE;
    }

  /* GnuPG and other programs don't use the safe ASSIGN_RVID call
     because they guarantee that the context exists during the whole
     time the child process runs.  GPGME is more asynchronous and
     relies on ASSIGN_RVID monitors.  So, first check for open
     contexts, then check for monitors.  */

  for (idx = 0; idx < opnctx_table_size; idx++)
    if (opnctx_table[idx].inuse && opnctx_table[idx].rvid == rvid)
      {
        peerctx = &opnctx_table[idx];
        break;
      }
  if (peerctx)
    {
      /* This is the GnuPG etc case, where the context is still open.
	 It may also cover the GPGME case if GPGME is still using its
	 own end of the pipe at the time of this call.  */
      if (peerctx == ctx)
	{
	  log_debug ("  make_pipe (ctx=%i): error: target and source identical\n",
		     ctx_arg);
	  SetLastError (ERROR_INVALID_TARGET_HANDLE);
	  return FALSE;
	}

      if ((ctx->access_code & GENERIC_READ))
	{
	  /* Check that the peer is a write end.  */
	  if (!(peerctx->access_code & GENERIC_WRITE))
	    {
	      log_debug ("  make_pipe (ctx=%i): error: peer is not writer\n",
			 ctx_arg);
	      SetLastError (ERROR_INVALID_ACCESS);
	      return FALSE;
	    }
	}
      else if ((ctx->access_code & GENERIC_WRITE))
	{
	  /* Check that the peer is a read end.  */
	  if (!(peerctx->access_code & GENERIC_READ))
	    {
	      log_debug ("  make_pipe (ctx=%i): error: peer is not reader\n",
			 ctx_arg);
	      SetLastError (ERROR_INVALID_ACCESS);
	      return FALSE;
	    }
	}
      else
	{
	  log_debug ("  make_pipe (ctx=%i): error: invalid access\n", ctx_arg);
	  SetLastError (ERROR_INVALID_ACCESS);
	  return FALSE;
	}
      
      /* Make sure the peer has a pipe implementation to be shared.  If
	 not yet, create one.  */
      pimpl = assert_pipeimpl (peerctx);
      if (! pimpl)
	return FALSE;
    }
  else
    {
      /* This is the case where ASSIGN_RVID was called to create a
	 monitor, and the pipe is already closed at the parent side.
	 For example GPGME verify detached plain text, where GPG calls
	 MAKE_PIPE very late.  */
      
      for (idx = 0; idx < monitor_table_size; idx++)
	if (monitor_table[idx].inuse
	    && monitor_table[idx].pipeimpl->monitor_rvid == rvid)
	  {
	    pimpl = monitor_table[idx].pipeimpl;
	    break;
	  }
      if (idx == monitor_table_size)
	{
	  log_debug ("  make_pipe (ctx=%i): error: not found\n", ctx_arg);
	  SetLastError (ERROR_NOT_FOUND);
	  return FALSE;
	}

      if (ctx->access_code & GENERIC_READ)
	{
	  /* Check that the peer is a write end.  */
	  if (!(pimpl->monitor_access & GENERIC_READ))
	    {
	      log_debug ("  make_pipe (ctx=%i): error: monitor is not reader\n",
			 ctx_arg);
	      SetLastError (ERROR_INVALID_ACCESS);
	      return FALSE;
	    }
	}
      else if ((ctx->access_code & GENERIC_WRITE))
	{
	  /* Check that the peer is a read end.  */
	  if (!(pimpl->monitor_access & GENERIC_WRITE))
	    {
	      log_debug ("  make_pipe (ctx=%i): error: monitor is not writer\n",
			 ctx_arg);
	      SetLastError (ERROR_INVALID_ACCESS);
	      return FALSE;
	    }
	}
      else
	{
	  log_debug ("  make_pipe (ctx=%i): error: invalid access\n", ctx_arg);
	  SetLastError (ERROR_INVALID_ACCESS);
	  return FALSE;
	}
    }

  EnterCriticalSection (&pimpl->critsect);
  pimpl->refcnt++;
  if (pimpl->monitor_proc != INVALID_HANDLE_VALUE)
    {
      /* If there is a monitor to the pipe, then it's now about time
	 to ask it to go away.  */
      log_debug ("  make_pipe (ctx=%i): waking up monitor for pipe 0x%p\n",
		 ctx_arg, pimpl);
      pimpl->flags |= PIPE_FLAG_HALT_MONITOR;
      if (pimpl->monitor_access & GENERIC_READ)
	SetEvent (pimpl->data_available);
      else
	SetEvent (pimpl->space_available);
    }
  LeaveCriticalSection (&pimpl->critsect);

  ctx->pipeimpl = pimpl;

  if (peerctx)
    {
      log_debug ("  make_pipe (ctx=%i): success: combined with peer ctx=%i "
		 "(pipe 0x%p)\n", ctx_arg, OPNCTX_TO_IDX (peerctx), pimpl);
    }
  else
    {
      log_debug ("  make_pipe (ctx=%i): success: combined with "
		 "pipe 0x%p\n", ctx_arg, OPNCTX_TO_IDX (peerctx), pimpl);
    }

  return TRUE;
}


/* opnctx_table_s is locked on entering and on exit.  */
static BOOL
unblock_call (opnctx_t ctx)
{
  /* If there is no pipe object, no thread can be blocked.  */
  if (!ctx->pipeimpl)
    return TRUE;

  EnterCriticalSection (&ctx->pipeimpl->critsect);
  if (ctx->access_code & GENERIC_READ)
    {
      ctx->pipeimpl->flags |= PIPE_FLAG_UNBLOCK_READER;
      SetEvent (ctx->pipeimpl->data_available);
    }
  else if (ctx->access_code & GENERIC_WRITE)
    {
      ctx->pipeimpl->flags |= PIPE_FLAG_UNBLOCK_WRITER;
      SetEvent (ctx->pipeimpl->space_available);
    }
  LeaveCriticalSection (&ctx->pipeimpl->critsect);

  return TRUE;
}


static DWORD CALLBACK 
monitor_main (void *arg)
{
  pipeimpl_t pimpl = (pipeimpl_t) arg;
  HANDLE handles[2];
  int idx;

  log_debug ("starting monitor (pimpl=0x%p)\n", pimpl);
  
  EnterCriticalSection (&pimpl->critsect);
  /* Putting proc first in the array is convenient, as this is a hard
     break-out condition (and thus takes precedence in WFMO).  The
     reader/writer event is a soft condition, which also requires a
     flag to be set.  */
  handles[0] = pimpl->monitor_proc;
  if (pimpl->monitor_access & GENERIC_READ)
    handles[1] = pimpl->data_available;
  else
    handles[1] = pimpl->space_available;

 retry:
  /* First check if the peer has not gone away.  If it has, we are done.  */
  if (pimpl->flags & PIPE_FLAG_HALT_MONITOR)
    {
      log_debug ("monitor (pimpl=0x%p): done: monitored process taking over\n",
		 pimpl);
    }    
  else
    {
      DWORD res;

      LeaveCriticalSection (&pimpl->critsect);
      log_debug ("monitor (pimpl=0x%p): waiting\n", pimpl);
      res = WaitForMultipleObjects (2, handles, FALSE, INFINITE);
      log_debug ("monitor (pimpl=0x%p): resuming\n", pimpl);
      EnterCriticalSection (&pimpl->critsect);

      if (res == WAIT_FAILED)
	{
	  log_debug ("monitor (pimpl=0x%p): WFMO failed: %i\n",
		     pimpl, GetLastError ());
	}
      else if (res == WAIT_OBJECT_0)
	{
	  log_debug ("monitor (pimpl=0x%p): done: monitored process died\n",
		     pimpl);
	}	  
      else if (res == WAIT_OBJECT_0 + 1)
	goto retry;
      else
	{
	  log_debug ("monitor (pimpl=0x%p): unexpected result of WFMO: %i\n",
		     pimpl, res);
	}
    }

  log_debug ("ending monitor (pimpl=0x%p)\n", pimpl);

  /* Remove the monitor from the monitor table.  */
  LeaveCriticalSection (&pimpl->critsect);
  EnterCriticalSection (&opnctx_table_cs);
  for (idx = 0; idx < monitor_table_size; idx++)
    if (monitor_table[idx].inuse
	&& monitor_table[idx].pipeimpl == pimpl)
      {
	monitor_table[idx].pipeimpl = NULL;
	monitor_table[idx].inuse = 0;
	break;
      }
  if (idx == monitor_table_size)
    log_debug ("can not find monitor in table (pimpl=0x%p)\n", pimpl);
  LeaveCriticalSection (&opnctx_table_cs);
  EnterCriticalSection (&pimpl->critsect);

  /* Now do the rest of the cleanup.  */
  CloseHandle (pimpl->monitor_proc);
  pimpl->monitor_proc = INVALID_HANDLE_VALUE;
  pimpl->monitor_access = 0;
  pimpl->monitor_rvid = 0;
  pimpl->flags &= ~PIPE_FLAG_HALT_MONITOR;
  pipeimpl_unref (pimpl);

  return 0;
}


/* opnctx_table_s is locked on entering and on exit.  */
static BOOL
assign_rvid (opnctx_t ctx, DWORD rvid, DWORD pid)
{
  DWORD ctx_arg = OPNCTX_TO_IDX (ctx);
  int idx;
  opnctx_t peerctx;
  HANDLE monitor_hnd;
  HANDLE proc;
  pipeimpl_t pimpl;
  monitor_t monitor;

  log_debug ("  assign_rvid (ctx=%i, rvid=%08lx, pid=%08lx)\n",
	     ctx_arg, rvid, pid);

  for (idx = 0; idx < opnctx_table_size; idx++)
    if (opnctx_table[idx].inuse && opnctx_table[idx].rvid == rvid)
      {
        peerctx = &opnctx_table[idx];
        break;
      }
  if (! peerctx)
    {
      log_debug ("  assign_rvid (ctx=%i): error: not found\n", ctx_arg);
      SetLastError (ERROR_NOT_FOUND);
      return FALSE;
    }
  
  if (peerctx->pipeimpl
      && peerctx->pipeimpl->monitor_proc != INVALID_HANDLE_VALUE)
    {
      log_debug ("  assign_rvid (ctx=%i): error: rvid already assigned\n",
		 ctx_arg);
      SetLastError (ERROR_ALREADY_ASSIGNED);
      return FALSE;
    }

  proc = OpenProcess (0, FALSE, pid);
  if (proc == NULL)
    {
      log_debug ("  assign_rvid (ctx=%i): error: process not found\n",
		 ctx_arg);
      return FALSE;
    }

  /* Acquire a reference to the pipe.  We don't want accesss to be
     checked.  */
  pimpl = assert_pipeimpl (peerctx);
  if (! pimpl)
    {
      CloseHandle (proc);
      return FALSE;
    }

  monitor = allocate_monitor ();
  if (!monitor)
    {
      log_debug ("  assign_rvid (ctx=%i): error: could not allocate monitor\n",
		 ctx_arg);
      CloseHandle (proc);
      return FALSE;
    }
  monitor->pipeimpl = pimpl;

  EnterCriticalSection (&pimpl->critsect);
  pimpl->refcnt++;

  /* The monitor shadows the peer, so it takes its access.  Our access
     is the opposite of that of the peer.  */
  pimpl->monitor_proc = proc;
  if (peerctx->access_code == GENERIC_READ)
    pimpl->monitor_access = GENERIC_WRITE;
  else
    pimpl->monitor_access = GENERIC_READ;
  pimpl->monitor_rvid = rvid;

  monitor_hnd = CreateThread (NULL, 0, monitor_main, pimpl, 0, NULL);
  if (monitor_hnd == INVALID_HANDLE_VALUE)
    {
      pimpl->monitor_access = 0;
      pimpl->monitor_proc = INVALID_HANDLE_VALUE;
      LeaveCriticalSection (&pimpl->critsect);

      monitor->pipeimpl = NULL;
      monitor->inuse = 0;

      CloseHandle (proc);
      log_debug ("  assign_rvid (ctx=%i): error: can not create monitor\n",
		 ctx_arg);
      return FALSE;
    }
  CloseHandle (monitor_hnd);

  /* Consume the pimpl reference.  */
  LeaveCriticalSection (&pimpl->critsect);

  return TRUE;
}


BOOL
GPG_IOControl (DWORD opnctx_arg, DWORD code, void *inbuf, DWORD inbuflen,
               void *outbuf, DWORD outbuflen, DWORD *actualoutlen)
{
  opnctx_t opnctx;
  BOOL result = FALSE;
  LONG rvid;
  LONG pid;

  log_debug ("GPG_IOControl (ctx=%i, %08x)\n", opnctx_arg, code);

  EnterCriticalSection (&opnctx_table_cs);
  opnctx = verify_opnctx (opnctx_arg);
  if (!opnctx)
    {
      log_debug ("GPG_IOControl (ctx=%i): error: could not access context\n", 
		 opnctx_arg);
      goto leave;
    }
  if (opnctx->is_log)
    {
      log_debug ("GPG_IOControl (ctx=%i): error: invalid code %u"
                 " for log device\n", opnctx_arg, (unsigned int)code);
      SetLastError (ERROR_INVALID_PARAMETER);
      goto leave;
    }

  switch (code)
    {
    case GPGCEDEV_IOCTL_GET_RVID:
      log_debug ("GPG_IOControl (ctx=%i): code: GET_RVID\n", opnctx_arg);
      if (inbuf || inbuflen || !outbuf || outbuflen < sizeof (LONG))
        {
	  log_debug ("GPG_IOControl (ctx=%i): error: invalid parameter\n", 
		     opnctx_arg);
          SetLastError (ERROR_INVALID_PARAMETER);
          goto leave;
        }

      if (! opnctx->rvid) 
	opnctx->rvid = create_rendezvous_id ();
      log_debug ("GPG_IOControl (ctx=%i): returning rvid: %08lx\n",
		 opnctx_arg, opnctx->rvid);

      memcpy (outbuf, &opnctx->rvid, sizeof (LONG));
      if (actualoutlen)
        *actualoutlen = sizeof (LONG);
      result = TRUE;
      break;

    case GPGCEDEV_IOCTL_MAKE_PIPE:
      log_debug ("GPG_IOControl (ctx=%i): code: MAKE_PIPE\n", opnctx_arg);
      if (!inbuf || inbuflen < sizeof (LONG) 
          || outbuf || outbuflen || actualoutlen)
        {
	  log_debug ("GPG_IOControl (ctx=%i): error: invalid parameter\n", 
		     opnctx_arg);
          SetLastError (ERROR_INVALID_PARAMETER);
          goto leave;
        }
      memcpy (&rvid, inbuf, sizeof (LONG));
      log_debug ("GPG_IOControl (ctx=%i): make pipe for rvid: %08lx\n", 
		 opnctx_arg, rvid);
      if (make_pipe (opnctx, rvid))
        result = TRUE;
      break;

    case GPGCEDEV_IOCTL_UNBLOCK:
      log_debug ("GPG_IOControl (ctx=%i): code: UNBLOCK\n", opnctx_arg);
      if (inbuf || inbuflen || outbuf || outbuflen || actualoutlen)
        {
	  log_debug ("GPG_IOControl (ctx=%i): error: invalid parameter\n", 
		     opnctx_arg);
          SetLastError (ERROR_INVALID_PARAMETER);
          goto leave;
        }
      
      if (unblock_call (opnctx))
        result = TRUE;
      break;

    case GPGCEDEV_IOCTL_ASSIGN_RVID:
      log_debug ("GPG_IOControl (ctx=%i): code: ASSIGN_RVID\n", opnctx_arg);
      if (!inbuf || inbuflen < 2 * sizeof (DWORD)
          || outbuf || outbuflen || actualoutlen)
        {
	  log_debug ("GPG_IOControl (ctx=%i): error: invalid parameter\n", 
		     opnctx_arg);
          SetLastError (ERROR_INVALID_PARAMETER);
          goto leave;
        }
      memcpy (&rvid, inbuf, sizeof (DWORD));
      memcpy (&pid, ((char *) inbuf) + sizeof (DWORD), sizeof (DWORD));
      log_debug ("GPG_IOControl (ctx=%i): assign rvid %08lx to pid %08lx\n", 
		 opnctx_arg, rvid, pid);
      if (assign_rvid (opnctx, rvid, pid))
        result = TRUE;
      break;

    case IOCTL_PSL_NOTIFY:
      /* This notification is received if the application's main
         thread exits and the application has other threads running
         and the application has open handles for this device.  What
         we do is to unblock them all simialr to an explicit unblock
         call.  */
      log_debug ("GPG_IOControl (ctx=%i): code: NOTIFY\n", opnctx_arg);

      if (unblock_call (opnctx))
        result = TRUE;
      break;

    default:
      log_debug ("GPG_IOControl (ctx=%i): code: (unknown)\n", opnctx_arg);
      SetLastError (ERROR_INVALID_PARAMETER);
      break;
    }

  log_debug ("GPG_IOControl (ctx=%i): success: result=%d\n", opnctx_arg,
	     result);

 leave:
  LeaveCriticalSection (&opnctx_table_cs);
  return result;
}



void
GPG_PowerUp (DWORD devctx)
{
  log_debug ("GPG_PowerUp (devctx=%i)\n", devctx);
}



void
GPG_PowerDown (DWORD devctx)
{
  log_debug ("GPG_PowerDown (devctx=%i)\n", devctx);
}





/* Entry point called by the DLL loader.  */
int WINAPI
DllMain (HINSTANCE hinst, DWORD reason, LPVOID reserved)
{
  (void)reserved;

  switch (reason)
    {
    case DLL_PROCESS_ATTACH:
      InitializeCriticalSection (&opnctx_table_cs);
      InitializeCriticalSection (&logcontrol.lock);
      break;

    case DLL_THREAD_ATTACH:
      break;

    case DLL_THREAD_DETACH:
      break;

    case DLL_PROCESS_DETACH:
      DeleteCriticalSection (&opnctx_table_cs);
      break;

    default:
      break;
    }
  
  return TRUE;
}
