// flat.h - Directory traversal utility functions // // Thread safety: Not thread-safe. External synchronization required. // Memory: flat_directory_entries returns a NULL-terminated array of // heap-allocated strings. Use flat_free_entries to release both. // Portability: Provides POSIX (dirent.h) and Windows (FindFirstFileA) impls. #ifndef FLAT_H #define FLAT_H #include "type.h" #include #ifdef _WIN32 #include #include #define MKDIR(x) (mkdir(x)) #else #include #define MKDIR(x) (mkdir(x, 0755)) #endif char *flat_path(int, ...); void flat_free_entries(char **); i64 flat_directory_entry_count(char const *); char **flat_directory_entries(char const *); #endif // FLAT_H #ifdef FLAT_IMPLEMENTATION #include #include #include #ifndef _WIN32 // flat_path() // // Join path components into a single allocated string. Uses '/' separator // on POSIX, '\\' on Windows. Caller must free the result. // // s_n - Number of string components // // return - Allocated path string, or NULL on failure char *flat_path(int s_n, ...) { va_list list; size_t c_n = 0; if (s_n < 1) return NULL; va_start(list, s_n); for (int i = 0; i < s_n; i++) { const char *s = va_arg(list, char *); if (s) c_n += strlen(s); } va_end(list); size_t separators = (s_n > 1 ? (size_t)(s_n - 1) : 0); if (c_n > SIZE_MAX - separators - 1) return NULL; size_t total = c_n + separators + 1; char *r = malloc(total); if (r == NULL) return NULL; char *d = r; va_start(list, s_n); for (int i = 0; i < s_n; i++) { const char *s = va_arg(list, char *); // Add separator between components (not before the first). if (i > 0) *d++ = '/'; if (s) { while ((*d++ = *s++)) ; d--; // Step back over the null terminator. } } va_end(list); *d = '\0'; return r; } i64 flat_directory_entry_count(char const *path) { DIR *dir; struct dirent *ent; if (path == NULL) return 0; if (!(dir = opendir(path))) { return 0; } i64 n = 0; while ((ent = readdir(dir))) { if (!strcmp(ent->d_name, ".")) continue; if (!strcmp(ent->d_name, "..")) continue; n++; } closedir(dir); return n; } static int flat_skip_entry(struct dirent *ent) { if (!ent) return 1; if (!strcmp(ent->d_name, ".")) return 1; if (!strcmp(ent->d_name, "..")) return 1; return 0; } char **flat_directory_entries(char const *path) { DIR *dir; struct dirent *ent; if (path == NULL) return NULL; if (!(dir = opendir(path))) { return NULL; } i64 capacity = 16; char **entries = calloc((size_t)capacity, sizeof(char *)); if (!entries) { closedir(dir); return NULL; } i64 count = 0; while ((ent = readdir(dir))) { if (flat_skip_entry(ent)) continue; if (count >= capacity - 1) { if (capacity > INT64_MAX / 2) { flat_free_entries(entries); closedir(dir); return NULL; } i64 new_capacity = capacity * 2; if ((size_t)new_capacity > SIZE_MAX / sizeof(char *)) { flat_free_entries(entries); closedir(dir); return NULL; } char **new_entries = realloc(entries, (size_t)new_capacity * sizeof(char *)); if (!new_entries) { flat_free_entries(entries); closedir(dir); return NULL; } entries = new_entries; capacity = new_capacity; } size_t entry_n = strlen(ent->d_name); entries[count] = malloc(entry_n + 1); if (!entries[count]) { flat_free_entries(entries); closedir(dir); return NULL; } strcpy(entries[count], ent->d_name); count++; } entries[count] = NULL; closedir(dir); return entries; } void flat_free_entries(char **entries) { if (!entries) return; i64 i = 0; while (entries[i]) { free(entries[i]); i++; } free(entries); } #else /* _WIN32 */ #include #include static int flat_skip_entry_win(char const *name) { if (!name) return 1; if (name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) return 1; return 0; } char *flat_path(int s_n, ...) { va_list list; size_t c_n = 0; if (s_n < 1) return NULL; va_start(list, s_n); for (int i = 0; i < s_n; i++) { const char *s = va_arg(list, char *); if (s) c_n += strlen(s); } va_end(list); size_t separators = (s_n > 1 ? (size_t)(s_n - 1) : 0); if (c_n > SIZE_MAX - separators - 1) return NULL; size_t total = c_n + separators + 1; char *r = malloc(total); if (r == NULL) return NULL; char *d = r; va_start(list, s_n); for (int i = 0; i < s_n; i++) { const char *s = va_arg(list, char *); if (i > 0) *d++ = '\\'; if (s) { while ((*d++ = *s++)) ; d--; } } va_end(list); *d = '\0'; return r; } i64 flat_directory_entry_count(char const *path) { if (path == NULL) return 0; char pattern[4096]; if (strlen(path) > sizeof(pattern) - 3) return 0; sprintf(pattern, "%s\\*", path); WIN32_FIND_DATAA fd; HANDLE h = FindFirstFileA(pattern, &fd); if (h == INVALID_HANDLE_VALUE) return 0; i64 n = 0; do { if (!flat_skip_entry_win(fd.cFileName)) n++; } while (FindNextFileA(h, &fd)); FindClose(h); return n; } char **flat_directory_entries(char const *path) { if (path == NULL) return NULL; char pattern[4096]; if (strlen(path) > sizeof(pattern) - 3) return NULL; sprintf(pattern, "%s\\*", path); WIN32_FIND_DATAA fd; HANDLE h = FindFirstFileA(pattern, &fd); if (h == INVALID_HANDLE_VALUE) return NULL; i64 capacity = 16; char **entries = calloc((size_t)capacity, sizeof(char *)); if (!entries) { FindClose(h); return NULL; } i64 count = 0; do { if (flat_skip_entry_win(fd.cFileName)) continue; if (count >= capacity - 1) { if (capacity > INT64_MAX / 2) { flat_free_entries(entries); FindClose(h); return NULL; } i64 new_capacity = capacity * 2; if ((size_t)new_capacity > SIZE_MAX / sizeof(char *)) { flat_free_entries(entries); FindClose(h); return NULL; } char **new_entries = realloc(entries, (size_t)new_capacity * sizeof(char *)); if (!new_entries) { flat_free_entries(entries); FindClose(h); return NULL; } entries = new_entries; capacity = new_capacity; } size_t entry_n = strlen(fd.cFileName); entries[count] = malloc(entry_n + 1); if (!entries[count]) { flat_free_entries(entries); FindClose(h); return NULL; } strcpy(entries[count], fd.cFileName); count++; } while (FindNextFileA(h, &fd)); entries[count] = NULL; FindClose(h); return entries; } void flat_free_entries(char **entries) { if (!entries) return; i64 i = 0; while (entries[i]) { free(entries[i]); i++; } free(entries); } #endif /* _WIN32 */ #endif // FLAT_IMPLEMENTATION