Blame src/wait-global.c

Packit Service 672cf4
/* wait-global.c
Packit Service 0ef63b
 * Copyright (C) 2000 Werner Koch (dd9jn)
Packit Service 0ef63b
 * Copyright (C) 2001, 2002, 2003, 2004, 2005 g10 Code GmbH
Packit Service 0ef63b
 *
Packit Service 0ef63b
 * This file is part of GPGME.
Packit Service 0ef63b
 *
Packit Service 0ef63b
 * GPGME is free software; you can redistribute it and/or modify it
Packit Service 0ef63b
 * under the terms of the GNU Lesser General Public License as
Packit Service 0ef63b
 * published by the Free Software Foundation; either version 2.1 of
Packit Service 0ef63b
 * the License, or (at your option) any later version.
Packit Service 0ef63b
 *
Packit Service 0ef63b
 * GPGME is distributed in the hope that it will be useful, but
Packit Service 0ef63b
 * WITHOUT ANY WARRANTY; without even the implied warranty of
Packit Service 0ef63b
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit Service 0ef63b
 * Lesser General Public License for more details.
Packit Service 0ef63b
 *
Packit Service 0ef63b
 * You should have received a copy of the GNU Lesser General Public
Packit Service 0ef63b
 * License along with this program; if not, see <https://gnu.org/licenses/>.
Packit Service 0ef63b
 * SPDX-License-Identifier: LGPL-2.1-or-later
Packit Service 0ef63b
 */
Packit Service 672cf4
Packit Service 672cf4
#if HAVE_CONFIG_H
Packit Service 672cf4
#include <config.h>
Packit Service 672cf4
#endif
Packit Service 672cf4
#include <stdlib.h>
Packit Service 672cf4
#include <assert.h>
Packit Service 672cf4
#include <string.h>
Packit Service 672cf4
#include <errno.h>
Packit Service 672cf4
Packit Service 672cf4
#include "gpgme.h"
Packit Service 672cf4
#include "sema.h"
Packit Service 672cf4
#include "util.h"
Packit Service 672cf4
#include "context.h"
Packit Service 672cf4
#include "wait.h"
Packit Service 672cf4
#include "priv-io.h"
Packit Service 672cf4
#include "ops.h"
Packit Service 672cf4
#include "debug.h"
Packit Service 672cf4
Packit Service 672cf4
/* The global event loop is used for all asynchronous operations
Packit Service 672cf4
   (except key listing) for which no user I/O callbacks are specified.
Packit Service 672cf4
Packit Service 672cf4
   A context sets up its initial I/O callbacks and then sends the
Packit Service 672cf4
   GPGME_EVENT_START event.  After that, it is added to the global
Packit Service 672cf4
   list of active contexts.
Packit Service 672cf4
Packit Service 672cf4
   The gpgme_wait function contains a select() loop over all file
Packit Service 672cf4
   descriptors in all active contexts.  If an error occurs, it closes
Packit Service 672cf4
   all fds in that context and moves the context to the global done
Packit Service 672cf4
   list.  Likewise, if a context has removed all I/O callbacks, it is
Packit Service 672cf4
   moved to the global done list.
Packit Service 672cf4
Packit Service 672cf4
   All contexts in the global done list are eligible for being
Packit Service 672cf4
   returned by gpgme_wait if requested by the caller.  */
Packit Service 672cf4
Packit Service 672cf4
/* The ctx_list_lock protects the list of active and done contexts.
Packit Service 672cf4
   Insertion into any of these lists is only allowed when the lock is
Packit Service 672cf4
   held.  This allows a muli-threaded program to loop over gpgme_wait
Packit Service 672cf4
   and in parallel start asynchronous gpgme operations.
Packit Service 672cf4
Packit Service 672cf4
   However, the fd tables in the contexts are not protected by this
Packit Service 672cf4
   lock.  They are only allowed to change either before the context is
Packit Service 672cf4
   added to the active list (ie, before the start event is signalled)
Packit Service 672cf4
   or in a callback handler.  */
Packit Service 672cf4
DEFINE_STATIC_LOCK (ctx_list_lock);
Packit Service 672cf4
Packit Service 672cf4
/* A ctx_list_item is an item in the global list of active or done
Packit Service 672cf4
   contexts.  */
