Пример #1
0
gpgme_error_t
_gpgme_data_inbound_handler (void *opaque, int fd)
{
  struct io_cb_data *data = (struct io_cb_data *) opaque;
  gpgme_data_t dh = (gpgme_data_t) data->handler_value;
  char buffer[BUFFER_SIZE];
  char *bufp = buffer;
  gpgme_ssize_t buflen;
  TRACE_BEG1 (DEBUG_CTX, "_gpgme_data_inbound_handler", dh,
	      "fd=0x%x", fd);

  buflen = _gpgme_io_read (fd, buffer, BUFFER_SIZE);
  if (buflen < 0)
    return gpg_error_from_syserror ();
  if (buflen == 0)
    {
      _gpgme_io_close (fd);
      return TRACE_ERR (0);
    }

  do
    {
      gpgme_ssize_t amt = gpgme_data_write (dh, bufp, buflen);
      if (amt == 0 || (amt < 0 && errno != EINTR))
	return TRACE_ERR (gpg_error_from_syserror ());
      bufp += amt;
      buflen -= amt;
    }
  while (buflen > 0);
  return TRACE_ERR (0);
}
Пример #2
0
/* 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;
  DWORD nwritten;
  TRACE_BEG1 (DEBUG_SYSIO, "gpgme:writer", ctx->file_hd,
	      "thread=%p", ctx->thread_hd);

  for (;;)
    {
      LOCK (ctx->mutex);
      if (ctx->stop_me)
	{
	  UNLOCK (ctx->mutex);
	  break;
        }
      if (!ctx->nbytes)
	{ 
	  if (!SetEvent (ctx->is_empty))
	    TRACE_LOG1 ("SetEvent failed: ec=%d", (int) GetLastError ());
	  if (!ResetEvent (ctx->have_data))
	    TRACE_LOG1 ("ResetEvent failed: ec=%d", (int) GetLastError ());
	  UNLOCK (ctx->mutex);
	  TRACE_LOG ("idle");
	  WaitForSingleObject (ctx->have_data, INFINITE);
	  TRACE_LOG ("got data to send");
	  LOCK (ctx->mutex);
       	}
      if (ctx->stop_me)
	{
	  UNLOCK (ctx->mutex);
	  break;
        }
      UNLOCK (ctx->mutex);
      
      TRACE_LOG1 ("writing %d bytes", 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;
	}
      TRACE_LOG1 ("wrote %d bytes", (int) nwritten);
      
      LOCK (ctx->mutex);
      ctx->nbytes -= nwritten;
      UNLOCK (ctx->mutex);
    }
  /* Indicate that we have an error.  */
  if (!SetEvent (ctx->is_empty))
    TRACE_LOG1 ("SetEvent failed: ec=%d", (int) GetLastError ());
  SetEvent (ctx->stopped);

  return TRACE_SUC ();
}
Пример #3
0
/* Fork and exec the program with /dev/null as stdin, stdout and
   stderr.  Returns 0 on success or an error code.  */
gpg_error_t
gpgex_spawn_detached (const char *cmdline)
{
    SECURITY_ATTRIBUTES sec_attr;
    PROCESS_INFORMATION pi =
    {
        NULL,      /* Returns process handle.  */
        0,         /* Returns primary thread handle.  */
        0,         /* Returns pid.  */
        0          /* Returns tid.  */
    };
    STARTUPINFO si;
    int cr_flags;

    TRACE_BEG1 (DEBUG_ASSUAN, "gpgex_spawn_detached", cmdline,
                "cmdline=%s", cmdline);

    /* Prepare security attributes.  */
    memset (&sec_attr, 0, sizeof sec_attr);
    sec_attr.nLength = sizeof sec_attr;
    sec_attr.bInheritHandle = FALSE;

    /* Start the process.  Note that we can't run the PREEXEC function
       because this would change our own environment. */
    memset (&si, 0, sizeof si);
    si.cb = sizeof (si);
    si.dwFlags = STARTF_USESHOWWINDOW;
    si.wShowWindow = DEBUG_W32_SPAWN ? SW_SHOW : SW_MINIMIZE;

    cr_flags = (CREATE_DEFAULT_ERROR_MODE
                | GetPriorityClass (GetCurrentProcess ())
                | CREATE_NEW_PROCESS_GROUP
                | DETACHED_PROCESS);

    if (!CreateProcess (NULL,          /* pgmname; Program to start.  */
                        (char *) cmdline, /* Command line arguments.  */
                        &sec_attr,     /* Process security attributes.  */
                        &sec_attr,     /* Thread security attributes.  */
                        TRUE,          /* Inherit handles.  */
                        cr_flags,      /* Creation flags.  */
                        NULL,          /* Environment.  */
                        NULL,          /* Use current drive/directory.  */
                        &si,           /* Startup information. */
                        &pi            /* Returns process information.  */
                       ))
    {
        (void) TRACE_LOG1 ("CreateProcess failed: %i\n", GetLastError ());
        return gpg_error (GPG_ERR_GENERAL);
    }

    /* Process has been created suspended; resume it now. */
    CloseHandle (pi.hThread);
    CloseHandle (pi.hProcess);

    return 0;
}
Пример #4
0
gpgme_error_t
gpgme_op_import_start (gpgme_ctx_t ctx, gpgme_data_t keydata)
{
  gpg_error_t err;

  TRACE_BEG1 (DEBUG_CTX, "gpgme_op_import_start", ctx,
	      "keydata=%p", keydata);

  err = _gpgme_op_import_start (ctx, 0, keydata);
  return TRACE_ERR (err);
}
Пример #5
0
/* Set the encoding meta information for the data object with handle
   DH to ENC.  */
