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