Date: Fri, 20 Jun 2025 11:10:50 GMT From: Dag-Erling =?utf-8?Q?Sm=C3=B8rgrav?= <des@FreeBSD.org> To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org Subject: git: deeebfdecab5 - main - libc: Add fscandir(), fscandir_b(), scandirat_b(). Message-ID: <202506201110.55KBAo1Q051229@gitrepo.freebsd.org>
next in thread | raw e-mail | index | archive | help
The branch main has been updated by des: URL: https://cgit.FreeBSD.org/src/commit/?id=deeebfdecab56729fa898271ae53d01c8e156302 commit deeebfdecab56729fa898271ae53d01c8e156302 Author: Dag-Erling Smørgrav <des@FreeBSD.org> AuthorDate: 2025-06-20 11:10:23 +0000 Commit: Dag-Erling Smørgrav <des@FreeBSD.org> CommitDate: 2025-06-20 11:10:23 +0000 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 --- include/dirent.h | 13 ++++ lib/libc/gen/Makefile.inc | 5 +- lib/libc/gen/Symbol.map | 3 + lib/libc/gen/scandir.3 | 94 ++++++++++++++++++++++-- lib/libc/gen/scandir.c | 101 +++++++++++++++++--------- lib/libc/tests/gen/Makefile | 6 +- lib/libc/tests/gen/scandir_blocks_test.c | 118 +++++++++++++++++++++++++++++++ lib/libc/tests/gen/scandir_test.c | 112 +++++++++++++++++++++++++++++ 8 files changed, 410 insertions(+), 42 deletions(-) diff --git a/include/dirent.h b/include/dirent.h index 460be40c1064..00319c0a8bd0 100644 --- a/include/dirent.h +++ b/include/dirent.h @@ -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); diff --git a/lib/libc/gen/Makefile.inc b/lib/libc/gen/Makefile.inc index 1ab3b026ac07..f2f5afbb24d8 100644 --- a/lib/libc/gen/Makefile.inc +++ b/lib/libc/gen/Makefile.inc @@ -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 diff --git a/lib/libc/gen/Symbol.map b/lib/libc/gen/Symbol.map index 765db07019b5..e7483d3e6ec2 100644 --- a/lib/libc/gen/Symbol.map +++ b/lib/libc/gen/Symbol.map @@ -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; diff --git a/lib/libc/gen/scandir.3 b/lib/libc/gen/scandir.3 index 6656842c251f..f74bd1f23613 100644 --- a/lib/libc/gen/scandir.3 +++ b/lib/libc/gen/scandir.3 @@ -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 . diff --git a/lib/libc/gen/scandir.c b/lib/libc/gen/scandir.c index f59f57047278..172937392ddc 100644 --- a/lib/libc/gen/scandir.c +++ b/lib/libc/gen/scandir.c @@ -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 -scandirat(int dirfd, const char *dirname, struct dirent ***namelist, +#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 fd; + 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 +{ + 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(). diff --git a/lib/libc/tests/gen/Makefile b/lib/libc/tests/gen/Makefile index 4776dc4c774d..b7df4b1d037b 100644 --- a/lib/libc/tests/gen/Makefile +++ b/lib/libc/tests/gen/Makefile @@ -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 diff --git a/lib/libc/tests/gen/scandir_blocks_test.c b/lib/libc/tests/gen/scandir_blocks_test.c new file mode 100644 index 000000000000..28aeef4e7d4c --- /dev/null +++ b/lib/libc/tests/gen/scandir_blocks_test.c @@ -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()); +} diff --git a/lib/libc/tests/gen/scandir_test.c b/lib/libc/tests/gen/scandir_test.c new file mode 100644 index 000000000000..54848c0572ca --- /dev/null +++ b/lib/libc/tests/gen/scandir_test.c @@ -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()); +}
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?202506201110.55KBAo1Q051229>