#define _POSIX_C_SOURCE 200809L #include "DirectoryWalker/DirectoryWalker.h" #include "Path/Path.h" #include #include #include #include #include #include #include #include #define WALKER_METATABLE "yapssg.directory_walker" typedef struct { DIR *directory; char *absolute_path; char *display_path; size_t depth; } WalkFrame; typedef struct { WalkFrame *frames; size_t length; size_t capacity; } DirectoryWalker; static void close_frame(WalkFrame *frame) { if (frame->directory != NULL) { closedir(frame->directory); } free(frame->absolute_path); free(frame->display_path); } static void clear_walker(DirectoryWalker *walker) { while (walker->length > 0) { close_frame(&walker->frames[--walker->length]); } free(walker->frames); walker->frames = NULL; walker->capacity = 0; } static int walker_gc(lua_State *lua) { DirectoryWalker *walker = lua_touserdata(lua, 1); clear_walker(walker); return 0; } static int grow_walker(DirectoryWalker *walker) { size_t larger_capacity; WalkFrame *larger_frames; if (walker->capacity > SIZE_MAX / (2 * sizeof(*walker->frames))) { errno = ENOMEM; return -1; } larger_capacity = walker->capacity == 0 ? 8 : walker->capacity * 2; larger_frames = realloc(walker->frames, larger_capacity * sizeof(*walker->frames)); if (larger_frames == NULL) { errno = ENOMEM; return -1; } walker->frames = larger_frames; walker->capacity = larger_capacity; return 0; } static int push_frame(DirectoryWalker *walker, char *absolute_path, char *display_path, size_t depth) { DIR *directory = opendir(absolute_path); WalkFrame *frame; if (directory == NULL) { return -1; } if (walker->length == walker->capacity && grow_walker(walker) != 0) { int error_number = errno; closedir(directory); errno = error_number; return -1; } frame = &walker->frames[walker->length++]; frame->directory = directory; frame->absolute_path = absolute_path; frame->display_path = display_path; frame->depth = depth; return 0; } static const char *entry_type(mode_t mode) { if (S_ISREG(mode)) { return "file"; } if (S_ISDIR(mode)) { return "directory"; } if (S_ISLNK(mode)) { return "symlink"; } return "other"; } static void push_entry(lua_State *lua, const char *path, const char *name, const struct stat *status, size_t depth) { lua_newtable(lua); lua_pushstring(lua, path); lua_setfield(lua, -2, "path"); lua_pushstring(lua, name); lua_setfield(lua, -2, "name"); lua_pushstring(lua, entry_type(status->st_mode)); lua_setfield(lua, -2, "type"); lua_pushnumber(lua, (lua_Number)depth); lua_setfield(lua, -2, "depth"); if (S_ISREG(status->st_mode)) { lua_pushnumber(lua, (lua_Number)status->st_size); lua_setfield(lua, -2, "size"); } } static int walker_next(lua_State *lua) { DirectoryWalker *walker = lua_touserdata(lua, lua_upvalueindex(1)); while (walker->length > 0) { WalkFrame *frame = &walker->frames[walker->length - 1]; struct dirent *entry; char *absolute_path; char *display_path; struct stat status; size_t depth; errno = 0; entry = readdir(frame->directory); if (entry == NULL) { int error_number = errno; close_frame(frame); --walker->length; if (error_number != 0) { clear_walker(walker); return luaL_error(lua, "cannot read directory while walking: %s", strerror(error_number)); } continue; } if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { continue; } absolute_path = path_join(frame->absolute_path, entry->d_name); display_path = path_join(frame->display_path, entry->d_name); if (absolute_path == NULL || display_path == NULL) { free(absolute_path); free(display_path); clear_walker(walker); return luaL_error(lua, "unable to allocate directory-walk path"); } if (lstat(absolute_path, &status) != 0) { int error_number = errno; free(absolute_path); free(display_path); clear_walker(walker); return luaL_error(lua, "cannot inspect directory entry: %s", strerror(error_number)); } depth = frame->depth + 1; push_entry(lua, display_path, entry->d_name, &status, depth); if (S_ISDIR(status.st_mode)) { if (push_frame(walker, absolute_path, display_path, depth) != 0) { int error_number = errno; free(absolute_path); free(display_path); clear_walker(walker); return luaL_error(lua, "cannot open directory while walking: %s", strerror(error_number)); } } else { free(absolute_path); free(display_path); } return 1; } clear_walker(walker); return 0; } static int walk(lua_State *lua) { const char *input_path = lua_tostring(lua, lua_upvalueindex(1)); const char *requested_path = luaL_checkstring(lua, 1); char *absolute_path = path_resolve_from_file(input_path, requested_path); char *display_path = path_join("", requested_path); DirectoryWalker *walker; if (absolute_path == NULL || display_path == NULL) { free(absolute_path); free(display_path); return luaL_error(lua, "unable to allocate directory-walk root"); } walker = lua_newuserdata(lua, sizeof(*walker)); memset(walker, 0, sizeof(*walker)); luaL_getmetatable(lua, WALKER_METATABLE); lua_setmetatable(lua, -2); if (push_frame(walker, absolute_path, display_path, 0) != 0) { int error_number = errno; free(absolute_path); free(display_path); return luaL_error(lua, "cannot open directory '%s': %s", requested_path, strerror(error_number)); } lua_pushcclosure(lua, walker_next, 1); return 1; } void directory_walker_register(lua_State *lua, const char *input_path) { if (luaL_newmetatable(lua, WALKER_METATABLE)) { lua_pushcfunction(lua, walker_gc); lua_setfield(lua, -2, "__gc"); } lua_pop(lua, 1); lua_newtable(lua); lua_pushstring(lua, input_path); lua_pushcclosure(lua, walk, 1); lua_setfield(lua, -2, "walk"); lua_setglobal(lua, "fs"); }