/*
 * 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.
 *
 * $FreeBSD$
 */

#include <sys/stat.h>

#include <err.h>

#include "config.h"
#include "common.h"

static int sigusr1_caught = 0;

int kqfd;

static void
sig_handler(int signum)
{
    sigusr1_caught = 1;
}

static void
add_and_delete(void)
{
    struct kevent kev;
    pid_t pid;

    /* Create a child that waits to be killed and then exits */
    pid = fork();
    if (pid == 0) {
        struct stat s;
        if (fstat(kqfd, &s) != -1)
            errx(1, "kqueue inherited across fork! (%s() at %s:%d)",
	        __func__, __FILE__, __LINE__);

        pause();
        exit(2);
    }
    printf(" -- child created (pid %d)\n", (int) pid);

    test_begin("kevent(EVFILT_PROC, EV_ADD)");

    test_no_kevents();
    kevent_add(kqfd, &kev, pid, EVFILT_PROC, EV_ADD, 0, 0, NULL);
    test_no_kevents();

    success();

    test_begin("kevent(EVFILT_PROC, EV_DELETE)");

    sleep(1);
    test_no_kevents();
    kevent_add(kqfd, &kev, pid, EVFILT_PROC, EV_DELETE, 0, 0, NULL);
    if (kill(pid, SIGKILL) < 0)
        err(1, "kill");
    sleep(1);
    test_no_kevents();

    success();

}

