summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrian Gianforcaro <bgianf@serenityos.org>2021-05-02 02:36:59 -0700
committerAndreas Kling <kling@serenityos.org>2021-05-02 13:33:41 +0200
commit331ab52318033e2385f77b0d92e173376a701127 (patch)
tree5f87f9fbde4ed36f1ac37ec350516194d0e7d664
parentd4d988532a07a49520f20d00bc431415e86cec50 (diff)
downloadserenity-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.cpp62
-rw-r--r--Userland/Libraries/LibC/dirent.h4
-rw-r--r--Userland/Tests/LibC/CMakeLists.txt1
-rw-r--r--Userland/Tests/LibC/TestLibCDirEnt.cpp28
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);
+}