LCOV - code coverage report
Current view: top level - src - wait-global.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 111 147 75.5 %
Date: 2016-11-29 15:07:43 Functions: 6 6 100.0 %

          Line data    Source code
       1             : /* wait-global.c
       2             :    Copyright (C) 2000 Werner Koch (dd9jn)
       3             :    Copyright (C) 2001, 2002, 2003, 2004, 2005 g10 Code GmbH
       4             : 
       5             :    This file is part of GPGME.
       6             : 
       7             :    GPGME is free software; you can redistribute it and/or modify it
       8             :    under the terms of the GNU Lesser General Public License as
       9             :    published by the Free Software Foundation; either version 2.1 of
      10             :    the License, or (at your option) any later version.
      11             : 
      12             :    GPGME is distributed in the hope that it will be useful, but
      13             :    WITHOUT ANY WARRANTY; without even the implied warranty of
      14             :    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      15             :    Lesser General Public License for more details.
      16             : 
      17             :    You should have received a copy of the GNU Lesser General Public
      18             :    License along with this program; if not, write to the Free Software
      19             :    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
      20             :    02111-1307, USA.  */
      21             : 
      22             : #if HAVE_CONFIG_H
      23             : #include <config.h>
      24             : #endif
      25             : #include <stdlib.h>
      26             : #include <assert.h>
      27             : #include <string.h>
      28             : #include <errno.h>
      29             : 
      30             : #include "gpgme.h"
      31             : #include "sema.h"
      32             : #include "util.h"
      33             : #include "context.h"
      34             : #include "wait.h"
      35             : #include "priv-io.h"
      36             : #include "ops.h"
      37             : #include "debug.h"
      38             : 
      39             : /* The global event loop is used for all asynchronous operations
      40             :    (except key listing) for which no user I/O callbacks are specified.
      41             : 
      42             :    A context sets up its initial I/O callbacks and then sends the
      43             :    GPGME_EVENT_START event.  After that, it is added to the global
      44             :    list of active contexts.
      45             : 
      46             :    The gpgme_wait function contains a select() loop over all file
      47             :    descriptors in all active contexts.  If an error occurs, it closes
      48             :    all fds in that context and moves the context to the global done
      49             :    list.  Likewise, if a context has removed all I/O callbacks, it is
      50             :    moved to the global done list.
      51             : 
      52             :    All contexts in the global done list are eligible for being
      53             :    returned by gpgme_wait if requested by the caller.  */
      54             : 
      55             : /* The ctx_list_lock protects the list of active and done contexts.
      56             :    Insertion into any of these lists is only allowed when the lock is
      57             :    held.  This allows a muli-threaded program to loop over gpgme_wait
      58             :    and in parallel start asynchronous gpgme operations.
      59             : 
      60             :    However, the fd tables in the contexts are not protected by this
      61             :    lock.  They are only allowed to change either before the context is
      62             :    added to the active list (ie, before the start event is signalled)
      63             :    or in a callback handler.  */
      64             : DEFINE_STATIC_LOCK (ctx_list_lock);
      65             : 
      66             : /* A ctx_list_item is an item in the global list of active or done
      67             :    contexts.  */
      68             : struct ctx_list_item
      69             : {
      70             :   /* Every ctx_list_item is an element in a doubly linked list.  The
      71             :      list pointers are protected by the ctx_list_lock.  */
      72             :   struct ctx_list_item *next;
      73             :   struct ctx_list_item *prev;
      74             : 
      75             :   gpgme_ctx_t ctx;
      76             :   /* The status is set when the ctx is moved to the done list.  */
      77             :   gpgme_error_t status;
      78             :   gpgme_error_t op_err;
      79             : };
      80             : 
      81             : /* The active list contains all contexts that are in the global event
      82             :    loop, have active I/O callbacks, and have already seen the start
      83             :    event.  */
      84             : static struct ctx_list_item *ctx_active_list;
      85             : 
      86             : /* The done list contains all contexts that have previously been
      87             :    active but now are not active any longer, either because they
      88             :    finished successfully or an I/O callback returned an error.  The
      89             :    status field in the list item contains the error value (or 0 if
      90             :    successful).  */
      91             : static struct ctx_list_item *ctx_done_list;
      92             : 
      93             : 
      94             : /* Enter the context CTX into the active list.  */
      95             : static gpgme_error_t
      96           3 : ctx_active (gpgme_ctx_t ctx)
      97             : {
      98           3 :   struct ctx_list_item *li = malloc (sizeof (struct ctx_list_item));
      99           3 :   if (!li)
     100           0 :     return gpg_error_from_syserror ();
     101           3 :   li->ctx = ctx;
     102             : 
     103           3 :   LOCK (ctx_list_lock);
     104             :   /* Add LI to active list.  */
     105           3 :   li->next = ctx_active_list;
     106           3 :   li->prev = NULL;
     107           3 :   if (ctx_active_list)
     108           0 :     ctx_active_list->prev = li;
     109           3 :   ctx_active_list = li;
     110           3 :   UNLOCK (ctx_list_lock);
     111           3 :   return 0;
     112             : }
     113             : 
     114             : 
     115             : /* Enter the context CTX into the done list with status STATUS.  */
     116             : static void
     117           3 : ctx_done (gpgme_ctx_t ctx, gpgme_error_t status, gpgme_error_t op_err)
     118             : {
     119             :   struct ctx_list_item *li;
     120             : 
     121           3 :   LOCK (ctx_list_lock);
     122           3 :   li = ctx_active_list;
     123           6 :   while (li && li->ctx != ctx)
     124           0 :     li = li->next;
     125           3 :   assert (li);
     126             : 
     127             :   /* Remove LI from active list.  */
     128           3 :   if (li->next)
     129           0 :     li->next->prev = li->prev;
     130           3 :   if (li->prev)
     131           0 :     li->prev->next = li->next;
     132             :   else
     133           3 :     ctx_active_list = li->next;
     134             : 
     135           3 :   li->status = status;
     136           3 :   li->op_err = op_err;
     137             : 
     138             :   /* Add LI to done list.  */
     139           3 :   li->next = ctx_done_list;
     140           3 :   li->prev = NULL;
     141           3 :   if (ctx_done_list)
     142           0 :     ctx_done_list->prev = li;
     143           3 :   ctx_done_list = li;
     144           3 :   UNLOCK (ctx_list_lock);
     145           3 : }
     146             : 
     147             : 
     148             : /* Find finished context CTX (or any context if CTX is NULL) and
     149             :    return its status in STATUS after removing it from the done list.
     150             :    If a matching context could be found, return it.  Return NULL if no
     151             :    context could be found.  */
     152             : static gpgme_ctx_t
     153           9 : ctx_wait (gpgme_ctx_t ctx, gpgme_error_t *status, gpgme_error_t *op_err)
     154             : {
     155             :   struct ctx_list_item *li;
     156             : 
     157           9 :   LOCK (ctx_list_lock);
     158           9 :   li = ctx_done_list;
     159           9 :   if (ctx)
     160             :     {
     161             :       /* A specific context is requested.  */
     162          18 :       while (li && li->ctx != ctx)
     163           0 :         li = li->next;
     164             :     }
     165           9 :   if (li)
     166             :     {
     167           3 :       ctx = li->ctx;
     168           3 :       if (status)
     169           3 :         *status = li->status;
     170           3 :       if (op_err)
     171           0 :         *op_err = li->op_err;
     172             : 
     173             :       /* Remove LI from done list.  */
     174           3 :       if (li->next)
     175           0 :         li->next->prev = li->prev;
     176           3 :       if (li->prev)
     177           0 :         li->prev->next = li->next;
     178             :       else
     179           3 :         ctx_done_list = li->next;
     180           3 :       free (li);
     181             :     }
     182             :   else
     183           6 :     ctx = NULL;
     184           9 :   UNLOCK (ctx_list_lock);
     185           9 :   return ctx;
     186             : }
     187             : 
     188             : 
     189             : /* Internal I/O callback functions.  */
     190             : 
     191             : /* The add_io_cb and remove_io_cb handlers are shared with the private
     192             :    event loops.  */
     193             : 
     194             : void
     195           6 : _gpgme_wait_global_event_cb (void *data, gpgme_event_io_t type,
     196             :                              void *type_data)
     197             : {
     198           6 :   gpgme_ctx_t ctx = (gpgme_ctx_t) data;
     199             : 
     200           6 :   assert (ctx);
     201             : 
     202           6 :   switch (type)
     203             :     {
     204             :     case GPGME_EVENT_START:
     205             :       {
     206           3 :         gpgme_error_t err = ctx_active (ctx);
     207             : 
     208           3 :         if (err)
     209             :           /* An error occurred.  Close all fds in this context, and
     210             :              send the error in a done event.  */
     211           0 :           _gpgme_cancel_with_err (ctx, err, 0);
     212             :       }
     213           3 :       break;
     214             : 
     215             :     case GPGME_EVENT_DONE:
     216             :       {
     217           3 :         gpgme_io_event_done_data_t done_data =
     218             :           (gpgme_io_event_done_data_t) type_data;
     219             : 
     220           3 :         ctx_done (ctx, done_data->err, done_data->op_err);
     221             :       }
     222           3 :       break;
     223             : 
     224             :     case GPGME_EVENT_NEXT_KEY:
     225           0 :       assert (!"Unexpected event GPGME_EVENT_NEXT_KEY");
     226             :       break;
     227             : 
     228             :     case GPGME_EVENT_NEXT_TRUSTITEM:
     229           0 :       assert (!"Unexpected event GPGME_EVENT_NEXT_TRUSTITEM");
     230             :       break;
     231             : 
     232             :     default:
     233           0 :       assert (!"Unexpected event");
     234             :       break;
     235             :     }
     236           6 : }
     237             : 
     238             : 
     239             : 
     240             : /* Perform asynchronous operations in the global event loop (ie, any
     241             :    asynchronous operation except key listing and trustitem listing
     242             :    operations).  If CTX is not a null pointer, the function will
     243             :    return if the asynchronous operation in the context CTX finished.
     244             :    Otherwise the function will return if any asynchronous operation
     245             :    finished.  If HANG is zero, the function will not block for a long
     246             :    time.  Otherwise the function does not return until an operation
     247             :    matching CTX finished.
     248             : 
     249             :    If a matching context finished, it is returned, and *STATUS is set
     250             :    to the error value of the operation in that context.  Otherwise, if
     251             :    the timeout expires, NULL is returned and *STATUS is 0.  If an
     252             :    error occurs, NULL is returned and *STATUS is set to the error
     253             :    value.  */
     254             : gpgme_ctx_t
     255           9 : gpgme_wait_ext (gpgme_ctx_t ctx, gpgme_error_t *status,
     256             :                 gpgme_error_t *op_err, int hang)
     257             : {
     258             :   do
     259             :     {
     260           9 :       unsigned int i = 0;
     261             :       struct ctx_list_item *li;
     262             :       struct fd_table fdt;
     263             :       int nr;
     264             : 
     265             :       /* Collect the active file descriptors.  */
     266           9 :       LOCK (ctx_list_lock);
     267          18 :       for (li = ctx_active_list; li; li = li->next)
     268           9 :         i += li->ctx->fdt.size;
     269           9 :       fdt.fds = malloc (i * sizeof (struct io_select_fd_s));
     270           9 :       if (!fdt.fds)
     271             :         {
     272           0 :           int saved_err = gpg_error_from_syserror ();
     273           0 :           UNLOCK (ctx_list_lock);
     274           0 :           if (status)
     275           0 :             *status = saved_err;
     276           0 :           if (op_err)
     277           0 :             *op_err = 0;
     278           0 :           return NULL;
     279             :         }
     280           9 :       fdt.size = i;
     281           9 :       i = 0;
     282          18 :       for (li = ctx_active_list; li; li = li->next)
     283             :         {
     284           9 :           memcpy (&fdt.fds[i], li->ctx->fdt.fds,
     285           9 :                   li->ctx->fdt.size * sizeof (struct io_select_fd_s));
     286           9 :           i += li->ctx->fdt.size;
     287             :         }
     288           9 :       UNLOCK (ctx_list_lock);
     289             : 
     290           9 :       nr = _gpgme_io_select (fdt.fds, fdt.size, 0);
     291           9 :       if (nr < 0)
     292             :         {
     293           0 :           int saved_err = gpg_error_from_syserror ();
     294           0 :           free (fdt.fds);
     295           0 :           if (status)
     296           0 :             *status = saved_err;
     297           0 :           if (op_err)
     298           0 :             *op_err = 0;
     299           0 :           return NULL;
     300             :         }
     301             : 
     302          21 :       for (i = 0; i < fdt.size && nr; i++)
     303             :         {
     304          15 :           if (fdt.fds[i].fd != -1 && fdt.fds[i].signaled)
     305             :             {
     306             :               gpgme_ctx_t ictx;
     307           9 :               gpgme_error_t err = 0;
     308           9 :               gpgme_error_t local_op_err = 0;
     309             :               struct wait_item_s *item;
     310             : 
     311           9 :               assert (nr);
     312           9 :               nr--;
     313             : 
     314           9 :               item = (struct wait_item_s *) fdt.fds[i].opaque;
     315           9 :               assert (item);
     316           9 :               ictx = item->ctx;
     317           9 :               assert (ictx);
     318             : 
     319           9 :               LOCK (ctx->lock);
     320           9 :               if (ctx->canceled)
     321           0 :                 err = gpg_error (GPG_ERR_CANCELED);
     322           9 :               UNLOCK (ctx->lock);
     323             : 
     324           9 :               if (!err)
     325           9 :                 err = _gpgme_run_io_cb (&fdt.fds[i], 0, &local_op_err);
     326           9 :               if (err || local_op_err)
     327             :                 {
     328             :                   /* An error occurred.  Close all fds in this context,
     329             :                      and signal it.  */
     330           3 :                   _gpgme_cancel_with_err (ictx, err, local_op_err);
     331             : 
     332             :                   /* Break out of the loop, and retry the select()
     333             :                      from scratch, because now all fds should be
     334             :                      gone.  */
     335           3 :                   break;
     336             :                 }
     337             :             }
     338             :         }
     339           9 :       free (fdt.fds);
     340             : 
     341             :       /* Now some contexts might have finished successfully.  */
     342           9 :       LOCK (ctx_list_lock);
     343             :     retry:
     344          15 :       for (li = ctx_active_list; li; li = li->next)
     345             :         {
     346           6 :           gpgme_ctx_t actx = li->ctx;
     347             : 
     348           6 :           for (i = 0; i < actx->fdt.size; i++)
     349           6 :             if (actx->fdt.fds[i].fd != -1)
     350           6 :               break;
     351           6 :           if (i == actx->fdt.size)
     352             :             {
     353             :               struct gpgme_io_event_done_data data;
     354           0 :               data.err = 0;
     355           0 :               data.op_err = 0;
     356             : 
     357             :               /* FIXME: This does not perform too well.  We have to
     358             :                  release the lock because the I/O event handler
     359             :                  acquires it to remove the context from the active
     360             :                  list.  Two alternative strategies are worth
     361             :                  considering: Either implement the DONE event handler
     362             :                  here in a lock-free manner, or save a list of all
     363             :                  contexts to be released and call the DONE events
     364             :                  afterwards.  */
     365           0 :               UNLOCK (ctx_list_lock);
     366           0 :               _gpgme_engine_io_event (actx->engine, GPGME_EVENT_DONE, &data);
     367           0 :               LOCK (ctx_list_lock);
     368           0 :               goto retry;
     369             :             }
     370             :         }
     371           9 :       UNLOCK (ctx_list_lock);
     372             : 
     373             :       {
     374           9 :         gpgme_ctx_t dctx = ctx_wait (ctx, status, op_err);
     375             : 
     376           9 :         if (dctx)
     377             :           {
     378           3 :             ctx = dctx;
     379           3 :             hang = 0;
     380             :           }
     381           6 :         else if (!hang)
     382             :           {
     383           6 :             ctx = NULL;
     384           6 :             if (status)
     385           6 :               *status = 0;
     386           6 :             if (op_err)
     387           0 :               *op_err = 0;
     388             :           }
     389             :       }
     390             :     }
     391           9 :   while (hang);
     392             : 
     393           9 :   return ctx;
     394             : }
     395             : 
     396             : 
     397             : gpgme_ctx_t
     398           9 : gpgme_wait (gpgme_ctx_t ctx, gpgme_error_t *status, int hang)
     399             : {
     400           9 :   return gpgme_wait_ext (ctx, status, NULL, hang);
     401             : }

Generated by: LCOV version 1.11