diff --git a/src/fdtable.c b/src/fdtable.c index 13d6ef94..ecec3477 100644 --- a/src/fdtable.c +++ b/src/fdtable.c @@ -1,680 +1,714 @@ /* fdtable.c - Keep track of file descriptors. * Copyright (C) 2019 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 . * SPDX-License-Identifier: LGPL-2.1-or-later */ #if HAVE_CONFIG_H #include #endif #include #include #include "gpgme.h" #include "debug.h" #include "context.h" #include "fdtable.h" /* The table to hold information about file descriptors. Currently we * use a linear search and extend the table as needed. Eventually we * may swicth to a hash table and allocate items on the fly. */ 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; void *value; } close_notify; }; typedef struct fdtable_item_s *fdtable_item_t; /* The actual table, its size and the lock to guard access. */ static fdtable_item_t fdtable; static unsigned int fdtablesize; DEFINE_STATIC_LOCK (fdtable_lock); /* Insert FD into our file descriptor table. This function checks * that FD is not yet in the table. On success 0 is returned; if FD * is already in the table GPG_ERR_DUP_KEY is returned. Other error * codes may also be returned. */ gpg_error_t _gpgme_fdtable_insert (int fd) { gpg_error_t err; int firstunused, idx; TRACE_BEG (DEBUG_SYSIO, __func__, NULL, "fd=%d", fd); if (fd < 0 ) return TRACE_ERR (gpg_error (GPG_ERR_INV_ARG)); LOCK (fdtable_lock); firstunused = -1; for (idx=0; idx < fdtablesize; idx++) if (fdtable[idx].fd == -1) { if (firstunused == -1) firstunused = idx; } else if (fdtable[idx].fd == fd) { err = gpg_error (GPG_ERR_DUP_KEY); goto leave; } if (firstunused == -1) { /* We need to increase the size of the table. The approach we * take is straightforward to minimize the risk of bugs. */ fdtable_item_t newtbl; size_t newsize = fdtablesize + 64; newtbl = calloc (newsize, sizeof *newtbl); if (!newtbl) { err = gpg_error_from_syserror (); goto leave; } for (idx=0; idx < fdtablesize; idx++) newtbl[idx] = fdtable[idx]; for (; idx < newsize; idx++) newtbl[idx].fd = -1; free (fdtable); fdtable = newtbl; idx = fdtablesize; fdtablesize = newsize; } else 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; leave: UNLOCK (fdtable_lock); return TRACE_ERR (err); } /* Add the close notification HANDLER to the table under the key FD. * FD must exist. VALUE is a pointer passed to the handler along with * the FD. */ gpg_error_t _gpgme_fdtable_add_close_notify (int fd, fdtable_handler_t handler, void *value) { gpg_error_t err; int idx; TRACE_BEG (DEBUG_SYSIO, __func__, NULL, "fd=%d", fd); if (fd < 0 ) return TRACE_ERR (gpg_error (GPG_ERR_INV_ARG)); LOCK (fdtable_lock); for (idx=0; idx < fdtablesize; idx++) if (fdtable[idx].fd == fd) break; if (idx == fdtablesize) { err = gpg_error (GPG_ERR_NO_KEY); goto leave; } if (fdtable[idx].close_notify.handler) { err = gpg_error (GPG_ERR_DUP_VALUE); goto leave; } fdtable[idx].close_notify.handler = handler; fdtable[idx].close_notify.value = value; err = 0; leave: UNLOCK (fdtable_lock); return TRACE_ERR (err); } /* 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 * fdtable anymore and assume that FD is still there. Callers may * want to handle the error code GPG_ERR_NO_KEY which indicates that * FD is not anymore or not yet in the table. */ gpg_error_t _gpgme_fdtable_remove (int fd) { gpg_error_t err; int idx; fdtable_handler_t handler; void *handlervalue; TRACE_BEG (DEBUG_SYSIO, __func__, NULL, "fd=%d", fd); if (fd < 0 ) return TRACE_ERR (gpg_error (GPG_ERR_INV_ARG)); LOCK (fdtable_lock); for (idx=0; idx < fdtablesize; idx++) if (fdtable[idx].fd == fd) break; if (idx == fdtablesize) { UNLOCK (fdtable_lock); 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; - /* The handler might call into the fdtable again, so of we have a + /* The handler might call into the fdtable again, so if 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); 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. */ +/* Return the number of FDs for OWNER (or for all if OWNER is 0) + * which match FLAGS. Recognized flag values are: + * 0 - Number FDs with IO callbacks + * FDTABLE_FLAG_ACTIVE - Number of FDs in the active state. + * FDTABLE_FLAG_DONE - Number of FDs in the done state. + * FDTABLE_FLAG_NOT_DONE - Number of FDs not in the done state. + */ unsigned int -_gpgme_fdtable_io_cb_count (uint64_t owner) +_gpgme_fdtable_get_count (uint64_t owner, unsigned int flags) { 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++; + { + if (fdtable[idx].closing) + continue; + + if ((flags & FDTABLE_FLAG_DONE) && fdtable[idx].done) + count++; + else if ((flags & FDTABLE_FLAG_NOT_DONE) && !fdtable[idx].done) + count++; + else if ((flags & FDTABLE_FLAG_ACTIVE) && fdtable[idx].active) + count++; + else if (!flags && fdtable[idx].io_cb.cb) + count++; + } UNLOCK (fdtable_lock); - TRACE (DEBUG_SYSIO, __func__, NULL, "ctx=%lu count=%u", - (unsigned long)owner, count); + TRACE (DEBUG_SYSIO, __func__, NULL, "ctx=%lu flags=0x%u -> count=%u", + (unsigned long)owner, flags, 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. */ + * can be stored there. For EOF the respective flags are set. */ gpg_error_t -_gpgme_fdtable_run_io_cbs (uint64_t owner, gpg_error_t *r_op_err) +_gpgme_fdtable_run_io_cbs (uint64_t owner, gpg_error_t *r_op_err, + uint64_t *r_owner) { 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; TRACE_BEG (DEBUG_SYSIO, __func__, NULL, "ctx=%lu", owner); if (r_op_err) *r_op_err = 0; + if (r_owner) + *r_owner = 0; 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); + if (r_owner) + *r_owner = serial; 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; + if (r_owner) + *r_owner = serial; 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_NOT_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); + if (r_status) + *r_status = 0; + if (r_op_err) + *r_op_err = 0; + 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; + TRACE_LOG ("found fd=%d", fdtable[idx].fd); + if (r_status) + *r_status = fdtable[idx].done_status; + if (r_op_err) + *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 6f621849..431c7969 100644 --- a/src/fdtable.h +++ b/src/fdtable.h @@ -1,81 +1,83 @@ /* fdtable.h - Keep track of file descriptors. * Copyright (C) 2019 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 . * SPDX-License-Identifier: LGPL-2.1-or-later */ #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. */ +#define FDTABLE_FLAG_NOT_DONE 4 /* Only those with the done flag cleared. */ +#define FDTABLE_FLAG_FOR_READ 16 /* Only those with the signaled flag set. */ +#define FDTABLE_FLAG_FOR_WRITE 32 /* Only those with the for_read flag set. */ +#define FDTABLE_FLAG_SIGNALED 64 /* Only those with the signaled flag set. */ +#define FDTABLE_FLAG_NOT_SIGNALED 128 /* Ditto reversed. */ +#define FDTABLE_FLAG_CLEAR 256 /* 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 * errors from close notifications can't inhibit the the closing. */ typedef gpg_error_t (*fdtable_handler_t) (int, void*); /* Insert a new FD into the table. */ gpg_error_t _gpgme_fdtable_insert (int fd); /* Add a close notification handler to the FD item. */ 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); +unsigned int _gpgme_fdtable_get_count (uint64_t owner, unsigned int flags); /* 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); +gpg_error_t _gpgme_fdtable_run_io_cbs (uint64_t owner, gpg_error_t *r_op_err, + uint64_t *r_owner); /* 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/wait.c b/src/wait.c index b88b7813..091a93b9 100644 --- a/src/wait.c +++ b/src/wait.c @@ -1,458 +1,497 @@ /* wait.c * Copyright (C) 2000 Werner Koch (dd9jn) * Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007 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 . * SPDX-License-Identifier: LGPL-2.1-or-later */ #if HAVE_CONFIG_H #include #endif #include #include #include #include #ifdef HAVE_SYS_TYPES_H # include #endif #include "util.h" #include "context.h" #include "ops.h" #include "wait.h" #include "sema.h" #include "priv-io.h" #include "engine.h" #include "debug.h" #include "fdtable.h" /* 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) { 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; assert (data); serial = tag->serial; assert (serial); - err = _gpgme_fdtable_run_io_cbs (serial, &op_err); + err = _gpgme_fdtable_run_io_cbs (serial, &op_err, NULL); if (err || op_err) ; - else if (!_gpgme_fdtable_io_cb_count (serial)) + else if (!_gpgme_fdtable_get_count (serial, 0)) { /* 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); } 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 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; 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); tag = calloc (1, sizeof *tag); if (!tag) return gpg_error_from_syserror (); tag->serial = ctx->serial; tag->fd = fd; err = _gpgme_fdtable_set_io_cb (fd, ctx->serial, dir, fnc, fnc_data); if (err) { free (tag); return TRACE_ERR (err); } *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 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; assert (tag); _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); 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; 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; if (ctx->user_io_cbs.event) ctx->user_io_cbs.event (ctx->user_io_cbs.event_priv, type, type_data); } /* 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; + TRACE_BEG (DEBUG_SYSIO, __func__, NULL, "ctx=%lu hang=%d", + CTXSERIAL (ctx), hang); + do { /* Get all fds of CTX (or all if CTX is NULL) we want to wait * for and which are in the active state. */ + TRACE_LOG + ("active=%u done=%u !done=%d cbs=%u", + _gpgme_fdtable_get_count (ctx?ctx->serial:0,FDTABLE_FLAG_ACTIVE), + _gpgme_fdtable_get_count (ctx?ctx->serial:0,FDTABLE_FLAG_DONE), + _gpgme_fdtable_get_count (ctx?ctx->serial:0,FDTABLE_FLAG_NOT_DONE), + _gpgme_fdtable_get_count (ctx?ctx->serial:0,0)); + 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; + ctx = NULL; + goto leave; } - /* Nothing to select. Run the select anyway, so that we use - * its timeout. */ + /* Nothing to select. */ + + if (!_gpgme_fdtable_get_count (ctx? ctx->serial : 0, + FDTABLE_FLAG_NOT_DONE)) + { + if (status) + *status = 0; + if (op_err) + *op_err = 0; + goto leave; + } + /* There are not yet active FDS. Use the select's standard + * timeout and then try again. */ } 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; + ctx = NULL; + goto leave; } _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) + err = _gpgme_fdtable_run_io_cbs (ctx? ctx->serial : 0, op_err, &serial); + if (err || (op_err && *op_err)) { - _gpgme_get_ctx (serial, &ctx); + if (status) + *status = err; + if (serial) + _gpgme_get_ctx (serial, &ctx); hang = 0; } - else if (!hang) + else { - ctx = NULL; - if (status) - *status = 0; - if (op_err) - *op_err = 0; + 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); + leave: free (fds); + if (status) + TRACE_LOG ("status=%d", *status); + if (op_err) + TRACE_LOG ("op_err=%d", *op_err); + TRACE_SUC ("result=%lu", ctx? ctx->serial : 0); 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); } /* Wait until the blocking operation in context CTX has finished and * return the error value. If COND is not NULL return early if COND * is satisfied. A session based error will be returned at R_OP_ERR * if it is not NULL. */ gpgme_error_t _gpgme_sync_wait (gpgme_ctx_t ctx, volatile int *cond, gpg_error_t *r_op_err) { gpgme_error_t err = 0; int hang = 1; io_select_t fds = NULL; unsigned int nfds; int nr; TRACE_BEG (DEBUG_SYSIO, __func__, NULL, "ctx=%lu", CTXSERIAL (ctx)); if (r_op_err) *r_op_err = 0; if (!ctx) { err = gpg_error (GPG_ERR_INV_VALUE); goto leave; } do { /* 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) goto leave; } else { nr = _gpgme_io_select (fds, nfds, 0); 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); goto leave; } _gpgme_fdtable_set_signaled (fds, nfds); - err = _gpgme_fdtable_run_io_cbs (ctx->serial, r_op_err); + err = _gpgme_fdtable_run_io_cbs (ctx->serial, r_op_err, NULL); if (err || (r_op_err && *r_op_err)) goto leave; } - if (!_gpgme_fdtable_io_cb_count (ctx->serial)) + if (!_gpgme_fdtable_get_count (ctx->serial, 0)) { /* No more matching fds with IO callbacks. */ 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); err = 0; leave: free (fds); return TRACE_ERR (err); } diff --git a/tests/gpg/t-wait.c b/tests/gpg/t-wait.c index c19502cd..0428e8d8 100644 --- a/tests/gpg/t-wait.c +++ b/tests/gpg/t-wait.c @@ -1,76 +1,76 @@ /* t-wait.c - Regression test. * Copyright (C) 2000 Werner Koch (dd9jn) * Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007 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 . * SPDX-License-Identifier: LGPL-2.1-or-later */ /* We need to include config.h so that we know whether we are building with large file system (LFS) support. */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #ifdef HAVE_W32_SYSTEM #define sleep _sleep #endif #include #include "t-support.h" int main (void) { gpgme_ctx_t ctx; gpgme_error_t err; gpgme_data_t sig, text; init_gpgme (GPGME_PROTOCOL_OpenPGP); err = gpgme_new (&ctx); fail_if_err (err); /* Checking a message without a signature. */ err = gpgme_data_new_from_mem (&sig, "foo\n", 4, 0); fail_if_err (err); err = gpgme_data_new (&text); fail_if_err (err); err = gpgme_op_verify_start (ctx, sig, NULL, text); fail_if_err (err); while (gpgme_wait (ctx, &err, 0) == NULL && err == 0) - sleep(1); + ; if (gpgme_err_code (err) != GPG_ERR_NO_DATA) { fprintf (stderr, "%s:%d: %s: %s\n", __FILE__, __LINE__, gpgme_strsource (err), gpgme_strerror (err)); exit (1); } gpgme_data_release (sig); gpgme_data_release (text); gpgme_release (ctx); return 0; }