Packit Service 672cf4
struct ctx_list_item
Packit Service 672cf4
{
Packit Service 672cf4
  /* Every ctx_list_item is an element in a doubly linked list.  The
Packit Service 672cf4
     list pointers are protected by the ctx_list_lock.  */
Packit Service 672cf4
  struct ctx_list_item *next;
Packit Service 672cf4
  struct ctx_list_item *prev;
Packit Service 672cf4
Packit Service 672cf4
  gpgme_ctx_t ctx;
Packit Service 672cf4
  /* The status is set when the ctx is moved to the done list.  */
Packit Service 672cf4
  gpgme_error_t status;
Packit Service 672cf4
  gpgme_error_t op_err;
Packit Service 672cf4
};
Packit Service 672cf4
Packit Service 672cf4
/* The active list contains all contexts that are in the global event
Packit Service 672cf4
   loop, have active I/O callbacks, and have already seen the start
Packit Service 672cf4
   event.  */
Packit Service 672cf4
static struct ctx_list_item *ctx_active_list;
Packit Service 672cf4
Packit Service 672cf4
/* The done list contains all contexts that have previously been
Packit Service 672cf4
   active but now are not active any longer, either because they
Packit Service 672cf4
   finished successfully or an I/O callback returned an error.  The
Packit Service 672cf4
   status field in the list item contains the error value (or 0 if
Packit Service 672cf4
   successful).  */
Packit Service 672cf4
static struct ctx_list_item *ctx_done_list;
Packit Service 672cf4
Packit Service 672cf4

Packit Service 672cf4
/* Enter the context CTX into the active list.  */
Packit Service 672cf4
static gpgme_error_t
Packit Service 672cf4
ctx_active (gpgme_ctx_t ctx)
Packit Service 672cf4
{
Packit Service 672cf4
  struct ctx_list_item *li = malloc (sizeof (struct ctx_list_item));
Packit Service 672cf4
  if (!li)
Packit Service 672cf4
    return gpg_error_from_syserror ();
Packit Service 672cf4
  li->ctx = ctx;
Packit Service 672cf4
Packit Service 672cf4
  LOCK (ctx_list_lock);
Packit Service 672cf4
  /* Add LI to active list.  */
Packit Service 672cf4
  li->next = ctx_active_list;
Packit Service 672cf4
  li->prev = NULL;
Packit Service 672cf4
  if (ctx_active_list)
Packit Service 672cf4
    ctx_active_list->prev = li;
Packit Service 672cf4
  ctx_active_list = li;
Packit Service 672cf4
  UNLOCK (ctx_list_lock);
Packit Service 672cf4
  return 0;
Packit Service 672cf4
}
Packit Service 672cf4
Packit Service 672cf4
Packit Service 672cf4
/* Enter the context CTX into the done list with status STATUS.  */
Packit Service 672cf4
static void
Packit Service 672cf4
ctx_done (gpgme_ctx_t ctx, gpgme_error_t status, gpgme_error_t op_err)
Packit Service 672cf4
{
Packit Service 672cf4
  struct ctx_list_item *li;
Packit Service 672cf4
Packit Service 672cf4
  LOCK (ctx_list_lock);
Packit Service 672cf4
  li = ctx_active_list;
Packit Service 672cf4
  while (li && li->ctx != ctx)
Packit Service 672cf4
    li = li->next;
Packit Service 672cf4
  assert (li);
Packit Service 672cf4
Packit Service 672cf4
  /* Remove LI from active list.  */
Packit Service 672cf4
  if (li->next)
Packit Service 672cf4
    li->next->prev = li->prev;
Packit Service 672cf4
  if (li->prev)
Packit Service 672cf4
    li->prev->next = li->next;
Packit Service 672cf4
  else
Packit Service 672cf4
    ctx_active_list = li->next;
Packit Service 672cf4
Packit Service 672cf4
  li->status = status;
Packit Service 672cf4
  li->op_err = op_err;
Packit Service 672cf4
Packit Service 672cf4
  /* Add LI to done list.  */
Packit Service 672cf4
  li->next = ctx_done_list;
Packit Service 672cf4
  li->prev = NULL;
Packit Service 672cf4
  if (ctx_done_list)
Packit Service 672cf4
    ctx_done_list->prev = li;
Packit Service 672cf4
  ctx_done_list = li;
Packit Service 672cf4
  UNLOCK (ctx_list_lock);
Packit Service 672cf4
}
Packit Service 672cf4
Packit Service 672cf4
Packit Service 672cf4
/* Find finished context CTX (or any context if CTX is NULL) and
Packit Service 672cf4
   return its status in STATUS after removing it from the done list.
Packit Service 672cf4
   If a matching context could be found, return it.  Return NULL if no
Packit Service 672cf4
   context could be found.  */
