diff --git a/userspace/libraries/LibC/CMakeLists.txt b/userspace/libraries/LibC/CMakeLists.txt index 9f27cf45..33692905 100644 --- a/userspace/libraries/LibC/CMakeLists.txt +++ b/userspace/libraries/LibC/CMakeLists.txt @@ -13,6 +13,7 @@ set(LIBC_SOURCES fnmatch.cpp ftw.cpp getopt.cpp + glob.cpp grp.cpp ifaddrs.cpp inttypes.cpp diff --git a/userspace/libraries/LibC/glob.cpp b/userspace/libraries/LibC/glob.cpp new file mode 100644 index 00000000..1b72d698 --- /dev/null +++ b/userspace/libraries/LibC/glob.cpp @@ -0,0 +1,217 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +int do_glob(const char* __restrict path, const char* __restrict pattern, size_t pattern_index, int flags, int (*errfunc)(const char* epath, int eerrno), glob_t* __restrict pglob) +{ +#define DO_GLOB_RETURN(code) do { error_code = code; goto do_glob_error; } while (false) + int error_code = 0; + + size_t index_s = pattern_index; + size_t index_e = pattern_index; + size_t index = pattern_index; + + if (pattern[index] == '/') + index++; + + bool needs_fnmatch = false; + while (pattern[index]) + { + if (!(flags & GLOB_NOESCAPE) && pattern[index] == '\\') + { + index++; + if (pattern[index] == '\0') + break; + continue; + } + + if (pattern[index] == '/') + { + if (needs_fnmatch) + break; + index_e = index; + } + + if (pattern[index] == '?' || pattern[index] == '[' || pattern[index] == '*') + needs_fnmatch = true; + + index++; + } + + if (!needs_fnmatch) + index_e = index; + + const size_t path_buf_len = strlen(path) + (index_e - index_s) + 1; + char* path_buf = static_cast(malloc(path_buf_len + 1)); + + char* subpattern = static_cast(malloc(index + 1)); + + DIR* dirp = nullptr; + char* dent_path = nullptr; + const char* dir_path = path_buf; + + if (path_buf == nullptr || subpattern == nullptr) + DO_GLOB_RETURN(GLOB_NOSPACE); + + sprintf(path_buf, "%s%.*s", + path, + static_cast(index_e - index_s), + pattern + index_s + ); + + sprintf(subpattern, "%.*s", + static_cast(index), + pattern + ); + + if (!needs_fnmatch) + { + struct stat st; + if (stat(path_buf, &st) == -1) + { + if ((flags & GLOB_ERR) || (errfunc && errfunc(path_buf, errno))) + DO_GLOB_RETURN(GLOB_ABORTED); + DO_GLOB_RETURN(0); + } + + if ((flags & GLOB_MARK) && S_ISDIR(st.st_mode)) + strcat(path_buf, "/"); + + void* new_pathv = realloc(pglob->gl_pathv, sizeof(char*) * (pglob->gl_offs + pglob->gl_pathc + 2)); + if (new_pathv == nullptr) + DO_GLOB_RETURN(GLOB_NOSPACE); + pglob->gl_pathv = static_cast(new_pathv); + pglob->gl_pathv[pglob->gl_offs + pglob->gl_pathc] = path_buf; + pglob->gl_pathc++; + + free(subpattern); + + return 0; + } + + if (dir_path[0] == '\0') + dir_path = (pattern[0] == '/') ? "/" : "."; + if ((dirp = opendir(dir_path)) == nullptr) + { + if ((flags & GLOB_ERR) || (errfunc && errfunc(path_buf, errno))) + DO_GLOB_RETURN(GLOB_ABORTED); + DO_GLOB_RETURN(0); + } + + for (;;) + { + errno = 0; + dirent* dent = readdir(dirp); + if (dent == nullptr) + { + if ((flags & GLOB_ERR) || (errfunc && errfunc(path_buf, errno))) + DO_GLOB_RETURN(GLOB_ABORTED); + DO_GLOB_RETURN(0); + } + + if (strcmp(dent->d_name, ".") == 0 || strcmp(dent->d_name, "..") == 0) + continue; + + if ((dent_path = static_cast(malloc(path_buf_len + 1 + strlen(dent->d_name) + 1))) == nullptr) + DO_GLOB_RETURN(GLOB_NOSPACE); + + if (path_buf[0] || pattern[0] == '/') + sprintf(dent_path, "%s/%s", path_buf, dent->d_name); + else + strcpy(dent_path, dent->d_name); + + int fnmatch_flags = FNM_PATHNAME | FNM_PERIOD; + if (flags & GLOB_NOESCAPE) + fnmatch_flags |= FNM_NOESCAPE; + + if (fnmatch(subpattern, dent_path, fnmatch_flags) == 0) + if (const int result = do_glob(dent_path, pattern, index, flags, errfunc, pglob)) + DO_GLOB_RETURN(result); + + free(dent_path); + dent_path = nullptr; + } + +do_glob_error: + if (dirp) + closedir(dirp); + if (path_buf) + free(path_buf); + if (subpattern) + free(subpattern); + if (dent_path) + free(dent_path); + return error_code; +#undef DO_GLOB_ERROR +} + +int glob(const char* __restrict pattern, int flags, int (*errfunc)(const char* epath, int eerrno), glob_t* __restrict pglob) +{ + if (!(flags & GLOB_APPEND)) + { + pglob->gl_pathv = nullptr; + pglob->gl_pathc = 0; + + if (!(flags & GLOB_DOOFFS)) + pglob->gl_offs = 0; + + pglob->gl_pathv = static_cast(malloc(sizeof(char*) * (pglob->gl_offs + 1))); + if (pglob->gl_pathv == nullptr) + return GLOB_NOSPACE; + for (size_t i = 0; i < pglob->gl_offs; i++) + pglob->gl_pathv[i] = nullptr; + } + + const size_t start_off = pglob->gl_offs + pglob->gl_pathc; + + const int result = do_glob("", pattern, 0, flags, errfunc, pglob); + + const size_t end_off = pglob->gl_offs + pglob->gl_pathc; + + if (!(flags & GLOB_NOSORT) && end_off != start_off) + { + qsort( + pglob->gl_pathv + start_off, + end_off - start_off, + sizeof(char*), + [](const void* a, const void* b) -> int + { + const char* a_cstr = *static_cast(a); + const char* b_cstr = *static_cast(b); + return strcoll(a_cstr, b_cstr); + } + ); + } + + pglob->gl_pathv[pglob->gl_offs + pglob->gl_pathc] = nullptr; + + if (result != 0) + return result; + if (end_off != start_off || !(flags & GLOB_NOCHECK)) + return 0; + + void* new_pathv = realloc(pglob->gl_pathv, sizeof(char*) * (pglob->gl_offs + pglob->gl_pathc + 2)); + if (new_pathv == nullptr) + return GLOB_NOSPACE; + + pglob->gl_pathv = static_cast(new_pathv); + pglob->gl_pathv[pglob->gl_offs] = strdup(pattern); + if (pglob->gl_pathv[pglob->gl_offs] == nullptr) + return GLOB_NOSPACE; + pglob->gl_pathc = 1; + return 0; +} + +void globfree(glob_t* pglob) +{ + for (size_t i = 0; i < pglob->gl_pathc; i++) + free(pglob->gl_pathv[i + pglob->gl_offs]); + free(pglob->gl_pathv); + + pglob->gl_pathv = nullptr; +}