gpgme_error_t
gpgme_data_set_encoding (gpgme_data_t dh, gpgme_data_encoding_t enc)
{
  TRACE_BEG1 (DEBUG_DATA, "gpgme_data_set_encoding", dh,
	      "encoding=%i", enc);
  if (!dh)
    return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE));
  if (enc < 0 || enc > GPGME_DATA_ENCODING_URL0)
    return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE));
  dh->encoding = enc;
  return TRACE_ERR (0);
}
Пример #6
0
gpgme_error_t
gpgme_data_new_from_fd (gpgme_data_t *r_dh, int fd)
{
  gpgme_error_t err;
  TRACE_BEG1 (DEBUG_DATA, "gpgme_data_new_from_fd", r_dh, "fd=0x%x", fd);

  err = _gpgme_data_new (r_dh, &fd_cbs);
  if (err)
    return TRACE_ERR (err);

  (*r_dh)->data.fd = fd;
  return TRACE_SUC1 ("dh=%p", *r_dh);
}
Пример #7
0
gpgme_error_t
gpgme_data_new_from_cbs (gpgme_data_t *r_dh, gpgme_data_cbs_t cbs, void *handle)
{
  gpgme_error_t err;
  TRACE_BEG1 (DEBUG_DATA, "gpgme_data_new_from_cbs", r_dh, "handle=%p", handle);

  err = _gpgme_data_new (r_dh, &user_cbs);
  if (err)
    return TRACE_ERR (err);

  (*r_dh)->data.user.cbs = cbs;
  (*r_dh)->data.user.handle = handle;
  return TRACE_SUC1 ("dh=%p", *r_dh);
}
Пример #8
0
/* Write the printable version of FD to the buffer BUF of length
   BUFLEN.  The printable version is the representation on the command
   line that the child process expects.  */
int
_gpgme_io_fd2str (char *buf, int buflen, int fd)
{
  HANDLE hndl;
    
  TRACE_BEG1 (DEBUG_SYSIO, "_gpgme_io_fd2str", fd, "fd=%d", fd);
  if (giochannel_table[fd].fd != -1)
    hndl = (HANDLE) _get_osfhandle (giochannel_table[fd].fd);
  else
    hndl = (HANDLE) giochannel_table[fd].socket;

  TRACE_SUC1 ("syshd=%p", hndl);
  
  return snprintf (buf, buflen, "%d", (int) hndl);
}
Пример #9
0
/* Destroy the data buffer DH and return a pointer to its content.
   The memory has be to released with gpgme_free() by the user.  It's
   size is returned in R_LEN.  */
