Path: blob/main/lib/libc/tests/gen/fts_set_test.c
289379 views
/*1* Copyright (c) 2026 Jitendra Bhati2*3* SPDX-License-Identifier: BSD-2-Clause4*/56/*7* Tests for fts_set(), fts_set_clientptr(), fts_get_clientptr(),8* and fts_get_stream().9*/1011#include <sys/stat.h>1213#include <errno.h>14#include <fcntl.h>15#include <fts.h>16#include <stdbool.h>17#include <stdio.h>18#include <stdlib.h>19#include <string.h>20#include <unistd.h>2122#include <atf-c.h>2324/*25* fts_set with invalid options must return non-zero with EINVAL.26* Note: fts_set returns 1 (not -1) on error.27*/28ATF_TC(invalid_options);29ATF_TC_HEAD(invalid_options, tc)30{31atf_tc_set_md_var(tc, "descr",32"fts_set with invalid options returns non-zero with EINVAL");33}34ATF_TC_BODY(invalid_options, tc)35{36char *paths[] = { ".", NULL };37FTS *fts;38FTSENT *ent;3940ATF_REQUIRE((fts = fts_open(paths, FTS_PHYSICAL, NULL)) != NULL);4142ent = fts_read(fts);43ATF_REQUIRE(ent != NULL);44ATF_REQUIRE_ERRNO(EINVAL, fts_set(fts, ent, 99) != 0);45ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m");46}4748/*49* FTS_AGAIN causes the current node to be re-stat()ed and returned50* again on the next fts_read() call.51*/52ATF_TC(again);53ATF_TC_HEAD(again, tc)54{55atf_tc_set_md_var(tc, "descr",56"FTS_AGAIN causes the current node to be returned once more");57}58ATF_TC_BODY(again, tc)59{60char *paths[] = { "dir", NULL };61FTS *fts;62FTSENT *ent;63int revisit_count;6465ATF_REQUIRE_EQ(0, mkdir("dir", 0755));66ATF_REQUIRE_EQ(0, close(creat("dir/file", 0644)));6768ATF_REQUIRE((fts = fts_open(paths, FTS_PHYSICAL, NULL)) != NULL);6970revisit_count = 0;71for (errno = 0; (ent = fts_read(fts)) != NULL; errno = 0) {72if (ent->fts_info == FTS_F && revisit_count == 0) {73ATF_REQUIRE_EQ_MSG(0,74fts_set(fts, ent, FTS_AGAIN),75"fts_set(FTS_AGAIN): %m");76revisit_count++;77} else if (ent->fts_info == FTS_F && revisit_count >= 1) {78revisit_count++;79}80}81ATF_CHECK_EQ_MSG(0, errno, "traversal ended with errno %d", errno);82ATF_CHECK_EQ_MSG(2, revisit_count,83"expected file visited twice via FTS_AGAIN, saw %d",84revisit_count);8586ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m");87}8889/*90* FTS_AGAIN set twice in a row causes the node to be visited three91* times total. Each fts_read() clears fts_options, so the caller must92* set FTS_AGAIN again explicitly each time.93*/94ATF_TC(again_consecutive);95ATF_TC_HEAD(again_consecutive, tc)96{97atf_tc_set_md_var(tc, "descr",98"FTS_AGAIN set twice in a row visits the node three times");99}100ATF_TC_BODY(again_consecutive, tc)101{102char *paths[] = { "file", NULL };103FTS *fts;104FTSENT *ent;105int visit_count;106107ATF_REQUIRE_EQ(0, close(creat("file", 0644)));108109ATF_REQUIRE((fts = fts_open(paths, FTS_PHYSICAL, NULL)) != NULL);110111visit_count = 0;112while ((ent = fts_read(fts)) != NULL) {113if (ent->fts_info == FTS_F) {114visit_count++;115if (visit_count < 3)116ATF_REQUIRE_EQ(0,117fts_set(fts, ent, FTS_AGAIN));118}119}120ATF_CHECK_EQ_MSG(3, visit_count,121"expected 3 visits with consecutive FTS_AGAIN, got %d",122visit_count);123124ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m");125}126127/*128* FTS_FOLLOW on an FTS_SL entry pointing to a regular file yields FTS_F.129*/130ATF_TC(follow_symlink_to_file);131ATF_TC_HEAD(follow_symlink_to_file, tc)132{133atf_tc_set_md_var(tc, "descr",134"FTS_FOLLOW on FTS_SL to regular file yields FTS_F");135}136ATF_TC_BODY(follow_symlink_to_file, tc)137{138char *paths[] = { "dir", NULL };139FTS *fts;140FTSENT *ent;141bool followed;142143ATF_REQUIRE_EQ(0, mkdir("dir", 0755));144ATF_REQUIRE_EQ(0, close(creat("dir/target", 0644)));145ATF_REQUIRE_EQ(0, symlink("target", "dir/link"));146147ATF_REQUIRE((fts = fts_open(paths, FTS_PHYSICAL, NULL)) != NULL);148149followed = false;150while ((ent = fts_read(fts)) != NULL) {151if (ent->fts_info == FTS_SL &&152strcmp(ent->fts_name, "link") == 0)153ATF_REQUIRE_EQ(0, fts_set(fts, ent, FTS_FOLLOW));154else if (ent->fts_info == FTS_F &&155strcmp(ent->fts_name, "link") == 0)156followed = true;157}158ATF_CHECK_MSG(followed,159"FTS_FOLLOW on symlink-to-file must yield FTS_F");160161ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m");162}163164/*165* FTS_FOLLOW on an FTS_SL entry pointing to a directory causes descent166* into the target directory.167*/168ATF_TC(follow_symlink_to_dir);169ATF_TC_HEAD(follow_symlink_to_dir, tc)170{171atf_tc_set_md_var(tc, "descr",172"FTS_FOLLOW on FTS_SL to directory causes descent");173}174ATF_TC_BODY(follow_symlink_to_dir, tc)175{176char *paths[] = { "dir", NULL };177FTS *fts;178FTSENT *ent;179bool saw_inside;180181ATF_REQUIRE_EQ(0, mkdir("dir", 0755));182ATF_REQUIRE_EQ(0, mkdir("dir/real", 0755));183ATF_REQUIRE_EQ(0, close(creat("dir/real/inside", 0644)));184ATF_REQUIRE_EQ(0, symlink("real", "dir/link"));185186ATF_REQUIRE((fts = fts_open(paths, FTS_PHYSICAL, NULL)) != NULL);187188saw_inside = false;189while ((ent = fts_read(fts)) != NULL) {190if (ent->fts_info == FTS_SL &&191strcmp(ent->fts_name, "link") == 0)192ATF_REQUIRE_EQ(0, fts_set(fts, ent, FTS_FOLLOW));193if (ent->fts_info == FTS_F &&194strcmp(ent->fts_name, "inside") == 0 &&195strcmp(ent->fts_path, "dir/link/inside") == 0)196saw_inside = true;197}198ATF_CHECK_MSG(saw_inside,199"FTS_FOLLOW on symlink-to-dir should descend and visit 'inside'");200201ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m");202}203204/*205* FTS_FOLLOW on a dangling symlink (FTS_SLNONE) yields FTS_SLNONE again.206* FTS_SLNONE requires FTS_LOGICAL — under FTS_PHYSICAL a dangling207* symlink is reported as FTS_SL.208*/209ATF_TC(follow_dead_symlink);210ATF_TC_HEAD(follow_dead_symlink, tc)211{212atf_tc_set_md_var(tc, "descr",213"FTS_FOLLOW on dead symlink yields FTS_SLNONE");214}215ATF_TC_BODY(follow_dead_symlink, tc)216{217char *paths[] = { "dead", NULL };218FTS *fts;219FTSENT *ent;220221ATF_REQUIRE_EQ(0, symlink("no-such-target", "dead"));222223ATF_REQUIRE((fts = fts_open(paths, FTS_LOGICAL, NULL)) != NULL);224225ent = fts_read(fts);226ATF_REQUIRE(ent != NULL);227ATF_REQUIRE_EQ_MSG(FTS_SLNONE, ent->fts_info,228"expected FTS_SLNONE for dead symlink, got %d", ent->fts_info);229230ATF_REQUIRE_EQ(0, fts_set(fts, ent, FTS_FOLLOW));231ent = fts_read(fts);232ATF_REQUIRE(ent != NULL);233ATF_CHECK_EQ_MSG(FTS_SLNONE, ent->fts_info,234"FTS_FOLLOW on dead symlink should still be FTS_SLNONE, got %d",235ent->fts_info);236237ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m");238}239240/*241* FTS_SKIP on an FTS_D node prevents descent into that directory.242* The next fts_read() converts the node to FTS_DP without visiting243* any children.244*/245ATF_TC(skip);246ATF_TC_HEAD(skip, tc)247{248atf_tc_set_md_var(tc, "descr",249"FTS_SKIP prevents descent into a directory");250}251ATF_TC_BODY(skip, tc)252{253char *paths[] = { "dir", NULL };254FTS *fts;255FTSENT *ent;256bool saw_inside;257258ATF_REQUIRE_EQ(0, mkdir("dir", 0755));259ATF_REQUIRE_EQ(0, mkdir("dir/skip_me", 0755));260ATF_REQUIRE_EQ(0, close(creat("dir/skip_me/inside", 0644)));261ATF_REQUIRE_EQ(0, close(creat("dir/sibling", 0644)));262263ATF_REQUIRE((fts = fts_open(paths, FTS_PHYSICAL, NULL)) != NULL);264265saw_inside = false;266while ((ent = fts_read(fts)) != NULL) {267if (ent->fts_info == FTS_D &&268strcmp(ent->fts_name, "skip_me") == 0)269ATF_REQUIRE_EQ(0, fts_set(fts, ent, FTS_SKIP));270if (strcmp(ent->fts_name, "inside") == 0)271saw_inside = true;272}273ATF_CHECK_MSG(!saw_inside,274"FTS_SKIP: 'inside' must not have been visited");275276ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m");277}278279/*280* fts_set_clientptr() and fts_get_clientptr() store and retrieve an281* arbitrary pointer on the FTS stream.282*/283ATF_TC(clientptr_roundtrip);284ATF_TC_HEAD(clientptr_roundtrip, tc)285{286atf_tc_set_md_var(tc, "descr",287"fts_set_clientptr / fts_get_clientptr round-trip");288}289ATF_TC_BODY(clientptr_roundtrip, tc)290{291char *paths[] = { "dir", NULL };292FTS *fts;293FTSENT *ent;294int value = 42;295296ATF_REQUIRE_EQ(0, mkdir("dir", 0755));297ATF_REQUIRE_EQ(0, close(creat("dir/file", 0644)));298299ATF_REQUIRE((fts = fts_open(paths, FTS_PHYSICAL, NULL)) != NULL);300301/* Initially NULL. */302ATF_CHECK_EQ(NULL, fts_get_clientptr(fts));303304fts_set_clientptr(fts, &value);305306while ((ent = fts_read(fts)) != NULL) {307/*308* Verify the pointer is accessible and correct309* while traversal is active.310*/311ATF_CHECK_EQ_MSG(&value, fts_get_clientptr(fts),312"fts_get_clientptr did not return the stored pointer "313"for entry '%s'", ent->fts_name);314}315316/* Overwrite with NULL, verify. */317fts_set_clientptr(fts, NULL);318ATF_CHECK_EQ(NULL, fts_get_clientptr(fts));319320ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m");321}322323/*324* fts_get_stream() returns the parent FTS* from any FTSENT* returned325* by fts_read().326*/327ATF_TC(get_stream_backpointer);328ATF_TC_HEAD(get_stream_backpointer, tc)329{330atf_tc_set_md_var(tc, "descr",331"fts_get_stream returns the parent FTS* from an FTSENT*");332}333ATF_TC_BODY(get_stream_backpointer, tc)334{335char *paths[] = { "dir", NULL };336FTS *fts;337FTSENT *ent;338339ATF_REQUIRE_EQ(0, mkdir("dir", 0755));340ATF_REQUIRE_EQ(0, close(creat("dir/file", 0644)));341342ATF_REQUIRE((fts = fts_open(paths, FTS_PHYSICAL, NULL)) != NULL);343344while ((ent = fts_read(fts)) != NULL) {345ATF_CHECK_EQ_MSG(fts, fts_get_stream(ent),346"fts_get_stream(ent) must return the parent FTS*, "347"entry: %s info: %d",348ent->fts_name, ent->fts_info);349}350351ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m");352}353354ATF_TP_ADD_TCS(tp)355{356ATF_TP_ADD_TC(tp, invalid_options);357ATF_TP_ADD_TC(tp, again);358ATF_TP_ADD_TC(tp, again_consecutive);359ATF_TP_ADD_TC(tp, follow_symlink_to_file);360ATF_TP_ADD_TC(tp, follow_symlink_to_dir);361ATF_TP_ADD_TC(tp, follow_dead_symlink);362ATF_TP_ADD_TC(tp, skip);363ATF_TP_ADD_TC(tp, clientptr_roundtrip);364ATF_TP_ADD_TC(tp, get_stream_backpointer);365366return (atf_no_error());367}368369370