xref: /freebsd/lib/libc/tests/gen/fts_open_test.c (revision 113c262b2ad157790e19188bf298b7205bd1887b)
1*113c262bSJitendra Bhati /*
2*113c262bSJitendra Bhati  * Copyright (c) 2026 Jitendra Bhati
3*113c262bSJitendra Bhati  *
4*113c262bSJitendra Bhati  * SPDX-License-Identifier: BSD-2-Clause
5*113c262bSJitendra Bhati  */
6*113c262bSJitendra Bhati 
7*113c262bSJitendra Bhati /*
8*113c262bSJitendra Bhati  * Tests for fts_open() error conditions and edge cases.
9*113c262bSJitendra Bhati  */
10*113c262bSJitendra Bhati 
11*113c262bSJitendra Bhati #include <sys/stat.h>
12*113c262bSJitendra Bhati 
13*113c262bSJitendra Bhati #include <errno.h>
14*113c262bSJitendra Bhati #include <fcntl.h>
15*113c262bSJitendra Bhati #include <fts.h>
16*113c262bSJitendra Bhati #include <stdbool.h>
17*113c262bSJitendra Bhati #include <stdio.h>
18*113c262bSJitendra Bhati #include <stdlib.h>
19*113c262bSJitendra Bhati #include <unistd.h>
20*113c262bSJitendra Bhati 
21*113c262bSJitendra Bhati #include <atf-c.h>
22*113c262bSJitendra Bhati 
23*113c262bSJitendra Bhati #include "fts_test.h"
24*113c262bSJitendra Bhati 
25*113c262bSJitendra Bhati /*
26*113c262bSJitendra Bhati  * Option bits outside FTS_OPTIONMASK must fail with EINVAL.
27*113c262bSJitendra Bhati  */
28*113c262bSJitendra Bhati ATF_TC(invalid_options);
ATF_TC_HEAD(invalid_options,tc)29*113c262bSJitendra Bhati ATF_TC_HEAD(invalid_options, tc)
30*113c262bSJitendra Bhati {
31*113c262bSJitendra Bhati 	atf_tc_set_md_var(tc, "descr",
32*113c262bSJitendra Bhati 	    "fts_open with out-of-mask option bits fails with EINVAL");
33*113c262bSJitendra Bhati }
ATF_TC_BODY(invalid_options,tc)34*113c262bSJitendra Bhati ATF_TC_BODY(invalid_options, tc)
35*113c262bSJitendra Bhati {
36*113c262bSJitendra Bhati 	char *paths[] = { ".", NULL };
37*113c262bSJitendra Bhati 
38*113c262bSJitendra Bhati 	ATF_REQUIRE_ERRNO(EINVAL, fts_open(paths, 0x10000, NULL) == NULL);
39*113c262bSJitendra Bhati }
40*113c262bSJitendra Bhati 
41*113c262bSJitendra Bhati /*
42*113c262bSJitendra Bhati  * Empty argv (NULL as first element) must fail with EINVAL.
43*113c262bSJitendra Bhati  */
44*113c262bSJitendra Bhati ATF_TC(empty_argv);
ATF_TC_HEAD(empty_argv,tc)45*113c262bSJitendra Bhati ATF_TC_HEAD(empty_argv, tc)
46*113c262bSJitendra Bhati {
47*113c262bSJitendra Bhati 	atf_tc_set_md_var(tc, "descr",
48*113c262bSJitendra Bhati 	    "fts_open with NULL first argv element fails with EINVAL");
49*113c262bSJitendra Bhati }
ATF_TC_BODY(empty_argv,tc)50*113c262bSJitendra Bhati ATF_TC_BODY(empty_argv, tc)
51*113c262bSJitendra Bhati {
52*113c262bSJitendra Bhati 	char *paths[] = { NULL };
53*113c262bSJitendra Bhati 
54*113c262bSJitendra Bhati 	ATF_REQUIRE_ERRNO(EINVAL,
55*113c262bSJitendra Bhati 	    fts_open(paths, FTS_PHYSICAL, NULL) == NULL);
56*113c262bSJitendra Bhati }
57*113c262bSJitendra Bhati 
58*113c262bSJitendra Bhati /*
59*113c262bSJitendra Bhati  * An empty string in argv is a valid path but stat("") fails with ENOENT.
60*113c262bSJitendra Bhati  * fts_open() succeeds; the resulting FTSENT has fts_info == FTS_NS and
61*113c262bSJitendra Bhati  * fts_errno == ENOENT.
62*113c262bSJitendra Bhati  */
63*113c262bSJitendra Bhati ATF_TC(empty_path_string);
ATF_TC_HEAD(empty_path_string,tc)64*113c262bSJitendra Bhati ATF_TC_HEAD(empty_path_string, tc)
65*113c262bSJitendra Bhati {
66*113c262bSJitendra Bhati 	atf_tc_set_md_var(tc, "descr",
67*113c262bSJitendra Bhati 	    "empty string in argv produces FTS_NS entry");
68*113c262bSJitendra Bhati }
ATF_TC_BODY(empty_path_string,tc)69*113c262bSJitendra Bhati ATF_TC_BODY(empty_path_string, tc)
70*113c262bSJitendra Bhati {
71*113c262bSJitendra Bhati 	char *paths[] = { "", NULL };
72*113c262bSJitendra Bhati 	FTS *fts;
73*113c262bSJitendra Bhati 	FTSENT *ent;
74*113c262bSJitendra Bhati 
75*113c262bSJitendra Bhati 	ATF_REQUIRE((fts = fts_open(paths, FTS_PHYSICAL, NULL)) != NULL);
76*113c262bSJitendra Bhati 
77*113c262bSJitendra Bhati 	ent = fts_read(fts);
78*113c262bSJitendra Bhati 	ATF_REQUIRE(ent != NULL);
79*113c262bSJitendra Bhati 	ATF_CHECK_EQ(FTS_NS, ent->fts_info);
80*113c262bSJitendra Bhati 	ATF_CHECK_EQ(ENOENT, ent->fts_errno);
81*113c262bSJitendra Bhati 
82*113c262bSJitendra Bhati 	fts_close(fts);
83*113c262bSJitendra Bhati }
84*113c262bSJitendra Bhati 
85*113c262bSJitendra Bhati /*
86*113c262bSJitendra Bhati  * A nonexistent path produces an FTS_NS entry rather than causing
87*113c262bSJitendra Bhati  * fts_open() itself to fail.  fts_open() does not validate whether
88*113c262bSJitendra Bhati  * paths exist.  errno must be 0 after the traversal ends normally.
89*113c262bSJitendra Bhati  */
90*113c262bSJitendra Bhati ATF_TC(nonexistent_path);
ATF_TC_HEAD(nonexistent_path,tc)91*113c262bSJitendra Bhati ATF_TC_HEAD(nonexistent_path, tc)
92*113c262bSJitendra Bhati {
93*113c262bSJitendra Bhati 	atf_tc_set_md_var(tc, "descr",
94*113c262bSJitendra Bhati 	    "nonexistent path produces FTS_NS entry, not fts_open failure");
95*113c262bSJitendra Bhati }
ATF_TC_BODY(nonexistent_path,tc)96*113c262bSJitendra Bhati ATF_TC_BODY(nonexistent_path, tc)
97*113c262bSJitendra Bhati {
98*113c262bSJitendra Bhati 	char *paths[] = { "this-path-does-not-exist", NULL };
99*113c262bSJitendra Bhati 	FTS *fts;
100*113c262bSJitendra Bhati 	FTSENT *ent;
101*113c262bSJitendra Bhati 
102*113c262bSJitendra Bhati 	ATF_REQUIRE((fts = fts_open(paths, FTS_PHYSICAL, NULL)) != NULL);
103*113c262bSJitendra Bhati 
104*113c262bSJitendra Bhati 	ent = fts_read(fts);
105*113c262bSJitendra Bhati 	ATF_REQUIRE(ent != NULL);
106*113c262bSJitendra Bhati 	ATF_CHECK_EQ(FTS_NS, ent->fts_info);
107*113c262bSJitendra Bhati 	ATF_CHECK_EQ(ENOENT, ent->fts_errno);
108*113c262bSJitendra Bhati 
109*113c262bSJitendra Bhati 	/*
110*113c262bSJitendra Bhati 	 * Next fts_read must return NULL with errno == 0 —
111*113c262bSJitendra Bhati 	 * end-of-traversal, not an error.
112*113c262bSJitendra Bhati 	 */
113*113c262bSJitendra Bhati 	errno = 1;	/* sentinel — fts_read must clear this */
114*113c262bSJitendra Bhati 	ATF_CHECK_EQ(NULL, fts_read(fts));
115*113c262bSJitendra Bhati 	ATF_CHECK_EQ(0, errno);
116*113c262bSJitendra Bhati 
117*113c262bSJitendra Bhati 	ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m");
118*113c262bSJitendra Bhati }
119*113c262bSJitendra Bhati 
120*113c262bSJitendra Bhati /*
121*113c262bSJitendra Bhati  * A path with a trailing slash must not crash and must traverse the
122*113c262bSJitendra Bhati  * directory normally.  This is a regression test for SVN r49851.
123*113c262bSJitendra Bhati  */
124*113c262bSJitendra Bhati ATF_TC(trailing_slash);
ATF_TC_HEAD(trailing_slash,tc)125*113c262bSJitendra Bhati ATF_TC_HEAD(trailing_slash, tc)
126*113c262bSJitendra Bhati {
127*113c262bSJitendra Bhati 	atf_tc_set_md_var(tc, "descr",
128*113c262bSJitendra Bhati 	    "trailing slash on root path must not crash (SVN r49851)");
129*113c262bSJitendra Bhati }
ATF_TC_BODY(trailing_slash,tc)130*113c262bSJitendra Bhati ATF_TC_BODY(trailing_slash, tc)
131*113c262bSJitendra Bhati {
132*113c262bSJitendra Bhati 	char *paths[] = { "dir/", NULL };
133*113c262bSJitendra Bhati 	FTS *fts;
134*113c262bSJitendra Bhati 	FTSENT *ent;
135*113c262bSJitendra Bhati 	int seen_dir, seen_file;
136*113c262bSJitendra Bhati 
137*113c262bSJitendra Bhati 	ATF_REQUIRE_EQ(0, mkdir("dir", 0755));
138*113c262bSJitendra Bhati 	ATF_REQUIRE_EQ(0, close(creat("dir/file", 0644)));
139*113c262bSJitendra Bhati 
140*113c262bSJitendra Bhati 	ATF_REQUIRE((fts = fts_open(paths, FTS_PHYSICAL, NULL)) != NULL);
141*113c262bSJitendra Bhati 
142*113c262bSJitendra Bhati 	seen_dir = 0;
143*113c262bSJitendra Bhati 	seen_file = 0;
144*113c262bSJitendra Bhati 	while ((ent = fts_read(fts)) != NULL) {
145*113c262bSJitendra Bhati 		if (ent->fts_info == FTS_D || ent->fts_info == FTS_DP)
146*113c262bSJitendra Bhati 			seen_dir = 1;
147*113c262bSJitendra Bhati 		if (ent->fts_info == FTS_F)
148*113c262bSJitendra Bhati 			seen_file = 1;
149*113c262bSJitendra Bhati 	}
150*113c262bSJitendra Bhati 
151*113c262bSJitendra Bhati 	ATF_CHECK_EQ_MSG(0, errno,
152*113c262bSJitendra Bhati 	    "fts_read loop should end with errno 0, not %d", errno);
153*113c262bSJitendra Bhati 	ATF_CHECK_MSG(seen_dir != 0, "directory was never visited");
154*113c262bSJitendra Bhati 	ATF_CHECK_MSG(seen_file != 0, "file inside dir was never visited");
155*113c262bSJitendra Bhati 
156*113c262bSJitendra Bhati 	ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m");
157*113c262bSJitendra Bhati }
158*113c262bSJitendra Bhati 
159*113c262bSJitendra Bhati /*
160*113c262bSJitendra Bhati  * An unreadable directory must produce FTS_D then FTS_DNR.  It must NOT
161*113c262bSJitendra Bhati  * produce FTS_DP because fts never successfully entered it.
162*113c262bSJitendra Bhati  *
163*113c262bSJitendra Bhati  * Requires an unprivileged user because root ignores directory permissions.
164*113c262bSJitendra Bhati  */
165*113c262bSJitendra Bhati ATF_TC(unreadable_dir);
ATF_TC_HEAD(unreadable_dir,tc)166*113c262bSJitendra Bhati ATF_TC_HEAD(unreadable_dir, tc)
167*113c262bSJitendra Bhati {
168*113c262bSJitendra Bhati 	atf_tc_set_md_var(tc, "descr",
169*113c262bSJitendra Bhati 	    "unreadable directory yields FTS_D then FTS_DNR, never FTS_DP");
170*113c262bSJitendra Bhati 	atf_tc_set_md_var(tc, "require.user", "unprivileged");
171*113c262bSJitendra Bhati }
ATF_TC_BODY(unreadable_dir,tc)172*113c262bSJitendra Bhati ATF_TC_BODY(unreadable_dir, tc)
173*113c262bSJitendra Bhati {
174*113c262bSJitendra Bhati 	ATF_REQUIRE_EQ(0, mkdir("unr", 0000));
175*113c262bSJitendra Bhati 	fts_test(tc, &(struct fts_testcase){
176*113c262bSJitendra Bhati 		    (char *[]){ "unr", NULL },
177*113c262bSJitendra Bhati 		    FTS_PHYSICAL,
178*113c262bSJitendra Bhati 		    (struct fts_expect[]){
179*113c262bSJitendra Bhati 			    { FTS_D,   "unr", "unr" },
180*113c262bSJitendra Bhati 			    { FTS_DNR, "unr", "unr" },
181*113c262bSJitendra Bhati 			    { 0 }
182*113c262bSJitendra Bhati 		    },
183*113c262bSJitendra Bhati 	    });
184*113c262bSJitendra Bhati }
185*113c262bSJitendra Bhati 
186*113c262bSJitendra Bhati /*
187*113c262bSJitendra Bhati  * Multiple root paths must all be visited left-to-right, each tree
188*113c262bSJitendra Bhati  * traversed completely before moving to the next root.
189*113c262bSJitendra Bhati  */
190*113c262bSJitendra Bhati ATF_TC(multiple_roots);
ATF_TC_HEAD(multiple_roots,tc)191*113c262bSJitendra Bhati ATF_TC_HEAD(multiple_roots, tc)
192*113c262bSJitendra Bhati {
193*113c262bSJitendra Bhati 	atf_tc_set_md_var(tc, "descr",
194*113c262bSJitendra Bhati 	    "fts_open visits multiple root paths left-to-right");
195*113c262bSJitendra Bhati }
ATF_TC_BODY(multiple_roots,tc)196*113c262bSJitendra Bhati ATF_TC_BODY(multiple_roots, tc)
197*113c262bSJitendra Bhati {
198*113c262bSJitendra Bhati 	ATF_REQUIRE_EQ(0, mkdir("a", 0755));
199*113c262bSJitendra Bhati 	ATF_REQUIRE_EQ(0, mkdir("b", 0755));
200*113c262bSJitendra Bhati 	ATF_REQUIRE_EQ(0, close(creat("a/x", 0644)));
201*113c262bSJitendra Bhati 	ATF_REQUIRE_EQ(0, close(creat("b/y", 0644)));
202*113c262bSJitendra Bhati 
203*113c262bSJitendra Bhati 	fts_test(tc, &(struct fts_testcase){
204*113c262bSJitendra Bhati 		    (char *[]){ "a", "b", NULL },
205*113c262bSJitendra Bhati 		    FTS_PHYSICAL,
206*113c262bSJitendra Bhati 		    (struct fts_expect[]){
207*113c262bSJitendra Bhati 			    { FTS_D,  "a", "a" },
208*113c262bSJitendra Bhati 			    { FTS_F,  "x", "x" },
209*113c262bSJitendra Bhati 			    { FTS_DP, "a", "a" },
210*113c262bSJitendra Bhati 			    { FTS_D,  "b", "b" },
211*113c262bSJitendra Bhati 			    { FTS_F,  "y", "y" },
212*113c262bSJitendra Bhati 			    { FTS_DP, "b", "b" },
213*113c262bSJitendra Bhati 			    { 0 }
214*113c262bSJitendra Bhati 		    },
215*113c262bSJitendra Bhati 	    });
216*113c262bSJitendra Bhati }
217*113c262bSJitendra Bhati 
ATF_TP_ADD_TCS(tp)218*113c262bSJitendra Bhati ATF_TP_ADD_TCS(tp)
219*113c262bSJitendra Bhati {
220*113c262bSJitendra Bhati 	fts_check_debug();
221*113c262bSJitendra Bhati 	ATF_TP_ADD_TC(tp, invalid_options);
222*113c262bSJitendra Bhati 	ATF_TP_ADD_TC(tp, empty_argv);
223*113c262bSJitendra Bhati 	ATF_TP_ADD_TC(tp, empty_path_string);
224*113c262bSJitendra Bhati 	ATF_TP_ADD_TC(tp, nonexistent_path);
225*113c262bSJitendra Bhati 	ATF_TP_ADD_TC(tp, trailing_slash);
226*113c262bSJitendra Bhati 	ATF_TP_ADD_TC(tp, unreadable_dir);
227*113c262bSJitendra Bhati 	ATF_TP_ADD_TC(tp, multiple_roots);
228*113c262bSJitendra Bhati 
229*113c262bSJitendra Bhati 	return (atf_no_error());
230*113c262bSJitendra Bhati }
231