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