diff --git a/components/newlib/CMakeLists.txt b/components/newlib/CMakeLists.txt index a74220bb7b5..1027332cb3b 100644 --- a/components/newlib/CMakeLists.txt +++ b/components/newlib/CMakeLists.txt @@ -26,7 +26,9 @@ set(srcs "stdatomic.c" "time.c" "sysconf.c" - "realpath.c") + "realpath.c" + "scandir.c" +) set(include_dirs platform_include) if(CONFIG_SPIRAM_CACHE_WORKAROUND) diff --git a/components/newlib/platform_include/sys/dirent.h b/components/newlib/platform_include/sys/dirent.h index d3a40e48065..f3d13ab3be5 100644 --- a/components/newlib/platform_include/sys/dirent.h +++ b/components/newlib/platform_include/sys/dirent.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -55,6 +55,10 @@ void seekdir(DIR* pdir, long loc); void rewinddir(DIR* pdir); int closedir(DIR* pdir); int readdir_r(DIR* pdir, struct dirent* entry, struct dirent** out_dirent); +int scandir(const char *dirname, struct dirent ***out_dirlist, + int (*select_func)(const struct dirent *), + int (*cmp_func)(const struct dirent **, const struct dirent **)); +int alphasort(const struct dirent **d1, const struct dirent **d2); #ifdef __cplusplus } diff --git a/components/newlib/scandir.c b/components/newlib/scandir.c new file mode 100644 index 00000000000..cf5d85e7387 --- /dev/null +++ b/components/newlib/scandir.c @@ -0,0 +1,78 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include "esp_check.h" +#include "esp_log.h" + +static const char *TAG = "scandir"; + +int alphasort(const struct dirent **lhs, const struct dirent **rhs) +{ + return strcoll((*lhs)->d_name, (*rhs)->d_name); +} + +int scandir(const char *dirname, struct dirent ***out_dirlist, + int (*select_func)(const struct dirent *), + int (*cmp_func)(const struct dirent **, const struct dirent **)) +{ + DIR *dir_ptr = NULL; + struct dirent *entry; + size_t num_entries = 0; + size_t array_size = 8; /* initial estimate */ + struct dirent **entries = NULL; + int ret = -1; + + entries = malloc(array_size * sizeof(struct dirent *)); + ESP_RETURN_ON_FALSE(entries, -1, TAG, "Malloc failed for entries"); + + dir_ptr = opendir(dirname); + ESP_GOTO_ON_FALSE(dir_ptr, -1, out, TAG, "Failed to open directory: %s", dirname); + + while ((entry = readdir(dir_ptr)) != NULL) { + /* skip entries that don't match the filter function */ + if (select_func != NULL && !select_func(entry)) { + continue; + } + + struct dirent *entry_copy = malloc(sizeof(struct dirent)); + ESP_GOTO_ON_FALSE(entry_copy, -1, out, TAG, "Malloc failed for entry_copy"); + + *entry_copy = *entry; + entries[num_entries++] = entry_copy; + + /* grow the array size if it's full */ + if (num_entries >= array_size) { + array_size *= 2; + struct dirent **new_entries = realloc(entries, array_size * sizeof(struct dirent *)); + ESP_GOTO_ON_FALSE(new_entries, -1, out, TAG, "Realloc failed for entries"); + entries = new_entries; + } + } + + /* sort the entries if a comparison function is provided */ + if (num_entries && cmp_func) { + qsort(entries, num_entries, sizeof(struct dirent *), + (int (*)(const void *, const void *))cmp_func); + } + + *out_dirlist = entries; + ret = num_entries; + +out: + if (ret < 0) { + while (num_entries > 0) { + free(entries[--num_entries]); + } + free(entries); + } + if (dir_ptr) { + closedir(dir_ptr); + } + return ret; +} diff --git a/components/newlib/test_apps/newlib/main/test_misc.c b/components/newlib/test_apps/newlib/main/test_misc.c index ec8a69b3f9f..edbe5a1fa03 100644 --- a/components/newlib/test_apps/newlib/main/test_misc.c +++ b/components/newlib/test_apps/newlib/main/test_misc.c @@ -12,8 +12,11 @@ #include #include #include +#include +#include "sys/dirent.h" #include "unity.h" #include "esp_heap_caps.h" +#include "esp_vfs.h" TEST_CASE("misc - posix_memalign", "[newlib_misc]") @@ -91,3 +94,90 @@ TEST_CASE("misc - realpath", "[newlib_misc]") TEST_ASSERT_EQUAL_STRING("/abc/def", out_new); free(out_new); } + +static int s_select_calls = 0; + +typedef struct { + const char **filenames; + size_t num_files; + size_t current_index; +} scandir_test_dir_context_t; + +static DIR *scandir_test_opendir(void* ctx, const char* name) +{ + scandir_test_dir_context_t *dir_ctx = (scandir_test_dir_context_t *)ctx; + dir_ctx->current_index = 0; + static DIR dir = {}; + return &dir; +} + +static struct dirent *scandir_test_readdir(void* ctx, DIR* pdir) +{ + scandir_test_dir_context_t *dir_ctx = (scandir_test_dir_context_t *)ctx; + if (dir_ctx->current_index >= dir_ctx->num_files) { + return NULL; + } + + static struct dirent entry; + snprintf(entry.d_name, sizeof(entry.d_name), "%s", dir_ctx->filenames[dir_ctx->current_index]); + dir_ctx->current_index++; + return &entry; +} + +static int scandir_test_closedir(void* ctx, DIR* pdir) +{ + return 0; +} + +static int scandir_test_select(const struct dirent *entry) +{ + s_select_calls++; + return strstr(entry->d_name, "test") != NULL; +} + +TEST_CASE("file - scandir", "[newlib_misc]") +{ + const char *test_filenames[] = { + ".", + "..", + "test_file1.txt", + "file2.txt", + "test_file3.txt", + "test_file4.txt", + "test_file5.txt", + "test_file6.txt", + "test_file7.txt", + "test_file8.txt", + "test_file9.txt", + "test_file10.txt", + }; + size_t num_test_files = sizeof(test_filenames) / sizeof(test_filenames[0]); + + scandir_test_dir_context_t scandir_test_dir_ctx = { .filenames = test_filenames, .num_files = num_test_files }; + + const esp_vfs_t scandir_test_vfs = { + .flags = ESP_VFS_FLAG_CONTEXT_PTR, + .opendir_p = scandir_test_opendir, + .readdir_p = scandir_test_readdir, + .closedir_p = scandir_test_closedir + }; + + TEST_ESP_OK(esp_vfs_register("/data", &scandir_test_vfs, &scandir_test_dir_ctx)); + + struct dirent **namelist; + s_select_calls = 0; + int n = scandir("/data", &namelist, scandir_test_select, alphasort); + TEST_ASSERT_NOT_NULL(namelist); + TEST_ASSERT_GREATER_THAN(0, n); + + TEST_ASSERT_EQUAL(num_test_files, s_select_calls); + TEST_ASSERT_EQUAL(9, n); + + for (int i = 0; i < n; ++i) { + TEST_ASSERT_NOT_NULL(strstr(namelist[i]->d_name, "test")); + free(namelist[i]); + } + free(namelist); + + esp_vfs_unregister("/data"); +}