git: 8d02b7190d3b - main - fts: Add test cases for unreadable directories.

From: Dag-Erling Smørgrav <des_at_FreeBSD.org>
Date: Wed, 02 Jul 2025 10:23:03 UTC
The branch main has been updated by des:

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

commit 8d02b7190d3b926118fafc6a70a80676c349e186
Author:     Dag-Erling Smørgrav <des@FreeBSD.org>
AuthorDate: 2025-07-02 10:22:16 +0000
Commit:     Dag-Erling Smørgrav <des@FreeBSD.org>
CommitDate: 2025-07-02 10:22:29 +0000

    fts: Add test cases for unreadable directories.
    
    Sponsored by:   Klara, Inc.
    Reviewed by:    kevans
    Differential Revision:  https://reviews.freebsd.org/D51098
---
 lib/libc/tests/gen/Makefile           |  9 ++++
 lib/libc/tests/gen/fts_misc_test.c    | 78 ++++++++++++++++++++++++++++++++
 lib/libc/tests/gen/fts_options_test.c | 84 +++++------------------------------
 lib/libc/tests/gen/fts_test.h         | 81 +++++++++++++++++++++++++++++++++
 4 files changed, 180 insertions(+), 72 deletions(-)

diff --git a/lib/libc/tests/gen/Makefile b/lib/libc/tests/gen/Makefile
index b7df4b1d037b..a967ad5ddf91 100644
--- a/lib/libc/tests/gen/Makefile
+++ b/lib/libc/tests/gen/Makefile
@@ -10,6 +10,7 @@ ATF_TESTS_C+=		fpclassify2_test
 .if ${COMPILER_FEATURES:Mblocks}
 ATF_TESTS_C+=		fts_blocks_test
 .endif
+ATF_TESTS_C+=		fts_misc_test
 ATF_TESTS_C+=		fts_options_test
 ATF_TESTS_C+=		ftw_test
 ATF_TESTS_C+=		getentropy_test
@@ -104,6 +105,14 @@ TEST_METADATA.setdomainname_test+=	is_exclusive=true
 TESTS_SUBDIRS=	execve
 TESTS_SUBDIRS+=	posix_spawn
 
+# Tests that require address sanitizer
+.if ${COMPILER_FEATURES:Masan}
+.for t in scandir_test realpath2_test
+CFLAGS.${t}.c+=		-fsanitize=address
+LDFLAGS.${t}+=		-fsanitize=address
+.endfor
+.endif
+
 # Tests that require blocks support
 .for t in fts_blocks_test glob_blocks_test scandir_blocks_test
 CFLAGS.${t}.c+=		-fblocks
