2 Copyright (C) 2000 Werner Koch (dd9jn)
3 Copyright (C) 2001, 2002, 2003, 2004, 2005 g10 Code GmbH
5 This file is part of GPGME.
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.
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.
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
38 /* The global event loop is used for all asynchronous operations
39 (except key listing) for which no user I/O callbacks are specified.
41 A context sets up its initial I/O callbacks and then sends the
42 GPGME_EVENT_START event. After that, it is added to the global
43 list of active contexts.
45 The gpgme_wait function contains a select() loop over all file
46 descriptors in all active contexts. If an error occurs, it closes
47 all fds in that context and moves the context to the global done
48 list. Likewise, if a context has removed all I/O callbacks, it is
49 moved to the global done list.
51 All contexts in the global done list are eligible for being
52 returned by gpgme_wait if requested by the caller. */
54 /* The ctx_list_lock protects the list of active and done contexts.
55 Insertion into any of these lists is only allowed when the lock is
56 held. This allows a muli-threaded program to loop over gpgme_wait
57 and in parallel start asynchronous gpgme operations.
59 However, the fd tables in the contexts are not protected by this
60 lock. They are only allowed to change either before the context is
61 added to the active list (ie, before the start event is signalled)
62 or in a callback handler. */
63 DEFINE_STATIC_LOCK (ctx_list_lock);
65 /* A ctx_list_item is an item in the global list of active or done
69 /* Every ctx_list_item is an element in a doubly linked list. The
70 list pointers are protected by the ctx_list_lock. */
71 struct ctx_list_item *next;
72 struct ctx_list_item *prev;
75 /* The status is set when the ctx is moved to the done list. */
80 /* The active list contains all contexts that are in the global event
81 loop, have active I/O callbacks, and have already seen the start
83 static struct ctx_list_item *ctx_active_list;
85 /* The done list contains all contexts that have previously been
86 active but now are not active any longer, either because they
87 finished successfully or an I/O callback returned an error. The
88 status field in the list item contains the error value (or 0 if
90 static struct ctx_list_item *ctx_done_list;
93 /* Enter the context CTX into the active list. */
95 ctx_active (gpgme_ctx_t ctx)
97 struct ctx_list_item *li = malloc (sizeof (struct ctx_list_item));
99 return gpg_error_from_errno (errno);
102 LOCK (ctx_list_lock);
103 /* Add LI to active list. */
104 li->next = ctx_active_list;
107 ctx_active_list->prev = li;
108 ctx_active_list = li;
109 UNLOCK (ctx_list_lock);
114 /* Enter the context CTX into the done list with status STATUS. */
116 ctx_done (gpgme_ctx_t ctx, gpgme_error_t status, gpgme_error_t op_err)
118 struct ctx_list_item *li;
120 LOCK (ctx_list_lock);
121 li = ctx_active_list;
122 while (li && li->ctx != ctx)
126 /* Remove LI from active list. */
128 li->next->prev = li->prev;
130 li->prev->next = li->next;
132 ctx_active_list = li->next;
137 /* Add LI to done list. */
138 li->next = ctx_done_list;
141 ctx_done_list->prev = li;
143 UNLOCK (ctx_list_lock);
147 /* Find finished context CTX (or any context if CTX is NULL) and
148 return its status in STATUS after removing it from the done list.
149 If a matching context could be found, return it. Return NULL if no
150 context could be found. */
152 ctx_wait (gpgme_ctx_t ctx, gpgme_error_t *status, gpgme_error_t *op_err)
154 struct ctx_list_item *li;
156 LOCK (ctx_list_lock);
160 /* A specific context is requested. */
161 while (li && li->ctx != ctx)
168 *status = li->status;
170 *op_err = li->op_err;
172 /* Remove LI from done list. */
174 li->next->prev = li->prev;
176 li->prev->next = li->next;
178 ctx_done_list = li->next;
183 UNLOCK (ctx_list_lock);
188 /* Internal I/O callback functions. */
190 /* The add_io_cb and remove_io_cb handlers are shared with the private
194 _gpgme_wait_global_event_cb (void *data, gpgme_event_io_t type,
197 gpgme_ctx_t ctx = (gpgme_ctx_t) data;
203 case GPGME_EVENT_START:
205 gpgme_error_t err = ctx_active (ctx);
208 /* An error occured. Close all fds in this context, and
209 send the error in a done event. */
210 _gpgme_cancel_with_err (ctx, err, 0);
214 case GPGME_EVENT_DONE:
216 gpgme_io_event_done_data_t done_data =
217 (gpgme_io_event_done_data_t) type_data;
219 ctx_done (ctx, done_data->err, done_data->op_err);
223 case GPGME_EVENT_NEXT_KEY:
224 assert (!"Unexpected event GPGME_EVENT_NEXT_KEY");
227 case GPGME_EVENT_NEXT_TRUSTITEM:
228 assert (!"Unexpected event GPGME_EVENT_NEXT_TRUSTITEM");
232 assert (!"Unexpected event");
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.
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
254 gpgme_wait_ext (gpgme_ctx_t ctx, gpgme_error_t *status,
255 gpgme_error_t *op_err, int hang)
260 struct ctx_list_item *li;
264 /* Collect the active file descriptors. */
265 LOCK (ctx_list_lock);
266 for (li = ctx_active_list; li; li = li->next)
267 i += li->ctx->fdt.size;
268 fdt.fds = malloc (i * sizeof (struct io_select_fd_s));
271 int saved_errno = errno;
272 UNLOCK (ctx_list_lock);
274 *status = gpg_error_from_errno (saved_errno);
281 for (li = ctx_active_list; li; li = li->next)
283 memcpy (&fdt.fds[i], li->ctx->fdt.fds,
284 li->ctx->fdt.size * sizeof (struct io_select_fd_s));
285 i += li->ctx->fdt.size;
287 UNLOCK (ctx_list_lock);
289 nr = _gpgme_io_select (fdt.fds, fdt.size, 0);
292 int saved_errno = errno;
295 *status = gpg_error_from_errno (saved_errno);
301 for (i = 0; i < fdt.size && nr; i++)
303 if (fdt.fds[i].fd != -1 && fdt.fds[i].signaled)
306 gpgme_error_t err = 0;
307 gpgme_error_t local_op_err = 0;
308 struct wait_item_s *item;
313 item = (struct wait_item_s *) fdt.fds[i].opaque;
320 err = gpg_error (GPG_ERR_CANCELED);
324 err = _gpgme_run_io_cb (&fdt.fds[i], 0, &local_op_err);
325 if (err || local_op_err)
327 /* An error occured. Close all fds in this context,
329 _gpgme_cancel_with_err (ictx, err, local_op_err);
331 /* Break out of the loop, and retry the select()
332 from scratch, because now all fds should be
340 /* Now some contexts might have finished successfully. */
341 LOCK (ctx_list_lock);
343 for (li = ctx_active_list; li; li = li->next)
345 gpgme_ctx_t actx = li->ctx;
347 for (i = 0; i < actx->fdt.size; i++)
348 if (actx->fdt.fds[i].fd != -1)
350 if (i == actx->fdt.size)
352 struct gpgme_io_event_done_data data;
356 /* FIXME: This does not perform too well. We have to
357 release the lock because the I/O event handler
358 acquires it to remove the context from the active
359 list. Two alternative strategies are worth
360 considering: Either implement the DONE event handler
361 here in a lock-free manner, or save a list of all
362 contexts to be released and call the DONE events
364 UNLOCK (ctx_list_lock);
365 _gpgme_engine_io_event (actx->engine, GPGME_EVENT_DONE, &data);
366 LOCK (ctx_list_lock);
370 UNLOCK (ctx_list_lock);
373 gpgme_ctx_t dctx = ctx_wait (ctx, status, op_err);
397 gpgme_wait (gpgme_ctx_t ctx, gpgme_error_t *status, int hang)
399 return gpgme_wait_ext (ctx, status, NULL, hang);