2003-07-31 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 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 General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (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    General Public License for more details.
16  
17    You should have received a copy of the GNU General Public License
18    along with GPGME; if not, write to the Free Software Foundation,
19    Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
20
21 #if HAVE_CONFIG_H
22 #include <config.h>
23 #endif
24 #include <stdlib.h>
25 #include <assert.h>
26 #include <string.h>
27 #include <errno.h>
28
29 #include "gpgme.h"
30 #include "sema.h"
31 #include "util.h"
32 #include "context.h"
33 #include "wait.h"
34 #include "io.h"
35
36 /* The global event loop is used for all asynchronous operations
37    (except key listing) for which no user I/O callbacks are specified.
38
39    A context sets up its initial I/O callbacks and then sends the
40    GPGME_EVENT_START event.  After that, it is added to the global
41    list of active contexts.
42
43    The gpgme_wait function contains a select() loop over all file
44    descriptors in all active contexts.  If an error occurs, it closes
45    all fds in that context and moves the context to the global done
46    list.  Likewise, if a context has removed all I/O callbacks, it is
47    moved to the global done list.
48
49    All contexts in the global done list are eligible for being
50    returned by gpgme_wait if requested by the caller.  */
51
52 /* The ctx_list_lock protects the list of active and done contexts.
53    Insertion into any of these lists is only allowed when the lock is
54    held.  This allows a muli-threaded program to loop over gpgme_wait
55    and in parallel start asynchronous gpgme operations.
56
57    However, the fd tables in the contexts are not protected by this
58    lock.  They are only allowed to change either before the context is
59    added to the active list (ie, before the start event is signalled)
60    or in a callback handler.  */
61 DEFINE_STATIC_LOCK (ctx_list_lock);
62
63 /* A ctx_list_item is an item in the global list of active or done
64    contexts.  */
65 struct ctx_list_item
66 {
67   /* Every ctx_list_item is an element in a doubly linked list.  The
68      list pointers are protected by the ctx_list_lock.  */
69   struct ctx_list_item *next;
70   struct ctx_list_item *prev;
71
72   gpgme_ctx_t ctx;
73   /* The status is set when the ctx is moved to the done list.  */
74   gpgme_error_t status;
75 };
76
77 /* The active list contains all contexts that are in the global event
78    loop, have active I/O callbacks, and have already seen the start
79    event.  */
80 static struct ctx_list_item *ctx_active_list;
81
82 /* The done list contains all contexts that have previously been
83    active but now are not active any longer, either because they
84    finished successfully or an I/O callback returned an error.  The
85    status field in the list item contains the error value (or 0 if
86    successful).  */
87 static struct ctx_list_item *ctx_done_list;
88
89 \f
90 /* Enter the context CTX into the active list.  */
91 static gpgme_error_t
92 ctx_active (gpgme_ctx_t ctx)
93 {
94   struct ctx_list_item *li = malloc (sizeof (struct ctx_list_item));
95   if (!li)
96     return gpg_error_from_errno (errno);
97   li->ctx = ctx;
98
99   LOCK (ctx_list_lock);
100   /* Add LI to active list.  */
101   li->next = ctx_active_list;
102   li->prev = NULL;
103   if (ctx_active_list)
104     ctx_active_list->prev = li;
105   ctx_active_list = li;
106   UNLOCK (ctx_list_lock);
107   return 0;
108 }
109
110
111 /* Enter the context CTX into the done list with status STATUS.  */
112 static void
113 ctx_done (gpgme_ctx_t ctx, gpgme_error_t status)
114 {
115   struct ctx_list_item *li;
116
117   LOCK (ctx_list_lock);
118   li = ctx_active_list;
119   while (li && li->ctx != ctx)
120     li = li->next;
121   assert (li);
122
123   /* Remove LI from active list.  */
124   if (li->next)
125     li->next->prev = li->prev;
126   if (li->prev)
127     li->prev->next = li->next;
128   else
129     ctx_active_list = li->next;
130
131   li->status = status;
132
133   /* Add LI to done list.  */
134   li->next = ctx_done_list;
135   li->prev = NULL;
136   if (ctx_done_list)
137     ctx_done_list->prev = li;
138   ctx_done_list = li;
139   UNLOCK (ctx_list_lock);
140 }
141
142
143 /* Find finished context CTX (or any context if CTX is NULL) and
144    return its status in STATUS after removing it from the done list.
145    If a matching context could be found, return it.  Return NULL if no
146    context could be found.  */
147 static gpgme_ctx_t
148 ctx_wait (gpgme_ctx_t ctx, gpgme_error_t *status)
149 {
150   struct ctx_list_item *li;
151
152   LOCK (ctx_list_lock);
153   li = ctx_done_list;
154   if (ctx)
155     {
156       /* A specific context is requested.  */
157       while (li && li->ctx != ctx)
158         li = li->next;
159     }
160   if (li)
161     {
162       ctx = li->ctx;
163       if (status)
164         *status = li->status;
165
166       /* Remove LI from done list.  */
167       if (li->next)
168         li->next->prev = li->prev;
169       if (li->prev)
170         li->prev->next = li->next;
171       else
172         ctx_done_list = li->next;
173       free (li);
174     }
175   else
176     ctx = NULL;
177   UNLOCK (ctx_list_lock);
178   return ctx;
179 }
180
181 \f
182 /* Internal I/O callback functions.  */
183
184 /* The add_io_cb and remove_io_cb handlers are shared with the private
185    event loops.  */
186
187 void
188 _gpgme_wait_global_event_cb (void *data, gpgme_event_io_t type,
189                              void *type_data)
190 {
191   gpgme_ctx_t ctx = (gpgme_ctx_t) data;
192
193   assert (ctx);
194
195   switch (type)
196     {
197     case GPGME_EVENT_START:
198       {
199         gpgme_error_t err = ctx_active (ctx);
200
201         if (err)
202           {
203             /* An error occured.  Close all fds in this context, and
204                send the error in a done event.  */
205             unsigned int idx;
206             
207             for (idx = 0; idx <= ctx->fdt.size; idx++)
208               if (ctx->fdt.fds[idx].fd != -1)
209                 _gpgme_io_close (ctx->fdt.fds[idx].fd);
210             _gpgme_engine_io_event (ctx->engine, GPGME_EVENT_DONE, &err);
211           }
212       }
213       break;
214
215     case GPGME_EVENT_DONE:
216       {
217         gpgme_error_t *errp = (gpgme_error_t *) type_data;
218         assert (errp);
219         ctx_done (ctx, *errp);
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 (gpgme_ctx_t ctx, gpgme_error_t *status, int hang)
255 {
256   do
257     {
258       unsigned int i = 0;
259       struct ctx_list_item *li;
260       struct fd_table fdt;
261       int nr;
262
263       /* Collect the active file descriptors.  */
264       LOCK (ctx_list_lock);
265       for (li = ctx_active_list; li; li = li->next)
266         i += li->ctx->fdt.size;
267       fdt.fds = malloc (i * sizeof (struct io_select_fd_s));
268       if (!fdt.fds)
269         {
270           int saved_errno = errno;
271           UNLOCK (ctx_list_lock);
272           if (status)
273             *status = gpg_error_from_errno (saved_errno);
274           return NULL;
275         }
276       fdt.size = i;
277       i = 0;
278       for (li = ctx_active_list; li; li = li->next)
279         {
280           memcpy (&fdt.fds[i], li->ctx->fdt.fds,
281                   li->ctx->fdt.size * sizeof (struct io_select_fd_s));
282           i += li->ctx->fdt.size;
283         }
284       UNLOCK (ctx_list_lock);
285
286       nr = _gpgme_io_select (fdt.fds, fdt.size, 0);
287       if (nr < 0)
288         {
289           int saved_errno = errno;
290           free (fdt.fds);
291           if (status)
292             *status = gpg_error_from_errno (saved_errno);
293           return NULL;
294         }
295
296       for (i = 0; i < fdt.size && nr; i++)
297         {
298           if (fdt.fds[i].fd != -1 && fdt.fds[i].signaled)
299             {
300               gpgme_ctx_t ictx;
301               gpgme_error_t err;
302               struct wait_item_s *item;
303               
304               assert (nr);
305               nr--;
306               
307               item = (struct wait_item_s *) fdt.fds[i].opaque;
308               assert (item);
309               ictx = item->ctx;
310               assert (ictx);
311
312               err = item->handler (item->handler_value, fdt.fds[i].fd);
313               if (err)
314                 {
315                   /* An error occured.  Close all fds in this context,
316                      and signal it.  */
317                   unsigned int idx;
318             
319                   for (idx = 0; idx < ictx->fdt.size; idx++)
320                     if (ictx->fdt.fds[idx].fd != -1)
321                       _gpgme_io_close (ictx->fdt.fds[idx].fd);
322                   _gpgme_engine_io_event (ictx->engine, GPGME_EVENT_DONE,
323                                           &err);
324                 }
325             }
326         }
327       free (fdt.fds);
328
329       /* Now some contexts might have finished successfully.  */
330       LOCK (ctx_list_lock);
331       for (li = ctx_active_list; li; li = li->next)
332         {
333           for (i = 0; i < ctx->fdt.size; i++)
334             if (ctx->fdt.fds[i].fd != -1)
335               break;
336           if (i == ctx->fdt.size)
337             {
338               gpgme_error_t err = 0;
339               _gpgme_engine_io_event (ctx->engine, GPGME_EVENT_DONE, &err);
340             }
341         }
342       UNLOCK (ctx_list_lock);
343
344       {
345         gpgme_ctx_t dctx = ctx_wait (ctx, status);
346
347         if (dctx)
348           {
349             ctx = dctx;
350             hang = 0;
351           }
352       }
353     }
354   while (hang);
355
356   return ctx;
357 }