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 <fcntl.h>
10 #include <fts.h>
11 #include <stdbool.h>
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <unistd.h>
15
16 #include <atf-c.h>
17
18 #include "fts_test.h"
19
20 static char *all_paths[] = {
21 "dir",
22 "dirl",
23 "file",
24 "filel",
25 "dead",
26 "noent",
27 NULL
28 };
29
30 /*
31 * Prepare the files and directories we will be inspecting.
32 */
33 static void
fts_options_prepare(const struct atf_tc * tc)34 fts_options_prepare(const struct atf_tc *tc)
35 {
36 ATF_REQUIRE_EQ(0, mkdir("dir", 0755));
37 ATF_REQUIRE_EQ(0, close(creat("file", 0644)));
38 ATF_REQUIRE_EQ(0, close(creat("dir/file", 0644)));
39 ATF_REQUIRE_EQ(0, symlink("..", "dir/up"));
40 ATF_REQUIRE_EQ(0, symlink("dir", "dirl"));
41 ATF_REQUIRE_EQ(0, symlink("file", "filel"));
42 ATF_REQUIRE_EQ(0, symlink("noent", "dead"));
43 }
44
45 ATF_TC(fts_options_logical);
ATF_TC_HEAD(fts_options_logical,tc)46 ATF_TC_HEAD(fts_options_logical, tc)
47 {
48 atf_tc_set_md_var(tc, "descr", "FTS_LOGICAL");
49 }
ATF_TC_BODY(fts_options_logical,tc)50 ATF_TC_BODY(fts_options_logical, tc)
51 {
52 fts_options_prepare(tc);
53 fts_test(tc, &(struct fts_testcase){
54 all_paths,
55 FTS_LOGICAL,
56 (struct fts_expect[]){
57 { FTS_DL, "dead", "dead" },
58 { FTS_D, "dir", "dir" },
59 { FTS_F, "file", "dir/file" },
60 { FTS_D, "up", "dir/up" },
61 { FTS_DL, "dead", "dir/up/dead" },
62 { FTS_DC, "dir", "dir/up/dir" },
63 { FTS_DC, "dirl", "dir/up/dirl" },
64 { FTS_F, "file", "dir/up/file" },
65 { FTS_F, "filel", "dir/up/filel" },
66 { FTS_DP, "up", "dir/up" },
67 { FTS_DP, "dir", "dir" },
68 { FTS_D, "dirl", "dirl" },
69 { FTS_F, "file", "dirl/file" },
70 { FTS_D, "up", "dirl/up" },
71 { FTS_DL, "dead", "dirl/up/dead" },
72 { FTS_DC, "dir", "dirl/up/dir" },
73 { FTS_DC, "dirl", "dirl/up/dirl" },
74 { FTS_F, "file", "dirl/up/file" },
75 { FTS_F, "filel", "dirl/up/filel" },
76 { FTS_DP, "up", "dirl/up" },
77 { FTS_DP, "dirl", "dirl" },
78 { FTS_F, "file", "file" },
79 { FTS_F, "filel", "filel" },
80 { FTS_NS, "noent", "noent" },
81 { 0 }
82 },
83 });
84 }
85
86 ATF_TC(fts_options_logical_nostat);
ATF_TC_HEAD(fts_options_logical_nostat,tc)87 ATF_TC_HEAD(fts_options_logical_nostat, tc)
88 {
89 atf_tc_set_md_var(tc, "descr", "FTS_LOGICAL | FTS_NOSTAT");
90 }
ATF_TC_BODY(fts_options_logical_nostat,tc)91 ATF_TC_BODY(fts_options_logical_nostat, tc)
92 {
93 /*
94 * While FTS_LOGICAL is not documented as being incompatible with
95 * FTS_NOSTAT, and FTS does not clear FTS_NOSTAT if FTS_LOGICAL is
96 * set, FTS_LOGICAL effectively nullifies FTS_NOSTAT by overriding
97 * the follow check in fts_stat(). In theory, FTS could easily be
98 * changed to only stat links (to check what they point to) in the
99 * FTS_LOGICAL | FTS_NOSTAT case, which would produce a different
100 * result here, so keep the test around in case that ever happens.
101 */
102 atf_tc_expect_fail("FTS_LOGICAL nullifies FTS_NOSTAT");
103 fts_options_prepare(tc);
104 fts_test(tc, &(struct fts_testcase){
105 all_paths,
106 FTS_LOGICAL | FTS_NOSTAT,
107 (struct fts_expect[]){
108 { FTS_DL, "dead", "dead" },
109 { FTS_D, "dir", "dir" },
110 { FTS_NSOK, "file", "dir/file" },
111 { FTS_D, "up", "dir/up" },
112 { FTS_DL, "dead", "dir/up/dead" },
113 { FTS_DC, "dir", "dir/up/dir" },
114 { FTS_DC, "dirl", "dir/up/dirl" },
115 { FTS_NSOK, "file", "dir/up/file" },
116 { FTS_NSOK, "filel", "dir/up/filel" },
117 { FTS_DP, "up", "dir/up" },
118 { FTS_DP, "dir", "dir" },
119 { FTS_D, "dirl", "dirl" },
120 { FTS_NSOK, "file", "dirl/file" },
121 { FTS_D, "up", "dirl/up" },
122 { FTS_DL, "dead", "dirl/up/dead" },
123 { FTS_DC, "dir", "dirl/up/dir" },
124 { FTS_DC, "dirl", "dirl/up/dirl" },
125 { FTS_NSOK, "file", "dirl/up/file" },
126 { FTS_NSOK, "filel", "dirl/up/filel" },
127 { FTS_DP, "up", "dirl/up" },
128 { FTS_DP, "dirl", "dirl" },
129 { FTS_F, "file", "file" },
130 { FTS_F, "filel", "filel" },
131 { FTS_NS, "noent", "noent" },
132 { 0 }
133 },
134 });
135 }
136
137 ATF_TC(fts_options_logical_seedot);
ATF_TC_HEAD(fts_options_logical_seedot,tc)138 ATF_TC_HEAD(fts_options_logical_seedot, tc)
139 {
140 atf_tc_set_md_var(tc, "descr", "FTS_LOGICAL | FTS_SEEDOT");
141 }
ATF_TC_BODY(fts_options_logical_seedot,tc)142 ATF_TC_BODY(fts_options_logical_seedot, tc)
143 {
144 fts_options_prepare(tc);
145 fts_test(tc, &(struct fts_testcase){
146 all_paths,
147 FTS_LOGICAL | FTS_SEEDOT,
148 (struct fts_expect[]){
149 { FTS_DL, "dead", "dead" },
150 { FTS_D, "dir", "dir" },
151 { FTS_DOT, ".", "dir/." },
152 { FTS_DOT, "..", "dir/.." },
153 { FTS_F, "file", "dir/file" },
154 { FTS_D, "up", "dir/up" },
155 { FTS_DOT, ".", "dir/up/." },
156 { FTS_DOT, "..", "dir/up/.." },
157 { FTS_DL, "dead", "dir/up/dead" },
158 { FTS_DC, "dir", "dir/up/dir" },
159 { FTS_DC, "dirl", "dir/up/dirl" },
160 { FTS_F, "file", "dir/up/file" },
161 { FTS_F, "filel", "dir/up/filel" },
162 { FTS_DP, "up", "dir/up" },
163 { FTS_DP, "dir", "dir" },
164 { FTS_D, "dirl", "dirl" },
165 { FTS_DOT, ".", "dirl/." },
166 { FTS_DOT, "..", "dirl/.." },
167 { FTS_F, "file", "dirl/file" },
168 { FTS_D, "up", "dirl/up" },
169 { FTS_DOT, ".", "dirl/up/." },
170 { FTS_DOT, "..", "dirl/up/.." },
171 { FTS_DL, "dead", "dirl/up/dead" },
172 { FTS_DC, "dir", "dirl/up/dir" },
173 { FTS_DC, "dirl", "dirl/up/dirl" },
174 { FTS_F, "file", "dirl/up/file" },
175 { FTS_F, "filel", "dirl/up/filel" },
176 { FTS_DP, "up", "dirl/up" },
177 { FTS_DP, "dirl", "dirl" },
178 { FTS_F, "file", "file" },
179 { FTS_F, "filel", "filel" },
180 { FTS_NS, "noent", "noent" },
181 { 0 }
182 },
183 });
184 }
185
186 ATF_TC(fts_options_physical);
ATF_TC_HEAD(fts_options_physical,tc)187 ATF_TC_HEAD(fts_options_physical, tc)
188 {
189 atf_tc_set_md_var(tc, "descr", "FTS_PHYSICAL");
190 }
ATF_TC_BODY(fts_options_physical,tc)191 ATF_TC_BODY(fts_options_physical, tc)
192 {
193 fts_options_prepare(tc);
194 fts_test(tc, &(struct fts_testcase){
195 all_paths,
196 FTS_PHYSICAL,
197 (struct fts_expect[]){
198 { FTS_SL, "dead", "dead" },
199 { FTS_D, "dir", "dir" },
200 { FTS_F, "file", "file" },
201 { FTS_SL, "up", "up" },
202 { FTS_DP, "dir", "dir" },
203 { FTS_SL, "dirl", "dirl" },
204 { FTS_F, "file", "file" },
205 { FTS_SL, "filel", "filel" },
206 { FTS_NS, "noent", "noent" },
207 { 0 }
208 },
209 });
210 }
211
212 ATF_TC(fts_options_physical_nochdir);
ATF_TC_HEAD(fts_options_physical_nochdir,tc)213 ATF_TC_HEAD(fts_options_physical_nochdir, tc)
214 {
215 atf_tc_set_md_var(tc, "descr", "FTS_PHYSICAL | FTS_NOCHDIR");
216 }
ATF_TC_BODY(fts_options_physical_nochdir,tc)217 ATF_TC_BODY(fts_options_physical_nochdir, tc)
218 {
219 fts_options_prepare(tc);
220 fts_test(tc, &(struct fts_testcase){
221 all_paths,
222 FTS_PHYSICAL | FTS_NOCHDIR,
223 (struct fts_expect[]){
224 { FTS_SL, "dead", "dead" },
225 { FTS_D, "dir", "dir" },
226 { FTS_F, "file", "dir/file" },
227 { FTS_SL, "up", "dir/up" },
228 { FTS_DP, "dir", "dir" },
229 { FTS_SL, "dirl", "dirl" },
230 { FTS_F, "file", "file" },
231 { FTS_SL, "filel", "filel" },
232 { FTS_NS, "noent", "noent" },
233 { 0 }
234 },
235 });
236 }
237
238 ATF_TC(fts_options_physical_comfollow);
ATF_TC_HEAD(fts_options_physical_comfollow,tc)239 ATF_TC_HEAD(fts_options_physical_comfollow, tc)
240 {
241 atf_tc_set_md_var(tc, "descr", "FTS_PHYSICAL | FTS_COMFOLLOW");
242 }
ATF_TC_BODY(fts_options_physical_comfollow,tc)243 ATF_TC_BODY(fts_options_physical_comfollow, tc)
244 {
245 fts_options_prepare(tc);
246 fts_test(tc, &(struct fts_testcase){
247 all_paths,
248 FTS_PHYSICAL | FTS_COMFOLLOW,
249 (struct fts_expect[]){
250 { FTS_DL, "dead", "dead" },
251 { FTS_D, "dir", "dir" },
252 { FTS_F, "file", "file" },
253 { FTS_SL, "up", "up" },
254 { FTS_DP, "dir", "dir" },
255 { FTS_D, "dirl", "dirl" },
256 { FTS_F, "file", "file" },
257 { FTS_SL, "up", "up" },
258 { FTS_DP, "dirl", "dirl" },
259 { FTS_F, "file", "file" },
260 { FTS_F, "filel", "filel" },
261 { FTS_NS, "noent", "noent" },
262 { 0 }
263 },
264 });
265 }
266
267 ATF_TC(fts_options_physical_comfollowdir);
ATF_TC_HEAD(fts_options_physical_comfollowdir,tc)268 ATF_TC_HEAD(fts_options_physical_comfollowdir, tc)
269 {
270 atf_tc_set_md_var(tc, "descr", "FTS_PHYSICAL | FTS_COMFOLLOWDIR");
271 }
ATF_TC_BODY(fts_options_physical_comfollowdir,tc)272 ATF_TC_BODY(fts_options_physical_comfollowdir, tc)
273 {
274 fts_options_prepare(tc);
275 fts_test(tc, &(struct fts_testcase){
276 all_paths,
277 FTS_PHYSICAL | FTS_COMFOLLOWDIR,
278 (struct fts_expect[]){
279 { FTS_DL, "dead", "dead" },
280 { FTS_D, "dir", "dir" },
281 { FTS_F, "file", "file" },
282 { FTS_SL, "up", "up" },
283 { FTS_DP, "dir", "dir" },
284 { FTS_D, "dirl", "dirl" },
285 { FTS_F, "file", "file" },
286 { FTS_SL, "up", "up" },
287 { FTS_DP, "dirl", "dirl" },
288 { FTS_F, "file", "file" },
289 { FTS_SL, "filel", "filel" },
290 { FTS_NS, "noent", "noent" },
291 { 0 }
292 },
293 });
294 }
295
296 ATF_TC(fts_options_physical_nostat);
ATF_TC_HEAD(fts_options_physical_nostat,tc)297 ATF_TC_HEAD(fts_options_physical_nostat, tc)
298 {
299 atf_tc_set_md_var(tc, "descr", "FTS_PHYSICAL | FTS_NOSTAT");
300 }
ATF_TC_BODY(fts_options_physical_nostat,tc)301 ATF_TC_BODY(fts_options_physical_nostat, tc)
302 {
303 fts_options_prepare(tc);
304 fts_test(tc, &(struct fts_testcase){
305 all_paths,
306 FTS_PHYSICAL | FTS_NOSTAT,
307 (struct fts_expect[]){
308 { FTS_SL, "dead", "dead" },
309 { FTS_D, "dir", "dir" },
310 { FTS_NSOK, "file", "file" },
311 { FTS_NSOK, "up", "up" },
312 { FTS_DP, "dir", "dir" },
313 { FTS_SL, "dirl", "dirl" },
314 { FTS_F, "file", "file" },
315 { FTS_SL, "filel", "filel" },
316 { FTS_NS, "noent", "noent" },
317 { 0 }
318 },
319 });
320 }
321
322 ATF_TC(fts_options_physical_nostat_type);
ATF_TC_HEAD(fts_options_physical_nostat_type,tc)323 ATF_TC_HEAD(fts_options_physical_nostat_type, tc)
324 {
325 atf_tc_set_md_var(tc, "descr", "FTS_PHYSICAL | FTS_NOSTAT_TYPE");
326 }
ATF_TC_BODY(fts_options_physical_nostat_type,tc)327 ATF_TC_BODY(fts_options_physical_nostat_type, tc)
328 {
329 fts_options_prepare(tc);
330 fts_test(tc, &(struct fts_testcase){
331 all_paths,
332 FTS_PHYSICAL | FTS_NOSTAT_TYPE,
333 (struct fts_expect[]){
334 { FTS_SL, "dead", "dead" },
335 { FTS_D, "dir", "dir" },
336 { FTS_F, "file", "file" },
337 { FTS_SL, "up", "up" },
338 { FTS_DP, "dir", "dir" },
339 { FTS_SL, "dirl", "dirl" },
340 { FTS_F, "file", "file" },
341 { FTS_SL, "filel", "filel" },
342 { FTS_NS, "noent", "noent" },
343 { 0 }
344 },
345 });
346 }
347
348 ATF_TC(fts_options_physical_seedot);
ATF_TC_HEAD(fts_options_physical_seedot,tc)349 ATF_TC_HEAD(fts_options_physical_seedot, tc)
350 {
351 atf_tc_set_md_var(tc, "descr", "FTS_PHYSICAL | FTS_SEEDOT");
352 }
ATF_TC_BODY(fts_options_physical_seedot,tc)353 ATF_TC_BODY(fts_options_physical_seedot, tc)
354 {
355 fts_options_prepare(tc);
356 fts_test(tc, &(struct fts_testcase){
357 all_paths,
358 FTS_PHYSICAL | FTS_SEEDOT,
359 (struct fts_expect[]){
360 { FTS_SL, "dead", "dead" },
361 { FTS_D, "dir", "dir" },
362 { FTS_DOT, ".", "." },
363 { FTS_DOT, "..", ".." },
364 { FTS_F, "file", "file" },
365 { FTS_SL, "up", "up" },
366 { FTS_DP, "dir", "dir" },
367 { FTS_SL, "dirl", "dirl" },
368 { FTS_F, "file", "file" },
369 { FTS_SL, "filel", "filel" },
370 { FTS_NS, "noent", "noent" },
371 { 0 }
372 },
373 });
374 }
375
376 /*
377 * TODO: Add tests for FTS_XDEV and FTS_WHITEOUT
378 */
379
ATF_TP_ADD_TCS(tp)380 ATF_TP_ADD_TCS(tp)
381 {
382 fts_check_debug();
383 ATF_TP_ADD_TC(tp, fts_options_logical);
384 ATF_TP_ADD_TC(tp, fts_options_logical_nostat);
385 ATF_TP_ADD_TC(tp, fts_options_logical_seedot);
386 ATF_TP_ADD_TC(tp, fts_options_physical);
387 ATF_TP_ADD_TC(tp, fts_options_physical_nochdir);
388 ATF_TP_ADD_TC(tp, fts_options_physical_comfollow);
389 ATF_TP_ADD_TC(tp, fts_options_physical_comfollowdir);
390 ATF_TP_ADD_TC(tp, fts_options_physical_nostat);
391 ATF_TP_ADD_TC(tp, fts_options_physical_nostat_type);
392 ATF_TP_ADD_TC(tp, fts_options_physical_seedot);
393 return (atf_no_error());
394 }
395