From nobody Mon Nov 02 21:13:04 2026 X-Original-To: dev-commits-src-main@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id 4jJPYr2tf2z6yY4W for ; Mon, 02 Nov 2026 21:13:04 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange x25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by mx1.freebsd.org (Postfix) with ESMTPS id 4jJPYr1qHCz4w43 for ; Mon, 02 Nov 2026 21:13:04 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1793653984; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=JyGTbwIKUtlDHjXHBVn7Apg9fVsMbQ2LVtvaYvQQMh4=; b=pcmxRCOJP5PYlNfyQgjq7AqcQS0WcbSqwl7glmhPZx2OinupBK60n5/VnjJxVXOFCdqaTQ TNX+ocFInpeMixjxQt42YZ7DehgAdFObrgDNAZihw/K7fzmwoW/XmxTUrOvT353wqR0rvv +AcJbtKIKB9c7QYenw8eVaXp4+K+Xt6F/6tThb0sCULu7UqKkp+Mz5nykn2HdUQ+F6iRNN Z5J9dfi2rgfuWMnOpDn1hzbyfINraVFBfbWR1j0vsg0diRFKKSzdm9l2FfWjHZQ0KpoWbb jdNG7MdF480rYWWt4WTqqN2Ql6JDZKHFXen2/sOGPQN9XSfF+SbZyIw+wsQZTQ== ARC-Seal: i=1; a=rsa-sha256; d=freebsd.org; s=dkim; cv=none; t=1793653984; b=HohVDcas/wHfR44dquUL1wpfDLqBIYifQxuFhtmnjc/Bu9w2uyDgpEOsmeAnRZF1z4E/4K WEVXBklli8LoQrPxCupLo7cMRGzAeczekG+q6o8rCu2VxE7kmcN6Yi8EsoQxTRS3aOEKDe HYehlr8oIb47axlGybwSVXaTJYi0Efp42QdL8hXL546s1A/0RJJKYyBbV/3Wfr66Wq+Lx/ MjzuLj+DZnpfPrOGaCdkFG/hNHjrv0bV4hUdrUkO0WRyuYmLP/3BOJBwFTHnloFiyBZ01t aAwXsETYYC6MfZpWQSp71RrAszvZlJ3TZnAdOl7Y/e01X99TrjV1Gd0DNyAd4w== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1793653984; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=JyGTbwIKUtlDHjXHBVn7Apg9fVsMbQ2LVtvaYvQQMh4=; b=VkElL+7OvLRxZe+na4A+sNlKRtzVXpvRGFqlC4IdZC8UMiwuH0RHTZ4RBdxlSyuo6ur8NI 0oIr2KrSHWNvHGGpfMJJmjQfolh3Fv4+xYpKzkuPOxvK/xRR3cGX8faaxut6M84MQKEvAs 8l+hivXX1tbwUwLspmb//gX1270sDJv1LIkrIoE0qvpcUOxZl/AKzQpXMOJNk09JEd61Vx aopwk7O28QUsrvANdXytthzrX6YiXle4HoTYKfTGfgolSCLa6RQgMANaFdAfmAJwBW4kLY OcRAotrq7im+LOZhjiRObf/vtfuU20NmRx7wMelcqNzJEe2JCEjAKj7dMMkASQ== ARC-Authentication-Results: i=1; mx1.freebsd.org; none Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) by mxrelay.nyi.freebsd.org (Postfix) with ESMTP id 4jJPYr1SNDz12vK for ; Mon, 02 Nov 2026 21:13:04 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from git (uid 1279) (envelope-from git@FreeBSD.org) id 3f636 by gitrepo.freebsd.org (DragonFly Mail Agent v0.13+ on gitrepo.freebsd.org); Mon, 02 Nov 2026 21:13:04 +0000 To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org From: Maxim Konovalov Subject: git: f9f4abe8c9f8 - main - tests/sys/file: add tests for the lowest fd after fork List-Archive: https://lists.freebsd.org/archives/dev-commits-src-main X-BeenThere: dev-commits-src-main@freebsd.org Sender: owner-dev-commits-src-main@FreeBSD.org List-Id: Commit messages for the main branch of the src repository List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: maxim X-Git-Repository: src X-Git-Refname: refs/heads/main X-Git-Reftype: branch X-Git-Commit: f9f4abe8c9f8b5a783538d2b99254d9845572e70 Auto-Submitted: auto-generated Date: Mon, 02 Nov 2026 21:13:04 +0000 Message-Id: <6ae8fce0.3f636.2b0a2fb1@gitrepo.freebsd.org> The branch main has been updated by maxim: URL: https://cgit.FreeBSD.org/src/commit/?id=f9f4abe8c9f8b5a783538d2b99254d9845572e70 commit f9f4abe8c9f8b5a783538d2b99254d9845572e70 Author: Maxim Konovalov AuthorDate: 2026-11-01 14:59:08 +0000 Commit: Maxim Konovalov CommitDate: 2026-11-02 21:11:44 +0000 tests/sys/file: add tests for the lowest fd after fork fdcopy() skips plain kqueues and close-on-fork descriptors when it copies the table into the child. Check that dup(2) in the child still returns the lowest unused descriptor, both when the skipped descriptor is the lowest free slot and when it sits above an existing hole. Test cases: kqueue_no_hole a plain kqueue is the lowest descriptor; the child's dup() reuses its slot kqueue_two two adjacent plain kqueues; the child's dup() returns the lower one kqueue_above_hole a plain kqueue sits above a closed descriptor; the child's dup() returns the hole kqueue_cponfork a KQUEUE_CPONFORK kqueue is copied; the child's dup() returns the next slot clofork_no_hole an O_CLOFORK descriptor is the lowest one; the child's dup() reuses its slot clofork_above_hole an O_CLOFORK descriptor sits above a closed descriptor; the child's dup() returns the hole Without 99a58803f9a3, kqueue_no_hole, kqueue_two and clofork_above_hole fail. PR: 300234 Reviewed by: kib MFC after: 1 week MFC with: 99a58803f9a3 Co-authored-by: Claude Code Opus 5.5 Differential Revision: https://reviews.freebsd.org/D61124 --- tests/sys/file/Makefile | 1 + tests/sys/file/fork_lowfd_test.c | 141 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 142 insertions(+) diff --git a/tests/sys/file/Makefile b/tests/sys/file/Makefile index c1fcef68d08e..173a01a1ff61 100644 --- a/tests/sys/file/Makefile +++ b/tests/sys/file/Makefile @@ -6,6 +6,7 @@ ATF_TESTS_C+= path_test ATF_TESTS_C+= closefrom_test TAP_TESTS_C+= dup_test ATF_TESTS_C+= fcntlflags_test +ATF_TESTS_C+= fork_lowfd_test TAP_TESTS_SH+= flock_test PLAIN_TESTS_C+= ftruncate_test PLAIN_TESTS_C+= newfileops_on_fork_test diff --git a/tests/sys/file/fork_lowfd_test.c b/tests/sys/file/fork_lowfd_test.c new file mode 100644 index 000000000000..81d867e03255 --- /dev/null +++ b/tests/sys/file/fork_lowfd_test.c @@ -0,0 +1,141 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2026 Maxim Konovalov + */ + +/* + * fdcopy() does not copy some descriptors into the child (plain kqueues, + * close-on-fork descriptors). The child's lowest-free-descriptor hint + * must still let dup(2) return the lowest unused descriptor, as POSIX + * requires. See PR 300234. + */ + +#include +#include +#include + +#include +#include + +#include + +#define RFD 100 +#define WFD 101 + +/* Fork and return the descriptor the child gets from dup(0). */ +static int +child_dup(void) +{ + int fds[2], fd, status; + pid_t pid; + + /* Park the pipe high so that it does not change the layout. */ + ATF_REQUIRE(pipe(fds) == 0); + ATF_REQUIRE(dup2(fds[0], RFD) == RFD); + ATF_REQUIRE(dup2(fds[1], WFD) == WFD); + ATF_REQUIRE(close(fds[0]) == 0); + ATF_REQUIRE(close(fds[1]) == 0); + + pid = fork(); + ATF_REQUIRE(pid != -1); + if (pid == 0) { + fd = dup(STDIN_FILENO); + _exit(write(WFD, &fd, sizeof(fd)) == sizeof(fd) ? 0 : 1); + } + ATF_REQUIRE(close(WFD) == 0); + ATF_REQUIRE(read(RFD, &fd, sizeof(fd)) == sizeof(fd)); + ATF_REQUIRE(close(RFD) == 0); + ATF_REQUIRE(waitpid(pid, &status, 0) == pid); + ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == 0); + return (fd); +} + +static int +open_null(int flags) +{ + int fd; + + fd = open("/dev/null", O_RDONLY | flags); + ATF_REQUIRE(fd != -1); + return (fd); +} + +ATF_TC_WITHOUT_HEAD(kqueue_no_hole); +ATF_TC_BODY(kqueue_no_hole, tc) +{ + int kq; + + kq = kqueue(); + ATF_REQUIRE(kq != -1); + ATF_CHECK_EQ(child_dup(), kq); +} + +ATF_TC_WITHOUT_HEAD(kqueue_two); +ATF_TC_BODY(kqueue_two, tc) +{ + int kq1, kq2; + + kq1 = kqueue(); + ATF_REQUIRE(kq1 != -1); + kq2 = kqueue(); + ATF_REQUIRE(kq2 != -1); + ATF_CHECK_EQ(child_dup(), kq1); +} + +ATF_TC_WITHOUT_HEAD(kqueue_above_hole); +ATF_TC_BODY(kqueue_above_hole, tc) +{ + int a, b, kq; + + a = open_null(0); + b = open_null(0); + kq = kqueue(); + ATF_REQUIRE(kq > b); + ATF_REQUIRE(close(a) == 0); + ATF_CHECK_EQ(child_dup(), a); +} + +ATF_TC_WITHOUT_HEAD(kqueue_cponfork); +ATF_TC_BODY(kqueue_cponfork, tc) +{ + int kq; + + kq = kqueuex(KQUEUE_CPONFORK); + ATF_REQUIRE(kq != -1); + ATF_CHECK_EQ(child_dup(), kq + 1); +} + +ATF_TC_WITHOUT_HEAD(clofork_no_hole); +ATF_TC_BODY(clofork_no_hole, tc) +{ + int fd; + + fd = open_null(O_CLOFORK); + ATF_CHECK_EQ(child_dup(), fd); +} + +ATF_TC_WITHOUT_HEAD(clofork_above_hole); +ATF_TC_BODY(clofork_above_hole, tc) +{ + int a, b, c; + + a = open_null(0); + b = open_null(0); + c = open_null(O_CLOFORK); + ATF_REQUIRE(c > b); + ATF_REQUIRE(close(a) == 0); + ATF_CHECK_EQ(child_dup(), a); +} + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, kqueue_no_hole); + ATF_TP_ADD_TC(tp, kqueue_two); + ATF_TP_ADD_TC(tp, kqueue_above_hole); + ATF_TP_ADD_TC(tp, kqueue_cponfork); + ATF_TP_ADD_TC(tp, clofork_no_hole); + ATF_TP_ADD_TC(tp, clofork_above_hole); + + return (atf_no_error()); +}