/* -*- mode: c; -*- */

static char const *vdev_linux_config_basename(struct vdev_linux_context const *ctx)
{
    char const *path = ctx->os_ctx->state->config->config_path;
    char const *base = strrchr(path, '/');
    return base ? base + 1 : path;
}

static bool vdev_linux_is_long_running(struct vdev_linux_context const *ctx)
{
	struct vdev_globals const *g = &ctx->os_ctx->state->globals;
	char const *tmp = vdev_linux_config_basename(ctx);

	return g->long_running != NULL && strcmp(tmp, g->long_running) == 0;
}

static void vdev_linux_stop_netlink(struct vdev_linux_context *ctx)
{
    if (ctx->pfd[FD_POLL_NETLINK].fd >= 0) {
        close(ctx->pfd[FD_POLL_NETLINK].fd);
        ctx->pfd[FD_POLL_NETLINK].fd = -1;
        ctx->pfd[FD_POLL_NETLINK].events = 0;
    }
}

// yield the next device event
// return 0 on success
// return 1 if there are no more devices
// return -EAGAIN if vdev should try to get this device again
// return -errno on failure to poll for devices or read the next device packet.
int vdev_os_next_device(struct vdev_device_request *vreq, void *cls)
{
    int rc = 0;
    struct vdev_linux_context *ctx = (struct vdev_linux_context*)cls;
    char buf[VDEV_LINUX_NETLINK_BUF_MAX];
    ssize_t len = 0;
    int timeout = -1;

    char cbuf[CMSG_SPACE(sizeof(struct ucred))];
    struct cmsghdr *chdr = NULL;
    struct ucred *cred = NULL;
    struct msghdr hdr;
    struct iovec iov;
    struct sockaddr_nl cnls;

    pthread_mutex_lock(&ctx->initial_requests_lock);

    /* do we have initial requests? */
    if (ctx->initial_requests) {
        /* next request */
        struct vdev_device_request *req = ctx->initial_requests;
        /* consume */
        ctx->initial_requests = ctx->initial_requests->next;
        memcpy(vreq, req, sizeof(struct vdev_device_request));
        free(req);
        pthread_mutex_unlock(&ctx->initial_requests_lock);
        /* was that the last of them? */
        if (!ctx->initial_requests)
            /* tell vdevd that we've finished coldplug processing */
            vdev_os_context_signal_coldplug_finished(ctx->os_ctx);
        return 0;
    }

    pthread_mutex_unlock(&ctx->initial_requests_lock);

    memset(&hdr, 0, sizeof(struct msghdr));

    // next event (wait forever; SIGTERM wakes via signalfd)
    // NOTE: this is a cancellation point!
    rc = poll(ctx->pfd, FD_POLL_MAX, timeout);

    {
        int last_signo = 0;
        int saw_sigterm = 0;

        while (ctx->pfd[FD_POLL_SIGNAL].fd >= 0) {
            struct signalfd_siginfo info;
            ssize_t n;

            n = read(ctx->pfd[FD_POLL_SIGNAL].fd, &info, sizeof(info));
            if (n != (ssize_t)sizeof(info)) {
                if (n < 0 && errno == EINTR)
                    continue;
                break;
            }
            if (info.ssi_signo == SIGTERM)
                saw_sigterm = 1;
            if (info.ssi_signo == SIGTERM || info.ssi_signo == SIGALRM ||
                info.ssi_signo == SIGHUP || info.ssi_signo == SIGINT ||
                info.ssi_signo == SIGQUIT)
                last_signo = (int)info.ssi_signo;
        }

        if (saw_sigterm) {
            vdev_info("Emergency exit detected\n");
            return SIGTERM;
        }

        if (last_signo != 0) {
            /* SIGINT (Ctrl+C): exit — foreground / coldplug_only test runs. */
            if (last_signo == SIGTERM || last_signo == SIGALRM ||
                last_signo == SIGINT)
                return last_signo;
            /*
             * SIGHUP / SIGQUIT: drain only. Promote and reload use the control
             * socket. SIGHUP stays in the signalfd mask so a stray HUP does not
             * terminate under the default disposition.
             */
            if (last_signo == SIGHUP)
                vdev_debug("%s",
                           "ignoring SIGHUP; use vdevadm control --promote/--reload\n");
            return -EAGAIN;
        }
    }

    if (ctx->pfd[FD_POLL_CONTROL].fd >= 0 &&
        (ctx->pfd[FD_POLL_CONTROL].revents & POLLIN)) {
        /*
         * Drain every pending control connection so a non-blocking listen
         * socket does not keep poll(2) returning immediately with no device.
         */
        for (;;) {
            struct pollfd cpfd = {
                .fd = ctx->pfd[FD_POLL_CONTROL].fd,
                .events = POLLIN,
            };
            int ctrl_rc;

            if (poll(&cpfd, 1, 0) <= 0 || !(cpfd.revents & POLLIN))
                break;

            ctrl_rc = vdev_linux_ctrl_dispatch(ctx);
            if (ctrl_rc == SIGTERM)
                return ctrl_rc;
        }
        return -EAGAIN;
    }

    if (ctx->os_ctx->state->ctrl_pending_signal != 0) {
        int sig = ctx->os_ctx->state->ctrl_pending_signal;

        ctx->os_ctx->state->ctrl_pending_signal = 0;
        return sig;
    }

    if (rc < 0) {
        rc = -errno;
        if (rc == -EINTR)
            /* try again */
            return -EAGAIN;
        vdev_error("FATAL: poll(%d) rc = %d\n", ctx->pfd[FD_POLL_NETLINK].fd, rc);
        return rc;
    } else if (rc == 0 && ctx->os_ctx->state->device_wq.running == false) {
        /* Not 1/2: those are SIGHUP/SIGINT on Linux. */
        return VDEV_OS_NEXT_WQ_IDLE;
    }

    if (ctx->pfd[FD_POLL_NETLINK].fd >= 0 &&
        (ctx->pfd[FD_POLL_NETLINK].revents & POLLIN)) {

        // get the event
        iov.iov_base = buf;
        iov.iov_len = VDEV_LINUX_NETLINK_BUF_MAX;

        hdr.msg_iov = &iov;
        hdr.msg_iovlen = 1;

        // get control-plane messages
        hdr.msg_control = cbuf;
        hdr.msg_controllen = sizeof(cbuf);

        hdr.msg_name = &cnls;
        hdr.msg_namelen = sizeof(cnls);

        // get the event
        len = recvmsg(ctx->pfd[FD_POLL_NETLINK].fd, &hdr, 0);
        if (len < 0) {
            rc = -errno;
            vdev_error("FATAL: recvmsg(%d) rc = %d\n", ctx->pfd[FD_POLL_NETLINK].fd, rc);
            return rc;
        }

        // big enough?
        if (len < 32 || len >= VDEV_LINUX_NETLINK_BUF_MAX) {
            vdev_error("Netlink message is %zd bytes; ignoring...\n", len);
            return -EAGAIN;
        }

        // control message, for credentials
        chdr = CMSG_FIRSTHDR(&hdr);
        if (chdr == NULL || chdr->cmsg_type != SCM_CREDENTIALS) {
            vdev_error("%s", "Netlink message has no credentials\n");
            return -EAGAIN;
        }

        // get the credentials
        cred = (struct ucred *)CMSG_DATA(chdr);
        // if not root, ignore
        if (cred->uid != 0) {
            vdev_error("Ignoring message from non-root ID %d\n", cred->uid);
            return -EAGAIN;
        }

        // if udev, ignore
        if (memcmp(buf, VDEV_LINUX_NETLINK_UDEV_HEADER, VDEV_LINUX_NETLINK_UDEV_HEADER_LEN) == 0) {
            // message from udev; ignore
            vdev_warn("%s", "Ignoring libudev message\n");
            return -EAGAIN;
        }

        // kernel messages don't come from userspace
        if (cnls.nl_pid > 0) {
            // from userspace???
            vdev_warn("Ignoring message from PID %d\n", (int)cnls.nl_pid);
            return -EAGAIN;
        }

        // parse the event buffer
        vdev_debug("%p from netlink\n", vreq);
        rc = vdev_linux_parse_request(ctx, vreq, buf, len);
        if (rc != 0) {
            vdev_error("vdev_linux_parse_request rc = %d\n", rc);
            return -EAGAIN;
        }

        return 0;
    }

    if (rc == 0) {
        /* poll(2) timeout if a finite timeout is ever used again. */
        return -EAGAIN;
    }

    /* poll returned without a filled device request (e.g. spurious wake). */
    return -EAGAIN;
}
