xref: /freebsd/lib/libc/tests/gen/fts_openat_test.c (revision 9aed7a7745512ca098cc2b247cef61ad57d63204)
1*9aed7a77SJitendra Bhati /*
2*9aed7a77SJitendra Bhati  * Copyright (c) 2026 Jitendra Bhati
3*9aed7a77SJitendra Bhati  *
4*9aed7a77SJitendra Bhati  * SPDX-License-Identifier: BSD-2-Clause
5*9aed7a77SJitendra Bhati  */
6*9aed7a77SJitendra Bhati 
7*9aed7a77SJitendra Bhati /*
8*9aed7a77SJitendra Bhati  * Basic tests for fts_openat().  When called with AT_FDCWD the
9*9aed7a77SJitendra Bhati  * behaviour must be identical to fts_open().
10*9aed7a77SJitendra Bhati  */
11*9aed7a77SJitendra Bhati 
12*9aed7a77SJitendra Bhati #include <sys/stat.h>
13*9aed7a77SJitendra Bhati 
14*9aed7a77SJitendra Bhati #include <limits.h>
15*9aed7a77SJitendra Bhati #include <stdio.h>
16*9aed7a77SJitendra Bhati #include <stdlib.h>
17*9aed7a77SJitendra Bhati #include <stdbool.h>
18*9aed7a77SJitendra Bhati #include <fcntl.h>
19*9aed7a77SJitendra Bhati #include <errno.h>
20*9aed7a77SJitendra Bhati #include <fts.h>
21*9aed7a77SJitendra Bhati #include <string.h>
22*9aed7a77SJitendra Bhati #include <unistd.h>
23*9aed7a77SJitendra Bhati #include <sys/capsicum.h>
24*9aed7a77SJitendra Bhati 
25*9aed7a77SJitendra Bhati #include <atf-c.h>
26*9aed7a77SJitendra Bhati 
27*9aed7a77SJitendra Bhati #define	FTS_TEST_MAXENTRIES 64
28*9aed7a77SJitendra Bhati 
29*9aed7a77SJitendra Bhati static int
fts_lexical_compar(const FTSENT * const * a,const FTSENT * const * b)30*9aed7a77SJitendra Bhati fts_lexical_compar(const FTSENT * const *a, const FTSENT * const *b)
31*9aed7a77SJitendra Bhati {
32*9aed7a77SJitendra Bhati 	return (strcmp((*a)->fts_name, (*b)->fts_name));
33*9aed7a77SJitendra Bhati }
34*9aed7a77SJitendra Bhati 
35*9aed7a77SJitendra Bhati /*
36*9aed7a77SJitendra Bhati  * fts_openat(AT_FDCWD, ...) must behave identically to fts_open().
37*9aed7a77SJitendra Bhati  */
38*9aed7a77SJitendra Bhati ATF_TC(atfdcwd_matches_fts_open);
ATF_TC_HEAD(atfdcwd_matches_fts_open,tc)39*9aed7a77SJitendra Bhati ATF_TC_HEAD(atfdcwd_matches_fts_open, tc)
40*9aed7a77SJitendra Bhati {
41*9aed7a77SJitendra Bhati 	atf_tc_set_md_var(tc, "descr",
42*9aed7a77SJitendra Bhati 	    "fts_openat(AT_FDCWD) behaves identically to fts_open");
43*9aed7a77SJitendra Bhati }
44*9aed7a77SJitendra Bhati 
ATF_TC_BODY(atfdcwd_matches_fts_open,tc)45*9aed7a77SJitendra Bhati ATF_TC_BODY(atfdcwd_matches_fts_open, tc)
46*9aed7a77SJitendra Bhati {
47*9aed7a77SJitendra Bhati 	char *cwd, *abspath;
48*9aed7a77SJitendra Bhati 	char *paths[2];
49*9aed7a77SJitendra Bhati 	FTS *fts;
50*9aed7a77SJitendra Bhati 	FTSENT *ent;
51*9aed7a77SJitendra Bhati 
52*9aed7a77SJitendra Bhati 	int *info1, *info2;
53*9aed7a77SJitendra Bhati 	char (*names1)[NAME_MAX + 1], (*names2)[NAME_MAX + 1];
54*9aed7a77SJitendra Bhati 	int n1, n2, i;
55*9aed7a77SJitendra Bhati 
56*9aed7a77SJitendra Bhati 	ATF_REQUIRE((info1 = malloc(FTS_TEST_MAXENTRIES *
57*9aed7a77SJitendra Bhati             sizeof(*info1))) != NULL);
58*9aed7a77SJitendra Bhati         ATF_REQUIRE((info2 = malloc(FTS_TEST_MAXENTRIES *
59*9aed7a77SJitendra Bhati             sizeof(*info2))) != NULL);
60*9aed7a77SJitendra Bhati         ATF_REQUIRE((names1 = malloc(FTS_TEST_MAXENTRIES *
61*9aed7a77SJitendra Bhati             sizeof(*names1))) != NULL);
62*9aed7a77SJitendra Bhati         ATF_REQUIRE((names2 = malloc(FTS_TEST_MAXENTRIES *
63*9aed7a77SJitendra Bhati             sizeof(*names2))) != NULL);
64*9aed7a77SJitendra Bhati 
65*9aed7a77SJitendra Bhati 	cwd = malloc(PATH_MAX);
66*9aed7a77SJitendra Bhati 	ATF_REQUIRE(cwd != NULL);
67*9aed7a77SJitendra Bhati 	abspath = malloc(PATH_MAX * 2);
68*9aed7a77SJitendra Bhati 	ATF_REQUIRE(abspath != NULL);
69*9aed7a77SJitendra Bhati 
70*9aed7a77SJitendra Bhati 	ATF_REQUIRE(getcwd(cwd, PATH_MAX) != NULL);
71*9aed7a77SJitendra Bhati 	ATF_REQUIRE_EQ(0, mkdir("dir", 0755));
72*9aed7a77SJitendra Bhati 	ATF_REQUIRE_EQ(0, mkdir("dir/sub", 0755));
73*9aed7a77SJitendra Bhati 	ATF_REQUIRE_EQ(0, close(creat("dir/sub/file", 0644)));
74*9aed7a77SJitendra Bhati 	ATF_REQUIRE_EQ(0, close(creat("dir/other", 0644)));
75*9aed7a77SJitendra Bhati 
76*9aed7a77SJitendra Bhati 	snprintf(abspath, PATH_MAX * 2, "%s/dir", cwd);
77*9aed7a77SJitendra Bhati 	paths[0] = abspath;
78*9aed7a77SJitendra Bhati 	paths[1] = NULL;
79*9aed7a77SJitendra Bhati 
80*9aed7a77SJitendra Bhati 	/* Collect fts_open results. */
81*9aed7a77SJitendra Bhati 	ATF_REQUIRE((fts = fts_open(paths, FTS_PHYSICAL,
82*9aed7a77SJitendra Bhati 	    fts_lexical_compar)) != NULL);
83*9aed7a77SJitendra Bhati 	for (n1 = 0;
84*9aed7a77SJitendra Bhati 	    (ent = fts_read(fts)) != NULL && n1 < FTS_TEST_MAXENTRIES;
85*9aed7a77SJitendra Bhati 	    n1++) {
86*9aed7a77SJitendra Bhati 		info1[n1] = ent->fts_info;
87*9aed7a77SJitendra Bhati 		strlcpy(names1[n1], ent->fts_name, NAME_MAX + 1);
88*9aed7a77SJitendra Bhati 	}
89*9aed7a77SJitendra Bhati 	ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close: %m");
90*9aed7a77SJitendra Bhati 
91*9aed7a77SJitendra Bhati 	/* Collect fts_openat results. */
92*9aed7a77SJitendra Bhati 	ATF_REQUIRE((fts = fts_openat(AT_FDCWD, paths, FTS_PHYSICAL,
93*9aed7a77SJitendra Bhati 	    fts_lexical_compar)) != NULL);
94*9aed7a77SJitendra Bhati 	for (n2 = 0;
95*9aed7a77SJitendra Bhati 	    (ent = fts_read(fts)) != NULL && n2 < FTS_TEST_MAXENTRIES;
96*9aed7a77SJitendra Bhati 	    n2++) {
97*9aed7a77SJitendra Bhati 		info2[n2] = ent->fts_info;
98*9aed7a77SJitendra Bhati 		strlcpy(names2[n2], ent->fts_name, NAME_MAX + 1);
99*9aed7a77SJitendra Bhati 	}
100*9aed7a77SJitendra Bhati 	ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close: %m");
101*9aed7a77SJitendra Bhati 
102*9aed7a77SJitendra Bhati 	/* Compare. */
103*9aed7a77SJitendra Bhati 	ATF_CHECK_EQ_MSG(n1, n2,
104*9aed7a77SJitendra Bhati 	    "entry count mismatch: fts_open=%d fts_openat=%d", n1, n2);
105*9aed7a77SJitendra Bhati 	for (i = 0; i < n1 && i < n2; i++) {
106*9aed7a77SJitendra Bhati 		ATF_CHECK_EQ_MSG(info1[i], info2[i],
107*9aed7a77SJitendra Bhati 		    "fts_info mismatch at entry %d: "
108*9aed7a77SJitendra Bhati 		    "fts_open=%d fts_openat=%d name=%s",
109*9aed7a77SJitendra Bhati 		    i, info1[i], info2[i], names1[i]);
110*9aed7a77SJitendra Bhati 		ATF_CHECK_STREQ_MSG(names1[i], names2[i],
111*9aed7a77SJitendra Bhati 		    "fts_name mismatch at entry %d: "
112*9aed7a77SJitendra Bhati 		    "fts_open='%s' fts_openat='%s'",
113*9aed7a77SJitendra Bhati 		    i, names1[i], names2[i]);
114*9aed7a77SJitendra Bhati 	}
115*9aed7a77SJitendra Bhati 
116*9aed7a77SJitendra Bhati 	free(cwd);
117*9aed7a77SJitendra Bhati 	free(abspath);
118*9aed7a77SJitendra Bhati 	free(info1);
119*9aed7a77SJitendra Bhati 	free(info2);
120*9aed7a77SJitendra Bhati 	free(names1);
121*9aed7a77SJitendra Bhati 	free(names2);
122*9aed7a77SJitendra Bhati }
123*9aed7a77SJitendra Bhati 
124*9aed7a77SJitendra Bhati /*
125*9aed7a77SJitendra Bhati  * fts_openat() with a real dirfd must work in Capsicum capability mode.
126*9aed7a77SJitendra Bhati  */
127*9aed7a77SJitendra Bhati ATF_TC(openat_capsicum);
ATF_TC_HEAD(openat_capsicum,tc)128*9aed7a77SJitendra Bhati ATF_TC_HEAD(openat_capsicum, tc)
129*9aed7a77SJitendra Bhati {
130*9aed7a77SJitendra Bhati 	atf_tc_set_md_var(tc, "descr",
131*9aed7a77SJitendra Bhati 	    "fts_openat() with dirfd works in Capsicum capability mode");
132*9aed7a77SJitendra Bhati }
ATF_TC_BODY(openat_capsicum,tc)133*9aed7a77SJitendra Bhati ATF_TC_BODY(openat_capsicum, tc)
134*9aed7a77SJitendra Bhati {
135*9aed7a77SJitendra Bhati 	char *paths[] = { ".", NULL };
136*9aed7a77SJitendra Bhati 	FTS *fts;
137*9aed7a77SJitendra Bhati 	FTSENT *ent;
138*9aed7a77SJitendra Bhati 	int dirfd;
139*9aed7a77SJitendra Bhati 	bool saw_file = false, saw_sub = false;
140*9aed7a77SJitendra Bhati 
141*9aed7a77SJitendra Bhati 	if (!feature_present("security_capabilities") ||
142*9aed7a77SJitendra Bhati 	    !feature_present("security_capability_mode"))
143*9aed7a77SJitendra Bhati 		atf_tc_skip("Capsicum not available");
144*9aed7a77SJitendra Bhati 
145*9aed7a77SJitendra Bhati 	ATF_REQUIRE_EQ(0, mkdir("dir", 0755));
146*9aed7a77SJitendra Bhati 	ATF_REQUIRE_EQ(0, mkdir("dir/sub", 0755));
147*9aed7a77SJitendra Bhati 	ATF_REQUIRE_EQ(0, close(creat("dir/sub/file", 0644)));
148*9aed7a77SJitendra Bhati 	ATF_REQUIRE_EQ(0, close(creat("dir/other", 0644)));
149*9aed7a77SJitendra Bhati 
150*9aed7a77SJitendra Bhati 	ATF_REQUIRE((dirfd = open("dir", O_RDONLY | O_DIRECTORY)) >= 0);
151*9aed7a77SJitendra Bhati 	ATF_REQUIRE_EQ(0, cap_enter());
152*9aed7a77SJitendra Bhati 	ATF_REQUIRE((fts = fts_openat(dirfd, paths,
153*9aed7a77SJitendra Bhati 	    FTS_PHYSICAL | FTS_NOCHDIR, NULL)) != NULL);
154*9aed7a77SJitendra Bhati 
155*9aed7a77SJitendra Bhati 	while ((ent = fts_read(fts)) != NULL) {
156*9aed7a77SJitendra Bhati 		if (ent->fts_info == FTS_DP)
157*9aed7a77SJitendra Bhati 			continue;
158*9aed7a77SJitendra Bhati 		if (strcmp(ent->fts_name, "sub") == 0 &&
159*9aed7a77SJitendra Bhati 		    ent->fts_info == FTS_D)
160*9aed7a77SJitendra Bhati 			saw_sub = true;
161*9aed7a77SJitendra Bhati 		if (strcmp(ent->fts_name, "file") == 0 &&
162*9aed7a77SJitendra Bhati 		    ent->fts_info == FTS_F)
163*9aed7a77SJitendra Bhati 			saw_file = true;
164*9aed7a77SJitendra Bhati 	}
165*9aed7a77SJitendra Bhati 
166*9aed7a77SJitendra Bhati 	ATF_CHECK_MSG(saw_sub, "must have visited 'sub' directory");
167*9aed7a77SJitendra Bhati 	ATF_CHECK_MSG(saw_file, "must have visited 'file'");
168*9aed7a77SJitendra Bhati 	ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m");
169*9aed7a77SJitendra Bhati }
170*9aed7a77SJitendra Bhati 
171*9aed7a77SJitendra Bhati /*
172*9aed7a77SJitendra Bhati  * Demonstrate the intended use of fts_dirfd: use
173*9aed7a77SJitendra Bhati  * fts_parent->fts_dirfd + fts_name to access files without
174*9aed7a77SJitendra Bhati  * relying on path-based operations.
175*9aed7a77SJitendra Bhati  */
176*9aed7a77SJitendra Bhati ATF_TC(fts_dirfd_openat);
ATF_TC_HEAD(fts_dirfd_openat,tc)177*9aed7a77SJitendra Bhati ATF_TC_HEAD(fts_dirfd_openat, tc)
178*9aed7a77SJitendra Bhati {
179*9aed7a77SJitendra Bhati 	atf_tc_set_md_var(tc, "descr",
180*9aed7a77SJitendra Bhati 	    "fts_parent->fts_dirfd + fts_name can be used with openat(2)");
181*9aed7a77SJitendra Bhati }
182*9aed7a77SJitendra Bhati 
ATF_TC_BODY(fts_dirfd_openat,tc)183*9aed7a77SJitendra Bhati ATF_TC_BODY(fts_dirfd_openat, tc)
184*9aed7a77SJitendra Bhati {
185*9aed7a77SJitendra Bhati 	char *paths[] = { "dir1", "dir2", NULL };
186*9aed7a77SJitendra Bhati 	FTS *fts;
187*9aed7a77SJitendra Bhati 	FTSENT *ent;
188*9aed7a77SJitendra Bhati 	struct stat sb_path, sb_dirfd;
189*9aed7a77SJitendra Bhati 	int dirfd;
190*9aed7a77SJitendra Bhati 	int nvisited = 0;
191*9aed7a77SJitendra Bhati 
192*9aed7a77SJitendra Bhati 	ATF_REQUIRE_EQ(0, mkdir("dir1", 0755));
193*9aed7a77SJitendra Bhati 	ATF_REQUIRE_EQ(0, mkdir("dir1/sub", 0755));
194*9aed7a77SJitendra Bhati 	ATF_REQUIRE_EQ(0, close(creat("dir1/sub/file", 0644)));
195*9aed7a77SJitendra Bhati 	ATF_REQUIRE_EQ(0, mkdir("dir2", 0755));
196*9aed7a77SJitendra Bhati 	ATF_REQUIRE_EQ(0, close(creat("dir2/file2", 0644)));
197*9aed7a77SJitendra Bhati 
198*9aed7a77SJitendra Bhati 	/*
199*9aed7a77SJitendra Bhati 	 * Open the current working directory as dirfd. fts_openat
200*9aed7a77SJitendra Bhati 	 * stores it in parent->fts_dirfd, allowing children to use
201*9aed7a77SJitendra Bhati 	 * fts_parent->fts_dirfd + fts_name with openat(2).
202*9aed7a77SJitendra Bhati 	 */
203*9aed7a77SJitendra Bhati 	ATF_REQUIRE((dirfd = open(".", O_RDONLY | O_DIRECTORY)) >= 0);
204*9aed7a77SJitendra Bhati 	ATF_REQUIRE((fts = fts_openat(dirfd, paths,
205*9aed7a77SJitendra Bhati 	    FTS_PHYSICAL, NULL)) != NULL);
206*9aed7a77SJitendra Bhati 	close(dirfd);
207*9aed7a77SJitendra Bhati 
208*9aed7a77SJitendra Bhati 	while ((ent = fts_read(fts)) != NULL) {
209*9aed7a77SJitendra Bhati 		if (ent->fts_info == FTS_DP)
210*9aed7a77SJitendra Bhati 			continue;
211*9aed7a77SJitendra Bhati 		if (ent->fts_level == FTS_ROOTLEVEL)
212*9aed7a77SJitendra Bhati 			continue;
213*9aed7a77SJitendra Bhati 
214*9aed7a77SJitendra Bhati 		ATF_REQUIRE_MSG(ent->fts_parent->fts_dirfd >= 0,
215*9aed7a77SJitendra Bhati 		    "fts_parent->fts_dirfd must be valid for '%s'",
216*9aed7a77SJitendra Bhati 		    ent->fts_name);
217*9aed7a77SJitendra Bhati 
218*9aed7a77SJitendra Bhati 		ATF_REQUIRE_EQ_MSG(0,
219*9aed7a77SJitendra Bhati 		    fstatat(ent->fts_parent->fts_dirfd, ent->fts_name,
220*9aed7a77SJitendra Bhati 		    &sb_dirfd, AT_SYMLINK_NOFOLLOW),
221*9aed7a77SJitendra Bhati 		    "fstatat(fts_parent->fts_dirfd, '%s') failed: %m",
222*9aed7a77SJitendra Bhati 		    ent->fts_name);
223*9aed7a77SJitendra Bhati 
224*9aed7a77SJitendra Bhati 		ATF_REQUIRE_EQ_MSG(0,
225*9aed7a77SJitendra Bhati 		    lstat(ent->fts_accpath, &sb_path),
226*9aed7a77SJitendra Bhati 		    "lstat('%s') failed: %m", ent->fts_accpath);
227*9aed7a77SJitendra Bhati 
228*9aed7a77SJitendra Bhati 		ATF_CHECK_EQ_MSG(sb_path.st_ino, sb_dirfd.st_ino,
229*9aed7a77SJitendra Bhati 		    "inode mismatch for '%s': accpath=%ju dirfd=%ju",
230*9aed7a77SJitendra Bhati 		    ent->fts_name,
231*9aed7a77SJitendra Bhati 		    (uintmax_t)sb_path.st_ino,
232*9aed7a77SJitendra Bhati 		    (uintmax_t)sb_dirfd.st_ino);
233*9aed7a77SJitendra Bhati 
234*9aed7a77SJitendra Bhati 		nvisited++;
235*9aed7a77SJitendra Bhati 	}
236*9aed7a77SJitendra Bhati 
237*9aed7a77SJitendra Bhati 	ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m");
238*9aed7a77SJitendra Bhati 
239*9aed7a77SJitendra Bhati 	/* dir1/sub, dir1/sub/file, dir2/file2 = 3 entries */
240*9aed7a77SJitendra Bhati 	ATF_CHECK_EQ_MSG(3, nvisited,
241*9aed7a77SJitendra Bhati 	    "expected 3 entries, got %d", nvisited);
242*9aed7a77SJitendra Bhati }
243*9aed7a77SJitendra Bhati 
ATF_TP_ADD_TCS(tp)244*9aed7a77SJitendra Bhati ATF_TP_ADD_TCS(tp)
245*9aed7a77SJitendra Bhati {
246*9aed7a77SJitendra Bhati 	ATF_TP_ADD_TC(tp, atfdcwd_matches_fts_open);
247*9aed7a77SJitendra Bhati 	ATF_TP_ADD_TC(tp, openat_capsicum);
248*9aed7a77SJitendra Bhati 	ATF_TP_ADD_TC(tp, fts_dirfd_openat);
249*9aed7a77SJitendra Bhati 	return (atf_no_error());
250*9aed7a77SJitendra Bhati }
251