summaryrefslogtreecommitdiffstats
path: root/libkdenetwork/libgpgme-copy/gpgme/wait-global.c
blob: 9083cb4c338c89381af88a40ab739fdb87b28d43 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
/* wait-global.c 
   Copyright (C) 2000 Werner Koch (dd9jn)
   Copyright (C) 2001, 2002, 2003, 2004, 2005 g10 Code GmbH
 
   This file is part of GPGME.
 
   GPGME 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 2.1 of
   the License, or (at your option) any later version.
   
   GPGME 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, write to the Free Software
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
   02111-1307, USA.  */

#if HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <errno.h>

#include "gpgme.h"
#include "sema.h"
#include "util.h"
#include "context.h"
#include "wait.h"
#include "priv-io.h"

/* The global event loop is used for all asynchronous operations
   (except key listing) for which no user I/O callbacks are specified.

   A context sets up its initial I/O callbacks and then sends the
   GPGME_EVENT_START event.  After that, it is added to the global
   list of active contexts.

   The gpgme_wait function contains a select() loop over all file
   descriptors in all active contexts.  If an error occurs, it closes
   all fds in that context and moves the context to the global done
   list.  Likewise, if a context has removed all I/O callbacks, it is
   moved to the global done list.

   All contexts in the global done list are eligible for being
   returned by gpgme_wait if requested by the caller.  */

/* The ctx_list_lock protects the list of active and done contexts.
   Insertion into any of these lists is only allowed when the lock is
   held.  This allows a muli-threaded program to loop over gpgme_wait
   and in parallel start asynchronous gpgme operations.

   However, the fd tables in the contexts are not protected by this
   lock.  They are only allowed to change either before the context is
   added to the active list (ie, before the start event is signalled)
   or in a callback handler.  */
DEFINE_STATIC_LOCK (ctx_list_lock);

/* A ctx_list_item is an item in the global list of active or done
   contexts.  */
struct ctx_list_item
{
  /* Every ctx_list_item is an element in a doubly linked list.  The
     list pointers are protected by the ctx_list_lock.  */
  struct ctx_list_item *next;
  struct ctx_list_item *prev;

  gpgme_ctx_t ctx;
  /* The status is set when the ctx is moved to the done list.  */
  gpgme_error_t status;
};

/* The active list contains all contexts that are in the global event
   loop, have active I/O callbacks, and have already seen the start
   event.  */
static struct ctx_list_item *ctx_active_list;

/* The done list contains all contexts that have previously been
   active but now are not active any longer, either because they
   finished successfully or an I/O callback returned an error.  The
   status field in the list item contains the error value (or 0 if
   successful).  */
static struct ctx_list_item *ctx_done_list;


/* Enter the context CTX into the active list.  */
static gpgme_error_t
ctx_active (gpgme_ctx_t ctx)
{
  struct ctx_list_item *li = malloc (sizeof (struct ctx_list_item));
  if (!li)
    return gpg_error_from_errno (errno);
  li->ctx = ctx;

  LOCK (ctx_list_lock);
  /* Add LI to active list.  */
  li->next = ctx_active_list;
  li->prev = NULL;
  if (ctx_active_list)
    ctx_active_list->prev = li;
  ctx_active_list = li;
  UNLOCK (ctx_list_lock);
  return 0;
}


/* Enter the context CTX into the done list with status STATUS.  */
static void
ctx_done (gpgme_ctx_t ctx, gpgme_error_t status)
{
  struct ctx_list_item *li;

  LOCK (ctx_list_lock);
  li = ctx_active_list;
  while (li && li->ctx != ctx)
    li = li->next;
  assert (li);

  /* Remove LI from active list.  */
  if (li->next)
    li->next->prev = li->prev;
  if (li->prev)
    li->prev->next = li->next;
  else
    ctx_active_list = li->next;

  li->status = status;

  /* Add LI to done list.  */
  li->next = ctx_done_list;
  li->prev = NULL;
  if (ctx_done_list)
    ctx_done_list->prev = li;
  ctx_done_list = li;
  UNLOCK (ctx_list_lock);
}


/* Find finished context CTX (or any context if CTX is NULL) and
   return its status in STATUS after removing it from the done list.
   If a matching context could be found, return it.  Return NULL if no
   context could be found.  */
static gpgme_ctx_t
ctx_wait (gpgme_ctx_t ctx, gpgme_error_t *status)
{
  struct ctx_list_item *li;

  LOCK (ctx_list_lock);
  li = ctx_done_list;
  if (ctx)
    {
      /* A specific context is requested.  */
      while (li && li->ctx != ctx)
	li = li->next;
    }
  if (li)
    {
      ctx = li->ctx;
      if (status)
	*status = li->status;

      /* Remove LI from done list.  */
      if (li->next)
	li->next->prev = li->prev;
      if (li->prev)
	li->prev->next = li->next;
      else
	ctx_done_list = li->next;
      free (li);
    }
  else
    ctx = NULL;
  UNLOCK (ctx_list_lock);
  return ctx;
}


/* Internal I/O callback functions.  */

/* The add_io_cb and remove_io_cb handlers are shared with the private
   event loops.  */