char *
gpgme_data_release_and_get_mem (gpgme_data_t dh, size_t *r_len)
{
  char *str = NULL;

  TRACE_BEG1 (DEBUG_DATA, "gpgme_data_release_and_get_mem", dh,
	      "r_len=%p", r_len);

  if (!dh || dh->cbs != &mem_cbs)
    {
      gpgme_data_release (dh);
      TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE));
      return NULL;
    }

  str = dh->data.mem.buffer;
  if (!str && dh->data.mem.orig_buffer)
    {
      str = malloc (dh->data.mem.length);
      if (!str)
	{
	  int saved_err = gpg_error_from_syserror ();
	  gpgme_data_release (dh);
	  TRACE_ERR (saved_err);
	  return NULL;
	}
      memcpy (str, dh->data.mem.orig_buffer, dh->data.mem.length);
    }
  else
    /* Prevent mem_release from releasing the buffer memory.  We must
       not fail from this point.  */
    dh->data.mem.buffer = NULL;

  if (r_len)
    *r_len = dh->data.mem.length;

  gpgme_data_release (dh);

  if (r_len)
    {
      TRACE_SUC2 ("buffer=%p, len=%u", str, *r_len);
    }
  else
    {
      TRACE_SUC1 ("buffer=%p", str);
    }
  return str;
}
Пример #10
0
/* Import the key in KEYDATA into the keyring.  */
gpgme_error_t
gpgme_op_import (gpgme_ctx_t ctx, gpgme_data_t keydata)
{
  gpgme_error_t err;

  TRACE_BEG1 (DEBUG_CTX, "gpgme_op_import", ctx,
	      "keydata=%p", keydata);

  if (!ctx)
    return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE));

  err = _gpgme_op_import_start (ctx, 1, keydata);
  if (!err)
    err = _gpgme_wait_one (ctx);
  return TRACE_ERR (err);
}
Пример #11
0
gpgme_error_t
_gpgme_data_outbound_handler (void *opaque, int fd)
{
  struct io_cb_data *data = (struct io_cb_data *) opaque;
  gpgme_data_t dh = (gpgme_data_t) data->handler_value;
  gpgme_ssize_t nwritten;
  TRACE_BEG1 (DEBUG_CTX, "_gpgme_data_outbound_handler", dh,
	      "fd=0x%x", fd);

  if (!dh->pending_len)
    {
      gpgme_ssize_t amt = gpgme_data_read (dh, dh->pending, BUFFER_SIZE);
      if (amt < 0)
	return TRACE_ERR (gpg_error_from_syserror ());
      if (amt == 0)
	{
	  _gpgme_io_close (fd);
	  return TRACE_ERR (0);
	}
      dh->pending_len = amt;
    }

  nwritten = _gpgme_io_write (fd, dh->pending, dh->pending_len);
  if (nwritten == -1 && errno == EAGAIN)
    return TRACE_ERR (0);

  if (nwritten == -1 && errno == EPIPE)
    {
      /* Not much we can do.  The other end closed the pipe, but we
	 still have data.  This should only ever happen if the other
	 end is going to tell us what happened on some other channel.
	 Silently close our end.  */
      _gpgme_io_close (fd);
      return TRACE_ERR (0);
    }

  if (nwritten <= 0)
    return TRACE_ERR (gpg_error_from_syserror ());

  if (nwritten < dh->pending_len)
    memmove (dh->pending, dh->pending + nwritten, dh->pending_len - nwritten);
  dh->pending_len -= nwritten;
  return TRACE_ERR (0);
}
Пример #12
0
/* Return the VALUE of FLAG in context CTX.  */
int
assuan_get_flag (assuan_context_t ctx, assuan_flag_t flag)
{
  int res = 0;
  TRACE_BEG1 (ctx, ASSUAN_LOG_CTX, "assuan_get_flag", ctx,
	      "flag=%i", flag);

  if (! ctx)
    return 0;

  switch (flag)
    {
    case ASSUAN_NO_WAITPID:
      res = ctx->flags.no_waitpid;
      break;

    case ASSUAN_CONFIDENTIAL:
      res = ctx->flags.confidential;
      break;

    case ASSUAN_NO_FIXSIGNALS:
      res = ctx->flags.no_fixsignals;
      break;

    case ASSUAN_CONVEY_COMMENTS:
      res = ctx->flags.convey_comments;
      break;

    case ASSUAN_NO_LOGGING:
      res = ctx->flags.no_logging;
      break;

    case ASSUAN_FORCE_CLOSE:
      res = ctx->flags.force_close;
      break;
    }

  return TRACE_SUC1 ("flag_value=%i", res);
}
Пример #13
0
/* Set the file name associated with the data object with handle DH to
   FILE_NAME.  */
