diff options
author | Brian Gianforcaro <bgianf@serenityos.org> | 2021-05-02 02:36:59 -0700 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2021-05-02 13:33:41 +0200 |
commit | 331ab52318033e2385f77b0d92e173376a701127 (patch) | |
tree | 5f87f9fbde4ed36f1ac37ec350516194d0e7d664 | |
parent | d4d988532a07a49520f20d00bc431415e86cec50 (diff) | |
download | serenity-331ab52318033e2385f77b0d92e173376a701127.zip |
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.
-rw-r--r-- | Userland/Libraries/LibC/dirent.cpp | 62 | ||||
-rw-r--r-- | Userland/Libraries/LibC/dirent.h | 4 | ||||
-rw-r--r-- | Userland/Tests/LibC/CMakeLists.txt | 1 | ||||
-rw-r--r-- | Userland/Tests/LibC/TestLibCDirEnt.cpp | 28 |
4 files changed, 95 insertions, 0 deletions
diff --git a/Userland/Libraries/LibC/dirent.cpp b/Userland/Libraries/LibC/dirent.cpp index 41f86d91b5..7002be78b9 100644 --- a/Userland/Libraries/LibC/dirent.cpp +++ b/Userland/Libraries/LibC/dirent.cpp @@ -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; +} } diff --git a/Userland/Libraries/LibC/dirent.h b/Userland/Libraries/LibC/dirent.h index d468e33e6c..149dd122dc 100644 --- a/Userland/Libraries/LibC/dirent.h +++ b/Userland/Libraries/LibC/dirent.h @@ -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 diff --git a/Userland/Tests/LibC/CMakeLists.txt b/Userland/Tests/LibC/CMakeLists.txt index 0cc745d060..e7c5aa17d2 100644 --- a/Userland/Tests/LibC/CMakeLists.txt +++ b/Userland/Tests/LibC/CMakeLists.txt @@ -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") diff --git a/Userland/Tests/LibC/TestLibCDirEnt.cpp b/Userland/Tests/LibC/TestLibCDirEnt.cpp new file mode 100644 index 0000000000..b3c7a2e171 --- /dev/null +++ b/Userland/Tests/LibC/TestLibCDirEnt.cpp @@ -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); +} |