From 8daa83a594a2e98f39d764422bfbdbc62c9efd44 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 19:20:00 +0200 Subject: Adding upstream version 2:4.20.0+dfsg. Signed-off-by: Daniel Baumann --- source3/lib/tevent_glib_glue.c | 881 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 881 insertions(+) create mode 100644 source3/lib/tevent_glib_glue.c (limited to 'source3/lib/tevent_glib_glue.c') diff --git a/source3/lib/tevent_glib_glue.c b/source3/lib/tevent_glib_glue.c new file mode 100644 index 0000000..3be08fc --- /dev/null +++ b/source3/lib/tevent_glib_glue.c @@ -0,0 +1,881 @@ +/* + Unix SMB/CIFS implementation. + Integration of a glib g_main_context into a tevent_context + Copyright (C) Stefan Metzmacher 2016 + Copyright (C) Ralph Boehme 2016 + + ** NOTE! The following LGPL license applies to the tevent + ** library. This does NOT imply that all of Samba is released + ** under the LGPL + + This library 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 3 of the License, or (at your option) any later version. + + This library 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 library; if not, see . +*/ + +#include "replace.h" +#include "system/filesys.h" +#include "lib/util/debug.h" +#include "lib/util/select.h" +#include + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_TEVENT + +#ifdef HAVE_GLIB +#include +#include "tevent_glib_glue.h" + +struct fd_map { + struct tevent_glib_glue *glue; + int fd; + struct tevent_fd *fd_event; +}; + +struct tevent_glib_glue { + /* + * The tevent context we're feeding. + */ + struct tevent_context *ev; + + /* + * The glib gmain context we're polling and whether we're currently + * owning it by virtue of g_main_context_acquire(). + */ + GMainContext *gmain_ctx; + bool gmain_owner; + + /* + * Set by samba_tevent_glib_glue_quit(). + */ + bool quit; + + /* + * tevent trace callback and data we got from tevent_get_trace_callback() + * before installing our own trace callback. + */ + tevent_trace_callback_t prev_tevent_trace_cb; + void *prev_tevent_trace_data; + + /* + * Don't call tevent_glib_prepare() in the tevent tracepoint handler if + * explicitly told so. This is an optimisation for the case that glib + * event sources are created from glib event callbacks. + */ + bool skip_glib_refresh; + + /* + * Used when acquiring the glib gmain context failed. + */ + struct tevent_timer *acquire_retry_timer; + + /* + * glib gmain context timeout and priority for the current event look + * iteration. gtimeout is translated to a tevent timer event, unless it + * is 0 which signals some event source is pending. In that case we + * dispatch an immediate event. gpriority is ignored by us, just passed + * to the glib relevant functions. + */ + gint gtimeout; + gint gpriority; + struct tevent_timer *timer; + struct tevent_immediate *im; + bool scheduled_im; + + /* + * glib gmain context fds returned from g_main_context_query(). These + * get translated to tevent fd events. + */ + GPollFD *gpollfds; + gint num_gpollfds; + + /* + * A copy of gpollfds and num_gpollfds from the previous event loop + * iteration, used to detect changes in the set of fds. + */ + GPollFD *prev_gpollfds; + gint num_prev_gpollfds; + + /* + * An array of pointers to fd_map's. The fd_map'd contain the tevent + * event fd as well as a pointer to the corresponding glib GPollFD. + */ + struct fd_map **fd_map; + size_t num_maps; +}; + +static bool tevent_glib_prepare(struct tevent_glib_glue *glue); +static bool tevent_glib_process(struct tevent_glib_glue *glue); +static bool tevent_glib_glue_reinit(struct tevent_glib_glue *glue); +static void tevent_glib_fd_handler(struct tevent_context *ev, + struct tevent_fd *fde, + uint16_t flags, + void *private_data); + +typedef int (*gfds_cmp_cb)(const void *fd1, const void *fd2); +typedef bool (*gfds_found_cb)(struct tevent_glib_glue *glue, + const GPollFD *new, + const GPollFD *old); +typedef bool (*gfds_new_cb)(struct tevent_glib_glue *glue, + const GPollFD *fd); +typedef bool (*gfds_removed_cb)(struct tevent_glib_glue *glue, + const GPollFD *fd); + +/** + * Compare two sorted GPollFD arrays + * + * For every element that exists in gfds and prev_gfds found_fn() is called. + * For every element in gfds but not in prev_gfds, new_fn() is called. + * For every element in prev_gfds but not in gfds removed_fn() is called. + **/ +static bool cmp_gfds(struct tevent_glib_glue *glue, + GPollFD *gfds, + GPollFD *prev_gfds, + size_t num_gfds, + size_t num_prev_gfds, + gfds_cmp_cb cmp_cb, + gfds_found_cb found_cb, + gfds_new_cb new_cb, + gfds_removed_cb removed_cb) +{ + bool ok; + size_t i = 0, j = 0; + int cmp; + + while (i < num_gfds && j < num_prev_gfds) { + cmp = cmp_cb(&gfds[i], &prev_gfds[j]); + if (cmp == 0) { + ok = found_cb(glue, &gfds[i], &prev_gfds[j]); + if (!ok) { + return false; + } + i++; + j++; + } else if (cmp < 0) { + ok = new_cb(glue, &gfds[i]); + if (!ok) { + return false; + } + i++; + } else { + ok = removed_cb(glue, &prev_gfds[j]); + if (!ok) { + return false; + } + j++; + } + } + + while (i < num_gfds) { + ok = new_cb(glue, &gfds[i++]); + if (!ok) { + return false; + } + } + + while (j < num_prev_gfds) { + ok = removed_cb(glue, &prev_gfds[j++]); + if (!ok) { + return false; + } + } + + return true; +} + +static int glib_fd_cmp_func(const void *p1, const void *p2) +{ + const GPollFD *lhs = p1; + const GPollFD *rhs = p2; + + if (lhs->fd < rhs->fd) { + return -1; + } else if (lhs->fd > rhs->fd) { + return 1; + } + + return 0; +} + +/* + * We already have a tevent fd event for the glib GPollFD, but we may have to + * update flags. + */ +static bool match_gfd_cb(struct tevent_glib_glue *glue, + const GPollFD *new_gfd, + const GPollFD *old_gfd) +{ + size_t i; + struct fd_map *fd_map = NULL; + struct tevent_fd *fd_event = NULL; + + if (new_gfd->events == old_gfd->events) { + return true; + } + + for (i = 0; i < glue->num_maps; i++) { + if (glue->fd_map[i]->fd == new_gfd->fd) { + break; + } + } + + if (i == glue->num_maps) { + DBG_ERR("match_gfd_cb: glib fd %d not in map\n", new_gfd->fd); + return false; + } + + fd_map = glue->fd_map[i]; + if (fd_map == NULL) { + DBG_ERR("fd_map for fd %d is NULL\n", new_gfd->fd); + return false; + } + + fd_event = fd_map->fd_event; + if (fd_event == NULL) { + DBG_ERR("fd_event for fd %d is NULL\n", new_gfd->fd); + return false; + } + + tevent_fd_set_flags(fd_event, 0); + + if (new_gfd->events & (G_IO_IN | G_IO_HUP | G_IO_ERR)) { + TEVENT_FD_READABLE(fd_event); + } + if (new_gfd->events & G_IO_OUT) { + TEVENT_FD_WRITEABLE(fd_event); + } + + return true; +} + +static bool new_gfd_cb(struct tevent_glib_glue *glue, const GPollFD *gfd) +{ + struct tevent_fd *fd_event = NULL; + struct fd_map *fd_map = NULL; + uint16_t events = 0; + bool revent; + bool wevent; + + revent = (gfd->events & (G_IO_IN | G_IO_HUP | G_IO_ERR)); + wevent = (gfd->events & G_IO_OUT); + if (revent) { + events |= TEVENT_FD_READ; + } + if (wevent) { + events |= TEVENT_FD_WRITE; + } + + glue->fd_map = talloc_realloc(glue, + glue->fd_map, + struct fd_map *, + glue->num_maps + 1); + if (glue->fd_map == NULL) { + DBG_ERR("talloc_realloc failed\n"); + return false; + } + fd_map = talloc_zero(glue->fd_map, struct fd_map); + if (fd_map == NULL) { + DBG_ERR("talloc_realloc failed\n"); + return false; + } + glue->fd_map[glue->num_maps] = fd_map; + glue->num_maps++; + + fd_event = tevent_add_fd(glue->ev, + glue->fd_map, + gfd->fd, + events, + tevent_glib_fd_handler, + fd_map); + if (fd_event == NULL) { + DBG_ERR("tevent_add_fd failed\n"); + return false; + } + + *fd_map = (struct fd_map) { + .glue = glue, + .fd = gfd->fd, + .fd_event = fd_event, + }; + + DBG_DEBUG("added tevent_fd for glib fd %d\n", gfd->fd); + + return true; +} + +static bool remove_gfd_cb(struct tevent_glib_glue *glue, const GPollFD *gfd) +{ + size_t i; + + for (i = 0; i < glue->num_maps; i++) { + if (glue->fd_map[i]->fd == gfd->fd) { + break; + } + } + + if (i == glue->num_maps) { + DBG_ERR("remove_gfd_cb: glib fd %d not in map\n", gfd->fd); + return false; + } + + TALLOC_FREE(glue->fd_map[i]->fd_event); + TALLOC_FREE(glue->fd_map[i]); + + if (i + 1 < glue->num_maps) { + memmove(&glue->fd_map[i], + &glue->fd_map[i+1], + (glue->num_maps - (i + 1)) * sizeof(struct fd_map *)); + } + + glue->fd_map = talloc_realloc(glue, + glue->fd_map, + struct fd_map *, + glue->num_maps - 1); + if (glue->num_maps > 0 && glue->fd_map == NULL) { + DBG_ERR("talloc_realloc failed\n"); + return false; + } + glue->num_maps--; + + return true; +} + +static short gpoll_to_poll_event(gushort gevent) +{ + short pevent = 0; + + if (gevent & G_IO_IN) { + pevent |= POLLIN; + } + if (gevent & G_IO_OUT) { + pevent |= POLLOUT; + } + if (gevent & G_IO_HUP) { + pevent |= POLLHUP; + } + if (gevent & G_IO_ERR) { + pevent |= POLLERR; + } + + return pevent; +} + +static gushort poll_to_gpoll_event(short pevent) +{ + gushort gevent = 0; + + if (pevent & POLLIN) { + gevent |= G_IO_IN; + } + if (pevent & POLLOUT) { + gevent |= G_IO_OUT; + } + if (pevent & POLLHUP) { + gevent |= G_IO_HUP; + } + if (pevent & POLLERR) { + gevent |= G_IO_ERR; + } + + return gevent; +} + +static void tevent_glib_fd_handler(struct tevent_context *ev, + struct tevent_fd *fde, + uint16_t flags, + void *private_data) +{ + struct fd_map *fd_map = talloc_get_type_abort( + private_data, struct fd_map); + struct tevent_glib_glue *glue = NULL; + GPollFD *gpollfd = NULL; + struct pollfd fd; + int ret; + int i; + + glue = fd_map->glue; + + for (i = 0; i < glue->num_gpollfds; i++) { + if (glue->gpollfds[i].fd != fd_map->fd) { + continue; + } + gpollfd = &glue->gpollfds[i]; + break; + } + if (gpollfd == NULL) { + DBG_ERR("No gpollfd for fd_map [%p] fd [%d]\n", + fd_map, fd_map->fd); + return; + } + /* + * We have to poll() the fd to get the correct fd event for glib. tevent + * only tells us about readable/writable in flags, but we need the full + * glory for glib. + */ + + fd = (struct pollfd) { + .fd = gpollfd->fd, + .events = gpoll_to_poll_event(gpollfd->events), + }; + + ret = sys_poll_intr(&fd, 1, 0); + if (ret == -1) { + DBG_ERR("poll: %s\n", strerror(errno)); + return; + } + if (ret == 0) { + return; + } + + gpollfd->revents = poll_to_gpoll_event(fd.revents); + + tevent_glib_process(glue); + return; +} + +static void tevent_glib_timer_handler(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *private_data) +{ + struct tevent_glib_glue *glue = talloc_get_type_abort( + private_data, struct tevent_glib_glue); + + glue->timer = NULL; + tevent_glib_process(glue); + return; +} + +static void tevent_glib_im_handler(struct tevent_context *ev, + struct tevent_immediate *im, + void *private_data) +{ + struct tevent_glib_glue *glue = talloc_get_type_abort( + private_data, struct tevent_glib_glue); + + glue->scheduled_im = false; + tevent_glib_process(glue); + return; +} + +static bool save_current_fdset(struct tevent_glib_glue *glue) +{ + /* + * Save old glib fds. We only grow the prev array. + */ + + if (glue->num_prev_gpollfds < glue->num_gpollfds) { + glue->prev_gpollfds = talloc_realloc(glue, + glue->prev_gpollfds, + GPollFD, + glue->num_gpollfds); + if (glue->prev_gpollfds == NULL) { + DBG_ERR("talloc_realloc failed\n"); + return false; + } + } + glue->num_prev_gpollfds = glue->num_gpollfds; + if (glue->num_gpollfds > 0) { + memcpy(glue->prev_gpollfds, glue->gpollfds, + sizeof(GPollFD) * glue->num_gpollfds); + memset(glue->gpollfds, 0, sizeof(GPollFD) * glue->num_gpollfds); + } + + return true; +} + +static bool get_glib_fds_and_timeout(struct tevent_glib_glue *glue) +{ + bool ok; + gint num_fds; + + ok = save_current_fdset(glue); + if (!ok) { + return false; + } + + while (true) { + num_fds = g_main_context_query(glue->gmain_ctx, + glue->gpriority, + &glue->gtimeout, + glue->gpollfds, + glue->num_gpollfds); + if (num_fds == glue->num_gpollfds) { + break; + } + glue->gpollfds = talloc_realloc(glue, + glue->gpollfds, + GPollFD, + num_fds); + if (num_fds > 0 && glue->gpollfds == NULL) { + DBG_ERR("talloc_realloc failed\n"); + return false; + } + glue->num_gpollfds = num_fds; + }; + + if (glue->num_gpollfds > 0) { + qsort(glue->gpollfds, + num_fds, + sizeof(GPollFD), + glib_fd_cmp_func); + } + + DBG_DEBUG("num fds: %d, timeout: %d ms\n", + num_fds, glue->gtimeout); + + return true; +} + +static bool tevent_glib_update_events(struct tevent_glib_glue *glue) +{ + struct timeval tv; + bool ok; + + ok = cmp_gfds(glue, + glue->gpollfds, + glue->prev_gpollfds, + glue->num_gpollfds, + glue->num_prev_gpollfds, + glib_fd_cmp_func, + match_gfd_cb, + new_gfd_cb, + remove_gfd_cb); + if (!ok) { + return false; + } + + TALLOC_FREE(glue->timer); + + if (glue->gtimeout == -1) { + return true; + } + + if (glue->gtimeout == 0) { + /* + * glue->gtimeout is 0 if g_main_context_query() returned + * timeout=0. That means there are pending events ready to be + * dispatched. We only want to run one event handler per loop + * iteration, so we schedule an immediate event to run it in the + * next iteration. + */ + if (glue->scheduled_im) { + return true; + } + tevent_schedule_immediate(glue->im, + glue->ev, + tevent_glib_im_handler, + glue); + glue->scheduled_im = true; + return true; + } + + tv = tevent_timeval_current_ofs(glue->gtimeout / 1000, + (glue->gtimeout % 1000) * 1000); + + glue->timer = tevent_add_timer(glue->ev, + glue, + tv, + tevent_glib_timer_handler, + glue); + if (glue->timer == NULL) { + DBG_ERR("tevent_add_timer failed\n"); + return false; + } + + return true; +} + +static void tevent_glib_retry_timer(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *private_data) +{ + struct tevent_glib_glue *glue = talloc_get_type_abort( + private_data, struct tevent_glib_glue); + + glue->acquire_retry_timer = NULL; + (void)tevent_glib_prepare(glue); +} + +/** + * Fetch glib event sources and add them to tevent + * + * Fetch glib event sources and attach corresponding tevent events to our tevent + * context. get_glib_fds_and_timeout() gets the relevant glib event sources: the + * set of active fds and the next timer. tevent_glib_update_events() then + * translates those to tevent and creates tevent events. + * + * When called, the thread must NOT be the owner to the glib main + * context. tevent_glib_prepare() is either the first function when the + * tevent_glib_glue is created, or after tevent_glib_process() has been called + * to process pending event, which will have ceased ownership. + **/ +static bool tevent_glib_prepare(struct tevent_glib_glue *glue) +{ + bool ok; + gboolean gok; + + if (glue->quit) { + /* Set via samba_tevent_glib_glue_quit() */ + return true; + } + + if (glue->acquire_retry_timer != NULL) { + /* + * We're still waiting on the below g_main_context_acquire() to + * succeed, just return. + */ + return true; + } + + if (glue->gmain_owner) { + g_main_context_release(glue->gmain_ctx); + glue->gmain_owner = false; + } + + gok = g_main_context_acquire(glue->gmain_ctx); + if (!gok) { + DBG_ERR("couldn't acquire g_main_context\n"); + + /* + * Ensure no tevent event fires while we're not the gmain + * context owner. The event handler would call + * tevent_glib_process() and that expects being the owner of the + * context. + */ + ok = tevent_glib_glue_reinit(glue); + if (!ok) { + DBG_ERR("tevent_glib_glue_reinit failed\n"); + samba_tevent_glib_glue_quit(glue); + return false; + } + + glue->acquire_retry_timer = tevent_add_timer( + glue->ev, + glue, + tevent_timeval_current_ofs(0, 1000), + tevent_glib_retry_timer, + glue); + if (glue->acquire_retry_timer == NULL) { + DBG_ERR("tevent_add_timer failed\n"); + samba_tevent_glib_glue_quit(glue); + return false; + } + return true; + } + glue->gmain_owner = true; + + /* + * Discard "ready" return value from g_main_context_prepare(). We don't + * want to dispatch events here, that's only done in from the tevent loop. + */ + (void)g_main_context_prepare(glue->gmain_ctx, &glue->gpriority); + + ok = get_glib_fds_and_timeout(glue); + if (!ok) { + DBG_ERR("get_glib_fds_and_timeout failed\n"); + samba_tevent_glib_glue_quit(glue); + return false; + } + + ok = tevent_glib_update_events(glue); + if (!ok) { + DBG_ERR("tevent_glib_update_events failed\n"); + samba_tevent_glib_glue_quit(glue); + return false; + } + + return true; +} + +/** + * Process pending glib events + * + * tevent_glib_process() gets called to process pending glib events via + * g_main_context_check() and then g_main_context_dispatch(). + * + * After pending event handlers are dispatched, we rearm the glib glue event + * handlers in tevent by calling tevent_glib_prepare(). + * + * When tevent_glib_process() is called the thread must own the glib + * gmain_ctx. That is achieved by tevent_glib_prepare() being the only function + * that acquires context ownership. + * + * To give other threads that are blocked on g_main_context_acquire(gmain_ctx) a + * chance to acquire context ownership (eg needed to attach event sources), we + * release context ownership before calling tevent_glib_prepare() which will + * acquire it again. + */ +static bool tevent_glib_process(struct tevent_glib_glue *glue) +{ + bool ok; + + DBG_DEBUG("tevent_glib_process\n"); + + /* + * Ignore the "sources_ready" return from g_main_context_check(). glib + * itself also ignores it in g_main_context_iterate(). In theory only + * calling g_main_context_dispatch() if g_main_context_check() returns + * true should work, but older glib versions had a bug where + * g_main_context_check() returns false even though events are pending. + * + * https://bugzilla.gnome.org/show_bug.cgi?id=11059 + */ + (void)g_main_context_check(glue->gmain_ctx, + glue->gpriority, + glue->gpollfds, + glue->num_gpollfds); + + g_main_context_dispatch(glue->gmain_ctx); + + ok = tevent_glib_prepare(glue); + if (!ok) { + return false; + } + glue->skip_glib_refresh = true; + return true; +} + +static void tevent_glib_glue_trace_callback(enum tevent_trace_point point, + void *private_data) +{ + struct tevent_glib_glue *glue = talloc_get_type_abort( + private_data, struct tevent_glib_glue); + + if (point == TEVENT_TRACE_AFTER_LOOP_ONCE) { + if (!glue->skip_glib_refresh) { + tevent_glib_prepare(glue); + } + glue->skip_glib_refresh = false; + } + + /* chain previous handler */ + if (glue->prev_tevent_trace_cb != NULL) { + glue->prev_tevent_trace_cb(point, glue->prev_tevent_trace_data); + } +} + +static void tevent_glib_glue_cleanup(struct tevent_glib_glue *glue) +{ + size_t n = talloc_array_length(glue->fd_map); + size_t i; + + for (i = 0; i < n; i++) { + TALLOC_FREE(glue->fd_map[i]->fd_event); + TALLOC_FREE(glue->fd_map[i]); + } + + tevent_set_trace_callback(glue->ev, + glue->prev_tevent_trace_cb, + glue->prev_tevent_trace_data); + glue->prev_tevent_trace_cb = NULL; + glue->prev_tevent_trace_data = NULL; + + TALLOC_FREE(glue->fd_map); + glue->num_maps = 0; + + TALLOC_FREE(glue->gpollfds); + glue->num_gpollfds = 0; + + TALLOC_FREE(glue->prev_gpollfds); + glue->num_prev_gpollfds = 0; + + TALLOC_FREE(glue->timer); + TALLOC_FREE(glue->acquire_retry_timer); + TALLOC_FREE(glue->im); + + /* + * These are not really needed, but let's wipe the slate clean. + */ + glue->skip_glib_refresh = false; + glue->gtimeout = 0; + glue->gpriority = 0; +} + +static bool tevent_glib_glue_reinit(struct tevent_glib_glue *glue) +{ + tevent_glib_glue_cleanup(glue); + + glue->im = tevent_create_immediate(glue); + if (glue->im == NULL) { + return false; + } + + tevent_get_trace_callback(glue->ev, + &glue->prev_tevent_trace_cb, + &glue->prev_tevent_trace_data); + tevent_set_trace_callback(glue->ev, + tevent_glib_glue_trace_callback, + glue); + + return true; +} + +void samba_tevent_glib_glue_quit(struct tevent_glib_glue *glue) +{ + tevent_glib_glue_cleanup(glue); + glue->quit = true; + return; +} + +struct tevent_glib_glue *samba_tevent_glib_glue_create(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + GMainContext *gmain_ctx) +{ + bool ok; + struct tevent_glib_glue *glue = NULL; + + glue = talloc_zero(mem_ctx, struct tevent_glib_glue); + if (glue == NULL) { + DBG_ERR("talloc_zero failed\n"); + return NULL; + } + + *glue = (struct tevent_glib_glue) { + .ev = ev, + .gmain_ctx = gmain_ctx, + }; + + glue->im = tevent_create_immediate(glue); + + tevent_get_trace_callback(glue->ev, + &glue->prev_tevent_trace_cb, + &glue->prev_tevent_trace_data); + tevent_set_trace_callback(glue->ev, + tevent_glib_glue_trace_callback, + glue); + + ok = tevent_glib_prepare(glue); + if (!ok) { + TALLOC_FREE(glue); + return NULL; + } + + return glue; +} + +#else /* HAVE_GLIB */ + +struct tevent_glib_glue *samba_tevent_glib_glue_create(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + GMainContext *gmain_ctx) +{ + errno = ENOSYS; + return NULL; +} + +void samba_tevent_glib_glue_quit(struct tevent_glib_glue *glue) +{ + return; +} +#endif /* HAVE_GLIB */ -- cgit v1.2.3