/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
 * poll(2) file descriptor monitoring
 *
 * Uses ppoll(2) when available, g_poll() otherwise.
 */

#include "qemu/osdep.h"
#include "aio-posix.h"
#include "qemu/rcu_queue.h"

/*
 * These thread-local variables are used only in fdmon_poll_wait() around the
 * call to the poll() system call.  In particular they are not used while
 * aio_poll is performing callbacks, which makes it much easier to think about
 * reentrancy!
 *
 * Stack-allocated arrays would be perfect but they have size limitations;
 * heap allocation is expensive enough that we want to reuse arrays across
 * calls to aio_poll().  And because poll() has to be called without holding
 * any lock, the arrays cannot be stored in AioContext.  Thread-local data
 * has none of the disadvantages of these three options.
 */
static __thread GPollFD *pollfds;
static __thread AioHandler **nodes;
static __thread unsigned npfd, nalloc;
static __thread Notifier pollfds_cleanup_notifier;

static void pollfds_cleanup(Notifier *n, void *unused)
{
    g_assert(npfd == 0);
    g_free(pollfds);
    g_free(nodes);
    nalloc = 0;
}

static void add_pollfd(AioHandler *node)
{
    if (npfd == nalloc) {
        if (nalloc == 0) {
            pollfds_cleanup_notifier.notify = pollfds_cleanup;
            qemu_thread_atexit_add(&pollfds_cleanup_notifier);
            nalloc = 8;
        } else {
            g_assert(nalloc <= INT_MAX);
            nalloc *= 2;
        }
        pollfds = g_renew(GPollFD, pollfds, nalloc);
        nodes = g_renew(AioHandler *, nodes, nalloc);
    }
    nodes[npfd] = node;
    pollfds[npfd] = (GPollFD) {
        .fd = node->pfd.fd,
        .events = node->pfd.events,
    };
    npfd++;
}

static int fdmon_poll_wait(AioContext *ctx, AioHandlerList *ready_list,
                            int64_t timeout)
{
    AioHandler *node;
    int ret;

    assert(npfd == 0);

    QLIST_FOREACH_RCU(node, &ctx->aio_handlers, node) {
        if (!QLIST_IS_INSERTED(node, node_deleted) && node->pfd.events) {
            add_pollfd(node);
        }
    }

    /* epoll(7) is faster above a certain number of fds */
    if (fdmon_epoll_try_upgrade(ctx, npfd)) {
        QLIST_FOREACH_RCU(node, &ctx->aio_handlers, node) {
            if (!QLIST_IS_INSERTED(node, node_deleted) && node->pfd.events) {
                g_source_remove_poll(&ctx->source, &node->pfd);
            }
        }
        npfd = 0; /* we won't need pollfds[], reset npfd */
        return ctx->fdmon_ops->wait(ctx, ready_list, timeout);
    }

    ret = qemu_poll_ns(pollfds, npfd, timeout);
    if (ret > 0) {
        int i;

        for (i = 0; i < npfd; i++) {
            int revents = pollfds[i].revents;

            if (revents) {
                aio_add_ready_handler(ready_list, nodes[i], revents);
            }
        }
    }

    npfd = 0;
    return ret;
}

static void fdmon_poll_update(AioContext *ctx,
                              AioHandler *old_node,
                              AioHandler *new_node)
{
    if (old_node) {
        /*
         * If the GSource is in the process of being destroyed then
         * g_source_remove_poll() causes an assertion failure.  Skip removal in
         * that case, because glib cleans up its state during destruction
         * anyway.
         */
        if (!g_source_is_destroyed(&ctx->source)) {
            g_source_remove_poll(&ctx->source, &old_node->pfd);
        }
    }

    if (new_node) {
        g_source_add_poll(&ctx->source, &new_node->pfd);
    }
}

static void fdmon_poll_gsource_prepare(AioContext *ctx)
{
    /* Do nothing */
}

static bool fdmon_poll_gsource_check(AioContext *ctx)
{
    AioHandler *node;
    bool result = false;

    /*
     * We have to walk very carefully in case aio_set_fd_handler is
     * called while we're walking.
     */
    qemu_lockcnt_inc(&ctx->list_lock);

    QLIST_FOREACH_RCU(node, &ctx->aio_handlers, node) {
        int revents = node->pfd.revents & node->pfd.events;

        if (revents & (G_IO_IN | G_IO_HUP | G_IO_ERR) && node->io_read) {
            result = true;
            break;
        }
        if (revents & (G_IO_OUT | G_IO_ERR) && node->io_write) {
            result = true;
            break;
        }
    }

    qemu_lockcnt_dec(&ctx->list_lock);

    return result;
}

static void fdmon_poll_gsource_dispatch(AioContext *ctx,
                                        AioHandlerList *ready_list)
{
    AioHandler *node;

    QLIST_FOREACH_RCU(node, &ctx->aio_handlers, node) {
        int revents = node->pfd.revents;

        if (revents) {
            aio_add_ready_handler(ready_list, node, revents);
        }
    }
}

const FDMonOps fdmon_poll_ops = {
    .update = fdmon_poll_update,
    .wait = fdmon_poll_wait,
    .need_wait = aio_poll_disabled,
    .gsource_prepare = fdmon_poll_gsource_prepare,
    .gsource_check = fdmon_poll_gsource_check,
    .gsource_dispatch = fdmon_poll_gsource_dispatch,
};

void fdmon_poll_downgrade(AioContext *ctx)
{
    AioHandler *node;

    ctx->fdmon_ops = &fdmon_poll_ops;

    QLIST_FOREACH_RCU(node, &ctx->aio_handlers, node) {
        if (!QLIST_IS_INSERTED(node, node_deleted) && node->pfd.events) {
            g_source_add_poll(&ctx->source, &node->pfd);
        }
    }
}
