aboutsummaryrefslogtreecommitdiffstats
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
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]>
-rw-r--r--src/Makefile.am2
-rw-r--r--src/context.h8
-rw-r--r--src/engine-gpgsm.c34
-rw-r--r--src/fdtable.c479
-rw-r--r--src/fdtable.h38
-rw-r--r--src/gpgme.c62
-rw-r--r--src/op-support.c8
-rw-r--r--src/posix-io.c13
-rw-r--r--src/priv-io.h13
-rw-r--r--src/verify.c2
-rw-r--r--src/wait-global.c401
-rw-r--r--src/wait-private.c180
-rw-r--r--src/wait-user.c133
-rw-r--r--src/wait.c496
-rw-r--r--src/wait.h62
15 files changed, 992 insertions, 939 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index 79028a17..781441cc 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -75,7 +75,7 @@ main_sources = \
data-compat.c data-identify.c \
signers.c sig-notation.c \
fdtable.c fdtable.h \
- wait.c wait-global.c wait-private.c wait-user.c wait.h \
+ wait.c wait.h \
op-support.c \
encrypt.c encrypt-sign.c decrypt.c decrypt-verify.c verify.c \
sign.c passphrase.c progress.c \
diff --git a/src/context.h b/src/context.h
index 7333d2d5..4142b684 100644
--- a/src/context.h
+++ b/src/context.h
@@ -194,10 +194,8 @@ struct gpgme_context
gpgme_status_cb_t status_cb;
void *status_cb_value;
- /* A list of file descriptors in active use by the current
- operation. */
- struct fd_table fdt;
- struct gpgme_io_cbs io_cbs;
+ /* User specific I/O callbacks. */
+ struct gpgme_io_cbs user_io_cbs;
};
@@ -208,7 +206,7 @@ struct gpgme_context
gpg_error_t _gpgme_get_ctx (uint64_t serial, gpgme_ctx_t *r_ctx);
-gpgme_error_t _gpgme_cancel_with_err (gpgme_ctx_t ctx, gpg_error_t ctx_err,
+gpgme_error_t _gpgme_cancel_with_err (uint64_t serial, gpg_error_t ctx_err,
gpg_error_t op_err);
diff --git a/src/engine-gpgsm.c b/src/engine-gpgsm.c
index 9ecdbe47..086ed32d 100644
--- a/src/engine-gpgsm.c
+++ b/src/engine-gpgsm.c
@@ -910,7 +910,7 @@ status_handler (void *opaque, int fd)
/* Try our best to terminate the connection friendly. */
/* assuan_write_line (gpgsm->assuan_ctx, "BYE"); */
TRACE (DEBUG_CTX, "gpgme:status_handler", gpgsm,
- "fd 0x%x: error from assuan (%d) getting status line : %s",
+ "fd=%d: error from assuan (%d) getting status line : %s",
fd, err, gpg_strerror (err));
}
else if (linelen >= 3
@@ -922,7 +922,7 @@ status_handler (void *opaque, int fd)
if (! err)
err = gpg_error (GPG_ERR_GENERAL);
TRACE (DEBUG_CTX, "gpgme:status_handler", gpgsm,
- "fd 0x%x: ERR line - mapped to: %s",
+ "fd=%d: ERR line - mapped to: %s",
fd, err ? gpg_strerror (err) : "ok");
/* Try our best to terminate the connection friendly. */
/* assuan_write_line (gpgsm->assuan_ctx, "BYE"); */
@@ -951,7 +951,7 @@ status_handler (void *opaque, int fd)
err = gpgsm->colon.fnc (gpgsm->colon.fnc_value, NULL);
}
TRACE (DEBUG_CTX, "gpgme:status_handler", gpgsm,
- "fd 0x%x: OK line - final status: %s",
+ "fd=%d: OK line - final status: %s",
fd, err ? gpg_strerror (err) : "ok");
_gpgme_io_close (gpgsm->status_cb.fd);
return err;
@@ -1026,7 +1026,7 @@ status_handler (void *opaque, int fd)
}
}
TRACE (DEBUG_CTX, "gpgme:status_handler", gpgsm,
- "fd 0x%x: D line; final status: %s",
+ "fd=%d: D line; final status: %s",
fd, err? gpg_strerror (err):"ok");
}
else if (linelen > 2
@@ -1068,7 +1068,7 @@ status_handler (void *opaque, int fd)
}
TRACE (DEBUG_CTX, "gpgme:status_handler", gpgsm,
- "fd 0x%x: D inlinedata; final status: %s",
+ "fd=%d: D inlinedata; final status: %s",
fd, err? gpg_strerror (err):"ok");
}
else if (linelen > 2
@@ -1106,7 +1106,7 @@ status_handler (void *opaque, int fd)
else
fprintf (stderr, "[UNKNOWN STATUS]%s %s", line + 2, rest);
TRACE (DEBUG_CTX, "gpgme:status_handler", gpgsm,
- "fd 0x%x: S line (%s) - final status: %s",
+ "fd=%d: S line (%s) - final status: %s",
fd, line+2, err? gpg_strerror (err):"ok");
}
else if (linelen >= 7
@@ -1131,12 +1131,14 @@ status_handler (void *opaque, int fd)
static gpgme_error_t
-add_io_cb (engine_gpgsm_t gpgsm, iocb_data_t *iocbd, gpgme_io_cb_t handler)
+add_io_cb (engine_gpgsm_t gpgsm, iocb_data_t *iocbd, gpgme_io_cb_t handler,
+ const char *handler_desc)
{
gpgme_error_t err;
- TRACE_BEG (DEBUG_ENGINE, "engine-gpgsm:add_io_cb", gpgsm,
- "fd=%d, dir %d", iocbd->fd, iocbd->dir);
+ TRACE_BEG (DEBUG_ENGINE, "engine-gpgsm:add_io_cb", NULL,
+ "fd=%d, dir %d (%s-handler)",
+ iocbd->fd, iocbd->dir, handler_desc);
err = (*gpgsm->io_cbs.add) (gpgsm->io_cbs.add_priv,
iocbd->fd, iocbd->dir,
handler, iocbd->data, &iocbd->tag);
@@ -1205,15 +1207,19 @@ start (engine_gpgsm_t gpgsm, const char *command)
return gpg_error (GPG_ERR_GENERAL);
}
- err = add_io_cb (gpgsm, &gpgsm->status_cb, status_handler);
+ err = add_io_cb (gpgsm, &gpgsm->status_cb, status_handler, "status");
if (!err && gpgsm->input_cb.fd != -1)
- err = add_io_cb (gpgsm, &gpgsm->input_cb, _gpgme_data_outbound_handler);
+ err = add_io_cb (gpgsm, &gpgsm->input_cb,
+ _gpgme_data_outbound_handler, "outbound");
if (!err && gpgsm->output_cb.fd != -1)
- err = add_io_cb (gpgsm, &gpgsm->output_cb, _gpgme_data_inbound_handler);
+ err = add_io_cb (gpgsm, &gpgsm->output_cb,
+ _gpgme_data_inbound_handler, "inbound");
if (!err && gpgsm->message_cb.fd != -1)
- err = add_io_cb (gpgsm, &gpgsm->message_cb, _gpgme_data_outbound_handler);
+ err = add_io_cb (gpgsm, &gpgsm->message_cb,
+ _gpgme_data_outbound_handler, "outbound");
if (!err && gpgsm->diag_cb.fd != -1)
- err = add_io_cb (gpgsm, &gpgsm->diag_cb, _gpgme_data_inbound_handler);
+ err = add_io_cb (gpgsm, &gpgsm->diag_cb,
+ _gpgme_data_inbound_handler, "inbound");
if (!err)
err = assuan_write_line (gpgsm->assuan_ctx, command);
diff --git a/src/fdtable.c b/src/fdtable.c
index 2e682dea..de240fc3 100644
--- a/src/fdtable.c
+++ b/src/fdtable.c
@@ -37,6 +37,41 @@ struct fdtable_item_s
{
int fd; /* -1 indicates an unused entry. */
+ uint64_t owner; /* The S/N of the context owning this FD. */
+
+ /* ACTIVE is set if this fd is in the global event loop, has an
+ * active callback (.io_cb), and has seen the start event. */
+ unsigned int active:1;
+ /* DONE is set if this fd was previously active but is not active
+ * any longer, either because is finished successfully or its I/O
+ * callback returned an error. Note that ACTIVE and DONE should
+ * never both be set. */
+ unsigned int done:1;
+
+ /* Infos for io_select. */
+ unsigned int for_read:1;
+ unsigned int for_write:1;
+ unsigned int signaled:1;
+
+ /* We are in a closing handler. Note that while this flag is active
+ * the remove code holds an index into the table. Thus we better
+ * make sure that the index won't change. Or change the removal
+ * code to re-find the fd. */
+ unsigned int closing:1;
+
+ /* We are currently running the IO callback. */
+ unsigned int io_cb_running:1;
+
+ /* The I/O callback handler with its value context. */
+ struct {
+ gpgme_io_cb_t cb;
+ void *value;
+ } io_cb;
+
+ /* The error code and the operational error for the done status. */
+ gpg_error_t done_status;
+ gpg_error_t done_op_err;
+
/* The callback to be called before the descriptor is actually closed. */
struct {
fdtable_handler_t handler;
@@ -109,6 +144,16 @@ _gpgme_fdtable_insert (int fd)
idx = firstunused;
fdtable[idx].fd = fd;
+ fdtable[idx].owner = 0;
+ fdtable[idx].active = 0;
+ fdtable[idx].done = 0;
+ fdtable[idx].for_read = 0;
+ fdtable[idx].for_write = 0;
+ fdtable[idx].signaled = 0;
+ fdtable[idx].closing = 0;
+ fdtable[idx].io_cb_running = 0;
+ fdtable[idx].io_cb.cb = NULL;
+ fdtable[idx].io_cb.value = NULL;
fdtable[idx].close_notify.handler = NULL;
fdtable[idx].close_notify.value = NULL;
err = 0;
@@ -161,6 +206,178 @@ _gpgme_fdtable_add_close_notify (int fd,
}
+/* Set the I/O callback for the FD. FD must already exist otherwise
+ * GPG_ERR_NO_KEY is returned. OWNER is the serial of the owning
+ * context. If DIRECTION is 1 the callback wants to read from it; if
+ * it is 0 the callback want to write to it. CB is the actual
+ * callback and CB_VALUE the values passed to that callback. If a
+ * callback as already been set GPG_ERR_DUP_VALUE is returned. To
+ * remove the handler, FD and OWNER must be passed as usual but CB be
+ * passed as NULL.
+ */
+gpg_error_t
+_gpgme_fdtable_set_io_cb (int fd, uint64_t owner, int direction,
+ gpgme_io_cb_t cb, void *cb_value)
+{
+ gpg_error_t err;
+ int idx;
+
+ TRACE_BEG (DEBUG_SYSIO, __func__, NULL, "fd=%d ctx=%lu dir=%d",
+ fd, (unsigned long)owner, direction);
+
+ if (fd < 0 || !owner)
+ return TRACE_ERR (gpg_error (GPG_ERR_INV_ARG));
+
+ LOCK (fdtable_lock);
+
+ if (cb)
+ {
+ for (idx=0; idx < fdtablesize; idx++)
+ if (fdtable[idx].fd == fd)
+ break;
+ if (idx == fdtablesize)
+ {
+ err = gpg_error (GPG_ERR_NO_KEY);
+ TRACE_LOG ("with_cb: fd=%d owner=%lu", fd, (unsigned long)owner);
+ goto leave;
+ }
+
+ if (fdtable[idx].io_cb.cb)
+ {
+ err = gpg_error (GPG_ERR_DUP_VALUE);
+ goto leave;
+ }
+
+ fdtable[idx].owner = owner;
+
+ fdtable[idx].for_read = (direction == 1);
+ fdtable[idx].for_write = (direction == 0);
+ fdtable[idx].signaled = 0;
+
+ fdtable[idx].io_cb.cb = cb;
+ fdtable[idx].io_cb.value = cb_value;
+ }
+ else /* Remove. */
+ {
+ /* We compare also the owner as a cross-check. */
+ for (idx=0; idx < fdtablesize; idx++)
+ if (fdtable[idx].fd == fd && fdtable[idx].owner == owner)
+ break;
+ if (idx == fdtablesize)
+ {
+ err = gpg_error (GPG_ERR_NO_KEY);
+ TRACE_LOG ("remove: fd=%d owner=%lu", fd, (unsigned long)owner);
+ for (idx=0; idx < fdtablesize; idx++)
+ TRACE_LOG (" TBL: fd=%d owner=%lu", fdtable[idx].fd, (unsigned long)fdtable[idx].owner);
+ goto leave;
+ }
+
+ fdtable[idx].for_read = 0;
+ fdtable[idx].for_write = 0;
+ fdtable[idx].signaled = 0;
+
+ fdtable[idx].io_cb.cb = NULL;
+ fdtable[idx].io_cb.value = NULL;
+ fdtable[idx].owner = 0;
+ }
+ err = 0;
+
+ leave:
+ UNLOCK (fdtable_lock);
+ return TRACE_ERR (err);
+}
+
+
+/* Set all FDs of OWNER into the active state. */
+gpg_error_t
+_gpgme_fdtable_set_active (uint64_t owner)
+{
+ int idx;
+
+ TRACE_BEG (DEBUG_SYSIO, __func__, NULL, "ctx=%lu", (unsigned long)owner);
+
+ if (!owner )
+ return TRACE_ERR (gpg_error (GPG_ERR_INV_ARG));
+
+ LOCK (fdtable_lock);
+
+ for (idx=0; idx < fdtablesize; idx++)
+ if (fdtable[idx].fd != -1 && fdtable[idx].owner == owner
+ && fdtable[idx].io_cb.cb)
+ {
+ fdtable[idx].active = 1;
+ fdtable[idx].done = 0;
+ }
+
+ UNLOCK (fdtable_lock);
+ return TRACE_ERR (0);
+}
+
+
+/* Set all FDs of OWNER into the done state. STATUS and OP_ERR are
+ * recorded. */
+gpg_error_t
+_gpgme_fdtable_set_done (uint64_t owner, gpg_error_t status, gpg_error_t op_err)
+{
+ int idx;
+
+ TRACE_BEG (DEBUG_SYSIO, __func__, NULL, "ctx=%lu", (unsigned long)owner);
+
+ if (!owner )
+ return TRACE_ERR (gpg_error (GPG_ERR_INV_ARG));
+
+ LOCK (fdtable_lock);
+
+ for (idx=0; idx < fdtablesize; idx++)
+ if (fdtable[idx].fd != -1 && fdtable[idx].owner == owner
+ && fdtable[idx].active)
+ {
+ fdtable[idx].active = 0;
+ fdtable[idx].done = 1;
+ fdtable[idx].done_status = status;
+ fdtable[idx].done_op_err = op_err;
+ }
+
+ UNLOCK (fdtable_lock);
+ return TRACE_ERR (0);
+}
+
+
+/* Walk over all fds in FDS and copy the signaled flag if set. It
+ * does not clear any signal flag in the global table. */
+void
+_gpgme_fdtable_set_signaled (io_select_t fds, unsigned int nfds)
+{
+ int idx;
+ unsigned int n, count;
+
+ if (!nfds)
+ return;
+
+ /* FIXME: Highly inefficient code in case of large select lists. */
+ count = 0;
+ LOCK (fdtable_lock);
+ for (idx=0; idx < fdtablesize; idx++)
+ {
+ if (fdtable[idx].fd == -1)
+ continue;
+ for (n = 0; n < nfds; n++)
+ if (fdtable[idx].fd == fds[n].fd)
+ {
+ if (fds[n].signaled && !fdtable[idx].signaled)
+ {
+ fdtable[idx].signaled = 1;
+ count++; /* Only for tracing. */
+ }
+ break;
+ }
+ }
+ UNLOCK (fdtable_lock);
+
+ TRACE (DEBUG_SYSIO, __func__, NULL, "fds newly signaled=%u", count);
+}
+
+
/* Remove FD from the table after calling the close handler. Note
* that at the time the close handler is called the FD has been
* removed form the table. Thus the close handler may not access the
@@ -191,15 +408,273 @@ _gpgme_fdtable_remove (int fd)
return TRACE_ERR (gpg_error (GPG_ERR_NO_KEY));
}
+ TRACE_LOG ("removal of fd=%d owner=%lu (closing=%d)",
+ fdtable[idx].fd, (unsigned long)fdtable[idx].owner,
+ fdtable[idx].closing);
+
handler = fdtable[idx].close_notify.handler;
fdtable[idx].close_notify.handler = NULL;
handlervalue = fdtable[idx].close_notify.value;
fdtable[idx].close_notify.value = NULL;
- fdtable[idx].fd = -1;
+
+ /* The handler might call into the fdtable again, so of we have a
+ * handler we can't immediately close it but instead record the fact
+ * and remove the entry from the table only after the handler has
+ * been run. */
+ if (handler)
+ fdtable[idx].closing = 1;
+ else if (!fdtable[idx].closing)
+ fdtable[idx].fd = -1;
UNLOCK (fdtable_lock);
- err = handler? handler (fd, handlervalue) : 0;
+ if (handler)
+ {
+ err = handler (fd, handlervalue);
+ LOCK (fdtable_lock);
+ TRACE_LOG ("final removal of fd=%d owner=%lu (closing=%d)",
+ fdtable[idx].fd, (unsigned long)fdtable[idx].owner,
+ fdtable[idx].closing);
+ fdtable[idx].fd = -1;
+ UNLOCK (fdtable_lock);
+ }
+ else
+ err = 0;
return TRACE_ERR (err);
}
+
+
+/* Return the number of active I/O callbacks for OWNER or for all if
+ * OWNER is 0. */
+unsigned int
+_gpgme_fdtable_io_cb_count (uint64_t owner)
+{
+ int idx;
+ unsigned int count = 0;
+
+ LOCK (fdtable_lock);
+ for (idx=0; idx < fdtablesize; idx++)
+ if (fdtable[idx].fd != -1 && (!owner || fdtable[idx].owner == owner))
+ count++;
+ UNLOCK (fdtable_lock);
+
+ TRACE (DEBUG_SYSIO, __func__, NULL, "ctx=%lu count=%u",
+ (unsigned long)owner, count);
+ return count;
+}
+
+
+/* Run all signaled IO callbacks of OWNER or all signaled callbacks if
+ * OWNER is 0. Returns an error code on the first real error
+ * encountered. If R_OP_ERR is not NULL an optional operational error
+ * can be stored tehre. For EOF the respective flags are set. */
+gpg_error_t
+_gpgme_fdtable_run_io_cbs (uint64_t owner, gpg_error_t *r_op_err)
+{
+ gpg_error_t err;
+ int idx;
+ int fd;
+ gpgme_io_cb_t iocb;
+ struct io_cb_data iocb_data;
+ uint64_t serial;
+ unsigned int cb_count;
+ gpgme_ctx_t actx;
+
+ if (r_op_err)
+ *r_op_err = 0;
+
+ TRACE_BEG (DEBUG_SYSIO, __func__, NULL, "ctx=%lu", owner);
+
+ for (;;)
+ {
+ fd = -1;
+ LOCK (fdtable_lock);
+ for (idx=0; idx < fdtablesize; idx++)
+ if (fdtable[idx].fd != -1 && (!owner || fdtable[idx].owner == owner)
+ && fdtable[idx].signaled)
+ {
+ fd = fdtable[idx].fd;
+ serial = fdtable[idx].owner;
+ iocb = fdtable[idx].io_cb.cb;
+ iocb_data.handler_value = fdtable[idx].io_cb.value;
+ iocb_data.op_err = 0;
+ fdtable[idx].signaled = 0;
+ if (iocb)
+ {
+ fdtable[idx].io_cb_running = 1;
+ break;
+ }
+ }
+ UNLOCK (fdtable_lock);
+ if (fd == -1)
+ break; /* No more callbacks found. */
+
+ /* If the context object is still valid and has not been
+ * canceled, we run the I/O callback. */
+ err = _gpgme_get_ctx (serial, &actx);
+ if (!err)
+ {
+ err = iocb (&iocb_data, fd);
+ if (err)
+ TRACE_LOG ("iocb(fd=%d) err=%s", fd, gpg_strerror (err));
+ }
+
+ /* Clear the running flag and while we are at it also count the
+ * remaining callbacks. */
+ cb_count = 0;
+ LOCK (fdtable_lock);
+ for (idx=0; idx < fdtablesize; idx++)
+ {
+ if (fdtable[idx].fd == -1)
+ continue;
+ if (fdtable[idx].fd == fd)
+ fdtable[idx].io_cb_running = 0;
+ if (fdtable[idx].owner == serial)
+ cb_count++;
+ }
+ UNLOCK (fdtable_lock);
+
+ /* Handle errors or success from the IO callback. In the error
+ * case we close all fds belonging to the same context. In the
+ * success case we check whether any callback is left and only
+ * if that is not the case, tell the engine that we are done.
+ * The latter indirectly sets the fd into the done state. */
+ if (err)
+ {
+ _gpgme_cancel_with_err (serial, err, 0);
+ return TRACE_ERR (err);
+ }
+ else if (iocb_data.op_err)
+ {
+ /* An operational error occurred. Cancel the current
+ * operation but not the session, and signal it. */
+ _gpgme_cancel_with_err (serial, 0, iocb_data.op_err);
+
+ /* NOTE: This relies on the operational error being
+ * generated after the operation really has completed, for
+ * example after no further status line output is generated.
+ * Otherwise the following I/O will spill over into the next
+ * operation. */
+ if (r_op_err)
+ *r_op_err = iocb_data.op_err;
+ return TRACE_ERR (0);
+ }
+ else if (!cb_count && actx)
+ {
+ struct gpgme_io_event_done_data data = { 0, 0 };
+ _gpgme_engine_io_event (actx->engine, GPGME_EVENT_DONE, &data);
+ }
+ }
+
+ return TRACE_ERR (0);
+}
+
+
+/* Retrieve a list of file descriptors owned by OWNER, or with OWNER
+ * being 0 of all fds, and store that list as a new array at R_FDS.
+ * Return the number of FDS in that list or 0 if none were selected.
+ * FLAGS give further selection flags:
+ * FDTABLE_FLAG_ACTIVE - Only those with the active flag set.
+ * FDTABLE_FLAG_DONE - Only those with the done flag set.
+ * FDTABLE_FLAG_FOR_READ - Only those with the readable FDs.
+ * FDTABLE_FLAG_FOR_WRITE - Only those with the writable FDs.
+ * FDTABLE_FLAG_SIGNALED - Only those with the signaled flag set.
+ * FDTABLE_FLAG_NOT_SIGNALED - Only those with the signaled flag cleared.
+ * FDTABLE_FLAG_CLEAR - Clear the signaled flag..
+ */
+unsigned int
+_gpgme_fdtable_get_fds (io_select_t *r_fds, uint64_t owner, unsigned int flags)
+{
+ int idx;
+ unsigned int count = 0;
+ io_select_t fds;
+
+ *r_fds = NULL;
+ gpg_err_set_errno (0);
+ /* We take an easy approach and allocate the array at the size of
+ * the entire fdtable. */
+ fds = calloc (fdtablesize, sizeof *fds);
+ if (!fds)
+ return 0;
+
+ LOCK (fdtable_lock);
+ for (idx=0; idx < fdtablesize; idx++)
+ if (fdtable[idx].fd != -1 && (!owner || fdtable[idx].owner == owner))
+ {
+ if ((flags & FDTABLE_FLAG_ACTIVE) && !fdtable[idx].active)
+ continue;
+ if ((flags & FDTABLE_FLAG_DONE) && !fdtable[idx].done)
+ continue;
+ if ((flags & FDTABLE_FLAG_FOR_READ) && !fdtable[idx].for_read)
+ continue;
+ if ((flags & FDTABLE_FLAG_FOR_WRITE) && !fdtable[idx].for_write)
+ continue;
+ if ((flags & FDTABLE_FLAG_SIGNALED) && !fdtable[idx].signaled)
+ continue;
+ if ((flags & FDTABLE_FLAG_NOT_SIGNALED) && fdtable[idx].signaled)
+ continue;
+
+ if (fdtable[idx].io_cb_running || fdtable[idx].closing)
+ continue; /* The callback has not yet finished or we are
+ * already closing. Does not make sense to allow
+ * selecting on it. */
+
+ fds[count].fd = fdtable[idx].fd;
+ fds[count].for_read = fdtable[idx].for_read;
+ fds[count].for_write = fdtable[idx].for_write;
+ fds[count].signaled =
+ (flags & FDTABLE_FLAG_SIGNALED)? 0 : fdtable[idx].signaled;
+
+ count++;
+ }
+
+ UNLOCK (fdtable_lock);
+ *r_fds = fds;
+
+ TRACE (DEBUG_SYSIO, __func__, NULL, "ctx=%lu count=%u",
+ (unsigned long)owner, count);
+ return count;
+}
+
+
+/* If OWNER is 0 return the status info of the first fd with the done
+ * flag set. If OWNER is not 0 search for a matching owner with the
+ * done flag set and return its status info. Returns the serial
+ * number of the context found. */
+uint64_t
+_gpgme_fdtable_get_done (uint64_t owner,
+ gpg_error_t *r_status, gpg_error_t *r_op_err)
+{
+ uint64_t serial = 0;
+ int idx;
+
+ TRACE_BEG (DEBUG_SYSIO, __func__, NULL, "ctx=%lu", (unsigned long)owner);
+
+ LOCK (fdtable_lock);
+
+ for (idx=0; idx < fdtablesize; idx++)
+ if (fdtable[idx].fd != -1 && (!owner || fdtable[idx].owner == owner)
+ && fdtable[idx].done)
+ {
+ /* Found. If an owner has been given also clear the done
+ * flags from all other fds of this owner. Note that they
+ * have the same status info anyway. */
+ *r_status = fdtable[idx].done_status;
+ *r_op_err = fdtable[idx].done_op_err;
+ fdtable[idx].done = 0;
+ serial = fdtable[idx].owner;
+ if (owner)
+ {
+ for (; idx < fdtablesize; idx++)
+ if (fdtable[idx].fd != -1 && fdtable[idx].owner == owner)
+ fdtable[idx].done = 0;
+ }
+ break;
+ }
+
+ UNLOCK (fdtable_lock);
+
+ TRACE_SUC ("ctx=%lu", (unsigned long)serial);
+ return serial;
+}
diff --git a/src/fdtable.h b/src/fdtable.h
index 6038b249..6f621849 100644
--- a/src/fdtable.h
+++ b/src/fdtable.h
@@ -21,6 +21,18 @@
#ifndef GPGME_FDTABLE_H
#define GPGME_FDTABLE_H
+#include "priv-io.h"
+
+/* Flags used by _gpgme_fdtable_get_fds. */
+#define FDTABLE_FLAG_ACTIVE 1 /* Only those with the active flag set. */
+#define FDTABLE_FLAG_DONE 2 /* Only those with the done flag set */
+#define FDTABLE_FLAG_FOR_READ 4 /* Only those with the signaled flag set. */
+#define FDTABLE_FLAG_FOR_WRITE 8 /* Only those with the for_read flag set. */
+#define FDTABLE_FLAG_SIGNALED 16 /* Only those with the signaled flag set. */
+#define FDTABLE_FLAG_NOT_SIGNALED 32 /* Ditto reversed. */
+#define FDTABLE_FLAG_CLEAR 128 /* Clear the signaled flag. */
+
+
/* The handler type associated with an FD. It is called with the FD
* and the registered pointer. The handler may return an error code
* but there is no guarantee that this code is used; in particular
@@ -35,9 +47,35 @@ gpg_error_t _gpgme_fdtable_insert (int fd);
gpg_error_t _gpgme_fdtable_add_close_notify (int fd,
fdtable_handler_t handler,
void *value);
+/* Set or remove the I/O callback. */
+gpg_error_t _gpgme_fdtable_set_io_cb (int fd, uint64_t owner, int direction,
+ gpgme_io_cb_t cb, void *cb_value);
+
+/* Set all FDs of OWNER into the active state. */
+gpg_error_t _gpgme_fdtable_set_active (uint64_t owner);
+
+/* Set all FDs of OWNER into the done state. */
+gpg_error_t _gpgme_fdtable_set_done (uint64_t owner,
+ gpg_error_t status, gpg_error_t op_err);
+
+/* Walk over all FDS and copy the signaled flag if set. */
+void _gpgme_fdtable_set_signaled (io_select_t fds, unsigned int nfds);
/* Remove FD from the table. This also runs the close handlers. */
gpg_error_t _gpgme_fdtable_remove (int fd);
+/* Return the number of active I/O callbacks for OWNER. */
+unsigned int _gpgme_fdtable_io_cb_count (uint64_t owner);
+
+/* Run all the signaled IO callbacks of OWNER. */
+gpg_error_t _gpgme_fdtable_run_io_cbs (uint64_t owner, gpg_error_t *r_op_err);
+
+/* Return a list of FDs matching the OWNER and FLAGS. */
+unsigned int _gpgme_fdtable_get_fds (io_select_t *r_fds,
+ uint64_t owner, unsigned int flags);
+
+/* Return the status info for the entry of OWNER. */
+uint64_t _gpgme_fdtable_get_done (uint64_t owner, gpg_error_t *r_status,
+ gpg_error_t *r_op_err);
#endif /*GPGME_FDTABLE_H*/
diff --git a/src/gpgme.c b/src/gpgme.c
index 283290f4..19489023 100644
--- a/src/gpgme.c
+++ b/src/gpgme.c
@@ -135,7 +135,6 @@ gpgme_new (gpgme_ctx_t *r_ctx)
ctx->include_certs = GPGME_INCLUDE_CERTS_DEFAULT;
ctx->protocol = GPGME_PROTOCOL_OpenPGP;
ctx->sub_protocol = GPGME_PROTOCOL_DEFAULT;
- _gpgme_fd_table_init (&ctx->fdt);
LOCK (context_list_lock);
if (def_lc_ctype)
@@ -218,36 +217,43 @@ _gpgme_get_ctx (uint64_t serial, gpgme_ctx_t *r_ctx)
*r_ctx = ctx;
return err;
}
+
+
+
+/* Cancel the context indetified with SERIAL. Pass CTX_ERR or OP_ERR
+ * down to the engine. */
gpgme_error_t
-_gpgme_cancel_with_err (gpgme_ctx_t ctx, gpg_error_t ctx_err,
+_gpgme_cancel_with_err (uint64_t serial, gpg_error_t ctx_err,
gpg_error_t op_err)
{
gpgme_error_t err;
+ gpgme_ctx_t ctx;
struct gpgme_io_event_done_data data;
TRACE_BEG (DEBUG_CTX, "_gpgme_cancel_with_err", NULL,
"ctx=%lu ctx_err=%i op_err=%i",
- CTXSERIAL (ctx), ctx_err, op_err);
+ (unsigned long)serial, ctx_err, op_err);
- if (ctx_err)
- {
- err = _gpgme_engine_cancel (ctx->engine);
- if (err)
- return TRACE_ERR (err);
- }
+ LOCK (context_list_lock);
+ for (ctx = context_list; ctx; ctx = ctx->next_ctx)
+ if (ctx->serial == serial)
+ break;
+ UNLOCK (context_list_lock);
+
+ if (!ctx)
+ err = gpg_error (GPG_ERR_NO_OBJ);
+ else if (ctx_err)
+ err = _gpgme_engine_cancel (ctx->engine);
else
+ err = _gpgme_engine_cancel_op (ctx->engine);
+
+ if (!err)
{
- err = _gpgme_engine_cancel_op (ctx->engine);
- if (err)
- return TRACE_ERR (err);
+ data.err = ctx_err;
+ data.op_err = op_err;
+ _gpgme_engine_io_event (ctx->engine, GPGME_EVENT_DONE, &data);
}
-
- data.err = ctx_err;
- data.op_err = op_err;
-
- _gpgme_engine_io_event (ctx->engine, GPGME_EVENT_DONE, &data);
-
- return TRACE_ERR (0);
+ return TRACE_ERR (err);
}
@@ -262,7 +268,7 @@ gpgme_cancel (gpgme_ctx_t ctx)
if (!ctx)
return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE));
- err = _gpgme_cancel_with_err (ctx, gpg_error (GPG_ERR_CANCELED), 0);
+ err = _gpgme_cancel_with_err (ctx->serial, gpg_error (GPG_ERR_CANCELED), 0);
return TRACE_ERR (err);
}
@@ -317,7 +323,7 @@ gpgme_release (gpgme_ctx_t ctx)
_gpgme_engine_release (ctx->engine);
ctx->engine = NULL;
- _gpgme_fd_table_deinit (&ctx->fdt);
+ /* FIXME: Remove stale FDs belonging to us? */
_gpgme_release_result (ctx);
_gpgme_signers_clear (ctx);
_gpgme_sig_notation_clear (ctx);
@@ -972,17 +978,17 @@ gpgme_set_io_cbs (gpgme_ctx_t ctx, gpgme_io_cbs_t io_cbs)
CTXSERIAL (ctx),
io_cbs, io_cbs->add, io_cbs->add_priv, io_cbs->remove,
io_cbs->event, io_cbs->event_priv);
- ctx->io_cbs = *io_cbs;
+ ctx->user_io_cbs = *io_cbs;
}
else
{
TRACE (DEBUG_CTX, "gpgme_set_io_cbs", NULL,
"ctx=%lu io_cbs=%p (default)", CTXSERIAL (ctx), io_cbs);
- ctx->io_cbs.add = NULL;
- ctx->io_cbs.add_priv = NULL;
- ctx->io_cbs.remove = NULL;
- ctx->io_cbs.event = NULL;
- ctx->io_cbs.event_priv = NULL;
+ ctx->user_io_cbs.add = NULL;
+ ctx->user_io_cbs.add_priv = NULL;
+ ctx->user_io_cbs.remove = NULL;
+ ctx->user_io_cbs.event = NULL;
+ ctx->user_io_cbs.event_priv = NULL;
}
}
@@ -1054,7 +1060,7 @@ gpgme_get_io_cbs (gpgme_ctx_t ctx, gpgme_io_cbs_t io_cbs)
io_cbs, io_cbs->add, io_cbs->add_priv, io_cbs->remove,
io_cbs->event, io_cbs->event_priv);
- *io_cbs = ctx->io_cbs;
+ *io_cbs = ctx->user_io_cbs;
}
diff --git a/src/op-support.c b/src/op-support.c
index 6affb5e4..874827e1 100644
--- a/src/op-support.c
+++ b/src/op-support.c
@@ -173,7 +173,7 @@ _gpgme_op_reset (gpgme_ctx_t ctx, int type)
return err;
}
- if (type == 1 || (type == 2 && !ctx->io_cbs.add))
+ if (type == 1 || (type == 2 && !ctx->user_io_cbs.add))
{
/* Use private event loop. */
io_cbs.add = _gpgme_add_io_cb;
@@ -182,7 +182,7 @@ _gpgme_op_reset (gpgme_ctx_t ctx, int type)
io_cbs.event = _gpgme_wait_private_event_cb;
io_cbs.event_priv = ctx;
}
- else if (! ctx->io_cbs.add)
+ else if (!ctx->user_io_cbs.add)
{
/* Use global event loop. */
io_cbs.add = _gpgme_add_io_cb;
@@ -194,9 +194,9 @@ _gpgme_op_reset (gpgme_ctx_t ctx, int type)
else
{
/* Use user event loop. */
- io_cbs.add = _gpgme_wait_user_add_io_cb;
+ io_cbs.add = _gpgme_add_io_cb_user;
io_cbs.add_priv = ctx;
- io_cbs.remove = _gpgme_wait_user_remove_io_cb;
+ io_cbs.remove = _gpgme_remove_io_cb_user;
io_cbs.event = _gpgme_wait_user_event_cb;
io_cbs.event_priv = ctx;
}
diff --git a/src/posix-io.c b/src/posix-io.c
index b754ac34..5d9edf10 100644
--- a/src/posix-io.c
+++ b/src/posix-io.c
@@ -622,10 +622,14 @@ _gpgme_io_spawn (const char *path, char *const argv[], unsigned int flags,
}
-/* Select on the list of fds. Returns: -1 = error, 0 = timeout or
- nothing to select, > 0 = number of signaled fds. */
+/* Select on the list of fds.
+ *
+ * Returns: -1 = error,
+ * 0 = timeout or nothing to select,
+ * > 0 = number of signaled fds.
+ */
int
-_gpgme_io_select (struct io_select_fd_s *fds, size_t nfds, int nonblock)
+_gpgme_io_select (io_select_t fds, unsigned int nfds, int nonblock)
{
fd_set readfds;
fd_set writefds;
@@ -638,7 +642,7 @@ _gpgme_io_select (struct io_select_fd_s *fds, size_t nfds, int nonblock)
struct timeval timeout = { 1, 0 };
void *dbg_help = NULL;
TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_select", NULL,
- "nfds=%zu, nonblock=%u", nfds, nonblock);
+ "nfds=%u, nonblock=%u", nfds, nonblock);
FD_ZERO (&readfds);
FD_ZERO (&writefds);
@@ -736,6 +740,7 @@ _gpgme_io_select (struct io_select_fd_s *fds, size_t nfds, int nonblock)
return TRACE_SYSRES (count);
}
+
int
_gpgme_io_recvmsg (int fd, struct msghdr *msg, int flags)
diff --git a/src/priv-io.h b/src/priv-io.h
index f40cdffc..27cc07eb 100644
--- a/src/priv-io.h
+++ b/src/priv-io.h
@@ -51,14 +51,15 @@ struct spawn_fd_item_s
int arg_loc;
};
-struct io_select_fd_s
+struct io_select_s
{
int fd;
- int for_read;
- int for_write;
- int signaled;
- void *opaque;
+ unsigned int for_read:1;
+ unsigned int for_write:1;
+ unsigned int signaled:1;
};
+typedef struct io_select_s *io_select_t;
+
/* These function are either defined in posix-io.c or w32-io.c. */
void _gpgme_io_subsystem_init (void);
@@ -89,7 +90,7 @@ int _gpgme_io_spawn (const char *path, char *const argv[], unsigned int flags,
void (*atfork) (void *opaque, int reserved),
void *atforkvalue, pid_t *r_pid);
-int _gpgme_io_select (struct io_select_fd_s *fds, size_t nfds, int nonblock);
+int _gpgme_io_select (io_select_t fds, unsigned int nfds, int nonblock);
/* Write the printable version of FD to the buffer BUF of length
BUFLEN. The printable version is the representation on the command
diff --git a/src/verify.c b/src/verify.c
index 8aa9d281..f6bc23c4 100644
--- a/src/verify.c
+++ b/src/verify.c
@@ -911,7 +911,7 @@ parse_error (gpgme_signature_t sig, char *args, int set_status)
&& gpg_err_code (err) == GPG_ERR_BAD_DATA)
{
/* This indicates a double plaintext. The only solid way to
- handle this is by failing the oepration. */
+ handle this is by failing the operation. */
return gpg_error (GPG_ERR_BAD_DATA);
}
else if (!set_status)
diff --git a/src/wait-global.c b/src/wait-global.c
deleted file mode 100644
index e88962e0..00000000
--- a/src/wait-global.c
+++ /dev/null
@@ -1,401 +0,0 @@
-/* wait-global.c
- * Copyright (C) 2000 Werner Koch (dd9jn)
- * Copyright (C) 2001, 2002, 2003, 2004, 2005 g10 Code GmbH
- *
- * This file is part of GPGME.
- *
- * GPGME is free software; you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as
- * published by the Free Software Foundation; either version 2.1 of
- * the License, or (at your option) any later version.
- *
- * GPGME is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program; if not, see <https://gnu.org/licenses/>.
- * SPDX-License-Identifier: LGPL-2.1-or-later
- */
-
-#if HAVE_CONFIG_H
-#include <config.h>
-#endif
-#include <stdlib.h>
-#include <assert.h>
-#include <string.h>
-#include <errno.h>
-
-#include "gpgme.h"
-#include "sema.h"
-#include "util.h"
-#include "context.h"
-#include "wait.h"
-#include "priv-io.h"
-#include "ops.h"
-#include "debug.h"
-
-/* The global event 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. */
-
-/* The ctx_list_lock protects the list of active and done contexts.
- Insertion into any of these lists is only allowed when the lock is
- held. This allows a muli-threaded program to loop over gpgme_wait
- and in parallel start asynchronous gpgme operations.
-
- However, the fd tables in the contexts are not protected by this
- lock. They are only allowed to change either before the context is
- added to the active list (ie, before the start event is signalled)
- or in a callback handler. */
-DEFINE_STATIC_LOCK (ctx_list_lock);
-
-/* A ctx_list_item is an item in the global list of active or done
- contexts. */
-struct ctx_list_item
-{
- /* Every ctx_list_item is an element in a doubly linked list. The
- list pointers are protected by the ctx_list_lock. */
- struct ctx_list_item *next;
- struct ctx_list_item *prev;
-
- gpgme_ctx_t ctx;
- /* The status is set when the ctx is moved to the done list. */
- gpgme_error_t status;
- gpgme_error_t op_err;
-};
-
-/* The active list contains all contexts that are in the global event
- loop, have active I/O callbacks, and have already seen the start
- event. */
-static struct ctx_list_item *ctx_active_list;
-
-/* The done list contains all contexts that have previously been
- active but now are not active any longer, either because they
- finished successfully or an I/O callback returned an error. The
- status field in the list item contains the error value (or 0 if
- successful). */
-static struct ctx_list_item *ctx_done_list;
-
-
-/* Enter the context CTX into the active list. */
-static gpgme_error_t
-ctx_active (gpgme_ctx_t ctx)
-{
- struct ctx_list_item *li = malloc (sizeof (struct ctx_list_item));
- if (!li)
- return gpg_error_from_syserror ();
- li->ctx = ctx;
-
- LOCK (ctx_list_lock);
- /* Add LI to active list. */
- li->next = ctx_active_list;
- li->prev = NULL;
- if (ctx_active_list)
- ctx_active_list->prev = li;
- ctx_active_list = li;
- UNLOCK (ctx_list_lock);
- return 0;
-}
-
-
-/* Enter the context CTX into the done list with status STATUS. */
-static void
-ctx_done (gpgme_ctx_t ctx, gpgme_error_t status, gpgme_error_t op_err)
-{
- struct ctx_list_item *li;
-
- LOCK (ctx_list_lock);
- li = ctx_active_list;
- while (li && li->ctx != ctx)
- li = li->next;
- assert (li);
-
- /* Remove LI from active list. */
- if (li->next)
- li->next->prev = li->prev;
- if (li->prev)
- li->prev->next = li->next;
- else
- ctx_active_list = li->next;
-
- li->status = status;
- li->op_err = op_err;
-
- /* Add LI to done list. */
- li->next = ctx_done_list;
- li->prev = NULL;
- if (ctx_done_list)
- ctx_done_list->prev = li;
- ctx_done_list = li;
- UNLOCK (ctx_list_lock);
-}
-
-
-/* Find finished context CTX (or any context if CTX is NULL) and
- return its status in STATUS after removing it from the done list.
- If a matching context could be found, return it. Return NULL if no
- context could be found. */
-static gpgme_ctx_t
-ctx_wait (gpgme_ctx_t ctx, gpgme_error_t *status, gpgme_error_t *op_err)
-{
- struct ctx_list_item *li;
-
- LOCK (ctx_list_lock);
- li = ctx_done_list;
- if (ctx)
- {
- /* A specific context is requested. */
- while (li && li->ctx != ctx)
- li = li->next;
- }
- if (li)
- {
- ctx = li->ctx;
- if (status)
- *status = li->status;
- if (op_err)
- *op_err = li->op_err;
-
- /* Remove LI from done list. */
- if (li->next)
- li->next->prev = li->prev;
- if (li->prev)
- li->prev->next = li->next;
- else
- ctx_done_list = li->next;
- free (li);
- }
- else
- ctx = NULL;
- UNLOCK (ctx_list_lock);
- return ctx;
-}
-
-
-/* Internal I/O callback functions. */
-
-/* The add_io_cb and remove_io_cb handlers are shared with the private
- event loops. */
-
-void
-_gpgme_wait_global_event_cb (void *data, gpgme_event_io_t type,
- void *type_data)
-{
- gpgme_ctx_t ctx = (gpgme_ctx_t) data;
-
- assert (ctx);
-
- switch (type)
- {
- case GPGME_EVENT_START:
- {
- gpgme_error_t err = ctx_active (ctx);
-
- if (err)
- /* An error occurred. Close all fds in this context, and
- send the error in a done event. */
- _gpgme_cancel_with_err (ctx, err, 0);
- }
- break;
-
- case GPGME_EVENT_DONE:
- {
- gpgme_io_event_done_data_t done_data =
- (gpgme_io_event_done_data_t) type_data;
-
- ctx_done (ctx, 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;
- }
-}
-
-
-
-/* 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)
-{
- do
- {
- unsigned int i = 0;
- struct ctx_list_item *li;
- struct fd_table fdt;
- int nr;
-
- /* Collect the active file descriptors. */
- LOCK (ctx_list_lock);
- for (li = ctx_active_list; li; li = li->next)
- i += li->ctx->fdt.size;
- fdt.fds = malloc (i * sizeof (struct io_select_fd_s));
- if (!fdt.fds)
- {
- int saved_err = gpg_error_from_syserror ();
- UNLOCK (ctx_list_lock);
- if (status)
- *status = saved_err;
- if (op_err)
- *op_err = 0;
- return NULL;
- }
- fdt.size = i;
- i = 0;
- for (li = ctx_active_list; li; li = li->next)
- {
- memcpy (&fdt.fds[i], li->ctx->fdt.fds,
- li->ctx->fdt.size * sizeof (struct io_select_fd_s));
- i += li->ctx->fdt.size;
- }
- UNLOCK (ctx_list_lock);
-
- nr = _gpgme_io_select (fdt.fds, fdt.size, 0);
- if (nr < 0)
- {
- int saved_err = gpg_error_from_syserror ();
- free (fdt.fds);
- if (status)
- *status = saved_err;
- if (op_err)
- *op_err = 0;
- return NULL;
- }
-
- for (i = 0; i < fdt.size && nr; i++)
- {
- if (fdt.fds[i].fd != -1 && fdt.fds[i].signaled)
- {
- gpgme_ctx_t ictx;
- gpgme_error_t err = 0;
- gpgme_error_t local_op_err = 0;
- struct wait_item_s *item;
-
- assert (nr);
- nr--;
-
- item = (struct wait_item_s *) fdt.fds[i].opaque;
- assert (item);
- ictx = item->ctx;
- assert (ictx);
-
- LOCK (ctx->lock);
- if (ctx->canceled)
- err = gpg_error (GPG_ERR_CANCELED);
- UNLOCK (ctx->lock);
-
- if (!err)
- err = _gpgme_run_io_cb (&fdt.fds[i], 0, &local_op_err);
- if (err || local_op_err)
- {
- /* An error occurred. Close all fds in this context,
- and signal it. */
- _gpgme_cancel_with_err (ictx, err, local_op_err);
-
- /* Break out of the loop, and retry the select()
- from scratch, because now all fds should be
- gone. */
- break;
- }
- }
- }
- free (fdt.fds);
-
- /* Now some contexts might have finished successfully. */
- LOCK (ctx_list_lock);
- retry:
- for (li = ctx_active_list; li; li = li->next)
- {
- gpgme_ctx_t actx = li->ctx;
-
- for (i = 0; i < actx->fdt.size; i++)
- if (actx->fdt.fds[i].fd != -1)
- break;
- if (i == actx->fdt.size)
- {
- struct gpgme_io_event_done_data data;
- data.err = 0;
- data.op_err = 0;
-
- /* FIXME: This does not perform too well. We have to
- release the lock because the I/O event handler
- acquires it to remove the context from the active
- list. Two alternative strategies are worth
- considering: Either implement the DONE event handler
- here in a lock-free manner, or save a list of all
- contexts to be released and call the DONE events
- afterwards. */
- UNLOCK (ctx_list_lock);
- _gpgme_engine_io_event (actx->engine, GPGME_EVENT_DONE, &data);
- LOCK (ctx_list_lock);
- goto retry;
- }
- }
- UNLOCK (ctx_list_lock);
-
- {
- gpgme_ctx_t dctx = ctx_wait (ctx, status, op_err);
-
- if (dctx)
- {
- ctx = dctx;
- hang = 0;
- }
- else if (!hang)
- {
- ctx = NULL;
- if (status)
- *status = 0;
- if (op_err)
- *op_err = 0;
- }
- }
- }
- while (hang);
-
- 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);
-}
diff --git a/src/wait-private.c b/src/wait-private.c
deleted file mode 100644
index 417cb2b3..00000000
--- a/src/wait-private.c
+++ /dev/null
@@ -1,180 +0,0 @@
-/* wait-private.c
- * Copyright (C) 2000 Werner Koch (dd9jn)
- * Copyright (C) 2001, 2002, 2003, 2004, 2005 g10 Code GmbH
- *
- * This file is part of GPGME.
- *
- * GPGME is free software; you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as
- * published by the Free Software Foundation; either version 2.1 of
- * the License, or (at your option) any later version.
- *
- * GPGME is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program; if not, see <https://gnu.org/licenses/>.
- * SPDX-License-Identifier: LGPL-2.1-or-later
- */
-
-#if HAVE_CONFIG_H
-#include <config.h>
-#endif
-#include <assert.h>
-#include <errno.h>
-
-#include "gpgme.h"
-#include "context.h"
-#include "wait.h"
-#include "ops.h"
-#include "priv-io.h"
-#include "util.h"
-#include "debug.h"
-
-
-/* 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. */
-
-
-/* Internal I/O callback functions. */
-
-/* The add_io_cb and remove_io_cb handlers are shared with the global
- event loops. */
-
-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;
- }
-}
-
-
-/* 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_wait_on_condition (gpgme_ctx_t ctx, volatile int *cond,
- gpgme_error_t *op_err_p)
-{
- gpgme_error_t err = 0;
- int hang = 1;
-
- if (op_err_p)
- *op_err_p = 0;
-
- do
- {
- int nr = _gpgme_io_select (ctx->fdt.fds, ctx->fdt.size, 0);
- unsigned int i;
-
- 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, err, 0);
-
- return err;
- }
-
- for (i = 0; i < ctx->fdt.size && nr; i++)
- {
- if (ctx->fdt.fds[i].fd != -1 && ctx->fdt.fds[i].signaled)
- {
- gpgme_error_t op_err = 0;
-
- ctx->fdt.fds[i].signaled = 0;
- assert (nr);
- nr--;
-
- LOCK (ctx->lock);
- if (ctx->canceled)
- err = gpg_error (GPG_ERR_CANCELED);
- UNLOCK (ctx->lock);
-
- if (!err)
- err = _gpgme_run_io_cb (&ctx->fdt.fds[i], 0, &op_err);
- if (err)
- {
- /* An error occurred. Close all fds in this context,
- and signal it. */
- _gpgme_cancel_with_err (ctx, err, 0);
-
- return err;
- }
- else if (op_err)
- {
- /* An operational error occurred. Cancel the current
- operation but not the session, and signal it. */
- _gpgme_cancel_with_err (ctx, 0, op_err);
-
- /* NOTE: This relies on the operational error being
- generated after the operation really has
- completed, for example after no further status
- line output is generated. Otherwise the
- following I/O will spill over into the next
- operation. */
- if (op_err_p)
- *op_err_p = op_err;
- return 0;
- }
- }
- }
-
- for (i = 0; i < ctx->fdt.size; i++)
- if (ctx->fdt.fds[i].fd != -1)
- break;
- if (i == ctx->fdt.size)
- {
- struct gpgme_io_event_done_data data;
- data.err = 0;
- data.op_err = 0;
- _gpgme_engine_io_event (ctx->engine, GPGME_EVENT_DONE, &data);
- hang = 0;
- }
- if (cond && *cond)
- hang = 0;
- }
- while (hang);
-
- return 0;
-}
-
-
-/* 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);
-}
diff --git a/src/wait-user.c b/src/wait-user.c
deleted file mode 100644
index 2a42170c..00000000
--- a/src/wait-user.c
+++ /dev/null
@@ -1,133 +0,0 @@
-/* wait-user.c
- * Copyright (C) 2000 Werner Koch (dd9jn)
- * Copyright (C) 2001, 2002, 2003, 2004, 2005 g10 Code GmbH
- *
- * This file is part of GPGME.
- *
- * GPGME is free software; you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as
- * published by the Free Software Foundation; either version 2.1 of
- * the License, or (at your option) any later version.
- *
- * GPGME is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program; if not, see <https://gnu.org/licenses/>.
- * SPDX-License-Identifier: LGPL-2.1-or-later
- */
-
-#if HAVE_CONFIG_H
-#include <config.h>
-#endif
-#include <assert.h>
-
-#include "gpgme.h"
-#include "context.h"
-#include "priv-io.h"
-#include "wait.h"
-#include "ops.h"
-#include "debug.h"
-
-
-/* The user event loops are used for all asynchronous operations for
- which a user callback is defined. */
-
-
-/* Internal I/O Callbacks. */
-
-gpgme_error_t
-_gpgme_user_io_cb_handler (void *data, int fd)
-{
- gpgme_error_t err = 0;
- gpgme_error_t op_err = 0;
- struct tag *tag = (struct tag *) data;
- gpgme_ctx_t ctx;
-
- (void)fd;
-
- assert (data);
- ctx = tag->ctx;
- assert (ctx);
-
- LOCK (ctx->lock);
- if (ctx->canceled)
- err = gpg_error (GPG_ERR_CANCELED);
- UNLOCK (ctx->lock);
-
- if (! err)
- err = _gpgme_run_io_cb (&ctx->fdt.fds[tag->idx], 0, &op_err);
- if (err || op_err)
- _gpgme_cancel_with_err (ctx, err, op_err);
- else
- {
- unsigned int i;
-
- for (i = 0; i < ctx->fdt.size; i++)
- if (ctx->fdt.fds[i].fd != -1)
- break;
-
- if (i == ctx->fdt.size)
- {
- struct gpgme_io_event_done_data done_data;
-
- done_data.err = 0;
- done_data.op_err = 0;
- _gpgme_engine_io_event (ctx->engine, GPGME_EVENT_DONE, &done_data);
- }
- }
- 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. */
-gpgme_error_t
-_gpgme_wait_user_add_io_cb (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 tag *tag;
- gpgme_error_t err;
-
- assert (ctx);
- err = _gpgme_add_io_cb (data, fd, dir, fnc, fnc_data, r_tag);
- if (err)
- return err;
- tag = *r_tag;
- assert (tag);
- err = (*ctx->io_cbs.add) (ctx->io_cbs.add_priv, fd, dir,
- _gpgme_user_io_cb_handler, *r_tag,
- &tag->user_tag);
- if (err)
- _gpgme_remove_io_cb (*r_tag);
- return err;
-}
-
-
-void
-_gpgme_wait_user_remove_io_cb (void *data)
-{
- struct tag *tag = (struct tag *) data;
- gpgme_ctx_t ctx;
-
- assert (tag);
- ctx = tag->ctx;
-
- (*ctx->io_cbs.remove) (tag->user_tag);
- _gpgme_remove_io_cb (data);
-}
-
-
-void
-_gpgme_wait_user_event_cb (void *data, gpgme_event_io_t type, void *type_data)
-{
- gpgme_ctx_t ctx = data;
-
- if (ctx->io_cbs.event)
- (*ctx->io_cbs.event) (ctx->io_cbs.event_priv, type, type_data);
-}
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);
}
diff --git a/src/wait.h b/src/wait.h
index 4beac6e1..b7584cf5 100644
--- a/src/wait.h
+++ b/src/wait.h
@@ -1,6 +1,6 @@
/* wait.h - Definitions for the wait queue interface.
Copyright (C) 2000 Werner Koch (dd9jn)
- Copyright (C) 2001, 2002, 2003, 2004 g10 Code GmbH
+ Copyright (C) 2001, 2002, 2003, 2004. 2019 g10 Code GmbH
This file is part of GPGME.
@@ -25,58 +25,46 @@
#include "gpgme.h"
#include "sema.h"
-struct fd_table
+/* A registered fd handler can be removed using the tag that
+ * identifies it. In the public API that tag is an an opaque
+ * pointer. */
+struct io_cb_tag_s
{
- struct io_select_fd_s *fds;
- size_t size;
-};
-typedef struct fd_table *fd_table_t;
+ /* The s/n of the context for which the fd was registered. */
+ uint64_t serial;
-/* Wait items are hooked into the io_select_fd_s to connect an fd with
- a callback handler. */
-struct wait_item_s
-{
- gpgme_ctx_t ctx;
- gpgme_io_cb_t handler;
- void *handler_value;
- int dir;
-};
-
-/* A registered fd handler is removed later using the tag that
- identifies it. */
-struct tag
-{
- /* The context for which the fd was registered. */
- gpgme_ctx_t ctx;
-
- /* The index into the fd table for this context. */
- int idx;
+ /* The actual fd. */
+ int fd;
/* This is used by the wrappers for the user event loop. */
void *user_tag;
-};
+ /* A string used describing the data. This is used for tracing. */
+ const char *desc;
+};
-void _gpgme_fd_table_init (fd_table_t fdt);
-void _gpgme_fd_table_deinit (fd_table_t fdt);
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_io_cb_t fnc, void *fnc_data,
+ void **r_tag);
+gpgme_error_t _gpgme_add_io_cb_user (void *data, int fd, int dir,
+ gpgme_io_cb_t fnc, void *fnc_data,
+ void **r_tag);
+
void _gpgme_remove_io_cb (void *tag);
-void _gpgme_wait_private_event_cb (void *data, gpgme_event_io_t type,
- void *type_data);
+void _gpgme_remove_io_cb_user (void *tag);
+
+
void _gpgme_wait_global_event_cb (void *data, gpgme_event_io_t type,
void *type_data);
-gpgme_error_t _gpgme_wait_user_add_io_cb (void *data, int fd, int dir,
- gpgme_io_cb_t fnc, void *fnc_data,
- void **r_tag);
-void _gpgme_wait_user_remove_io_cb (void *tag);
+
+void _gpgme_wait_private_event_cb (void *data, gpgme_event_io_t type,
+ void *type_data);
void _gpgme_wait_user_event_cb (void *data, gpgme_event_io_t type,
void *type_data);
-gpgme_error_t _gpgme_run_io_cb (struct io_select_fd_s *an_fds, int checked,
- gpgme_error_t *err);
+
/* Session based interfaces require to make a distinction between IPC