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