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