/*
 * Copyright (c) 2009 Mark Heily <mark@heily.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */
#include "private.h"

#if HAVE_LINUX_SOCKIOS_H
# include <linux/sockios.h>
#endif

#include <sys/ioctl.h>
#include <sys/socket.h>

#ifndef SIOCINQ
# define SIOCINQ FIONREAD
#endif

/*
 * Return the offset from the current position to end of file.
 *
 * lseek() and fstat() are not atomic, so curpos can legitimately
 * exceed st_size in two cases:
 *
 *   1. The file was truncated/shrunk between the two calls.
 *   2. The caller seeked past the existing EOF (sparse-file write
 *      idiom; lseek() is allowed to return a position beyond the
 *      current file size).
 *
 * Both cases used to underflow the signed subtraction and return
 * a large negative intptr_t, which (a) lied to the caller and (b)
 * caused the dst->data == 0 EOF-detection branch in
 * evfilt_read_copyout() to miss, leaving a spinning knote
 * registered.  Clamp to zero so EOF is reported correctly.
 *
 * fstat() failure path is left as-is (sb.st_size = 1) so the
 * caller's next read() surfaces the real underlying errno rather
 * than us silently masking it as EOF.
 *
 * struct kevent.data is intptr_t per the BSD spec, so the return
 * type is correct on the wire; no widening to int64_t needed.
 */
static intptr_t
get_eof_offset(int fd)
{
    off_t curpos;
    struct stat sb;

    curpos = lseek(fd, 0, SEEK_CUR);
    if (curpos == (off_t) -1) {
        dbg_perror("lseek(2)");
        curpos = 0;
    }
    if (fstat(fd, &sb) < 0) {
        dbg_perror("fstat(2)");
        sb.st_size = 1;
    }

    dbg_printf("curpos=%zu size=%zu", (size_t)curpos, (size_t)sb.st_size);
    if (curpos >= sb.st_size)
        return 0;

    return (intptr_t) (sb.st_size - curpos);
}

int
evfilt_read_copyout(struct kevent *dst, UNUSED int nevents, struct filter *filt,
    struct knote *src, void *ptr)
{
    int ret;
    int serr;
    socklen_t slen = sizeof(serr);
    struct epoll_event * const ev = (struct epoll_event *) ptr;

    /* Special case: for regular files, return the offset from current position to end of file */
    if (src->kn_flags & KNFL_FILE) {
        memcpy(dst, &src->kev, sizeof(*dst));
        dst->data = get_eof_offset(src->kev.ident);

        if (dst->data == 0) {
            dst->filter = 0;    /* Will cause the kevent to be discarded */
            if (epoll_ctl(src->kn_epollfd, EPOLL_CTL_DEL, src->kn_read.eventfd, NULL) < 0) {
                dbg_perror("epoll_ctl(2)");
                return (-1);
            }
            src->kn_registered = 0;
            /*
             * TODO: when EOF is reported once, switch to monitoring
             * IN_ATTRIB on the file via inotify so a subsequent size
             * change re-arms delivery.  Currently we just stop firing.
             */
        }

        return (1);
    }

    dbg_printf("epoll_ev=%s", epoll_event_dump(ev));
    memcpy(dst, &src->kev, sizeof(*dst));
#if defined(HAVE_EPOLLRDHUP)
    if (ev->events & EPOLLRDHUP || ev->events & EPOLLHUP)
        dst->flags |= EV_EOF;
#else
    if (ev->events & EPOLLHUP)
        dst->flags |= EV_EOF;
#endif
    if (ev->events & EPOLLERR) {
        if (src->kn_flags & KNFL_SOCKET) {
            ret = getsockopt(src->kev.ident, SOL_SOCKET, SO_ERROR, &serr, &slen);
            dst->fflags = ((ret < 0) ? errno : serr);
        } else
            dst->fflags = EIO;

        /*
         * The only way we seem to be able to signal an error
         * is by setting EOF on the socket.
         */
        dst->flags |= EV_EOF;
    }

