Blame src/wait-global.c

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