xref: /freebsd/lib/libc/tests/gen/scandir_test.c (revision 62e0f12f5104585b7346fee183e5c667b39ddbad)
1 /*-
2  * Copyright (c) 2025 Klara, Inc.
3  *
4  * SPDX-License-Identifier: BSD-2-Clause
5  */
6 
7 #include <sys/stat.h>
8 
9 #include <dirent.h>
10 #include <errno.h>
11 #include <fcntl.h>
12 #include <stdio.h>
13 #include <stdlib.h>
14 
15 #include <atf-c.h>
16 
17 static void
scandir_prepare(const struct atf_tc * tc)18 scandir_prepare(const struct atf_tc *tc)
19 {
20 	ATF_REQUIRE_EQ(0, mkdir("dir", 0755));
21 	ATF_REQUIRE_EQ(0, mkdir("dir/dir", 0755));
22 	ATF_REQUIRE_EQ(0, close(creat("dir/file", 0644)));
23 	ATF_REQUIRE_EQ(0, symlink("file", "dir/link"));
24 	ATF_REQUIRE_EQ(0, mkdir("dir/skip", 0755));
25 }
26 
27 static void
scandir_verify(const struct atf_tc * tc,int n,struct dirent ** namelist)28 scandir_verify(const struct atf_tc *tc, int n, struct dirent **namelist)
29 {
30 	ATF_REQUIRE_EQ_MSG(5, n, "return value is %d", n);
31 	ATF_CHECK_STREQ("link", namelist[0]->d_name);
32 	ATF_CHECK_STREQ("file", namelist[1]->d_name);
33 	ATF_CHECK_STREQ("dir", namelist[2]->d_name);
34 	ATF_CHECK_STREQ("..", namelist[3]->d_name);
35 	ATF_CHECK_STREQ(".", namelist[4]->d_name);
36 }
37 
38 static int
scandir_select(const struct dirent * ent)39 scandir_select(const struct dirent *ent)
40 {
41 	return (strcmp(ent->d_name, "skip") != 0);
42 }
43 
44 static int
scandir_compare(const struct dirent ** a,const struct dirent ** b)45 scandir_compare(const struct dirent **a, const struct dirent **b)
46 {
47 	return (strcmp((*b)->d_name, (*a)->d_name));
48 }
49 
50 ATF_TC(scandir_test);
ATF_TC_HEAD(scandir_test,tc)51 ATF_TC_HEAD(scandir_test, tc)
52 {
53 	atf_tc_set_md_var(tc, "descr", "Test scandir()");
54 }
ATF_TC_BODY(scandir_test,tc)55 ATF_TC_BODY(scandir_test, tc)
56 {
57 	struct dirent **namelist = NULL;
58 	int i, ret;
59 
60 	scandir_prepare(tc);
61 	ret = scandir("dir", &namelist, scandir_select, scandir_compare);
62 	scandir_verify(tc, ret, namelist);
63 	for (i = 0; i < ret; i++)
64 		free(namelist[i]);
65 	free(namelist);
66 }
67 
68 ATF_TC(fdscandir_test);
ATF_TC_HEAD(fdscandir_test,tc)69 ATF_TC_HEAD(fdscandir_test, tc)
70 {
71 	atf_tc_set_md_var(tc, "descr", "Test fdscandir()");
72 }
ATF_TC_BODY(fdscandir_test,tc)73 ATF_TC_BODY(fdscandir_test, tc)
74 {
75 	struct dirent **namelist = NULL;
76 	int fd, i, ret;
77 
78 	scandir_prepare(tc);
79 	ATF_REQUIRE((fd = open("dir", O_DIRECTORY | O_RDONLY)) >= 0);
80 	ret = fdscandir(fd, &namelist, scandir_select, scandir_compare);
81 	scandir_verify(tc, ret, namelist);
82 	for (i = 0; i < ret; i++)
83 		free(namelist[i]);
84 	free(namelist);
85 	ATF_REQUIRE_EQ(0, close(fd));
86 }
87 
88 ATF_TC(scandirat_test);
ATF_TC_HEAD(scandirat_test,tc)89 ATF_TC_HEAD(scandirat_test, tc)
90 {
91 	atf_tc_set_md_var(tc, "descr", "Test scandirat()");
92 }
ATF_TC_BODY(scandirat_test,tc)93 ATF_TC_BODY(scandirat_test, tc)
94 {
95 	struct dirent **namelist = NULL;
96 	int fd, i, ret;
97 
98 	scandir_prepare(tc);
99 	ATF_REQUIRE((fd = open("dir", O_DIRECTORY | O_SEARCH)) >= 0);
100 	ret = scandirat(fd, ".", &namelist, scandir_select, scandir_compare);
101 	scandir_verify(tc, ret, namelist);
102 	for (i = 0; i < ret; i++)
103 		free(namelist[i]);
104 	free(namelist);
105 	ATF_REQUIRE_EQ(0, close(fd));
106 }
107 
108 static int
scandir_none(const struct dirent * ent __unused)109 scandir_none(const struct dirent *ent __unused)
110 {
111 	return (0);
112 }
113 
114 ATF_TC(scandir_none);
ATF_TC_HEAD(scandir_none,tc)115 ATF_TC_HEAD(scandir_none, tc)
116 {
117 	atf_tc_set_md_var(tc, "descr",
118 	    "Test scandir() when no entries are selected");
119 }
ATF_TC_BODY(scandir_none,tc)120 ATF_TC_BODY(scandir_none, tc)
121 {
122 	struct dirent **namelist = NULL;
123 
124 	ATF_REQUIRE_EQ(0, scandir(".", &namelist, scandir_none, alphasort));
125 	ATF_REQUIRE(namelist);
126 	free(namelist);
127 }
128 
129 /*
130  * Test that scandir() propagates errors from readdir(): we create a
131  * directory with enough entries that it can't be read in a single
132  * getdirentries() call, then abuse the selection callback to close the
133  * file descriptor scandir() is using after the first call, causing the
134  * next one to fail, and verify that readdir() returns an error instead of
135  * a partial result.  We make two passes, one in which nothing was
136  * selected before the error occurred, and one in which everything was.
137  */
138 static int scandir_error_count;
139 static int scandir_error_fd;
140 static int scandir_error_select_return;
141 
142 static int
scandir_error_select(const struct dirent * ent __unused)143 scandir_error_select(const struct dirent *ent __unused)
144 {
145 	if (scandir_error_count++ == 0)
146 		close(scandir_error_fd);
147 	return (scandir_error_select_return);
148 }
149 
150 ATF_TC(scandir_error);
ATF_TC_HEAD(scandir_error,tc)151 ATF_TC_HEAD(scandir_error, tc)
152 {
153 	atf_tc_set_md_var(tc, "descr",
154 	    "Test that scandir() propagates errors from readdir()");
155 }
ATF_TC_BODY(scandir_error,tc)156 ATF_TC_BODY(scandir_error, tc)
157 {
158 	char path[16];
159 	struct dirent **namelist = NULL;
160 	int fd, i, ret;
161 
162 	ATF_REQUIRE_EQ(0, mkdir("dir", 0755));
163 	for (i = 0; i < 1024; i++) {
164 		snprintf(path, sizeof(path), "dir/%04x", i);
165 		ATF_REQUIRE_EQ(0, symlink(path + 4, path));
166 	}
167 
168 	/* first pass, select nothing */
169 	ATF_REQUIRE((fd = open("dir", O_DIRECTORY | O_RDONLY)) >= 0);
170 	scandir_error_count = 0;
171 	scandir_error_fd = fd;
172 	scandir_error_select_return = 0;
173 	ret = fdscandir(fd, &namelist, scandir_error_select, NULL);
174 	ATF_CHECK_EQ(-1, ret);
175 	ATF_CHECK_ERRNO(EBADF, ret < 0);
176 	ATF_CHECK_EQ(NULL, namelist);
177 
178 	/* second pass, select everything */
179 	ATF_REQUIRE((fd = open("dir", O_DIRECTORY | O_RDONLY)) >= 0);
180 	scandir_error_count = 0;
181 	scandir_error_fd = fd;
182 	scandir_error_select_return = 1;
183 	ret = fdscandir(fd, &namelist, scandir_error_select, NULL);
184 	ATF_CHECK_EQ(-1, ret);
185 	ATF_CHECK_ERRNO(EBADF, ret < 0);
186 	ATF_CHECK_EQ(NULL, namelist);
187 }
188 
ATF_TP_ADD_TCS(tp)189 ATF_TP_ADD_TCS(tp)
190 {
191 	ATF_TP_ADD_TC(tp, scandir_test);
192 	ATF_TP_ADD_TC(tp, fdscandir_test);
193 	ATF_TP_ADD_TC(tp, scandirat_test);
194 	ATF_TP_ADD_TC(tp, scandir_none);
195 	ATF_TP_ADD_TC(tp, scandir_error);
196 	return (atf_no_error());
197 }
198