From 2e587973eb105ee2d0eaa5cf9c980364af94c330 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20H=C3=BC=C3=9Fler?= Date: Fri, 22 Jul 2022 09:23:50 +0200 Subject: [PATCH] sys/vfs_util: add recursive unlink --- sys/include/vfs_util.h | 27 ++++++++ sys/vfs_util/vfs_util.c | 137 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 164 insertions(+) diff --git a/sys/include/vfs_util.h b/sys/include/vfs_util.h index a6ae1ec911..f2e11b95d0 100644 --- a/sys/include/vfs_util.h +++ b/sys/include/vfs_util.h @@ -101,6 +101,33 @@ int vfs_file_sha256(const char* file, void *digest, void *work_buf, size_t work_buf_len); #endif +/** + * @brief Checks if @p path is a file or a directory. + * + * This function uses @ref vfs_stat(), so if you need @ref vfs_stat() anyway, + * you should not do double work and check it yourself. + * + * @param[in] path Path to check + * + * @return < 0 on FS error + * @return 0 if @p path is a file + * @return > 0 if @p path is a directory + */ +int vfs_is_dir(const char *path); + +/** + * @brief Behaves like `rm -r @p root`. + * + * @param[in] root FS root directory to be deleted + * @param[in] path_buf Buffer that must be able to store the longest path + * of the file or directory being deleted + * @param[in] max_size Size of @p path_buf + * + * @return < 0 on error + * @return 0 + */ +int vfs_unlink_recursive(const char *root, char *path_buf, size_t max_size); + #ifdef __cplusplus } #endif diff --git a/sys/vfs_util/vfs_util.c b/sys/vfs_util/vfs_util.c index adf7e7f13f..be92d0d66e 100644 --- a/sys/vfs_util/vfs_util.c +++ b/sys/vfs_util/vfs_util.c @@ -154,3 +154,140 @@ int vfs_file_sha256(const char* file, void *digest, } #endif /* MODULE_HASHES */ + +int vfs_is_dir(const char *path) +{ + assert(path); + + int err; + struct stat stat; + if (*path != '/') { + /* only accept absolute paths */ + return -EINVAL; + } + if ((err = vfs_stat(path, &stat)) < 0) { + return err; + } + return ((stat.st_mode & S_IFMT) == S_IFDIR); +} + +/** + * @brief Removes additional "/" slashes from @p path + * + * @param[in] path Path to be prepared + */ +static void _vfs_prepare_path(char *path) +{ + assert(path); + assert(*path == '/'); + + int path_len = strlen(path); + char *p_write = path; /* end of so far constructed path */ + int len = 0; + const char *p_read = p_write; /* segment to be appended to the path */ + while (p_read < path + path_len) { + len = 0; + while (*p_read && *p_read == '/') { + p_read++; /* skip slashes */ + } + while (p_read[len] && p_read[len] != '/') { + len++; /* length of segment to be copied */ + } + if (*p_read && p_write + len + 1 <= path + path_len) { + memmove(p_write + 1, p_read, len); + p_write = p_write + len + 1; /* advance write pointer by segment length + 1 */ + *p_write = p_read[len]; /* either '\0' or '/' */ + } + p_read += len; /* advance read pointer by segment length */ + } + if (*p_write) { + *++p_write = '\0'; + } +} + +int vfs_unlink_recursive(const char *root, char *path_buf, size_t max_size) +{ + assert(root); + assert(path_buf); + + /* This function works like a Depth-first search (DFS). + First, we go as deep as we can into a directory and delete contained files. + Then we delete the now empty directory and go to the parent directory + and repeat the process. */ + int err; + if (*root != '/' || !strcmp(root, "/")) { + /* only accept absolute paths and not the FS root */ + return -EINVAL; + } + if (strlen(root) >= max_size) { + return -ENOBUFS; + } + strcpy(path_buf, root); + _vfs_prepare_path(path_buf); + if (path_buf[strlen(path_buf) - 1] != '/') { + if ((err = vfs_is_dir(path_buf)) < 0) { + return err; /* early unexpected error */ + } + else if (!err) { + /* just a file */ + return vfs_unlink(path_buf); + } + strcat(path_buf, "/"); + } + vfs_DIR dir; + vfs_dirent_t entry; + char seg[VFS_NAME_MAX + 1] = {0}; /* + 1 to append a '/' */ + size_t seg_len, root_len, fin = strlen(path_buf); + while ((root_len = strlen(path_buf)) >= fin) { + strcat(path_buf, seg); + *seg = '\0'; + if ((err = vfs_opendir(&dir, path_buf)) < 0) { /* this works with a trailing '/' */ + return err; + } + while (vfs_readdir(&dir, &entry) > 0) { + if (!strcmp(entry.d_name, "..")) { + continue; + } + seg_len = strlen(entry.d_name); + root_len = strlen(path_buf); + if (root_len + seg_len >= max_size) { + vfs_closedir(&dir); + return -ENOBUFS; + } + strcat(path_buf, entry.d_name); + if ((err = vfs_is_dir(path_buf)) < 0) { + /* error */ + vfs_closedir(&dir); + return err; + } + else if (err) { + /* is dir */ + if (*seg == '\0') { + strcat(seg, entry.d_name); + strcat(seg, "/"); + } + } + else { + /* is file */ + if ((err = vfs_unlink(path_buf)) < 0) { + vfs_closedir(&dir); + return err; + } + } + path_buf[root_len] = '\0'; + } + vfs_closedir(&dir); + if (*seg == '\0') { + /* no files and no subdirectory */ + if ((err = vfs_rmdir(path_buf)) < 0) { + return err; + } + /* go one segment up */ + char *end = &path_buf[strlen(path_buf) - 1]; + assert(*end == '/'); + while (*--end != '/') { } + *++end = '\0'; + } + } + return 0; +}