Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/lib/libc/tests/gen/fts_set_test.c
289379 views
1
/*
2
* Copyright (c) 2026 Jitendra Bhati
3
*
4
* SPDX-License-Identifier: BSD-2-Clause
5
*/
6
7
/*
8
* Tests for fts_set(), fts_set_clientptr(), fts_get_clientptr(),
9
* and fts_get_stream().
10
*/
11
12
#include <sys/stat.h>
13
14
#include <errno.h>
15
#include <fcntl.h>
16
#include <fts.h>
17
#include <stdbool.h>
18
#include <stdio.h>
19
#include <stdlib.h>
20
#include <string.h>
21
#include <unistd.h>
22
23
#include <atf-c.h>
24
25
/*
26
* fts_set with invalid options must return non-zero with EINVAL.
27
* Note: fts_set returns 1 (not -1) on error.
28
*/
29
ATF_TC(invalid_options);
30
ATF_TC_HEAD(invalid_options, tc)
31
{
32
atf_tc_set_md_var(tc, "descr",
33
"fts_set with invalid options returns non-zero with EINVAL");
34
}
35
ATF_TC_BODY(invalid_options, tc)
36
{
37
char *paths[] = { ".", NULL };
38
FTS *fts;
39
FTSENT *ent;
40
41
ATF_REQUIRE((fts = fts_open(paths, FTS_PHYSICAL, NULL)) != NULL);
42
43
ent = fts_read(fts);
44
ATF_REQUIRE(ent != NULL);
45
ATF_REQUIRE_ERRNO(EINVAL, fts_set(fts, ent, 99) != 0);
46
ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m");
47
}
48
49
/*
50
* FTS_AGAIN causes the current node to be re-stat()ed and returned
51
* again on the next fts_read() call.
52
*/
53
ATF_TC(again);
54
ATF_TC_HEAD(again, tc)
55
{
56
atf_tc_set_md_var(tc, "descr",
57
"FTS_AGAIN causes the current node to be returned once more");
58
}
59
ATF_TC_BODY(again, tc)
60
{
61
char *paths[] = { "dir", NULL };
62
FTS *fts;
63
FTSENT *ent;
64
int revisit_count;
65
66
ATF_REQUIRE_EQ(0, mkdir("dir", 0755));
67
ATF_REQUIRE_EQ(0, close(creat("dir/file", 0644)));
68
69
ATF_REQUIRE((fts = fts_open(paths, FTS_PHYSICAL, NULL)) != NULL);
70
71
revisit_count = 0;
72
for (errno = 0; (ent = fts_read(fts)) != NULL; errno = 0) {
73
if (ent->fts_info == FTS_F && revisit_count == 0) {
74
ATF_REQUIRE_EQ_MSG(0,
75
fts_set(fts, ent, FTS_AGAIN),
76
"fts_set(FTS_AGAIN): %m");
77
revisit_count++;
78
} else if (ent->fts_info == FTS_F && revisit_count >= 1) {
79
revisit_count++;
80
}
81
}
82
ATF_CHECK_EQ_MSG(0, errno, "traversal ended with errno %d", errno);
83
ATF_CHECK_EQ_MSG(2, revisit_count,
84
"expected file visited twice via FTS_AGAIN, saw %d",
85
revisit_count);
86
87
ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m");
88
}
89
90
/*
91
* FTS_AGAIN set twice in a row causes the node to be visited three
92
* times total. Each fts_read() clears fts_options, so the caller must
93
* set FTS_AGAIN again explicitly each time.
94
*/
95
ATF_TC(again_consecutive);
96
ATF_TC_HEAD(again_consecutive, tc)
97
{
98
atf_tc_set_md_var(tc, "descr",
99
"FTS_AGAIN set twice in a row visits the node three times");
100
}
101
ATF_TC_BODY(again_consecutive, tc)
102
{
103
char *paths[] = { "file", NULL };
104
FTS *fts;
105
FTSENT *ent;
106
int visit_count;
107
108
ATF_REQUIRE_EQ(0, close(creat("file", 0644)));
109
110
ATF_REQUIRE((fts = fts_open(paths, FTS_PHYSICAL, NULL)) != NULL);
111
112
visit_count = 0;
113
while ((ent = fts_read(fts)) != NULL) {
114
if (ent->fts_info == FTS_F) {
115
visit_count++;
116
if (visit_count < 3)
117
ATF_REQUIRE_EQ(0,
118
fts_set(fts, ent, FTS_AGAIN));
119
}
120
}
121
ATF_CHECK_EQ_MSG(3, visit_count,
122
"expected 3 visits with consecutive FTS_AGAIN, got %d",
123
visit_count);
124
125
ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m");
126
}
127
128
/*
129
* FTS_FOLLOW on an FTS_SL entry pointing to a regular file yields FTS_F.
130
*/
131
ATF_TC(follow_symlink_to_file);
132
ATF_TC_HEAD(follow_symlink_to_file, tc)
133
{
134
atf_tc_set_md_var(tc, "descr",
135
"FTS_FOLLOW on FTS_SL to regular file yields FTS_F");
136
}
137
ATF_TC_BODY(follow_symlink_to_file, tc)
138
{
139
char *paths[] = { "dir", NULL };
140
FTS *fts;
141
FTSENT *ent;
142
bool followed;
143
144
ATF_REQUIRE_EQ(0, mkdir("dir", 0755));
145
ATF_REQUIRE_EQ(0, close(creat("dir/target", 0644)));
146
ATF_REQUIRE_EQ(0, symlink("target", "dir/link"));
147
148
ATF_REQUIRE((fts = fts_open(paths, FTS_PHYSICAL, NULL)) != NULL);
149
150
followed = false;
151
while ((ent = fts_read(fts)) != NULL) {
152
if (ent->fts_info == FTS_SL &&
153
strcmp(ent->fts_name, "link") == 0)
154
ATF_REQUIRE_EQ(0, fts_set(fts, ent, FTS_FOLLOW));
155
else if (ent->fts_info == FTS_F &&
156
strcmp(ent->fts_name, "link") == 0)
157
followed = true;
158
}
159
ATF_CHECK_MSG(followed,
160
"FTS_FOLLOW on symlink-to-file must yield FTS_F");
161
162
ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m");
163
}
164
165
/*
166
* FTS_FOLLOW on an FTS_SL entry pointing to a directory causes descent
167
* into the target directory.
168
*/
169
ATF_TC(follow_symlink_to_dir);
170
ATF_TC_HEAD(follow_symlink_to_dir, tc)
171
{
172
atf_tc_set_md_var(tc, "descr",
173
"FTS_FOLLOW on FTS_SL to directory causes descent");
174
}
175
ATF_TC_BODY(follow_symlink_to_dir, tc)
176
{
177
char *paths[] = { "dir", NULL };
178
FTS *fts;
179
FTSENT *ent;
180
bool saw_inside;
181
182
ATF_REQUIRE_EQ(0, mkdir("dir", 0755));
183
ATF_REQUIRE_EQ(0, mkdir("dir/real", 0755));
184
ATF_REQUIRE_EQ(0, close(creat("dir/real/inside", 0644)));
185
ATF_REQUIRE_EQ(0, symlink("real", "dir/link"));
186
187
ATF_REQUIRE((fts = fts_open(paths, FTS_PHYSICAL, NULL)) != NULL);
188
189
saw_inside = false;
190
while ((ent = fts_read(fts)) != NULL) {
191
if (ent->fts_info == FTS_SL &&
192
strcmp(ent->fts_name, "link") == 0)
193
ATF_REQUIRE_EQ(0, fts_set(fts, ent, FTS_FOLLOW));
194
if (ent->fts_info == FTS_F &&
195
strcmp(ent->fts_name, "inside") == 0 &&
196
strcmp(ent->fts_path, "dir/link/inside") == 0)
197
saw_inside = true;
198
}
199
ATF_CHECK_MSG(saw_inside,
200
"FTS_FOLLOW on symlink-to-dir should descend and visit 'inside'");
201
202
ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m");
203
}
204
205
/*
206
* FTS_FOLLOW on a dangling symlink (FTS_SLNONE) yields FTS_SLNONE again.
207
* FTS_SLNONE requires FTS_LOGICAL — under FTS_PHYSICAL a dangling
208
* symlink is reported as FTS_SL.
209
*/
210
ATF_TC(follow_dead_symlink);
211
ATF_TC_HEAD(follow_dead_symlink, tc)
212
{
213
atf_tc_set_md_var(tc, "descr",
214
"FTS_FOLLOW on dead symlink yields FTS_SLNONE");
215
}
216
ATF_TC_BODY(follow_dead_symlink, tc)
217
{
218
char *paths[] = { "dead", NULL };
219
FTS *fts;
220
FTSENT *ent;
221
222
ATF_REQUIRE_EQ(0, symlink("no-such-target", "dead"));
223
224
ATF_REQUIRE((fts = fts_open(paths, FTS_LOGICAL, NULL)) != NULL);
225
226
ent = fts_read(fts);
227
ATF_REQUIRE(ent != NULL);
228
ATF_REQUIRE_EQ_MSG(FTS_SLNONE, ent->fts_info,
229
"expected FTS_SLNONE for dead symlink, got %d", ent->fts_info);
230
231
ATF_REQUIRE_EQ(0, fts_set(fts, ent, FTS_FOLLOW));
232
ent = fts_read(fts);
233
ATF_REQUIRE(ent != NULL);
234
ATF_CHECK_EQ_MSG(FTS_SLNONE, ent->fts_info,
235
"FTS_FOLLOW on dead symlink should still be FTS_SLNONE, got %d",
236
ent->fts_info);
237
238
ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m");
239
}
240
241
/*
242
* FTS_SKIP on an FTS_D node prevents descent into that directory.
243
* The next fts_read() converts the node to FTS_DP without visiting
244
* any children.
245
*/
246
ATF_TC(skip);
247
ATF_TC_HEAD(skip, tc)
248
{
249
atf_tc_set_md_var(tc, "descr",
250
"FTS_SKIP prevents descent into a directory");
251
}
252
ATF_TC_BODY(skip, tc)
253
{
254
char *paths[] = { "dir", NULL };
255
FTS *fts;
256
FTSENT *ent;
257
bool saw_inside;
258
259
ATF_REQUIRE_EQ(0, mkdir("dir", 0755));
260
ATF_REQUIRE_EQ(0, mkdir("dir/skip_me", 0755));
261
ATF_REQUIRE_EQ(0, close(creat("dir/skip_me/inside", 0644)));
262
ATF_REQUIRE_EQ(0, close(creat("dir/sibling", 0644)));
263
264
ATF_REQUIRE((fts = fts_open(paths, FTS_PHYSICAL, NULL)) != NULL);
265
266
saw_inside = false;
267
while ((ent = fts_read(fts)) != NULL) {
268
if (ent->fts_info == FTS_D &&
269
strcmp(ent->fts_name, "skip_me") == 0)
270
ATF_REQUIRE_EQ(0, fts_set(fts, ent, FTS_SKIP));
271
if (strcmp(ent->fts_name, "inside") == 0)
272
saw_inside = true;
273
}
274
ATF_CHECK_MSG(!saw_inside,
275
"FTS_SKIP: 'inside' must not have been visited");
276
277
ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m");
278
}
279
280
/*
281
* fts_set_clientptr() and fts_get_clientptr() store and retrieve an
282
* arbitrary pointer on the FTS stream.
283
*/
284
ATF_TC(clientptr_roundtrip);
285
ATF_TC_HEAD(clientptr_roundtrip, tc)
286
{
287
atf_tc_set_md_var(tc, "descr",
288
"fts_set_clientptr / fts_get_clientptr round-trip");
289
}
290
ATF_TC_BODY(clientptr_roundtrip, tc)
291
{
292
char *paths[] = { "dir", NULL };
293
FTS *fts;
294
FTSENT *ent;
295
int value = 42;
296
297
ATF_REQUIRE_EQ(0, mkdir("dir", 0755));
298
ATF_REQUIRE_EQ(0, close(creat("dir/file", 0644)));
299
300
ATF_REQUIRE((fts = fts_open(paths, FTS_PHYSICAL, NULL)) != NULL);
301
302
/* Initially NULL. */
303
ATF_CHECK_EQ(NULL, fts_get_clientptr(fts));
304
305
fts_set_clientptr(fts, &value);
306
307
while ((ent = fts_read(fts)) != NULL) {
308
/*
309
* Verify the pointer is accessible and correct
310
* while traversal is active.
311
*/
312
ATF_CHECK_EQ_MSG(&value, fts_get_clientptr(fts),
313
"fts_get_clientptr did not return the stored pointer "
314
"for entry '%s'", ent->fts_name);
315
}
316
317
/* Overwrite with NULL, verify. */
318
fts_set_clientptr(fts, NULL);
319
ATF_CHECK_EQ(NULL, fts_get_clientptr(fts));
320
321
ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m");
322
}
323
324
/*
325
* fts_get_stream() returns the parent FTS* from any FTSENT* returned
326
* by fts_read().
327
*/
328
ATF_TC(get_stream_backpointer);
329
ATF_TC_HEAD(get_stream_backpointer, tc)
330
{
331
atf_tc_set_md_var(tc, "descr",
332
"fts_get_stream returns the parent FTS* from an FTSENT*");
333
}
334
ATF_TC_BODY(get_stream_backpointer, tc)
335
{
336
char *paths[] = { "dir", NULL };
337
FTS *fts;
338
FTSENT *ent;
339
340
ATF_REQUIRE_EQ(0, mkdir("dir", 0755));
341
ATF_REQUIRE_EQ(0, close(creat("dir/file", 0644)));
342
343
ATF_REQUIRE((fts = fts_open(paths, FTS_PHYSICAL, NULL)) != NULL);
344
345
while ((ent = fts_read(fts)) != NULL) {
346
ATF_CHECK_EQ_MSG(fts, fts_get_stream(ent),
347
"fts_get_stream(ent) must return the parent FTS*, "
348
"entry: %s info: %d",
349
ent->fts_name, ent->fts_info);
350
}
351
352
ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m");
353
}
354
355
ATF_TP_ADD_TCS(tp)
356
{
357
ATF_TP_ADD_TC(tp, invalid_options);
358
ATF_TP_ADD_TC(tp, again);
359
ATF_TP_ADD_TC(tp, again_consecutive);
360
ATF_TP_ADD_TC(tp, follow_symlink_to_file);
361
ATF_TP_ADD_TC(tp, follow_symlink_to_dir);
362
ATF_TP_ADD_TC(tp, follow_dead_symlink);
363
ATF_TP_ADD_TC(tp, skip);
364
ATF_TP_ADD_TC(tp, clientptr_roundtrip);
365
ATF_TP_ADD_TC(tp, get_stream_backpointer);
366
367
return (atf_no_error());
368
}
369
370