diff --git a/lib/libc/tests/gen/fts_misc_test.c b/lib/libc/tests/gen/fts_misc_test.c
new file mode 100644
index 000000000000..91640078f63c
--- /dev/null
+++ b/lib/libc/tests/gen/fts_misc_test.c
@@ -0,0 +1,78 @@
+/*-
+ * Copyright (c) 2025 Klara, Inc.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <sys/stat.h>
+
+#include <fcntl.h>
+#include <fts.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <atf-c.h>
+
+#include "fts_test.h"
+
+ATF_TC(fts_unrdir);
+ATF_TC_HEAD(fts_unrdir, tc)
+{
+	atf_tc_set_md_var(tc, "descr", "unreadable directories");
+	atf_tc_set_md_var(tc, "require.user", "unprivileged");
+}
+ATF_TC_BODY(fts_unrdir, tc)
+{
+	ATF_REQUIRE_EQ(0, mkdir("dir", 0755));
+	ATF_REQUIRE_EQ(0, mkdir("dir/unr", 0100));
+	ATF_REQUIRE_EQ(0, mkdir("dir/unx", 0400));
+	fts_test(tc, &(struct fts_testcase){
+		    (char *[]){ "dir", NULL },
+		    FTS_PHYSICAL,
+		    (struct fts_expect[]){
+			    { FTS_D,	"dir",	"dir" },
+			    { FTS_D,	"unr",	"unr" },
+			    { FTS_DNR,	"unr",	"unr" },
+			    { FTS_D,	"unx",	"unx" },
+			    { FTS_DP,	"unx",	"unx" },
+			    { FTS_DP,	"dir",	"dir" },
+			    { 0 }
+		    },
+	    });
+}
+
+ATF_TC(fts_unrdir_nochdir);
+ATF_TC_HEAD(fts_unrdir_nochdir, tc)
+{
+	atf_tc_set_md_var(tc, "descr", "unreadable directories (nochdir)");
+	atf_tc_set_md_var(tc, "require.user", "unprivileged");
+}
+ATF_TC_BODY(fts_unrdir_nochdir, tc)
+{
+	ATF_REQUIRE_EQ(0, mkdir("dir", 0755));
+	ATF_REQUIRE_EQ(0, mkdir("dir/unr", 0100));
+	ATF_REQUIRE_EQ(0, mkdir("dir/unx", 0400));
+	fts_test(tc, &(struct fts_testcase){
+		    (char *[]){ "dir", NULL },
+		    FTS_PHYSICAL | FTS_NOCHDIR,
+		    (struct fts_expect[]){
+			    { FTS_D,	"dir",	"dir" },
+			    { FTS_D,	"unr",	"dir/unr" },
+			    { FTS_DNR,	"unr",	"dir/unr" },
+			    { FTS_D,	"unx",	"dir/unx" },
+			    { FTS_DP,	"unx",	"dir/unx" },
+			    { FTS_DP,	"dir",	"dir" },
+			    { 0 }
+		    },
+	    });
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+	fts_check_debug();
+	ATF_TP_ADD_TC(tp, fts_unrdir);
+	ATF_TP_ADD_TC(tp, fts_unrdir_nochdir);
+	return (atf_no_error());
+}
diff --git a/lib/libc/tests/gen/fts_options_test.c b/lib/libc/tests/gen/fts_options_test.c
index c80474b70ac7..fc3015138a49 100644
--- a/lib/libc/tests/gen/fts_options_test.c
+++ b/lib/libc/tests/gen/fts_options_test.c
@@ -15,17 +15,7 @@
 
 #include <atf-c.h>
 
-struct fts_expect {
-	int fts_info;
-	const char *fts_name;
-	const char *fts_accpath;
-};
-
-struct fts_testcase {
-	char **paths;
-	int fts_options;
-	struct fts_expect *fts_expect;
-};
+#include "fts_test.h"
 
 static char *all_paths[] = {
 	"dir",
@@ -37,20 +27,12 @@ static char *all_paths[] = {
 	NULL
 };
 
-/* shorter name for dead links */
-#define FTS_DL FTS_SLNONE
-
-/* are we being debugged? */
-static bool debug;
-
 /*
  * Prepare the files and directories we will be inspecting.
  */
 static void
 fts_options_prepare(const struct atf_tc *tc)
 {
-	debug = !getenv("__RUNNING_INSIDE_ATF_RUN") &&
-	    isatty(STDERR_FILENO);
 	ATF_REQUIRE_EQ(0, mkdir("dir", 0755));
 	ATF_REQUIRE_EQ(0, close(creat("file", 0644)));
 	ATF_REQUIRE_EQ(0, close(creat("dir/file", 0644)));
@@ -60,49 +42,6 @@ fts_options_prepare(const struct atf_tc *tc)
 	ATF_REQUIRE_EQ(0, symlink("noent", "dead"));
 }
 
