/* SPDX-License-Identifier: MIT */ /* * konfs.h * * Copyright (c) 2026 KonAki * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ /* * folders and paths for the programs that browse them, on POSIX and on Windows. * paths are written with / everywhere, Windows takes it too. on Windows an empty path is the list of drives * * #define KONFS_IMPLEMENTATION in one .c file before including this to get the code. on POSIX that file has to be built with * _POSIX_C_SOURCE 200809L (src/impl/konfs.c does that) */ #ifndef KONFS_H #define KONFS_H #include #ifdef __cplusplus extern "C" { #endif /*** definition ***/ typedef struct kon_fsEntry { char *name; /* no folder part */ int isDir; long long size; } kon_fsEntry_t; typedef struct kon_fsList { kon_fsEntry_t *items; size_t count, cap; } kon_fsList_t; /*** declarations ***/ /* fills list with what is in dir: folders first, then files, each sorted by name. names starting with a dot, and hidden files on Windows, are left out. returns 0 when dir cannot be read, the list is empty then. free it with kon_fsFree either way */ int kon_fsRead(const char *dir, kon_fsList_t *list); void kon_fsFree(kon_fsList_t *list); int kon_fsIsDir(const char *path); int kon_fsIsFile(const char *path); /* 1 when the folder is made or was already there. the folder above has to exist */ int kon_fsMakeDir(const char *path); /* the current folder. returns 0 when it does not fit */ int kon_fsCwd(char *out, size_t size); /* dir/name, the / is only added when dir does not end with one. cut off when it does not fit */ void kon_fsJoin(char *out, size_t size, const char *dir, const char *name); /* takes the last part off path, in place. returns 0 when there is nothing to take off (the root, or "."). the parent of a drive on Windows is "" */ int kon_fsParent(char *path); /* the last part of path */ const char *kon_fsBaseName(const char *path); #ifdef __cplusplus } #endif #endif /* KONFS_H */ /*** implementation ***/ #ifdef KONFS_IMPLEMENTATION #include #include #include #include #ifdef _WIN32 #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include #else #include #include #include #include #endif #define KON_FS_PATH 1024 static char *kon_fsDup_(const char *s) { size_t n = strlen(s) + 1; char *out = malloc(n); if (out) memcpy(out, s, n); return out; } static int kon_fsPush_(kon_fsList_t *list, const char *name, int isDir, long long size) { kon_fsEntry_t *e; if (list->count == list->cap) { size_t cap = list->cap ? list->cap * 2 : 32; kon_fsEntry_t *items = realloc(list->items, cap * sizeof(kon_fsEntry_t)); if (!items) return 0; list->items = items; list->cap = cap; } e = &list->items[list->count]; e->name = kon_fsDup_(name); if (!e->name) return 0; e->isDir = isDir; e->size = size; list->count++; return 1; } static int kon_fsCompare_(const void *a, const void *b) { const kon_fsEntry_t *x = a, *y = b; const unsigned char *p, *q; if (x->isDir != y->isDir) return y->isDir - x->isDir; p = (const unsigned char *)x->name; q = (const unsigned char *)y->name; while (*p && *q) { int c = tolower(*p) - tolower(*q); if (c) return c; p++; q++; } if (*p != *q) return (int)*p - (int)*q; return strcmp(x->name, y->name); } void kon_fsFree(kon_fsList_t *list) { size_t i; for (i = 0; i < list->count; i++) free(list->items[i].name); free(list->items); list->items = NULL; list->count = list->cap = 0; } /* how much of the start of path is the root: / on POSIX, X:/ or X: on Windows. 0 for a relative path */ static size_t kon_fsRootLength_(const char *path) { #ifdef _WIN32 if (isalpha((unsigned char)path[0]) && path[1] == ':') return path[2] == '/' ? 3 : 2; #endif return path[0] == '/' ? 1 : 0; } int kon_fsParent(char *path) { size_t n = strlen(path), root = kon_fsRootLength_(path); if (n <= root) { #ifdef _WIN32 if (root && n) { path[0] = 0; return 1; } #endif return 0; } if (strcmp(path, ".") == 0) return 0; while (n > root && path[n - 1] == '/') n--; while (n > root && path[n - 1] != '/') n--; while (n > root && path[n - 1] == '/') n--; if (n == 0) { strcpy(path, "."); return 1; } path[n] = 0; return 1; } const char *kon_fsBaseName(const char *path) { const char *slash = strrchr(path, '/'); return slash ? slash + 1 : path; } void kon_fsJoin(char *out, size_t size, const char *dir, const char *name) { size_t n = strlen(dir); if (size == 0) return; if (n == 0) { snprintf(out, size, "%s", name); } else if (dir[n - 1] == '/') { snprintf(out, size, "%s%s", dir, name); } else { snprintf(out, size, "%s/%s", dir, name); } } #ifdef _WIN32 int kon_fsIsDir(const char *path) { DWORD a = GetFileAttributesA(path); return a != INVALID_FILE_ATTRIBUTES && (a & FILE_ATTRIBUTE_DIRECTORY) != 0; } int kon_fsIsFile(const char *path) { DWORD a = GetFileAttributesA(path); return a != INVALID_FILE_ATTRIBUTES && (a & FILE_ATTRIBUTE_DIRECTORY) == 0; } int kon_fsMakeDir(const char *path) { if (CreateDirectoryA(path, NULL)) return 1; return kon_fsIsDir(path); } int kon_fsCwd(char *out, size_t size) { DWORD n = GetCurrentDirectoryA((DWORD)(size > 0xFFFF ? 0xFFFF : size), out); DWORD i; if (n == 0 || (size_t)n >= size) return 0; for (i = 0; i < n; i++) { if (out[i] == '\\') out[i] = '/'; } return 1; } int kon_fsRead(const char *dir, kon_fsList_t *list) { memset(list, 0, sizeof(*list)); if (dir[0] == 0) { DWORD drives = GetLogicalDrives(); char name[4] = "A:/"; int i; for (i = 0; i < 26; i++) { if (!(drives & (1UL << i))) continue; name[0] = (char)('A' + i); if (!kon_fsPush_(list, name, 1, 0)) break; } return 1; } else { char pattern[KON_FS_PATH]; WIN32_FIND_DATAA data; HANDLE h; kon_fsJoin(pattern, sizeof(pattern), dir, "*"); h = FindFirstFileA(pattern, &data); if (h == INVALID_HANDLE_VALUE) return 0; do { const char *name = data.cFileName; long long size = ((long long)data.nFileSizeHigh << 32) | (long long)data.nFileSizeLow; if (name[0] == '.') continue; if (data.dwFileAttributes & (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM)) continue; if (!kon_fsPush_(list, name, (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0, size)) break; } while (FindNextFileA(h, &data)); FindClose(h); } if (list->count > 1) qsort(list->items, list->count, sizeof(kon_fsEntry_t), kon_fsCompare_); return 1; } #else int kon_fsIsDir(const char *path) { struct stat st; return stat(path, &st) == 0 && S_ISDIR(st.st_mode); } int kon_fsIsFile(const char *path) { struct stat st; return stat(path, &st) == 0 && S_ISREG(st.st_mode); } int kon_fsMakeDir(const char *path) { if (mkdir(path, 0777) == 0) return 1; return kon_fsIsDir(path); } int kon_fsCwd(char *out, size_t size) { return getcwd(out, size) != NULL; } int kon_fsRead(const char *dir, kon_fsList_t *list) { DIR *d; struct dirent *ent; char full[KON_FS_PATH]; memset(list, 0, sizeof(*list)); d = opendir(dir[0] ? dir : "."); if (!d) return 0; while ((ent = readdir(d)) != NULL) { struct stat st; if (ent->d_name[0] == '.') continue; kon_fsJoin(full, sizeof(full), dir[0] ? dir : ".", ent->d_name); if (strlen(full) >= sizeof(full) - 1) continue; if (stat(full, &st) != 0) continue; if (!kon_fsPush_(list, ent->d_name, S_ISDIR(st.st_mode) != 0, S_ISDIR(st.st_mode) ? 0 : (long long)st.st_size)) break; } closedir(d); if (list->count > 1) qsort(list->items, list->count, sizeof(kon_fsEntry_t), kon_fsCompare_); return 1; } #endif /* _WIN32 */ #endif /* KONFS_IMPLEMENTATION */