/*
 * 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 <assert.h>
#include <stdio.h>

#include "private.h"

/*
 * The list of filters compiled into this build is supplied by
 * CMake via the generated filter_list.h.  It expands FILTER_ENTRY
 * once per filter that has a backing TU; redefining FILTER_ENTRY
 * before each include lets us reuse the list for externs,
 * registration, and per-pass loops without the #ifdef noise.
 */
#define FILTER_ENTRY(_name) extern const struct filter _name;
#include "filter_list.h"
#undef FILTER_ENTRY

void
filter_init_all(void)
{
#define FILTER_ENTRY(_name) \
    if (_name.libkqueue_init) _name.libkqueue_init();
#include "filter_list.h"
#undef FILTER_ENTRY
}

#ifndef _WIN32
void
filter_fork_all(void)
{
#define FILTER_ENTRY(_name) \
    if (_name.libkqueue_fork) _name.libkqueue_fork();
#include "filter_list.h"
#undef FILTER_ENTRY
}
#endif

void
filter_free_all(void)
{
#define FILTER_ENTRY(_name) \
    if (_name.libkqueue_free) _name.libkqueue_free();
#include "filter_list.h"
#undef FILTER_ENTRY
}

static int
filter_register(struct kqueue *kq, const struct filter *src)
{
    struct filter *dst;
    unsigned int filt;
    int rv = 0;

    /*
     * This filter is not implemented, see EVFILT_NOTIMPL.
     */
    if (src->kf_id == 0) return (0);

    filt = (-1 * src->kf_id) - 1; /* flip sign, and convert to array offset */
    if (filt >= EVFILT_SYSCOUNT)
        return (-1);

    dst = &kq->kq_filt[filt];
    memcpy(dst, src, sizeof(*src));
    dst->kf_kqueue = kq;
    RB_INIT(&dst->kf_index);

    assert(src->kf_copyout);
    assert(src->kn_create);
    assert(src->kn_modify);
    assert(src->kn_delete);
    assert(src->kn_enable);
    assert(src->kn_disable);

    /* Perform (optional) per-filter initialization */
    if (src->kf_init != NULL) {
        rv = src->kf_init(dst);
        if (rv < 0) {
            dbg_puts("filter failed to initialize");
            dst->kf_id = 0;
            return (-1);
        }
    }

    /* kqops.filter_init's signature takes non-const; cast is safe
     * here (we're the only caller) but the vtable should take
     * const - see TODO.md. */
    if ((kqops.filter_init != NULL) && (kqops.filter_init(kq, dst) < 0))
        return (-1);

    return (0);
}

int
filter_register_all(struct kqueue *kq)
{
    int rv;

    rv = 0;
#define FILTER_ENTRY(_name) rv += filter_register(kq, &_name);
#include "filter_list.h"
#undef FILTER_ENTRY
    if (rv != 0) {
        /*
         * Per-kq lock is required by knote_delete_all's lockset
         * assertion.  filter_register_all runs from kqueue() before
         * the kq is reachable from any other thread, so the lock is
         * uncontended; we just satisfy the invariant.
         */
        kqueue_lock(kq);
        filter_unregister_all(kq);
        kqueue_unlock(kq);
        return (-1);
    } else {
        dbg_puts("complete");
        return (0);
    }
}

void
filter_unregister_all(struct kqueue *kq)
{
    int i;

    for (i = 0; i < NUM_ELEMENTS(kq->kq_filt); i++) {
        struct filter *filt = &kq->kq_filt[i];

        if (filt->kf_id == 0)
            continue;

        /*
         * Delete the knotes before destroying the filter.  kf_destroy
         * frees the filter's per-filter state - EVFILT_SIGNAL frees the
         * sig_filter_state whose array holds the per-signal list heads
         * the knotes are linked onto, and NULLs filt->kf_state - so a
         * kn_delete running afterwards walks freed memory: it crashed
         * dereferencing the freed signal state, or hung when
         * kf_destroy's dispatch-thread join ran with live knotes still
         * registered (issue #173).
         */
        knote_delete_all(filt);

        if (filt->kf_destroy != NULL)
            filt->kf_destroy(&kq->kq_filt[i]);

        if (kqops.filter_free != NULL)
            kqops.filter_free(kq, filt);

    }
    memset(&kq->kq_filt[0], 0, sizeof(kq->kq_filt));
}

/*
 * Lookup filters in the array of filters registered for kq
 *
 * @param[out] filt    the specified ID resolves to.
 * @param[in] kq       to lookup the filter in.
 * @param[in] id       of the filter to lookup.
 * @return
 *    - 0 on success.
 *    - -1 on failure (filter not implemented).
 */
int
filter_lookup(struct filter **filt, struct kqueue *kq, short id)
{
    if (~id < 0 || ~id >= EVFILT_SYSCOUNT) {
        dbg_printf("filt=%d inv_filt=%d - invalid id", id, (~id));
        errno = EINVAL;
        *filt = NULL;
        return (-1);
    }
    *filt = &kq->kq_filt[~id];
    if ((*filt)->kf_copyout == NULL) {
        dbg_printf("filt=%d - filt_name=%s not implemented", id, filter_name(id));
        errno = ENOSYS;
        *filt = NULL;
        return (-1);
    }

    return (0);
}

const char *
filter_name(short filt)
{
    int id;
    const char *fname[EVFILT_SYSCOUNT] = {
#ifdef EVFILT_READ
        [~EVFILT_READ] = "EVFILT_READ",
#endif
#ifdef EVFILT_WRITE
        [~EVFILT_WRITE] = "EVFILT_WRITE",
#endif
#ifdef EVFILT_AIO
        [~EVFILT_AIO] = "EVFILT_AIO",
#endif
#ifdef EVFILT_VNODE
        [~EVFILT_VNODE] = "EVFILT_VNODE",
#endif
#ifdef EVFILT_PROC
        [~EVFILT_PROC] = "EVFILT_PROC",
#endif
#ifdef EVFILT_SIGNAL
        [~EVFILT_SIGNAL] = "EVFILT_SIGNAL",
#endif
#ifdef EVFILT_TIMER
        [~EVFILT_TIMER] = "EVFILT_TIMER",
#endif
#ifdef EVFILT_NETDEV
        [~EVFILT_NETDEV] = "EVFILT_NETDEV",
#endif
#ifdef EVFILT_FS
        [~EVFILT_FS] = "EVFILT_FS",
#endif
#ifdef EVFILT_LIO
        [~EVFILT_LIO] = "EVFILT_LIO",
#endif
#ifdef EVFILT_USER
        [~EVFILT_USER] = "EVFILT_USER",
#endif
    };

    id = ~filt;
    if (id < 0 || id >= NUM_ELEMENTS(fname))
        return "EVFILT_INVALID";
    else
        return fname[id];
}