-/*
- * Lexical order for reproducability.
- */
-static int
-fts_options_compar(const FTSENT * const *a, const FTSENT * const *b)
-{
-	return (strcmp((*a)->fts_name, (*b)->fts_name));
-}
-
-/*
- * Run FTS with the specified paths and options and verify that it
- * produces the expected result in the correct order.
- */
-static void
-fts_options_test(const struct atf_tc *tc, const struct fts_testcase *fts_tc)
-{
-	FTS *fts;
-	FTSENT *ftse;
-	const struct fts_expect *expect = fts_tc->fts_expect;
-	long level = 0;
-
-	fts = fts_open(fts_tc->paths, fts_tc->fts_options, fts_options_compar);
-	ATF_REQUIRE_MSG(fts != NULL, "fts_open(): %m");
-	while ((ftse = fts_read(fts)) != NULL && expect->fts_name != NULL) {
-		if (expect->fts_info == FTS_DP)
-			level--;
-		if (debug) {
-			fprintf(stderr, "%2ld %2d %s\n", level,
-			    ftse->fts_info, ftse->fts_name);
-		}
-		ATF_CHECK_STREQ(expect->fts_name, ftse->fts_name);
-		ATF_CHECK_STREQ(expect->fts_accpath, ftse->fts_accpath);
-		ATF_CHECK_INTEQ(expect->fts_info, ftse->fts_info);
-		ATF_CHECK_INTEQ(level, ftse->fts_level);
-		if (expect->fts_info == FTS_D)
-			level++;
-		expect++;
-	}
-	ATF_CHECK_EQ(NULL, ftse);
-	ATF_CHECK_EQ(NULL, expect->fts_name);
-	ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m");
-}
-
 ATF_TC(fts_options_logical);
 ATF_TC_HEAD(fts_options_logical, tc)
 {
@@ -111,7 +50,7 @@ ATF_TC_HEAD(fts_options_logical, tc)
 ATF_TC_BODY(fts_options_logical, tc)
 {
 	fts_options_prepare(tc);
-	fts_options_test(tc, &(struct fts_testcase){
+	fts_test(tc, &(struct fts_testcase){
 		    all_paths,
 		    FTS_LOGICAL,
 		    (struct fts_expect[]){
@@ -162,7 +101,7 @@ ATF_TC_BODY(fts_options_logical_nostat, tc)
 	 */
 	atf_tc_expect_fail("FTS_LOGICAL nullifies FTS_NOSTAT");
 	fts_options_prepare(tc);
-	fts_options_test(tc, &(struct fts_testcase){
+	fts_test(tc, &(struct fts_testcase){
 		    all_paths,
 		    FTS_LOGICAL | FTS_NOSTAT,
 		    (struct fts_expect[]){
@@ -203,7 +142,7 @@ ATF_TC_HEAD(fts_options_logical_seedot, tc)
 ATF_TC_BODY(fts_options_logical_seedot, tc)
 {
 	fts_options_prepare(tc);
-	fts_options_test(tc, &(struct fts_testcase){
+	fts_test(tc, &(struct fts_testcase){
 		    all_paths,
 		    FTS_LOGICAL | FTS_SEEDOT,
 		    (struct fts_expect[]){
@@ -252,7 +191,7 @@ ATF_TC_HEAD(fts_options_physical, tc)
 ATF_TC_BODY(fts_options_physical, tc)
 {
 	fts_options_prepare(tc);
-	fts_options_test(tc, &(struct fts_testcase){
+	fts_test(tc, &(struct fts_testcase){
 		    all_paths,
 		    FTS_PHYSICAL,
 		    (struct fts_expect[]){
@@ -278,7 +217,7 @@ ATF_TC_HEAD(fts_options_physical_nochdir, tc)
 ATF_TC_BODY(fts_options_physical_nochdir, tc)
 {
 	fts_options_prepare(tc);
-	fts_options_test(tc, &(struct fts_testcase){
+	fts_test(tc, &(struct fts_testcase){
 		    all_paths,
 		    FTS_PHYSICAL | FTS_NOCHDIR,
 		    (struct fts_expect[]){
@@ -304,7 +243,7 @@ ATF_TC_HEAD(fts_options_physical_comfollow, tc)
 ATF_TC_BODY(fts_options_physical_comfollow, tc)
 {
 	fts_options_prepare(tc);
-	fts_options_test(tc, &(struct fts_testcase){
+	fts_test(tc, &(struct fts_testcase){
 		    all_paths,
 		    FTS_PHYSICAL | FTS_COMFOLLOW,
 		    (struct fts_expect[]){
@@ -333,7 +272,7 @@ ATF_TC_HEAD(fts_options_physical_comfollowdir, tc)
 ATF_TC_BODY(fts_options_physical_comfollowdir, tc)
 {
 	fts_options_prepare(tc);
-	fts_options_test(tc, &(struct fts_testcase){
+	fts_test(tc, &(struct fts_testcase){
 		    all_paths,
 		    FTS_PHYSICAL | FTS_COMFOLLOWDIR,
 		    (struct fts_expect[]){
@@ -362,7 +301,7 @@ ATF_TC_HEAD(fts_options_physical_nostat, tc)
 ATF_TC_BODY(fts_options_physical_nostat, tc)
 {
 	fts_options_prepare(tc);
-	fts_options_test(tc, &(struct fts_testcase){
+	fts_test(tc, &(struct fts_testcase){
 		    all_paths,
 		    FTS_PHYSICAL | FTS_NOSTAT,
 		    (struct fts_expect[]){
@@ -388,7 +327,7 @@ ATF_TC_HEAD(fts_options_physical_nostat_type, tc)
 ATF_TC_BODY(fts_options_physical_nostat_type, tc)
 {
 	fts_options_prepare(tc);
-	fts_options_test(tc, &(struct fts_testcase){
+	fts_test(tc, &(struct fts_testcase){
 		    all_paths,
 		    FTS_PHYSICAL | FTS_NOSTAT_TYPE,
 		    (struct fts_expect[]){
@@ -414,7 +353,7 @@ ATF_TC_HEAD(fts_options_physical_seedot, tc)
 ATF_TC_BODY(fts_options_physical_seedot, tc)
 {
 	fts_options_prepare(tc);
-	fts_options_test(tc, &(struct fts_testcase){
+	fts_test(tc, &(struct fts_testcase){
 		    all_paths,
 		    FTS_PHYSICAL | FTS_SEEDOT,
 		    (struct fts_expect[]){
@@ -440,6 +379,7 @@ ATF_TC_BODY(fts_options_physical_seedot, tc)
 
 ATF_TP_ADD_TCS(tp)
 {
+	fts_check_debug();
 	ATF_TP_ADD_TC(tp, fts_options_logical);
 	ATF_TP_ADD_TC(tp, fts_options_logical_nostat);
 	ATF_TP_ADD_TC(tp, fts_options_logical_seedot);
diff --git a/lib/libc/tests/gen/fts_test.h b/lib/libc/tests/gen/fts_test.h
new file mode 100644
index 000000000000..b3f15050f265
--- /dev/null
+++ b/lib/libc/tests/gen/fts_test.h
@@ -0,0 +1,81 @@
+/*-
+ * Copyright (c) 2025 Klara, Inc.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#ifndef FTS_TEST_H_INCLUDED
+#define FTS_TEST_H_INCLUDED
+
+struct fts_expect {
+	int fts_info;
+	const char *fts_name;
+	const char *fts_accpath;
+};
+
+struct fts_testcase {
+	char **paths;
+	int fts_options;
+	struct fts_expect *fts_expect;
+};
+
+/* shorter name for dead links */
+#define FTS_DL FTS_SLNONE
+
+/* are we being debugged? */
+static bool fts_test_debug;
+
+/*
+ * Set debug flag if appropriate.
+ */
+static void
+fts_check_debug(void)
+{
+	fts_test_debug = !getenv("__RUNNING_INSIDE_ATF_RUN") &&
+	    isatty(STDERR_FILENO);
+}
+
+/*
+ * Lexical order for reproducability.
+ */
+static int
+fts_lexical_compar(const FTSENT * const *a, const FTSENT * const *b)
+{
+	return (strcmp((*a)->fts_name, (*b)->fts_name));
+}
+
+/*
+ * Run FTS with the specified paths and options and verify that it
+ * produces the expected result in the correct order.
+ */
+static void
+fts_test(const struct atf_tc *tc, const struct fts_testcase *fts_tc)
+{
+	FTS *fts;
+	FTSENT *ftse;
+	const struct fts_expect *expect = fts_tc->fts_expect;
+	long level = 0;
+
+	fts = fts_open(fts_tc->paths, fts_tc->fts_options, fts_lexical_compar);
+	ATF_REQUIRE_MSG(fts != NULL, "fts_open(): %m");
+	while ((ftse = fts_read(fts)) != NULL && expect->fts_name != NULL) {
+		if (expect->fts_info == FTS_DP || expect->fts_info == FTS_DNR)
+			level--;
+		if (fts_test_debug) {
+			fprintf(stderr, "%2ld %2d %s\n", level,
+			    ftse->fts_info, ftse->fts_name);
+		}
+		ATF_CHECK_STREQ(expect->fts_name, ftse->fts_name);
+		ATF_CHECK_STREQ(expect->fts_accpath, ftse->fts_accpath);
+		ATF_CHECK_INTEQ(expect->fts_info, ftse->fts_info);
+		ATF_CHECK_INTEQ(level, ftse->fts_level);
+		if (expect->fts_info == FTS_D)
+			level++;
+		expect++;
+	}
+	ATF_CHECK_EQ(NULL, ftse);
+	ATF_CHECK_EQ(NULL, expect->fts_name);
+	ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m");
+}
+
+#endif /* FTS_TEST_H_INCLUDED */