Packit Service 672cf4
static gpgme_ctx_t
Packit Service 672cf4
ctx_wait (gpgme_ctx_t ctx, gpgme_error_t *status, gpgme_error_t *op_err)
Packit Service 672cf4
{
Packit Service 672cf4
  struct ctx_list_item *li;
Packit Service 672cf4
Packit Service 672cf4
  LOCK (ctx_list_lock);
Packit Service 672cf4
  li = ctx_done_list;
Packit Service 672cf4
  if (ctx)
Packit Service 672cf4
    {
Packit Service 672cf4
      /* A specific context is requested.  */
Packit Service 672cf4
      while (li && li->ctx != ctx)
Packit Service 672cf4
	li = li->next;
Packit Service 672cf4
    }
Packit Service 672cf4
  if (li)
Packit Service 672cf4
    {
Packit Service 672cf4
      ctx = li->ctx;
Packit Service 672cf4
      if (status)
Packit Service 672cf4
	*status = li->status;
Packit Service 672cf4
      if (op_err)
Packit Service 672cf4
	*op_err = li->op_err;
Packit Service 672cf4
Packit Service 672cf4
      /* Remove LI from done list.  */
Packit Service 672cf4
      if (li->next)
Packit Service 672cf4
	li->next->prev = li->prev;
Packit Service 672cf4
      if (li->prev)
Packit Service 672cf4
	li->prev->next = li->next;
Packit Service 672cf4
      else
Packit Service 672cf4
	ctx_done_list = li->next;
Packit Service 672cf4
      free (li);
Packit Service 672cf4
    }
Packit Service 672cf4
  else
Packit Service 672cf4
    ctx = NULL;
Packit Service 672cf4
  UNLOCK (ctx_list_lock);
Packit Service 672cf4
  return ctx;
Packit Service 672cf4
}
Packit Service 672cf4
Packit Service 672cf4

Packit Service 672cf4
/* Internal I/O callback functions.  */
Packit Service 672cf4
Packit Service 672cf4
/* The add_io_cb and remove_io_cb handlers are shared with the private
Packit Service 672cf4
   event loops.  */
Packit Service 672cf4
Packit Service 672cf4
void
Packit Service 672cf4
_gpgme_wait_global_event_cb (void *data, gpgme_event_io_t type,
Packit Service 672cf4
			     void *type_data)
Packit Service 672cf4
{
Packit Service 672cf4
  gpgme_ctx_t ctx = (gpgme_ctx_t) data;
Packit Service 672cf4
Packit Service 672cf4
  assert (ctx);
Packit Service 672cf4
Packit Service 672cf4
  switch (type)
Packit Service 672cf4
    {
Packit Service 672cf4
    case GPGME_EVENT_START:
Packit Service 672cf4
      {
Packit Service 672cf4
	gpgme_error_t err = ctx_active (ctx);
Packit Service 672cf4
Packit Service 672cf4
	if (err)
Packit Service 672cf4
	  /* An error occurred.  Close all fds in this context, and
Packit Service 672cf4
	     send the error in a done event.  */
Packit Service 672cf4
	  _gpgme_cancel_with_err (ctx, err, 0);
Packit Service 672cf4
      }
Packit Service 672cf4
      break;
Packit Service 672cf4
Packit Service 672cf4
    case GPGME_EVENT_DONE:
Packit Service 672cf4
      {
Packit Service 672cf4
	gpgme_io_event_done_data_t done_data =
Packit Service 672cf4
	  (gpgme_io_event_done_data_t) type_data;
Packit Service 672cf4
Packit Service 672cf4
	ctx_done (ctx, done_data->err, done_data->op_err);
Packit Service 672cf4
      }
Packit Service 672cf4
      break;
Packit Service 672cf4
Packit Service 672cf4
    case GPGME_EVENT_NEXT_KEY:
Packit Service 672cf4
      assert (!"Unexpected event GPGME_EVENT_NEXT_KEY");
Packit Service 672cf4
      break;
Packit Service 672cf4
Packit Service 672cf4
    case GPGME_EVENT_NEXT_TRUSTITEM:
Packit Service 672cf4
      assert (!"Unexpected event GPGME_EVENT_NEXT_TRUSTITEM");
Packit Service 672cf4
      break;
Packit Service 672cf4
Packit Service 672cf4
    default:
Packit Service 672cf4
      assert (!"Unexpected event");
Packit Service 672cf4
      break;
Packit Service 672cf4
    }
Packit Service 672cf4
}
Packit Service 672cf4
Packit Service 672cf4
Packit Service 672cf4

