git: b3836322a1e6 - main - linux: preserve epoll descriptors across fork using KQUEUE_CPONFORK

From: Konstantin Belousov <kib_at_FreeBSD.org>
Date: Fri, 16 Oct 2026 20:16:00 UTC
The branch main has been updated by kib:

URL: https://cgit.FreeBSD.org/src/commit/?id=b3836322a1e6863734427ac9c01381ab25458126

commit b3836322a1e6863734427ac9c01381ab25458126
Author:     Vladimir Grebenshchikov <vova@zote.me>
AuthorDate: 2026-10-12 18:40:23 +0000
Commit:     Konstantin Belousov <kib@FreeBSD.org>
CommitDate: 2026-10-16 20:10:55 +0000

    linux: preserve epoll descriptors across fork using KQUEUE_CPONFORK
    
    Linuxulator implements epoll instances using kqueues. Currently,
    epoll_create_common() creates the backing kqueue with copy-on-fork
    disabled. When fork() copies the descriptor table, the kqueue is
    omitted from the child's table.
    
    This differs from Linux, where an epoll descriptor survives fork(),
    including when it was created with EPOLL_CLOEXEC. That flag controls
    descriptor lifetime across exec(), not fork().
    
    The missing descriptor breaks dbus-daemon daemonization. The daemon
    creates its epoll instance before forking and continues using the
    saved descriptor number in the child. F_GETFD initially fails with
    EBADF. Once another open reuses that number, subsequent epoll_ctl()
    calls fail with EINVAL because the descriptor no longer refers to
    an epoll instance. The daemon then exits, preventing applications
    that depend on the session bus from starting correctly.
    
    Enable KQUEUE_CPONFORK for epoll's backing kqueue. This preserves the
    descriptor number and copies supported event registrations into the
    child, allowing the daemonized process to continue using epoll.
    
    This is a partial compatibility improvement, not a complete
    implementation of Linux epoll inheritance. Linux parent and child
    reference the same epoll instance, whereas KQUEUE_CPONFORK creates
    independent queues. Consequently, registration changes and one-shot
    event consumption in one process are not reflected in the other.
    Applications relying on shared epoll state across fork still require
    further work.
    
    Validation:
    - A static Linux x86-64 C reproducer fails under the original
      Linuxulator and passes on a real Linux kernel under QEMU.
    - Before this change, the child loses its epoll descriptor with
      both EPOLL_CLOEXEC enabled and disabled, while ordinary pipe
      descriptors survive.
    - After this change, descriptor flags, inherited readiness and
      event data remain available in the child. epoll_ctl() also
      succeeds after allocating additional descriptors.
    - Both dbus-daemon --session --fork and dbus-launch start
      successfully and answer a ListNames request from a separate
      client.
    - Shared-state regression tests still fail for parent visibility
      of child ADD/MOD/DEL operations and EPOLLONESHOT consumption,
      documenting the remaining semantic differences.
    
    Reviewed by:    kib
    MFC after:      1 week
    Differential revision:  https://reviews.freebsd.org/D60783
---
 sys/compat/linux/linux_event.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/sys/compat/linux/linux_event.c b/sys/compat/linux/linux_event.c
index de313aefff97..507ddf1cde6f 100644
--- a/sys/compat/linux/linux_event.c
+++ b/sys/compat/linux/linux_event.c
@@ -104,7 +104,7 @@ static int
 epoll_create_common(struct thread *td, int flags)
 {
 
-	return (kern_kqueue(td, flags, false, NULL));
+	return (kern_kqueue(td, flags, true, NULL));
 }
 
 #ifdef LINUX_LEGACY_SYSCALLS