From 8862cdf2e0893b2c68529beed4a29762a709c63c Mon Sep 17 00:00:00 2001 From: Max Wash Date: Wed, 12 Feb 2025 22:11:18 +0000 Subject: [PATCH] io: implement pre- and post-order directory traversal for windows --- io-test/tree.c | 32 ++++ io/include/blue/io/directory.h | 11 +- io/include/blue/io/path.h | 2 + io/sys/windows/directory.c | 325 ++++++++++++++++++++++++++++++++- io/sys/windows/path.c | 9 +- 5 files changed, 368 insertions(+), 11 deletions(-) create mode 100644 io-test/tree.c diff --git a/io-test/tree.c b/io-test/tree.c new file mode 100644 index 0000000..03e84ac --- /dev/null +++ b/io-test/tree.c @@ -0,0 +1,32 @@ +#include +#include + +#define NRAND_NUMBERS 12 +#define NRAND_BYTES 128 +#define NRAND_DOUBLES 8 + +int main(int argc, const char **argv) +{ + if (argc < 2) { + return -1; + } + + b_directory *dir = NULL; + b_path *path = b_path_create_from_cstr(argv[1]); + b_status status = b_directory_open(B_DIRECTORY_ROOT, path, &dir); + + if (!B_OK(status)) { + printf("cannot open %s\n", b_path_ptr(path)); + printf("%s\n", b_status_to_string(status)); + return -1; + } + + b_directory_iterator it = {0}; + b_directory_iterator_begin(dir, &it, B_DIRECTORY_ITERATE_PARENT_FIRST); + while (b_directory_iterator_is_valid(&it)) { + printf("%s\n", it.filename); + b_directory_iterator_next(&it); + } + + return 0; +} diff --git a/io/include/blue/io/directory.h b/io/include/blue/io/directory.h index 6021f07..1138a98 100644 --- a/io/include/blue/io/directory.h +++ b/io/include/blue/io/directory.h @@ -4,6 +4,7 @@ #include #include #include +#include #define B_DIRECTORY_ROOT ((b_directory *)NULL) @@ -11,8 +12,14 @@ typedef struct b_directory b_directory; struct z__b_directory_iterator; +typedef enum b_directory_iterator_flags { + B_DIRECTORY_ITERATE_PARENT_FIRST = 0x01u, + B_DIRECTORY_ITERATE_PARENT_LAST = 0x02u, +} b_directory_iterator_flags; + typedef struct b_directory_iterator { b_iterator _base; + b_directory_iterator_flags flags; char *filename; char *filepath; struct z__b_directory_iterator *_z; @@ -23,10 +30,10 @@ typedef struct b_directory_iterator { b_directory_iterator_is_valid(it); b_directory_iterator_next(it)) BLUE_API b_status b_directory_open( - b_directory *root, const char *path, b_directory **out); + b_directory *root, const b_path *path, b_directory **out); BLUE_API int b_directory_iterator_begin( - b_directory *directory, b_directory_iterator *it); + b_directory *directory, b_directory_iterator *it, b_directory_iterator_flags flags); BLUE_API bool b_directory_iterator_next(b_directory_iterator *it); BLUE_API b_status b_directory_iterator_erase(b_directory_iterator *it); BLUE_API bool b_directory_iterator_is_valid(const b_directory_iterator *it); diff --git a/io/include/blue/io/path.h b/io/include/blue/io/path.h index 03b6d2d..e0cd248 100644 --- a/io/include/blue/io/path.h +++ b/io/include/blue/io/path.h @@ -24,6 +24,8 @@ BLUE_API bool b_path_exists(const b_path *path); BLUE_API bool b_path_is_file(const b_path *path); BLUE_API bool b_path_is_directory(const b_path *path); +BLUE_API const char *b_path_ptr(const b_path *path); + BLUE_API b_path *b_path_retain(b_path *path); BLUE_API void b_path_release(b_path *path); diff --git a/io/sys/windows/directory.c b/io/sys/windows/directory.c index c9b491f..2fbb02e 100644 --- a/io/sys/windows/directory.c +++ b/io/sys/windows/directory.c @@ -1,28 +1,324 @@ +#define WIN32_LEAN_AND_MEAN + +#include #include +#include +#include struct b_directory { - char *abs_path; + struct b_object base; + HANDLE handle; + struct b_path *abs_path; }; struct z__b_directory_iterator { - int x; + b_queue state_stack; }; -enum b_status b_directory_open( - struct b_directory *root, const char *path, struct b_directory **out) +struct iteration_state { + const b_path *search_path; + b_queue_entry entry; + HANDLE search; + WIN32_FIND_DATAA data; + bool child_search_complete; +}; + +static void directory_release(struct b_object *obj); + +static struct b_object_type directory_type = { + .t_name = "corelib::directory", + .t_flags = B_OBJECT_FUNDAMENTAL, + .t_id = B_OBJECT_TYPE_PATH, + .t_instance_size = sizeof(struct b_directory), + .t_release = directory_release, +}; + +static enum b_status status_from_win32_error(int error, enum b_status default_value) { + switch (error) { + case ERROR_FILE_NOT_FOUND: + return B_ERR_NO_ENTRY; + default: + return default_value; + } +} + +enum b_status b_directory_open( + struct b_directory *root, const struct b_path *path, struct b_directory **out) +{ + enum b_status status = B_SUCCESS; + + const b_path *parts[] = { + root ? root->abs_path : NULL, + path, + }; + + b_path *new_path = b_path_join(parts, sizeof parts / sizeof *parts); + if (!new_path) { + return B_ERR_NO_MEMORY; + } + + HANDLE dir_handle = CreateFileA( + b_path_ptr(new_path), GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, INVALID_HANDLE_VALUE); + + if (dir_handle == INVALID_HANDLE_VALUE) { + status = status_from_win32_error(GetLastError(), B_ERR_IO_FAILURE); + + b_path_release(new_path); + + return status; + } + + BY_HANDLE_FILE_INFORMATION dir_info; + if (!GetFileInformationByHandle(dir_handle, &dir_info)) { + status = status_from_win32_error(GetLastError(), B_ERR_IO_FAILURE); + + CloseHandle(dir_handle); + b_path_release(new_path); + + return status; + } + + + if (!(dir_info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { + CloseHandle(dir_handle); + b_path_release(new_path); + return B_ERR_NOT_DIRECTORY; + } + + struct b_directory *dir + = (struct b_directory *)b_object_type_instantiate(&directory_type); + if (!path) { + CloseHandle(dir_handle); + b_path_release(new_path); + return B_ERR_NO_MEMORY; + } + + dir->abs_path = new_path; + dir->handle = dir_handle; + + *out = dir; + return B_SUCCESS; } -int b_directory_iterator_begin( - struct b_directory *directory, struct b_directory_iterator *it) +static struct iteration_state *get_iteration_state(struct z__b_directory_iterator *it) { + b_queue_entry *last = b_queue_last(&it->state_stack); + + if (!last) { + return NULL; + } + + return b_unbox(struct iteration_state, last, entry); +} + +static struct iteration_state *push_iteration_state(struct z__b_directory_iterator *it) +{ + struct iteration_state *state = malloc(sizeof *state); + if (!state) { + return NULL; + } + + memset(state, 0x0, sizeof *state); + + b_queue_push_back(&it->state_stack, &state->entry); + return state; +} + +static void pop_iteration_state(struct z__b_directory_iterator *it) +{ + struct iteration_state *state = get_iteration_state(it); + + if (!state) { + return; + } + + b_queue_pop_back(&it->state_stack); + FindClose(state->search); + free(state); +} + +static void cleanup_iterator(struct b_directory_iterator *it) +{ + while (!b_queue_empty(&it->_z->state_stack)) { + pop_iteration_state(it->_z); + } + + free(it->_z); + it->_z = NULL; +} + +static void update_iterator_data(struct b_directory_iterator *it) +{ + struct iteration_state *state = get_iteration_state(it->_z); + it->filename = state->data.cFileName; +} + +static bool move_into_directory(struct b_directory_iterator *it, const char *dir_name) +{ + struct iteration_state *state = get_iteration_state(it->_z); + + struct b_path *dir_name_p = b_path_create_from_cstr(dir_name); + struct b_path *wildcard = b_path_create_from_cstr("*"); + + const struct b_path *parts[] = { + state ? state->search_path : NULL, + dir_name_p, + }; + + struct b_path *dir_path + = b_path_join(parts, sizeof parts / sizeof *parts); + + parts[0] = dir_path; + parts[1] = wildcard; + + struct b_path *search_path + = b_path_join(parts, sizeof parts / sizeof *parts); + + state = push_iteration_state(it->_z); + state->search_path = dir_path; + state->search = FindFirstFileA(b_path_ptr(search_path), &state->data); + + b_path_release(search_path); + b_path_release(wildcard); + b_path_release(dir_name_p); + + if (state->search == INVALID_HANDLE_VALUE) { + pop_iteration_state(it->_z); + return false; + } + + while (!strcmp(state->data.cFileName, ".") + || !strcmp(state->data.cFileName, "..")) { + BOOL ok = FindNextFileA(state->search, &state->data); + + if (!ok) { + pop_iteration_state(it->_z); + return false; + } + } + + return true; +} + +static bool move_to_first_item(struct b_directory_iterator *it, const struct b_path *root_dir) +{ + bool has_results = move_into_directory(it, b_path_ptr(root_dir)); + + if (!has_results) { + return false; + } + + if (it->flags & B_DIRECTORY_ITERATE_PARENT_FIRST) { + return true; + } + + while (true) { + struct iteration_state *state = get_iteration_state(it->_z); + + if (!(state->data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { + break; + } + + has_results = move_into_directory(it, state->data.cFileName); + + if (!has_results) { + pop_iteration_state(it->_z); + break; + } + } + + return true; +} + +static bool move_to_next_item(struct b_directory_iterator *it) +{ + while (true) { + struct iteration_state *state = get_iteration_state(it->_z); + + if (!state->child_search_complete && (state->data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && (it->flags & B_DIRECTORY_ITERATE_PARENT_FIRST)) { + if (move_into_directory(it, state->data.cFileName)) { + return true; + } + } + + bool has_items = FindNextFileA(state->search, &state->data); + + if (!has_items) { + pop_iteration_state(it->_z); + state = get_iteration_state(it->_z); + + if (it->flags & B_DIRECTORY_ITERATE_PARENT_FIRST && state != NULL) { + state->child_search_complete = true; + continue; + } + + return state != NULL; + } + + state->child_search_complete = false; + + if (!(state->data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { + return true; + } + + if (it->flags & B_DIRECTORY_ITERATE_PARENT_FIRST) { + return true; + } + + while (true) { + state = get_iteration_state(it->_z); + has_items = move_into_directory(it, state->data.cFileName); + + if (!has_items) { + pop_iteration_state(it->_z); + return true; + } + + state = get_iteration_state(it->_z); + + if (!(state->data.dwFileAttributes + & FILE_ATTRIBUTE_DIRECTORY)) { + return true; + } + } + } + + return true; +} + +int b_directory_iterator_begin( + struct b_directory *directory, struct b_directory_iterator *it, enum b_directory_iterator_flags flags) +{ + if (b_directory_iterator_is_valid(it)) { + cleanup_iterator(it); + } + + it->flags = flags; + + struct z__b_directory_iterator *it_data = malloc(sizeof *it_data); + memset(it_data, 0x0, sizeof *it_data); + + it->_z = it_data; + move_to_first_item(it, directory->abs_path); + + update_iterator_data(it); + return 0; } bool b_directory_iterator_next(struct b_directory_iterator *it) { - return false; + if (!it->_z) { + return false; + } + + bool result = move_to_next_item(it); + update_iterator_data(it); + return result; } enum b_status b_directory_iterator_erase(struct b_directory_iterator *it) @@ -32,5 +328,18 @@ enum b_status b_directory_iterator_erase(struct b_directory_iterator *it) bool b_directory_iterator_is_valid(const struct b_directory_iterator *it) { - return false; + if (!it->_z) { + return false; + } + + if (!get_iteration_state(it->_z)) { + return false; + } + + return true; } + +static void directory_release(struct b_object *obj) +{ + +} \ No newline at end of file diff --git a/io/sys/windows/path.c b/io/sys/windows/path.c index 08e0e63..15a60cc 100644 --- a/io/sys/windows/path.c +++ b/io/sys/windows/path.c @@ -156,7 +156,9 @@ struct b_path *b_path_join( } for (size_t i = 0; i < nr_paths; i++) { - append_path(result, paths[i]); + if (paths[i]) { + append_path(result, paths[i]); + } } return result; @@ -213,6 +215,11 @@ bool b_path_is_directory(const struct b_path *path) } +const char *b_path_ptr(const struct b_path *path) +{ + return b_string_ptr(path->pathstr); +} + struct b_path *b_path_retain(struct b_path *path) { }