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