aboutsummaryrefslogtreecommitdiffstats
path: root/src/wait.c
diff options
context:
space:
mode:
authorWerner Koch <[email protected]>2019-06-13 06:40:33 +0000
committerWerner Koch <[email protected]>2019-06-13 06:40:33 +0000
commit28e620fa169dcbfc2301ae9bea58ebe3ccc3504a (patch)
tree28be2b60116d54a3ba83f8297332bf56e5561728 /src/wait.c
parentcore: Use fully correct command args for gpg --verify. (diff)
downloadgpgme-28e620fa169dcbfc2301ae9bea58ebe3ccc3504a.tar.gz
gpgme-28e620fa169dcbfc2301ae9bea58ebe3ccc3504a.zip
core: Refactor the wait code utilizing the new fdtable.
* src/fdtable.c, src/fdtable.h: Largely extend. * src/wait-global.c, src/wait-private.c, src/wait-user.c: Remove and move code to ... * src/wait.c: here. (_gpgme_fd_table_init, fd_table_put): Remove. Do not call them. (_gpgme_add_io_cb, _gpgme_add_io_cb_user): Change to use the fdtable. (_gpgme_remove_io_cb, _gpgme_remove_io_cb_user): Ditto. (_gpgme_wait_global_event_cb): Ditto. (gpgme_wait_ext, _gpgme_wait_on_condition): Ditto. * src/wait.h (struct io_cb_tag_s): Add fields 'serial' and 'desc'. Change 'idx' to 'fd'. (struct fd_table): Remove. * src/context.h (struct gpgme_context): Remoce 'fdt'. Rename io_cbs to user_io_cbs for clarity. * src/engine-gpgsm.c: Unify trace output. (start): Pass a description along with the IO handlers. * src/priv-io.h (struct io_select_fd_s): Rename to io_select_s. (io_select_t): New. * src/gpgme.c (_gpgme_cancel_with_err): Replace arg 'ctx' by 'serial'. (gpgme_cancel): Adjust. -- This is the second part of a larger refactoring of the wait/event code. Does currently only work on Unix and with the private wait functions (i.e. the async operations don't yet work). Signed-off-by: Werner Koch <[email protected]>
Diffstat (limited to '')
-rw-r--r--src/wait.c496
1 files changed, 373 insertions, 123 deletions
diff --git a/src/wait.c b/src/wait.c
index 52eb6f4d..7418f00d 100644
--- a/src/wait.c
+++ b/src/wait.c
@@ -38,190 +38,440 @@
#include "priv-io.h"
#include "engine.h"
#include "debug.h"
+#include "fdtable.h"
-
-void
-_gpgme_fd_table_init (fd_table_t fdt)
-{
- fdt->fds = NULL;
- fdt->size = 0;
-}
-void
-_gpgme_fd_table_deinit (fd_table_t fdt)
+/* Wrapper for the user wait handler to match the exported prototype.
+ * This is used by _gpgme_add_io_cb_user. */
+static gpg_error_t
+user_io_cb_handler (void *data, int fd)
{
- if (fdt->fds)
- free (fdt->fds);
-}
+ struct io_cb_tag_s *tag = data;
+ gpg_error_t err;
+ uint64_t serial;
+ gpgme_ctx_t ctx;
+ gpg_error_t op_err;
+ (void)fd;
-/* XXX We should keep a marker and roll over for speed. */
-static gpgme_error_t
-fd_table_put (fd_table_t fdt, int fd, int dir, void *opaque, int *idx)
-{
- unsigned int i, j;
- struct io_select_fd_s *new_fds;
+ assert (data);
+ serial = tag->serial;
+ assert (serial);
- for (i = 0; i < fdt->size; i++)
+ err = _gpgme_fdtable_run_io_cbs (serial, &op_err);
+ if (err || op_err)
+ ;
+ else if (!_gpgme_fdtable_io_cb_count (serial))
{
- if (fdt->fds[i].fd == -1)
- break;
+ /* No more active callbacks - emit a DONE. */
+ struct gpgme_io_event_done_data done_data = { 0, 0 };
+ _gpgme_get_ctx (serial, &ctx);
+ if (ctx)
+ _gpgme_engine_io_event (ctx->engine, GPGME_EVENT_DONE, &done_data);
}
- if (i == fdt->size)
- {
-#define FDT_ALLOCSIZE 10
- new_fds = realloc (fdt->fds, (fdt->size + FDT_ALLOCSIZE)
- * sizeof (*new_fds));
- if (!new_fds)
- return gpg_error_from_syserror ();
-
- fdt->fds = new_fds;
- fdt->size += FDT_ALLOCSIZE;
- for (j = 0; j < FDT_ALLOCSIZE; j++)
- fdt->fds[i + j].fd = -1;
- }
-
- fdt->fds[i].fd = fd;
- fdt->fds[i].for_read = (dir == 1);
- fdt->fds[i].for_write = (dir == 0);
- fdt->fds[i].signaled = 0;
- fdt->fds[i].opaque = opaque;
- *idx = i;
return 0;
}
+
/* Register the file descriptor FD with the handler FNC (which gets
FNC_DATA as its first argument) for the direction DIR. DATA should
be the context for which the fd is added. R_TAG will hold the tag
- that can be used to remove the fd. */
+ that can be used to remove the fd. This function is used for the
+ global and the private wait loops. */
gpgme_error_t
_gpgme_add_io_cb (void *data, int fd, int dir, gpgme_io_cb_t fnc,
void *fnc_data, void **r_tag)
{
gpgme_error_t err;
gpgme_ctx_t ctx = (gpgme_ctx_t) data;
- fd_table_t fdt;
- struct wait_item_s *item;
- struct tag *tag;
+ struct io_cb_tag_s *tag;
+
+ TRACE_BEG (DEBUG_SYSIO, __func__, NULL, "ctx=%lu fd=%d, dir %d",
+ CTXSERIAL (ctx), fd, dir);
+
+ if (!fnc)
+ return gpg_error (GPG_ERR_INV_ARG);
assert (fnc);
assert (ctx);
- fdt = &ctx->fdt;
- assert (fdt);
-
- tag = malloc (sizeof *tag);
+ tag = calloc (1, sizeof *tag);
if (!tag)
return gpg_error_from_syserror ();
- tag->ctx = ctx;
-
- /* Allocate a structure to hold information about the handler. */
- item = calloc (1, sizeof *item);
- if (!item)
- {
- free (tag);
- return gpg_error_from_syserror ();
- }
- item->ctx = ctx;
- item->dir = dir;
- item->handler = fnc;
- item->handler_value = fnc_data;
+ tag->serial = ctx->serial;
+ tag->fd = fd;
- err = fd_table_put (fdt, fd, dir, item, &tag->idx);
+ err = _gpgme_fdtable_set_io_cb (fd, ctx->serial, dir, fnc, fnc_data);
if (err)
{
free (tag);
- free (item);
- return err;
+ return TRACE_ERR (err);
}
- TRACE (DEBUG_CTX, "_gpgme_add_io_cb", NULL,
- "ctx=%lu fd=%d dir=%d -> tag=%p", CTXSERIAL (ctx), fd, dir, tag);
-
*r_tag = tag;
+
+ TRACE_SUC ("tag=%p", tag);
return 0;
}
+/* Register the file descriptor FD with the handler FNC (which gets
+ FNC_DATA as its first argument) for the direction DIR. DATA should
+ be the context for which the fd is added. R_TAG will hold the tag
+ that can be used to remove the fd. This function is used for the
+ user wait loops. */
+gpg_error_t
+_gpgme_add_io_cb_user (void *data, int fd, int dir, gpgme_io_cb_t fnc,
+ void *fnc_data, void **r_tag)
+{
+ gpgme_ctx_t ctx = (gpgme_ctx_t) data;
+ struct io_cb_tag_s *tag;
+ gpgme_error_t err;
+
+ TRACE_BEG (DEBUG_SYSIO, __func__, NULL, "ctx=%lu fd=%d, dir %d",
+ CTXSERIAL (ctx), fd, dir);
+
+ assert (ctx);
+ err = _gpgme_add_io_cb (data, fd, dir, fnc, fnc_data, r_tag);
+ if (err)
+ return TRACE_ERR (err);
+ tag = *r_tag;
+ assert (tag);
+
+ err = ctx->user_io_cbs.add (ctx->user_io_cbs.add_priv, fd, dir,
+ user_io_cb_handler, *r_tag,
+ &tag->user_tag);
+ if (err)
+ _gpgme_remove_io_cb (*r_tag);
+ return TRACE_ERR (err);
+}
+
+
+/* This function is used for the global and the private wait loops. */
void
_gpgme_remove_io_cb (void *data)
{
- struct tag *tag = data;
+ struct io_cb_tag_s *tag = data;
+ gpg_error_t err;
+
+ assert (tag);
+
+ err = _gpgme_fdtable_set_io_cb (tag->fd, tag->serial, 0, NULL, NULL);
+ if (err)
+ {
+ TRACE (DEBUG_CTX, __func__, NULL, "tag=%p (ctx=%lu fd=%d) failed: %s",
+ tag, tag->serial, tag->fd, gpg_strerror (err));
+ }
+ else
+ {
+ TRACE (DEBUG_CTX, __func__, NULL, "tag=%p (ctx=%lu fd=%d) done",
+ tag, tag->serial, tag->fd);
+ }
+ free (tag);
+}
+
+
+/* This function is used for the user wait loops. */
+void
+_gpgme_remove_io_cb_user (void *data)
+{
+ struct io_cb_tag_s *tag = data;
gpgme_ctx_t ctx;
- fd_table_t fdt;
- int idx;
assert (tag);
- ctx = tag->ctx;
+ _gpgme_get_ctx (tag->serial, &ctx);
+
+ if (ctx)
+ ctx->user_io_cbs.remove (tag->user_tag);
+ _gpgme_remove_io_cb (data);
+}
+
+
+
+/* The internal I/O callback function used for the global event loop.
+ That loop is used for all asynchronous operations (except key
+ listing) for which no user I/O callbacks are specified.
+
+ A context sets up its initial I/O callbacks and then sends the
+ GPGME_EVENT_START event. After that, it is added to the global
+ list of active contexts.
+
+ The gpgme_wait function contains a select() loop over all file
+ descriptors in all active contexts. If an error occurs, it closes
+ all fds in that context and moves the context to the global done
+ list. Likewise, if a context has removed all I/O callbacks, it is
+ moved to the global done list.
+
+ All contexts in the global done list are eligible for being
+ returned by gpgme_wait if requested by the caller. */
+void
+_gpgme_wait_global_event_cb (void *data, gpgme_event_io_t type,
+ void *type_data)
+{
+ gpgme_ctx_t ctx = (gpgme_ctx_t) data;
+ gpg_error_t err;
+
assert (ctx);
- fdt = &ctx->fdt;
- assert (fdt);
- idx = tag->idx;
- TRACE (DEBUG_CTX, "_gpgme_remove_io_cb", NULL,
- "ctx=%lu setting fd=%d (item=%p data=%p) done",
- CTXSERIAL (ctx),
- fdt->fds[idx].fd,
- fdt->fds[idx].opaque, data);
+ switch (type)
+ {
+ case GPGME_EVENT_START:
+ {
+ err = _gpgme_fdtable_set_active (ctx->serial);
+ if (err)
+ /* An error occurred. Close all fds in this context, and
+ send the error in a done event. */
+ _gpgme_cancel_with_err (ctx->serial, err, 0);
+ }
+ break;
- free (fdt->fds[idx].opaque);
- free (tag);
+ case GPGME_EVENT_DONE:
+ {
+ gpgme_io_event_done_data_t done_data =
+ (gpgme_io_event_done_data_t) type_data;
+
+ _gpgme_fdtable_set_done (ctx->serial,
+ done_data->err, done_data->op_err);
+ }
+ break;
+
+ case GPGME_EVENT_NEXT_KEY:
+ assert (!"Unexpected event GPGME_EVENT_NEXT_KEY");
+ break;
+
+ case GPGME_EVENT_NEXT_TRUSTITEM:
+ assert (!"Unexpected event GPGME_EVENT_NEXT_TRUSTITEM");
+ break;
+
+ default:
+ assert (!"Unexpected event");
+ break;
+ }
+}
+
+
+/* The internal I/O callback function used for private event loops.
+ * The private event loops are used for all blocking operations, and
+ * for the key and trust item listing operations. They are completely
+ * separated from each other. */
+void
+_gpgme_wait_private_event_cb (void *data, gpgme_event_io_t type,
+ void *type_data)
+{
+ switch (type)
+ {
+ case GPGME_EVENT_START:
+ /* Nothing to do here, as the wait routine is called after the
+ initialization is finished. */
+ break;
+
+ case GPGME_EVENT_DONE:
+ break;
+
+ case GPGME_EVENT_NEXT_KEY:
+ _gpgme_op_keylist_event_cb (data, type, type_data);
+ break;
+
+ case GPGME_EVENT_NEXT_TRUSTITEM:
+ _gpgme_op_trustlist_event_cb (data, type, type_data);
+ break;
+ }
+}
+
+
+/* The internal I/O callback function used for user event loops. User
+ * event loops are used for all asynchronous operations for which a
+ * user callback is defined. */
+void
+_gpgme_wait_user_event_cb (void *data, gpgme_event_io_t type, void *type_data)
+{
+ gpgme_ctx_t ctx = data;
- /* Free the table entry. */
- fdt->fds[idx].fd = -1;
- fdt->fds[idx].for_read = 0;
- fdt->fds[idx].for_write = 0;
- fdt->fds[idx].opaque = NULL;
+ if (ctx->user_io_cbs.event)
+ ctx->user_io_cbs.event (ctx->user_io_cbs.event_priv, type, type_data);
}
+
-/* This is slightly embarrassing. The problem is that running an I/O
- callback _may_ influence the status of other file descriptors. Our
- own event loops could compensate for that, but the external event
- loops cannot. FIXME: We may still want to optimize this a bit when
- we are called from our own event loops. So if CHECKED is 1, the
- check is skipped. FIXME: Give an example on how the status of other
- fds can be influenced. */
+/* Perform asynchronous operations in the global event loop (ie, any
+ asynchronous operation except key listing and trustitem listing
+ operations). If CTX is not a null pointer, the function will
+ return if the asynchronous operation in the context CTX finished.
+ Otherwise the function will return if any asynchronous operation
+ finished. If HANG is zero, the function will not block for a long
+ time. Otherwise the function does not return until an operation
+ matching CTX finished.
+
+ If a matching context finished, it is returned, and *STATUS is set
+ to the error value of the operation in that context. Otherwise, if
+ the timeout expires, NULL is returned and *STATUS is 0. If an
+ error occurs, NULL is returned and *STATUS is set to the error
+ value. */
+gpgme_ctx_t
+gpgme_wait_ext (gpgme_ctx_t ctx, gpgme_error_t *status,
+ gpgme_error_t *op_err, int hang)
+{
+ gpg_error_t err;
+ io_select_t fds = NULL;
+ unsigned int nfds;
+ int nr;
+ uint64_t serial;
+
+ do
+ {
+ /* Get all fds of CTX (or all if CTX is NULL) we want to wait
+ * for and which are in the active state. */
+ free (fds);
+ nfds = _gpgme_fdtable_get_fds (&fds, ctx? ctx->serial : 0,
+ ( FDTABLE_FLAG_ACTIVE
+ | FDTABLE_FLAG_CLEAR));
+ if (!nfds)
+ {
+ err = gpg_error_from_syserror ();
+ if (gpg_err_code (err) != GPG_ERR_MISSING_ERRNO)
+ {
+ if (status)
+ *status = err;
+ if (op_err)
+ *op_err = 0;
+ free (fds);
+ return NULL;
+ }
+ /* Nothing to select. Run the select anyway, so that we use
+ * its timeout. */
+ }
+
+ nr = _gpgme_io_select (fds, nfds, 0);
+ if (nr < 0)
+ {
+ if (status)
+ *status = gpg_error_from_syserror ();
+ if (op_err)
+ *op_err = 0;
+ free (fds);
+ return NULL;
+ }
+ _gpgme_fdtable_set_signaled (fds, nfds);
+
+ _gpgme_fdtable_run_io_cbs (ctx? ctx->serial : 0, NULL);
+ serial = _gpgme_fdtable_get_done (ctx? ctx->serial : 0, status, op_err);
+ if (serial)
+ {
+ _gpgme_get_ctx (serial, &ctx);
+ hang = 0;
+ }
+ else if (!hang)
+ {
+ ctx = NULL;
+ if (status)
+ *status = 0;
+ if (op_err)
+ *op_err = 0;
+ }
+ }
+ while (hang);
+
+ free (fds);
+ return ctx;
+}
+
+
+gpgme_ctx_t
+gpgme_wait (gpgme_ctx_t ctx, gpgme_error_t *status, int hang)
+{
+ return gpgme_wait_ext (ctx, status, NULL, hang);
+}
+
+
+
+/* If COND is a null pointer, wait until the blocking operation in CTX
+ finished and return its error value. Otherwise, wait until COND is
+ satisfied or the operation finished. */
gpgme_error_t
-_gpgme_run_io_cb (struct io_select_fd_s *an_fds, int checked,
- gpgme_error_t *op_err)
+_gpgme_wait_on_condition (gpgme_ctx_t ctx, volatile int *cond,
+ gpgme_error_t *r_op_err)
{
- struct wait_item_s *item;
- struct io_cb_data iocb_data;
- gpgme_error_t err;
+ gpgme_error_t err = 0;
+ int hang = 1;
+ io_select_t fds = NULL;
+ unsigned int nfds;
+ int op_err;
+ int nr;
+
+ if (r_op_err)
+ *r_op_err = 0;
- item = (struct wait_item_s *) an_fds->opaque;
- assert (item);
+ if (!ctx)
+ return gpg_error (GPG_ERR_INV_VALUE);
- if (!checked)
+ do
{
- int nr;
- struct io_select_fd_s fds;
-
- TRACE (DEBUG_CTX, "_gpgme_run_io_cb", item, "need to check");
- fds = *an_fds;
- fds.signaled = 0;
- /* Just give it a quick poll. */
- nr = _gpgme_io_select (&fds, 1, 1);
- assert (nr <= 1);
+ /* Get all fds of CTX we want to wait for. */
+ free (fds);
+ nfds = _gpgme_fdtable_get_fds (&fds, ctx->serial,
+ FDTABLE_FLAG_CLEAR);
+ if (!nfds)
+ {
+ err = gpg_error_from_syserror ();
+ if (gpg_err_code (err) != GPG_ERR_MISSING_ERRNO)
+ {
+ free (fds);
+ return err;
+ }
+ /* Nothing to select. Run the select anyway, so that we use
+ * its timeout. */
+ }
+
+ nr = _gpgme_io_select (fds, nfds, 0);
if (nr < 0)
- return gpg_error_from_syserror ();
- else if (nr == 0)
+ {
+ /* An error occurred. Close all fds in this context, and
+ signal it. */
+ err = gpg_error_from_syserror ();
+ _gpgme_cancel_with_err (ctx->serial, err, 0);
+ free (fds);
+ return err;
+ }
+ _gpgme_fdtable_set_signaled (fds, nfds);
+
+ err = _gpgme_fdtable_run_io_cbs (ctx->serial, r_op_err);
+ if (err || (r_op_err && *r_op_err))
{
- /* The status changed in the meantime, there is nothing left
- * to do. */
- return 0;
+ free (fds);
+ return err;
}
+
+ if (!_gpgme_fdtable_io_cb_count (ctx->serial))
+ {
+ struct gpgme_io_event_done_data data = {0, 0};
+ _gpgme_engine_io_event (ctx->engine, GPGME_EVENT_DONE, &data);
+ hang = 0;
+ }
+ if (cond && *cond)
+ hang = 0;
}
+ while (hang);
- TRACE (DEBUG_CTX, "_gpgme_run_io_cb", item, "handler (%p, %d)",
- item->handler_value, an_fds->fd);
+ free (fds);
+ return 0;
+}
- iocb_data.handler_value = item->handler_value;
- iocb_data.op_err = 0;
- err = item->handler (&iocb_data, an_fds->fd);
- *op_err = iocb_data.op_err;
- return err;
+/* Wait until the blocking operation in context CTX has finished and
+ return the error value. This variant can not be used for
+ session-based protocols. */
+gpgme_error_t
+_gpgme_wait_one (gpgme_ctx_t ctx)
+{
+ return _gpgme_wait_on_condition (ctx, NULL, NULL);
+}
+
+/* Wait until the blocking operation in context CTX has finished and
+ return the error value. This is the right variant to use for
+ sesion-based protocols. */
+gpgme_error_t
+_gpgme_wait_one_ext (gpgme_ctx_t ctx, gpgme_error_t *op_err)
+{
+ return _gpgme_wait_on_condition (ctx, NULL, op_err);
}