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 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 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 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); 107 ATF_TC_HEAD(fts_options_logical, tc) 108 { 109 atf_tc_set_md_var(tc, "descr", "FTS_LOGICAL"); 110 } 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); 148 ATF_TC_HEAD(fts_options_logical_nostat, tc) 149 { 150 atf_tc_set_md_var(tc, "descr", "FTS_LOGICAL | FTS_NOSTAT"); 151 } 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); 199 ATF_TC_HEAD(fts_options_logical_seedot, tc) 200 { 201 atf_tc_set_md_var(tc, "descr", "FTS_LOGICAL | FTS_SEEDOT"); 202 } 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); 248 ATF_TC_HEAD(fts_options_physical, tc) 249 { 250 atf_tc_set_md_var(tc, "descr", "FTS_PHYSICAL"); 251 } 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); 274 ATF_TC_HEAD(fts_options_physical_nochdir, tc) 275 { 276 atf_tc_set_md_var(tc, "descr", "FTS_PHYSICAL | FTS_NOCHDIR"); 277 } 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); 300 ATF_TC_HEAD(fts_options_physical_comfollow, tc) 301 { 302 atf_tc_set_md_var(tc, "descr", "FTS_PHYSICAL | FTS_COMFOLLOW"); 303 } 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); 329 ATF_TC_HEAD(fts_options_physical_comfollowdir, tc) 330 { 331 atf_tc_set_md_var(tc, "descr", "FTS_PHYSICAL | FTS_COMFOLLOWDIR"); 332 } 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); 358 ATF_TC_HEAD(fts_options_physical_nostat, tc) 359 { 360 atf_tc_set_md_var(tc, "descr", "FTS_PHYSICAL | FTS_NOSTAT"); 361 } 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); 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 } 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); 410 ATF_TC_HEAD(fts_options_physical_seedot, tc) 411 { 412 atf_tc_set_md_var(tc, "descr", "FTS_PHYSICAL | FTS_SEEDOT"); 413 } 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 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