/*
 * 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_SYS_TIMERFD_H != 1

/* Android 4.0 does not have this header, but the kernel supports timerfds */
#ifndef SYS_timerfd_create
#ifdef __ARM_EABI__
#define __NR_timerfd_create             (__NR_SYSCALL_BASE+350)
#define __NR_timerfd_settime            (__NR_SYSCALL_BASE+353)
#define __NR_timerfd_gettime            (__NR_SYSCALL_BASE+354)
#else
#error Unsupported architecture, need to get the syscall numbers
#endif

#define SYS_timerfd_create __NR_timerfd_create
#define SYS_timerfd_settime __NR_timerfd_settime
#define SYS_timerfd_gettime __NR_timerfd_gettime
#endif /* ! SYS_timerfd_create */

/*
 * XXX-FIXME
   These are horrible hacks that are only known to be true on RHEL 5 x86.
 */
#ifndef SYS_timerfd_settime
#define SYS_timerfd_settime (SYS_timerfd_create + 1)
#endif
#ifndef SYS_timerfd_gettime
#define SYS_timerfd_gettime (SYS_timerfd_create + 2)
#endif

int timerfd_create(int clockid, int flags)
{
  return syscall(SYS_timerfd_create, clockid, flags);
}

int timerfd_settime(int ufc, int flags, const struct itimerspec *utmr,
                    struct itimerspec *otmr)
{
  return syscall(SYS_timerfd_settime, ufc, flags, utmr, otmr);
}

int timerfd_gettime(int ufc, struct itimerspec *otmr)
{
  return syscall(SYS_timerfd_gettime, ufc, otmr);
}
#else
#include <sys/timerfd.h>
#endif

#ifndef NDEBUG
static char *
itimerspec_dump(struct itimerspec *ts)
{
    static __thread char buf[1024];

    snprintf(buf, sizeof(buf),
            "itimer: [ interval=%lu s %lu ns, next expire=%lu s %lu ns ]",
            ts->it_interval.tv_sec,
            ts->it_interval.tv_nsec,
            ts->it_value.tv_sec,
            ts->it_value.tv_nsec
           );

    return (buf);
}
#endif

/* Convert time data into seconds+nanoseconds */

#define NOTE_TIMER_MASK (NOTE_ABSOLUTE-1)

static void
convert_timedata_to_itimerspec(struct itimerspec *dst, long src,
                               unsigned int flags, int oneshot)
{
    time_t sec, nsec;

    switch (flags & NOTE_TIMER_MASK) {
    case NOTE_USECONDS:
        sec = src / 1000000;
        nsec = (src % 1000000) * 1000;
        break;

    case NOTE_NSECONDS:
        sec = src / 1000000000;
        nsec = (src % 1000000000);
        break;

    case NOTE_SECONDS:
        sec = src;
        nsec = 0;
        break;

    default: /* milliseconds */
        sec = src / 1000;
        nsec = (src % 1000) * 1000000;
    }

    /*
     * NOTE_ABSOLUTE is inherently one-shot: kev.data is a deadline,
     * not a period.  Force it_interval to 0 even if EV_ONESHOT isn't
     * set, otherwise the timer would re-arm using the deadline value
     * as a period and refire <deadline> seconds later.
     */
    if (oneshot || (flags & NOTE_ABSOLUTE)) {
        dst->it_interval.tv_sec = 0;
        dst->it_interval.tv_nsec = 0;
    } else {
        dst->it_interval.tv_sec = sec;
        dst->it_interval.tv_nsec = nsec;
    }

    /* Set the initial expiration */
    dst->it_value.tv_sec = sec;
    dst->it_value.tv_nsec = nsec;
    dbg_printf("%s", itimerspec_dump(dst));
}

int
evfilt_timer_copyout(struct kevent *dst, UNUSED int nevents, struct filter *filt,
    struct knote *src, void *ptr)
{
    struct epoll_event * const ev = (struct epoll_event *) ptr;
    uint64_t expired;
    ssize_t n;

