Skip to content

Commit

Permalink
kqueue EVFILT_PROC: avoid collision between NOTE_CHILD and NOTE_EXIT
Browse files Browse the repository at this point in the history
NOTE_CHILD and NOTE_EXIT return something in kevent.data: the parent
pid (ppid) for NOTE_CHILD and the exit status for NOTE_EXIT.
Do not let the two events be combined, since one would overwrite
the other's data.

PR:		180385
Submitted by:	David A. Bright <david_a_bright@dell.com>
Reviewed by:	jhb
MFC after:	1 month
Sponsored by:	Dell Inc.
Differential Revision:	https://reviews.freebsd.org/D4900
  • Loading branch information
vangyzen committed Jan 28, 2016
1 parent f91c9c2 commit f8f3d27
Show file tree
Hide file tree
Showing 5 changed files with 281 additions and 25 deletions.
61 changes: 51 additions & 10 deletions sys/kern/kern_event.c
Expand Up @@ -373,21 +373,32 @@ filt_procattach(struct knote *kn)
kn->kn_flags |= EV_CLEAR; /* automatically set */

/*
* internal flag indicating registration done by kernel
* Internal flag indicating registration done by kernel for the
* purposes of getting a NOTE_CHILD notification.
*/
if (kn->kn_flags & EV_FLAG1) {
if (kn->kn_flags & EV_FLAG2) {
kn->kn_flags &= ~EV_FLAG2;
kn->kn_data = kn->kn_sdata; /* ppid */
kn->kn_fflags = NOTE_CHILD;
kn->kn_sfflags &= ~NOTE_EXIT;
immediate = 1; /* Force immediate activation of child note. */
}
/*
* Internal flag indicating registration done by kernel (for other than
* NOTE_CHILD).
*/
if (kn->kn_flags & EV_FLAG1) {
kn->kn_flags &= ~EV_FLAG1;
}

if (immediate == 0)
knlist_add(&p->p_klist, kn, 1);

/*
* Immediately activate any exit notes if the target process is a
* zombie. This is necessary to handle the case where the target
* process, e.g. a child, dies before the kevent is registered.
* Immediately activate any child notes or, in the case of a zombie
* target process, exit notes. The latter is necessary to handle the
* case where the target process, e.g. a child, dies before the kevent
* is registered.
*/
if (immediate && filt_proc(kn, NOTE_EXIT))
KNOTE_ACTIVATE(kn, 0);
Expand Down Expand Up @@ -495,7 +506,7 @@ knote_fork(struct knlist *list, int pid)

/*
* The NOTE_TRACK case. In addition to the activation
* of the event, we need to register new event to
* of the event, we need to register new events to
* track the child. Drop the locks in preparation for
* the call to kqueue_register().
*/
Expand All @@ -504,8 +515,27 @@ knote_fork(struct knlist *list, int pid)
list->kl_unlock(list->kl_lockarg);

/*
* Activate existing knote and register a knote with
* Activate existing knote and register tracking knotes with
* new process.
*
* First register a knote to get just the child notice. This
* must be a separate note from a potential NOTE_EXIT
* notification since both NOTE_CHILD and NOTE_EXIT are defined
* to use the data field (in conflicting ways).
*/
kev.ident = pid;
kev.filter = kn->kn_filter;
kev.flags = kn->kn_flags | EV_ADD | EV_ENABLE | EV_ONESHOT | EV_FLAG2;
kev.fflags = kn->kn_sfflags;
kev.data = kn->kn_id; /* parent */
kev.udata = kn->kn_kevent.udata;/* preserve udata */
error = kqueue_register(kq, &kev, NULL, 0);
if (error)
kn->kn_fflags |= NOTE_TRACKERR;

/*
* Then register another knote to track other potential events
* from the new process.
*/
kev.ident = pid;
kev.filter = kn->kn_filter;
Expand Down Expand Up @@ -1129,7 +1159,7 @@ kqueue_register(struct kqueue *kq, struct kevent *kev, struct thread *td, int wa

if (fp->f_type == DTYPE_KQUEUE) {
/*
* if we add some inteligence about what we are doing,
* If we add some intelligence about what we are doing,
* we should be able to support events on ourselves.
* We need to know when we are doing this to prevent
* getting both the knlist lock and the kq lock since
Expand Down Expand Up @@ -1161,7 +1191,18 @@ kqueue_register(struct kqueue *kq, struct kevent *kev, struct thread *td, int wa
kqueue_expand(kq, fops, kev->ident, waitok);

KQ_LOCK(kq);
if (kq->kq_knhashmask != 0) {

/*
* If possible, find an existing knote to use for this kevent.
*/
if (kev->filter == EVFILT_PROC &&
(kev->flags & (EV_FLAG1 | EV_FLAG2)) != 0) {
/* This is an internal creation of a process tracking
* note. Don't attempt to coalesce this with an
* existing note.
*/
;
} else if (kq->kq_knhashmask != 0) {
struct klist *list;

list = &kq->kq_knhash[
Expand All @@ -1173,7 +1214,7 @@ kqueue_register(struct kqueue *kq, struct kevent *kev, struct thread *td, int wa
}
}

/* knote is in the process of changing, wait for it to stablize. */
/* knote is in the process of changing, wait for it to stabilize. */
if (kn != NULL && (kn->kn_status & KN_INFLUX) == KN_INFLUX) {
KQ_GLOBAL_UNLOCK(&kq_global, haskqglobal);
if (filedesc_unlock) {
Expand Down
1 change: 1 addition & 0 deletions sys/sys/event.h
Expand Up @@ -80,6 +80,7 @@ struct kevent {
#define EV_SYSFLAGS 0xF000 /* reserved by system */
#define EV_DROP 0x1000 /* note should be dropped */
#define EV_FLAG1 0x2000 /* filter-specific flag */
#define EV_FLAG2 0x4000 /* filter-specific flag */

/* returned values */
#define EV_EOF 0x8000 /* EOF detected */
Expand Down
1 change: 1 addition & 0 deletions tests/sys/kqueue/common.h
Expand Up @@ -46,6 +46,7 @@ int vnode_fd;

extern const char * kevent_to_str(struct kevent *);
struct kevent * kevent_get(int);
struct kevent * kevent_get_timeout(int, int);


void kevent_cmp(struct kevent *, struct kevent *);
Expand Down
75 changes: 60 additions & 15 deletions tests/sys/kqueue/main.c
Expand Up @@ -69,6 +69,28 @@ kevent_get(int kqfd)
return (kev);
}

/* Retrieve a single kevent, specifying a maximum time to wait for it. */
struct kevent *
kevent_get_timeout(int kqfd, int seconds)
{
int nfds;
struct kevent *kev;
struct timespec timeout = {seconds, 0};

if ((kev = calloc(1, sizeof(*kev))) == NULL)
err(1, "out of memory");

nfds = kevent(kqfd, NULL, 0, kev, 1, &timeout);
if (nfds < 0) {
err(1, "kevent(2)");
} else if (nfds == 0) {
free(kev);
kev = NULL;
}

return (kev);
}

char *
kevent_fflags_dump(struct kevent *kev)
{
Expand All @@ -82,25 +104,39 @@ kevent_fflags_dump(struct kevent *kev)
abort();

/* Not every filter has meaningful fflags */
if (kev->filter != EVFILT_VNODE) {
snprintf(buf, 1024, "fflags = %d", kev->fflags);
return (buf);
}

snprintf(buf, 1024, "fflags = %d (", kev->fflags);
KEVFFL_DUMP(NOTE_DELETE);
KEVFFL_DUMP(NOTE_WRITE);
KEVFFL_DUMP(NOTE_EXTEND);
if (kev->filter == EVFILT_PROC) {
snprintf(buf, 1024, "fflags = %x (", kev->fflags);
KEVFFL_DUMP(NOTE_EXIT);
KEVFFL_DUMP(NOTE_FORK);
KEVFFL_DUMP(NOTE_EXEC);
KEVFFL_DUMP(NOTE_CHILD);
KEVFFL_DUMP(NOTE_TRACKERR);
KEVFFL_DUMP(NOTE_TRACK);
buf[strlen(buf) - 1] = ')';
} else if (kev->filter == EVFILT_PROCDESC) {
snprintf(buf, 1024, "fflags = %x (", kev->fflags);
KEVFFL_DUMP(NOTE_EXIT);
KEVFFL_DUMP(NOTE_FORK);
KEVFFL_DUMP(NOTE_EXEC);
buf[strlen(buf) - 1] = ')';
} else if (kev->filter == EVFILT_VNODE) {
snprintf(buf, 1024, "fflags = %x (", kev->fflags);
KEVFFL_DUMP(NOTE_DELETE);
KEVFFL_DUMP(NOTE_WRITE);
KEVFFL_DUMP(NOTE_EXTEND);
#if HAVE_NOTE_TRUNCATE
KEVFFL_DUMP(NOTE_TRUNCATE);
KEVFFL_DUMP(NOTE_TRUNCATE);
#endif
KEVFFL_DUMP(NOTE_ATTRIB);
KEVFFL_DUMP(NOTE_LINK);
KEVFFL_DUMP(NOTE_RENAME);
KEVFFL_DUMP(NOTE_ATTRIB);
KEVFFL_DUMP(NOTE_LINK);
KEVFFL_DUMP(NOTE_RENAME);
#if HAVE_NOTE_REVOKE
KEVFFL_DUMP(NOTE_REVOKE);
KEVFFL_DUMP(NOTE_REVOKE);
#endif
buf[strlen(buf) - 1] = ')';
buf[strlen(buf) - 1] = ')';
} else {
snprintf(buf, 1024, "fflags = %x", kev->fflags);
}

return (buf);
}
Expand Down Expand Up @@ -260,6 +296,15 @@ main(int argc, char **argv)
argc--;
}

/*
* Some tests fork. If output is fully buffered,
* the children inherit some buffered data and flush
* it when they exit, causing some data to be printed twice.
* Use line buffering to avoid this problem.
*/
setlinebuf(stdout);
setlinebuf(stderr);

test_kqueue();
test_kqueue_close();

Expand Down

0 comments on commit f8f3d27

Please sign in to comment.