d340488122
After r337820, which "corrected" some spaces-instead-of-tab whitespace issues in the libkqueue tests, jmg@ pointed out that these files were originally space-based, not tab-spaced, and so the correction should have been to get rid of the tabs that had been introduced in previous changes, not the spaces. This change does that. This is a whitespace only change; no functional change is intended. Reported by: jmg@ MFC after: 3 days Sponsored by: Dell EMC Isilon
427 lines
12 KiB
C
427 lines
12 KiB
C
/*
|
|
* 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;
|
|
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 == 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);
|
|
}
|