Fix comment indentation.
[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
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 GpgmeError
91 ctx_active (GpgmeCtx 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 (GpgmeCtx ctx, GpgmeError 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 GpgmeCtx
147 ctx_wait (GpgmeCtx ctx, GpgmeError *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, GpgmeEventIO type, void *type_data)
188 {
189   GpgmeCtx ctx = (GpgmeCtx) data;
190
191   assert (ctx);
192
193   switch (type)
194     {
195     case GPGME_EVENT_START:
196       {
197         GpgmeError err = ctx_active (ctx);
198
199         if (err)
200           {
201             /* An error occured.  Close all fds in this context, and
202                send the error in a done event.  */
203             int idx;
204             
205             for (idx = 0; idx <= ctx->fdt.size; idx++)
206               if (ctx->fdt.fds[idx].fd != -1)
207                 _gpgme_io_close (ctx->fdt.fds[idx].fd);
208             _gpgme_engine_io_event (ctx->engine, GPGME_EVENT_DONE, &err);
209           }
210       }
211       break;
212
213     case GPGME_EVENT_DONE:
214       {
215         GpgmeError *errp = (GpgmeError *) type_data;
216         assert (errp);
217         ctx_done (ctx, *errp);
218       }
219       break;
220
221     case GPGME_EVENT_NEXT_KEY:
222       assert (!"Unexpected event GPGME_EVENT_NEXT_KEY");
223       break;
224
225     case GPGME_EVENT_NEXT_TRUSTITEM:
226       assert (!"Unexpected event GPGME_EVENT_NEXT_TRUSTITEM");
227       break;
228
229     default:
230       assert (!"Unexpected event");
231       break;
232     }
233 }
234
235
236 \f
237 /* Perform asynchronous operations in the global event loop (ie, any
238    asynchronous operation except key listing and trustitem listing
239    operations).  If CTX is not a null pointer, the function will
240    return if the asynchronous operation in the context CTX finished.
241    Otherwise the function will return if any asynchronous operation
242    finished.  If HANG is zero, the function will not block for a long
243    time.  Otherwise the function does not return until an operation
244    matching CTX finished.
245
246    If a matching context finished, it is returned, and *STATUS is set
247    to the error value of the operation in that context.  Otherwise, if
248    the timeout expires, NULL is returned and *STATUS is 0.  If an
249    error occurs, NULL is returned and *STATUS is set to the error
250    value.  */
251 GpgmeCtx
252 gpgme_wait (GpgmeCtx ctx, GpgmeError *status, int hang)
253 {
254   do
255     {
256       int i = 0;
257       struct ctx_list_item *li;
258       struct fd_table fdt;
259       int nr;
260
261       /* Collect the active file descriptors.  */
262       LOCK (ctx_list_lock);
263       for (li = ctx_active_list; li; li = li->next)
264         i += li->ctx->fdt.size;
265       fdt.fds = malloc (i * sizeof (struct io_select_fd_s));
266       if (!fdt.fds)
267         {
268           UNLOCK (ctx_list_lock);
269           if (status)
270             *status = GPGME_Out_Of_Core;
271           return NULL;
272         }
273       fdt.size = i;
274       i = 0;
275       for (li = ctx_active_list; li; li = li->next)
276         {
277           memcpy (&fdt.fds[i], li->ctx->fdt.fds,
278                   li->ctx->fdt.size * sizeof (struct io_select_fd_s));
279           i += li->ctx->fdt.size;
280         }
281       UNLOCK (ctx_list_lock);
282
283       nr = _gpgme_io_select (fdt.fds, fdt.size, 0);
284       if (nr < 0)
285         {
286           free (fdt.fds);
287           if (status)
288             *status = GPGME_File_Error;
289           return NULL;
290         }
291
292       for (i = 0; i < fdt.size && nr; i++)
293         {
294           if (fdt.fds[i].fd != -1 && fdt.fds[i].signaled)
295             {
296               GpgmeCtx ictx;
297               GpgmeError err;
298               struct wait_item_s *item;
299               
300               assert (nr);
301               nr--;
302               
303               item = (struct wait_item_s *) fdt.fds[i].opaque;
304               assert (item);
305               ictx = item->ctx;
306               assert (ictx);
307
308               err = item->handler (item->handler_value, fdt.fds[i].fd);
309               if (!err && ictx->cancel)
310                 err = GPGME_Canceled;
311               if (err)
312                 {
313                   /* An error occured.  Close all fds in this context,
314                      and signal it.  */
315                   int idx;
316             
317                   for (idx = 0; idx < ictx->fdt.size; idx++)
318                     if (ictx->fdt.fds[idx].fd != -1)
319                       _gpgme_io_close (ictx->fdt.fds[idx].fd);
320                   _gpgme_engine_io_event (ictx->engine, GPGME_EVENT_DONE, &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               GpgmeError err = 0;
336               _gpgme_engine_io_event (ctx->engine, GPGME_EVENT_DONE, &err);
337             }
338         }
339       UNLOCK (ctx_list_lock);
340
341       {
342         GpgmeCtx dctx = ctx_wait (ctx, status);
343
344         if (dctx)
345           {
346             ctx = dctx;
347             hang = 0;
348             ctx->pending = 0;
349           }
350       }
351     }
352   while (hang);
353
354   return ctx;
355 }
356
357