static void
proc_track(int sleep_time)
{
    char test_id[64];
    struct kevent kev;
    pid_t pid;
    int pipe_fd[2];
    ssize_t result;

    snprintf(test_id, sizeof(test_id),
             "kevent(EVFILT_PROC, NOTE_TRACK); sleep %d", sleep_time);
    test_begin(test_id);
    test_no_kevents();

    if (pipe(pipe_fd)) {
        err(1, "pipe (parent) failed! (%s() at %s:%d)",
            __func__, __FILE__, __LINE__);
    }

    /* Create a child to track. */
    pid = fork();
    if (pid == 0) { /* Child */
        pid_t grandchild = -1;

        /*
         * Give the parent a chance to start tracking us.
         */
        result = read(pipe_fd[1], test_id, 1);
        if (result != 1) {
            err(1, "read from pipe in child failed! (ret %zd) (%s() at %s:%d)",
                result, __func__, __FILE__, __LINE__);
        }

        /*
         * Spawn a grandchild that will immediately exit. If the kernel has bug
         * 180385, the parent will see a kevent with both NOTE_CHILD and
         * NOTE_EXIT. If that bug is fixed, it will see two separate kevents
         * for those notes. Note that this triggers the conditions for
         * detecting the bug quite reliably on a 1 CPU system (or if the test
         * process is restricted to a single CPU), but may not trigger it on a
         * multi-CPU system.
         */
        grandchild = fork();
        if (grandchild == 0) { /* Grandchild */
            if (sleep_time) sleep(sleep_time);
            exit(1);
        } else if (grandchild == -1) { /* Error */
            err(1, "fork (grandchild) failed! (%s() at %s:%d)",
                __func__, __FILE__, __LINE__);
        } else { /* Child (Grandchild Parent) */
            printf(" -- grandchild created (pid %d)\n", (int) grandchild);
        }
        if (sleep_time) sleep(sleep_time);
        exit(0);
    } else if (pid == -1) { /* Error */
        err(1, "fork (child) failed! (%s() at %s:%d)",
            __func__, __FILE__, __LINE__);
    }
    
    printf(" -- child created (pid %d)\n", (int) pid);

    kevent_add(kqfd, &kev, pid, EVFILT_PROC, EV_ADD | EV_ENABLE,
               NOTE_TRACK | NOTE_EXEC | NOTE_EXIT | NOTE_FORK,
               0, NULL);

    printf(" -- tracking child (pid %d)\n", (int) pid);

    /* Now that we're tracking the child, tell it to proceed. */
    result = write(pipe_fd[0], test_id, 1);
    if (result != 1) {
        err(1, "write to pipe in parent failed! (ret %zd) (%s() at %s:%d)",
            result, __func__, __FILE__, __LINE__);
    }

    /*
     * Several events should be received:
     *  - NOTE_FORK (from child)
     *  - NOTE_CHILD (from grandchild)
     *  - NOTE_EXIT (from grandchild)
     *  - NOTE_EXIT (from child)
     *
     * The NOTE_FORK and NOTE_EXIT from the child could be combined into a
     * single event, but the NOTE_CHILD and NOTE_EXIT from the grandchild must
     * not be combined.
     *
     * The loop continues until no events are received within a 5 second
     * period, at which point it is assumed that no more will be coming. The
     * loop is deliberately designed to attempt to get events even after all
     * the expected ones are received in case some spurious events are
     * generated as well as the expected ones.
     */
    {
        int child_exit = 0;
        int child_fork = 0;
        int gchild_exit = 0;
        int gchild_note = 0;
        pid_t gchild_pid = -1;
        int done = 0;
        
        while (!done)
        {
            int handled = 0;
            struct kevent *kevp;

            kevp = kevent_get_timeout(kqfd, 5);
            if (kevp == NULL) {
                done = 1;
            } else {
                printf(" -- Received kevent: %s\n", kevent_to_str(kevp));
            
                if ((kevp->fflags & NOTE_CHILD) && (kevp->fflags & NOTE_EXIT)) {
                    errx(1, "NOTE_CHILD and NOTE_EXIT in same kevent: %s", kevent_to_str(kevp));
                }
            
                if (kevp->fflags & NOTE_CHILD) {
                    if (kevp->data == pid) {
                        if (!gchild_note) {
                            ++gchild_note;
                            gchild_pid = kevp->ident;
                            ++handled;
                        } else {
                            errx(1, "Spurious NOTE_CHILD: %s", kevent_to_str(kevp));
                        }
                    }
                }

                if (kevp->fflags & NOTE_EXIT) {
                    if ((kevp->ident == pid) && (!child_exit)) {
                        ++child_exit;
                        ++handled;
                    } else if ((kevp->ident == gchild_pid) && (!gchild_exit)) {
                        ++gchild_exit;
                        ++handled;
                    } else {
                        errx(1, "Spurious NOTE_EXIT: %s", kevent_to_str(kevp));
                    }
                }

                if (kevp->fflags & NOTE_FORK) {
                    if ((kevp->ident == pid) && (!child_fork)) {
                        ++child_fork;
                        ++handled;
                    } else {
                        errx(1, "Spurious NOTE_FORK: %s", kevent_to_str(kevp));
                    }
                }

                if (!handled) {
                    errx(1, "Spurious kevent: %s", kevent_to_str(kevp));
                }

                free(kevp);
            }
        }
        
        /* Make sure all expected events were received. */
        if (child_exit && child_fork && gchild_exit && gchild_note) {
            printf(" -- Received all expected events.\n");
        } else {
            errx(1, "Did not receive all expected events.");
        }
    }

    success();
}

#ifdef TODO
static void
event_trigger(void)
{
    struct kevent kev;
    pid_t pid;

    test_begin("kevent(EVFILT_PROC, wait)");

    /* Create a child that waits to be killed and then exits */
    pid = fork();
    if (pid == 0) {
        pause();
        printf(" -- child caught signal, exiting\n");
        exit(2);
    }
    printf(" -- child created (pid %d)\n", (int) pid);

    test_no_kevents();
    kevent_add(kqfd, &kev, pid, EVFILT_PROC, EV_ADD, 0, 0, NULL);

    /* Cause the child to exit, then retrieve the event */
    printf(" -- killing process %d\n", (int) pid);
    if (kill(pid, SIGUSR1) < 0)
        err(1, "kill");
    kevent_cmp(&kev, kevent_get(kqfd));
    test_no_kevents();

    success();
}