    if (src->kn_flags & KNFL_SOCKET_PASSIVE) {
        /*
         * On return, data contains the length of the
           socket backlog. This is not available under Linux.
         */
        dst->data = 1;
    } else {
        /*
         * On return, data contains the number of bytes of protocol
           data available to read.
         */
        int i;
        if (ioctl(dst->ident, SIOCINQ, &i) < 0) {
            /* race condition with socket close, so ignore this error */
            dbg_puts("ioctl(2) of socket failed");
            dst->data = 0;
        } else {
            dst->data = i;
            if (dst->data == 0 && src->kn_flags & KNFL_SOCKET_STREAM)
                dst->flags |= EV_EOF;
        }
    }

    if (knote_copyout_flag_actions(filt, src) < 0) return -1;

    return (1);
}

int
evfilt_read_knote_create(struct filter *filt, struct knote *kn)
{
    /* TODO: kn_create arms before EV_DISABLE - see kevent_copyin_one EV_ADD|EV_DISABLE race. */
    if (linux_get_descriptor_type(kn) < 0)
        return (-1);

    /* Convert the kevent into an epoll_event */
#if defined(HAVE_EPOLLRDHUP)
    kn->epoll_events = EPOLLIN | EPOLLRDHUP;
#else
    kn->epoll_events = EPOLLIN;
#endif
    if (kn->kev.flags & EV_CLEAR)
        kn->epoll_events |= EPOLLET;

    /*
     * NOTE_LOWAT: kernel-level threshold via SO_RCVLOWAT.  Per-socket,
     * not per-knote; sharing an fd between knotes with different
     * thresholds gets last-writer-wins.  BSD does this per-knote in
     * the filter; we accept the divergence to avoid userspace gating.
     */
    if ((kn->kev.fflags & NOTE_LOWAT) && !(kn->kn_flags & KNFL_FILE)) {
        const int lwm = (int) kn->kev.data;
        if (setsockopt(kn->kev.ident, SOL_SOCKET, SO_RCVLOWAT, &lwm, sizeof(lwm)) < 0)
            dbg_perror("setsockopt(SO_RCVLOWAT)");
    }

    /* Special case: for regular files, add a surrogate eventfd that is always readable */
    if (kn->kn_flags & KNFL_FILE) {
        int evfd;

        /*
         * We only set oneshot for cases where we're not going to
         * be using EPOLL_CTL_MOD.
         *
         * We rely on the common code disabling the event after
         * it's fired once.
         *
         * See this SO post for details:
         * https://stackoverflow.com/questions/59517961/how-should-i-use-epoll-to-read-and-write-from-the-same-fd
         */
        if (kn->kev.flags & EV_ONESHOT || kn->kev.flags & EV_DISPATCH)
            kn->epoll_events |= EPOLLONESHOT;

        kn->kn_epollfd = filter_epoll_fd(filt);
        evfd = eventfd(0, EFD_CLOEXEC);
        if (evfd < 0) {
            dbg_perror("eventfd(2)");
            return (-1);
        }
        if (eventfd_write(evfd, 1) < 0) {
            dbg_perror("eventfd_write(3)");
            (void) close(evfd);
            return (-1);
        }

        kn->kn_read.eventfd = evfd;

        KN_UDATA_ALLOC(kn);   /* populate this knote's kn_udata field */
        if (epoll_ctl(kn->kn_epollfd, EPOLL_CTL_ADD, kn->kn_read.eventfd, EPOLL_EV_KN(kn->epoll_events, kn)) < 0) {
            dbg_printf("epoll_ctl(2): %s", strerror(errno));
            (void) close(evfd);
            /* Kernel never accepted the registration; free direct. */
            KN_UDATA_FREE(kn);
            return (-1);
        }

        kn->kn_registered = 1;

        return (0);
    }

    return epoll_update(EPOLL_CTL_ADD, filt, kn, kn->epoll_events, false);
}

