/* * Copyright (c) 2009 Mark Heily * * 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 #include #include "config.h" #include "common.h" static int sigusr1_caught = 0; static void sig_handler(__unused 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; char *kev_str; while (!done) { int handled = 0; struct kevent *kevp; kevp = kevent_get_timeout(kqfd, 5); if (kevp == NULL) { done = 1; } else { kev_str = kevent_to_str(kevp); printf(" -- Received kevent: %s\n", kev_str); free(kev_str); 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 == (uintptr_t)pid) && (!child_exit)) { ++child_exit; ++handled; } else if ((kevp->ident == (uintptr_t)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 == (uintptr_t)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(); } static 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(void) { 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); }