This example is fairly simple, it takes a process id as an argument and uses kqueue to track forks, exec’s and exits.
EVFILT_PROC makes use of the filter specific flags field in struct kevent to describe what to pay attention to. In this example we use NOTE_EXIT which mean to provide notification when the process exits, NOTE_FORK to show when process forks, NOTE_EXEC to show calls to exec and NOTE_TRACK which means to pass these flags on to subsequent children. Web use EV_SET() and kqueue to set this up as such:
EV_SET(&ke, pid, EVFILT_PROC, EV_ADD,
NOTE_EXIT | NOTE_FORK | NOTE_EXEC | NOTE_TRACK, 0, NULL);
i = kevent(kq, &ke, 1, NULL, 0, NULL); /*< line 38 */
if (i == -1)
err(1, "proc kevent!");
ke is the struct kevent. The pid argument to EV_SET() is the process id we want to track. We specify the process filter, that we want to add this kevent and the filter specific flags OR’d together. There is no filter specific data and we give no timeout. In line 38 we register the kevent in kqueue.
The code for the output is quite simple:
i = kevent(kq, NULL, 0, &ke, 1, NULL);
if (i == -1)
err(1, "kevent!");
if (ke.fflags & NOTE_FORK)
printf("pid %d called fork()\n", ke.ident);
if (ke.fflags & NOTE_CHILD)
printf("pid %d has %d as parent\n", ke.ident,
ke.data);
if (ke.fflags & NOTE_EXIT)
printf("pid %d exited\n", ke.ident);
if (ke.fflags & NOTE_EXEC)
printf("pid %d called exec()\n", ke.ident);
if (ke.fflags & NOTE_TRACKERR)
printf("couldnt attach to child of %d\n", ke.ident);
This code occurs in an endless while loop. The call to kevent() will block until an event occurs, then the filter specific flags data ke.fflags will tell use what event has happened.
When NOTE_TRACK has been set a few noticeable things occur. Firstly, when a process forks two kevents may be returned. One with NOTE_CHILD in fflags and the childs process id in ke.ident and the parents process id in ke.data. The other if NOTE_FORK was set in the flags when the kevent was registered a NOTE_FORK kevent with the parents process id in ke.ident will also be returned.
Secondly, a NOTE_TRACKERR kevent may be returned if attached the kevent to the child failed. In my experience this is usually the result of a process calling setuid() somewhere along the line, not what the manpage says. The rules for attaching to processes differ across the BSD’s though.
Here is an example run:
$ ./proc $$ &
[1] 9412
pid 19366 has 28955 as parent
pid 28955 called fork()
pid 19366 called exec()
2:30PM up 5:07, 7 users, load averages: 0.35, 0.17, 0.11
pid 19366 exited
cd html
cd is builtin so no fork/exec
Lets start up another shell
$ bash
pid 20742 has 28955 as parent
pid 28955 called fork()
pid 20742 called exec()
bash-2.05b$ cd ..
bash-2.05b$ ls
pid 5426 has 20742 as parent
pid 20742 called fork()
pid 5426 called exec()
chat error file html kqerror.c kqueue.c proc.c signal.c
chat.c error.c file.c kqerror kqueue proc signal
pid 5426 exited
bash-2.05b$ mooo
pid 30278 has 20742 as parent
pid 20742 called fork()
bash: mooo: command not found
pid 30278 exited
bash-2.05b$ exit
pid 20742 exited
$ mooo
sh: mooo: not found
$
So they behave similarly, but we can see bash will fork and let the exec fail if a command is not valid, while sh will make sure the command is valid before forking.
Also note the order in which the events are returned. First we get NOTE_CHILD, then NOTE_FORK, that is, the child’s kevent is received before the parents. In my experience it is not safe for this order to be relied on.