From nobody Thu Feb 5 15:27:27 2026 X-Original-To: dev-commits-src-all@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 4f6Lgg544Wz6Qkw4 for ; Thu, 05 Feb 2026 15:27:27 +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 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "R13" (verified OK)) by mx1.freebsd.org (Postfix) with ESMTPS id 4f6Lgg2Vqpz3XPw for ; Thu, 05 Feb 2026 15:27:27 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1770305247; 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=IzKYa0bk0MeoGrJI9CsfiIeY0p+CJ3D09r5j/D5YpM4=; b=lj1CbpKtVRvwe9qLtXD4Uv+m2S19S98rZQMMjU8214YSB26yIpMIBOkaN5yUYApzh+KjWr A6RUiWElEBjS7v5PleFbp6n4ffBlkxVYQtGGxzoiKi5pAx7NQRvjAVbrRs/izXs32G3gvZ ccHVlMnc3Wx11rvs05mN32S3n04iChg3l84WlrBqO+9IYHkqGBDzCnh+4qb6EcrGxzfST5 u5Yv6TzN31gzl3t9ALD6KludwDXxKWGDBcbQT/ocqSX4QEwZsAsnyXBIXBbkVb592/X1iL W28tsOnX/zUafTCuVdxFiZW0/TpfLbpHp0Lb+nssgSwbWZXbKNeiotUJOGZEhw== ARC-Seal: i=1; s=dkim; d=freebsd.org; t=1770305247; a=rsa-sha256; cv=none; b=qbyzL/wCzw0uzlW+Oxa9FmvZBaTKQybHUR7tZHl+yqgLhO1fi0JJ969LHoCtUBJ/N7D4QI +qywNt8X4zURpbXUR9fVnSGcFoLGlG6iiVNuBM4WWjNoCX46vjfi8+iabwQxoKme05K+tG 7SVea6lrkfGnZN2sPJOJ8gC/ayAbfJzyjhQfoYX8eA82rho7P//sJ4Z/fcAMbSoG3BOb39 cJ9POy/eTMtHTFun4qKaDXQHdKAVI+HtEE3qLgimH9rxH4QtlyRN8jbhvJy6V3DWmYuzDM fezFHAskFN4gmiAdKH7njUhALj1TL+lexh4OPI5mzvOTWQoHkWkDiL+taNIf0Q== ARC-Authentication-Results: i=1; mx1.freebsd.org; none ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1770305247; 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=IzKYa0bk0MeoGrJI9CsfiIeY0p+CJ3D09r5j/D5YpM4=; b=bhVQLTUr1/R2hukcXpKINOw8By6y58whqKvCNRbu65XzBUis+RkP0UA2PpWJWSyNWVePhV wOA0Fk7jOSocQ14buMasgLR2p574RGiiDGMJrrY5OIVnEj6pGIOKkjfDCUjiKZB6jP3M6W rYrRw9Ltv9RReGHtOVcA3lLVCvYrxPTpUT//bmgnT8NY2t7ijx4zZ/lKns8LbWQMXIiKoe qlbcJ7aRx54X6m2KI4P/ptmO606aytiYmOR/T1BKcmhh2mS59sc52Q5bEmPvhDCXeihx2B VNIl8lLaSvAwuCoaQYTdvCmgRtn7Hx0ZH8e/WpyXQXJ2MctlYzwxsI/i8pc7Ew== Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) by mxrelay.nyi.freebsd.org (Postfix) with ESMTP id 4f6Lgg23ldzbGy for ; Thu, 05 Feb 2026 15:27:27 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from git (uid 1279) (envelope-from git@FreeBSD.org) id 181ab by gitrepo.freebsd.org (DragonFly Mail Agent v0.13+ on gitrepo.freebsd.org); Thu, 05 Feb 2026 15:27:27 +0000 To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-branches@FreeBSD.org From: Dag-Erling=?utf-8?Q? Sm=C3=B8rg?=rav Subject: git: 1a3c32bac0f0 - stable/14 - scandir: Propagate errors from readdir(). List-Id: Commit messages for all branches of the src repository List-Archive: https://lists.freebsd.org/archives/dev-commits-src-all List-Help: List-Post: List-Subscribe: List-Unsubscribe: X-BeenThere: dev-commits-src-all@freebsd.org Sender: owner-dev-commits-src-all@FreeBSD.org MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: des X-Git-Repository: src X-Git-Refname: refs/heads/stable/14 X-Git-Reftype: branch X-Git-Commit: 1a3c32bac0f029b0186bb856373fe3bcd2d5e18f Auto-Submitted: auto-generated Date: Thu, 05 Feb 2026 15:27:27 +0000 Message-Id: <6984b6df.181ab.2a9659a6@gitrepo.freebsd.org> The branch stable/14 has been updated by des: URL: https://cgit.FreeBSD.org/src/commit/?id=1a3c32bac0f029b0186bb856373fe3bcd2d5e18f commit 1a3c32bac0f029b0186bb856373fe3bcd2d5e18f Author: Dag-Erling Smørgrav AuthorDate: 2025-06-26 07:37:00 +0000 Commit: Dag-Erling Smørgrav CommitDate: 2026-02-05 14:47:46 +0000 scandir: Propagate errors from readdir(). Currently, if `readdir()` fails, `scandir()` simply returns a partial result (or a null result if it fails before any entries were selected). There is no way within the current API design to return both a partial result and an error indicator, so err on the side of caution: if an error occurs, discard any partial result and return the error instead. MFC after: 1 week Reported by: Maxim Suhanov Sponsored by: Klara, Inc. Reviewed by: markj Differential Revision: https://reviews.freebsd.org/D51046 (cherry picked from commit 62e0f12f5104585b7346fee183e5c667b39ddbad) --- lib/libc/gen/scandir.3 | 32 +++++++++++++++++++- lib/libc/gen/scandir.c | 12 ++++++-- lib/libc/tests/gen/scandir_test.c | 63 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+), 3 deletions(-) diff --git a/lib/libc/gen/scandir.3 b/lib/libc/gen/scandir.3 index 1d26d9f8be34..82365adcad29 100644 --- a/lib/libc/gen/scandir.3 +++ b/lib/libc/gen/scandir.3 @@ -27,7 +27,7 @@ .\" .\" @(#)scandir.3 8.1 (Berkeley) 6/4/93 .\" -.Dd June 20, 2025 +.Dd June 25, 2025 .Dt SCANDIR 3 .Os .Sh NAME @@ -217,6 +217,36 @@ cannot allocate enough memory to hold all the directory entries, they return \-1 and set .Va errno to an appropriate value. +.Sh ERRORS +The +.Fn scandir , +.Fn scandirat , +.Fn scandir_b , +and +.Fn scandirat_b +functions may fail and set +.Va errno +for any of the errors specified for the +.Xr opendir 3 , +.Xr malloc 3 , +.Xr readdir 3 , +and +.Xr closedir 3 +functions. +.Pp +The +.Fn fdscandir +and +.Fn fdscandir_b +functions may fail and set +.Va errno +for any of the errors specified for the +.Xr fdopendir 3 , +.Xr malloc 3 , +.Xr readdir 3 , +and +.Xr closedir 3 +functions. .Sh SEE ALSO .Xr openat 2 , .Xr directory 3 , diff --git a/lib/libc/gen/scandir.c b/lib/libc/gen/scandir.c index eee8e39cb7eb..4d1ccab14f91 100644 --- a/lib/libc/gen/scandir.c +++ b/lib/libc/gen/scandir.c @@ -83,7 +83,7 @@ scandir_dirp(DIR *dirp, struct dirent ***namelist, if (names == NULL) return (-1); - while ((d = readdir(dirp)) != NULL) { + while (errno = 0, (d = readdir(dirp)) != NULL) { if (select != NULL && !SELECT(d)) continue; /* just selected names */ /* @@ -110,6 +110,13 @@ scandir_dirp(DIR *dirp, struct dirent ***namelist, } names[numitems++] = p; } + /* + * Since we can't simultaneously return both -1 and a count, we + * must either suppress the error or discard the partial result. + * The latter seems the lesser of two evils. + */ + if (errno != 0) + goto fail; if (numitems && dcomp != NULL) #ifdef I_AM_SCANDIR_B qsort_b(names, numitems, sizeof(struct dirent *), (void*)dcomp); @@ -122,7 +129,8 @@ scandir_dirp(DIR *dirp, struct dirent ***namelist, fail: serrno = errno; - free(p); + if (numitems == 0 || names[numitems - 1] != p) + free(p); while (numitems > 0) free(names[--numitems]); free(names); diff --git a/lib/libc/tests/gen/scandir_test.c b/lib/libc/tests/gen/scandir_test.c index 9a9940aca881..f7b52b5e3616 100644 --- a/lib/libc/tests/gen/scandir_test.c +++ b/lib/libc/tests/gen/scandir_test.c @@ -7,7 +7,9 @@ #include #include +#include #include +#include #include #include @@ -124,11 +126,72 @@ ATF_TC_BODY(scandir_none, tc) free(namelist); } +/* + * Test that scandir() propagates errors from readdir(): we create a + * directory with enough entries that it can't be read in a single + * getdirentries() call, then abuse the selection callback to close the + * file descriptor scandir() is using after the first call, causing the + * next one to fail, and verify that readdir() returns an error instead of + * a partial result. We make two passes, one in which nothing was + * selected before the error occurred, and one in which everything was. + */ +static int scandir_error_count; +static int scandir_error_fd; +static int scandir_error_select_return; + +static int +scandir_error_select(const struct dirent *ent __unused) +{ + if (scandir_error_count++ == 0) + close(scandir_error_fd); + return (scandir_error_select_return); +} + +ATF_TC(scandir_error); +ATF_TC_HEAD(scandir_error, tc) +{ + atf_tc_set_md_var(tc, "descr", + "Test that scandir() propagates errors from readdir()"); +} +ATF_TC_BODY(scandir_error, tc) +{ + char path[16]; + struct dirent **namelist = NULL; + int fd, i, ret; + + ATF_REQUIRE_EQ(0, mkdir("dir", 0755)); + for (i = 0; i < 1024; i++) { + snprintf(path, sizeof(path), "dir/%04x", i); + ATF_REQUIRE_EQ(0, symlink(path + 4, path)); + } + + /* first pass, select nothing */ + ATF_REQUIRE((fd = open("dir", O_DIRECTORY | O_RDONLY)) >= 0); + scandir_error_count = 0; + scandir_error_fd = fd; + scandir_error_select_return = 0; + ret = fdscandir(fd, &namelist, scandir_error_select, NULL); + ATF_CHECK_EQ(-1, ret); + ATF_CHECK_ERRNO(EBADF, ret < 0); + ATF_CHECK_EQ(NULL, namelist); + + /* second pass, select everything */ + ATF_REQUIRE((fd = open("dir", O_DIRECTORY | O_RDONLY)) >= 0); + scandir_error_count = 0; + scandir_error_fd = fd; + scandir_error_select_return = 1; + ret = fdscandir(fd, &namelist, scandir_error_select, NULL); + ATF_CHECK_EQ(-1, ret); + ATF_CHECK_ERRNO(EBADF, ret < 0); + ATF_CHECK_EQ(NULL, namelist); +} + ATF_TP_ADD_TCS(tp) { ATF_TP_ADD_TC(tp, scandir_test); ATF_TP_ADD_TC(tp, fdscandir_test); ATF_TP_ADD_TC(tp, scandirat_test); ATF_TP_ADD_TC(tp, scandir_none); + ATF_TP_ADD_TC(tp, scandir_error); return (atf_no_error()); }