aboutsummaryrefslogtreecommitdiff
path: root/src/DirectoryWalker
diff options
context:
space:
mode:
Diffstat (limited to 'src/DirectoryWalker')
-rw-r--r--src/DirectoryWalker/DirectoryWalker.c249
-rw-r--r--src/DirectoryWalker/DirectoryWalker.h8
2 files changed, 257 insertions, 0 deletions
diff --git a/src/DirectoryWalker/DirectoryWalker.c b/src/DirectoryWalker/DirectoryWalker.c
new file mode 100644
index 0000000..dc3729a
--- /dev/null
+++ b/src/DirectoryWalker/DirectoryWalker.c
@@ -0,0 +1,249 @@
+#define _POSIX_C_SOURCE 200809L
+
+#include "DirectoryWalker/DirectoryWalker.h"
+
+#include "Path/Path.h"
+
+#include <dirent.h>
+#include <errno.h>
+#include <lauxlib.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#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");
+}
diff --git a/src/DirectoryWalker/DirectoryWalker.h b/src/DirectoryWalker/DirectoryWalker.h
new file mode 100644
index 0000000..94dcbf6
--- /dev/null
+++ b/src/DirectoryWalker/DirectoryWalker.h
@@ -0,0 +1,8 @@
+#ifndef YAPSSG_DIRECTORY_WALKER_H
+#define YAPSSG_DIRECTORY_WALKER_H
+
+#include <lua.h>
+
+void directory_walker_register(lua_State *lua, const char *input_path);
+
+#endif