gpgme_error_t
gpgme_data_set_file_name (gpgme_data_t dh, const char *file_name)
{
  TRACE_BEG1 (DEBUG_DATA, "gpgme_data_set_file_name", dh,
	      "file_name=%s", file_name);

  if (!dh)
    return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE));

  if (dh->file_name)
    free (dh->file_name);

  if (file_name)
    {
      dh->file_name = strdup (file_name);
      if (!dh->file_name)
	return TRACE_ERR (gpg_error_from_syserror ());
    }
  else
    dh->file_name = 0;

  return TRACE_ERR (0);
}
Пример #14
0
int
_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 */
      0,         /* returns primary thread handle */
      0,         /* returns pid */
      0          /* returns tid */
    };
  STARTUPINFO si;
  int cr_flags = (CREATE_DEFAULT_ERROR_MODE
                  | GetPriorityClass (GetCurrentProcess ()));
  int i;
  char **args;
  char *arg_string;
  /* FIXME.  */
  int debug_me = 0;
  int tmp_fd;
  char *tmp_name;

  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++;
    }
  
  /* We do not inherit any handles by default, and just insert those
     handles we want the child to have afterwards.  But some handle
     values occur on the command line, and we need to move
     stdin/out/err to the right location.  So we use a wrapper program
     which gets the information from a temporary file.  */
  if (_gpgme_mkstemp (&tmp_fd, &tmp_name) < 0)
    {
      TRACE_LOG1 ("_gpgme_mkstemp failed: %s", strerror (errno));
      return TRACE_SYSRES (-1);
    }
  TRACE_LOG1 ("tmp_name = %s", tmp_name);

  args = calloc (2 + i + 1, sizeof (*args));
  args[0] = (char *) _gpgme_get_w32spawn_path ();
  args[1] = tmp_name;
  args[2] = path;
  memcpy (&args[3], &argv[1], i * sizeof (*args));

  memset (&sec_attr, 0, sizeof sec_attr);
  sec_attr.nLength = sizeof sec_attr;
  sec_attr.bInheritHandle = FALSE;
  
  arg_string = build_commandline (args);
  free (args);
  if (!arg_string)
    {
      close (tmp_fd);
      DeleteFile (tmp_name);
      return TRACE_SYSRES (-1);
    }

  memset (&si, 0, sizeof si);
  si.cb = sizeof (si);
  si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
  si.wShowWindow = debug_me ? SW_SHOW : SW_HIDE;
  si.hStdInput = INVALID_HANDLE_VALUE;
  si.hStdOutput = INVALID_HANDLE_VALUE;
  si.hStdError = INVALID_HANDLE_VALUE;

  cr_flags |= CREATE_SUSPENDED;
  cr_flags |= DETACHED_PROCESS;
  if (!CreateProcessA (_gpgme_get_w32spawn_path (),
		       arg_string,
		       &sec_attr,     /* process security attributes */
		       &sec_attr,     /* thread security attributes */
		       FALSE,         /* inherit handles */
		       cr_flags,      /* creation flags */
		       NULL,          /* environment */
		       NULL,          /* use current drive/directory */
		       &si,           /* startup information */
		       &pi))          /* returns process information */
    {
      TRACE_LOG1 ("CreateProcess failed: ec=%d", (int) GetLastError ());
      free (arg_string);
      close (tmp_fd);
      DeleteFile (tmp_name);

      /* FIXME: Should translate the error code.  */
      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;

      /* Make it inheritable for the wrapper process.  */
      if (!DuplicateHandle (GetCurrentProcess(),
			    _get_osfhandle (giochannel_table[fd_list[i].fd].fd),
			    pi.hProcess, &hd, 0, TRUE, DUPLICATE_SAME_ACCESS))
	{
	  TRACE_LOG1 ("DuplicateHandle failed: ec=%d", (int) GetLastError ());
	  TerminateProcess (pi.hProcess, 0);
	  /* Just in case TerminateProcess didn't work, let the
	     process fail on its own.  */
	  ResumeThread (pi.hThread);
	  CloseHandle (pi.hThread);
	  CloseHandle (pi.hProcess);

	  close (tmp_fd);
	  DeleteFile (tmp_name);

	  /* FIXME: Should translate the error code.  */
	  errno = EIO;
	  return TRACE_SYSRES (-1);
        }
      /* Return the child name of this handle.  */
      fd_list[i].peer_name = (int) hd;
    }

  /* Write the handle translation information to the temporary
     file.  */
  {
    /* Hold roughly MAX_TRANS quadruplets of 64 bit numbers in hex
       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
    char line[BUFFER_MAX + 1];
    int res;
    int written;
    size_t len;

    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.  */
	len = strlen (line) - 1;
	
	/* Format is: Local name, stdin/stdout/stderr, peer name, argv idx.  */
	snprintf (&line[len], BUFFER_MAX - len, "0x%x %d 0x%x %d  \n",
		  fd_list[i].fd, fd_list[i].dup_to,
		  fd_list[i].peer_name, fd_list[i].arg_loc);
	/* Rather safe than sorry.  */
	line[BUFFER_MAX - 1] = '\n';
	line[BUFFER_MAX] = '\0';
      }
    len = strlen (line);
    written = 0;
    do
      {
	res = write (tmp_fd, &line[written], len - written);
	if (res > 0)
	  written += res;
      }
    while (res > 0 || (res < 0 && errno == EAGAIN));
  }
  close (tmp_fd);
  /* The temporary file is deleted by the gpgme-w32spawn process
     (hopefully).  */
    
  TRACE_LOG4 ("CreateProcess ready: hProcess=%p, hThread=%p, "
	      "dwProcessID=%d, dwThreadId=%d",
	      pi.hProcess, pi.hThread, 
	      (int) pi.dwProcessId, (int) pi.dwThreadId);
  
  if (r_pid)
    *r_pid = (pid_t)pi.dwProcessId;

  if (ResumeThread (pi.hThread) < 0)
    TRACE_LOG1 ("ResumeThread failed: ec=%d", (int) GetLastError ());
  
  if (!CloseHandle (pi.hThread))
    TRACE_LOG1 ("CloseHandle of thread failed: ec=%d",
		(int) GetLastError ());

  TRACE_LOG1 ("process=%p", pi.hProcess);

  /* We don't need to wait for the process.  */
  if (!CloseHandle (pi.hProcess))
    TRACE_LOG1 ("CloseHandle of process failed: ec=%d",
		(int) GetLastError ());

  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)
      TRACE_LOG3 ("fd[%i] = 0x%x -> 0x%x", i, fd_list[i].fd,
		  fd_list[i].peer_name);
    else
      TRACE_LOG4 ("fd[%i] = 0x%x -> 0x%x (std%s)", i, fd_list[i].fd,
		  fd_list[i].peer_name, (fd_list[i].dup_to == 0) ? "in" :
		  ((fd_list[i].dup_to == 1) ? "out" : "err"));

  return TRACE_SYSRES (0);
}
Пример #15
0
static DWORD CALLBACK 
reader (void *arg)
{
  struct reader_context_s *ctx = arg;
  int nbytes;
  DWORD nread;
  TRACE_BEG1 (DEBUG_SYSIO, "gpgme:reader", ctx->file_hd,
	      "thread=%p", ctx->thread_hd);

  for (;;)
    {
      LOCK (ctx->mutex);
      /* Leave a 1 byte gap so that we can see whether it is empty or
	 full.  */
      if ((ctx->writepos + 1) % READBUF_SIZE == ctx->readpos)
	{ 
	  /* Wait for space.  */
	  if (!ResetEvent (ctx->have_space_ev))
	    TRACE_LOG1 ("ResetEvent failed: ec=%d", (int) GetLastError ());
	  UNLOCK (ctx->mutex);
	  TRACE_LOG ("waiting for space");
	  WaitForSingleObject (ctx->have_space_ev, INFINITE);
	  TRACE_LOG ("got space");
	  LOCK (ctx->mutex);
       	}
      if (ctx->stop_me)
	{
	  UNLOCK (ctx->mutex);
	  break;
        }
      nbytes = (ctx->readpos + READBUF_SIZE
		- ctx->writepos - 1) % READBUF_SIZE;
      if (nbytes > READBUF_SIZE - ctx->writepos)
	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)");
            }
	  else
	    {
	      ctx->error = 1;
	      TRACE_LOG1 ("read error: ec=%d", ctx->error_code);
            }
	  break;
        }
      if (!nread)
	{
	  ctx->eof = 1;
	  TRACE_LOG ("got eof");
	  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,
		    (int) GetLastError ());
      UNLOCK (ctx->mutex);
    }
  /* Indicate that we have an error or EOF.  */
  if (!SetEvent (ctx->have_data_ev))
	TRACE_LOG2 ("SetEvent (0x%x) failed: ec=%d", ctx->have_data_ev,
		    (int) GetLastError ());
  SetEvent (ctx->stopped);
  
  return TRACE_SUC ();
}