mirror of
https://github.com/freebsd/freebsd-src.git
synced 2026-10-28 11:25:21 +01:00
fts: add fts_openat() API
Add fts_openat() as a new entry point for fts(3). When dirfd is AT_FDCWD the behaviour is identical to fts_open(). Passing a pre-opened directory fd allows fts traversal inside Capsicum capability mode where path-based operations are not permitted. Capability mode users should use fts_parent->fts_dirfd + fts_name with openat(2) to access files. Reviewed by: asomers Relnotes: yes Sponsored by: Google LLC (GSoC 2026) Pull Request: https://github.com/freebsd/freebsd-src/pull/2273
This commit is contained in:
1 parent
9b8c2bffeb
commit
9aed7a7745
7 files changed
+346
-13
No files matched your search
+3
-1
@@ -1,4 +1,4 @@
|
||||
/*-
|
||||
/*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*
|
||||
* Copyright (c) 1989, 1993
|
||||
@@ -148,6 +148,8 @@ FTS *fts_get_stream(FTSENT *);
|
||||
#define fts_get_stream(ftsent) ((ftsent)->fts_fts)
|
||||
FTS *fts_open(char * const *, int,
|
||||
int (*)(const FTSENT * const *, const FTSENT * const *));
|
||||
FTS *fts_openat(int, char * const *, int,
|
||||
int (*)(const FTSENT * const *, const FTSENT * const *));
|
||||
#ifdef __BLOCKS__
|
||||
FTS *fts_open_b(char * const *, int,
|
||||
int (^)(const FTSENT * const *, const FTSENT * const *));
|
||||
|
||||
@@ -389,6 +389,7 @@ MLINKS+=frexp.3 frexpf.3 \
|
||||
MLINKS+=fts.3 fts_children.3 \
|
||||
fts.3 fts_close.3 \
|
||||
fts.3 fts_open.3 \
|
||||
fts.3 fts_openat.3 \
|
||||
fts.3 fts_read.3 \
|
||||
fts.3 fts_set.3 \
|
||||
fts.3 fts_set_clientptr.3 \
|
||||
|
||||
@@ -476,6 +476,7 @@ FBSD_1.9 {
|
||||
fts_read;
|
||||
fts_set;
|
||||
fts_set_clientptr;
|
||||
fts_openat;
|
||||
posix_spawn_file_actions_addchdir;
|
||||
posix_spawn_file_actions_addfchdir;
|
||||
posix_spawnattr_getexecfd_np;
|
||||
|
||||
+33
-1
@@ -25,7 +25,7 @@
|
||||
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
.\" SUCH DAMAGE.
|
||||
.\"
|
||||
.Dd August 9, 2026
|
||||
.Dd August 13, 2026
|
||||
.Dt FTS 3
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -38,6 +38,8 @@
|
||||
.Ft FTS *
|
||||
.Fn fts_open "char * const *path_argv" "int options" "int (*compar)(const FTSENT * const *, const FTSENT * const *)"
|
||||
.Ft FTS *
|
||||
.Fn fts_openat "int dirfd" "char * const *path_argv" "int options" "int (*compar)(const FTSENT * const *, const FTSENT * const *)"
|
||||
.Ft FTS *
|
||||
.Fn fts_open_b "char * const *path_argv" "int options" "int (^compar)(const FTSENT * const *, const FTSENT * const *)"
|
||||
.Ft FTSENT *
|
||||
.Fn fts_read "FTS *ftsp"
|
||||
@@ -621,6 +623,36 @@ except that it takes a block pointer instead of a function pointer.
|
||||
The block is copied before
|
||||
.Fn fts_open_b
|
||||
returns, so the original can safely go out of scope or be released.
|
||||
.Ss Fn fts_openat
|
||||
The
|
||||
.Fn fts_openat
|
||||
function is identical to
|
||||
.Fn fts_open
|
||||
except that it accepts a file descriptor
|
||||
.Fa dirfd
|
||||
as its first argument.
|
||||
If
|
||||
.Fa dirfd
|
||||
is
|
||||
.Dv AT_FDCWD ,
|
||||
the behaviour is identical to
|
||||
.Fn fts_open .
|
||||
Otherwise,
|
||||
.Fa dirfd
|
||||
must be an open file descriptor referring to a directory,
|
||||
and the traversal is rooted there.
|
||||
This allows
|
||||
.Fn fts_openat
|
||||
to be used inside Capsicum capability mode
|
||||
.Pq Xr capsicum 4 ,
|
||||
where path-based operations are not permitted.
|
||||
The
|
||||
.Fn fts_openat
|
||||
function duplicates
|
||||
.Fa dirfd
|
||||
internally, so the caller may close it after
|
||||
.Fn fts_openat
|
||||
returns.
|
||||
.Ss Fn fts_read
|
||||
The
|
||||
.Fn fts_read
|
||||
|
||||
+57
-11
@@ -155,6 +155,10 @@ __fts_open(FTS *sp, char * const *argv, int rootfd)
|
||||
if ((parent = fts_alloc(sp, "", 0)) == NULL)
|
||||
goto mem2;
|
||||
parent->fts_level = FTS_ROOTPARENTLEVEL;
|
||||
parent->fts_dirfd = AT_FDCWD;
|
||||
|
||||
if (rootfd != AT_FDCWD)
|
||||
parent->fts_dirfd = rootfd;
|
||||
|
||||
/* Shush, GCC. */
|
||||
tmp = NULL;
|
||||
@@ -169,11 +173,20 @@ __fts_open(FTS *sp, char * const *argv, int rootfd)
|
||||
p->fts_accpath = p->fts_name;
|
||||
p->fts_info = fts_stat(sp, p,
|
||||
ISSET(FTS_COMFOLLOWDIR) ? -1 : ISSET(FTS_COMFOLLOW),
|
||||
-1);
|
||||
rootfd == AT_FDCWD ? -1 : rootfd);
|
||||
|
||||
/* Command-line "." and ".." are real directories. */
|
||||
if (p->fts_info == FTS_DOT)
|
||||
p->fts_info = FTS_D;
|
||||
if (p->fts_info == FTS_D) {
|
||||
if (strcmp(p->fts_name, ".") == 0)
|
||||
p->fts_dirfd = rootfd != AT_FDCWD ?
|
||||
_dup(rootfd) :
|
||||
_open(".", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
|
||||
else
|
||||
p->fts_dirfd = _openat(rootfd, p->fts_name,
|
||||
O_RDONLY | O_DIRECTORY | O_CLOEXEC);
|
||||
}
|
||||
|
||||
/*
|
||||
* If comparison routine supplied, traverse in sorted
|
||||
@@ -213,10 +226,16 @@ __fts_open(FTS *sp, char * const *argv, int rootfd)
|
||||
* descriptor we run anyway, just more slowly. We use _openat rather
|
||||
* than _dup because rootfd may be AT_FDCWD, not a real descriptor.
|
||||
*/
|
||||
if (!ISSET(FTS_NOCHDIR) &&
|
||||
(sp->fts_rfd = _openat(rootfd, ".", O_RDONLY |
|
||||
O_CLOEXEC, 0)) < 0)
|
||||
SET(FTS_NOCHDIR);
|
||||
|
||||
if (!ISSET(FTS_NOCHDIR)) {
|
||||
if (rootfd != AT_FDCWD)
|
||||
sp->fts_rfd = _dup(rootfd);
|
||||
if (sp->fts_rfd < 0)
|
||||
SET(FTS_NOCHDIR);
|
||||
else if ((sp->fts_rfd =
|
||||
_open(".", O_RDONLY | O_CLOEXEC, 0)) < 0)
|
||||
SET(FTS_NOCHDIR);
|
||||
}
|
||||
return (sp);
|
||||
|
||||
mem3: fts_lfree(root);
|
||||
@@ -229,9 +248,17 @@ mem1: free(sp);
|
||||
FTS *
|
||||
fts_open(char * const *argv, int options,
|
||||
int (*compar)(const FTSENT * const *, const FTSENT * const *))
|
||||
{
|
||||
return (fts_openat(AT_FDCWD, argv, options, compar));
|
||||
}
|
||||
|
||||
FTS *
|
||||
fts_openat(int dirfd, char * const *argv, int options,
|
||||
int (*compar)(const FTSENT * const *, const FTSENT * const *))
|
||||
{
|
||||
struct _fts_private *priv;
|
||||
FTS *sp;
|
||||
int rootfd;
|
||||
|
||||
/* Options check. */
|
||||
if (options & ~FTS_OPTIONMASK) {
|
||||
@@ -239,7 +266,7 @@ fts_open(char * const *argv, int options,
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* fts_open() requires at least one path */
|
||||
/* fts_openat() requires at least one path */
|
||||
if (*argv == NULL) {
|
||||
errno = EINVAL;
|
||||
return (NULL);
|
||||
@@ -251,10 +278,15 @@ fts_open(char * const *argv, int options,
|
||||
sp = &priv->ftsp_fts;
|
||||
sp->fts_compar = compar;
|
||||
sp->fts_options = options;
|
||||
if (dirfd == AT_FDCWD)
|
||||
rootfd = AT_FDCWD;
|
||||
else if ((rootfd = _dup(dirfd)) < 0) {
|
||||
free(priv);
|
||||
return (NULL);
|
||||
}
|
||||
return (__fts_open(sp, argv, rootfd));
|
||||
|
||||
return (__fts_open(sp, argv, AT_FDCWD));
|
||||
}
|
||||
|
||||
#ifdef __BLOCKS__
|
||||
FTS *
|
||||
fts_open_b(char * const *argv, int options,
|
||||
@@ -781,7 +813,16 @@ fts_build(FTS *sp, int type)
|
||||
oflag = DTF_NODUP;
|
||||
else
|
||||
oflag = DTF_HIDEW | DTF_NODUP;
|
||||
if ((dirp = __opendir2(cur->fts_accpath, oflag)) == NULL) {
|
||||
if (cur->fts_dirfd >= 0) {
|
||||
int fd;
|
||||
fd = _openat(cur->fts_dirfd, ".", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
|
||||
if (fd >= 0)
|
||||
dirp = fdopendir(fd);
|
||||
else
|
||||
dirp = __opendir2(cur->fts_accpath, oflag);
|
||||
} else
|
||||
dirp = __opendir2(cur->fts_accpath, oflag);
|
||||
if (dirp == NULL) {
|
||||
if (type == BREAD) {
|
||||
cur->fts_info = FTS_DNR;
|
||||
cur->fts_errno = errno;
|
||||
@@ -789,7 +830,8 @@ fts_build(FTS *sp, int type)
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
cur->fts_dirfd = _dup(_dirfd(dirp));
|
||||
if (cur->fts_dirfd < 0)
|
||||
cur->fts_dirfd = _dup(_dirfd(dirp));
|
||||
|
||||
/*
|
||||
* In the FTS_PHYSICAL | FTS_NOSTAT case, we want to avoid calling
|
||||
@@ -927,8 +969,12 @@ mem1: saved_errno = errno;
|
||||
|
||||
p->fts_level = level;
|
||||
p->fts_parent = sp->fts_cur;
|
||||
if (dp->d_type == DT_DIR) {
|
||||
p->fts_dirfd = _openat(_dirfd(dirp),
|
||||
p->fts_name,
|
||||
O_RDONLY | O_DIRECTORY | O_CLOEXEC);
|
||||
}
|
||||
p->fts_pathlen = len + dnamlen;
|
||||
|
||||
if (dp->d_type == DT_WHT)
|
||||
p->fts_flags |= FTS_ISW;
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ ATF_TESTS_C+= fts_blocks_test
|
||||
ATF_TESTS_C+= fts_children_test
|
||||
ATF_TESTS_C+= fts_misc_test
|
||||
ATF_TESTS_C+= fts_open_test
|
||||
ATF_TESTS_C+= fts_openat_test
|
||||
ATF_TESTS_C+= fts_options_test
|
||||
ATF_TESTS_C+= fts_regress_test
|
||||
ATF_TESTS_C+= fts_set_test
|
||||
|
||||
@@ -0,0 +1,250 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Jitendra Bhati
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
/*
|
||||
* Basic tests for fts_openat(). When called with AT_FDCWD the
|
||||
* behaviour must be identical to fts_open().
|
||||
*/
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <fts.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/capsicum.h>
|
||||
|
||||
#include <atf-c.h>
|
||||
|
||||
#define FTS_TEST_MAXENTRIES 64
|
||||
|
||||
static int
|
||||
fts_lexical_compar(const FTSENT * const *a, const FTSENT * const *b)
|
||||
{
|
||||
return (strcmp((*a)->fts_name, (*b)->fts_name));
|
||||
}
|
||||
|
||||
/*
|
||||
* fts_openat(AT_FDCWD, ...) must behave identically to fts_open().
|
||||
*/
|
||||
ATF_TC(atfdcwd_matches_fts_open);
|
||||
ATF_TC_HEAD(atfdcwd_matches_fts_open, tc)
|
||||
{
|
||||
atf_tc_set_md_var(tc, "descr",
|
||||
"fts_openat(AT_FDCWD) behaves identically to fts_open");
|
||||
}
|
||||
|
||||
ATF_TC_BODY(atfdcwd_matches_fts_open, tc)
|
||||
{
|
||||
char *cwd, *abspath;
|
||||
char *paths[2];
|
||||
FTS *fts;
|
||||
FTSENT *ent;
|
||||
|
||||
int *info1, *info2;
|
||||
char (*names1)[NAME_MAX + 1], (*names2)[NAME_MAX + 1];
|
||||
int n1, n2, i;
|
||||
|
||||
ATF_REQUIRE((info1 = malloc(FTS_TEST_MAXENTRIES *
|
||||
sizeof(*info1))) != NULL);
|
||||
ATF_REQUIRE((info2 = malloc(FTS_TEST_MAXENTRIES *
|
||||
sizeof(*info2))) != NULL);
|
||||
ATF_REQUIRE((names1 = malloc(FTS_TEST_MAXENTRIES *
|
||||
sizeof(*names1))) != NULL);
|
||||
ATF_REQUIRE((names2 = malloc(FTS_TEST_MAXENTRIES *
|
||||
sizeof(*names2))) != NULL);
|
||||
|
||||
cwd = malloc(PATH_MAX);
|
||||
ATF_REQUIRE(cwd != NULL);
|
||||
abspath = malloc(PATH_MAX * 2);
|
||||
ATF_REQUIRE(abspath != NULL);
|
||||
|
||||
ATF_REQUIRE(getcwd(cwd, PATH_MAX) != NULL);
|
||||
ATF_REQUIRE_EQ(0, mkdir("dir", 0755));
|
||||
ATF_REQUIRE_EQ(0, mkdir("dir/sub", 0755));
|
||||
ATF_REQUIRE_EQ(0, close(creat("dir/sub/file", 0644)));
|
||||
ATF_REQUIRE_EQ(0, close(creat("dir/other", 0644)));
|
||||
|
||||
snprintf(abspath, PATH_MAX * 2, "%s/dir", cwd);
|
||||
paths[0] = abspath;
|
||||
paths[1] = NULL;
|
||||
|
||||
/* Collect fts_open results. */
|
||||
ATF_REQUIRE((fts = fts_open(paths, FTS_PHYSICAL,
|
||||
fts_lexical_compar)) != NULL);
|
||||
for (n1 = 0;
|
||||
(ent = fts_read(fts)) != NULL && n1 < FTS_TEST_MAXENTRIES;
|
||||
n1++) {
|
||||
info1[n1] = ent->fts_info;
|
||||
strlcpy(names1[n1], ent->fts_name, NAME_MAX + 1);
|
||||
}
|
||||
ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close: %m");
|
||||
|
||||
/* Collect fts_openat results. */
|
||||
ATF_REQUIRE((fts = fts_openat(AT_FDCWD, paths, FTS_PHYSICAL,
|
||||
fts_lexical_compar)) != NULL);
|
||||
for (n2 = 0;
|
||||
(ent = fts_read(fts)) != NULL && n2 < FTS_TEST_MAXENTRIES;
|
||||
n2++) {
|
||||
info2[n2] = ent->fts_info;
|
||||
strlcpy(names2[n2], ent->fts_name, NAME_MAX + 1);
|
||||
}
|
||||
ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close: %m");
|
||||
|
||||
/* Compare. */
|
||||
ATF_CHECK_EQ_MSG(n1, n2,
|
||||
"entry count mismatch: fts_open=%d fts_openat=%d", n1, n2);
|
||||
for (i = 0; i < n1 && i < n2; i++) {
|
||||
ATF_CHECK_EQ_MSG(info1[i], info2[i],
|
||||
"fts_info mismatch at entry %d: "
|
||||
"fts_open=%d fts_openat=%d name=%s",
|
||||
i, info1[i], info2[i], names1[i]);
|
||||
ATF_CHECK_STREQ_MSG(names1[i], names2[i],
|
||||
"fts_name mismatch at entry %d: "
|
||||
"fts_open='%s' fts_openat='%s'",
|
||||
i, names1[i], names2[i]);
|
||||
}
|
||||
|
||||
free(cwd);
|
||||
free(abspath);
|
||||
free(info1);
|
||||
free(info2);
|
||||
free(names1);
|
||||
free(names2);
|
||||
}
|
||||
|
||||
/*
|
||||
* fts_openat() with a real dirfd must work in Capsicum capability mode.
|
||||
*/
|
||||
ATF_TC(openat_capsicum);
|
||||
ATF_TC_HEAD(openat_capsicum, tc)
|
||||
{
|
||||
atf_tc_set_md_var(tc, "descr",
|
||||
"fts_openat() with dirfd works in Capsicum capability mode");
|
||||
}
|
||||
ATF_TC_BODY(openat_capsicum, tc)
|
||||
{
|
||||
char *paths[] = { ".", NULL };
|
||||
FTS *fts;
|
||||
FTSENT *ent;
|
||||
int dirfd;
|
||||
bool saw_file = false, saw_sub = false;
|
||||
|
||||
if (!feature_present("security_capabilities") ||
|
||||
!feature_present("security_capability_mode"))
|
||||
atf_tc_skip("Capsicum not available");
|
||||
|
||||
ATF_REQUIRE_EQ(0, mkdir("dir", 0755));
|
||||
ATF_REQUIRE_EQ(0, mkdir("dir/sub", 0755));
|
||||
ATF_REQUIRE_EQ(0, close(creat("dir/sub/file", 0644)));
|
||||
ATF_REQUIRE_EQ(0, close(creat("dir/other", 0644)));
|
||||
|
||||
ATF_REQUIRE((dirfd = open("dir", O_RDONLY | O_DIRECTORY)) >= 0);
|
||||
ATF_REQUIRE_EQ(0, cap_enter());
|
||||
ATF_REQUIRE((fts = fts_openat(dirfd, paths,
|
||||
FTS_PHYSICAL | FTS_NOCHDIR, NULL)) != NULL);
|
||||
|
||||
while ((ent = fts_read(fts)) != NULL) {
|
||||
if (ent->fts_info == FTS_DP)
|
||||
continue;
|
||||
if (strcmp(ent->fts_name, "sub") == 0 &&
|
||||
ent->fts_info == FTS_D)
|
||||
saw_sub = true;
|
||||
if (strcmp(ent->fts_name, "file") == 0 &&
|
||||
ent->fts_info == FTS_F)
|
||||
saw_file = true;
|
||||
}
|
||||
|
||||
ATF_CHECK_MSG(saw_sub, "must have visited 'sub' directory");
|
||||
ATF_CHECK_MSG(saw_file, "must have visited 'file'");
|
||||
ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m");
|
||||
}
|
||||
|
||||
/*
|
||||
* Demonstrate the intended use of fts_dirfd: use
|
||||
* fts_parent->fts_dirfd + fts_name to access files without
|
||||
* relying on path-based operations.
|
||||
*/
|
||||
ATF_TC(fts_dirfd_openat);
|
||||
ATF_TC_HEAD(fts_dirfd_openat, tc)
|
||||
{
|
||||
atf_tc_set_md_var(tc, "descr",
|
||||
"fts_parent->fts_dirfd + fts_name can be used with openat(2)");
|
||||
}
|
||||
|
||||
ATF_TC_BODY(fts_dirfd_openat, tc)
|
||||
{
|
||||
char *paths[] = { "dir1", "dir2", NULL };
|
||||
FTS *fts;
|
||||
FTSENT *ent;
|
||||
struct stat sb_path, sb_dirfd;
|
||||
int dirfd;
|
||||
int nvisited = 0;
|
||||
|
||||
ATF_REQUIRE_EQ(0, mkdir("dir1", 0755));
|
||||
ATF_REQUIRE_EQ(0, mkdir("dir1/sub", 0755));
|
||||
ATF_REQUIRE_EQ(0, close(creat("dir1/sub/file", 0644)));
|
||||
ATF_REQUIRE_EQ(0, mkdir("dir2", 0755));
|
||||
ATF_REQUIRE_EQ(0, close(creat("dir2/file2", 0644)));
|
||||
|
||||
/*
|
||||
* Open the current working directory as dirfd. fts_openat
|
||||
* stores it in parent->fts_dirfd, allowing children to use
|
||||
* fts_parent->fts_dirfd + fts_name with openat(2).
|
||||
*/
|
||||
ATF_REQUIRE((dirfd = open(".", O_RDONLY | O_DIRECTORY)) >= 0);
|
||||
ATF_REQUIRE((fts = fts_openat(dirfd, paths,
|
||||
FTS_PHYSICAL, NULL)) != NULL);
|
||||
close(dirfd);
|
||||
|
||||
while ((ent = fts_read(fts)) != NULL) {
|
||||
if (ent->fts_info == FTS_DP)
|
||||
continue;
|
||||
if (ent->fts_level == FTS_ROOTLEVEL)
|
||||
continue;
|
||||
|
||||
ATF_REQUIRE_MSG(ent->fts_parent->fts_dirfd >= 0,
|
||||
"fts_parent->fts_dirfd must be valid for '%s'",
|
||||
ent->fts_name);
|
||||
|
||||
ATF_REQUIRE_EQ_MSG(0,
|
||||
fstatat(ent->fts_parent->fts_dirfd, ent->fts_name,
|
||||
&sb_dirfd, AT_SYMLINK_NOFOLLOW),
|
||||
"fstatat(fts_parent->fts_dirfd, '%s') failed: %m",
|
||||
ent->fts_name);
|
||||
|
||||
ATF_REQUIRE_EQ_MSG(0,
|
||||
lstat(ent->fts_accpath, &sb_path),
|
||||
"lstat('%s') failed: %m", ent->fts_accpath);
|
||||
|
||||
ATF_CHECK_EQ_MSG(sb_path.st_ino, sb_dirfd.st_ino,
|
||||
"inode mismatch for '%s': accpath=%ju dirfd=%ju",
|
||||
ent->fts_name,
|
||||
(uintmax_t)sb_path.st_ino,
|
||||
(uintmax_t)sb_dirfd.st_ino);
|
||||
|
||||
nvisited++;
|
||||
}
|
||||
|
||||
ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m");
|
||||
|
||||
/* dir1/sub, dir1/sub/file, dir2/file2 = 3 entries */
|
||||
ATF_CHECK_EQ_MSG(3, nvisited,
|
||||
"expected 3 entries, got %d", nvisited);
|
||||
}
|
||||
|
||||
ATF_TP_ADD_TCS(tp)
|
||||
{
|
||||
ATF_TP_ADD_TC(tp, atfdcwd_matches_fts_open);
|
||||
ATF_TP_ADD_TC(tp, openat_capsicum);
|
||||
ATF_TP_ADD_TC(tp, fts_dirfd_openat);
|
||||
return (atf_no_error());
|
||||
}
|
||||
Reference in new issue
Block a user