Fixes and improvements (#617)

This commit is contained in:
Ken Van Hoeylandt
2026-08-20 17:22:01 +02:00
committed by GitHub
parent b03759a111
commit 0ff1627385
38 changed files with 309 additions and 342 deletions
@@ -12,6 +12,11 @@
#include <dirent.h>
#include <string>
#include <sys/stat.h>
#ifdef ESP_PLATFORM
#include <sys/unistd.h>
#else
#include <unistd.h>
#endif
#include <vector>
inline bool app_fs_is_directory(const std::string& path) {
@@ -36,6 +41,87 @@ inline bool app_fs_is_file(const std::string& path) {
// Appends the full path of every direct subdirectory of @a path to @a out.
// No-op (not an error) if @a path can't be opened.
inline bool app_fs_delete_recursively(const std::string& path) {
if (path.empty() || path == "/" || path == "." || path == "..") {
return true;
}
// Use lstat() so symbolic links are not followed: a symlink that points at
// an external directory must be removed as a leaf entry (unlink), not
// recursed into. app_fs_is_directory() uses stat() and would follow the
// link, potentially deleting files outside the target tree.
// ESP-IDF newlib has no lstat(); ESP32 filesystems (FAT/SPIFFS) don't
// support symlinks, so stat() is equivalent there.
struct stat st {};
FileMutex file_mutex;
file_mutex_get(&file_mutex, path.c_str());
file_mutex_lock(&file_mutex);
#ifdef ESP_PLATFORM
int rc = stat(path.c_str(), &st);
#else
int rc = lstat(path.c_str(), &st);
#endif
file_mutex_unlock(&file_mutex);
if (rc != 0) {
return false;
}
#ifndef ESP_PLATFORM
if (S_ISLNK(st.st_mode)) {
// Symlink — remove as a leaf regardless of its target.
file_mutex_lock(&file_mutex);
bool result = unlink(path.c_str()) == 0;
file_mutex_unlock(&file_mutex);
return result;
}
#endif
if (S_ISDIR(st.st_mode)) {
// Collect child names while locked, then release before recursing —
// child paths can resolve to the same mount mutex (see
// app_fs_list_direct_subdirectories comment), so holding the parent
// lock across the recursive call would self-deadlock.
std::vector<std::string> children;
file_mutex_lock(&file_mutex);
DIR* dir = opendir(path.c_str());
if (dir == nullptr) {
file_mutex_unlock(&file_mutex);
return false;
}
struct dirent* entry;
while ((entry = readdir(dir)) != nullptr) {
if (std::strcmp(entry->d_name, ".") == 0 || std::strcmp(entry->d_name, "..") == 0) {
continue;
}
children.push_back(path + "/" + entry->d_name);
}
closedir(dir);
file_mutex_unlock(&file_mutex);
bool success = true;
for (const auto& child : children) {
success = app_fs_delete_recursively(child);
if (!success) {
return false;
}
}
file_mutex_lock(&file_mutex);
bool result = rmdir(path.c_str()) == 0;
file_mutex_unlock(&file_mutex);
return result;
}
// Regular file or other — unlink.
file_mutex_lock(&file_mutex);
bool result = unlink(path.c_str()) == 0;
file_mutex_unlock(&file_mutex);
return result;
}
inline void app_fs_list_direct_subdirectories(const std::string& path, std::vector<std::string>& out) {
// Collect child names while the directory lock is held, then release it before classifying
// each one with app_fs_is_directory() - that function looks up and locks a FileMutex too,