    memcpy(dst, &src->kev, sizeof(*dst));
    if (ev->events & EPOLLERR)
        dst->fflags = 1; /* FIXME: Return the actual timer error */

    /*
     * On return, data contains the number of times the
       timer has been trigered.
     */
    n = read(src->kn_timer.timerfd, &expired, sizeof(expired));
    if (n < 0 && errno == EAGAIN) {
        /*
         * Another waiter on the same kq already drained the
         * timerfd; skip dispatch.  Without TFD_NONBLOCK + this
         * short-circuit, the losing read would block forever in
         * multi-waiter setups.
         */
        return (0);
    }
    if (n != sizeof(expired)) {
        dbg_puts("invalid read from timerfd");
        expired = 1;  /* Fail gracefully */
    }
    dst->data = expired;

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

    return (1);
}

int
evfilt_timer_knote_create(struct filter *filt, struct knote *kn)
{
    struct itimerspec ts;
    int tfd;
    int flags;
    int events;

    /* TODO: kn_create arms before EV_DISABLE - see kevent_copyin_one EV_ADD|EV_DISABLE race. */
    kn->kev.flags |= EV_CLEAR;

    /*
     * TFD_NONBLOCK so concurrent readers (multiple kevent() callers
     * on the same kq racing for the same timer expiration) detect
     * "another thread already drained me" via EAGAIN rather than
     * blocking forever.  See evfilt_timer_copyout() for the consumer
     * side.
     *
     * NOTE_ABSOLUTE: BSD spec says the deadline is "milliseconds since
     * the Epoch", i.e. CLOCK_REALTIME.  Anything else gets
     * CLOCK_MONOTONIC so relative timers are immune to wall-clock
     * retunes / NTP.
     */
    {
        clockid_t clk = (kn->kev.fflags & NOTE_ABSOLUTE) ? CLOCK_REALTIME
                                                         : CLOCK_MONOTONIC;
        tfd = timerfd_create(clk, TFD_CLOEXEC | TFD_NONBLOCK);
    }
    if (tfd < 0) {
        if ((errno == EMFILE) || (errno == ENFILE)) {
            dbg_perror("timerfd_create(2) fd_used=%u fd_max=%u", get_fd_used(), get_fd_limit());
        } else {
            dbg_perror("timerfd_create(2)");
        }
        return (-1);
    }
    dbg_printf("timer_fd=%i - created", tfd);

    convert_timedata_to_itimerspec(&ts, kn->kev.data, kn->kev.fflags,
                                   kn->kev.flags & EV_ONESHOT);
    flags = (kn->kev.fflags & NOTE_ABSOLUTE) ? TFD_TIMER_ABSTIME : 0;
    if (timerfd_settime(tfd, flags, &ts, NULL) < 0) {
        dbg_printf("timerfd_settime(2): %s", strerror(errno));
        close(tfd);
        return (-1);
    }

    events = EPOLLIN | EPOLLET;
    if (kn->kev.flags & (EV_ONESHOT | EV_DISPATCH))
        events |= EPOLLONESHOT;

    KN_UDATA_ALLOC(kn);   /* populate this knote's kn_udata field */
    if (epoll_ctl(filter_epoll_fd(filt), EPOLL_CTL_ADD, tfd, EPOLL_EV_KN(events, kn)) < 0) {
        dbg_printf("epoll_ctl(2): %d", errno);
        close(tfd);
        /* Kernel never accepted the registration; safe to free direct. */
        KN_UDATA_FREE(kn);
        return (-1);
    }

    kn->kn_timer.timerfd = tfd;
    return (0);
}

