2003-01-29 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
28 #include "gpgme.h"
29 #include "sema.h"
30 #include "util.h"
31 #include "context.h"
32 #include "wait.h"
33 #include "io.h"
34
35 /* The global event loop is used for all asynchronous operations
36    (except key listing) for which no user I/O callbacks are specified.
37
38    A context sets up its initial I/O callbacks and then sends the
39    GPGME_EVENT_START event.  After that, it is added to the global
40    list of active contexts.
41
42    The gpgme_wait function contains a select() loop over all file
43    descriptors in all active contexts.  If an error occurs, it closes
44    all fds in that context and moves the context to the global done
45    list.  Likewise, if a context has removed all I/O callbacks, it is
46    moved to the global done list.
47
48    All contexts in the global done list are eligible for being
49    returned by gpgme_wait if requested by the caller.  */
50
51 /* The ctx_list_lock protects the list of active and done contexts.
52    Insertion into any of these lists is only allowed when the lock is
53    held.  This allows a muli-threaded program to loop over gpgme_wait
54    and in parallel start asynchronous gpgme operations.
55
56    However, the fd tables in the contexts are not protected by this
57    lock.  They are only allowed to change either before the context is
58    added to the active list (ie, before the start event is signalled)
59    or in a callback handler.  */
60 DEFINE_STATIC_LOCK (ctx_list_lock);
61
62 /* A ctx_list_item is an item in the global list of active or done
63    contexts.  */
64 struct ctx_list_item
65 {
66   /* Every ctx_list_item is an element in a doubly linked list.  The
67      list pointers are protected by the ctx_list_lock.  */
68   struct ctx_list_item *next;
69   struct ctx_list_item *prev;
70
71   GpgmeCtx ctx;
72   /* The status is set when the ctx is moved to the done list.  */
73   GpgmeError status;
74 };
75
76 /* The active list contains all contexts that are in the global event
77    loop, have active I/O callbacks, and have already seen the start
78    event.  */
79 static struct ctx_list_item *ctx_active_list;
80
81 /* The done list contains all contexts that have previously been
82 active but now are not active any longer, either because they finished
83 successfully or an I/O callback returned an error.  The status field
84 in the list item contains the error value (or 0 if successful).  */
85 static struct ctx_list_item *ctx_done_list;
86
87 \f
88 /* Enter the context CTX into the active list.  */
89 static GpgmeError
90 ctx_active (GpgmeCtx ctx)
91 {
92   struct ctx_list_item *li = malloc (sizeof (struct ctx_list_item));
93   if (!li)
94     return GPGME_Out_Of_Core;
95   li->ctx = ctx;
96
97   LOCK (ctx_list_lock);
98   /* Add LI to active list.  */
99   li->next = ctx_active_list;
100   li->prev = NULL;
101   if (ctx_active_list)
102     ctx_active_list->prev = li;
103   ctx_active_list = li;
104   UNLOCK (ctx_list_lock);
105   return 0;
106 }
107
108
109 /* Enter the context CTX into the done list with status STATUS.  */
110 static void
111 ctx_done (GpgmeCtx ctx, GpgmeError status)
112 {
113   struct ctx_list_item *li;
114
115   LOCK (ctx_list_lock);
116   li = ctx_active_list;
117   while (li && li->ctx != ctx)
118     li = li->next;
119   assert (li);
120
121   /* Remove LI from active list.  */
122   if (li->next)
123     li->next->prev = li->prev;
124   if (li->prev)
125     li->prev->next = li->next;
126   else
127     ctx_active_list = li->next;
128
129   li->status = status;
130
131   /* Add LI to done list.  */
132   li->next = ctx_done_list;
133   li->prev = NULL;
134   if (ctx_done_list)
135     ctx_done_list->prev = li;
136   ctx_done_list = li;
137   UNLOCK (ctx_list_lock);
138 }
139
140
141 /* Find finished context CTX (or any context if CTX is NULL) and
142    return its status in STATUS after removing it from the done list.
143    If a matching context could be found, return it.  Return NULL if no
144    context could be found.  */
145 static GpgmeCtx
146 ctx_wait (GpgmeCtx ctx, GpgmeError *status)
147 {
148   struct ctx_list_item *li;
149
150   LOCK (ctx_list_lock);
151   li = ctx_done_list;
152   if (ctx)
153     {
154       /* A specific context is requested.  */
155       while (li && li->ctx != ctx)
156         li = li->next;
157     }
158   if (li)
159     {
160       ctx = li->ctx;
161       if (status)
162         *status = li->status;
163
164       /* Remove LI from done list.  */
165       if (li->next)
166         li->next->prev = li->prev;
167       if (li->prev)
168         li->prev->next = li->next;
169       else
170         ctx_done_list = li->next;
171       free (li);
172     }
173   else
174     ctx = NULL;
175   UNLOCK (ctx_list_lock);
176   return ctx;
177 }
178
179 \f
180 /* Internal I/O callback functions.  */
181
182 /* The add_io_cb and remove_io_cb handlers are shared with the private
183    event loops.  */
184
185 void
186 _gpgme_wait_global_event_cb (void *data, GpgmeEventIO type, void *type_data)
187 {
188   GpgmeCtx ctx = (GpgmeCtx) data;
189
190   assert (ctx);
191
192   switch (type)
193     {
194     case GPGME_EVENT_START:
195       {
196         GpgmeError err = ctx_active (ctx);
197
198         if (err)
199           {
200             /* An error occured.  Close all fds in this context, and
201                send the error in a done event.  */
202             int idx;
203             
204             for (idx = 0; idx <= ctx->fdt.size; idx++)
205               if (ctx->fdt.fds[idx].fd != -1)
206                 _gpgme_io_close (ctx->fdt.fds[idx].fd);
207             _gpgme_engine_io_event (ctx->engine, GPGME_EVENT_DONE, &err);
208           }
209       }
210       break;
211
212     case GPGME_EVENT_DONE:
213       {
214         GpgmeError *errp = (GpgmeError *) type_data;
215         assert (errp);
216         ctx_done (ctx, *errp);
217       }
218       break;
219
220     case GPGME_EVENT_NEXT_KEY:
221       assert (!"Unexpected event GPGME_EVENT_NEXT_KEY");
222       break;
223
224     case GPGME_EVENT_NEXT_TRUSTITEM:
225       assert (!"Unexpected event GPGME_EVENT_NEXT_TRUSTITEM");
226       break;
227
228     default:
229       assert (!"Unexpected event");
230       break;
231     }
232 }
233
234
235 \f
236 /* Perform asynchronous operations in the global event loop (ie, any
237    asynchronous operation except key listing and trustitem listing
238    operations).  If CTX is not a null pointer, the function will
239    return if the asynchronous operation in the context CTX finished.
240    Otherwise the function will return if any asynchronous operation
241    finished.  If HANG is zero, the function will not block for a long
242    time.  Otherwise the function does not return until an operation
243    matching CTX finished.
244
245    If a matching context finished, it is returned, and *STATUS is set
246    to the error value of the operation in that context.  Otherwise, if
247    the timeout expires, NULL is returned and *STATUS is 0.  If an
248    error occurs, NULL is returned and *STATUS is set to the error
249    value.  */
250 GpgmeCtx
251 gpgme_wait (GpgmeCtx ctx, GpgmeError *status, int hang)
252 {
253   do
254     {
255       int i = 0;
256       struct ctx_list_item *li;
257       struct fd_table fdt;
258       int nr;
259
260       /* Collect the active file descriptors.  */
261       LOCK (ctx_list_lock);
262       for (li = ctx_active_list; li; li = li->next)
263         i += li->ctx->fdt.size;
264       fdt.fds = malloc (i * sizeof (struct io_select_fd_s));
265       if (!fdt.fds)
266         {
267           UNLOCK (ctx_list_lock);
268           if (status)
269             *status = GPGME_Out_Of_Core;
270           return NULL;
271         }
272       fdt.size = i;
273       i = 0;
274       for (li = ctx_active_list; li; li = li->next)
275         {
276           memcpy (&fdt.fds[i], li->ctx->fdt.fds,
277                   li->ctx->fdt.size * sizeof (struct io_select_fd_s));
278           i += li->ctx->fdt.size;
279         }
280       UNLOCK (ctx_list_lock);
281
282       nr = _gpgme_io_select (fdt.fds, fdt.size, 0);
283       if (nr < 0)
284         {
285           free (fdt.fds);
286           if (status)
287             *status = GPGME_File_Error;
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               GpgmeCtx ictx;
296               GpgmeError err;
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               err = item->handler (item->handler_value, fdt.fds[i].fd);
308               if (!err && ictx->cancel)
309                 err = GPGME_Canceled;
310               if (err)
311                 {
312                   /* An error occured.  Close all fds in this context,
313                      and signal it.  */
314                   int idx;
315             
316                   for (idx = 0; idx < ictx->fdt.size; idx++)
317                     if (ictx->fdt.fds[idx].fd != -1)
318                       _gpgme_io_close (ictx->fdt.fds[idx].fd);
319                   _gpgme_engine_io_event (ictx->engine, GPGME_EVENT_DONE, &err);
320                 }
321             }
322         }
323       free (fdt.fds);
324
325       /* Now some contexts might have finished successfully.  */
326       LOCK (ctx_list_lock);
327       for (li = ctx_active_list; li; li = li->next)
328         {
329           for (i = 0; i < ctx->fdt.size; i++)
330             if (ctx->fdt.fds[i].fd != -1)
331               break;
332           if (i == ctx->fdt.size)
333             {
334               GpgmeError err = 0;
335               _gpgme_engine_io_event (ctx->engine, GPGME_EVENT_DONE, &err);
336             }
337         }
338       UNLOCK (ctx_list_lock);
339
340       {
341         GpgmeCtx dctx = ctx_wait (ctx, status);
342
343         if (dctx)
344           {
345             ctx = dctx;
346             hang = 0;
347             ctx->pending = 0;
348           }
349       }
350     }
351   while (hang);
352
353   return ctx;
354 }
355
356