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 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); 46 ATF_TC_HEAD(fts_options_logical, tc) 47 { 48 atf_tc_set_md_var(tc, "descr", "FTS_LOGICAL"); 49 } 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); 87 ATF_TC_HEAD(fts_options_logical_nostat, tc) 88 { 89 atf_tc_set_md_var(tc, "descr", "FTS_LOGICAL | FTS_NOSTAT"); 90 } 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); 138 ATF_TC_HEAD(fts_options_logical_seedot, tc) 139 { 140 atf_tc_set_md_var(tc, "descr", "FTS_LOGICAL | FTS_SEEDOT"); 141 } 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); 187 ATF_TC_HEAD(fts_options_physical, tc) 188 { 189 atf_tc_set_md_var(tc, "descr", "FTS_PHYSICAL"); 190 } 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); 213 ATF_TC_HEAD(fts_options_physical_nochdir, tc) 214 { 215 atf_tc_set_md_var(tc, "descr", "FTS_PHYSICAL | FTS_NOCHDIR"); 216 } 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); 239 ATF_TC_HEAD(fts_options_physical_comfollow, tc) 240 { 241 atf_tc_set_md_var(tc, "descr", "FTS_PHYSICAL | FTS_COMFOLLOW"); 242 } 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); 268 ATF_TC_HEAD(fts_options_physical_comfollowdir, tc) 269 { 270 atf_tc_set_md_var(tc, "descr", "FTS_PHYSICAL | FTS_COMFOLLOWDIR"); 271 } 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); 297 ATF_TC_HEAD(fts_options_physical_nostat, tc) 298 { 299 atf_tc_set_md_var(tc, "descr", "FTS_PHYSICAL | FTS_NOSTAT"); 300 } 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); 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 } 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); 349 ATF_TC_HEAD(fts_options_physical_seedot, tc) 350 { 351 atf_tc_set_md_var(tc, "descr", "FTS_PHYSICAL | FTS_SEEDOT"); 352 } 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 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