LibC: Implement scandir(...) to enumerate directories.

I ran into a need for this when running  stress-ng against the system.
This change implements the full functionality of scandir, where it
accepts a selection callback, as well as a comparison callback.
These can be used to trim and sort the entries from the directory
that we are being asked to enumerate. A test was also included to
validate the new functionality.
This commit is contained in:
Brian Gianforcaro 2021-05-02 02:36:59 -07:00 committed by Andreas Kling
parent d4d988532a
commit 331ab52318
Notes: sideshowbarker 2024-07-18 18:47:14 +09:00
4 changed files with 95 additions and 0 deletions

View file

@ -5,7 +5,9 @@
*/
#include <AK/Assertions.h>
#include <AK/ScopeGuard.h>
#include <AK/StdLibExtras.h>
#include <AK/Vector.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
@ -184,4 +186,64 @@ int dirfd(DIR* dirp)
VERIFY(dirp);
return dirp->fd;
}
int scandir(const char* dir_name,
struct dirent*** namelist,
int (*select)(const struct dirent*),
int (*compare)(const struct dirent**, const struct dirent**))
{
auto dir = opendir(dir_name);
if (dir == nullptr)
return -1;
ScopeGuard guard = [&] {
closedir(dir);
};
Vector<struct dirent*> tmp_names;
ScopeGuard names_guard = [&] {
tmp_names.remove_all_matching([&](auto& entry) {
free(entry);
return true;
});
};
while (true) {
errno = 0;
auto entry = readdir(dir);
if (!entry)
break;
// Omit entries the caller chooses to ignore.
if (select && !select(entry))
continue;
auto entry_copy = (struct dirent*)malloc(entry->d_reclen);
if (!entry_copy)
break;
memcpy(entry_copy, entry, entry->d_reclen);
tmp_names.append(entry_copy);
}
// Propagate any errors encountered while accumulating back to the user.
if (errno) {
return -1;
}
// Sort the entries if the user provided a comparator.
if (compare) {
qsort(tmp_names.data(), tmp_names.size(), sizeof(struct dirent*), (int (*)(const void*, const void*))compare);
}
const int size = tmp_names.size();
auto names = (struct dirent**)malloc(size * sizeof(struct dirent*));
for (auto i = 0; i < size; i++) {
names[i] = tmp_names[i];
}
// Disable the scope guard which free's names on error.
tmp_names.clear();
*namelist = names;
return size;
}
}

View file

@ -55,4 +55,8 @@ struct dirent* readdir(DIR*);
int readdir_r(DIR*, struct dirent*, struct dirent**);
int dirfd(DIR*);
int scandir(const char* dirp, struct dirent*** namelist,
int (*filter)(const struct dirent*),
int (*compar)(const struct dirent**, const struct dirent**));
__END_DECLS

View file

@ -4,6 +4,7 @@ set(TEST_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/TestLibCTime.cpp
${CMAKE_CURRENT_SOURCE_DIR}/TestLibCMkTemp.cpp
${CMAKE_CURRENT_SOURCE_DIR}/TestLibCExec.cpp
${CMAKE_CURRENT_SOURCE_DIR}/TestLibCDirEnt.cpp
)
file(GLOB CMD_SOURCES CONFIGURE_DEPENDS "*.cpp")

View file

@ -0,0 +1,28 @@
/*
* Copyright (c) 2021, Brian Gianforcaro <bgianf@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibTest/TestCase.h>
#include <dirent.h>
#include <string.h>
TEST_CASE(scandir_basic_scenario)
{
struct dirent** namelist = nullptr;
auto entries = scandir("/etc", &namelist, nullptr, nullptr);
EXPECT(entries > 0);
EXPECT_NE(namelist, nullptr);
bool found_passwd = false;
for (auto i = 0; i < entries; i++) {
if (strcmp(namelist[i]->d_name, "passwd") == 0) {
found_passwd = true;
break;
}
free(namelist[i]);
}
EXPECT(found_passwd);
free(namelist);
}