Packit Service 672cf4
/* Perform asynchronous operations in the global event loop (ie, any
Packit Service 672cf4
   asynchronous operation except key listing and trustitem listing
Packit Service 672cf4
   operations).  If CTX is not a null pointer, the function will
Packit Service 672cf4
   return if the asynchronous operation in the context CTX finished.
Packit Service 672cf4
   Otherwise the function will return if any asynchronous operation
Packit Service 672cf4
   finished.  If HANG is zero, the function will not block for a long
Packit Service 672cf4
   time.  Otherwise the function does not return until an operation
Packit Service 672cf4
   matching CTX finished.
Packit Service 672cf4
Packit Service 672cf4
   If a matching context finished, it is returned, and *STATUS is set
Packit Service 672cf4
   to the error value of the operation in that context.  Otherwise, if
Packit Service 672cf4
   the timeout expires, NULL is returned and *STATUS is 0.  If an
Packit Service 672cf4
   error occurs, NULL is returned and *STATUS is set to the error
Packit Service 672cf4
   value.  */
Packit Service 672cf4
gpgme_ctx_t
Packit Service 672cf4
gpgme_wait_ext (gpgme_ctx_t ctx, gpgme_error_t *status,
Packit Service 672cf4
		gpgme_error_t *op_err, int hang)