int
evfilt_timer_knote_modify(struct filter *filt, struct knote *kn,
        const struct kevent *kev)
{
    struct itimerspec ts;
    int flags;
    bool clk_changed = ((kev->fflags ^ kn->kev.fflags) & NOTE_ABSOLUTE) != 0;

    dbg_printf("timer_fd=%i - modified", kn->kn_timer.timerfd);

    /*
     * NOTE_ABSOLUTE selects the clockid baked into the timerfd at
     * create (CLOCK_REALTIME vs CLOCK_MONOTONIC), and timerfd has no
     * runtime way to change clocks.  Toggling NOTE_ABSOLUTE on modify
     * means the deadline domain changed; tear down the old timerfd
     * and create a fresh one in the right clock.  Matches FreeBSD's
     * filt_timertouch which drains the callout and re-arms via
     * filt_timerstart in the new domain (kern_event.c:1033).
     */
    if (clk_changed) {
        clockid_t clk = (kev->fflags & NOTE_ABSOLUTE) ? CLOCK_REALTIME
                                                      : CLOCK_MONOTONIC;
        int newfd;
        int events;

        newfd = timerfd_create(clk, TFD_CLOEXEC | TFD_NONBLOCK);
        if (newfd < 0) {
            dbg_perror("timerfd_create(2) on NOTE_ABSOLUTE toggle");
            return (-1);
        }

        events = EPOLLIN | EPOLLET;
        if (kev->flags & (EV_ONESHOT | EV_DISPATCH))
            events |= EPOLLONESHOT;

        if (epoll_ctl(filter_epoll_fd(filt), EPOLL_CTL_DEL,
                      kn->kn_timer.timerfd, NULL) < 0)
            dbg_perror("epoll_ctl(DEL) on NOTE_ABSOLUTE toggle");
        (void) close(kn->kn_timer.timerfd);

        if (epoll_ctl(filter_epoll_fd(filt), EPOLL_CTL_ADD, newfd,
                      EPOLL_EV_KN(events, kn)) < 0) {
            dbg_perror("epoll_ctl(ADD) on NOTE_ABSOLUTE toggle");
            (void) close(newfd);
            return (-1);
        }

        kn->kn_timer.timerfd = newfd;
    }

    convert_timedata_to_itimerspec(&ts, kev->data, kev->fflags,
                                   kev->flags & EV_ONESHOT);
    flags = (kev->fflags & NOTE_ABSOLUTE) ? TFD_TIMER_ABSTIME : 0;
    if (timerfd_settime(kn->kn_timer.timerfd, flags, &ts, NULL) < 0) {
        dbg_printf("timerfd_settime(2): %s", strerror(errno));
        return (-1);
    }

    /* EV_RECEIPT is sticky on BSD; preserve across modify. */
    kn->kev.flags  = kev->flags | EV_CLEAR | (kn->kev.flags & EV_RECEIPT);
    kn->kev.fflags = kev->fflags;
    kn->kev.data   = kev->data;

    return (0);
}

int
evfilt_timer_knote_delete(struct filter *filt, struct knote *kn)
{
    int rv = 0;

    if (kn->kn_timer.timerfd == -1)
        return (0);

    if (epoll_ctl(filter_epoll_fd(filt), EPOLL_CTL_DEL, kn->kn_timer.timerfd, NULL) < 0) {
        dbg_printf("epoll_ctl(2): %s", strerror(errno));
        rv = -1;
    }

    KN_UDATA_DEFER_FREE(filt->kf_kqueue, kn);

    dbg_printf("timer_fd=%i - closed", kn->kn_timer.timerfd);
    if (close(kn->kn_timer.timerfd) < 0) {
        dbg_printf("close(2): %s", strerror(errno));
        rv = -1;
    }

    kn->kn_timer.timerfd = -1;
    return (rv);
}

int
evfilt_timer_knote_enable(struct filter *filt, struct knote *kn)
{
    return evfilt_timer_knote_create(filt, kn);
}

int
evfilt_timer_knote_disable(struct filter *filt, struct knote *kn)
{
    return evfilt_timer_knote_delete(filt, kn);
}

const struct filter evfilt_timer = {
    .kf_id      = EVFILT_TIMER,
    .kf_copyout = evfilt_timer_copyout,
    .kn_create  = evfilt_timer_knote_create,
    .kn_modify  = evfilt_timer_knote_modify,
    .kn_delete  = evfilt_timer_knote_delete,
    .kn_enable  = evfilt_timer_knote_enable,
    .kn_disable = evfilt_timer_knote_disable,
};
