2005-09-12 Marcus Brinkmann <marcus@g10code.de>
[gpgme.git] / gpgme / 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
37 /* The global event loop is used for all asynchronous operations
38    (except key listing) for which no user I/O callbacks are specified.
39
40    A context sets up its initial I/O callbacks and then sends the
41    GPGME_EVENT_START event.  After that, it is added to the global
42    list of active contexts.
43
44    The gpgme_wait function contains a select() loop over all file
45    descriptors in all active contexts.  If an error occurs, it closes
46    all fds in that context and moves the context to the global done
47    list.  Likewise, if a context has removed all I/O callbacks, it is
48    moved to the global done list.
49
50    All contexts in the global done list are eligible for being
51    returned by gpgme_wait if requested by the caller.  */
52
53 /* The ctx_list_lock protects the list of active and done contexts.
54    Insertion into any of these lists is only allowed when the lock is
55    held.  This allows a muli-threaded program to loop over gpgme_wait
56    and in parallel start asynchronous gpgme operations.
57
58    However, the fd tables in the contexts are not protected by this
59    lock.  They are only allowed to change either before the context is
60    added to the active list (ie, before the start event is signalled)
61    or in a callback handler.  */
62 DEFINE_STATIC_LOCK (ctx_list_lock);
63
64 /* A ctx_list_item is an item in the global list of active or done
65    contexts.  */
66 struct ctx_list_item
67 {
68   /* Every ctx_list_item is an element in a doubly linked list.  The
69      list pointers are protected by the ctx_list_lock.  */
70   struct ctx_list_item *next;
71   struct ctx_list_item *prev;
72
73   gpgme_ctx_t ctx;
74   /* The status is set when the ctx is moved to the done list.  */
75   gpgme_error_t status;
76 };
77
78 /* The active list contains all contexts that are in the global event
79    loop, have active I/O callbacks, and have already seen the start
80    event.  */
81 static struct ctx_list_item *ctx_active_list;
82
83 /* The done list contains all contexts that have previously been
84    active but now are not active any longer, either because they
85    finished successfully or an I/O callback returned an error.  The
86    status field in the list item contains the error value (or 0 if
87    successful).  */
88 static struct ctx_list_item *ctx_done_list;
89
90 \f
91 /* Enter the context CTX into the active list.  */
92 static gpgme_error_t
93 ctx_active (gpgme_ctx_t ctx)
94 {
95   struct ctx_list_item *li = malloc (sizeof (struct ctx_list_item));
96   if (!li)
97     return gpg_error_from_errno (errno);
98   li->ctx = ctx;
99
100   LOCK (ctx_list_lock);
101   /* Add LI to active list.  */
102   li->next = ctx_active_list;
103   li->prev = NULL;
104   if (ctx_active_list)
105     ctx_active_list->prev = li;
106   ctx_active_list = li;
107   UNLOCK (ctx_list_lock);
108   return 0;
109 }
110
111
112 /* Enter the context CTX into the done list with status STATUS.  */
113 static void
114 ctx_done (gpgme_ctx_t ctx, gpgme_error_t status)
115 {
116   struct ctx_list_item *li;
117
118   LOCK (ctx_list_lock);
119   li = ctx_active_list;
120   while (li && li->ctx != ctx)
121     li = li->next;
122   assert (li);
123
124   /* Remove LI from active list.  */
125   if (li->next)
126     li->next->prev = li->prev;
127   if (li->prev)
128     li->prev->next = li->next;
129   else
130     ctx_active_list = li->next;
131
132   li->status = status;
133
134   /* Add LI to done list.  */
135   li->next = ctx_done_list;
136   li->prev = NULL;
137   if (ctx_done_list)
138     ctx_done_list->prev = li;
139   ctx_done_list = li;
140   UNLOCK (ctx_list_lock);
141 }
142
143
144 /* Find finished context CTX (or any context if CTX is NULL) and
145    return its status in STATUS after removing it from the done list.
146    If a matching context could be found, return it.  Return NULL if no
147    context could be found.  */
148 static gpgme_ctx_t
149 ctx_wait (gpgme_ctx_t ctx, gpgme_error_t *status)
150 {
151   struct ctx_list_item *li;
152
153   LOCK (ctx_list_lock);
154   li = ctx_done_list;
155   if (ctx)
156     {
157       /* A specific context is requested.  */
158       while (li && li->ctx != ctx)
159         li = li->next;
160     }
161   if (li)
162     {
163       ctx = li->ctx;
164       if (status)
165         *status = li->status;
166
167       /* Remove LI from done list.  */
168       if (li->next)
169         li->next->prev = li->prev;
170       if (li->prev)
171         li->prev->next = li->next;
172       else
173         ctx_done_list = li->next;
174       free (li);
175     }
176   else
177     ctx = NULL;
178   UNLOCK (ctx_list_lock);
179   return ctx;
180 }
181
182 \f
183 /* Internal I/O callback functions.  */
184
185 /* The add_io_cb and remove_io_cb handlers are shared with the private
186    event loops.  */
187
188 void
189 _gpgme_wait_global_event_cb (void *data, gpgme_event_io_t type,
190                              void *type_data)
191 {
192   gpgme_ctx_t ctx = (gpgme_ctx_t) data;
193
194   assert (ctx);
195
196   switch (type)
197     {
198     case GPGME_EVENT_START:
199       {
200         gpgme_error_t err = ctx_active (ctx);
201
202         if (err)
203           {
204             /* An error occured.  Close all fds in this context, and
205                send the error in a done event.  */
206             unsigned int idx;
207             
208             for (idx = 0; idx <= ctx->fdt.size; idx++)
209               if (ctx->fdt.fds[idx].fd != -1)
210                 _gpgme_io_close (ctx->fdt.fds[idx].fd);
211             _gpgme_engine_io_event (ctx->engine, GPGME_EVENT_DONE, &err);
212           }
213       }
214       break;
215
216     case GPGME_EVENT_DONE:
217       {
218         gpgme_error_t *errp = (gpgme_error_t *) type_data;
219         assert (errp);
220         ctx_done (ctx, *errp);
221       }
222       break;
223
224     case GPGME_EVENT_NEXT_KEY:
225       assert (!"Unexpected event GPGME_EVENT_NEXT_KEY");
226       break;
227
228     case GPGME_EVENT_NEXT_TRUSTITEM:
229       assert (!"Unexpected event GPGME_EVENT_NEXT_TRUSTITEM");
230       break;
231
232     default:
233       assert (!"Unexpected event");
234       break;
235     }
236 }
237
238
239 \f
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 gpgme_wait (gpgme_ctx_t ctx, gpgme_error_t *status, 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           return NULL;
276         }
277       fdt.size = i;
278       i = 0;
279       for (li = ctx_active_list; li; li = li->next)
280         {
281           memcpy (&fdt.fds[i], li->ctx->fdt.fds,
282                   li->ctx->fdt.size * sizeof (struct io_select_fd_s));
283           i += li->ctx->fdt.size;
284         }
285       UNLOCK (ctx_list_lock);
286
287       nr = _gpgme_io_select (fdt.fds, fdt.size, 0);
288       if (nr < 0)
289         {
290           int saved_errno = errno;
291           free (fdt.fds);
292           if (status)
293             *status = gpg_error_from_errno (saved_errno);
294           return NULL;
295         }
296
297       for (i = 0; i < fdt.size && nr; i++)
298         {
299           if (fdt.fds[i].fd != -1 && fdt.fds[i].signaled)
300             {
301               gpgme_ctx_t ictx;
302               gpgme_error_t err;
303               struct wait_item_s *item;
304               
305               assert (nr);
306               nr--;
307               
308               item = (struct wait_item_s *) fdt.fds[i].opaque;
309               assert (item);
310               ictx = item->ctx;
311               assert (ictx);
312
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                   unsigned int idx;
319             
320                   for (idx = 0; idx < ictx->fdt.size; idx++)
321                     if (ictx->fdt.fds[idx].fd != -1)
322                       _gpgme_io_close (ictx->fdt.fds[idx].fd);
323                   _gpgme_engine_io_event (ictx->engine, GPGME_EVENT_DONE,
324                                           &err);
325
326                   /* Break out of the loop, and retry the select()
327                      from scratch, because now all fds should be
328                      gone.  */
329                   break;
330                 }
331             }
332         }
333       free (fdt.fds);
334
335       /* Now some contexts might have finished successfully.  */
336       LOCK (ctx_list_lock);
337       for (li = ctx_active_list; li; li = li->next)
338         {
339           gpgme_ctx_t actx = li->ctx;
340
341           for (i = 0; i < actx->fdt.size; i++)
342             if (actx->fdt.fds[i].fd != -1)
343               break;
344           if (i == actx->fdt.size)
345             {
346               gpgme_error_t err = 0;
347               _gpgme_engine_io_event (actx->engine, GPGME_EVENT_DONE, &err);
348             }
349         }
350       UNLOCK (ctx_list_lock);
351
352       {
353         gpgme_ctx_t dctx = ctx_wait (ctx, status);
354
355         if (dctx)
356           {
357             ctx = dctx;
358             hang = 0;
359           }
360         else if (!hang)
361           {
362             ctx = NULL;
363             if (status)
364               *status = 0;
365           }
366       }
367     }
368   while (hang);
369
370   return ctx;
371 }