xref: /freebsd/lib/libc/tests/gen/fts_options_test.c (revision d30a84ab442e13ba0ed9ac3b01743e49f94dcb41)
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 struct fts_expect {
19 	int fts_info;
20 	const char *fts_name;
21 	const char *fts_accpath;
22 };
23 
24 struct fts_testcase {
25 	char **paths;
26 	int fts_options;
27 	struct fts_expect *fts_expect;
28 };
29 
30 static char *all_paths[] = {
31 	"dir",
32 	"dirl",
33 	"file",
34 	"filel",
35 	"dead",
36 	"noent",
37 	NULL
38 };
39 
40 /* shorter name for dead links */
41 #define FTS_DL FTS_SLNONE
42 
43 /* are we being debugged? */
44 static bool debug;
45 
46 /*
47  * Prepare the files and directories we will be inspecting.
48  */
49 static void
fts_options_prepare(const struct atf_tc * tc)50 fts_options_prepare(const struct atf_tc *tc)
51 {
52 	debug = !getenv("__RUNNING_INSIDE_ATF_RUN") &&
53 	    isatty(STDERR_FILENO);
54 	ATF_REQUIRE_EQ(0, mkdir("dir", 0755));
55 	ATF_REQUIRE_EQ(0, close(creat("file", 0644)));
56 	ATF_REQUIRE_EQ(0, close(creat("dir/file", 0644)));
57 	ATF_REQUIRE_EQ(0, symlink("..", "dir/up"));
58 	ATF_REQUIRE_EQ(0, symlink("dir", "dirl"));
59 	ATF_REQUIRE_EQ(0, symlink("file", "filel"));
60 	ATF_REQUIRE_EQ(0, symlink("noent", "dead"));
61 }
62 
63 /*
64  * Lexical order for reproducability.
65  */
66 static int
fts_options_compar(const FTSENT * const * a,const FTSENT * const * b)67 fts_options_compar(const FTSENT * const *a, const FTSENT * const *b)
68 {
69 	return (strcmp((*a)->fts_name, (*b)->fts_name));
70 }
71 
72 /*
73  * Run FTS with the specified paths and options and verify that it
74  * produces the expected result in the correct order.
75  */
76 static void
fts_options_test(const struct atf_tc * tc,const struct fts_testcase * fts_tc)77 fts_options_test(const struct atf_tc *tc, const struct fts_testcase *fts_tc)
78 {
79 	FTS *fts;
80 	FTSENT *ftse;
81 	const struct fts_expect *expect = fts_tc->fts_expect;
82 	long level = 0;
83 
84 	fts = fts_open(fts_tc->paths, fts_tc->fts_options, fts_options_compar);
85 	ATF_REQUIRE_MSG(fts != NULL, "fts_open(): %m");
86 	while ((ftse = fts_read(fts)) != NULL && expect->fts_name != NULL) {
87 		if (expect->fts_info == FTS_DP)
88 			level--;
89 		if (debug) {
90 			fprintf(stderr, "%2ld %2d %s\n", level,
91 			    ftse->fts_info, ftse->fts_name);
92 		}
93 		ATF_CHECK_STREQ(expect->fts_name, ftse->fts_name);
94 		ATF_CHECK_STREQ(expect->fts_accpath, ftse->fts_accpath);
95 		ATF_CHECK_INTEQ(expect->fts_info, ftse->fts_info);
96 		ATF_CHECK_INTEQ(level, ftse->fts_level);
97 		if (expect->fts_info == FTS_D)
98 			level++;
99 		expect++;
100 	}
101 	ATF_CHECK_EQ(NULL, ftse);
102 	ATF_CHECK_EQ(NULL, expect->fts_name);
103 	ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m");
104 }
105 
106 ATF_TC(fts_options_logical);
ATF_TC_HEAD(fts_options_logical,tc)107 ATF_TC_HEAD(fts_options_logical, tc)
108 {
109 	atf_tc_set_md_var(tc, "descr", "FTS_LOGICAL");
110 }
ATF_TC_BODY(fts_options_logical,tc)111 ATF_TC_BODY(fts_options_logical, tc)
112 {
113 	fts_options_prepare(tc);
114 	fts_options_test(tc, &(struct fts_testcase){
115 		    all_paths,
116 		    FTS_LOGICAL,
117 		    (struct fts_expect[]){
118 			    { FTS_DL,	"dead",		"dead"		},
119 			    { FTS_D,	"dir",		"dir"		},
120 			    { FTS_F,	"file",		"dir/file"	},
121 			    { FTS_D,	"up",		"dir/up"	},
122 			    { FTS_DL,	"dead",		"dir/up/dead"	},
123 			    { FTS_DC,	"dir",		"dir/up/dir"	},
124 			    { FTS_DC,	"dirl",		"dir/up/dirl"	},
125 			    { FTS_F,	"file",		"dir/up/file"	},
126 			    { FTS_F,	"filel",	"dir/up/filel"	},
127 			    { FTS_DP,	"up",		"dir/up"	},
128 			    { FTS_DP,	"dir",		"dir"		},
129 			    { FTS_D,	"dirl",		"dirl"		},
130 			    { FTS_F,	"file",		"dirl/file"	},
131 			    { FTS_D,	"up",		"dirl/up"	},
132 			    { FTS_DL,	"dead",		"dirl/up/dead"	},
133 			    { FTS_DC,	"dir",		"dirl/up/dir"	},
134 			    { FTS_DC,	"dirl",		"dirl/up/dirl"	},
135 			    { FTS_F,	"file",		"dirl/up/file"	},
136 			    { FTS_F,	"filel",	"dirl/up/filel"	},
137 			    { FTS_DP,	"up",		"dirl/up"	},
138 			    { FTS_DP,	"dirl",		"dirl"		},
139 			    { FTS_F,	"file",		"file"		},
140 			    { FTS_F,	"filel",	"filel"		},
141 			    { FTS_NS,	"noent",	"noent"		},
142 			    { 0 }
143 		    },
144 	    });
145 }
146 
147 ATF_TC(fts_options_logical_nostat);
ATF_TC_HEAD(fts_options_logical_nostat,tc)148 ATF_TC_HEAD(fts_options_logical_nostat, tc)
149 {
150 	atf_tc_set_md_var(tc, "descr", "FTS_LOGICAL | FTS_NOSTAT");
151 }
ATF_TC_BODY(fts_options_logical_nostat,tc)152 ATF_TC_BODY(fts_options_logical_nostat, tc)
153 {
154 	/*
155 	 * While FTS_LOGICAL is not documented as being incompatible with
156 	 * FTS_NOSTAT, and FTS does not clear FTS_NOSTAT if FTS_LOGICAL is
157 	 * set, FTS_LOGICAL effectively nullifies FTS_NOSTAT by overriding
158 	 * the follow check in fts_stat().  In theory, FTS could easily be
159 	 * changed to only stat links (to check what they point to) in the
160 	 * FTS_LOGICAL | FTS_NOSTAT case, which would produce a different
161 	 * result here, so keep the test around in case that ever happens.
162 	 */
163 	atf_tc_expect_fail("FTS_LOGICAL nullifies FTS_NOSTAT");
164 	fts_options_prepare(tc);
165 	fts_options_test(tc, &(struct fts_testcase){
166 		    all_paths,
167 		    FTS_LOGICAL | FTS_NOSTAT,
168 		    (struct fts_expect[]){
169 			    { FTS_DL,	"dead",		"dead"		},
170 			    { FTS_D,	"dir",		"dir"		},
171 			    { FTS_NSOK,	"file",		"dir/file"	},
172 			    { FTS_D,	"up",		"dir/up"	},
173 			    { FTS_DL,	"dead",		"dir/up/dead"	},
174 			    { FTS_DC,	"dir",		"dir/up/dir"	},
175 			    { FTS_DC,	"dirl",		"dir/up/dirl"	},
176 			    { FTS_NSOK,	"file",		"dir/up/file"	},
177 			    { FTS_NSOK,	"filel",	"dir/up/filel"	},
178 			    { FTS_DP,	"up",		"dir/up"	},
179 			    { FTS_DP,	"dir",		"dir"		},
180 			    { FTS_D,	"dirl",		"dirl"		},
181 			    { FTS_NSOK,	"file",		"dirl/file"	},
182 			    { FTS_D,	"up",		"dirl/up"	},
183 			    { FTS_DL,	"dead",		"dirl/up/dead"	},
184 			    { FTS_DC,	"dir",		"dirl/up/dir"	},
185 			    { FTS_DC,	"dirl",		"dirl/up/dirl"	},
186 			    { FTS_NSOK,	"file",		"dirl/up/file"	},
187 			    { FTS_NSOK,	"filel",	"dirl/up/filel"	},
188 			    { FTS_DP,	"up",		"dirl/up"	},
189 			    { FTS_DP,	"dirl",		"dirl"		},
190 			    { FTS_F,	"file",		"file"		},
191 			    { FTS_F,	"filel",	"filel"		},
192 			    { FTS_NS,	"noent",	"noent"		},
193 			    { 0 }
194 		    },
195 	    });
196 }
197 
198 ATF_TC(fts_options_logical_seedot);
ATF_TC_HEAD(fts_options_logical_seedot,tc)199 ATF_TC_HEAD(fts_options_logical_seedot, tc)
200 {
201 	atf_tc_set_md_var(tc, "descr", "FTS_LOGICAL | FTS_SEEDOT");
202 }
ATF_TC_BODY(fts_options_logical_seedot,tc)203 ATF_TC_BODY(fts_options_logical_seedot, tc)
204 {
205 	fts_options_prepare(tc);
206 	fts_options_test(tc, &(struct fts_testcase){
207 		    all_paths,
208 		    FTS_LOGICAL | FTS_SEEDOT,
209 		    (struct fts_expect[]){
210 			    { FTS_DL,	"dead",		"dead"		},
211 			    { FTS_D,	"dir",		"dir"		},
212 			    { FTS_DOT,	".",		"dir/."		},
213 			    { FTS_DOT,	"..",		"dir/.."	},
214 			    { FTS_F,	"file",		"dir/file"	},
215 			    { FTS_D,	"up",		"dir/up"	},
216 			    { FTS_DOT,	".",		"dir/up/."	},
217 			    { FTS_DOT,	"..",		"dir/up/.."	},
218 			    { FTS_DL,	"dead",		"dir/up/dead"	},
219 			    { FTS_DC,	"dir",		"dir/up/dir"	},
220 			    { FTS_DC,	"dirl",		"dir/up/dirl"	},
221 			    { FTS_F,	"file",		"dir/up/file"	},
222 			    { FTS_F,	"filel",	"dir/up/filel"	},
223 			    { FTS_DP,	"up",		"dir/up"	},
224 			    { FTS_DP,	"dir",		"dir"		},
225 			    { FTS_D,	"dirl",		"dirl"		},
226 			    { FTS_DOT,	".",		"dirl/."	},
227 			    { FTS_DOT,	"..",		"dirl/.."	},
228 			    { FTS_F,	"file",		"dirl/file"	},
229 			    { FTS_D,	"up",		"dirl/up"	},
230 			    { FTS_DOT,	".",		"dirl/up/."	},
231 			    { FTS_DOT,	"..",		"dirl/up/.."	},
232 			    { FTS_DL,	"dead",		"dirl/up/dead"	},
233 			    { FTS_DC,	"dir",		"dirl/up/dir"	},
234 			    { FTS_DC,	"dirl",		"dirl/up/dirl"	},
235 			    { FTS_F,	"file",		"dirl/up/file"	},
236 			    { FTS_F,	"filel",	"dirl/up/filel"	},
237 			    { FTS_DP,	"up",		"dirl/up"	},
238 			    { FTS_DP,	"dirl",		"dirl"		},
239 			    { FTS_F,	"file",		"file"		},
240 			    { FTS_F,	"filel",	"filel"		},
241 			    { FTS_NS,	"noent",	"noent"		},
242 			    { 0 }
243 		    },
244 	    });
245 }
246 
247 ATF_TC(fts_options_physical);
ATF_TC_HEAD(fts_options_physical,tc)248 ATF_TC_HEAD(fts_options_physical, tc)
249 {
250 	atf_tc_set_md_var(tc, "descr", "FTS_PHYSICAL");
251 }
ATF_TC_BODY(fts_options_physical,tc)252 ATF_TC_BODY(fts_options_physical, tc)
253 {
254 	fts_options_prepare(tc);
255 	fts_options_test(tc, &(struct fts_testcase){
256 		    all_paths,
257 		    FTS_PHYSICAL,
258 		    (struct fts_expect[]){
259 			    { FTS_SL,	"dead",		"dead"		},
260 			    { FTS_D,	"dir",		"dir"		},
261 			    { FTS_F,	"file",		"file"		},
262 			    { FTS_SL,	"up",		"up"		},
263 			    { FTS_DP,	"dir",		"dir"		},
264 			    { FTS_SL,	"dirl",		"dirl"		},
265 			    { FTS_F,	"file",		"file"		},
266 			    { FTS_SL,	"filel",	"filel"		},
267 			    { FTS_NS,	"noent",	"noent"		},
268 			    { 0 }
269 		    },
270 	    });
271 }
272 
273 ATF_TC(fts_options_physical_nochdir);
ATF_TC_HEAD(fts_options_physical_nochdir,tc)274 ATF_TC_HEAD(fts_options_physical_nochdir, tc)
275 {
276 	atf_tc_set_md_var(tc, "descr", "FTS_PHYSICAL | FTS_NOCHDIR");
277 }
ATF_TC_BODY(fts_options_physical_nochdir,tc)278 ATF_TC_BODY(fts_options_physical_nochdir, tc)
279 {
280 	fts_options_prepare(tc);
281 	fts_options_test(tc, &(struct fts_testcase){
282 		    all_paths,
283 		    FTS_PHYSICAL | FTS_NOCHDIR,
284 		    (struct fts_expect[]){
285 			    { FTS_SL,	"dead",		"dead"		},
286 			    { FTS_D,	"dir",		"dir"		},
287 			    { FTS_F,	"file",		"dir/file"	},
288 			    { FTS_SL,	"up",		"dir/up"	},
289 			    { FTS_DP,	"dir",		"dir"		},
290 			    { FTS_SL,	"dirl",		"dirl"		},
291 			    { FTS_F,	"file",		"file"		},
292 			    { FTS_SL,	"filel",	"filel"		},
293 			    { FTS_NS,	"noent",	"noent"		},
294 			    { 0 }
295 		    },
296 	    });
297 }
298 
299 ATF_TC(fts_options_physical_comfollow);
ATF_TC_HEAD(fts_options_physical_comfollow,tc)300 ATF_TC_HEAD(fts_options_physical_comfollow, tc)
301 {
302 	atf_tc_set_md_var(tc, "descr", "FTS_PHYSICAL | FTS_COMFOLLOW");
303 }
ATF_TC_BODY(fts_options_physical_comfollow,tc)304 ATF_TC_BODY(fts_options_physical_comfollow, tc)
305 {
306 	fts_options_prepare(tc);
307 	fts_options_test(tc, &(struct fts_testcase){
308 		    all_paths,
309 		    FTS_PHYSICAL | FTS_COMFOLLOW,
310 		    (struct fts_expect[]){
311 			    { FTS_DL,	"dead",		"dead"		},
312 			    { FTS_D,	"dir",		"dir"		},
313 			    { FTS_F,	"file",		"file"		},
314 			    { FTS_SL,	"up",		"up"		},
315 			    { FTS_DP,	"dir",		"dir"		},
316 			    { FTS_D,	"dirl",		"dirl"		},
317 			    { FTS_F,	"file",		"file"		},
318 			    { FTS_SL,	"up",		"up"		},
319 			    { FTS_DP,	"dirl",		"dirl"		},
320 			    { FTS_F,	"file",		"file"		},
321 			    { FTS_F,	"filel",	"filel"		},
322 			    { FTS_NS,	"noent",	"noent"		},
323 			    { 0 }
324 		    },
325 	    });
326 }
327 
328 ATF_TC(fts_options_physical_comfollowdir);
ATF_TC_HEAD(fts_options_physical_comfollowdir,tc)329 ATF_TC_HEAD(fts_options_physical_comfollowdir, tc)
330 {
331 	atf_tc_set_md_var(tc, "descr", "FTS_PHYSICAL | FTS_COMFOLLOWDIR");
332 }
ATF_TC_BODY(fts_options_physical_comfollowdir,tc)333 ATF_TC_BODY(fts_options_physical_comfollowdir, tc)
334 {
335 	fts_options_prepare(tc);
336 	fts_options_test(tc, &(struct fts_testcase){
337 		    all_paths,
338 		    FTS_PHYSICAL | FTS_COMFOLLOWDIR,
339 		    (struct fts_expect[]){
340 			    { FTS_DL,	"dead",		"dead"		},
341 			    { FTS_D,	"dir",		"dir"		},
342 			    { FTS_F,	"file",		"file"		},
343 			    { FTS_SL,	"up",		"up"		},
344 			    { FTS_DP,	"dir",		"dir"		},
345 			    { FTS_D,	"dirl",		"dirl"		},
346 			    { FTS_F,	"file",		"file"		},
347 			    { FTS_SL,	"up",		"up"		},
348 			    { FTS_DP,	"dirl",		"dirl"		},
349 			    { FTS_F,	"file",		"file"		},
350 			    { FTS_SL,	"filel",	"filel"		},
351 			    { FTS_NS,	"noent",	"noent"		},
352 			    { 0 }
353 		    },
354 	    });
355 }
356 
357 ATF_TC(fts_options_physical_nostat);
ATF_TC_HEAD(fts_options_physical_nostat,tc)358 ATF_TC_HEAD(fts_options_physical_nostat, tc)
359 {
360 	atf_tc_set_md_var(tc, "descr", "FTS_PHYSICAL | FTS_NOSTAT");
361 }
ATF_TC_BODY(fts_options_physical_nostat,tc)362 ATF_TC_BODY(fts_options_physical_nostat, tc)
363 {
364 	fts_options_prepare(tc);
365 	fts_options_test(tc, &(struct fts_testcase){
366 		    all_paths,
367 		    FTS_PHYSICAL | FTS_NOSTAT,
368 		    (struct fts_expect[]){
369 			    { FTS_SL,	"dead",		"dead"		},
370 			    { FTS_D,	"dir",		"dir"		},
371 			    { FTS_NSOK,	"file",		"file"		},
372 			    { FTS_NSOK,	"up",		"up"		},
373 			    { FTS_DP,	"dir",		"dir"		},
374 			    { FTS_SL,	"dirl",		"dirl"		},
375 			    { FTS_F,	"file",		"file"		},
376 			    { FTS_SL,	"filel",	"filel"		},
377 			    { FTS_NS,	"noent",	"noent"		},
378 			    { 0 }
379 		    },
380 	    });
381 }
382 
383 ATF_TC(fts_options_physical_nostat_type);
ATF_TC_HEAD(fts_options_physical_nostat_type,tc)384 ATF_TC_HEAD(fts_options_physical_nostat_type, tc)
385 {
386 	atf_tc_set_md_var(tc, "descr", "FTS_PHYSICAL | FTS_NOSTAT_TYPE");
387 }
ATF_TC_BODY(fts_options_physical_nostat_type,tc)388 ATF_TC_BODY(fts_options_physical_nostat_type, tc)
389 {
390 	fts_options_prepare(tc);
391 	fts_options_test(tc, &(struct fts_testcase){
392 		    all_paths,
393 		    FTS_PHYSICAL | FTS_NOSTAT_TYPE,
394 		    (struct fts_expect[]){
395 			    { FTS_SL,	"dead",		"dead"		},
396 			    { FTS_D,	"dir",		"dir"		},
397 			    { FTS_F,	"file",		"file"		},
398 			    { FTS_SL,	"up",		"up"		},
399 			    { FTS_DP,	"dir",		"dir"		},
400 			    { FTS_SL,	"dirl",		"dirl"		},
401 			    { FTS_F,	"file",		"file"		},
402 			    { FTS_SL,	"filel",	"filel"		},
403 			    { FTS_NS,	"noent",	"noent"		},
404 			    { 0 }
405 		    },
406 	    });
407 }
408 
409 ATF_TC(fts_options_physical_seedot);
ATF_TC_HEAD(fts_options_physical_seedot,tc)410 ATF_TC_HEAD(fts_options_physical_seedot, tc)
411 {
412 	atf_tc_set_md_var(tc, "descr", "FTS_PHYSICAL | FTS_SEEDOT");
413 }
ATF_TC_BODY(fts_options_physical_seedot,tc)414 ATF_TC_BODY(fts_options_physical_seedot, tc)
415 {
416 	fts_options_prepare(tc);
417 	fts_options_test(tc, &(struct fts_testcase){
418 		    all_paths,
419 		    FTS_PHYSICAL | FTS_SEEDOT,
420 		    (struct fts_expect[]){
421 			    { FTS_SL,	"dead",		"dead"		},
422 			    { FTS_D,	"dir",		"dir"		},
423 			    { FTS_DOT,	".",		"."		},
424 			    { FTS_DOT,	"..",		".."		},
425 			    { FTS_F,	"file",		"file"		},
426 			    { FTS_SL,	"up",		"up"		},
427 			    { FTS_DP,	"dir",		"dir"		},
428 			    { FTS_SL,	"dirl",		"dirl"		},
429 			    { FTS_F,	"file",		"file"		},
430 			    { FTS_SL,	"filel",	"filel"		},
431 			    { FTS_NS,	"noent",	"noent"		},
432 			    { 0 }
433 		    },
434 	    });
435 }
436 
437 /*
438  * TODO: Add tests for FTS_XDEV and FTS_WHITEOUT
439  */
440 
ATF_TP_ADD_TCS(tp)441 ATF_TP_ADD_TCS(tp)
442 {
443 	ATF_TP_ADD_TC(tp, fts_options_logical);
444 	ATF_TP_ADD_TC(tp, fts_options_logical_nostat);
445 	ATF_TP_ADD_TC(tp, fts_options_logical_seedot);
446 	ATF_TP_ADD_TC(tp, fts_options_physical);
447 	ATF_TP_ADD_TC(tp, fts_options_physical_nochdir);
448 	ATF_TP_ADD_TC(tp, fts_options_physical_comfollow);
449 	ATF_TP_ADD_TC(tp, fts_options_physical_comfollowdir);
450 	ATF_TP_ADD_TC(tp, fts_options_physical_nostat);
451 	ATF_TP_ADD_TC(tp, fts_options_physical_nostat_type);
452 	ATF_TP_ADD_TC(tp, fts_options_physical_seedot);
453 	return (atf_no_error());
454 }
455