int
evfilt_read_knote_modify(UNUSED struct filter *filt, struct knote *kn,
        const struct kevent *kev)
{
    if (!(kn->kn_flags & KNFL_FILE)) {
        /*
         * This should reset the EOF sate of the socket.
         * but it's not even clear what that really means.
         *
         * With the native kqueue implementations it
         * basically does nothing.
         */
        if ((kev->flags & EV_CLEAR)) {
            /*
             * Sync SO_RCVLOWAT against the new fflags.  The kernel
             * setting is per-socket and survives across knotes; if
             * the modify clears NOTE_LOWAT or changes the threshold
             * we have to push the new value (or restore the default
             * 1) so the kernel gate matches the knote.
             */
            if (kn->kn_flags & KNFL_SOCKET) {
                int new_lwm = (kev->fflags & NOTE_LOWAT) ? (int) kev->data : 1;
                int old_lwm = (kn->kev.fflags & NOTE_LOWAT) ? (int) kn->kev.data : 1;
                if (new_lwm != old_lwm &&
                    setsockopt(kn->kev.ident, SOL_SOCKET, SO_RCVLOWAT,
                               &new_lwm, sizeof(new_lwm)) < 0)
                    dbg_perror("setsockopt(SO_RCVLOWAT) on EV_MODIFY");
            }

            /*
             * Common code only copies udata (and conditionally
             * EV_DISPATCH) after kn_modify.  Sync fflags/data
             * (NOTE_LOWAT and friends are caller-mutable), but
             * leave kev.flags alone: BSD treats EV_CLEAR as
             * register-only on sockets and EV_RECEIPT is sticky.
             */
            kn->kev.fflags = kev->fflags;
            kn->kev.data   = kev->data;
            return (0);
        }
        return (-1);
    }

    return (-1);
}

int
evfilt_read_knote_delete(struct filter *filt, struct knote *kn)
{
    /*
     * Restore the default SO_RCVLOWAT on socket fds the knote bumped.
     * Skipped for KNFL_FILE because the kn->kev.ident there is the
     * watched file, not a socket.
     */
    if ((kn->kev.fflags & NOTE_LOWAT) && (kn->kn_flags & KNFL_SOCKET)) {
        const int one = 1;
        if (setsockopt(kn->kev.ident, SOL_SOCKET, SO_RCVLOWAT, &one, sizeof(one)) < 0)
            dbg_perror("setsockopt(restore SO_RCVLOWAT) on EV_DELETE");
    }

    if ((kn->kn_flags & KNFL_FILE) && (kn->kn_read.eventfd != -1)) {
        if (kn->kn_registered && epoll_ctl(kn->kn_epollfd, EPOLL_CTL_DEL, kn->kn_read.eventfd, NULL) < 0) {
            dbg_perror("epoll_ctl(2)");
            return (-1);
        }
        kn->kn_registered = 0;

        /*
         * KNFL_FILE path uses a per-knote eventfd registration with
         * its own kn_udata.  The shared-fd_state path below uses
         * fds_udata, which is freed inside epoll_fd_state_del.
         */
        KN_UDATA_DEFER_FREE(filt->kf_kqueue, kn);

        (void) close(kn->kn_read.eventfd);
        kn->kn_read.eventfd = -1;
        return (0);
    }

    return epoll_update(EPOLL_CTL_DEL, filt, kn, EPOLLIN, true);
}

int
evfilt_read_knote_enable(struct filter *filt, struct knote *kn)
{
    if (kn->kn_flags & KNFL_FILE) {
        if (epoll_ctl(kn->kn_epollfd, EPOLL_CTL_ADD, kn->kn_read.eventfd, EPOLL_EV_KN(kn->epoll_events, kn)) < 0) {
            dbg_perror("epoll_ctl(2)");
            return (-1);
        }
        kn->kn_registered = 1;
        return (0);
    }

    return epoll_update(EPOLL_CTL_ADD, filt, kn, kn->epoll_events, false);
}

int
evfilt_read_knote_disable(struct filter *filt, struct knote *kn)
{
    if (kn->kn_flags & KNFL_FILE) {
        if (epoll_ctl(kn->kn_epollfd, EPOLL_CTL_DEL, kn->kn_read.eventfd, NULL) < 0) {
            dbg_perror("epoll_ctl(2)");
            return (-1);
        }
        kn->kn_registered = 0;
        return (0);
    }

    return epoll_update(EPOLL_CTL_DEL, filt, kn, EPOLLIN, false);
}

const struct filter evfilt_read = {
    .kf_id      = EVFILT_READ,
    .kf_copyout = evfilt_read_copyout,
    .kn_create  = evfilt_read_knote_create,
    .kn_modify  = evfilt_read_knote_modify,
    .kn_delete  = evfilt_read_knote_delete,
    .kn_enable  = evfilt_read_knote_enable,
    .kn_disable = evfilt_read_knote_disable,
};
