mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-06-12 02:30:30 +09: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: 331ab52318
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/Assertions.h>
|
||||||
|
#include <AK/ScopeGuard.h>
|
||||||
#include <AK/StdLibExtras.h>
|
#include <AK/StdLibExtras.h>
|
||||||
|
#include <AK/Vector.h>
|
||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
|
@ -184,4 +186,64 @@ int dirfd(DIR* dirp)
|
||||||
VERIFY(dirp);
|
VERIFY(dirp);
|
||||||
return dirp->fd;
|
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 readdir_r(DIR*, struct dirent*, struct dirent**);
|
||||||
int dirfd(DIR*);
|
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
|
__END_DECLS
|
||||||
|
|
|
@ -4,6 +4,7 @@ set(TEST_SOURCES
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/TestLibCTime.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/TestLibCTime.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/TestLibCMkTemp.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/TestLibCMkTemp.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/TestLibCExec.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/TestLibCExec.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/TestLibCDirEnt.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
file(GLOB CMD_SOURCES CONFIGURE_DEPENDS "*.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…
Add table
Add a link
Reference in a new issue