EVFILT_PROC always returns an EV_EOF event

John Baldwin jhb at freebsd.org
Thu Jul 25 19:37:13 UTC 2013


A co-worker ran into this undocumented behavior today.  If you register an 
EVFILT_PROC event but do not set NOTE_EXIT, you can still get an event with
fflags set to 0 but EV_EOF set.  This is not documented in the manpage, and
it seems inconsistent to me.  If the caller hasn't set NOTE_EXIT, then
presumably they do not wish to know about NOTE_EXIT events.

I have a specific test case below (watch for NOTE_EXEC on a process that only 
exits).  Is this behavior desired or should this be fixed?  If we want it
fixed I have a possible fix (tested with this test case) at 
http://people.freebsd.org/~jhb/patches/kevent_proc_eof.patch

#include <sys/types.h>
#include <sys/event.h>
#include <assert.h>
#include <err.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

static pid_t master;
static int kq;

static void
watch(uintptr_t ident, short filter, u_short flags, u_int fflags,
    intptr_t data, void *udata)
{
	struct kevent ev;

	EV_SET(&ev, ident, filter, flags, fflags, data, udata);
	if (kevent(kq, &ev, 1, NULL, 0, NULL) < 0)
		err(1, "kevent");
}

static void
dump_fflags(u_int fflags)
{
	int pipe;

	assert(fflags != 0);
	pipe = 0;
#define DUMP_FLAG(FLAG) do {						\
		if (fflags & FLAG) {					\
			printf("%s" #FLAG, pipe ? " | " : "");		\
			pipe = 1;					\
		}							\
	} while (0)

	DUMP_FLAG(NOTE_EXIT);
	DUMP_FLAG(NOTE_FORK);
	DUMP_FLAG(NOTE_EXEC);
	DUMP_FLAG(NOTE_TRACK);
	DUMP_FLAG(NOTE_TRACKERR);
	DUMP_FLAG(NOTE_CHILD);

	fflags &= ~(NOTE_EXIT | NOTE_FORK | NOTE_EXEC | NOTE_TRACK |
	    NOTE_TRACKERR | NOTE_CHILD);
	if (fflags != 0)
		printf("%s%u", pipe ? " | " : "", fflags);
}

static void
dump_event(struct kevent *ev)
{

	assert(ev->filter == EVFILT_PROC);
	printf("pid: %5d%s flags: ", (int)ev->ident,
	    ev->flags & EV_EOF ? " EV_EOF" : "");
	dump_fflags(ev->fflags);
	if (ev->data != 0)
		printf(" data: %jd", (uintmax_t)ev->data);
	printf("\n");
}

static void
child(int fd)
{
	pid_t pid;
	char c;

	if (fd > 0)
		(void)read(fd, &c, sizeof(c));
	pid = fork();
	if (pid == -1)
		err(1, "fork");
	usleep(5000);
	exit(1);
}

static void
waitfor(int count, const char *msg)
{
	struct timespec ts;
	struct kevent ev;
	int rv;

	printf("%s:\n", msg);

	/* Wait up to 250 ms before timing out. */
	ts.tv_sec = 0;
	ts.tv_nsec = 250 * 1000 * 1000;
	for (;;) {
		rv = kevent(kq, NULL, 0, &ev, 1, &ts);
		if (rv < 0)
			err(1, "kevent");
		if (rv == 0)
			break;
		dump_event(&ev);
		--count;
	}

	if (count > 0)
		warnx("%d events missing for %s", count, msg);
	else if (count < 0)
		warnx("%d extra events for %s", -count, msg);
}

int
main(int ac, char **av)
{
	pid_t pid;
	int fds[2];
	char c;

	kq = kqueue();
	if (kq < 0)
		err(1, "kqueue");
	if (pipe(fds) < 0)
		err(1, "pipe");
	master = getpid();
	printf("master: %d\n", (int)master);

	/* Test for a dummy EV_EOF event. */
	pid = fork();
	if (pid == -1)
		err(1, "fork");
	if (pid == 0)
		child(fds[1]);
	watch(pid, EVFILT_PROC, EV_ADD, NOTE_EXEC, 0, 0);
	write(fds[0], &c, sizeof(c));

	/* Should not get any events at all. */
	waitfor(0, "dummy EV_EOF");

	return (0);
}

-- 
John Baldwin


More information about the freebsd-arch mailing list