mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-12-04 05:20:30 +00:00
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:
parent
d4d988532a
commit
331ab52318
Notes:
sideshowbarker
2024-07-18 18:47:14 +09:00
Author: https://github.com/bgianfo Commit: https://github.com/SerenityOS/serenity/commit/331ab523180 Pull-request: https://github.com/SerenityOS/serenity/pull/6810
4 changed files with 95 additions and 0 deletions
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
28
Userland/Tests/LibC/TestLibCDirEnt.cpp
Normal file
28
Userland/Tests/LibC/TestLibCDirEnt.cpp
Normal 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);
|
||||
}
|
Loading…
Reference in a new issue