Path: blob/main/lib/libc/tests/gen/fts_open_test.c
289379 views
/*1* Copyright (c) 2026 Jitendra Bhati2*3* SPDX-License-Identifier: BSD-2-Clause4*/56/*7* Tests for fts_open() error conditions and edge cases.8*/910#include <sys/stat.h>1112#include <errno.h>13#include <fcntl.h>14#include <fts.h>15#include <stdbool.h>16#include <stdio.h>17#include <stdlib.h>18#include <unistd.h>1920#include <atf-c.h>2122#include "fts_test.h"2324/*25* Option bits outside FTS_OPTIONMASK must fail with EINVAL.26*/27ATF_TC(invalid_options);28ATF_TC_HEAD(invalid_options, tc)29{30atf_tc_set_md_var(tc, "descr",31"fts_open with out-of-mask option bits fails with EINVAL");32}33ATF_TC_BODY(invalid_options, tc)34{35char *paths[] = { ".", NULL };3637ATF_REQUIRE_ERRNO(EINVAL, fts_open(paths, 0x10000, NULL) == NULL);38}3940/*41* Empty argv (NULL as first element) must fail with EINVAL.42*/43ATF_TC(empty_argv);44ATF_TC_HEAD(empty_argv, tc)45{46atf_tc_set_md_var(tc, "descr",47"fts_open with NULL first argv element fails with EINVAL");48}49ATF_TC_BODY(empty_argv, tc)50{51char *paths[] = { NULL };5253ATF_REQUIRE_ERRNO(EINVAL,54fts_open(paths, FTS_PHYSICAL, NULL) == NULL);55}5657/*58* An empty string in argv is a valid path but stat("") fails with ENOENT.59* fts_open() succeeds; the resulting FTSENT has fts_info == FTS_NS and60* fts_errno == ENOENT.61*/62ATF_TC(empty_path_string);63ATF_TC_HEAD(empty_path_string, tc)64{65atf_tc_set_md_var(tc, "descr",66"empty string in argv produces FTS_NS entry");67}68ATF_TC_BODY(empty_path_string, tc)69{70char *paths[] = { "", NULL };71FTS *fts;72FTSENT *ent;7374ATF_REQUIRE((fts = fts_open(paths, FTS_PHYSICAL, NULL)) != NULL);7576ent = fts_read(fts);77ATF_REQUIRE(ent != NULL);78ATF_CHECK_EQ(FTS_NS, ent->fts_info);79ATF_CHECK_EQ(ENOENT, ent->fts_errno);8081fts_close(fts);82}8384/*85* A nonexistent path produces an FTS_NS entry rather than causing86* fts_open() itself to fail. fts_open() does not validate whether87* paths exist. errno must be 0 after the traversal ends normally.88*/89ATF_TC(nonexistent_path);90ATF_TC_HEAD(nonexistent_path, tc)91{92atf_tc_set_md_var(tc, "descr",93"nonexistent path produces FTS_NS entry, not fts_open failure");94}95ATF_TC_BODY(nonexistent_path, tc)96{97char *paths[] = { "this-path-does-not-exist", NULL };98FTS *fts;99FTSENT *ent;100101ATF_REQUIRE((fts = fts_open(paths, FTS_PHYSICAL, NULL)) != NULL);102103ent = fts_read(fts);104ATF_REQUIRE(ent != NULL);105ATF_CHECK_EQ(FTS_NS, ent->fts_info);106ATF_CHECK_EQ(ENOENT, ent->fts_errno);107108/*109* Next fts_read must return NULL with errno == 0 —110* end-of-traversal, not an error.111*/112errno = 1; /* sentinel — fts_read must clear this */113ATF_CHECK_EQ(NULL, fts_read(fts));114ATF_CHECK_EQ(0, errno);115116ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m");117}118119/*120* A path with a trailing slash must not crash and must traverse the121* directory normally. This is a regression test for SVN r49851.122*/123ATF_TC(trailing_slash);124ATF_TC_HEAD(trailing_slash, tc)125{126atf_tc_set_md_var(tc, "descr",127"trailing slash on root path must not crash (SVN r49851)");128}129ATF_TC_BODY(trailing_slash, tc)130{131char *paths[] = { "dir/", NULL };132FTS *fts;133FTSENT *ent;134int seen_dir, seen_file;135136ATF_REQUIRE_EQ(0, mkdir("dir", 0755));137ATF_REQUIRE_EQ(0, close(creat("dir/file", 0644)));138139ATF_REQUIRE((fts = fts_open(paths, FTS_PHYSICAL, NULL)) != NULL);140141seen_dir = 0;142seen_file = 0;143while ((ent = fts_read(fts)) != NULL) {144if (ent->fts_info == FTS_D || ent->fts_info == FTS_DP)145seen_dir = 1;146if (ent->fts_info == FTS_F)147seen_file = 1;148}149150ATF_CHECK_EQ_MSG(0, errno,151"fts_read loop should end with errno 0, not %d", errno);152ATF_CHECK_MSG(seen_dir != 0, "directory was never visited");153ATF_CHECK_MSG(seen_file != 0, "file inside dir was never visited");154155ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m");156}157158/*159* An unreadable directory must produce FTS_D then FTS_DNR. It must NOT160* produce FTS_DP because fts never successfully entered it.161*162* Requires an unprivileged user because root ignores directory permissions.163*/164ATF_TC(unreadable_dir);165ATF_TC_HEAD(unreadable_dir, tc)166{167atf_tc_set_md_var(tc, "descr",168"unreadable directory yields FTS_D then FTS_DNR, never FTS_DP");169atf_tc_set_md_var(tc, "require.user", "unprivileged");170}171ATF_TC_BODY(unreadable_dir, tc)172{173ATF_REQUIRE_EQ(0, mkdir("unr", 0000));174fts_test(tc, &(struct fts_testcase){175(char *[]){ "unr", NULL },176FTS_PHYSICAL,177(struct fts_expect[]){178{ FTS_D, "unr", "unr" },179{ FTS_DNR, "unr", "unr" },180{ 0 }181},182});183}184185/*186* Multiple root paths must all be visited left-to-right, each tree187* traversed completely before moving to the next root.188*/189ATF_TC(multiple_roots);190ATF_TC_HEAD(multiple_roots, tc)191{192atf_tc_set_md_var(tc, "descr",193"fts_open visits multiple root paths left-to-right");194}195ATF_TC_BODY(multiple_roots, tc)196{197ATF_REQUIRE_EQ(0, mkdir("a", 0755));198ATF_REQUIRE_EQ(0, mkdir("b", 0755));199ATF_REQUIRE_EQ(0, close(creat("a/x", 0644)));200ATF_REQUIRE_EQ(0, close(creat("b/y", 0644)));201202fts_test(tc, &(struct fts_testcase){203(char *[]){ "a", "b", NULL },204FTS_PHYSICAL,205(struct fts_expect[]){206{ FTS_D, "a", "a" },207{ FTS_F, "x", "x" },208{ FTS_DP, "a", "a" },209{ FTS_D, "b", "b" },210{ FTS_F, "y", "y" },211{ FTS_DP, "b", "b" },212{ 0 }213},214});215}216217ATF_TP_ADD_TCS(tp)218{219fts_check_debug();220ATF_TP_ADD_TC(tp, invalid_options);221ATF_TP_ADD_TC(tp, empty_argv);222ATF_TP_ADD_TC(tp, empty_path_string);223ATF_TP_ADD_TC(tp, nonexistent_path);224ATF_TP_ADD_TC(tp, trailing_slash);225ATF_TP_ADD_TC(tp, unreadable_dir);226ATF_TP_ADD_TC(tp, multiple_roots);227228return (atf_no_error());229}230231232