void
_gpgme_wait_global_event_cb (void *data, gpgme_event_io_t type,
			     void *type_data)
{
  gpgme_ctx_t ctx = (gpgme_ctx_t) data;

  assert (ctx);

  switch (type)
    {
    case GPGME_EVENT_START:
      {
	gpgme_error_t err = ctx_active (ctx);

	if (err)
	  {
	    /* An error occured.  Close all fds in this context, and
	       send the error in a done event.  */
	    unsigned int idx;
	    
	    for (idx = 0; idx <= ctx->fdt.size; idx++)
	      if (ctx->fdt.fds[idx].fd != -1)
		_gpgme_io_close (ctx->fdt.fds[idx].fd);
	    _gpgme_engine_io_event (ctx->engine, GPGME_EVENT_DONE, &err);
	  }
      }
      break;

    case GPGME_EVENT_DONE:
      {
	gpgme_error_t *errp = (gpgme_error_t *) type_data;
	assert (errp);
	ctx_done (ctx, *errp);
      }
      break;

    case GPGME_EVENT_NEXT_KEY:
      assert (!"Unexpected event GPGME_EVENT_NEXT_KEY");
      break;

    case GPGME_EVENT_NEXT_TRUSTITEM:
      assert (!"Unexpected event GPGME_EVENT_NEXT_TRUSTITEM");
      break;

    default:
      assert (!"Unexpected event");
      break;
    }
}



/* Perform asynchronous operations in the global event loop (ie, any
   asynchronous operation except key listing and trustitem listing
   operations).  If CTX is not a null pointer, the function will
   return if the asynchronous operation in the context CTX finished.
   Otherwise the function will return if any asynchronous operation
   finished.  If HANG is zero, the function will not block for a long
   time.  Otherwise the function does not return until an operation
   matching CTX finished.

   If a matching context finished, it is returned, and *STATUS is set
   to the error value of the operation in that context.  Otherwise, if
   the timeout expires, NULL is returned and *STATUS is 0.  If an
   error occurs, NULL is returned and *STATUS is set to the error
   value.  */
gpgme_ctx_t
gpgme_wait (gpgme_ctx_t ctx, gpgme_error_t *status, int hang)
{
  do
    {
      unsigned int i = 0;
      struct ctx_list_item *li;
      struct fd_table fdt;
      int nr;

      /* Collect the active file descriptors.  */
      LOCK (ctx_list_lock);
      for (li = ctx_active_list; li; li = li->next)
	i += li->ctx->fdt.size;
      fdt.fds = malloc (i * sizeof (struct io_select_fd_s));
      if (!fdt.fds)
	{
	  int saved_errno = errno;
	  UNLOCK (ctx_list_lock);
	  if (status)
	    *status = gpg_error_from_errno (saved_errno);
	  return NULL;
	}
      fdt.size = i;
      i = 0;
      for (li = ctx_active_list; li; li = li->next)
	{
	  memcpy (&fdt.fds[i], li->ctx->fdt.fds,
		  li->ctx->fdt.size * sizeof (struct io_select_fd_s));
	  i += li->ctx->fdt.size;
	}
      UNLOCK (ctx_list_lock);

      nr = _gpgme_io_select (fdt.fds, fdt.size, 0);
      if (nr < 0)
	{
	  int saved_errno = errno;
	  free (fdt.fds);
	  if (status)
	    *status = gpg_error_from_errno (saved_errno);
	  return NULL;
	}

      for (i = 0; i < fdt.size && nr; i++)
	{
	  if (fdt.fds[i].fd != -1 && fdt.fds[i].signaled)
	    {
	      gpgme_ctx_t ictx;
	      gpgme_error_t err;
	      struct wait_item_s *item;
	      
	      assert (nr);
	      nr--;
	      
	      item = (struct wait_item_s *) fdt.fds[i].opaque;
	      assert (item);
	      ictx = item->ctx;
	      assert (ictx);

	      err = _gpgme_run_io_cb (&fdt.fds[i], 0);
	      if (err)
		{
		  /* An error occured.  Close all fds in this context,
		     and signal it.  */
		  unsigned int idx;
	    
		  for (idx = 0; idx < ictx->fdt.size; idx++)
		    if (ictx->fdt.fds[idx].fd != -1)
		      _gpgme_io_close (ictx->fdt.fds[idx].fd);
		  _gpgme_engine_io_event (ictx->engine, GPGME_EVENT_DONE,
					  &err);

		  /* Break out of the loop, and retry the select()
		     from scratch, because now all fds should be
		     gone.  */
		  break;
		}
	    }
	}
      free (fdt.fds);

      /* Now some contexts might have finished successfully.  */
      LOCK (ctx_list_lock);
    retry:
      for (li = ctx_active_list; li; li = li->next)
	{
	  gpgme_ctx_t actx = li->ctx;

	  for (i = 0; i < actx->fdt.size; i++)
	    if (actx->fdt.fds[i].fd != -1)
	      break;
	  if (i == actx->fdt.size)
	    {
	      gpgme_error_t err = 0;

	      /* FIXME: This does not perform too well.  We have to
		 release the lock because the I/O event handler
		 acquires it to remove the context from the active
		 list.  Two alternative strategies are worth
		 considering: Either implement the DONE event handler
		 here in a lock-free manner, or save a list of all
		 contexts to be released and call the DONE events
		 afterwards.  */
	      UNLOCK (ctx_list_lock);
	      _gpgme_engine_io_event (actx->engine, GPGME_EVENT_DONE, &err);
	      LOCK (ctx_list_lock);
	      goto retry;
	    }
	}
      UNLOCK (ctx_list_lock);

      {
	gpgme_ctx_t dctx = ctx_wait (ctx, status);

	if (dctx)
	  {
	    ctx = dctx;
	    hang = 0;
	  }
	else if (!hang)
	  {
	    ctx = NULL;
	    if (status)
	      *status = 0;
	  }
      }
    }
  while (hang);

  return ctx;
}