Packit Service 672cf4
{
Packit Service 672cf4
  do
Packit Service 672cf4
    {
Packit Service 672cf4
      unsigned int i = 0;
Packit Service 672cf4
      struct ctx_list_item *li;
Packit Service 672cf4
      struct fd_table fdt;
Packit Service 672cf4
      int nr;
Packit Service 672cf4
Packit Service 672cf4
      /* Collect the active file descriptors.  */
Packit Service 672cf4
      LOCK (ctx_list_lock);
Packit Service 672cf4
      for (li = ctx_active_list; li; li = li->next)
Packit Service 672cf4
	i += li->ctx->fdt.size;
Packit Service 672cf4
      fdt.fds = malloc (i * sizeof (struct io_select_fd_s));
Packit Service 672cf4
      if (!fdt.fds)
Packit Service 672cf4
	{
Packit Service 672cf4
          int saved_err = gpg_error_from_syserror ();
Packit Service 672cf4
	  UNLOCK (ctx_list_lock);
Packit Service 672cf4
	  if (status)
Packit Service 672cf4
	    *status = saved_err;
Packit Service 672cf4
	  if (op_err)
Packit Service 672cf4
	    *op_err = 0;
Packit Service 672cf4
	  return NULL;
Packit Service 672cf4
	}
Packit Service 672cf4
      fdt.size = i;
Packit Service 672cf4
      i = 0;
Packit Service 672cf4
      for (li = ctx_active_list; li; li = li->next)
Packit Service 672cf4
	{
Packit Service 672cf4
	  memcpy (&fdt.fds[i], li->ctx->fdt.fds,
Packit Service 672cf4
		  li->ctx->fdt.size * sizeof (struct io_select_fd_s));
Packit Service 672cf4
	  i += li->ctx->fdt.size;
Packit Service 672cf4
	}
Packit Service 672cf4
      UNLOCK (ctx_list_lock);
Packit Service 672cf4
Packit Service 672cf4
      nr = _gpgme_io_select (fdt.fds, fdt.size, 0);
Packit Service 672cf4
      if (nr < 0)
Packit Service 672cf4
	{
Packit Service 672cf4
          int saved_err = gpg_error_from_syserror ();
Packit Service 672cf4
	  free (fdt.fds);
Packit Service 672cf4
	  if (status)
Packit Service 672cf4
	    *status = saved_err;
Packit Service 672cf4
	  if (op_err)
Packit Service 672cf4
	    *op_err = 0;
Packit Service 672cf4
	  return NULL;
Packit Service 672cf4
	}
Packit Service 672cf4
Packit Service 672cf4
      for (i = 0; i < fdt.size && nr; i++)
Packit Service 672cf4
	{
Packit Service 672cf4
	  if (fdt.fds[i].fd != -1 && fdt.fds[i].signaled)
Packit Service 672cf4
	    {
Packit Service 672cf4
	      gpgme_ctx_t ictx;
Packit Service 672cf4
	      gpgme_error_t err = 0;
Packit Service 672cf4
	      gpgme_error_t local_op_err = 0;
Packit Service 672cf4
	      struct wait_item_s *item;
Packit Service 672cf4
Packit Service 672cf4
	      assert (nr);
Packit Service 672cf4
	      nr--;
Packit Service 672cf4
Packit Service 672cf4
	      item = (struct wait_item_s *) fdt.fds[i].opaque;
Packit Service 672cf4
	      assert (item);
Packit Service 672cf4
	      ictx = item->ctx;
Packit Service 672cf4
	      assert (ictx);
Packit Service 672cf4
Packit Service 672cf4
	      LOCK (ctx->lock);
Packit Service 672cf4
	      if (ctx->canceled)
Packit Service 672cf4
		err = gpg_error (GPG_ERR_CANCELED);
Packit Service 672cf4
	      UNLOCK (ctx->lock);
Packit Service 672cf4
Packit Service 672cf4
	      if (!err)
Packit Service 672cf4
		err = _gpgme_run_io_cb (&fdt.fds[i], 0, &local_op_err);
Packit Service 672cf4
	      if (err || local_op_err)
Packit Service 672cf4
		{
Packit Service 672cf4
		  /* An error occurred.  Close all fds in this context,
Packit Service 672cf4
		     and signal it.  */
Packit Service 672cf4
		  _gpgme_cancel_with_err (ictx, err, local_op_err);
Packit Service 672cf4
Packit Service 672cf4
		  /* Break out of the loop, and retry the select()
Packit Service 672cf4
		     from scratch, because now all fds should be
Packit Service 672cf4
		     gone.  */
Packit Service 672cf4
		  break;
Packit Service 672cf4
		}
Packit Service 672cf4
	    }
Packit Service 672cf4
	}
Packit Service 672cf4
      free (fdt.fds);
Packit Service 672cf4
Packit Service 672cf4
      /* Now some contexts might have finished successfully.  */
Packit Service 672cf4
      LOCK (ctx_list_lock);
Packit Service 672cf4
    retry:
Packit Service 672cf4
      for (li = ctx_active_list; li; li = li->next)
Packit Service 672cf4
	{
Packit Service 672cf4
	  gpgme_ctx_t actx = li->ctx;
Packit Service 672cf4
Packit Service 672cf4
	  for (i = 0; i < actx->fdt.size; i++)
Packit Service 672cf4
	    if (actx->fdt.fds[i].fd != -1)
Packit Service 672cf4
	      break;
Packit Service 672cf4
	  if (i == actx->fdt.size)
Packit Service 672cf4
	    {
Packit Service 672cf4
	      struct gpgme_io_event_done_data data;
Packit Service 672cf4
	      data.err = 0;
Packit Service 672cf4
	      data.op_err = 0;
Packit Service 672cf4
Packit Service 672cf4
	      /* FIXME: This does not perform too well.  We have to
Packit Service 672cf4
		 release the lock because the I/O event handler
Packit Service 672cf4
		 acquires it to remove the context from the active
Packit Service 672cf4
		 list.  Two alternative strategies are worth
Packit Service 672cf4
		 considering: Either implement the DONE event handler
Packit Service 672cf4
		 here in a lock-free manner, or save a list of all
Packit Service 672cf4
		 contexts to be released and call the DONE events
Packit Service 672cf4
		 afterwards.  */
Packit Service 672cf4
	      UNLOCK (ctx_list_lock);
Packit Service 672cf4
	      _gpgme_engine_io_event (actx->engine, GPGME_EVENT_DONE, &data);
Packit Service 672cf4
	      LOCK (ctx_list_lock);
Packit Service 672cf4
	      goto retry;
Packit Service 672cf4
	    }
Packit Service 672cf4
	}
Packit Service 672cf4
      UNLOCK (ctx_list_lock);
Packit Service 672cf4
Packit Service 672cf4
      {
Packit Service 672cf4
	gpgme_ctx_t dctx = ctx_wait (ctx, status, op_err);
Packit Service 672cf4
Packit Service 672cf4
	if (dctx)
Packit Service 672cf4
	  {
Packit Service 672cf4
	    ctx = dctx;
Packit Service 672cf4
	    hang = 0;
Packit Service 672cf4
	  }
Packit Service 672cf4
	else if (!hang)
Packit Service 672cf4
	  {
Packit Service 672cf4
	    ctx = NULL;
Packit Service 672cf4
	    if (status)
Packit Service 672cf4
	      *status = 0;
Packit Service 672cf4
	    if (op_err)
Packit Service 672cf4
	      *op_err = 0;
Packit Service 672cf4
	  }
Packit Service 672cf4
      }
Packit Service 672cf4
    }
Packit Service 672cf4
  while (hang);
Packit Service 672cf4
Packit Service 672cf4
  return ctx;
Packit Service 672cf4
}
Packit Service 672cf4
Packit Service 672cf4
Packit Service 672cf4
gpgme_ctx_t
Packit Service 672cf4
gpgme_wait (gpgme_ctx_t ctx, gpgme_error_t *status, int hang)
Packit Service 672cf4
{
Packit Service 672cf4
  return gpgme_wait_ext (ctx, status, NULL, hang);
Packit Service 672cf4
}