This is a small library that implements epoll on top of kqueue. It has been successfully used to port libinput, libevdev, Wayland and more software to FreeBSD: https://www.freshports.org/devel/libepoll-shim/
When using timerfd, signalfd or eventfd, the system calls read, write and close are redefined as macros to internal helper functions. This is needed as there is some internal context that has to be free'd properly. This means that you shouldn't create a timerfd/signalfd in one part of a program and close it in a different part where sys/timerfd.h isn't included. The context would leak. Luckily, software such as libinput behaves very nicely and puts all timerfd related code in a single source file.
There is limited support for file descriptors that lack support for kqueue but are supported by poll(2). This includes graphics or sound devices under /dev. Those descriptors are handled in an outer poll(2) loop. Edge triggering using EPOLLET will not work.
Shimmed file descriptors cannot be shared between processes. On fork() those fds are closed. When trying to pass a shimmed fd to another process the sendmsg call will return EOPNOTSUPP. In most cases sharing epoll/timerfd/signalfd is a bad idea anyway, but there are some legitimate use cases (for example sharing semaphore eventfds, issue #23). When the OS natively supports eventfds (as is the case for FreeBSD >= 13) this library won't provide eventfd shims or the sys/eventfd.h header.