void
test_kevent_signal_disable(void)
{
    const char *test_id = "kevent(EVFILT_SIGNAL, EV_DISABLE)";
    struct kevent kev;

    test_begin(test_id);

    EV_SET(&kev, SIGUSR1, EVFILT_SIGNAL, EV_DISABLE, 0, 0, NULL);
    if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
        err(1, "%s", test_id);

    /* Block SIGUSR1, then send it to ourselves */
    sigset_t mask;
    sigemptyset(&mask);
    sigaddset(&mask, SIGUSR1);
    if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1)
        err(1, "sigprocmask");
    if (kill(getpid(), SIGKILL) < 0)
        err(1, "kill");

    test_no_kevents();

    success();
}

void
test_kevent_signal_enable(void)
{
    const char *test_id = "kevent(EVFILT_SIGNAL, EV_ENABLE)";
    struct kevent kev;

    test_begin(test_id);

    EV_SET(&kev, SIGUSR1, EVFILT_SIGNAL, EV_ENABLE, 0, 0, NULL);
    if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
        err(1, "%s", test_id);

    /* Block SIGUSR1, then send it to ourselves */
    sigset_t mask;
    sigemptyset(&mask);
    sigaddset(&mask, SIGUSR1);
    if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1)
        err(1, "sigprocmask");
    if (kill(getpid(), SIGUSR1) < 0)
        err(1, "kill");

    kev.flags = EV_ADD | EV_CLEAR;
#if LIBKQUEUE
    kev.data = 1; /* WORKAROUND */
#else
    kev.data = 2; // one extra time from test_kevent_signal_disable()
#endif
    kevent_cmp(&kev, kevent_get(kqfd));

    /* Delete the watch */
    kev.flags = EV_DELETE;
    if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
        err(1, "%s", test_id);

    success();
}

void
test_kevent_signal_del(void)
{
    const char *test_id = "kevent(EVFILT_SIGNAL, EV_DELETE)";
    struct kevent kev;

    test_begin(test_id);

    /* Delete the kevent */
    EV_SET(&kev, SIGUSR1, EVFILT_SIGNAL, EV_DELETE, 0, 0, NULL);
    if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
        err(1, "%s", test_id);

    /* Block SIGUSR1, then send it to ourselves */
    sigset_t mask;
    sigemptyset(&mask);
    sigaddset(&mask, SIGUSR1);
    if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1)
        err(1, "sigprocmask");
    if (kill(getpid(), SIGUSR1) < 0)
        err(1, "kill");

    test_no_kevents();
    success();
}

void
test_kevent_signal_oneshot(void)
{
    const char *test_id = "kevent(EVFILT_SIGNAL, EV_ONESHOT)";
    struct kevent kev;

    test_begin(test_id);

    EV_SET(&kev, SIGUSR1, EVFILT_SIGNAL, EV_ADD | EV_ONESHOT, 0, 0, NULL);
    if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
        err(1, "%s", test_id);

    /* Block SIGUSR1, then send it to ourselves */
    sigset_t mask;
    sigemptyset(&mask);
    sigaddset(&mask, SIGUSR1);
    if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1)
        err(1, "sigprocmask");
    if (kill(getpid(), SIGUSR1) < 0)
        err(1, "kill");

    kev.flags |= EV_CLEAR;
    kev.data = 1;
    kevent_cmp(&kev, kevent_get(kqfd));

    /* Send another one and make sure we get no events */
    if (kill(getpid(), SIGUSR1) < 0)
        err(1, "kill");
    test_no_kevents();

    success();
}
#endif

void
test_evfilt_proc()
{
    kqfd = kqueue();

    signal(SIGUSR1, sig_handler);

    add_and_delete();
    proc_track(0); /* Run without sleeping before children exit. */
    proc_track(1); /* Sleep a bit in the children before exiting. */

#if TODO
    event_trigger();
#endif

    signal(SIGUSR1, SIG_DFL);

#if TODO
    test_kevent_signal_add();
    test_kevent_signal_del();
    test_kevent_signal_get();
    test_kevent_signal_disable();
    test_kevent_signal_enable();
    test_kevent_signal_oneshot();
#endif
    close(kqfd);
}