libc: Add fscandir(), fscandir_b(), scandirat_b().

While here, clean up scandir() a bit and improve the documentation.

MFC after:	never
Sponsored by:	Klara, Inc.
Reviewed by:	markj
Differential Revision:	https://reviews.freebsd.org/D50935
This commit is contained in:
Dag-Erling Smørgrav 2025-06-20 13:10:23 +02:00
parent 609720ed97
commit deeebfdeca
8 changed files with 410 additions and 42 deletions

View file

@ -130,9 +130,22 @@ int scandir_b(const char *, struct dirent ***,
#endif
#endif
#if __BSD_VISIBLE
int fscandir(int, struct dirent ***,
int (*)(const struct dirent *), int (*)(const struct dirent **,
const struct dirent **));
#ifdef __BLOCKS__
int fscandir_b(int, struct dirent ***,
int (^)(const struct dirent *),
int (^)(const struct dirent **, const struct dirent **));
#endif
int scandirat(int, const char *, struct dirent ***,
int (*)(const struct dirent *), int (*)(const struct dirent **,
const struct dirent **));
#ifdef __BLOCKS__
int scandirat_b(int, const char *, struct dirent ***,
int (^)(const struct dirent *),
int (^)(const struct dirent **, const struct dirent **));
#endif
#endif
#if __XSI_VISIBLE
void seekdir(DIR *, long);

View file

@ -499,8 +499,11 @@ MLINKS+=rand48.3 _rand48.3 \
MLINKS+=rtld_get_var.3 \
rtld_set_var.3
MLINKS+=scandir.3 alphasort.3 \
scandir.3 scandirat.3 \
scandir.3 fscandir.3 \
scandir.3 fscandir_b.3 \
scandir.3 scandir_b.3 \
scandir.3 scandirat.3 \
scandir.3 scandirat_b.3 \
scandir.3 versionsort.3
MLINKS+=sem_open.3 sem_close.3 \
sem_open.3 sem_unlink.3

View file

@ -458,11 +458,14 @@ FBSD_1.8 {
aio_read2;
aio_write2;
execvpe;
fscandir;
fscandir_b;
fts_open_b;
glob_b;
psiginfo;
rtld_get_var;
rtld_set_var;
scandirat_b;
uexterr_gettext;
sig2str;
str2sig;

View file

@ -25,13 +25,16 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
.Dd August 31, 2023
.Dd June 19, 2025
.Dt SCANDIR 3
.Os
.Sh NAME
.Nm scandir ,
.Nm fscandir ,
.Nm scandirat ,
.Nm scandir_b ,
.Nm fscandir_b ,
.Nm fscandirat_b ,
.Nm alphasort ,
.Nm versionsort
.Nd scan a directory
@ -47,6 +50,13 @@
.Fa "int \*(lp*compar\*(rp\*(lpconst struct dirent **, const struct dirent **\*(rp"
.Fc
.Ft int
.Fo fscandir
.Fa "int dirfd"
.Fa "struct dirent ***namelist"
.Fa "int \*(lp*select\*(rp\*(lpconst struct dirent *\*(rp"
.Fa "int \*(lp*compar\*(rp\*(lpconst struct dirent **, const struct dirent **\*(rp"
.Fc
.Ft int
.Fo scandirat
.Fa "int dirfd"
.Fa "const char *dirname"
@ -62,6 +72,21 @@
.Fa "int \*(lp^compar\*(rp\*(lpconst struct dirent **, const struct dirent **\*(rp"
.Fc
.Ft int
.Fo fscandir_b
.Fa "int dirfd"
.Fa "struct dirent ***namelist"
.Fa "int \*(lp^select\*(rp\*(lpconst struct dirent *\*(rp"
.Fa "int \*(lp^compar\*(rp\*(lpconst struct dirent **, const struct dirent **\*(rp"
.Fc
.Ft int
.Fo scandirat_b
.Fa "int dirfd"
.Fa "const char *dirname"
.Fa "struct dirent ***namelist"
.Fa "int \*(lp^select\*(rp\*(lpconst struct dirent *\*(rp"
.Fa "int \*(lp^compar\*(rp\*(lpconst struct dirent **, const struct dirent **\*(rp"
.Fc
.Ft int
.Fn alphasort "const struct dirent **d1" "const struct dirent **d2"
.Ft int
.Fn versionsort "const struct dirent **d1" "const struct dirent **d2"
@ -118,6 +143,13 @@ The memory allocated for the array can be deallocated with
by freeing each pointer in the array and then the array itself.
.Pp
The
.Fn fscandir
function is similar to
.Fn scandir ,
but takes a file descriptor referencing a directory instead of a path.
The file descriptor is left open on return, regardless of outcome.
.Pp
The
.Fn scandirat
function is similar to
.Fn scandir ,
@ -151,17 +183,37 @@ See
for additional details.
.Pp
The
.Fn scandir_b
function behaves in the same way as
.Fn scandir_b ,
.Fn fscandir_b ,
and
.Fn scandirat_b
functions behave in the same way as
.Fn scandir ,
but takes blocks as arguments instead of function pointers and calls
.Fn fscandir ,
and
.Fn scandirat ,
respectively,
but take blocks as arguments instead of function pointers and call
.Fn qsort_b
rather than
.Fn qsort .
.Sh DIAGNOSTICS
Returns \-1 if the directory cannot be opened for reading or if
The
.Fn scandir ,
.Fn fscandir ,
.Fn scandirat ,
.Fn scandir_b ,
.Fn fscandir_b ,
and
.Fn scandirat_b
functions return the number of directory entries found on succes.
If the directory cannot be opened for reading, an error occurs
while reading the directory, or
.Xr malloc 3
cannot allocate enough memory to hold all the data structures.
cannot allocate enough memory to hold all the directory entries,
they return \-1 and set
.Va errno
to an appropriate value.
.Sh SEE ALSO
.Xr openat 2 ,
.Xr directory 3 ,
@ -172,8 +224,25 @@ cannot allocate enough memory to hold all the data structures.
.Xr dir 5
.Sh STANDARDS
The
.Fn alphasort
and
.Fn scandir
functions are expected to conform to
.St -p1003.1-2008 .
The
.Fn scandirat
and
.Fn versionsort
function is a GNU extension and conforms to no standard.
functions are GNU extensions and conform to no standard.
The
.Fn fscandir ,
.Fn scandir_b ,
.Fn fscandir_b ,
and
.Fn scandirat_b
functions are
.Fx
extensions.
.Sh HISTORY
The
.Fn scandir
@ -182,8 +251,19 @@ and
functions appeared in
.Bx 4.2 .
The
.Fn scandir_b
function was added in
.Fx 11.0 .
The
.Fn scandirat
and
.Fn versionsort
functions were added in
.Fx 13.2 .
The
.Fn fscandir ,
.Fn fscandir_b ,
and
.Fn scandirat_b
functions were added in
.Fx 15.0 .

View file

@ -38,6 +38,7 @@
#include "namespace.h"
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
@ -64,7 +65,7 @@ static int scandir_thunk_cmp(const void *p1, const void *p2, void *thunk);
static int
#ifdef I_AM_SCANDIR_B
scandir_b_dirp(DIR *dirp, struct dirent ***namelist, select_block select,
scandir_dirp_b(DIR *dirp, struct dirent ***namelist, select_block select,
dcomp_block dcomp)
#else
scandir_dirp(DIR *dirp, struct dirent ***namelist,
@ -72,14 +73,9 @@ scandir_dirp(DIR *dirp, struct dirent ***namelist,
const struct dirent **))
#endif
{
struct dirent *d, *p, **names = NULL;
size_t arraysz, numitems;
numitems = 0;
arraysz = 32; /* initial estimate of the array size */
names = (struct dirent **)malloc(arraysz * sizeof(struct dirent *));
if (names == NULL)
goto fail;
struct dirent *d, *p = NULL, **names = NULL, **names2;
size_t arraysz = 0, numitems = 0;
int serrno;
while ((d = readdir(dirp)) != NULL) {
if (select != NULL && !SELECT(d))
@ -87,33 +83,27 @@ scandir_dirp(DIR *dirp, struct dirent ***namelist,
/*
* Make a minimum size copy of the data
*/
p = (struct dirent *)malloc(_GENERIC_DIRSIZ(d));
p = malloc(_GENERIC_DIRSIZ(d));
if (p == NULL)
goto fail;
p->d_fileno = d->d_fileno;
p->d_type = d->d_type;
p->d_reclen = d->d_reclen;
p->d_namlen = d->d_namlen;
bcopy(d->d_name, p->d_name, p->d_namlen + 1);
memcpy(p->d_name, d->d_name, p->d_namlen + 1);
/*
* Check to make sure the array has space left and
* realloc the maximum size.
*/
if (numitems >= arraysz) {
struct dirent **names2;
names2 = reallocarray(names, arraysz,
2 * sizeof(struct dirent *));
if (names2 == NULL) {
free(p);
arraysz = arraysz ? arraysz * 2 : 32;
names2 = reallocarray(names, arraysz, sizeof(*names));
if (names2 == NULL)
goto fail;
}
names = names2;
arraysz *= 2;
}
names[numitems++] = p;
}
closedir(dirp);
if (numitems && dcomp != NULL)
#ifdef I_AM_SCANDIR_B
qsort_b(names, numitems, sizeof(struct dirent *), (void*)dcomp);
@ -125,10 +115,12 @@ scandir_dirp(DIR *dirp, struct dirent ***namelist,
return (numitems);
fail:
serrno = errno;
free(p);
while (numitems > 0)
free(names[--numitems]);
free(names);
closedir(dirp);
errno = serrno;
return (-1);
}
@ -143,39 +135,82 @@ scandir(const char *dirname, struct dirent ***namelist,
#endif
{
DIR *dirp;
int ret, serrno;
dirp = opendir(dirname);
if (dirp == NULL)
return (-1);
return (
ret =
#ifdef I_AM_SCANDIR_B
scandir_b_dirp
scandir_dirp_b
#else
scandir_dirp
#endif
(dirp, namelist, select, dcomp));
(dirp, namelist, select, dcomp);
serrno = errno;
closedir(dirp);
errno = serrno;
return (ret);
}
#ifndef I_AM_SCANDIR_B
int
#ifdef I_AM_SCANDIR_B
fscandir_b(int dirfd, struct dirent ***namelist, select_block select,
dcomp_block dcomp)
#else
fscandir(int dirfd, struct dirent ***namelist,
int (*select)(const struct dirent *), int (*dcomp)(const struct dirent **,
const struct dirent **))
#endif
{
DIR *dirp;
int ret, serrno;
dirp = fdopendir(dirfd);
if (dirp == NULL)
return (-1);
ret =
#ifdef I_AM_SCANDIR_B
scandir_dirp_b
#else
scandir_dirp
#endif
(dirp, namelist, select, dcomp);
serrno = errno;
fdclosedir(dirp);
errno = serrno;
return (ret);
}
int
#ifdef I_AM_SCANDIR_B
scandirat_b(int dirfd, const char *dirname, struct dirent ***namelist,
select_block select, dcomp_block dcomp)
#else
scandirat(int dirfd, const char *dirname, struct dirent ***namelist,
int (*select)(const struct dirent *), int (*dcomp)(const struct dirent **,
const struct dirent **))
#endif
{
DIR *dirp;
int fd;
int fd, ret, serrno;
fd = _openat(dirfd, dirname, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
if (fd == -1)
return (-1);
dirp = fdopendir(fd);
if (dirp == NULL) {
_close(fd);
return (-1);
}
return (scandir_dirp(dirp, namelist, select, dcomp));
ret =
#ifdef I_AM_SCANDIR_B
fscandir_b
#else
fscandir
#endif
(fd, namelist, select, dcomp);
serrno = errno;
_close(fd);
errno = serrno;
return (ret);
}
#ifndef I_AM_SCANDIR_B
/*
* Alphabetic order comparison routine for those who want it.
* POSIX 2008 requires that alphasort() uses strcoll().

View file

@ -22,6 +22,10 @@ ATF_TESTS_C+= makecontext_test
ATF_TESTS_C+= popen_test
ATF_TESTS_C+= posix_spawn_test
ATF_TESTS_C+= realpath2_test
ATF_TESTS_C+= scandir_test
.if ${COMPILER_FEATURES:Mblocks}
ATF_TESTS_C+= scandir_blocks_test
.endif
ATF_TESTS_C+= sig2str_test
ATF_TESTS_C+= sigsetops_test
ATF_TESTS_C+= wordexp_test
@ -101,7 +105,7 @@ TESTS_SUBDIRS= execve
TESTS_SUBDIRS+= posix_spawn
# Tests that require blocks support
.for t in fts_blocks_test glob_blocks_test
.for t in fts_blocks_test glob_blocks_test scandir_blocks_test
CFLAGS.${t}.c+= -fblocks
LIBADD.${t}+= BlocksRuntime
.endfor

View file

@ -0,0 +1,118 @@
/*-
* Copyright (c) 2025 Klara, Inc.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <sys/stat.h>
#include <dirent.h>
#include <fcntl.h>
#include <stdlib.h>
#include <atf-c.h>
static void
scandir_blocks_prepare(const struct atf_tc *tc)
{
ATF_REQUIRE_EQ(0, mkdir("dir", 0755));
ATF_REQUIRE_EQ(0, mkdir("dir/dir", 0755));
ATF_REQUIRE_EQ(0, close(creat("dir/file", 0644)));
ATF_REQUIRE_EQ(0, symlink("file", "dir/link"));
ATF_REQUIRE_EQ(0, mkdir("dir/skip", 0755));
}
static void
scandir_blocks_verify(const struct atf_tc *tc, int n, struct dirent **namelist)
{
ATF_REQUIRE_EQ_MSG(5, n, "return value is %d", n);
ATF_CHECK_STREQ("link", namelist[0]->d_name);
ATF_CHECK_STREQ("file", namelist[1]->d_name);
ATF_CHECK_STREQ("dir", namelist[2]->d_name);
ATF_CHECK_STREQ("..", namelist[3]->d_name);
ATF_CHECK_STREQ(".", namelist[4]->d_name);
}
ATF_TC(scandir_b_test);
ATF_TC_HEAD(scandir_b_test, tc)
{
atf_tc_set_md_var(tc, "descr", "Test scandir_b()");
}
ATF_TC_BODY(scandir_b_test, tc)
{
struct dirent **namelist = NULL;
int i, ret;
scandir_blocks_prepare(tc);
ret = scandir_b("dir", &namelist,
^(const struct dirent *ent) {
return (strcmp(ent->d_name, "skip") != 0);
},
^(const struct dirent **a, const struct dirent **b) {
return (strcmp((*b)->d_name, (*a)->d_name));
});
scandir_blocks_verify(tc, ret, namelist);
for (i = 0; i < ret; i++)
free(namelist[i]);
free(namelist);
}
ATF_TC(fscandir_b_test);
ATF_TC_HEAD(fscandir_b_test, tc)
{
atf_tc_set_md_var(tc, "descr", "Test fscandir_b()");
}
ATF_TC_BODY(fscandir_b_test, tc)
{
struct dirent **namelist = NULL;
int fd, i, ret;
scandir_blocks_prepare(tc);
ATF_REQUIRE((fd = open("dir", O_DIRECTORY | O_RDONLY)) >= 0);
ret = fscandir_b(fd, &namelist,
^(const struct dirent *ent) {
return (strcmp(ent->d_name, "skip") != 0);
},
^(const struct dirent **a, const struct dirent **b) {
return (strcmp((*b)->d_name, (*a)->d_name));
});
scandir_blocks_verify(tc, ret, namelist);
for (i = 0; i < ret; i++)
free(namelist[i]);
free(namelist);
ATF_REQUIRE_EQ(0, close(fd));
}
ATF_TC(scandirat_b_test);
ATF_TC_HEAD(scandirat_b_test, tc)
{
atf_tc_set_md_var(tc, "descr", "Test scandirat_b()");
}
ATF_TC_BODY(scandirat_b_test, tc)
{
struct dirent **namelist = NULL;
int fd, i, ret;
scandir_blocks_prepare(tc);
ATF_REQUIRE((fd = open("dir", O_DIRECTORY | O_SEARCH)) >= 0);
ret = scandirat_b(fd, ".", &namelist,
^(const struct dirent *ent) {
return (strcmp(ent->d_name, "skip") != 0);
},
^(const struct dirent **a, const struct dirent **b) {
return (strcmp((*b)->d_name, (*a)->d_name));
});
scandir_blocks_verify(tc, ret, namelist);
for (i = 0; i < ret; i++)
free(namelist[i]);
free(namelist);
ATF_REQUIRE_EQ(0, close(fd));
}
ATF_TP_ADD_TCS(tp)
{
ATF_TP_ADD_TC(tp, scandir_b_test);
ATF_TP_ADD_TC(tp, fscandir_b_test);
ATF_TP_ADD_TC(tp, scandirat_b_test);
return (atf_no_error());
}

View file

@ -0,0 +1,112 @@
/*-
* Copyright (c) 2025 Klara, Inc.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <sys/stat.h>
#include <dirent.h>
#include <fcntl.h>
#include <stdlib.h>
#include <atf-c.h>
static void
scandir_prepare(const struct atf_tc *tc)
{
ATF_REQUIRE_EQ(0, mkdir("dir", 0755));
ATF_REQUIRE_EQ(0, mkdir("dir/dir", 0755));
ATF_REQUIRE_EQ(0, close(creat("dir/file", 0644)));
ATF_REQUIRE_EQ(0, symlink("file", "dir/link"));
ATF_REQUIRE_EQ(0, mkdir("dir/skip", 0755));
}
static void
scandir_verify(const struct atf_tc *tc, int n, struct dirent **namelist)
{
ATF_REQUIRE_EQ_MSG(5, n, "return value is %d", n);
ATF_CHECK_STREQ("link", namelist[0]->d_name);
ATF_CHECK_STREQ("file", namelist[1]->d_name);
ATF_CHECK_STREQ("dir", namelist[2]->d_name);
ATF_CHECK_STREQ("..", namelist[3]->d_name);
ATF_CHECK_STREQ(".", namelist[4]->d_name);
}
static int
scandir_select(const struct dirent *ent)
{
return (strcmp(ent->d_name, "skip") != 0);
}
static int
scandir_compare(const struct dirent **a, const struct dirent **b)
{
return (strcmp((*b)->d_name, (*a)->d_name));
}
ATF_TC(scandir_test);
ATF_TC_HEAD(scandir_test, tc)
{
atf_tc_set_md_var(tc, "descr", "Test scandir()");
}
ATF_TC_BODY(scandir_test, tc)
{
struct dirent **namelist = NULL;
int i, ret;
scandir_prepare(tc);
ret = scandir("dir", &namelist, scandir_select, scandir_compare);
scandir_verify(tc, ret, namelist);
for (i = 0; i < ret; i++)
free(namelist[i]);
free(namelist);
}
ATF_TC(fscandir_test);
ATF_TC_HEAD(fscandir_test, tc)
{
atf_tc_set_md_var(tc, "descr", "Test fscandir()");
}
ATF_TC_BODY(fscandir_test, tc)
{
struct dirent **namelist = NULL;
int fd, i, ret;
scandir_prepare(tc);
ATF_REQUIRE((fd = open("dir", O_DIRECTORY | O_RDONLY)) >= 0);
ret = fscandir(fd, &namelist, scandir_select, scandir_compare);
scandir_verify(tc, ret, namelist);
for (i = 0; i < ret; i++)
free(namelist[i]);
free(namelist);
ATF_REQUIRE_EQ(0, close(fd));
}
ATF_TC(scandirat_test);
ATF_TC_HEAD(scandirat_test, tc)
{
atf_tc_set_md_var(tc, "descr", "Test scandirat()");
}
ATF_TC_BODY(scandirat_test, tc)
{
struct dirent **namelist = NULL;
int fd, i, ret;
scandir_prepare(tc);
ATF_REQUIRE((fd = open("dir", O_DIRECTORY | O_SEARCH)) >= 0);
ret = scandirat(fd, ".", &namelist, scandir_select, scandir_compare);
scandir_verify(tc, ret, namelist);
for (i = 0; i < ret; i++)
free(namelist[i]);
free(namelist);
ATF_REQUIRE_EQ(0, close(fd));
}
ATF_TP_ADD_TCS(tp)
{
ATF_TP_ADD_TC(tp, scandir_test);
ATF_TP_ADD_TC(tp, fscandir_test);
ATF_TP_ADD_TC(tp, scandirat_test);
return (atf_no_error());
}