xref: /freebsd/lib/libc/tests/gen/fts_children_test.c (revision e624417db8a136849caa31fc34266645ed6c3429)
1 /*
2  * Copyright (c) 2026 Jitendra Bhati
3  *
4  * SPDX-License-Identifier: BSD-2-Clause
5  */
6 
7 /*
8  * Tests for fts_children().
9  */
10 
11 #include <sys/stat.h>
12 
13 #include <errno.h>
14 #include <fcntl.h>
15 #include <fts.h>
16 #include <stdbool.h>
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <string.h>
20 #include <unistd.h>
21 
22 #include <atf-c.h>
23 
24 #include "fts_test.h"
25 
26 /*
27  * fts_children() before fts_read() returns the list of root entries.
28  */
29 ATF_TC(before_read);
ATF_TC_HEAD(before_read,tc)30 ATF_TC_HEAD(before_read, tc)
31 {
32 	atf_tc_set_md_var(tc, "descr",
33 	    "fts_children before fts_read returns root entry list");
34 }
ATF_TC_BODY(before_read,tc)35 ATF_TC_BODY(before_read, tc)
36 {
37 	char *paths[] = { "dir", NULL };
38 	FTS *fts;
39 	FTSENT *children, *p;
40 	int count;
41 
42 	ATF_REQUIRE_EQ(0, mkdir("dir", 0755));
43 	ATF_REQUIRE_EQ(0, close(creat("dir/a", 0644)));
44 
45 	ATF_REQUIRE((fts = fts_open(paths, FTS_PHYSICAL, NULL)) != NULL);
46 
47 	errno = 0;
48 	children = fts_children(fts, 0);
49 	ATF_REQUIRE_MSG(children != NULL,
50 	    "fts_children before fts_read must return the root list");
51 	ATF_CHECK_EQ(0, errno);
52 
53 	count = 0;
54 	for (p = children; p != NULL; p = p->fts_link) {
55 		ATF_CHECK_EQ_MSG(FTS_D, p->fts_info,
56 		    "root entry should be FTS_D, got %d", p->fts_info);
57 		count++;
58 	}
59 	ATF_CHECK_EQ_MSG(1, count,
60 	    "expected 1 root entry, found %d", count);
61 
62 	ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m");
63 }
64 
65 /*
66  * fts_children() on an empty directory returns NULL with errno == 0.
67  * errno=0 distinguishes "empty" from an actual error.
68  */
69 ATF_TC(empty_dir);
ATF_TC_HEAD(empty_dir,tc)70 ATF_TC_HEAD(empty_dir, tc)
71 {
72 	atf_tc_set_md_var(tc, "descr",
73 	    "fts_children on empty directory returns NULL with errno 0");
74 }
ATF_TC_BODY(empty_dir,tc)75 ATF_TC_BODY(empty_dir, tc)
76 {
77 	char *paths[] = { "dir", NULL };
78 	FTS *fts;
79 	FTSENT *ent, *children;
80 
81 	ATF_REQUIRE_EQ(0, mkdir("dir", 0755));
82 
83 	ATF_REQUIRE((fts = fts_open(paths, FTS_PHYSICAL, NULL)) != NULL);
84 
85 	ent = fts_read(fts);
86 	ATF_REQUIRE(ent != NULL);
87 	ATF_REQUIRE_EQ_MSG(FTS_D, ent->fts_info,
88 	    "expected FTS_D, got %d", ent->fts_info);
89 
90 	errno = 1;	/* sentinel — fts_children must clear this */
91 	children = fts_children(fts, 0);
92 	ATF_CHECK_MSG(children == NULL,
93 	    "fts_children on empty dir must return NULL");
94 	ATF_CHECK_EQ_MSG(0, errno,
95 	    "fts_children on empty dir must set errno=0, got %d", errno);
96 
97 	ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m");
98 }
99 
100 /*
101  * fts_children() on a non-empty directory returns a linked list of all
102  * children in comparator order.
103  */
104 ATF_TC(nonempty_dir);
ATF_TC_HEAD(nonempty_dir,tc)105 ATF_TC_HEAD(nonempty_dir, tc)
106 {
107 	atf_tc_set_md_var(tc, "descr",
108 	    "fts_children on non-empty directory returns all children");
109 }
ATF_TC_BODY(nonempty_dir,tc)110 ATF_TC_BODY(nonempty_dir, tc)
111 {
112 	static const char *expected[] = { "a", "b", "c", NULL };
113 	char *paths[] = { "dir", NULL };
114 	FTS *fts;
115 	FTSENT *ent, *children, *p;
116 	int i;
117 
118 	ATF_REQUIRE_EQ(0, mkdir("dir", 0755));
119 	ATF_REQUIRE_EQ(0, close(creat("dir/a", 0644)));
120 	ATF_REQUIRE_EQ(0, close(creat("dir/b", 0644)));
121 	ATF_REQUIRE_EQ(0, close(creat("dir/c", 0644)));
122 
123 	ATF_REQUIRE((fts = fts_open(paths, FTS_PHYSICAL,
124 	    fts_lexical_compar)) != NULL);
125 
126 	ent = fts_read(fts);
127 	ATF_REQUIRE(ent != NULL);
128 	ATF_REQUIRE_EQ(FTS_D, ent->fts_info);
129 
130 	children = fts_children(fts, 0);
131 	ATF_REQUIRE_MSG(children != NULL, "fts_children(): %m");
132 
133 	i = 0;
134 	for (p = children; p != NULL; p = p->fts_link, i++) {
135 		ATF_REQUIRE_MSG(expected[i] != NULL,
136 		    "more children returned than expected");
137 		ATF_CHECK_STREQ(expected[i], p->fts_name);
138 		ATF_CHECK_EQ(FTS_F, p->fts_info);
139 	}
140 	ATF_CHECK_MSG(expected[i] == NULL,
141 	    "fewer children returned than expected");
142 
143 	ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m");
144 }
145 
146 /*
147  * fts_children() called twice on the same FTS_D node must return an
148  * equivalent list both times.
149  */
150 ATF_TC(called_twice);
ATF_TC_HEAD(called_twice,tc)151 ATF_TC_HEAD(called_twice, tc)
152 {
153 	atf_tc_set_md_var(tc, "descr",
154 	    "fts_children called twice returns equivalent results");
155 }
ATF_TC_BODY(called_twice,tc)156 ATF_TC_BODY(called_twice, tc)
157 {
158 	char *paths[] = { "dir", NULL };
159 	FTS *fts;
160 	FTSENT *ent, *first, *second, *p;
161 	int count1, count2;
162 
163 	ATF_REQUIRE_EQ(0, mkdir("dir", 0755));
164 	ATF_REQUIRE_EQ(0, close(creat("dir/x", 0644)));
165 	ATF_REQUIRE_EQ(0, close(creat("dir/y", 0644)));
166 
167 	ATF_REQUIRE((fts = fts_open(paths, FTS_PHYSICAL,
168 	    fts_lexical_compar)) != NULL);
169 
170 	ent = fts_read(fts);
171 	ATF_REQUIRE(ent != NULL);
172 	ATF_REQUIRE_EQ(FTS_D, ent->fts_info);
173 
174 	first = fts_children(fts, 0);
175 	ATF_REQUIRE_MSG(first != NULL, "first fts_children call: %m");
176 
177 	count1 = 0;
178 	for (p = first; p != NULL; p = p->fts_link)
179 		count1++;
180 
181 	/*
182 	 * The second call frees the first list and rebuilds.  Do not
183 	 * dereference 'first' after this point — it has been freed.
184 	 */
185 	second = fts_children(fts, 0);
186 	ATF_REQUIRE_MSG(second != NULL, "second fts_children call: %m");
187 
188 	count2 = 0;
189 	for (p = second; p != NULL; p = p->fts_link)
190 		count2++;
191 
192 	ATF_CHECK_EQ_MSG(count1, count2,
193 	    "first call returned %d children, second returned %d",
194 	    count1, count2);
195 	ATF_CHECK_EQ(2, count2);
196 
197 	ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m");
198 }
199 
200 /*
201  * fts_children(FTS_NAMEONLY): only fts_name and fts_namelen are filled.
202  * fts_info is FTS_NSOK for every entry.
203  */
204 ATF_TC(nameonly);
ATF_TC_HEAD(nameonly,tc)205 ATF_TC_HEAD(nameonly, tc)
206 {
207 	atf_tc_set_md_var(tc, "descr",
208 	    "FTS_NAMEONLY fills only fts_name, fts_info is FTS_NSOK");
209 }
ATF_TC_BODY(nameonly,tc)210 ATF_TC_BODY(nameonly, tc)
211 {
212 	char *paths[] = { "dir", NULL };
213 	FTS *fts;
214 	FTSENT *ent, *children, *p;
215 	int count;
216 
217 	ATF_REQUIRE_EQ(0, mkdir("dir", 0755));
218 	ATF_REQUIRE_EQ(0, close(creat("dir/f1", 0644)));
219 	ATF_REQUIRE_EQ(0, close(creat("dir/f2", 0644)));
220 
221 	ATF_REQUIRE((fts = fts_open(paths, FTS_PHYSICAL,
222 	    fts_lexical_compar)) != NULL);
223 
224 	ent = fts_read(fts);
225 	ATF_REQUIRE(ent != NULL);
226 	ATF_REQUIRE_EQ(FTS_D, ent->fts_info);
227 
228 	children = fts_children(fts, FTS_NAMEONLY);
229 	ATF_REQUIRE_MSG(children != NULL, "fts_children(FTS_NAMEONLY): %m");
230 
231 	count = 0;
232 	for (p = children; p != NULL; p = p->fts_link) {
233 		ATF_CHECK_MSG(p->fts_name[0] != '\0',
234 		    "FTS_NAMEONLY: fts_name is empty");
235 		ATF_CHECK_EQ(strlen(p->fts_name), p->fts_namelen);
236 		ATF_CHECK_EQ_MSG(FTS_NSOK, p->fts_info,
237 		    "FTS_NAMEONLY: expected FTS_NSOK, got %d", p->fts_info);
238 		count++;
239 	}
240 	ATF_CHECK_EQ(2, count);
241 
242 	/* Normal traversal must still work after FTS_NAMEONLY. */
243 	while (fts_read(fts) != NULL)
244 		;
245 	ATF_CHECK_EQ_MSG(0, errno,
246 	    "traversal after FTS_NAMEONLY ended with errno %d", errno);
247 
248 	ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m");
249 }
250 
251 /*
252  * fts_children() on a non-directory node must return NULL with errno == 0.
253  */
254 ATF_TC(nondirectory);
ATF_TC_HEAD(nondirectory,tc)255 ATF_TC_HEAD(nondirectory, tc)
256 {
257 	atf_tc_set_md_var(tc, "descr",
258 	    "fts_children on a non-directory node returns NULL with errno 0");
259 }
ATF_TC_BODY(nondirectory,tc)260 ATF_TC_BODY(nondirectory, tc)
261 {
262 	char *paths[] = { "dir", NULL };
263 	FTS *fts;
264 	FTSENT *ent;
265 
266 	ATF_REQUIRE_EQ(0, mkdir("dir", 0755));
267 	ATF_REQUIRE_EQ(0, close(creat("dir/file", 0644)));
268 
269 	ATF_REQUIRE((fts = fts_open(paths, FTS_PHYSICAL,
270 	    fts_lexical_compar)) != NULL);
271 
272 	ent = fts_read(fts);	/* FTS_D dir */
273 	ATF_REQUIRE(ent != NULL);
274 	ATF_REQUIRE_EQ(FTS_D, ent->fts_info);
275 
276 	ent = fts_read(fts);	/* FTS_F file */
277 	ATF_REQUIRE(ent != NULL);
278 	ATF_REQUIRE_EQ(FTS_F, ent->fts_info);
279 
280 	errno = 1;
281 	ATF_CHECK_MSG(fts_children(fts, 0) == NULL,
282 	    "fts_children on FTS_F must return NULL");
283 	ATF_CHECK_EQ_MSG(0, errno,
284 	    "fts_children on FTS_F must set errno=0, got %d", errno);
285 
286 	ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m");
287 }
288 
289 /*
290  * fts_children() with an invalid options value must return NULL with
291  * errno == EINVAL.
292  */
293 ATF_TC(invalid_options);
ATF_TC_HEAD(invalid_options,tc)294 ATF_TC_HEAD(invalid_options, tc)
295 {
296 	atf_tc_set_md_var(tc, "descr",
297 	    "fts_children with invalid options returns NULL with EINVAL");
298 }
ATF_TC_BODY(invalid_options,tc)299 ATF_TC_BODY(invalid_options, tc)
300 {
301 	char *paths[] = { ".", NULL };
302 	FTS *fts;
303 
304 	ATF_REQUIRE((fts = fts_open(paths, FTS_PHYSICAL, NULL)) != NULL);
305 
306 	ATF_REQUIRE_ERRNO(EINVAL, fts_children(fts, 99) == NULL);
307 
308 	ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m");
309 }
310 
ATF_TP_ADD_TCS(tp)311 ATF_TP_ADD_TCS(tp)
312 {
313 	fts_check_debug();
314 	ATF_TP_ADD_TC(tp, before_read);
315 	ATF_TP_ADD_TC(tp, empty_dir);
316 	ATF_TP_ADD_TC(tp, nonempty_dir);
317 	ATF_TP_ADD_TC(tp, called_twice);
318 	ATF_TP_ADD_TC(tp, nameonly);
319 	ATF_TP_ADD_TC(tp, nondirectory);
320 	ATF_TP_ADD_TC(tp, invalid_options);
321 
322 	return (atf_no_error());
323 }
324